From e059dd82c25a84c704229b1a247c85307fef6cc0 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 16 Sep 2025 10:42:42 -0700 Subject: [PATCH 001/335] Tomo base plotting fixes --- src/quantem/tomography/tomography_base.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/quantem/tomography/tomography_base.py b/src/quantem/tomography/tomography_base.py index 0d937020..35c69b34 100644 --- a/src/quantem/tomography/tomography_base.py +++ b/src/quantem/tomography/tomography_base.py @@ -4,6 +4,7 @@ from numpy.typing import NDArray from torch._tensor import Tensor from tqdm.auto import tqdm +from typing import Tuple from quantem.core.datastructures.dataset3d import Dataset3d from quantem.core.io.serialize import AutoSerialize @@ -387,6 +388,7 @@ def plot_projections( fft: bool = False, norm: str = "log_auto", figax: tuple[plt.Figure, plt.Axes] | None = None, + fft_vmax: Tuple[float, float] = (0, 40), **kwargs, ): """ @@ -427,13 +429,15 @@ def plot_projections( if fft: fig, ax = plt.subplots(ncols=3, figsize=(25, 8)) - + print(fft_vmax) show_2d( np.abs(np.fft.fftshift(np.fft.fftn(volume_obj_np.sum(axis=0)))), figax=(fig, ax[0]), cmap=cmap, title="Y-X Projection FFT", - norm=norm, + # norm=norm, + vmin = fft_vmax[0], + vmax = fft_vmax[1], ) show_2d( @@ -441,14 +445,18 @@ def plot_projections( figax=(fig, ax[1]), cmap=cmap, title="Z-X Projection FFT", - norm=norm, + vmin = fft_vmax[0], + vmax = fft_vmax[1], + # norm=norm, ) show_2d( np.abs(np.fft.fftshift(np.fft.fftn(volume_obj_np.sum(axis=2)))), figax=(fig, ax[2]), cmap=cmap, title="Z-Y Projection FFT", - norm=norm, + vmin = fft_vmax[0], + vmax = fft_vmax[1], + # norm=norm, ) def plot_slice( From 993ba862451803983bc2eda2f23bb26e44030d6e Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 16 Sep 2025 22:13:23 -0700 Subject: [PATCH 002/335] Semi-working NeRF implementation; bug where it's not converging correctly --- src/quantem/tomography/models.py | 59 ++ src/quantem/tomography/tomography_dataset.py | 64 +- src/quantem/tomography/tomography_nerf.py | 662 +++++++++++++++++++ 3 files changed, 778 insertions(+), 7 deletions(-) create mode 100644 src/quantem/tomography/models.py create mode 100644 src/quantem/tomography/tomography_nerf.py diff --git a/src/quantem/tomography/models.py b/src/quantem/tomography/models.py new file mode 100644 index 00000000..0829712e --- /dev/null +++ b/src/quantem/tomography/models.py @@ -0,0 +1,59 @@ +import torch +from torch import nn +import numpy as np +import math +from torch.nn import init + +class SineLayer(nn.Module): + def __init__(self, in_features, out_features, bias=True, is_first=False, omega_0=30, hsiren=False, alpha=1.0): + super().__init__() + self.omega_0 = omega_0 + self.is_first = is_first + self.hsiren = hsiren + self.in_features = in_features + self.alpha = alpha + self.linear = nn.Linear(in_features, out_features, bias=bias) + self.init_weights() + + def init_weights(self): + with torch.no_grad(): + if self.is_first: + # Scale the first layer initialization by alpha + self.linear.weight.uniform_(-self.alpha / self.in_features, + self.alpha / self.in_features) + else: + # Scale the hidden layer initialization by alpha + self.linear.weight.uniform_(-self.alpha * np.sqrt(6 / self.in_features) / self.omega_0, + self.alpha * np.sqrt(6 / self.in_features) / self.omega_0) + + def forward(self, input): + if self.is_first and self.hsiren: + out = torch.sin(self.omega_0 * torch.sinh(2*self.linear(input))) + else: + out = torch.sin(self.omega_0 * self.linear(input)) + return out + +class HSiren(nn.Module): + def __init__(self, in_features=2, out_features=3, hidden_layers=3, hidden_features=256, + first_omega_0=30, hidden_omega_0=30, alpha=1.0): + super().__init__() + self.net = [] + self.net.append(SineLayer(in_features, hidden_features, is_first=True, + omega_0=first_omega_0, hsiren=True, alpha=alpha)) + + for i in range(hidden_layers): + self.net.append(SineLayer(hidden_features, hidden_features, is_first=False, + omega_0=hidden_omega_0, alpha=alpha)) + + final_linear = nn.Linear(hidden_features, out_features) + with torch.no_grad(): + # Final layer keeps original initialization (no alpha scaling) + final_linear.weight.uniform_(-np.sqrt(6 / hidden_features) / hidden_omega_0, + np.sqrt(6 / hidden_features) / hidden_omega_0) + self.net.append(final_linear) + self.net.append(nn.Softplus()) + self.net = nn.Sequential(*self.net) + + def forward(self, coords): + output = self.net(coords) + return output diff --git a/src/quantem/tomography/tomography_dataset.py b/src/quantem/tomography/tomography_dataset.py index 816259b6..967fb0f0 100644 --- a/src/quantem/tomography/tomography_dataset.py +++ b/src/quantem/tomography/tomography_dataset.py @@ -9,8 +9,10 @@ validate_tensor, ) +from torch.utils.data import Dataset -class TomographyDataset(AutoSerialize): + +class TomographyDataset(AutoSerialize, Dataset): _token = object() """ @@ -39,6 +41,16 @@ def __init__( self._initial_z1_angles = z1_angles.clone() self._initial_z3_angles = z3_angles.clone() self._initial_shifts = shifts.clone() + + # Move everything to CPU + self.to("cpu") + + # Number of indices (?) + + + # Enforce normalization of tilt series + tilt_percentile = torch.quantile(self._tilt_series, .95) + self._tilt_series = self._tilt_series / tilt_percentile # --- Class Methods --- @classmethod @@ -49,12 +61,6 @@ def from_data( z1_angles: NDArray | Tensor | None = None, z3_angles: NDArray | Tensor | None = None, shifts: NDArray | Tensor | None = None, - # name: str | None = None, - # origin: NDArray | tuple | list | float | int | None = None, - # sampling: NDArray | tuple | list | float | int | None = None, - # units: list[str] | tuple | list | None = None, - # signal_units: str = "arb. units", - # _token: object | None = None, ): """ tilt_series: (N, H, W) @@ -145,6 +151,50 @@ def initial_z3_angles(self) -> Tensor: @property def initial_shifts(self) -> Tensor: return self._initial_shifts + + @property + def num_projections(self) -> int: + + return self._tilt_series.shape[0] + + @property + def num_pixels(self) -> int: + + return self._tilt_series.shape[0] * self._tilt_series.shape[1] * self._tilt_series.shape[2] + + @property + def dims(self) -> tuple[int, int, int]: + + return self._tilt_series.shape[0], self._tilt_series.shape[1], self._tilt_series.shape[2] + + def __len__(self) -> int: + + return self.num_pixels + + def __getitem__(self, idx): + + actual_idx = idx + + projection_idx = actual_idx // (self.dims[1] * self.dims[2]) + remaining = actual_idx % (self.dims[1] * self.dims[2]) + + # TODO: What if non-square tilt images? + if self.dims[1] != self.dims[2]: + raise NotImplementedError("Non-square tilt images are not supported yet.") + + pixel_i = remaining // self.dims[1] + pixel_j = remaining % self.dims[1] + + target_value = self._tilt_series[projection_idx, pixel_i, pixel_j] + phi = self._tilt_angles[projection_idx] + + return { + 'projection_idx': projection_idx, + 'pixel_i': pixel_i, + 'pixel_j': pixel_j, + 'target_value': target_value, + 'phi': phi + } # --- Setters --- diff --git a/src/quantem/tomography/tomography_nerf.py b/src/quantem/tomography/tomography_nerf.py new file mode 100644 index 00000000..e83d8e88 --- /dev/null +++ b/src/quantem/tomography/tomography_nerf.py @@ -0,0 +1,662 @@ +from torch.utils.data import Dataset, DataLoader, DistributedSampler +import torch +import torch.distributed as dist +import numpy as np + +from quantem.tomography.tomography_dataset import TomographyDataset +from quantem.tomography.models import HSiren + +from torch.utils.tensorboard import SummaryWriter +import matplotlib.pyplot as plt + + +import os + +def get_accumulation_steps(epoch): + """Increase accumulation steps at specific epochs.""" + # schedule = config.get("accumulation_schedule", { + # 0: 1, + # 200: 4, + # 500: 28, + # }) + + schedule = { + 0: 1, + 200: 4, + 500: 28, + } + + accumulation_steps = 1 + for epoch_threshold, steps in sorted(schedule.items()): + if epoch >= epoch_threshold: + accumulation_steps = steps + + return accumulation_steps + +def get_num_samples_per_ray(epoch): + """Increase number of samples per ray at specific epochs.""" + # schedule = config.get("num_samples_schedule", { + # 0: 64, + # 200: 128, + # 500: 256, + # }) + + # TODO: Maybe delete + schedule = { + 0: 25, + 2: 50, + 4: 100, + 6: 200, + } + + num_samples = 64 + for epoch_threshold, samples in sorted(schedule.items()): + if epoch >= epoch_threshold: + num_samples = samples + + return num_samples + +class AuxiliaryParams(torch.nn.Module): + def __init__(self, num_tilts, device, zero_tilt_idx=None): + super().__init__() + + if zero_tilt_idx is None: + # If not provided, assume first projection is reference + zero_tilt_idx = 0 + + self.zero_tilt_idx = zero_tilt_idx + self.num_tilts = num_tilts + + # Shifts: only parameterize non-reference tilts + num_param_tilts = num_tilts - 1 + self.shifts_param = torch.nn.Parameter(torch.zeros(num_param_tilts, 2, device=device)) + + # Fixed zero shifts for reference + self.shifts_ref = torch.zeros(1, 2, device=device) + + # Z1 and Z3: parameterize all tilts EXCEPT the reference + self.z1_param = torch.nn.Parameter(torch.zeros(num_param_tilts, device=device)) + # self.z3_param = torch.nn.Parameter(torch.zeros(num_param_tilts, device=device)) + + # Fixed zeros for reference tilt + self.z1_ref = torch.zeros(1, device=device) + # self.z3_ref = torch.zeros(1, device=device) + + def forward(self, dummy_input=None): + # Reconstruct full arrays with zeros at reference position + before_shifts = self.shifts_param[:self.zero_tilt_idx] + after_shifts = self.shifts_param[self.zero_tilt_idx:] + shifts = torch.cat([before_shifts, self.shifts_ref, after_shifts], dim=0) + + before_z1 = self.z1_param[:self.zero_tilt_idx] + after_z1 = self.z1_param[self.zero_tilt_idx:] + z1 = torch.cat([before_z1, self.z1_ref, after_z1], dim=0) + + # before_z3 = self.z3_param[:self.zero_tilt_idx] + # after_z3 = self.z3_param[self.zero_tilt_idx:] + # z3 = torch.cat([before_z3, self.z3_ref, after_z3], dim=0) + + return shifts, z1, -z1 + + +class TomographyNerf(): + + def __init__( + self, + tomo_dataset: TomographyDataset, + + ): + + self.setup_distributed() + self.tomo_dataset = tomo_dataset + self.setup_logging() + + def setup_logging(self): + """Setup logging directory and TensorBoard writer.""" + + # base_logdir = self.config.get("logdir", "exp-logs") + + # hidden_features = self.config["hidden_features"] + # hidden_layers = self.config["hidden_layers"] + # lr = self.config["train_lr"] + # aux_lr = self.config["aux_lr"] + # bs = self.config["ray_batch_size"] + # fbs = self.config["fbs"] + # tv = self.config["tv_factor"] + + # lr_str = f"{lr:.1e}" + # tv_str = f"{tv:.1e}" + # aux_lr_str = f"{aux_lr:.1e}" + + + # descriptive_name = f"h{hidden_features}_l{hidden_layers}_lr{lr_str}_auxlr{aux_lr_str}_bs{bs}_tv{tv_str}_omega{fbs}" + # self.logdir = os.path.join(base_logdir, descriptive_name) + + if self.global_rank == 0: + os.makedirs("exp-logs", exist_ok=True) + self.writer = SummaryWriter() # Should put a logdir here + + # config_path = os.path.join(self.logdir, "config.yaml") + # with open(config_path, "w") as f: + # yaml.dump(self.config, f) + + print(f"Logging to: Need to fix") + else: + self.writer = None + def setup_distributed(self): + + """Setup distributed training if available, otherwise use single GPU/CPU.""" + + # Check if we're in a distributed environment + + + if "RANK" in os.environ: + # Distributed training + if not dist.is_initialized(): + dist.init_process_group(backend='nccl' if torch.cuda.is_available() else 'gloo', init_method='env://') + + self.world_size = dist.get_world_size() + self.global_rank = dist.get_rank() + print(f"Global rank: {self.global_rank}") + self.local_rank = int(os.environ["LOCAL_RANK"]) + + torch.cuda.set_device(self.local_rank) + self.device = torch.device("cuda", self.local_rank) + + else: + # Single GPU/CPU training + self.world_size = 1 + self.global_rank = 0 + self.local_rank = 0 + + if torch.cuda.is_available(): + self.device = torch.device("cuda:0") + torch.cuda.set_device(0) + print("Single GPU training") + else: + self.device = torch.device("cpu") + print("CPU training") + + # Optional performance optimizations (only for CUDA) + if self.device.type == "cuda": + torch.backends.cudnn.benchmark = True + torch.backends.cuda.matmul.allow_tf32 = True + torch.backends.cudnn.allow_tf32 = True + + def create_volume(self): + N = self.tomo_dataset.dims[1] + with torch.no_grad(): + coords_1d = torch.linspace(-1, 1, N) + x, y, z = torch.meshgrid(coords_1d, coords_1d, coords_1d, indexing='ij') + inputs = torch.stack([x, y, z], dim=-1).reshape(-1, 3) + + # Get the underlying model if using DDP + model = self.model.module if hasattr(self.model, 'module') else self.model + + samples_per_gpu = N**3 // self.world_size + start_idx = self.global_rank * samples_per_gpu + end_idx = start_idx + samples_per_gpu + + inputs_subset = inputs[start_idx:end_idx].to(self.device) + outputs = model(inputs_subset) + + if outputs.dim() > 1: + outputs = outputs.squeeze(-1) + + if self.world_size > 1: + # Ensure consistent tensor sizes across all ranks + gathered_outputs = [torch.empty_like(outputs) for _ in range(self.world_size)] + dist.all_gather(gathered_outputs, outputs.contiguous()) + + # Explicitly order by rank + pred_full = torch.cat(gathered_outputs, dim=0).reshape(N, N, N).float() + else: + pred_full = outputs.reshape(N, N, N).float() + + return pred_full + + def get_scaled_lr(self, base_lr, scaling_rule="sqrt"): + """Scale learning rate based on world size.""" + if self.world_size == 1: + return base_lr + + if scaling_rule == "linear": + # Linear scaling: lr = base_lr * world_size + return base_lr * self.world_size + elif scaling_rule == "sqrt": + # Square root scaling: lr = base_lr * sqrt(world_size) + return base_lr * np.sqrt(self.world_size) + else: + return base_lr + + + @torch.compile(mode="reduce-overhead") + def transform_batch_ray_coordinates(self, rays, z1, x, z3, shifts, N, sampling_rate): + + # Step 1: Apply shifts + shift_x_norm = (shifts[:, 0:1] * sampling_rate * 2) / (N - 1) + shift_y_norm = (shifts[:, 1:2] * sampling_rate * 2) / (N - 1) + + rays_x = rays[:, :, 0] - shift_x_norm + rays_y = rays[:, :, 1] - shift_y_norm + rays_z = rays[:, :, 2] + + # Rotation 1: Z(-z3) + theta = torch.deg2rad(-z3).view(-1, 1) + cos_t = torch.cos(theta) + sin_t = torch.sin(theta) + + rays_x_rot1 = cos_t * rays_x - sin_t * rays_y + rays_y_rot1 = sin_t * rays_x + cos_t * rays_y + rays_z_rot1 = rays_z + + # Rotation 2: X(x) + theta = torch.deg2rad(x).view(-1, 1) + cos_t = torch.cos(theta) + sin_t = torch.sin(theta) + + rays_x_rot2 = rays_x_rot1 + rays_y_rot2 = cos_t * rays_y_rot1 - sin_t * rays_z_rot1 + rays_z_rot2 = sin_t * rays_y_rot1 + cos_t * rays_z_rot1 + + # Rotation 3: Z(-z1) + theta = torch.deg2rad(-z1).view(-1, 1) + cos_t = torch.cos(theta) + sin_t = torch.sin(theta) + + rays_x_final = cos_t * rays_x_rot2 - sin_t * rays_y_rot2 + rays_y_final = sin_t * rays_x_rot2 + cos_t * rays_y_rot2 + rays_z_final = rays_z_rot2 + + # Stack the final result + transformed_rays = torch.stack([rays_x_final, rays_y_final, rays_z_final], dim=2) + + return transformed_rays + + def save_checkpoint(self, epoch, aux_params): + """Save model and auxiliary parameters checkpoint (rank 0 only).""" + if self.global_rank == 0: + model_to_save = self.model.module if hasattr(self.model, 'module') else self.model + aux_params_to_save = aux_params.module if hasattr(aux_params, 'module') else aux_params + + checkpoint = { + "model_state_dict": model_to_save.state_dict(), + "aux_params_state_dict": aux_params_to_save.state_dict(), + "epoch": epoch, + # "config": self.config, + } + checkpoint_path = os.path.join("exp-logs", f"checkpoint_epoch_{epoch:04d}.pt") + torch.save(checkpoint, checkpoint_path) + print(f"Checkpoint saved at epoch {epoch}: {checkpoint_path}") + + def create_batch_projection_rays(self, pixel_i, pixel_j, N, num_samples_per_ray): + """Create projection rays for entire batch simultaneously.""" + batch_size = len(pixel_i) + + # Convert all pixels to normalized coordinates + x_coords = (pixel_j / (N - 1)) * 2 - 1 + y_coords = (pixel_i / (N - 1)) * 2 - 1 + + # Create z coordinates + z_coords = torch.linspace(-1, 1, num_samples_per_ray, device=self.device) + + # Create rays for all pixels: [batch_size, num_samples_per_ray, 3] + rays = torch.zeros(batch_size, num_samples_per_ray, 3, device=self.device) + + # Fill coordinates efficiently + rays[:, :, 0] = x_coords.unsqueeze(1) # x constant per ray + rays[:, :, 1] = y_coords.unsqueeze(1) # y constant per ray + rays[:, :, 2] = z_coords.unsqueeze(0) # z varies along ray + + return rays + + def train( + self, + batch_size: int, + num_workers: int, + omega_0 : int, + hidden_omega_0 : int, + train_lr : float, + aux_lr : float, + warmup_epochs : int, + epochs : int, + use_amp : bool, + sampling_rate : float, + tv_weight : float, + viz_freq : int, + checkpoint_freq : int, + # hidden_layers: int, + # hidden_features: int, + # alpha: float, + ): + + ### DataLoader setup + batch_size = batch_size + num_workers = num_workers + + if self.world_size > 1: + self.train_sampler = DistributedSampler( + self.tomo_dataset, + num_replicas=self.world_size, + rank=self.global_rank, + shuffle=False + ) + shuffle = False + else: + self.train_sampler = None + shuffle = True # Need this!! + + self.dataloader = DataLoader( + self.tomo_dataset, + batch_size=batch_size, + sampler=self.train_sampler, + shuffle=shuffle, + num_workers=num_workers, + pin_memory=True if self.device.type == "cuda" else False, + drop_last=True, + persistent_workers=False if num_workers == 0 else True + ) + + # Print statistics + if self.global_rank == 0: + # total_pixels = len(self.tomo_dataset.tilt_angles) * self.config["N"] ** 2 + train_pixels = self.tomo_dataset.num_pixels + effective_batch_size = batch_size * self.world_size + + print(f"Dataloader setup complete:") + print(f" Total projections: {len(self.tomo_dataset.tilt_angles)}") + print(f" Grid size: {self.tomo_dataset.dims[1]}×{self.tomo_dataset.dims[2]}") + print(f" Total pixels: {train_pixels:,}") + + print(f" Local batch size (train): {batch_size}") + print(f" Global batch size: {effective_batch_size}") + print(f" Train batches per GPU per epoch: {len(self.dataloader)}") + + + ### + + ### Model Setup + + self.model = HSiren( + in_features = 3, + out_features = 1, + hidden_features = 512, + hidden_layers = 4, + first_omega_0 = omega_0, + hidden_omega_0 = omega_0, # Remove hidden_omega_0. + ).to(self.device) + + if self.world_size > 1: + self.model = torch.nn.parallel.DistributedDataParallel( + self.model, + device_ids=[self.local_rank], + output_device=self.local_rank, + find_unused_parameters=False, + bucket_cap_mb=100, + gradient_as_bucket_view=True, # TODO: Check if this is needed. + ) + if self.global_rank == 0: + print("Model wrapped with DDP") + + if self.world_size > 1: + + if self.global_rank == 0: + print("Model built, distributed, and compiled successfully.") + else: + print("Model built and compiled successfully.") + + ### Initializing optimizers, and auxiliary parameters + + # Torch module on z1, z3, and shifts as module + + phis = self.tomo_dataset.tilt_angles + zero_tilt_idx = torch.argmin(torch.abs(phis)).item() + + if self.global_rank == 0: + print(f"Using projection {zero_tilt_idx} (angle={phis[zero_tilt_idx]:.2f}°) as reference") + + + self.aux_params = AuxiliaryParams( + num_tilts = len(phis), + device = self.device, + zero_tilt_idx = zero_tilt_idx + ) + + + if self.world_size > 1: + self.aux_params = torch.nn.parallel.DistributedDataParallel( + self.aux_params, + device_ids=[self.local_rank], + output_device=self.local_rank, + find_unused_parameters=False, + broadcast_buffers=True + ) + if self.global_rank == 0: + print("Auxiliary parameters wrapped with DDP") + + # Learning rates + scaled_train_lr = train_lr*np.sqrt(self.world_size) + scaled_aux_lr = aux_lr*np.sqrt(self.world_size) + + # Optimizers + optimizer = torch.optim.Adam(self.model.parameters(), lr=scaled_train_lr, fused = True) + aux_optimizer = torch.optim.Adam(self.aux_params.parameters(), lr=scaled_aux_lr) + + optimizer.zero_grad() + aux_optimizer.zero_grad() + + warmup_scheduler_model = torch.optim.lr_scheduler.LinearLR(optimizer, start_factor=0.001, total_iters=warmup_epochs) # TODO: Start factor play around with this. + cosine_scheduler_model = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs - warmup_epochs, eta_min=scaled_train_lr/100) + scheduler_model = torch.optim.lr_scheduler.SequentialLR(optimizer, schedulers=[warmup_scheduler_model, cosine_scheduler_model], milestones=[warmup_epochs]) + + scheduler_aux = torch.optim.lr_scheduler.CosineAnnealingLR(aux_optimizer, T_max=epochs, eta_min=scaled_aux_lr/100) + + + accumulated_batches = 0 # TODO: Do this? Gradient accumulation + device_type = self.device.type + autocast_dtype = torch.bfloat16 if use_amp else None + + for epoch in range(epochs): + current_accumulation_steps = get_accumulation_steps(epoch) + num_samples_per_ray = get_num_samples_per_ray(epoch) + + if epoch > 0: + prev_steps = get_accumulation_steps(epoch - 1) + prev_samples = get_num_samples_per_ray(epoch - 1) + + if current_accumulation_steps != prev_steps and self.global_rank == 0: + print(f"Epoch {epoch}: Changing accumulation steps from {prev_steps} to {current_accumulation_steps}") + if num_samples_per_ray != prev_samples and self.global_rank == 0: + print(f"Epoch {epoch}: Changing num_samples_per_ray from {prev_samples} to {num_samples_per_ray}") + + if self.train_sampler is not None: + self.train_sampler.set_epoch(epoch) + + epoch_loss = 0.0 + epoch_mse_loss = 0.0 + epoch_tv_loss = 0.0 + epoch_z1_loss = 0.0 + + num_batches = 0 + + for batch_idx, batch in enumerate(self.dataloader): + + projection_indices = batch['projection_idx'] + + pixel_i = batch['pixel_i'].float().to(self.device, non_blocking=True) + pixel_j = batch['pixel_j'].float().to(self.device, non_blocking=True) + target_values = batch['target_value'].to(self.device, non_blocking=True) + phis = batch['phi'].to(self.device, non_blocking=True) + projection_indices = batch['projection_idx'].to(self.device, non_blocking=True) + + shifts, z1_params, z3_params = self.aux_params(None) # NONE just for DDP + batch_shifts = torch.index_select(shifts, 0, projection_indices) + batch_z1 = torch.index_select(z1_params, 0, projection_indices) + batch_z3 = torch.index_select(z3_params, 0, projection_indices) + + with torch.autocast(device_type=device_type, dtype=autocast_dtype, enabled=use_amp): + + with torch.no_grad(): # For every value of the tilts, shifts, and phi figure out where ray enters and exits and sample along that line. Ray has to be -1 to 1 + batch_ray_coords = self.create_batch_projection_rays( + pixel_i, pixel_j, self.tomo_dataset.dims[1], num_samples_per_ray + ) + + transformed_rays = self.transform_batch_ray_coordinates( + batch_ray_coords, + z1=batch_z1, + x=phis, + z3=batch_z3, + shifts=batch_shifts, + N=self.tomo_dataset.dims[1], + sampling_rate=1 # TODO: make input into train + ) + + all_coords = transformed_rays.view(-1, 3) + all_densities = self.model(all_coords) + + if all_densities.dim() > 1: + all_densities = all_densities.squeeze(-1) # [N, 1] → [N] + valid_mask = ( # TODO: Needs to be fixed edge of boxes + (all_coords[:, 0] >= -1) & (all_coords[:, 0] <= 1) & + (all_coords[:, 1] >= -1) & (all_coords[:, 1] <= 1) & + (all_coords[:, 2] >= -1) & (all_coords[:, 2] <= 1) + ).float() + + all_densities = all_densities * valid_mask + if tv_weight > 0: + + num_tv_samples = min(10000, all_coords.shape[0]) + tv_indices = torch.randperm(all_coords.shape[0], device=all_coords.device)[:num_tv_samples] + + tv_coords = all_coords[tv_indices].detach().requires_grad_(True) + tv_densities_recomputed = self.model(tv_coords) + + if tv_densities_recomputed.dim() > 1: + tv_densities_recomputed = tv_densities_recomputed.squeeze(-1) + + grad_outputs = torch.autograd.grad( + outputs=tv_densities_recomputed, + inputs=tv_coords, + grad_outputs=torch.ones_like(tv_densities_recomputed), + create_graph=True + )[0] + + + grad_norm = torch.norm(grad_outputs, dim=1) + tv_loss = tv_weight * grad_norm.mean() + else: + tv_loss = torch.tensor(0.0, device=self.device) + + ray_densities = all_densities.view(len(target_values), num_samples_per_ray) + + step_size = 2.0 / (num_samples_per_ray - 1) + + predicted_values = ray_densities.sum(dim=1) * step_size + + mse_loss = torch.nn.functional.mse_loss(predicted_values, target_values) + + tv_loss_z1 = torch.tensor(0.0, device=self.device) + + loss = mse_loss + tv_loss_z1 + tv_loss + loss = loss / current_accumulation_steps + + loss.backward() + + epoch_loss += loss.detach() + epoch_mse_loss += mse_loss.detach() + epoch_tv_loss += tv_loss.detach() + epoch_z1_loss += tv_loss_z1.detach() + + num_batches += 1 + if epoch >= warmup_epochs: + + model_norm = torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm = 1) + optimizer.step() + optimizer.zero_grad() + + aux_norm = torch.nn.utils.clip_grad_norm_(self.aux_params.parameters(), max_norm = 1) + aux_optimizer.step() + aux_optimizer.zero_grad() + else: + model_norm = torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm = 1) + optimizer.step() + optimizer.zero_grad() + + aux_optimizer.zero_grad() + + scheduler_model.step() + if epoch >= warmup_epochs: + scheduler_aux.step() + + if self.global_rank == 0: + print(f"Epoch Complete {epoch}") + + + if epoch % viz_freq == 0 or epoch == epochs - 1 or epoch % checkpoint_freq == 0 : + + with torch.no_grad(): + + pred_full = self.create_volume().cpu() + avg_loss = epoch_loss.item() / num_batches + avg_mse_loss = epoch_mse_loss.item() / num_batches + avg_tv_loss = epoch_tv_loss.item() / num_batches + avg_z1_loss = epoch_z1_loss.item() / num_batches + + metrics = torch.tensor([avg_loss, avg_mse_loss, avg_tv_loss, avg_z1_loss], device=self.device) + if self.world_size > 1: + torch.distributed.all_reduce(metrics, op=torch.distributed.ReduceOp.AVG) + avg_loss, avg_mse_loss, avg_tv_loss, avg_z1_loss = metrics.tolist() + if self.global_rank == 0 and (epoch % viz_freq == 0 or epoch == epochs - 1): + with torch.no_grad(): + current_lr = scheduler_model.get_last_lr()[0] + + # Log metrics + if self.writer is not None: + self.writer.add_scalar("train/mse_loss", avg_mse_loss, epoch) + self.writer.add_scalar("train/z1_loss", avg_z1_loss, epoch) + if tv_weight > 0: + self.writer.add_scalar("train/tv_loss", avg_tv_loss, epoch) + if self.writer is not None: + self.writer.add_scalar("train/total_loss", avg_loss, epoch) + if self.writer is not None: + self.writer.add_scalar("train/model_grad_norm", model_norm.item(), epoch) + + if hasattr(self, "aux_norm"): + if self.writer is not None: + self.writer.add_scalar("train/aux_grad_norm", aux_norm.item(), epoch) + else: + self.writer.add_scalar("train/aux_grad_norm", 0, epoch) + if self.writer is not None: + self.writer.add_scalar("train/lr", current_lr, epoch) + self.writer.add_scalar("train/num_samples_per_ray", num_samples_per_ray, epoch) + + fig, axes = plt.subplots(1, 3, figsize=(36, 12)) + axes[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap='turbo', vmin=0) + axes[0].set_title('Sum over Z-axis') + + axes[1].matshow(pred_full[self.tomo_dataset.dims[1]//2].cpu().numpy(), cmap='turbo', vmin=0) + axes[1].set_title(f'Slice at Z={self.tomo_dataset.dims[1]//2}') + + slice_start = max(0, self.tomo_dataset.dims[1]//2 - 5) + slice_end = min(self.tomo_dataset.dims[1], self.tomo_dataset.dims[1]//2 + 6) + thick_slice = pred_full[slice_start:slice_end].sum(dim=0).cpu().numpy() + axes[2].matshow(thick_slice, cmap='turbo', vmin=0) + axes[2].set_title(f'Thick slice sum (Z={slice_start}:{slice_end-1})') + + if self.writer is not None: + self.writer.add_figure("train/viz", fig, epoch, close=True) + plt.close(fig) + + if epoch % checkpoint_freq == 0 or epoch == epochs - 1: + with torch.no_grad(): + if self.global_rank == 0: + save_path = os.path.join("exp-logs", f"volume_epoch_{epoch:04d}.pt") + torch.save(pred_full.cpu(), save_path) + + self.save_checkpoint(epoch, self.aux_params) + + if self.global_rank == 0: + print("Training complete.") + + + \ No newline at end of file From 66502b2db064feed63c1781c1b8a7e7b068f090d Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Wed, 17 Sep 2025 15:08:09 -0700 Subject: [PATCH 003/335] NeRF kinda working; bugs with convergence --- src/quantem/tomography/tomography_dataset.py | 2 +- src/quantem/tomography/tomography_nerf.py | 52 +++++++++----------- 2 files changed, 24 insertions(+), 30 deletions(-) diff --git a/src/quantem/tomography/tomography_dataset.py b/src/quantem/tomography/tomography_dataset.py index 967fb0f0..ec51080f 100644 --- a/src/quantem/tomography/tomography_dataset.py +++ b/src/quantem/tomography/tomography_dataset.py @@ -12,7 +12,7 @@ from torch.utils.data import Dataset -class TomographyDataset(AutoSerialize, Dataset): +class TomographyDataset(Autoserialize, Dataset): _token = object() """ diff --git a/src/quantem/tomography/tomography_nerf.py b/src/quantem/tomography/tomography_nerf.py index e83d8e88..1cf40bec 100644 --- a/src/quantem/tomography/tomography_nerf.py +++ b/src/quantem/tomography/tomography_nerf.py @@ -365,7 +365,7 @@ def train( print(f"Dataloader setup complete:") print(f" Total projections: {len(self.tomo_dataset.tilt_angles)}") - print(f" Grid size: {self.tomo_dataset.dims[1]}×{self.tomo_dataset.dims[2]}") + print(f" Grid size: {self.tomo_dataset.dims[1]}{self.tomo_dataset.dims[2]}") print(f" Total pixels: {train_pixels:,}") print(f" Local batch size (train): {batch_size}") @@ -382,8 +382,8 @@ def train( out_features = 1, hidden_features = 512, hidden_layers = 4, - first_omega_0 = omega_0, - hidden_omega_0 = omega_0, # Remove hidden_omega_0. + first_omega_0 = 30, + hidden_omega_0 = 30, # Remove hidden_omega_0. ).to(self.device) if self.world_size > 1: @@ -442,17 +442,18 @@ def train( optimizer = torch.optim.Adam(self.model.parameters(), lr=scaled_train_lr, fused = True) aux_optimizer = torch.optim.Adam(self.aux_params.parameters(), lr=scaled_aux_lr) + aux_norm = torch.tensor(0.0, device=self.device) + model_norm = torch.tensor(0.0, device=self.device) + optimizer.zero_grad() aux_optimizer.zero_grad() - warmup_scheduler_model = torch.optim.lr_scheduler.LinearLR(optimizer, start_factor=0.001, total_iters=warmup_epochs) # TODO: Start factor play around with this. + warmup_scheduler_model = torch.optim.lr_scheduler.LinearLR(optimizer, start_factor=0.01, total_iters=warmup_epochs) # TODO: Start factor play around with this. cosine_scheduler_model = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs - warmup_epochs, eta_min=scaled_train_lr/100) scheduler_model = torch.optim.lr_scheduler.SequentialLR(optimizer, schedulers=[warmup_scheduler_model, cosine_scheduler_model], milestones=[warmup_epochs]) scheduler_aux = torch.optim.lr_scheduler.CosineAnnealingLR(aux_optimizer, T_max=epochs, eta_min=scaled_aux_lr/100) - - accumulated_batches = 0 # TODO: Do this? Gradient accumulation device_type = self.device.type autocast_dtype = torch.bfloat16 if use_amp else None @@ -593,58 +594,51 @@ def train( if epoch % viz_freq == 0 or epoch == epochs - 1 or epoch % checkpoint_freq == 0 : - with torch.no_grad(): - pred_full = self.create_volume().cpu() avg_loss = epoch_loss.item() / num_batches avg_mse_loss = epoch_mse_loss.item() / num_batches avg_tv_loss = epoch_tv_loss.item() / num_batches avg_z1_loss = epoch_z1_loss.item() / num_batches - + + metrics = torch.tensor([avg_loss, avg_mse_loss, avg_tv_loss, avg_z1_loss], device=self.device) if self.world_size > 1: torch.distributed.all_reduce(metrics, op=torch.distributed.ReduceOp.AVG) avg_loss, avg_mse_loss, avg_tv_loss, avg_z1_loss = metrics.tolist() + + if self.global_rank == 0 and (epoch % viz_freq == 0 or epoch == epochs - 1): with torch.no_grad(): current_lr = scheduler_model.get_last_lr()[0] - + # Log metrics - if self.writer is not None: - self.writer.add_scalar("train/mse_loss", avg_mse_loss, epoch) - self.writer.add_scalar("train/z1_loss", avg_z1_loss, epoch) + self.writer.add_scalar("train/mse_loss", avg_mse_loss, epoch) + + + self.writer.add_scalar("train/z1_loss", avg_z1_loss, epoch) if tv_weight > 0: self.writer.add_scalar("train/tv_loss", avg_tv_loss, epoch) - if self.writer is not None: self.writer.add_scalar("train/total_loss", avg_loss, epoch) - if self.writer is not None: - self.writer.add_scalar("train/model_grad_norm", model_norm.item(), epoch) - - if hasattr(self, "aux_norm"): - if self.writer is not None: - self.writer.add_scalar("train/aux_grad_norm", aux_norm.item(), epoch) - else: - self.writer.add_scalar("train/aux_grad_norm", 0, epoch) - if self.writer is not None: - self.writer.add_scalar("train/lr", current_lr, epoch) + self.writer.add_scalar("train/model_grad_norm", model_norm.item(), epoch) + self.writer.add_scalar("train/aux_grad_norm", aux_norm.item(), epoch) + self.writer.add_scalar("train/lr", current_lr, epoch) self.writer.add_scalar("train/num_samples_per_ray", num_samples_per_ray, epoch) - + fig, axes = plt.subplots(1, 3, figsize=(36, 12)) axes[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap='turbo', vmin=0) axes[0].set_title('Sum over Z-axis') axes[1].matshow(pred_full[self.tomo_dataset.dims[1]//2].cpu().numpy(), cmap='turbo', vmin=0) axes[1].set_title(f'Slice at Z={self.tomo_dataset.dims[1]//2}') - + slice_start = max(0, self.tomo_dataset.dims[1]//2 - 5) slice_end = min(self.tomo_dataset.dims[1], self.tomo_dataset.dims[1]//2 + 6) thick_slice = pred_full[slice_start:slice_end].sum(dim=0).cpu().numpy() axes[2].matshow(thick_slice, cmap='turbo', vmin=0) axes[2].set_title(f'Thick slice sum (Z={slice_start}:{slice_end-1})') - - if self.writer is not None: - self.writer.add_figure("train/viz", fig, epoch, close=True) + + self.writer.add_figure("train/viz", fig, epoch, close=True) plt.close(fig) if epoch % checkpoint_freq == 0 or epoch == epochs - 1: From bcb7d1da5d0fc0789ad1b32e15cb71c945812838 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Wed, 17 Sep 2025 16:03:48 -0700 Subject: [PATCH 004/335] TV loss squeeze axis --- src/quantem/tomography/models.py | 12 ++++++------ src/quantem/tomography/tomography_dataset.py | 5 ++++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/quantem/tomography/models.py b/src/quantem/tomography/models.py index 0829712e..a138990c 100644 --- a/src/quantem/tomography/models.py +++ b/src/quantem/tomography/models.py @@ -37,12 +37,12 @@ class HSiren(nn.Module): def __init__(self, in_features=2, out_features=3, hidden_layers=3, hidden_features=256, first_omega_0=30, hidden_omega_0=30, alpha=1.0): super().__init__() - self.net = [] - self.net.append(SineLayer(in_features, hidden_features, is_first=True, + self.net_list = [] + self.net_list.append(SineLayer(in_features, hidden_features, is_first=True, omega_0=first_omega_0, hsiren=True, alpha=alpha)) for i in range(hidden_layers): - self.net.append(SineLayer(hidden_features, hidden_features, is_first=False, + self.net_list.append(SineLayer(hidden_features, hidden_features, is_first=False, omega_0=hidden_omega_0, alpha=alpha)) final_linear = nn.Linear(hidden_features, out_features) @@ -50,9 +50,9 @@ def __init__(self, in_features=2, out_features=3, hidden_layers=3, hidden_featur # Final layer keeps original initialization (no alpha scaling) final_linear.weight.uniform_(-np.sqrt(6 / hidden_features) / hidden_omega_0, np.sqrt(6 / hidden_features) / hidden_omega_0) - self.net.append(final_linear) - self.net.append(nn.Softplus()) - self.net = nn.Sequential(*self.net) + self.net_list.append(final_linear) + self.net_list.append(nn.Softplus()) + self.net = nn.Sequential(*self.net_list) def forward(self, coords): output = self.net(coords) diff --git a/src/quantem/tomography/tomography_dataset.py b/src/quantem/tomography/tomography_dataset.py index ec51080f..682f0de3 100644 --- a/src/quantem/tomography/tomography_dataset.py +++ b/src/quantem/tomography/tomography_dataset.py @@ -12,7 +12,7 @@ from torch.utils.data import Dataset -class TomographyDataset(Autoserialize, Dataset): +class TomographyDataset(AutoSerialize, Dataset): _token = object() """ @@ -32,6 +32,9 @@ def __init__( shifts: Tensor, ): self._tilt_series = tilt_series + # Enforce Positivity + self._tilt_series = torch.clamp(self._tilt_series, min=0) + self._tilt_angles = tilt_angles self._z1_angles = z1_angles self._z3_angles = z3_angles From 0bb881bb9f0785fe113ab748376b84517c4627a2 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Wed, 17 Sep 2025 17:28:09 -0700 Subject: [PATCH 005/335] NeRF works, barebones. Start writing into quantem style --- src/quantem/tomography/models.py | 2 +- src/quantem/tomography/tomography_dataset.py | 3 +- src/quantem/tomography/tomography_nerf.py | 569 ++++++++------- src/quantem/tomography/tomography_nerf_old.py | 658 ++++++++++++++++++ 4 files changed, 932 insertions(+), 300 deletions(-) create mode 100644 src/quantem/tomography/tomography_nerf_old.py diff --git a/src/quantem/tomography/models.py b/src/quantem/tomography/models.py index a138990c..212385ee 100644 --- a/src/quantem/tomography/models.py +++ b/src/quantem/tomography/models.py @@ -51,7 +51,7 @@ def __init__(self, in_features=2, out_features=3, hidden_layers=3, hidden_featur final_linear.weight.uniform_(-np.sqrt(6 / hidden_features) / hidden_omega_0, np.sqrt(6 / hidden_features) / hidden_omega_0) self.net_list.append(final_linear) - self.net_list.append(nn.Softplus()) + self.net_list.append(nn.Softplus()) self.net = nn.Sequential(*self.net_list) def forward(self, coords): diff --git a/src/quantem/tomography/tomography_dataset.py b/src/quantem/tomography/tomography_dataset.py index 682f0de3..348964ad 100644 --- a/src/quantem/tomography/tomography_dataset.py +++ b/src/quantem/tomography/tomography_dataset.py @@ -33,7 +33,6 @@ def __init__( ): self._tilt_series = tilt_series # Enforce Positivity - self._tilt_series = torch.clamp(self._tilt_series, min=0) self._tilt_angles = tilt_angles self._z1_angles = z1_angles @@ -54,6 +53,8 @@ def __init__( # Enforce normalization of tilt series tilt_percentile = torch.quantile(self._tilt_series, .95) self._tilt_series = self._tilt_series / tilt_percentile + self._tilt_series = torch.clamp(self._tilt_series, min=0) + # --- Class Methods --- @classmethod diff --git a/src/quantem/tomography/tomography_nerf.py b/src/quantem/tomography/tomography_nerf.py index 1cf40bec..5251d2d9 100644 --- a/src/quantem/tomography/tomography_nerf.py +++ b/src/quantem/tomography/tomography_nerf.py @@ -11,37 +11,11 @@ import os +from pathlib import Path -def get_accumulation_steps(epoch): - """Increase accumulation steps at specific epochs.""" - # schedule = config.get("accumulation_schedule", { - # 0: 1, - # 200: 4, - # 500: 28, - # }) - - schedule = { - 0: 1, - 200: 4, - 500: 28, - } - - accumulation_steps = 1 - for epoch_threshold, steps in sorted(schedule.items()): - if epoch >= epoch_threshold: - accumulation_steps = steps - - return accumulation_steps def get_num_samples_per_ray(epoch): """Increase number of samples per ray at specific epochs.""" - # schedule = config.get("num_samples_schedule", { - # 0: 64, - # 200: 128, - # 500: 256, - # }) - - # TODO: Maybe delete schedule = { 0: 25, 2: 50, @@ -104,52 +78,24 @@ class TomographyNerf(): def __init__( self, tomo_dataset: TomographyDataset, - + batch_size: int, + num_workers: int = 0, ): - self.setup_distributed() self.tomo_dataset = tomo_dataset - self.setup_logging() + self.setup_distributed() + self.setup_dataloader( + batch_size = batch_size, + num_workers = num_workers, + ) + self.build_model() - def setup_logging(self): - """Setup logging directory and TensorBoard writer.""" - - # base_logdir = self.config.get("logdir", "exp-logs") - - # hidden_features = self.config["hidden_features"] - # hidden_layers = self.config["hidden_layers"] - # lr = self.config["train_lr"] - # aux_lr = self.config["aux_lr"] - # bs = self.config["ray_batch_size"] - # fbs = self.config["fbs"] - # tv = self.config["tv_factor"] - - # lr_str = f"{lr:.1e}" - # tv_str = f"{tv:.1e}" - # aux_lr_str = f"{aux_lr:.1e}" - - - # descriptive_name = f"h{hidden_features}_l{hidden_layers}_lr{lr_str}_auxlr{aux_lr_str}_bs{bs}_tv{tv_str}_omega{fbs}" - # self.logdir = os.path.join(base_logdir, descriptive_name) - - if self.global_rank == 0: - os.makedirs("exp-logs", exist_ok=True) - self.writer = SummaryWriter() # Should put a logdir here - - # config_path = os.path.join(self.logdir, "config.yaml") - # with open(config_path, "w") as f: - # yaml.dump(self.config, f) - - print(f"Logging to: Need to fix") - else: - self.writer = None + # --- Setup ---- + def setup_distributed(self): - """Setup distributed training if available, otherwise use single GPU/CPU.""" # Check if we're in a distributed environment - - if "RANK" in os.environ: # Distributed training if not dist.is_initialized(): @@ -157,7 +103,6 @@ def setup_distributed(self): self.world_size = dist.get_world_size() self.global_rank = dist.get_rank() - print(f"Global rank: {self.global_rank}") self.local_rank = int(os.environ["LOCAL_RANK"]) torch.cuda.set_device(self.local_rank) @@ -182,39 +127,118 @@ def setup_distributed(self): torch.backends.cudnn.benchmark = True torch.backends.cuda.matmul.allow_tf32 = True torch.backends.cudnn.allow_tf32 = True + + def build_model(self): + + self.model = HSiren( + in_features = 3, + out_features = 1, + hidden_layers = 4, + hidden_features = 512, + first_omega_0 = 30, + alpha = 1.0, + ).to(self.device) + + if self.world_size > 1: + self.model = torch.nn.parallel.DistributedDataParallel( + self.model, + device_ids = [self.local_rank], + output_device = self.local_rank, + find_unused_parameters = False, + broadcast_buffers = True, + bucket_cap_mb = 100, + gradient_as_bucket_view = True, + ) + + if self.global_rank == 0: + print("Model wrapped with DDP") + + if self.world_size > 1: + if self.global_rank == 0: + print("Model built, distributed, and compiled successfully") + + else: + print("Model built, compiled successfully") + + def setup_dataloader( + self, + batch_size: int, + num_workers: int = 0, + ): + + if self.world_size > 1: + sampler = DistributedSampler( + self.tomo_dataset, + num_replicas = self.world_size, + rank = self.global_rank, + shuffle = True, + ) + shuffle = False + else: + sampler = None + shuffle = True + self.dataloader = DataLoader( + self.tomo_dataset, + batch_size = batch_size, + num_workers = num_workers, + sampler = sampler, + shuffle = shuffle, + pin_memory = True if self.device.type == "cuda" else False, + drop_last = True, + persistent_workers = False if num_workers == 0 else True, + ) + + self.sampler = sampler + + + if self.global_rank == 0: + print(f"Dataloader setup complete:") + print(f" Total projections: {len(self.tomo_dataset.tilt_angles)}") + print(f" Grid size: {self.tomo_dataset.dims[1]}{self.tomo_dataset.dims[2]}") + print(f" Total pixels: {self.tomo_dataset.num_pixels:,}") + + print(f" Local batch size (train): {batch_size}") + print(f" Global batch size: {batch_size*self.world_size}") + print(f" Train batches per GPU per epoch: {len(self.dataloader)}") + + + # --- Creating Volume --- def create_volume(self): - N = self.tomo_dataset.dims[1] + + N = max(self.tomo_dataset.dims) + with torch.no_grad(): coords_1d = torch.linspace(-1, 1, N) x, y, z = torch.meshgrid(coords_1d, coords_1d, coords_1d, indexing='ij') inputs = torch.stack([x, y, z], dim=-1).reshape(-1, 3) - - # Get the underlying model if using DDP + model = self.model.module if hasattr(self.model, 'module') else self.model - + samples_per_gpu = N**3 // self.world_size start_idx = self.global_rank * samples_per_gpu end_idx = start_idx + samples_per_gpu - + inputs_subset = inputs[start_idx:end_idx].to(self.device) + + # TODO: torch.nn.functional.softplus here + # outputs = torch.nn.functional.softplus(model(inputs_subset)) outputs = model(inputs_subset) - + if outputs.dim() > 1: outputs = outputs.squeeze(-1) - + if self.world_size > 1: - # Ensure consistent tensor sizes across all ranks gathered_outputs = [torch.empty_like(outputs) for _ in range(self.world_size)] dist.all_gather(gathered_outputs, outputs.contiguous()) - - # Explicitly order by rank + pred_full = torch.cat(gathered_outputs, dim=0).reshape(N, N, N).float() else: pred_full = outputs.reshape(N, N, N).float() - + return pred_full - + + # --- Scaling LR --- def get_scaled_lr(self, base_lr, scaling_rule="sqrt"): """Scale learning rate based on world size.""" if self.world_size == 1: @@ -227,10 +251,46 @@ def get_scaled_lr(self, base_lr, scaling_rule="sqrt"): # Square root scaling: lr = base_lr * sqrt(world_size) return base_lr * np.sqrt(self.world_size) else: - return base_lr + raise ValueError(f"Invalid scaling rule: {scaling_rule}") + # --- Creating Optimizer --- + def create_optimizer( + self, + params, + lr, + fused = True, + ): - @torch.compile(mode="reduce-overhead") + return torch.optim.Adam( + params, + lr = lr, + fused = fused, + ) + + # Batch Projection Rays --- + + def create_batch_projection_rays(self, pixel_i, pixel_j, N, num_samples_per_ray): + """Create projection rays for entire batch simultaneously.""" + batch_size = len(pixel_i) + + # Convert all pixels to normalized coordinates + x_coords = (pixel_j / (N - 1)) * 2 - 1 + y_coords = (pixel_i / (N - 1)) * 2 - 1 + + # Create z coordinates + z_coords = torch.linspace(-1, 1, num_samples_per_ray, device=self.device) + + # Create rays for all pixels: [batch_size, num_samples_per_ray, 3] + rays = torch.zeros(batch_size, num_samples_per_ray, 3, device=self.device) + + # Fill coordinates efficiently + rays[:, :, 0] = x_coords.unsqueeze(1) # x constant per ray + rays[:, :, 1] = y_coords.unsqueeze(1) # y constant per ray + rays[:, :, 2] = z_coords.unsqueeze(0) # z varies along ray + + return rays + + @torch.compile(mode="reduce-overhead") def transform_batch_ray_coordinates(self, rays, z1, x, z3, shifts, N, sampling_rate): # Step 1: Apply shifts @@ -273,174 +333,61 @@ def transform_batch_ray_coordinates(self, rays, z1, x, z3, shifts, N, sampling_r return transformed_rays - def save_checkpoint(self, epoch, aux_params): - """Save model and auxiliary parameters checkpoint (rank 0 only).""" - if self.global_rank == 0: - model_to_save = self.model.module if hasattr(self.model, 'module') else self.model - aux_params_to_save = aux_params.module if hasattr(aux_params, 'module') else aux_params - - checkpoint = { - "model_state_dict": model_to_save.state_dict(), - "aux_params_state_dict": aux_params_to_save.state_dict(), - "epoch": epoch, - # "config": self.config, - } - checkpoint_path = os.path.join("exp-logs", f"checkpoint_epoch_{epoch:04d}.pt") - torch.save(checkpoint, checkpoint_path) - print(f"Checkpoint saved at epoch {epoch}: {checkpoint_path}") - - def create_batch_projection_rays(self, pixel_i, pixel_j, N, num_samples_per_ray): - """Create projection rays for entire batch simultaneously.""" - batch_size = len(pixel_i) - - # Convert all pixels to normalized coordinates - x_coords = (pixel_j / (N - 1)) * 2 - 1 - y_coords = (pixel_i / (N - 1)) * 2 - 1 - - # Create z coordinates - z_coords = torch.linspace(-1, 1, num_samples_per_ray, device=self.device) - - # Create rays for all pixels: [batch_size, num_samples_per_ray, 3] - rays = torch.zeros(batch_size, num_samples_per_ray, 3, device=self.device) - - # Fill coordinates efficiently - rays[:, :, 0] = x_coords.unsqueeze(1) # x constant per ray - rays[:, :, 1] = y_coords.unsqueeze(1) # y constant per ray - rays[:, :, 2] = z_coords.unsqueeze(0) # z varies along ray - - return rays - + # --- Training --- def train( self, - batch_size: int, - num_workers: int, - omega_0 : int, - hidden_omega_0 : int, - train_lr : float, - aux_lr : float, - warmup_epochs : int, - epochs : int, - use_amp : bool, - sampling_rate : float, - tv_weight : float, - viz_freq : int, - checkpoint_freq : int, - # hidden_layers: int, - # hidden_features: int, - # alpha: float, + train_lr: float = 1e-5, + epochs: int = 20, + warmup_epochs: int = 10, + use_amp: bool = True, + tv_weight: float = 0.0, + viz_freq: int = 1, + checkpoint_freq: int = 5, ): + log_path = Path("runs/test_1") + if not log_path.exists(): + log_path.mkdir(parents=True, exist_ok=True) + self.writer = SummaryWriter(log_dir=log_path) - ### DataLoader setup - batch_size = batch_size - num_workers = num_workers + # Find index closest to zero degrees + zero_tilt_idx = torch.argmin(torch.abs(self.tomo_dataset.tilt_angles)).item() - if self.world_size > 1: - self.train_sampler = DistributedSampler( - self.tomo_dataset, - num_replicas=self.world_size, - rank=self.global_rank, - shuffle=False - ) - shuffle = False - else: - self.train_sampler = None - shuffle = True # Need this!! - - self.dataloader = DataLoader( - self.tomo_dataset, - batch_size=batch_size, - sampler=self.train_sampler, - shuffle=shuffle, - num_workers=num_workers, - pin_memory=True if self.device.type == "cuda" else False, - drop_last=True, - persistent_workers=False if num_workers == 0 else True - ) - - # Print statistics if self.global_rank == 0: - # total_pixels = len(self.tomo_dataset.tilt_angles) * self.config["N"] ** 2 - train_pixels = self.tomo_dataset.num_pixels - effective_batch_size = batch_size * self.world_size - - print(f"Dataloader setup complete:") - print(f" Total projections: {len(self.tomo_dataset.tilt_angles)}") - print(f" Grid size: {self.tomo_dataset.dims[1]}{self.tomo_dataset.dims[2]}") - print(f" Total pixels: {train_pixels:,}") - - print(f" Local batch size (train): {batch_size}") - print(f" Global batch size: {effective_batch_size}") - print(f" Train batches per GPU per epoch: {len(self.dataloader)}") + print(f"Using projection {zero_tilt_idx} (angle={self.tomo_dataset.tilt_angles[zero_tilt_idx]:.2f}°) as reference") - - ### - - ### Model Setup - - self.model = HSiren( - in_features = 3, - out_features = 1, - hidden_features = 512, - hidden_layers = 4, - first_omega_0 = 30, - hidden_omega_0 = 30, # Remove hidden_omega_0. - ).to(self.device) - - if self.world_size > 1: - self.model = torch.nn.parallel.DistributedDataParallel( - self.model, - device_ids=[self.local_rank], - output_device=self.local_rank, - find_unused_parameters=False, - bucket_cap_mb=100, - gradient_as_bucket_view=True, # TODO: Check if this is needed. - ) - if self.global_rank == 0: - print("Model wrapped with DDP") - - if self.world_size > 1: - if self.global_rank == 0: - print("Model built, distributed, and compiled successfully.") - else: - print("Model built and compiled successfully.") - - ### Initializing optimizers, and auxiliary parameters - - # Torch module on z1, z3, and shifts as module - - phis = self.tomo_dataset.tilt_angles - zero_tilt_idx = torch.argmin(torch.abs(phis)).item() - - if self.global_rank == 0: - print(f"Using projection {zero_tilt_idx} (angle={phis[zero_tilt_idx]:.2f}°) as reference") - - - self.aux_params = AuxiliaryParams( - num_tilts = len(phis), + aux_params = AuxiliaryParams( + num_tilts = len(self.tomo_dataset.tilt_angles), device = self.device, - zero_tilt_idx = zero_tilt_idx + zero_tilt_idx = zero_tilt_idx, ) - if self.world_size > 1: - self.aux_params = torch.nn.parallel.DistributedDataParallel( - self.aux_params, - device_ids=[self.local_rank], - output_device=self.local_rank, - find_unused_parameters=False, - broadcast_buffers=True + aux_params = torch.nn.parallel.DistributedDataParallel( + aux_params, + device_ids = [self.local_rank], + output_device = self.local_rank, + find_unused_parameters = False, + broadcast_buffers = True, ) + if self.global_rank == 0: print("Auxiliary parameters wrapped with DDP") - # Learning rates - scaled_train_lr = train_lr*np.sqrt(self.world_size) - scaled_aux_lr = aux_lr*np.sqrt(self.world_size) + scaled_train_lr = self.get_scaled_lr(train_lr, scaling_rule="sqrt") + scaled_aux_lr = self.get_scaled_lr(train_lr, scaling_rule="sqrt") - # Optimizers - optimizer = torch.optim.Adam(self.model.parameters(), lr=scaled_train_lr, fused = True) - aux_optimizer = torch.optim.Adam(self.aux_params.parameters(), lr=scaled_aux_lr) + optimizer = self.create_optimizer( + self.model.parameters(), + scaled_train_lr, + fused = True, + ) + + aux_optimizer = self.create_optimizer( + aux_params.parameters(), + scaled_aux_lr, + fused = True, + ) aux_norm = torch.tensor(0.0, device=self.device) model_norm = torch.tensor(0.0, device=self.device) @@ -448,31 +395,47 @@ def train( optimizer.zero_grad() aux_optimizer.zero_grad() - warmup_scheduler_model = torch.optim.lr_scheduler.LinearLR(optimizer, start_factor=0.01, total_iters=warmup_epochs) # TODO: Start factor play around with this. - cosine_scheduler_model = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs - warmup_epochs, eta_min=scaled_train_lr/100) - scheduler_model = torch.optim.lr_scheduler.SequentialLR(optimizer, schedulers=[warmup_scheduler_model, cosine_scheduler_model], milestones=[warmup_epochs]) - - scheduler_aux = torch.optim.lr_scheduler.CosineAnnealingLR(aux_optimizer, T_max=epochs, eta_min=scaled_aux_lr/100) + warmup_scheduler_model = torch.optim.lr_scheduler.LinearLR( + optimizer, + start_factor = 0.001, + total_iters = warmup_epochs, + ) + cosine_scheduler_model = torch.optim.lr_scheduler.CosineAnnealingLR( + optimizer, + T_max = epochs - warmup_epochs, + eta_min = scaled_train_lr / 100, + ) - device_type = self.device.type + scheduler_model = torch.optim.lr_scheduler.SequentialLR( + optimizer, + schedulers = [warmup_scheduler_model, cosine_scheduler_model], + milestones = [warmup_epochs], + ) + + scheduler_aux = torch.optim.lr_scheduler.CosineAnnealingLR( + aux_optimizer, + T_max = epochs, + eta_min = scaled_aux_lr / 100, + ) + + N = max(self.tomo_dataset.dims) + + device_type = self.device.type autocast_dtype = torch.bfloat16 if use_amp else None for epoch in range(epochs): - current_accumulation_steps = get_accumulation_steps(epoch) num_samples_per_ray = get_num_samples_per_ray(epoch) + # Log the change if it happens if epoch > 0: - prev_steps = get_accumulation_steps(epoch - 1) prev_samples = get_num_samples_per_ray(epoch - 1) - - if current_accumulation_steps != prev_steps and self.global_rank == 0: - print(f"Epoch {epoch}: Changing accumulation steps from {prev_steps} to {current_accumulation_steps}") + if num_samples_per_ray != prev_samples and self.global_rank == 0: print(f"Epoch {epoch}: Changing num_samples_per_ray from {prev_samples} to {num_samples_per_ray}") - if self.train_sampler is not None: - self.train_sampler.set_epoch(epoch) - + if self.sampler is not None: + self.sampler.set_epoch(epoch) + epoch_loss = 0.0 epoch_mse_loss = 0.0 epoch_tv_loss = 0.0 @@ -489,52 +452,60 @@ def train( target_values = batch['target_value'].to(self.device, non_blocking=True) phis = batch['phi'].to(self.device, non_blocking=True) projection_indices = batch['projection_idx'].to(self.device, non_blocking=True) - - shifts, z1_params, z3_params = self.aux_params(None) # NONE just for DDP + + shifts, z1_params, z3_params = aux_params(None) batch_shifts = torch.index_select(shifts, 0, projection_indices) batch_z1 = torch.index_select(z1_params, 0, projection_indices) batch_z3 = torch.index_select(z3_params, 0, projection_indices) - + with torch.autocast(device_type=device_type, dtype=autocast_dtype, enabled=use_amp): - with torch.no_grad(): # For every value of the tilts, shifts, and phi figure out where ray enters and exits and sample along that line. Ray has to be -1 to 1 + with torch.no_grad(): batch_ray_coords = self.create_batch_projection_rays( - pixel_i, pixel_j, self.tomo_dataset.dims[1], num_samples_per_ray + pixel_i, pixel_j, N, num_samples_per_ray ) - + transformed_rays = self.transform_batch_ray_coordinates( batch_ray_coords, z1=batch_z1, x=phis, z3=batch_z3, shifts=batch_shifts, - N=self.tomo_dataset.dims[1], - sampling_rate=1 # TODO: make input into train + N=N, + sampling_rate=1.0 ) - + all_coords = transformed_rays.view(-1, 3) + # TODO: torch.nn.functional.softplus here + # all_densities = torch.nn.functional.softplus(self.model(all_coords)) all_densities = self.model(all_coords) - + if all_densities.dim() > 1: all_densities = all_densities.squeeze(-1) # [N, 1] → [N] - valid_mask = ( # TODO: Needs to be fixed edge of boxes + + # Create mask for valid coordinates (within [-1, 1]^3) + valid_mask = ( (all_coords[:, 0] >= -1) & (all_coords[:, 0] <= 1) & (all_coords[:, 1] >= -1) & (all_coords[:, 1] <= 1) & (all_coords[:, 2] >= -1) & (all_coords[:, 2] <= 1) ).float() all_densities = all_densities * valid_mask + if tv_weight > 0: - num_tv_samples = min(10000, all_coords.shape[0]) tv_indices = torch.randperm(all_coords.shape[0], device=all_coords.device)[:num_tv_samples] - + + # Rerun forward for gradient tracking tv_coords = all_coords[tv_indices].detach().requires_grad_(True) - tv_densities_recomputed = self.model(tv_coords) + # TODO: torch.nn.functional.softplus here + # tv_densities_recomputed = torch.nn.functional.softplus(self.model(tv_coords)) # Get rid of this + tv_densities_recomputed = self.model(tv_coords) if tv_densities_recomputed.dim() > 1: tv_densities_recomputed = tv_densities_recomputed.squeeze(-1) - + + # Compute gradients grad_outputs = torch.autograd.grad( outputs=tv_densities_recomputed, inputs=tv_coords, @@ -542,40 +513,40 @@ def train( create_graph=True )[0] - grad_norm = torch.norm(grad_outputs, dim=1) tv_loss = tv_weight * grad_norm.mean() else: tv_loss = torch.tensor(0.0, device=self.device) - - ray_densities = all_densities.view(len(target_values), num_samples_per_ray) - + + ray_densities = all_densities.view(len(target_values), num_samples_per_ray) # Reshape rays and integarte step_size = 2.0 / (num_samples_per_ray - 1) - + predicted_values = ray_densities.sum(dim=1) * step_size - + mse_loss = torch.nn.functional.mse_loss(predicted_values, target_values) - + tv_loss_z1 = torch.tensor(0.0, device=self.device) - + # tv_loss_z1 = tv_loss_1d(z1_params, factor = 1e-3) + tv_loss_1d(z3_params, factor = 1e-3) + # tv_loss_z1 = 1e-5*(torch.sum(z1_params**2) + torch.sum(z3_params**2)) + + # Combine losses loss = mse_loss + tv_loss_z1 + tv_loss - loss = loss / current_accumulation_steps loss.backward() - + epoch_loss += loss.detach() epoch_mse_loss += mse_loss.detach() epoch_tv_loss += tv_loss.detach() epoch_z1_loss += tv_loss_z1.detach() - + num_batches += 1 + if epoch >= warmup_epochs: - model_norm = torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm = 1) optimizer.step() optimizer.zero_grad() - aux_norm = torch.nn.utils.clip_grad_norm_(self.aux_params.parameters(), max_norm = 1) + aux_norm = torch.nn.utils.clip_grad_norm_(aux_params.parameters(), max_norm = 1) aux_optimizer.step() aux_optimizer.zero_grad() else: @@ -584,15 +555,12 @@ def train( optimizer.zero_grad() aux_optimizer.zero_grad() - + scheduler_model.step() + if epoch >= warmup_epochs: scheduler_aux.step() - - if self.global_rank == 0: - print(f"Epoch Complete {epoch}") - - + if epoch % viz_freq == 0 or epoch == epochs - 1 or epoch % checkpoint_freq == 0 : with torch.no_grad(): pred_full = self.create_volume().cpu() @@ -600,14 +568,13 @@ def train( avg_mse_loss = epoch_mse_loss.item() / num_batches avg_tv_loss = epoch_tv_loss.item() / num_batches avg_z1_loss = epoch_z1_loss.item() / num_batches - - + metrics = torch.tensor([avg_loss, avg_mse_loss, avg_tv_loss, avg_z1_loss], device=self.device) if self.world_size > 1: torch.distributed.all_reduce(metrics, op=torch.distributed.ReduceOp.AVG) avg_loss, avg_mse_loss, avg_tv_loss, avg_z1_loss = metrics.tolist() - + # Logging and visualization if self.global_rank == 0 and (epoch % viz_freq == 0 or epoch == epochs - 1): with torch.no_grad(): current_lr = scheduler_model.get_last_lr()[0] @@ -615,7 +582,6 @@ def train( # Log metrics self.writer.add_scalar("train/mse_loss", avg_mse_loss, epoch) - self.writer.add_scalar("train/z1_loss", avg_z1_loss, epoch) if tv_weight > 0: self.writer.add_scalar("train/tv_loss", avg_tv_loss, epoch) @@ -629,28 +595,35 @@ def train( axes[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap='turbo', vmin=0) axes[0].set_title('Sum over Z-axis') - axes[1].matshow(pred_full[self.tomo_dataset.dims[1]//2].cpu().numpy(), cmap='turbo', vmin=0) - axes[1].set_title(f'Slice at Z={self.tomo_dataset.dims[1]//2}') + axes[1].matshow(pred_full[N//2].cpu().numpy(), cmap='turbo', vmin=0) + axes[1].set_title(f'Slice at Z={N//2}') - slice_start = max(0, self.tomo_dataset.dims[1]//2 - 5) - slice_end = min(self.tomo_dataset.dims[1], self.tomo_dataset.dims[1]//2 + 6) + slice_start = max(0, N//2 - 5) + slice_end = min(N, N//2 + 6) thick_slice = pred_full[slice_start:slice_end].sum(dim=0).cpu().numpy() axes[2].matshow(thick_slice, cmap='turbo', vmin=0) axes[2].set_title(f'Thick slice sum (Z={slice_start}:{slice_end-1})') self.writer.add_figure("train/viz", fig, epoch, close=True) plt.close(fig) - + if epoch % checkpoint_freq == 0 or epoch == epochs - 1: - with torch.no_grad(): - if self.global_rank == 0: - save_path = os.path.join("exp-logs", f"volume_epoch_{epoch:04d}.pt") - torch.save(pred_full.cpu(), save_path) - - self.save_checkpoint(epoch, self.aux_params) - + self.save_checkpoint(epoch, aux_params, log_path) + if self.global_rank == 0: print("Training complete.") - - - \ No newline at end of file + + def save_checkpoint(self, epoch, aux_params, log_path): + """Save model and auxiliary parameters checkpoint (rank 0 only).""" + if self.global_rank == 0: + model_to_save = self.model.module if hasattr(self.model, 'module') else self.model + aux_params_to_save = aux_params.module if hasattr(aux_params, 'module') else aux_params + + checkpoint = { + "model_state_dict": model_to_save.state_dict(), + "aux_params_state_dict": aux_params_to_save.state_dict(), + "epoch": epoch, + } + checkpoint_path = os.path.join(log_path, f"checkpoint_epoch_{epoch:04d}.pt") + torch.save(checkpoint, checkpoint_path) + print(f"Checkpoint saved at epoch {epoch}: {checkpoint_path}") \ No newline at end of file diff --git a/src/quantem/tomography/tomography_nerf_old.py b/src/quantem/tomography/tomography_nerf_old.py new file mode 100644 index 00000000..adecfd6f --- /dev/null +++ b/src/quantem/tomography/tomography_nerf_old.py @@ -0,0 +1,658 @@ +from torch.utils.data import Dataset, DataLoader, DistributedSampler +import torch +import torch.distributed as dist +import numpy as np + +from quantem.tomography.tomography_dataset import TomographyDataset +from quantem.tomography.models import HSiren + +from torch.utils.tensorboard import SummaryWriter +import matplotlib.pyplot as plt + + +import os + +def get_accumulation_steps(epoch): + """Increase accumulation steps at specific epochs.""" + # schedule = config.get("accumulation_schedule", { + # 0: 1, + # 200: 4, + # 500: 28, + # }) + + schedule = { + 0: 1, + 200: 4, + 500: 28, + } + + accumulation_steps = 1 + for epoch_threshold, steps in sorted(schedule.items()): + if epoch >= epoch_threshold: + accumulation_steps = steps + + return accumulation_steps + +def get_num_samples_per_ray(epoch): + """Increase number of samples per ray at specific epochs.""" + # schedule = config.get("num_samples_schedule", { + # 0: 64, + # 200: 128, + # 500: 256, + # }) + + # TODO: Maybe delete + schedule = { + 0: 25, + 2: 50, + 4: 100, + 6: 200, + } + + num_samples = 64 + for epoch_threshold, samples in sorted(schedule.items()): + if epoch >= epoch_threshold: + num_samples = samples + + return num_samples + +class AuxiliaryParams(torch.nn.Module): + def __init__(self, num_tilts, device, zero_tilt_idx=None): + super().__init__() + + if zero_tilt_idx is None: + # If not provided, assume first projection is reference + zero_tilt_idx = 0 + + self.zero_tilt_idx = zero_tilt_idx + self.num_tilts = num_tilts + + # Shifts: only parameterize non-reference tilts + num_param_tilts = num_tilts - 1 + self.shifts_param = torch.nn.Parameter(torch.zeros(num_param_tilts, 2, device=device)) + + # Fixed zero shifts for reference + self.shifts_ref = torch.zeros(1, 2, device=device) + + # Z1 and Z3: parameterize all tilts EXCEPT the reference + self.z1_param = torch.nn.Parameter(torch.zeros(num_param_tilts, device=device)) + # self.z3_param = torch.nn.Parameter(torch.zeros(num_param_tilts, device=device)) + + # Fixed zeros for reference tilt + self.z1_ref = torch.zeros(1, device=device) + # self.z3_ref = torch.zeros(1, device=device) + + def forward(self, dummy_input=None): + # Reconstruct full arrays with zeros at reference position + before_shifts = self.shifts_param[:self.zero_tilt_idx] + after_shifts = self.shifts_param[self.zero_tilt_idx:] + shifts = torch.cat([before_shifts, self.shifts_ref, after_shifts], dim=0) + + before_z1 = self.z1_param[:self.zero_tilt_idx] + after_z1 = self.z1_param[self.zero_tilt_idx:] + z1 = torch.cat([before_z1, self.z1_ref, after_z1], dim=0) + + # before_z3 = self.z3_param[:self.zero_tilt_idx] + # after_z3 = self.z3_param[self.zero_tilt_idx:] + # z3 = torch.cat([before_z3, self.z3_ref, after_z3], dim=0) + + return shifts, z1, -z1 + + +class TomographyNerf(): + + def __init__( + self, + tomo_dataset: TomographyDataset, + + ): + + self.setup_distributed() + self.tomo_dataset = tomo_dataset + self.setup_logging() + + def setup_logging(self): + """Setup logging directory and TensorBoard writer.""" + + # base_logdir = self.config.get("logdir", "exp-logs") + + # hidden_features = self.config["hidden_features"] + # hidden_layers = self.config["hidden_layers"] + # lr = self.config["train_lr"] + # aux_lr = self.config["aux_lr"] + # bs = self.config["ray_batch_size"] + # fbs = self.config["fbs"] + # tv = self.config["tv_factor"] + + # lr_str = f"{lr:.1e}" + # tv_str = f"{tv:.1e}" + # aux_lr_str = f"{aux_lr:.1e}" + + + # descriptive_name = f"h{hidden_features}_l{hidden_layers}_lr{lr_str}_auxlr{aux_lr_str}_bs{bs}_tv{tv_str}_omega{fbs}" + # self.logdir = os.path.join(base_logdir, descriptive_name) + + if self.global_rank == 0: + os.makedirs("exp-logs", exist_ok=True) + self.writer = SummaryWriter() # Should put a logdir here + + # config_path = os.path.join(self.logdir, "config.yaml") + # with open(config_path, "w") as f: + # yaml.dump(self.config, f) + + print(f"Logging to: Need to fix") + else: + self.writer = None + def setup_distributed(self): + + """Setup distributed training if available, otherwise use single GPU/CPU.""" + + # Check if we're in a distributed environment + + + if "RANK" in os.environ: + # Distributed training + if not dist.is_initialized(): + dist.init_process_group(backend='nccl' if torch.cuda.is_available() else 'gloo', init_method='env://') + + self.world_size = dist.get_world_size() + self.global_rank = dist.get_rank() + print(f"Global rank: {self.global_rank}") + self.local_rank = int(os.environ["LOCAL_RANK"]) + + torch.cuda.set_device(self.local_rank) + self.device = torch.device("cuda", self.local_rank) + + else: + # Single GPU/CPU training + self.world_size = 1 + self.global_rank = 0 + self.local_rank = 0 + + if torch.cuda.is_available(): + self.device = torch.device("cuda:0") + torch.cuda.set_device(0) + print("Single GPU training") + else: + self.device = torch.device("cpu") + print("CPU training") + + # Optional performance optimizations (only for CUDA) + if self.device.type == "cuda": + torch.backends.cudnn.benchmark = True + torch.backends.cuda.matmul.allow_tf32 = True + torch.backends.cudnn.allow_tf32 = True + + def create_volume(self): + N = self.tomo_dataset.dims[1] + with torch.no_grad(): + coords_1d = torch.linspace(-1, 1, N) + x, y, z = torch.meshgrid(coords_1d, coords_1d, coords_1d, indexing='ij') + inputs = torch.stack([x, y, z], dim=-1).reshape(-1, 3) + + # Get the underlying model if using DDP + model = self.model.module if hasattr(self.model, 'module') else self.model + + samples_per_gpu = N**3 // self.world_size + start_idx = self.global_rank * samples_per_gpu + end_idx = start_idx + samples_per_gpu + + inputs_subset = inputs[start_idx:end_idx].to(self.device) + outputs = model(inputs_subset) + + if outputs.dim() > 1: + outputs = outputs.squeeze(-1) + + if self.world_size > 1: + # Ensure consistent tensor sizes across all ranks + gathered_outputs = [torch.empty_like(outputs) for _ in range(self.world_size)] + dist.all_gather(gathered_outputs, outputs.contiguous()) + + # Explicitly order by rank + pred_full = torch.cat(gathered_outputs, dim=0).reshape(N, N, N).float() + else: + pred_full = outputs.reshape(N, N, N).float() + + return pred_full + + def get_scaled_lr(self, base_lr, scaling_rule="sqrt"): + """Scale learning rate based on world size.""" + if self.world_size == 1: + return base_lr + + if scaling_rule == "linear": + # Linear scaling: lr = base_lr * world_size + return base_lr * self.world_size + elif scaling_rule == "sqrt": + # Square root scaling: lr = base_lr * sqrt(world_size) + return base_lr * np.sqrt(self.world_size) + else: + return base_lr + + + @torch.compile(mode="reduce-overhead") + def transform_batch_ray_coordinates(self, rays, z1, x, z3, shifts, N, sampling_rate): + + # Step 1: Apply shifts + shift_x_norm = (shifts[:, 0:1] * sampling_rate * 2) / (N - 1) + shift_y_norm = (shifts[:, 1:2] * sampling_rate * 2) / (N - 1) + + rays_x = rays[:, :, 0] - shift_x_norm + rays_y = rays[:, :, 1] - shift_y_norm + rays_z = rays[:, :, 2] + + # Rotation 1: Z(-z3) + theta = torch.deg2rad(-z3).view(-1, 1) + cos_t = torch.cos(theta) + sin_t = torch.sin(theta) + + rays_x_rot1 = cos_t * rays_x - sin_t * rays_y + rays_y_rot1 = sin_t * rays_x + cos_t * rays_y + rays_z_rot1 = rays_z + + # Rotation 2: X(x) + theta = torch.deg2rad(x).view(-1, 1) + cos_t = torch.cos(theta) + sin_t = torch.sin(theta) + + rays_x_rot2 = rays_x_rot1 + rays_y_rot2 = cos_t * rays_y_rot1 - sin_t * rays_z_rot1 + rays_z_rot2 = sin_t * rays_y_rot1 + cos_t * rays_z_rot1 + + # Rotation 3: Z(-z1) + theta = torch.deg2rad(-z1).view(-1, 1) + cos_t = torch.cos(theta) + sin_t = torch.sin(theta) + + rays_x_final = cos_t * rays_x_rot2 - sin_t * rays_y_rot2 + rays_y_final = sin_t * rays_x_rot2 + cos_t * rays_y_rot2 + rays_z_final = rays_z_rot2 + + # Stack the final result + transformed_rays = torch.stack([rays_x_final, rays_y_final, rays_z_final], dim=2) + + return transformed_rays + + def save_checkpoint(self, epoch, aux_params): + """Save model and auxiliary parameters checkpoint (rank 0 only).""" + if self.global_rank == 0: + model_to_save = self.model.module if hasattr(self.model, 'module') else self.model + aux_params_to_save = aux_params.module if hasattr(aux_params, 'module') else aux_params + + checkpoint = { + "model_state_dict": model_to_save.state_dict(), + "aux_params_state_dict": aux_params_to_save.state_dict(), + "epoch": epoch, + # "config": self.config, + } + checkpoint_path = os.path.join("exp-logs", f"checkpoint_epoch_{epoch:04d}.pt") + torch.save(checkpoint, checkpoint_path) + print(f"Checkpoint saved at epoch {epoch}: {checkpoint_path}") + + def create_batch_projection_rays(self, pixel_i, pixel_j, N, num_samples_per_ray): + """Create projection rays for entire batch simultaneously.""" + batch_size = len(pixel_i) + + # Convert all pixels to normalized coordinates + x_coords = (pixel_j / (N - 1)) * 2 - 1 + y_coords = (pixel_i / (N - 1)) * 2 - 1 + + # Create z coordinates + z_coords = torch.linspace(-1, 1, num_samples_per_ray, device=self.device) + + # Create rays for all pixels: [batch_size, num_samples_per_ray, 3] + rays = torch.zeros(batch_size, num_samples_per_ray, 3, device=self.device) + + # Fill coordinates efficiently + rays[:, :, 0] = x_coords.unsqueeze(1) # x constant per ray + rays[:, :, 1] = y_coords.unsqueeze(1) # y constant per ray + rays[:, :, 2] = z_coords.unsqueeze(0) # z varies along ray + + return rays + + def train( + self, + batch_size: int, + num_workers: int, + omega_0 : int, + hidden_omega_0 : int, + train_lr : float, + aux_lr : float, + warmup_epochs : int, + epochs : int, + use_amp : bool, + sampling_rate : float, + tv_weight : float, + viz_freq : int, + checkpoint_freq : int, + # hidden_layers: int, + # hidden_features: int, + # alpha: float, + ): + + ### DataLoader setup + batch_size = batch_size + num_workers = num_workers + + if self.world_size > 1: + self.train_sampler = DistributedSampler( + self.tomo_dataset, + num_replicas=self.world_size, + rank=self.global_rank, + shuffle=False + ) + shuffle = False + else: + self.train_sampler = None + shuffle = True # Need this!! + + self.dataloader = DataLoader( + self.tomo_dataset, + batch_size=batch_size, + sampler=self.train_sampler, + shuffle=shuffle, + num_workers=num_workers, + pin_memory=True if self.device.type == "cuda" else False, + drop_last=True, + persistent_workers=False if num_workers == 0 else True + ) + + # Print statistics + if self.global_rank == 0: + # total_pixels = len(self.tomo_dataset.tilt_angles) * self.config["N"] ** 2 + train_pixels = self.tomo_dataset.num_pixels + effective_batch_size = batch_size * self.world_size + + print(f"Dataloader setup complete:") + print(f" Total projections: {len(self.tomo_dataset.tilt_angles)}") + print(f" Grid size: {self.tomo_dataset.dims[1]}{self.tomo_dataset.dims[2]}") + print(f" Total pixels: {train_pixels:,}") + + print(f" Local batch size (train): {batch_size}") + print(f" Global batch size: {effective_batch_size}") + print(f" Train batches per GPU per epoch: {len(self.dataloader)}") + + + ### + + ### Model Setup + + self.model = HSiren( + in_features = 3, + out_features = 1, + hidden_features = 512, + hidden_layers = 4, + first_omega_0 = 30, + hidden_omega_0 = 30, # Remove hidden_omega_0. + ).to(self.device) + + if self.world_size > 1: + self.model = torch.nn.parallel.DistributedDataParallel( + self.model, + device_ids=[self.local_rank], + output_device=self.local_rank, + find_unused_parameters=False, + bucket_cap_mb=100, + gradient_as_bucket_view=True, # TODO: Check if this is needed. + ) + if self.global_rank == 0: + print("Model wrapped with DDP") + + if self.world_size > 1: + + if self.global_rank == 0: + print("Model built, distributed, and compiled successfully.") + else: + print("Model built and compiled successfully.") + + ### Initializing optimizers, and auxiliary parameters + + # Torch module on z1, z3, and shifts as module + + phis = self.tomo_dataset.tilt_angles + zero_tilt_idx = torch.argmin(torch.abs(phis)).item() + + if self.global_rank == 0: + print(f"Using projection {zero_tilt_idx} (angle={phis[zero_tilt_idx]:.2f}°) as reference") + + + self.aux_params = AuxiliaryParams( + num_tilts = len(phis), + device = self.device, + zero_tilt_idx = zero_tilt_idx + ) + + + if self.world_size > 1: + self.aux_params = torch.nn.parallel.DistributedDataParallel( + self.aux_params, + device_ids=[self.local_rank], + output_device=self.local_rank, + find_unused_parameters=False, + broadcast_buffers=True + ) + if self.global_rank == 0: + print("Auxiliary parameters wrapped with DDP") + + # Learning rates + scaled_train_lr = train_lr*np.sqrt(self.world_size) + scaled_aux_lr = aux_lr*np.sqrt(self.world_size) + + # Optimizers + optimizer = torch.optim.Adam(self.model.parameters(), lr=scaled_train_lr, fused = True) + aux_optimizer = torch.optim.Adam(self.aux_params.parameters(), lr=scaled_aux_lr) + + aux_norm = torch.tensor(0.0, device=self.device) + model_norm = torch.tensor(0.0, device=self.device) + + optimizer.zero_grad() + aux_optimizer.zero_grad() + + warmup_scheduler_model = torch.optim.lr_scheduler.LinearLR(optimizer, start_factor=0.01, total_iters=warmup_epochs) # TODO: Start factor play around with this. + cosine_scheduler_model = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs - warmup_epochs, eta_min=scaled_train_lr/100) + scheduler_model = torch.optim.lr_scheduler.SequentialLR(optimizer, schedulers=[warmup_scheduler_model, cosine_scheduler_model], milestones=[warmup_epochs]) + + scheduler_aux = torch.optim.lr_scheduler.CosineAnnealingLR(aux_optimizer, T_max=epochs, eta_min=scaled_aux_lr/100) + + device_type = self.device.type + autocast_dtype = torch.bfloat16 if use_amp else None + + for epoch in range(epochs): + current_accumulation_steps = get_accumulation_steps(epoch) + num_samples_per_ray = get_num_samples_per_ray(epoch) + + if epoch > 0: + prev_steps = get_accumulation_steps(epoch - 1) + prev_samples = get_num_samples_per_ray(epoch - 1) + + if current_accumulation_steps != prev_steps and self.global_rank == 0: + print(f"Epoch {epoch}: Changing accumulation steps from {prev_steps} to {current_accumulation_steps}") + if num_samples_per_ray != prev_samples and self.global_rank == 0: + print(f"Epoch {epoch}: Changing num_samples_per_ray from {prev_samples} to {num_samples_per_ray}") + + if self.train_sampler is not None: + self.train_sampler.set_epoch(epoch) + + self.model.train() + epoch_loss = 0.0 + epoch_mse_loss = 0.0 + epoch_tv_loss = 0.0 + epoch_z1_loss = 0.0 + + num_batches = 0 + + for batch_idx, batch in enumerate(self.dataloader): + + projection_indices = batch['projection_idx'] + + pixel_i = batch['pixel_i'].float().to(self.device, non_blocking=True) + pixel_j = batch['pixel_j'].float().to(self.device, non_blocking=True) + target_values = batch['target_value'].to(self.device, non_blocking=True) + phis = batch['phi'].to(self.device, non_blocking=True) + projection_indices = batch['projection_idx'].to(self.device, non_blocking=True) + + shifts, z1_params, z3_params = self.aux_params(None) # NONE just for DDP + batch_shifts = torch.index_select(shifts, 0, projection_indices) + batch_z1 = torch.index_select(z1_params, 0, projection_indices) + batch_z3 = torch.index_select(z3_params, 0, projection_indices) + + with torch.autocast(device_type=device_type, dtype=autocast_dtype, enabled=use_amp): + + with torch.no_grad(): # For every value of the tilts, shifts, and phi figure out where ray enters and exits and sample along that line. Ray has to be -1 to 1 + batch_ray_coords = self.create_batch_projection_rays( + pixel_i, pixel_j, self.tomo_dataset.dims[1], num_samples_per_ray + ) + + transformed_rays = self.transform_batch_ray_coordinates( + batch_ray_coords, + z1=batch_z1, + x=phis, + z3=batch_z3, + shifts=batch_shifts, + N=self.tomo_dataset.dims[1], + sampling_rate=1 # TODO: make input into train + ) + + all_coords = transformed_rays.view(-1, 3) + all_densities = self.model(all_coords) + + if all_densities.dim() > 1: + all_densities = all_densities.squeeze(-1) # [N, 1] → [N] + + valid_mask = ( # TODO: Needs to be fixed edge of boxes + (all_coords[:, 0] >= -1) & (all_coords[:, 0] <= 1) & + (all_coords[:, 1] >= -1) & (all_coords[:, 1] <= 1) & + (all_coords[:, 2] >= -1) & (all_coords[:, 2] <= 1) + ).float() + + all_densities = all_densities * valid_mask + if tv_weight > 0: + + num_tv_samples = min(10000, all_coords.shape[0]) + tv_indices = torch.randperm(all_coords.shape[0], device=all_coords.device)[:num_tv_samples] + + tv_coords = all_coords[tv_indices].detach().requires_grad_(True) + tv_densities_recomputed = self.model(tv_coords) + + if tv_densities_recomputed.dim() > 1: + tv_densities_recomputed = tv_densities_recomputed.squeeze(-1) + + grad_outputs = torch.autograd.grad( + outputs=tv_densities_recomputed, + inputs=tv_coords, + grad_outputs=torch.ones_like(tv_densities_recomputed), + create_graph=True + )[0] + + + grad_norm = torch.norm(grad_outputs, dim=1) + tv_loss = tv_weight * grad_norm.mean() + else: + tv_loss = torch.tensor(0.0, device=self.device) + + ray_densities = all_densities.view(len(target_values), num_samples_per_ray) + + step_size = 2.0 / (num_samples_per_ray - 1) + + predicted_values = ray_densities.sum(dim=1) * step_size + + mse_loss = torch.nn.functional.mse_loss(predicted_values, target_values) + + tv_loss_z1 = torch.tensor(0.0, device=self.device) + + loss = mse_loss + tv_loss_z1 + tv_loss + loss = loss / current_accumulation_steps + + loss.backward() + + epoch_loss += loss.detach() + epoch_mse_loss += mse_loss.detach() + epoch_tv_loss += tv_loss.detach() + epoch_z1_loss += tv_loss_z1.detach() + + num_batches += 1 + if epoch >= warmup_epochs: + + model_norm = torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm = 1) + optimizer.step() + optimizer.zero_grad() + + aux_norm = torch.nn.utils.clip_grad_norm_(self.aux_params.parameters(), max_norm = 1) + aux_optimizer.step() + aux_optimizer.zero_grad() + else: + model_norm = torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm = 1) + optimizer.step() + optimizer.zero_grad() + + aux_optimizer.zero_grad() + + scheduler_model.step() + if epoch >= warmup_epochs: + scheduler_aux.step() + + if self.global_rank == 0: + print(f"Epoch Complete {epoch}") + + + if epoch % viz_freq == 0 or epoch == epochs - 1 or epoch % checkpoint_freq == 0 : + with torch.no_grad(): + pred_full = self.create_volume().cpu() + avg_loss = epoch_loss.item() / num_batches + avg_mse_loss = epoch_mse_loss.item() / num_batches + avg_tv_loss = epoch_tv_loss.item() / num_batches + avg_z1_loss = epoch_z1_loss.item() / num_batches + + + metrics = torch.tensor([avg_loss, avg_mse_loss, avg_tv_loss, avg_z1_loss], device=self.device) + if self.world_size > 1: + torch.distributed.all_reduce(metrics, op=torch.distributed.ReduceOp.AVG) + avg_loss, avg_mse_loss, avg_tv_loss, avg_z1_loss = metrics.tolist() + + + if self.global_rank == 0 and (epoch % viz_freq == 0 or epoch == epochs - 1): + with torch.no_grad(): + current_lr = scheduler_model.get_last_lr()[0] + + # Log metrics + self.writer.add_scalar("train/mse_loss", avg_mse_loss, epoch) + + + self.writer.add_scalar("train/z1_loss", avg_z1_loss, epoch) + if tv_weight > 0: + self.writer.add_scalar("train/tv_loss", avg_tv_loss, epoch) + self.writer.add_scalar("train/total_loss", avg_loss, epoch) + self.writer.add_scalar("train/model_grad_norm", model_norm.item(), epoch) + self.writer.add_scalar("train/aux_grad_norm", aux_norm.item(), epoch) + self.writer.add_scalar("train/lr", current_lr, epoch) + self.writer.add_scalar("train/num_samples_per_ray", num_samples_per_ray, epoch) + + fig, axes = plt.subplots(1, 3, figsize=(36, 12)) + axes[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap='turbo', vmin=0) + axes[0].set_title('Sum over Z-axis') + + axes[1].matshow(pred_full[self.tomo_dataset.dims[1]//2].cpu().numpy(), cmap='turbo', vmin=0) + axes[1].set_title(f'Slice at Z={self.tomo_dataset.dims[1]//2}') + + slice_start = max(0, self.tomo_dataset.dims[1]//2 - 5) + slice_end = min(self.tomo_dataset.dims[1], self.tomo_dataset.dims[1]//2 + 6) + thick_slice = pred_full[slice_start:slice_end].sum(dim=0).cpu().numpy() + axes[2].matshow(thick_slice, cmap='turbo', vmin=0) + axes[2].set_title(f'Thick slice sum (Z={slice_start}:{slice_end-1})') + + self.writer.add_figure("train/viz", fig, epoch, close=True) + plt.close(fig) + + if epoch % checkpoint_freq == 0 or epoch == epochs - 1: + with torch.no_grad(): + if self.global_rank == 0: + save_path = os.path.join("exp-logs", f"volume_epoch_{epoch:04d}.pt") + torch.save(pred_full.cpu(), save_path) + + self.save_checkpoint(epoch, self.aux_params) + + if self.global_rank == 0: + print("Training complete.") + + + \ No newline at end of file From a6a701458b6c857ee71b2f0aa1228c288785358c Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 22 Sep 2025 10:12:44 -0700 Subject: [PATCH 006/335] Pre-transferring to quantem code --- src/quantem/tomography/object_models.py | 11 +++++++++++ src/quantem/tomography/tomography.py | 4 +++- src/quantem/tomography/tomography_dataset.py | 7 ++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index d103087d..00f9a901 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -536,5 +536,16 @@ def _pretrain( self._pretrain_lrs.append(optimizer.param_groups[0]["lr"]) pbar.set_description(f"Epoch {a0 + 1}/{num_epochs}, Loss: {loss.item():.4f}, ") +class ObjectINN(ObjectConstraints): + """ + Object model for INN objects. + + - VolumeDataset (?) dependent on the object + """ + + def __init__( + self + ): + pass ObjectModelType = Union[ObjectVoxelwise] # | ObjectDIP | ObjectImplicit (ObjectFFN?) diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index ef70d5f2..b144d4a2 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -90,7 +90,9 @@ def sirt_recon( if plot_loss: self.plot_loss() - + + # TODO: ML Recon which has NeRF and AD depending on the object type. + def ad_recon( self, optimizer_params: dict, diff --git a/src/quantem/tomography/tomography_dataset.py b/src/quantem/tomography/tomography_dataset.py index 348964ad..251538d0 100644 --- a/src/quantem/tomography/tomography_dataset.py +++ b/src/quantem/tomography/tomography_dataset.py @@ -11,6 +11,7 @@ from torch.utils.data import Dataset +import numpy as np class TomographyDataset(AutoSerialize, Dataset): _token = object() @@ -51,7 +52,10 @@ def __init__( # Enforce normalization of tilt series - tilt_percentile = torch.quantile(self._tilt_series, .95) + try: + tilt_percentile = torch.quantile(self._tilt_series, .95) + except: + tilt_percentile = np.quantile(self._tilt_series, .95) self._tilt_series = self._tilt_series / tilt_percentile self._tilt_series = torch.clamp(self._tilt_series, min=0) @@ -186,6 +190,7 @@ def __getitem__(self, idx): if self.dims[1] != self.dims[2]: raise NotImplementedError("Non-square tilt images are not supported yet.") + #TODO: row, column pixel_i = remaining // self.dims[1] pixel_j = remaining % self.dims[1] From e162dde36dbf6c2a4a8fdfe522335af3268273b9 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 22 Sep 2025 17:37:58 -0700 Subject: [PATCH 007/335] Baseline working Nerf reconstructions; created a TomoDDP class --- src/quantem/tomography/tomography.py | 460 +++++++++++++++++++++- src/quantem/tomography/tomography_base.py | 2 +- src/quantem/tomography/tomography_ddp.py | 128 ++++++ 3 files changed, 587 insertions(+), 3 deletions(-) create mode 100644 src/quantem/tomography/tomography_ddp.py diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index b144d4a2..e9e9cc35 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -7,10 +7,76 @@ from quantem.tomography.tomography_base import TomographyBase from quantem.tomography.tomography_conv import TomographyConv from quantem.tomography.tomography_ml import TomographyML +from quantem.tomography.tomography_ddp import TomographyDDP from quantem.tomography.utils import differentiable_shift_2d, gaussian_kernel_1d, rot_ZXZ - -class Tomography(TomographyConv, TomographyML, TomographyBase): +# Temporary imports for TomographyNERF +from quantem.tomography.models import HSiren +from torch.utils.tensorboard import SummaryWriter +import numpy as np +import matplotlib.pyplot as plt +import torch.distributed as dist + +# Temporary aux class for TomographyNERF +def get_num_samples_per_ray(epoch): + """Increase number of samples per ray at specific epochs.""" + schedule = { + 0: 25, + 2: 50, + 4: 100, + 6: 200, + } + + num_samples = 64 + for epoch_threshold, samples in sorted(schedule.items()): + if epoch >= epoch_threshold: + num_samples = samples + + return num_samples + +class AuxiliaryParams(torch.nn.Module): + def __init__(self, num_tilts, device, zero_tilt_idx=None): + super().__init__() + + if zero_tilt_idx is None: + # If not provided, assume first projection is reference + zero_tilt_idx = 0 + + self.zero_tilt_idx = zero_tilt_idx + self.num_tilts = num_tilts + + # Shifts: only parameterize non-reference tilts + num_param_tilts = num_tilts - 1 + self.shifts_param = torch.nn.Parameter(torch.zeros(num_param_tilts, 2, device=device)) + + # Fixed zero shifts for reference + self.shifts_ref = torch.zeros(1, 2, device=device) + + # Z1 and Z3: parameterize all tilts EXCEPT the reference + self.z1_param = torch.nn.Parameter(torch.zeros(num_param_tilts, device=device)) + # self.z3_param = torch.nn.Parameter(torch.zeros(num_param_tilts, device=device)) + + # Fixed zeros for reference tilt + self.z1_ref = torch.zeros(1, device=device) + # self.z3_ref = torch.zeros(1, device=device) + + def forward(self, dummy_input=None): + # Reconstruct full arrays with zeros at reference position + before_shifts = self.shifts_param[:self.zero_tilt_idx] + after_shifts = self.shifts_param[self.zero_tilt_idx:] + shifts = torch.cat([before_shifts, self.shifts_ref, after_shifts], dim=0) + + before_z1 = self.z1_param[:self.zero_tilt_idx] + after_z1 = self.z1_param[self.zero_tilt_idx:] + z1 = torch.cat([before_z1, self.z1_ref, after_z1], dim=0) + + # before_z3 = self.z3_param[:self.zero_tilt_idx] + # after_z3 = self.z3_param[self.zero_tilt_idx:] + # z3 = torch.cat([before_z3, self.z3_ref, after_z3], dim=0) + + return shifts, z1, -z1 + +class Tomography(TomographyConv, TomographyML, TomographyBase, TomographyDDP): """ Top level class for either using conventional or ML-based reconstruction methods for tomography. @@ -92,7 +158,397 @@ def sirt_recon( self.plot_loss() # TODO: ML Recon which has NeRF and AD depending on the object type. + # TODO: Temporary + def get_scaled_lr(self, base_lr, scaling_rule="sqrt"): + if scaling_rule == "sqrt": + return base_lr * np.sqrt(self.world_size) + elif scaling_rule == "linear": + return base_lr * self.world_size + else: + raise ValueError(f"Invalid scaling rule: {scaling_rule}") + + def create_optimizer(self, params, lr, fused = True): + + return torch.optim.Adam( + params, + lr = lr, + fused = fused, + ) + + def create_batch_projection_rays(self, pixel_i, pixel_j, N, num_samples_per_ray): + """Create projection rays for entire batch simultaneously.""" + batch_size = len(pixel_i) + + # Convert all pixels to normalized coordinates + x_coords = (pixel_j / (N - 1)) * 2 - 1 + y_coords = (pixel_i / (N - 1)) * 2 - 1 + + # Create z coordinates + z_coords = torch.linspace(-1, 1, num_samples_per_ray, device=self.device) + + # Create rays for all pixels: [batch_size, num_samples_per_ray, 3] + rays = torch.zeros(batch_size, num_samples_per_ray, 3, device=self.device) + + # Fill coordinates efficiently + rays[:, :, 0] = x_coords.unsqueeze(1) # x constant per ray + rays[:, :, 1] = y_coords.unsqueeze(1) # y constant per ray + rays[:, :, 2] = z_coords.unsqueeze(0) # z varies along ray + + return rays + @torch.compile(mode="reduce-overhead") + def transform_batch_ray_coordinates(self, rays, z1, x, z3, shifts, N, sampling_rate): + + # Step 1: Apply shifts + shift_x_norm = (shifts[:, 0:1] * sampling_rate * 2) / (N - 1) + shift_y_norm = (shifts[:, 1:2] * sampling_rate * 2) / (N - 1) + + rays_x = rays[:, :, 0] - shift_x_norm + rays_y = rays[:, :, 1] - shift_y_norm + rays_z = rays[:, :, 2] + + # Rotation 1: Z(-z3) + theta = torch.deg2rad(-z3).view(-1, 1) + cos_t = torch.cos(theta) + sin_t = torch.sin(theta) + + rays_x_rot1 = cos_t * rays_x - sin_t * rays_y + rays_y_rot1 = sin_t * rays_x + cos_t * rays_y + rays_z_rot1 = rays_z + + # Rotation 2: X(x) + theta = torch.deg2rad(x).view(-1, 1) + cos_t = torch.cos(theta) + sin_t = torch.sin(theta) + + rays_x_rot2 = rays_x_rot1 + rays_y_rot2 = cos_t * rays_y_rot1 - sin_t * rays_z_rot1 + rays_z_rot2 = sin_t * rays_y_rot1 + cos_t * rays_z_rot1 + + # Rotation 3: Z(-z1) + theta = torch.deg2rad(-z1).view(-1, 1) + cos_t = torch.cos(theta) + sin_t = torch.sin(theta) + + rays_x_final = cos_t * rays_x_rot2 - sin_t * rays_y_rot2 + rays_y_final = sin_t * rays_x_rot2 + cos_t * rays_y_rot2 + rays_z_final = rays_z_rot2 + + # Stack the final result + transformed_rays = torch.stack([rays_x_final, rays_y_final, rays_z_final], dim=2) + + return transformed_rays + + # --- Creating Volume --- + def create_volume(self): + + N = max(self.dataset.dims) + + with torch.no_grad(): + coords_1d = torch.linspace(-1, 1, N) + x, y, z = torch.meshgrid(coords_1d, coords_1d, coords_1d, indexing='ij') + inputs = torch.stack([x, y, z], dim=-1).reshape(-1, 3) + + model = self.model.module if hasattr(self.model, 'module') else self.model + + samples_per_gpu = N**3 // self.world_size + start_idx = self.global_rank * samples_per_gpu + end_idx = start_idx + samples_per_gpu + + inputs_subset = inputs[start_idx:end_idx].to(self.device) + + # TODO: torch.nn.functional.softplus here + # outputs = torch.nn.functional.softplus(model(inputs_subset)) + outputs = model(inputs_subset) + + if outputs.dim() > 1: + outputs = outputs.squeeze(-1) + + if self.world_size > 1: + gathered_outputs = [torch.empty_like(outputs) for _ in range(self.world_size)] + dist.all_gather(gathered_outputs, outputs.contiguous()) + + pred_full = torch.cat(gathered_outputs, dim=0).reshape(N, N, N).float() + else: + pred_full = outputs.reshape(N, N, N).float() + + return pred_full + + def recon( + self, + model: HSiren, + batch_size: int, + num_workers: int = 0, + train_lr = 1e-5, + aux_lr = 1e-5, + epochs = 20, + warmup_epochs = 10, + use_amp = True, + tv_weight = 0.0, + viz_freq = 1, + checkpoint_freq = 5, + ): + + + + self.setup_distributed() + self.setup_dataloader(self.dataset, batch_size, num_workers) + self.build_model(model) + # TODO: Temp-logger + if self.global_rank == 0: + logger = SummaryWriter() + else: + logger = None + zero_tilt_idx = torch.argmin(torch.abs(self.dataset.tilt_angles)).item() + + if self.global_rank == 0: + print(f"Using projection {zero_tilt_idx} (angle={self.dataset.tilt_angles[zero_tilt_idx]:.2f}°) as reference") + + aux_params = AuxiliaryParams( + num_tilts = len(self.dataset.tilt_angles), + device = self.device, + zero_tilt_idx = zero_tilt_idx, + ) + + scaled_train_lr = self.get_scaled_lr(train_lr, scaling_rule="sqrt") + scaled_aux_lr = self.get_scaled_lr(aux_lr, scaling_rule="sqrt") + + optimizer = self.create_optimizer( + self.model.parameters(), + scaled_train_lr, + fused = True, + ) + + aux_optimizer = self.create_optimizer( + aux_params.parameters(), + scaled_aux_lr, + fused = True, + ) + + aux_norm = torch.tensor(0.0, device=self.device) + model_norm = torch.tensor(0.0, device=self.device) + + optimizer.zero_grad() + aux_optimizer.zero_grad() + + + warmup_scheduler_model = torch.optim.lr_scheduler.LinearLR( + optimizer, + start_factor = 0.001, + total_iters = warmup_epochs, + ) + + + cosine_scheduler_model = torch.optim.lr_scheduler.CosineAnnealingLR( + optimizer, + T_max = epochs - warmup_epochs, + eta_min = scaled_train_lr / 100, + ) + + + scheduler_model = torch.optim.lr_scheduler.SequentialLR( + optimizer, + schedulers = [warmup_scheduler_model, cosine_scheduler_model], + milestones = [warmup_epochs], + ) + + + scheduler_aux = torch.optim.lr_scheduler.CosineAnnealingLR( + aux_optimizer, + T_max = epochs, + eta_min = scaled_aux_lr / 100, + ) + + N = max(self.dataset.dims) + + device_type = self.device.type + autocast_dtype = torch.bfloat16 if use_amp else None + + + for epoch in range(epochs): + num_samples_per_ray = get_num_samples_per_ray(epoch) + + # Log the change if it happens + if epoch > 0: + prev_samples = get_num_samples_per_ray(epoch - 1) + + if num_samples_per_ray != prev_samples and self.global_rank == 0: + print(f"Epoch {epoch}: Changing num_samples_per_ray from {prev_samples} to {num_samples_per_ray}") + + if self.sampler is not None: + self.sampler.set_epoch(epoch) + + epoch_loss = 0.0 + epoch_mse_loss = 0.0 + epoch_tv_loss = 0.0 + epoch_z1_loss = 0.0 + + num_batches = 0 + + for batch_idx, batch in enumerate(self.dataloader): + + projection_indices = batch['projection_idx'] + + pixel_i = batch['pixel_i'].float().to(self.device, non_blocking=True) + pixel_j = batch['pixel_j'].float().to(self.device, non_blocking=True) + target_values = batch['target_value'].to(self.device, non_blocking=True) + phis = batch['phi'].to(self.device, non_blocking=True) + projection_indices = batch['projection_idx'].to(self.device, non_blocking=True) + + shifts, z1_params, z3_params = aux_params(None) + batch_shifts = torch.index_select(shifts, 0, projection_indices) + batch_z1 = torch.index_select(z1_params, 0, projection_indices) + batch_z3 = torch.index_select(z3_params, 0, projection_indices) + + with torch.autocast(device_type=device_type, dtype=autocast_dtype, enabled=use_amp): + + with torch.no_grad(): + batch_ray_coords = self.create_batch_projection_rays( + pixel_i, pixel_j, N, num_samples_per_ray + ) + + transformed_rays = self.transform_batch_ray_coordinates( + batch_ray_coords, + z1=batch_z1, + x=phis, + z3=batch_z3, + shifts=batch_shifts, + N=N, + sampling_rate=1.0, + ) + + all_coords = transformed_rays.view(-1, 3) + # TODO: torch.nn.functional.softplus here + # all_densities = torch.nn.functional.softplus(self.model(all_coords)) + all_densities = self.model(all_coords) + + if all_densities.dim() > 1: + all_densities = all_densities.squeeze(-1) # [N, 1] → [N] + + # Create mask for valid coordinates (within [-1, 1]^3) + valid_mask = ( + (all_coords[:, 0] >= -1) & (all_coords[:, 0] <= 1) & + (all_coords[:, 1] >= -1) & (all_coords[:, 1] <= 1) & + (all_coords[:, 2] >= -1) & (all_coords[:, 2] <= 1) + ).float() + + all_densities = all_densities * valid_mask + + if tv_weight > 0: + num_tv_samples = min(10000, all_coords.shape[0]) + tv_indices = torch.randperm(all_coords.shape[0], device=all_coords.device)[:num_tv_samples] + + # Rerun forward for gradient tracking + tv_coords = all_coords[tv_indices].detach().requires_grad_(True) + + # TODO: torch.nn.functional.softplus here + # tv_densities_recomputed = torch.nn.functional.softplus(self.model(tv_coords)) # Get rid of this + tv_densities_recomputed = self.model(tv_coords) + if tv_densities_recomputed.dim() > 1: + tv_densities_recomputed = tv_densities_recomputed.squeeze(-1) + + # Compute gradients + grad_outputs = torch.autograd.grad( + outputs=tv_densities_recomputed, + inputs=tv_coords, + grad_outputs=torch.ones_like(tv_densities_recomputed), + create_graph=True + )[0] + + grad_norm = torch.norm(grad_outputs, dim=1) + tv_loss = tv_weight * grad_norm.mean() + else: + tv_loss = torch.tensor(0.0, device=self.device) + + ray_densities = all_densities.view(len(target_values), num_samples_per_ray) # Reshape rays and integarte + step_size = 2.0 / (num_samples_per_ray - 1) + + predicted_values = ray_densities.sum(dim=1) * step_size + + mse_loss = torch.nn.functional.mse_loss(predicted_values, target_values) + tv_loss_z1 = torch.tensor(0.0, device=self.device) + + loss = mse_loss + tv_loss + tv_loss_z1 + + loss.backward() + + epoch_loss += loss.detach() + epoch_mse_loss += mse_loss.detach() + epoch_tv_loss += tv_loss.detach() + epoch_z1_loss += tv_loss_z1.detach() + num_batches += 1 + + if epoch >= warmup_epochs: + + model_norm = torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm = 1) + optimizer.step() + optimizer.zero_grad() + + aux_norm = torch.nn.utils.clip_grad_norm_(aux_params.parameters(), max_norm = 1) + aux_optimizer.step() + aux_optimizer.zero_grad() + else: + model_norm = torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm = 1) + optimizer.step() + optimizer.zero_grad() + + aux_optimizer.zero_grad() + + scheduler_model.step() + + if epoch >= warmup_epochs: + scheduler_aux.step() + + if epoch % viz_freq == 0 or epoch == epochs - 1 or epoch % checkpoint_freq == 0 : + with torch.no_grad(): + pred_full = self.create_volume().cpu() + avg_loss = epoch_loss.item() / num_batches + avg_mse_loss = epoch_mse_loss.item() / num_batches + avg_tv_loss = epoch_tv_loss.item() / num_batches + avg_z1_loss = epoch_z1_loss.item() / num_batches + + metrics = torch.tensor([avg_loss, avg_mse_loss, avg_tv_loss, avg_z1_loss], device=self.device) + if self.world_size > 1: + dist.all_reduce(metrics, op=dist.ReduceOp.AVG) + avg_loss, avg_mse_loss, avg_tv_loss, avg_z1_loss = metrics.tolist() + + if self.global_rank == 0 and (epoch % viz_freq == 0 or epoch == epochs - 1): + with torch.no_grad(): + current_lr = scheduler_model.get_last_lr()[0] + + # Log metrics + logger.add_scalar("train/mse_loss", avg_mse_loss, epoch) + + logger.add_scalar("train/z1_loss", avg_z1_loss, epoch) + if tv_weight > 0: + logger.add_scalar("train/tv_loss", avg_tv_loss, epoch) + logger.add_scalar("train/total_loss", avg_loss, epoch) + logger.add_scalar("train/model_grad_norm", model_norm.item(), epoch) + logger.add_scalar("train/aux_grad_norm", aux_norm.item(), epoch) + logger.add_scalar("train/lr", current_lr, epoch) + logger.add_scalar("train/num_samples_per_ray", num_samples_per_ray, epoch) + + fig, axes = plt.subplots(1, 3, figsize=(36, 12)) + axes[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap='turbo', vmin=0) + axes[0].set_title('Sum over Z-axis') + + axes[1].matshow(pred_full[N//2].cpu().numpy(), cmap='turbo', vmin=0) + axes[1].set_title(f'Slice at Z={N//2}') + + slice_start = max(0, N//2 - 5) + slice_end = min(N, N//2 + 6) + thick_slice = pred_full[slice_start:slice_end].sum(dim=0).cpu().numpy() + axes[2].matshow(thick_slice, cmap='turbo', vmin=0) + axes[2].set_title(f'Thick slice sum (Z={slice_start}:{slice_end-1})') + + logger.add_figure("train/viz", fig, epoch, close=True) + plt.close(fig) + + if self.global_rank == 0: + print("Training complete.") + + # print("Successfully setup DDP and dataloader") + def ad_recon( self, optimizer_params: dict, diff --git a/src/quantem/tomography/tomography_base.py b/src/quantem/tomography/tomography_base.py index 35c69b34..61fb7937 100644 --- a/src/quantem/tomography/tomography_base.py +++ b/src/quantem/tomography/tomography_base.py @@ -85,7 +85,7 @@ def from_data( shifts=shifts, ) - dataset.to(device) + # dataset.to(device) if volume_obj is None: max_shape = max(dataset.tilt_series.shape) diff --git a/src/quantem/tomography/tomography_ddp.py b/src/quantem/tomography/tomography_ddp.py new file mode 100644 index 00000000..f733e012 --- /dev/null +++ b/src/quantem/tomography/tomography_ddp.py @@ -0,0 +1,128 @@ +import torch.distributed as dist +import torch +from torch.utils.data import Dataset, DataLoader, DistributedSampler +import torch.nn as nn +import os + +from quantem.tomography.tomography_dataset import TomographyDataset + +class TomographyDDP: + """ + Initializing DDP stuff for tomo class. + """ + def __init__( + self, + ): + + self.setup_distributed() + + + def setup_distributed(self): + + # Check if in distributed env + if "RANK" in os.environ: + # Distributed training + if not dist.is_initialized(): + dist.init_process_group(backend='nccl' if torch.cuda.is_available() else 'gloo', init_method='env://') + + self.world_size = dist.get_world_size() + self.global_rank = dist.get_rank() + self.local_rank = int(os.environ["LOCAL_RANK"]) + + torch.cuda.set_device(self.local_rank) + self.device = torch.device("cuda", self.local_rank) + + else: + # Single GPU/CPU training + self.world_size = 1 + self.global_rank = 0 + self.local_rank = 0 + + if torch.cuda.is_available(): + self.device = torch.device("cuda:0") + torch.cuda.set_device(0) + print("Single GPU training") + else: + self.device = torch.device("cpu") + print("CPU training") + + # Optional performance optimizations (only for CUDA) + if self.device.type == "cuda": + torch.backends.cudnn.benchmark = True + torch.backends.cuda.matmul.allow_tf32 = True + torch.backends.cudnn.allow_tf32 = True + + def build_model( + self, + model: nn.Module, + ): + + # TODO: Generalized model --> Should be instantiated in the object? Where does `HSIREN` get instantiated? + self.model = model.to(self.device) + + if self.world_size > 1: + self.model = torch.nn.parallel.DistributedDataParallel( + self.model, + device_ids = [self.local_rank], + output_device = self.local_rank, + find_unused_parameters = False, + broadcast_buffers = True, + bucket_cap_mb = 100, + gradient_as_bucket_view = True, + ) + + if self.global_rank == 0: + print("Model wrapped with DDP") + + if self.world_size > 1: + + if self.global_rank == 0: + print("Model built, distributed, and compiled successfully") + + else: + print("Model built, compiled successfully") + + def setup_dataloader( + self, + tomo_dataset: TomographyDataset, + batch_size: int, + num_workers: int = 0, + ): + + if self.world_size > 1: + sampler = DistributedSampler( + tomo_dataset, + num_replicas = self.world_size, + rank = self.global_rank, + shuffle = True, + ) + shuffle = False + + else: + sampler = None + shuffle = True + + self.dataloader = DataLoader( + tomo_dataset, + batch_size = batch_size, + num_workers = num_workers, + sampler = sampler, + shuffle = shuffle, + pin_memory = True if self.device.type == "cuda" else False, + drop_last = True, + persistent_workers = False if num_workers == 0 else True, + ) + + self.sampler = sampler + + if self.global_rank == 0: + print(f"Dataloader setup complete:") + print(f" Total projections: {len(tomo_dataset.tilt_angles)}") + print(f" Grid size: {tomo_dataset.dims[1]}{tomo_dataset.dims[2]}") + print(f" Total pixels: {tomo_dataset.num_pixels:,}") + + print(f" Local batch size (train): {batch_size}") + print(f" Global batch size: {batch_size*self.world_size}") + print(f" Train batches per GPU per epoch: {len(self.dataloader)}") + + \ No newline at end of file From bb40c4b940175251c4b037760125a64297270b1d Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 22 Sep 2025 22:54:43 -0700 Subject: [PATCH 008/335] Tomo optimizers + schedulers working; need to do object_models --- src/quantem/tomography/object_models.py | 13 +- src/quantem/tomography/tomography.py | 184 +++++-------------- src/quantem/tomography/tomography_dataset.py | 61 ++++++ src/quantem/tomography/tomography_ddp.py | 36 +++- src/quantem/tomography/tomography_ml.py | 30 ++- 5 files changed, 184 insertions(+), 140 deletions(-) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 00f9a901..284f743e 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -78,7 +78,7 @@ def reset(self): pass @abstractmethod - def to(self, device: str): + def to(self, device: str):z pass @abstractmethod @@ -544,8 +544,15 @@ class ObjectINN(ObjectConstraints): """ def __init__( - self + self, + model: torch.nn.Module, ): pass + + def forward( + self, + rays, + + ): -ObjectModelType = Union[ObjectVoxelwise] # | ObjectDIP | ObjectImplicit (ObjectFFN?) +ObjectModelType = ObjectVoxelwise | ObjectINN # | ObjectDIP | ObjectImplicit (ObjectFFN?) diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index e9e9cc35..d35a19b1 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -34,47 +34,7 @@ def get_num_samples_per_ray(epoch): return num_samples -class AuxiliaryParams(torch.nn.Module): - def __init__(self, num_tilts, device, zero_tilt_idx=None): - super().__init__() - if zero_tilt_idx is None: - # If not provided, assume first projection is reference - zero_tilt_idx = 0 - - self.zero_tilt_idx = zero_tilt_idx - self.num_tilts = num_tilts - - # Shifts: only parameterize non-reference tilts - num_param_tilts = num_tilts - 1 - self.shifts_param = torch.nn.Parameter(torch.zeros(num_param_tilts, 2, device=device)) - - # Fixed zero shifts for reference - self.shifts_ref = torch.zeros(1, 2, device=device) - - # Z1 and Z3: parameterize all tilts EXCEPT the reference - self.z1_param = torch.nn.Parameter(torch.zeros(num_param_tilts, device=device)) - # self.z3_param = torch.nn.Parameter(torch.zeros(num_param_tilts, device=device)) - - # Fixed zeros for reference tilt - self.z1_ref = torch.zeros(1, device=device) - # self.z3_ref = torch.zeros(1, device=device) - - def forward(self, dummy_input=None): - # Reconstruct full arrays with zeros at reference position - before_shifts = self.shifts_param[:self.zero_tilt_idx] - after_shifts = self.shifts_param[self.zero_tilt_idx:] - shifts = torch.cat([before_shifts, self.shifts_ref, after_shifts], dim=0) - - before_z1 = self.z1_param[:self.zero_tilt_idx] - after_z1 = self.z1_param[self.zero_tilt_idx:] - z1 = torch.cat([before_z1, self.z1_ref, after_z1], dim=0) - - # before_z3 = self.z3_param[:self.zero_tilt_idx] - # after_z3 = self.z3_param[self.zero_tilt_idx:] - # z3 = torch.cat([before_z3, self.z3_ref, after_z3], dim=0) - - return shifts, z1, -z1 class Tomography(TomographyConv, TomographyML, TomographyBase, TomographyDDP): """ @@ -90,7 +50,6 @@ def __init__( _token, ): super().__init__(dataset, volume_obj, device, _token) - # --- Reconstruction Method --- def sirt_recon( @@ -159,13 +118,7 @@ def sirt_recon( # TODO: ML Recon which has NeRF and AD depending on the object type. # TODO: Temporary - def get_scaled_lr(self, base_lr, scaling_rule="sqrt"): - if scaling_rule == "sqrt": - return base_lr * np.sqrt(self.world_size) - elif scaling_rule == "linear": - return base_lr * self.world_size - else: - raise ValueError(f"Invalid scaling rule: {scaling_rule}") + def create_optimizer(self, params, lr, fused = True): @@ -274,90 +227,63 @@ def create_volume(self): return pred_full + + # TODO: Temp logger + def setup_logger(self): + if self.global_rank == 0: + self.temp_logger = SummaryWriter() + else: + self.temp_logger = None + def recon( self, model: HSiren, batch_size: int, num_workers: int = 0, - train_lr = 1e-5, - aux_lr = 1e-5, epochs = 20, - warmup_epochs = 10, use_amp = True, tv_weight = 0.0, viz_freq = 1, checkpoint_freq = 5, + optimizer_params: dict = None, + scheduler_params: dict = None, ): - self.setup_distributed() self.setup_dataloader(self.dataset, batch_size, num_workers) self.build_model(model) - # TODO: Temp-logger - if self.global_rank == 0: - logger = SummaryWriter() - else: - logger = None + + + if not hasattr(self, "temp_logger"): + self.setup_logger() + zero_tilt_idx = torch.argmin(torch.abs(self.dataset.tilt_angles)).item() if self.global_rank == 0: print(f"Using projection {zero_tilt_idx} (angle={self.dataset.tilt_angles[zero_tilt_idx]:.2f}°) as reference") - - aux_params = AuxiliaryParams( - num_tilts = len(self.dataset.tilt_angles), - device = self.device, - zero_tilt_idx = zero_tilt_idx, - ) - scaled_train_lr = self.get_scaled_lr(train_lr, scaling_rule="sqrt") - scaled_aux_lr = self.get_scaled_lr(aux_lr, scaling_rule="sqrt") + # Auxiliary params setup + self.dataset.setup_auxiliary_params(zero_tilt_idx, self.device) + aux_params = self.dataset.auxiliary_params - optimizer = self.create_optimizer( - self.model.parameters(), - scaled_train_lr, - fused = True, - ) + # Scaling learning rates to account for distributed training + if optimizer_params is not None: + optimizer_params = self.scale_lr(optimizer_params) + self.optimizer_params = optimizer_params + self.set_optimizers() + + if scheduler_params is not None: + self.scheduler_params = scheduler_params + self.set_schedulers(self.scheduler_params, num_iter = epochs) - aux_optimizer = self.create_optimizer( - aux_params.parameters(), - scaled_aux_lr, - fused = True, - ) aux_norm = torch.tensor(0.0, device=self.device) model_norm = torch.tensor(0.0, device=self.device) - optimizer.zero_grad() - aux_optimizer.zero_grad() - - - warmup_scheduler_model = torch.optim.lr_scheduler.LinearLR( - optimizer, - start_factor = 0.001, - total_iters = warmup_epochs, - ) - - - cosine_scheduler_model = torch.optim.lr_scheduler.CosineAnnealingLR( - optimizer, - T_max = epochs - warmup_epochs, - eta_min = scaled_train_lr / 100, - ) - - - scheduler_model = torch.optim.lr_scheduler.SequentialLR( - optimizer, - schedulers = [warmup_scheduler_model, cosine_scheduler_model], - milestones = [warmup_epochs], - ) - - scheduler_aux = torch.optim.lr_scheduler.CosineAnnealingLR( - aux_optimizer, - T_max = epochs, - eta_min = scaled_aux_lr / 100, - ) + for _, opt in self.optimizers.items(): + opt.zero_grad() N = max(self.dataset.dims) @@ -477,28 +403,20 @@ def recon( epoch_tv_loss += tv_loss.detach() epoch_z1_loss += tv_loss_z1.detach() num_batches += 1 - - if epoch >= warmup_epochs: - - model_norm = torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm = 1) - optimizer.step() - optimizer.zero_grad() - - aux_norm = torch.nn.utils.clip_grad_norm_(aux_params.parameters(), max_norm = 1) - aux_optimizer.step() - aux_optimizer.zero_grad() - else: - model_norm = torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm = 1) - optimizer.step() - optimizer.zero_grad() + + for key, opt in self.optimizers.items(): - aux_optimizer.zero_grad() + if key == "model": + model_norm = torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm = 1) + elif key == "aux_params": + aux_norm = torch.nn.utils.clip_grad_norm_(self.dataset.auxiliary_params.parameters(), max_norm = 1) + opt.step() + opt.zero_grad() - scheduler_model.step() - - if epoch >= warmup_epochs: - scheduler_aux.step() + for key, sched in self.schedulers.items(): + sched.step() + if epoch % viz_freq == 0 or epoch == epochs - 1 or epoch % checkpoint_freq == 0 : with torch.no_grad(): pred_full = self.create_volume().cpu() @@ -514,19 +432,19 @@ def recon( if self.global_rank == 0 and (epoch % viz_freq == 0 or epoch == epochs - 1): with torch.no_grad(): - current_lr = scheduler_model.get_last_lr()[0] + current_lr = self.schedulers["model"].get_last_lr()[0] # Log metrics - logger.add_scalar("train/mse_loss", avg_mse_loss, epoch) + self.temp_logger.add_scalar("train/mse_loss", avg_mse_loss, epoch) - logger.add_scalar("train/z1_loss", avg_z1_loss, epoch) + self.temp_logger.add_scalar("train/z1_loss", avg_z1_loss, epoch) if tv_weight > 0: - logger.add_scalar("train/tv_loss", avg_tv_loss, epoch) - logger.add_scalar("train/total_loss", avg_loss, epoch) - logger.add_scalar("train/model_grad_norm", model_norm.item(), epoch) - logger.add_scalar("train/aux_grad_norm", aux_norm.item(), epoch) - logger.add_scalar("train/lr", current_lr, epoch) - logger.add_scalar("train/num_samples_per_ray", num_samples_per_ray, epoch) + self.temp_logger.add_scalar("train/tv_loss", avg_tv_loss, epoch) + self.temp_logger.add_scalar("train/total_loss", avg_loss, epoch) + self.temp_logger.add_scalar("train/model_grad_norm", model_norm.item(), epoch) + self.temp_logger.add_scalar("train/aux_grad_norm", aux_norm.item(), epoch) + self.temp_logger.add_scalar("train/lr", current_lr, epoch) + self.temp_logger.add_scalar("train/num_samples_per_ray", num_samples_per_ray, epoch) fig, axes = plt.subplots(1, 3, figsize=(36, 12)) axes[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap='turbo', vmin=0) @@ -541,7 +459,7 @@ def recon( axes[2].matshow(thick_slice, cmap='turbo', vmin=0) axes[2].set_title(f'Thick slice sum (Z={slice_start}:{slice_end-1})') - logger.add_figure("train/viz", fig, epoch, close=True) + self.temp_logger.add_figure("train/viz", fig, epoch, close=True) plt.close(fig) if self.global_rank == 0: diff --git a/src/quantem/tomography/tomography_dataset.py b/src/quantem/tomography/tomography_dataset.py index 251538d0..c66b9e61 100644 --- a/src/quantem/tomography/tomography_dataset.py +++ b/src/quantem/tomography/tomography_dataset.py @@ -13,6 +13,49 @@ import numpy as np +class AuxiliaryParams(torch.nn.Module): + def __init__(self, num_tilts, device, zero_tilt_idx=None): + super().__init__() + + if zero_tilt_idx is None: + # If not provided, assume first projection is reference + zero_tilt_idx = 0 + + self.zero_tilt_idx = zero_tilt_idx + self.num_tilts = num_tilts + + # Shifts: only parameterize non-reference tilts + num_param_tilts = num_tilts - 1 + self.shifts_param = torch.nn.Parameter(torch.zeros(num_param_tilts, 2, device=device)) + + # Fixed zero shifts for reference + self.shifts_ref = torch.zeros(1, 2, device=device) + + # Z1 and Z3: parameterize all tilts EXCEPT the reference + self.z1_param = torch.nn.Parameter(torch.zeros(num_param_tilts, device=device)) + # self.z3_param = torch.nn.Parameter(torch.zeros(num_param_tilts, device=device)) + + # Fixed zeros for reference tilt + self.z1_ref = torch.zeros(1, device=device) + # self.z3_ref = torch.zeros(1, device=device) + + def forward(self, dummy_input=None): + # Reconstruct full arrays with zeros at reference position + before_shifts = self.shifts_param[:self.zero_tilt_idx] + after_shifts = self.shifts_param[self.zero_tilt_idx:] + shifts = torch.cat([before_shifts, self.shifts_ref, after_shifts], dim=0) + + before_z1 = self.z1_param[:self.zero_tilt_idx] + after_z1 = self.z1_param[self.zero_tilt_idx:] + z1 = torch.cat([before_z1, self.z1_ref, after_z1], dim=0) + + # before_z3 = self.z3_param[:self.zero_tilt_idx] + # after_z3 = self.z3_param[self.zero_tilt_idx:] + # z3 = torch.cat([before_z3, self.z3_ref, after_z3], dim=0) + + return shifts, z1, -z1 + + class TomographyDataset(AutoSerialize, Dataset): _token = object() @@ -276,6 +319,24 @@ def initial_z3_angles(self, z3_angles: NDArray | Tensor) -> None: def initial_shifts(self, shifts: NDArray | Tensor) -> None: self._initial_shifts = shifts + # TODO: Temp auxiliary params + + def setup_auxiliary_params(self, zero_tilt_idx: int = None, device: str = "cpu") -> None: + + if not hasattr(self, "_auxiliary_params"): + self._auxiliary_params = AuxiliaryParams( + num_tilts = len(self.tilt_angles), + device = device, + zero_tilt_idx = zero_tilt_idx, + ) + + else: + print("Auxiliary params already set") + + @property + def auxiliary_params(self) -> AuxiliaryParams: + return self._auxiliary_params + # --- RESET --- def reset(self) -> None: diff --git a/src/quantem/tomography/tomography_ddp.py b/src/quantem/tomography/tomography_ddp.py index f733e012..ec093a32 100644 --- a/src/quantem/tomography/tomography_ddp.py +++ b/src/quantem/tomography/tomography_ddp.py @@ -3,7 +3,7 @@ from torch.utils.data import Dataset, DataLoader, DistributedSampler import torch.nn as nn import os - +import numpy as np from quantem.tomography.tomography_dataset import TomographyDataset class TomographyDDP: @@ -81,7 +81,8 @@ def build_model( else: print("Model built, compiled successfully") - + + def setup_dataloader( self, tomo_dataset: TomographyDataset, @@ -124,5 +125,36 @@ def setup_dataloader( print(f" Local batch size (train): {batch_size}") print(f" Global batch size: {batch_size*self.world_size}") print(f" Train batches per GPU per epoch: {len(self.dataloader)}") + + def get_scaled_lr(self, base_lr, scaling_rule="sqrt"): + if scaling_rule == "sqrt": + return base_lr * np.sqrt(self.world_size) + elif scaling_rule == "linear": + return base_lr * self.world_size + else: + raise ValueError(f"Invalid scaling rule: {scaling_rule}") + def scale_lr( + self, + optimizer_params: dict, + ): + + new_optimizer_params = {} + for key, value in optimizer_params.items(): + + if "original_lr" in value: + new_optimizer_params[key] = { + "type": value["type"], + "lr": self.get_scaled_lr(value["original_lr"]), + "original_lr": value["original_lr"], + } + else: + new_optimizer_params[key] = { + "type": value["type"], + "lr": self.get_scaled_lr(value["lr"]), + "original_lr": value["lr"], + } + + return new_optimizer_params + \ No newline at end of file diff --git a/src/quantem/tomography/tomography_ml.py b/src/quantem/tomography/tomography_ml.py index 4a902887..4953e45c 100644 --- a/src/quantem/tomography/tomography_ml.py +++ b/src/quantem/tomography/tomography_ml.py @@ -10,7 +10,7 @@ class TomographyML(TomographyBase): Class for handling conventional reconstruction methods of tomography data. """ - OPTIMIZABLE_VALS = ["volume", "z1", "x", "z3", "shifts"] + OPTIMIZABLE_VALS = ["volume", "z1", "x", "z3", "shifts", "model", "aux_params"] DEFAULT_LRS = { "volume": 1e-2, "z1": 1e-1, @@ -81,6 +81,18 @@ def set_optimizers(self): self._add_optimizer(key, self.dataset.tilt_angles, self._optimizer_params[key]) elif key == "z3": self._add_optimizer(key, self.dataset.z3_angles, self._optimizer_params[key]) + elif key == "model": + + if self._optimizer_params[key]["original_lr"] is not None: + optimizer_params = self._optimizer_params[key].copy() + optimizer_params.pop("original_lr", None) + self._add_optimizer(key, self.model.parameters(), optimizer_params) + elif key == "aux_params": + + if self._optimizer_params[key]["original_lr"] is not None: + optimizer_params = self._optimizer_params[key].copy() + optimizer_params.pop("original_lr", None) + self._add_optimizer(key, self.dataset.auxiliary_params.parameters(), optimizer_params) else: raise ValueError( f"key to be optimized, {key}, not in allowed keys: {self.OPTIMIZABLE_VALS}" @@ -151,7 +163,7 @@ def scheduler_params(self, d: dict) -> None: raise ValueError( f"key to be optimized, {k}, not in allowed keys: {self.OPTIMIZABLE_VALS}" ) - if v["type"] not in ["cyclic", "plateau", "exp", "gamma", "none"]: + if v["type"] not in ["cyclic", "plateau", "exp", "gamma", "linear", "cosine_annealing", "none"]: raise ValueError( f"Unknown scheduler type: {v['type']}, expected one of ['cyclic', 'plateau', 'exp', 'gamma', 'none']" ) @@ -166,6 +178,8 @@ def schedulers( torch.optim.lr_scheduler.CyclicLR | torch.optim.lr_scheduler.ReduceLROnPlateau | torch.optim.lr_scheduler.ExponentialLR + | torch.optim.lr_scheduler.LinearLR + | torch.optim.lr_scheduler.CosineAnnealingLR | None ), ]: @@ -271,6 +285,18 @@ def _get_scheduler( base_LR = optimizer.param_groups[0]["lr"] if sched_type == "none": scheduler = None + elif sched_type == "linear": + scheduler = torch.optim.lr_scheduler.LinearLR( + optimizer, + start_factor=params.get("start_factor", 0.1), + total_iters=params.get("total_iters", num_iter), + ) + elif sched_type == "cosine_annealing": + scheduler = torch.optim.lr_scheduler.CosineAnnealingLR( + optimizer, + T_max=params.get("T_max", num_iter), + eta_min=params.get("eta_min", base_LR / 100), + ) elif sched_type == "cyclic": scheduler = torch.optim.lr_scheduler.CyclicLR( optimizer, From c704ce3b89737c6146b4b5bbbe05d081c1c1277f Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 23 Sep 2025 00:16:18 -0700 Subject: [PATCH 009/335] Working DDP with objects and stuff --- src/quantem/tomography/object_models.py | 86 ++++++++++++++++++++++-- src/quantem/tomography/tomography.py | 50 ++++++++------ src/quantem/tomography/tomography_ddp.py | 8 ++- src/quantem/tomography/tomography_ml.py | 2 +- 4 files changed, 116 insertions(+), 30 deletions(-) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 284f743e..5ef08894 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -11,6 +11,8 @@ from quantem.core.utils.validators import validate_gt, validate_tensor from quantem.tomography.utils import get_TV_loss +import torch.nn as nn + class ObjectBase(AutoSerialize): """ @@ -78,7 +80,7 @@ def reset(self): pass @abstractmethod - def to(self, device: str):z + def to(self, device: str): pass @abstractmethod @@ -545,14 +547,88 @@ class ObjectINN(ObjectConstraints): def __init__( self, - model: torch.nn.Module, + model: nn.Module, + volume_shape = tuple[int, int, int], + device: str = "cuda", ): - pass + + super().__init__( + volume_shape = volume_shape, + device = device, + offset_obj = 0, + ) + + self._model = model + + + # --- Properties --- + @property + def obj(self): + + raise NotImplementedError - def forward( + @property + def model(self): + + return self._model + + @model.setter + def model(self, model): + + self._model = model + + def apply_soft_constraints( self, - rays, + coords: torch.Tensor, + tv_weight: float = 0.0, + ): + if tv_weight > 0: + + num_tv_samples = min(10000, coords.shape[0]) + tv_indices = torch.randperm(coords.shape[0], device = coords.device)[:num_tv_samples] + + # Rerun forward for gradient tracking + tv_coords = coords[tv_indices].detach().requires_grad_(True) + + tv_densities_recomputed = self.model(tv_coords) + if tv_densities_recomputed.dim() > 1: + tv_densities_recomputed = tv_densities_recomputed.squeeze(-1) + + # Compute gradients + grad_outputs = torch.autograd.grad( + outputs=tv_densities_recomputed, + inputs=tv_coords, + grad_outputs=torch.ones_like(tv_densities_recomputed), + create_graph=True + )[0] + + grad_norm = torch.norm(grad_outputs, dim = 1) + tv_loss = tv_weight * grad_norm.mean() + else: + tv_loss = torch.tensor(0.0, device = self.device) + return tv_loss + + def forward( + self, + all_coords: torch.Tensor, ): + + all_densities = self.model(all_coords) + + if all_densities.dim() > 1: + all_densities = all_densities.squeeze(-1) + + valid_mask = ( + (all_coords[:, 0] >= -1) & (all_coords[:, 0] <= 1) & + (all_coords[:, 1] >= -1) & (all_coords[:, 1] <= 1) & + (all_coords[:, 2] >= -1) & (all_coords[:, 2] <= 1) + ).float() + + all_densities = all_densities * valid_mask + + return all_densities + + ObjectModelType = ObjectVoxelwise | ObjectINN # | ObjectDIP | ObjectImplicit (ObjectFFN?) diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index d35a19b1..78567ddb 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -12,6 +12,7 @@ # Temporary imports for TomographyNERF from quantem.tomography.models import HSiren +from quantem.tomography.object_models import ObjectINN from torch.utils.tensorboard import SummaryWriter import numpy as np import matplotlib.pyplot as plt @@ -193,7 +194,7 @@ def transform_batch_ray_coordinates(self, rays, z1, x, z3, shifts, N, sampling_r return transformed_rays # --- Creating Volume --- - def create_volume(self): + def create_volume(self, model): N = max(self.dataset.dims) @@ -202,7 +203,7 @@ def create_volume(self): x, y, z = torch.meshgrid(coords_1d, coords_1d, coords_1d, indexing='ij') inputs = torch.stack([x, y, z], dim=-1).reshape(-1, 3) - model = self.model.module if hasattr(self.model, 'module') else self.model + model = model.module if hasattr(model, 'module') else model samples_per_gpu = N**3 // self.world_size start_idx = self.global_rank * samples_per_gpu @@ -237,7 +238,7 @@ def setup_logger(self): def recon( self, - model: HSiren, + obj: ObjectINN, batch_size: int, num_workers: int = 0, epochs = 20, @@ -247,13 +248,14 @@ def recon( checkpoint_freq = 5, optimizer_params: dict = None, scheduler_params: dict = None, - ): - + ): self.setup_distributed() self.setup_dataloader(self.dataset, batch_size, num_workers) - self.build_model(model) + obj.model = self.build_model(obj._model) + + self.obj = obj if not hasattr(self, "temp_logger"): self.setup_logger() @@ -346,20 +348,26 @@ def recon( all_coords = transformed_rays.view(-1, 3) # TODO: torch.nn.functional.softplus here # all_densities = torch.nn.functional.softplus(self.model(all_coords)) - all_densities = self.model(all_coords) - - if all_densities.dim() > 1: - all_densities = all_densities.squeeze(-1) # [N, 1] → [N] - - # Create mask for valid coordinates (within [-1, 1]^3) - valid_mask = ( - (all_coords[:, 0] >= -1) & (all_coords[:, 0] <= 1) & - (all_coords[:, 1] >= -1) & (all_coords[:, 1] <= 1) & - (all_coords[:, 2] >= -1) & (all_coords[:, 2] <= 1) - ).float() - all_densities = all_densities * valid_mask + # TODO: I don't think this work, object needs to be distributed to all devices? + all_densities = self.obj.forward(all_coords) + # all_densities = self.model(all_coords) + + # if all_densities.dim() > 1: + # all_densities = all_densities.squeeze(-1) # [N, 1] → [N] + + # # Create mask for valid coordinates (within [-1, 1]^3) + # valid_mask = ( + # (all_coords[:, 0] >= -1) & (all_coords[:, 0] <= 1) & + # (all_coords[:, 1] >= -1) & (all_coords[:, 1] <= 1) & + # (all_coords[:, 2] >= -1) & (all_coords[:, 2] <= 1) + # ).float() + # all_densities = all_densities * valid_mask + # tv_loss = obj.apply_soft_constraints( + # coords = all_coords, + # tv_weight = 1e-5, + # ) if tv_weight > 0: num_tv_samples = min(10000, all_coords.shape[0]) tv_indices = torch.randperm(all_coords.shape[0], device=all_coords.device)[:num_tv_samples] @@ -393,7 +401,7 @@ def recon( mse_loss = torch.nn.functional.mse_loss(predicted_values, target_values) tv_loss_z1 = torch.tensor(0.0, device=self.device) - + loss = mse_loss + tv_loss + tv_loss_z1 loss.backward() @@ -407,7 +415,7 @@ def recon( for key, opt in self.optimizers.items(): if key == "model": - model_norm = torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm = 1) + model_norm = torch.nn.utils.clip_grad_norm_(self.obj.model.parameters(), max_norm = 1) elif key == "aux_params": aux_norm = torch.nn.utils.clip_grad_norm_(self.dataset.auxiliary_params.parameters(), max_norm = 1) opt.step() @@ -419,7 +427,7 @@ def recon( if epoch % viz_freq == 0 or epoch == epochs - 1 or epoch % checkpoint_freq == 0 : with torch.no_grad(): - pred_full = self.create_volume().cpu() + pred_full = self.create_volume(self.obj.model).cpu() avg_loss = epoch_loss.item() / num_batches avg_mse_loss = epoch_mse_loss.item() / num_batches avg_tv_loss = epoch_tv_loss.item() / num_batches diff --git a/src/quantem/tomography/tomography_ddp.py b/src/quantem/tomography/tomography_ddp.py index ec093a32..58e4158d 100644 --- a/src/quantem/tomography/tomography_ddp.py +++ b/src/quantem/tomography/tomography_ddp.py @@ -58,11 +58,11 @@ def build_model( ): # TODO: Generalized model --> Should be instantiated in the object? Where does `HSIREN` get instantiated? - self.model = model.to(self.device) + model = model.to(self.device) if self.world_size > 1: - self.model = torch.nn.parallel.DistributedDataParallel( - self.model, + model = torch.nn.parallel.DistributedDataParallel( + model, device_ids = [self.local_rank], output_device = self.local_rank, find_unused_parameters = False, @@ -81,6 +81,8 @@ def build_model( else: print("Model built, compiled successfully") + + return model def setup_dataloader( diff --git a/src/quantem/tomography/tomography_ml.py b/src/quantem/tomography/tomography_ml.py index 4953e45c..47e479d8 100644 --- a/src/quantem/tomography/tomography_ml.py +++ b/src/quantem/tomography/tomography_ml.py @@ -86,7 +86,7 @@ def set_optimizers(self): if self._optimizer_params[key]["original_lr"] is not None: optimizer_params = self._optimizer_params[key].copy() optimizer_params.pop("original_lr", None) - self._add_optimizer(key, self.model.parameters(), optimizer_params) + self._add_optimizer(key, self.obj.model.parameters(), optimizer_params) elif key == "aux_params": if self._optimizer_params[key]["original_lr"] is not None: From 1960f35414100d59706f59700ea341c1723809b9 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 23 Sep 2025 00:22:44 -0700 Subject: [PATCH 010/335] Soft constraints also work --- src/quantem/tomography/tomography.py | 53 ++++++++++++++-------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 78567ddb..bdec96fb 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -368,32 +368,33 @@ def recon( # coords = all_coords, # tv_weight = 1e-5, # ) - if tv_weight > 0: - num_tv_samples = min(10000, all_coords.shape[0]) - tv_indices = torch.randperm(all_coords.shape[0], device=all_coords.device)[:num_tv_samples] + # if tv_weight > 0: + # num_tv_samples = min(10000, all_coords.shape[0]) + # tv_indices = torch.randperm(all_coords.shape[0], device=all_coords.device)[:num_tv_samples] - # Rerun forward for gradient tracking - tv_coords = all_coords[tv_indices].detach().requires_grad_(True) + # # Rerun forward for gradient tracking + # tv_coords = all_coords[tv_indices].detach().requires_grad_(True) - # TODO: torch.nn.functional.softplus here - # tv_densities_recomputed = torch.nn.functional.softplus(self.model(tv_coords)) # Get rid of this - tv_densities_recomputed = self.model(tv_coords) - if tv_densities_recomputed.dim() > 1: - tv_densities_recomputed = tv_densities_recomputed.squeeze(-1) - - # Compute gradients - grad_outputs = torch.autograd.grad( - outputs=tv_densities_recomputed, - inputs=tv_coords, - grad_outputs=torch.ones_like(tv_densities_recomputed), - create_graph=True - )[0] - - grad_norm = torch.norm(grad_outputs, dim=1) - tv_loss = tv_weight * grad_norm.mean() - else: - tv_loss = torch.tensor(0.0, device=self.device) + # # TODO: torch.nn.functional.softplus here + # # tv_densities_recomputed = torch.nn.functional.softplus(self.model(tv_coords)) # Get rid of this + # tv_densities_recomputed = self.model(tv_coords) + # if tv_densities_recomputed.dim() > 1: + # tv_densities_recomputed = tv_densities_recomputed.squeeze(-1) + + # # Compute gradients + # grad_outputs = torch.autograd.grad( + # outputs=tv_densities_recomputed, + # inputs=tv_coords, + # grad_outputs=torch.ones_like(tv_densities_recomputed), + # create_graph=True + # )[0] + # grad_norm = torch.norm(grad_outputs, dim=1) + # tv_loss = tv_weight * grad_norm.mean() + # else: + # tv_loss = torch.tensor(0.0, device=self.device) + + tv_loss = self.obj.apply_soft_constraints(all_coords, tv_weight = 1e-5) ray_densities = all_densities.view(len(target_values), num_samples_per_ray) # Reshape rays and integarte step_size = 2.0 / (num_samples_per_ray - 1) @@ -446,9 +447,9 @@ def recon( self.temp_logger.add_scalar("train/mse_loss", avg_mse_loss, epoch) self.temp_logger.add_scalar("train/z1_loss", avg_z1_loss, epoch) - if tv_weight > 0: - self.temp_logger.add_scalar("train/tv_loss", avg_tv_loss, epoch) - self.temp_logger.add_scalar("train/total_loss", avg_loss, epoch) + # if tv_weight > 0: + self.temp_logger.add_scalar("train/tv_loss", avg_tv_loss, epoch) + self.temp_logger.add_scalar("train/total_loss", avg_loss, epoch) self.temp_logger.add_scalar("train/model_grad_norm", model_norm.item(), epoch) self.temp_logger.add_scalar("train/aux_grad_norm", aux_norm.item(), epoch) self.temp_logger.add_scalar("train/lr", current_lr, epoch) From b0ccd8c8bd93953169f8b4e0907d191442ed2fe5 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 23 Sep 2025 00:43:08 -0700 Subject: [PATCH 011/335] Multi-step training with different schedulers kind of working. Need to figure out if I should just instantiate both aux_params, and model then change the LR after one chckpoint --- src/quantem/tomography/tomography.py | 96 +++++++++------------------- 1 file changed, 30 insertions(+), 66 deletions(-) diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index bdec96fb..0e963347 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -51,6 +51,11 @@ def __init__( _token, ): super().__init__(dataset, volume_obj, device, _token) + + # TODO: More elegant way of doing this. + self.global_epochs = 0 + self.ddp_instantiated = False + # --- Reconstruction Method --- def sirt_recon( @@ -250,12 +255,13 @@ def recon( scheduler_params: dict = None, ): - self.setup_distributed() - self.setup_dataloader(self.dataset, batch_size, num_workers) - - obj.model = self.build_model(obj._model) + if not self.ddp_instantiated: + self.setup_distributed() + self.setup_dataloader(self.dataset, batch_size, num_workers) - self.obj = obj + if not hasattr(self, "obj"): + obj.model = self.build_model(obj._model) + self.obj = obj if not hasattr(self, "temp_logger"): self.setup_logger() @@ -294,15 +300,16 @@ def recon( for epoch in range(epochs): - num_samples_per_ray = get_num_samples_per_ray(epoch) + num_samples_per_ray = get_num_samples_per_ray(self.global_epochs) # Log the change if it happens - if epoch > 0: - prev_samples = get_num_samples_per_ray(epoch - 1) + if self.global_epochs >= 0: + prev_samples = get_num_samples_per_ray(self.global_epochs) if num_samples_per_ray != prev_samples and self.global_rank == 0: print(f"Epoch {epoch}: Changing num_samples_per_ray from {prev_samples} to {num_samples_per_ray}") - + + if self.sampler is not None: self.sampler.set_epoch(epoch) @@ -346,54 +353,9 @@ def recon( ) all_coords = transformed_rays.view(-1, 3) - # TODO: torch.nn.functional.softplus here - # all_densities = torch.nn.functional.softplus(self.model(all_coords)) - - # TODO: I don't think this work, object needs to be distributed to all devices? - all_densities = self.obj.forward(all_coords) - # all_densities = self.model(all_coords) - # if all_densities.dim() > 1: - # all_densities = all_densities.squeeze(-1) # [N, 1] → [N] + all_densities = self.obj.forward(all_coords) - # # Create mask for valid coordinates (within [-1, 1]^3) - # valid_mask = ( - # (all_coords[:, 0] >= -1) & (all_coords[:, 0] <= 1) & - # (all_coords[:, 1] >= -1) & (all_coords[:, 1] <= 1) & - # (all_coords[:, 2] >= -1) & (all_coords[:, 2] <= 1) - # ).float() - - # all_densities = all_densities * valid_mask - # tv_loss = obj.apply_soft_constraints( - # coords = all_coords, - # tv_weight = 1e-5, - # ) - # if tv_weight > 0: - # num_tv_samples = min(10000, all_coords.shape[0]) - # tv_indices = torch.randperm(all_coords.shape[0], device=all_coords.device)[:num_tv_samples] - - # # Rerun forward for gradient tracking - # tv_coords = all_coords[tv_indices].detach().requires_grad_(True) - - # # TODO: torch.nn.functional.softplus here - # # tv_densities_recomputed = torch.nn.functional.softplus(self.model(tv_coords)) # Get rid of this - # tv_densities_recomputed = self.model(tv_coords) - # if tv_densities_recomputed.dim() > 1: - # tv_densities_recomputed = tv_densities_recomputed.squeeze(-1) - - # # Compute gradients - # grad_outputs = torch.autograd.grad( - # outputs=tv_densities_recomputed, - # inputs=tv_coords, - # grad_outputs=torch.ones_like(tv_densities_recomputed), - # create_graph=True - # )[0] - - # grad_norm = torch.norm(grad_outputs, dim=1) - # tv_loss = tv_weight * grad_norm.mean() - # else: - # tv_loss = torch.tensor(0.0, device=self.device) - tv_loss = self.obj.apply_soft_constraints(all_coords, tv_weight = 1e-5) ray_densities = all_densities.view(len(target_values), num_samples_per_ray) # Reshape rays and integarte step_size = 2.0 / (num_samples_per_ray - 1) @@ -412,6 +374,7 @@ def recon( epoch_tv_loss += tv_loss.detach() epoch_z1_loss += tv_loss_z1.detach() num_batches += 1 + for key, opt in self.optimizers.items(): @@ -421,7 +384,7 @@ def recon( aux_norm = torch.nn.utils.clip_grad_norm_(self.dataset.auxiliary_params.parameters(), max_norm = 1) opt.step() opt.zero_grad() - + for key, sched in self.schedulers.items(): sched.step() @@ -444,16 +407,16 @@ def recon( current_lr = self.schedulers["model"].get_last_lr()[0] # Log metrics - self.temp_logger.add_scalar("train/mse_loss", avg_mse_loss, epoch) + self.temp_logger.add_scalar("train/mse_loss", avg_mse_loss, self.global_epochs) - self.temp_logger.add_scalar("train/z1_loss", avg_z1_loss, epoch) + self.temp_logger.add_scalar("train/z1_loss", avg_z1_loss, self.global_epochs) # if tv_weight > 0: - self.temp_logger.add_scalar("train/tv_loss", avg_tv_loss, epoch) - self.temp_logger.add_scalar("train/total_loss", avg_loss, epoch) - self.temp_logger.add_scalar("train/model_grad_norm", model_norm.item(), epoch) - self.temp_logger.add_scalar("train/aux_grad_norm", aux_norm.item(), epoch) - self.temp_logger.add_scalar("train/lr", current_lr, epoch) - self.temp_logger.add_scalar("train/num_samples_per_ray", num_samples_per_ray, epoch) + self.temp_logger.add_scalar("train/tv_loss", avg_tv_loss, self.global_epochs) + self.temp_logger.add_scalar("train/total_loss", avg_loss, self.global_epochs) + self.temp_logger.add_scalar("train/model_grad_norm", model_norm.item(), self.global_epochs) + self.temp_logger.add_scalar("train/aux_grad_norm", aux_norm.item(), self.global_epochs) + self.temp_logger.add_scalar("train/lr", current_lr, self.global_epochs) + self.temp_logger.add_scalar("train/num_samples_per_ray", num_samples_per_ray, self.global_epochs) fig, axes = plt.subplots(1, 3, figsize=(36, 12)) axes[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap='turbo', vmin=0) @@ -468,9 +431,10 @@ def recon( axes[2].matshow(thick_slice, cmap='turbo', vmin=0) axes[2].set_title(f'Thick slice sum (Z={slice_start}:{slice_end-1})') - self.temp_logger.add_figure("train/viz", fig, epoch, close=True) + self.temp_logger.add_figure("train/viz", fig, self.global_epochs, close=True) plt.close(fig) - + self.global_epochs += 1 + if self.global_rank == 0: print("Training complete.") From f9a8afaddee2fa075c73a0e91e89f83c819ee95c Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 23 Sep 2025 09:44:54 -0700 Subject: [PATCH 012/335] Implemented ObjectINN with create volume in it (create_volume new version from Corneel's most recent code). Also implemented the soft constraints, thing left to abstract away is the logging. Need to look at auxiliary params, maybe looking at ray instantiation? --- src/quantem/tomography/object_models.py | 99 +++++++++++++++++++++++-- src/quantem/tomography/tomography.py | 59 +++------------ 2 files changed, 102 insertions(+), 56 deletions(-) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 5ef08894..bec78138 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -12,6 +12,7 @@ from quantem.tomography.utils import get_TV_loss import torch.nn as nn +import torch.distributed as dist class ObjectBase(AutoSerialize): @@ -560,12 +561,96 @@ def __init__( self._model = model + # --- Properties --- @property def obj(self): - raise NotImplementedError + return self._obj + + def create_volume( + self, + world_size: int, + global_rank: int, + ray_size: int, + ): + N = max(self._shape) + with torch.no_grad(): + coords_1d = torch.linspace(-1, 1, N) + x, y, z = torch.meshgrid(coords_1d, coords_1d, coords_1d, indexing='ij') + inputs = torch.stack([x, y, z], dim=-1).reshape(-1, 3) + + # Underlying model if using DDP + model = self.model.module if hasattr(self.model, 'module') else self.model + + # Batch size for inference + # 5x larger batches no gradients needed + + inference_batch_size = 5 * N * ray_size + + # Distribute work across GPUs + total_samples = N**3 + samples_per_gpu = total_samples // world_size + remainder = total_samples % world_size + + # Handle uneven distribution + if global_rank < remainder: + start_idx = global_rank * (samples_per_gpu + 1) + end_idx = start_idx + samples_per_gpu + 1 + else: + start_idx = global_rank * samples_per_gpu + remainder + end_idx = start_idx + samples_per_gpu + + inputs_subset = inputs[start_idx:end_idx] + num_samples = inputs_subset.shape[0] + outputs_list = [] + + for batch_start in range(0, num_samples, inference_batch_size): + batch_end = min(batch_start + inference_batch_size, num_samples) + batch_coords = inputs_subset[batch_start:batch_end].to(self.device, non_blocking=True) + + batch_outputs = model(batch_coords) + if batch_outputs.dim() > 1: + batch_outputs = batch_outputs.squeeze(-1) + + outputs_list.append(batch_outputs.cpu()) + + outputs = torch.cat(outputs_list, dim=0) + + if world_size > 1: + # Gather from all ranks + # handle potentially different sizes + output_size = torch.tensor(outputs.shape[0], device=self.device, dtype=torch.long) + all_sizes = [torch.zeros(1, device=self.device, dtype=torch.long) for _ in range(world_size)] + dist.all_gather(all_sizes, output_size) + + # Create gather list with correct sizes + max_size = max(size.item() for size in all_sizes) + + # Pad if necessary for gathering + if outputs.shape[0] < max_size: + padding = torch.zeros(max_size - outputs.shape[0], device=outputs.device, dtype=outputs.dtype) + outputs_padded = torch.cat([outputs, padding], dim=0).to(self.device) + else: + outputs_padded = outputs.to(self.device) + + gathered_outputs = [torch.empty(max_size, device=self.device, dtype=outputs.dtype) + for _ in range(world_size)] + dist.all_gather(gathered_outputs, outputs_padded.contiguous()) + + # Trim padding and concatenate + trimmed_outputs = [] + for rank, size in enumerate(all_sizes): + trimmed_outputs.append(gathered_outputs[rank][:size.item()]) + + pred_full = torch.cat(trimmed_outputs, dim=0).reshape(N, N, N).float() + else: + pred_full = outputs.reshape(N, N, N).float() + + + self._obj = pred_full.detach().cpu() + @property def model(self): @@ -577,12 +662,14 @@ def model(self, model): self._model = model + def apply_soft_constraints( self, coords: torch.Tensor, - tv_weight: float = 0.0, ): - if tv_weight > 0: + + soft_loss = torch.tensor(0.0, device = coords.device) + if self.soft_constraints["tv_vol"] > 0: num_tv_samples = min(10000, coords.shape[0]) tv_indices = torch.randperm(coords.shape[0], device = coords.device)[:num_tv_samples] @@ -603,11 +690,9 @@ def apply_soft_constraints( )[0] grad_norm = torch.norm(grad_outputs, dim = 1) - tv_loss = tv_weight * grad_norm.mean() - else: - tv_loss = torch.tensor(0.0, device = self.device) + soft_loss += self.soft_constraints["tv_vol"] * grad_norm.mean() - return tv_loss + return soft_loss def forward( self, diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 0e963347..b30f137b 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -124,15 +124,6 @@ def sirt_recon( # TODO: ML Recon which has NeRF and AD depending on the object type. # TODO: Temporary - - - def create_optimizer(self, params, lr, fused = True): - - return torch.optim.Adam( - params, - lr = lr, - fused = fused, - ) def create_batch_projection_rays(self, pixel_i, pixel_j, N, num_samples_per_ray): """Create projection rays for entire batch simultaneously.""" @@ -197,43 +188,7 @@ def transform_batch_ray_coordinates(self, rays, z1, x, z3, shifts, N, sampling_r transformed_rays = torch.stack([rays_x_final, rays_y_final, rays_z_final], dim=2) return transformed_rays - - # --- Creating Volume --- - def create_volume(self, model): - - N = max(self.dataset.dims) - - with torch.no_grad(): - coords_1d = torch.linspace(-1, 1, N) - x, y, z = torch.meshgrid(coords_1d, coords_1d, coords_1d, indexing='ij') - inputs = torch.stack([x, y, z], dim=-1).reshape(-1, 3) - - model = model.module if hasattr(model, 'module') else model - - samples_per_gpu = N**3 // self.world_size - start_idx = self.global_rank * samples_per_gpu - end_idx = start_idx + samples_per_gpu - - inputs_subset = inputs[start_idx:end_idx].to(self.device) - - # TODO: torch.nn.functional.softplus here - # outputs = torch.nn.functional.softplus(model(inputs_subset)) - outputs = model(inputs_subset) - - if outputs.dim() > 1: - outputs = outputs.squeeze(-1) - - if self.world_size > 1: - gathered_outputs = [torch.empty_like(outputs) for _ in range(self.world_size)] - dist.all_gather(gathered_outputs, outputs.contiguous()) - - pred_full = torch.cat(gathered_outputs, dim=0).reshape(N, N, N).float() - else: - pred_full = outputs.reshape(N, N, N).float() - - return pred_full - - + # TODO: Temp logger def setup_logger(self): if self.global_rank == 0: @@ -248,21 +203,25 @@ def recon( num_workers: int = 0, epochs = 20, use_amp = True, - tv_weight = 0.0, viz_freq = 1, checkpoint_freq = 5, optimizer_params: dict = None, scheduler_params: dict = None, + soft_constraints: dict = None, ): if not self.ddp_instantiated: self.setup_distributed() self.setup_dataloader(self.dataset, batch_size, num_workers) + self.ddp_instantiated = True if not hasattr(self, "obj"): obj.model = self.build_model(obj._model) self.obj = obj + if soft_constraints is not None: + self.obj.soft_constraints = soft_constraints + if not hasattr(self, "temp_logger"): self.setup_logger() @@ -356,7 +315,7 @@ def recon( all_densities = self.obj.forward(all_coords) - tv_loss = self.obj.apply_soft_constraints(all_coords, tv_weight = 1e-5) + tv_loss = self.obj.apply_soft_constraints(all_coords) ray_densities = all_densities.view(len(target_values), num_samples_per_ray) # Reshape rays and integarte step_size = 2.0 / (num_samples_per_ray - 1) @@ -391,7 +350,9 @@ def recon( if epoch % viz_freq == 0 or epoch == epochs - 1 or epoch % checkpoint_freq == 0 : with torch.no_grad(): - pred_full = self.create_volume(self.obj.model).cpu() + + self.obj.create_volume(world_size = self.world_size, global_rank = self.global_rank, ray_size = num_samples_per_ray) + pred_full = self.obj.obj avg_loss = epoch_loss.item() / num_batches avg_mse_loss = epoch_mse_loss.item() / num_batches avg_tv_loss = epoch_tv_loss.item() / num_batches From f3b5b86e84936301c8ac5a3f5ed43a36be11fa5a Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 23 Sep 2025 10:17:08 -0700 Subject: [PATCH 013/335] Tomo-NeRF working fully; just need to clean up a little bit --- src/quantem/tomography/tomography.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index b30f137b..d31964b4 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -19,6 +19,7 @@ import torch.distributed as dist # Temporary aux class for TomographyNERF +# TODO: Maybe put this in INN? def get_num_samples_per_ray(epoch): """Increase number of samples per ray at specific epochs.""" schedule = { @@ -132,7 +133,7 @@ def create_batch_projection_rays(self, pixel_i, pixel_j, N, num_samples_per_ray) # Convert all pixels to normalized coordinates x_coords = (pixel_j / (N - 1)) * 2 - 1 y_coords = (pixel_i / (N - 1)) * 2 - 1 - + # TODO: maybe pixel_j.device? # Create z coordinates z_coords = torch.linspace(-1, 1, num_samples_per_ray, device=self.device) @@ -214,11 +215,10 @@ def recon( self.setup_distributed() self.setup_dataloader(self.dataset, batch_size, num_workers) self.ddp_instantiated = True - - if not hasattr(self, "obj"): obj.model = self.build_model(obj._model) self.obj = obj + if soft_constraints is not None: self.obj.soft_constraints = soft_constraints @@ -248,7 +248,6 @@ def recon( aux_norm = torch.tensor(0.0, device=self.device) model_norm = torch.tensor(0.0, device=self.device) - for _, opt in self.optimizers.items(): opt.zero_grad() @@ -300,7 +299,8 @@ def recon( batch_ray_coords = self.create_batch_projection_rays( pixel_i, pixel_j, N, num_samples_per_ray ) - + + # TODO: .forward passing z1, x, z3, shifts, N, sampling_rate transformed_rays = self.transform_batch_ray_coordinates( batch_ray_coords, z1=batch_z1, From 867bb8e51ac76e07ed7adf91862511f218d1a154 Mon Sep 17 00:00:00 2001 From: cophus Date: Tue, 7 Oct 2025 12:54:50 -0700 Subject: [PATCH 014/335] Initial commit for background subtraction --- src/quantem/core/utils/imaging_utils.py | 195 +++++++++++++++++++++++- 1 file changed, 193 insertions(+), 2 deletions(-) diff --git a/src/quantem/core/utils/imaging_utils.py b/src/quantem/core/utils/imaging_utils.py index 7592dee4..125b0d30 100644 --- a/src/quantem/core/utils/imaging_utils.py +++ b/src/quantem/core/utils/imaging_utils.py @@ -1,12 +1,17 @@ # Utilities for processing images - -from typing import Optional, Tuple +from __future__ import annotations +from typing import Optional, Tuple, Union import numpy as np from numpy.typing import NDArray from scipy.ndimage import gaussian_filter +from scipy.special import comb from quantem.core.utils.utils import generate_batches +from quantem.core.datastructures.dataset2d import Dataset2d +from quantem.core.visualization import show_2d + +ArrayOrDS = Union[NDArray, Dataset2d] def dft_upsample( @@ -360,3 +365,189 @@ def fourier_cropping( result[-h2:, -w2:] = corner_centered_array[-h2:, -w2:] return result + + +def _as_array(x: ArrayOrDS) -> NDArray: + return x.array if isinstance(x, Dataset2d) else np.asarray(x) + + +def _like_dataset2d(arr: NDArray, template: Dataset2d, name: str) -> Dataset2d: + return Dataset2d.from_array( + arr, + name=name, + origin=getattr(template, "origin", None), + sampling=getattr(template, "sampling", None), + units=getattr(template, "units", None), + ) + + +def _bernstein_basis_1d(n: int, t: NDArray) -> NDArray: + """ + Bernstein basis B_k^n(t) for k=0..n, evaluated at t in [0,1]. + Returns shape (t.size, n+1). + """ + k = np.arange(n + 1, dtype=int) + return comb(n, k)[None, :] * (t[:, None] ** k[None, :]) * ((1.0 - t)[:, None] ** (n - k)[None, :]) + + +def _build_basis_matrix(im_shape: Tuple[int, int], order: Tuple[int, int]) -> NDArray: + """ + Builds A with shape (H*W, (ou+1)*(ov+1)) via a Kronecker product of 1D Bernstein bases. + """ + H, W = im_shape + ou, ov = int(order[0]), int(order[1]) + u = np.linspace(0.0, 1.0, H) + v = np.linspace(0.0, 1.0, W) + Bu = _bernstein_basis_1d(ou, u) # (H, ou+1) + Bv = _bernstein_basis_1d(ov, v) # (W, ov+1) + basis_cube = np.einsum("ik,jl->ijkl", Bu, Bv) # (H, W, ou+1, ov+1) + A = basis_cube.reshape(H * W, (ou + 1) * (ov + 1)) + return A + + +def background_subtract( + image: ArrayOrDS, + mask: Optional[ArrayOrDS] = None, + thresh_bg: Optional[float] = None, + order: Tuple[int, int] = (2, 2), + sigma: Optional[float] = None, + num_iter: int = 10, + plot_result: bool = True, + cmap: str = "turbo", + return_background: bool = False, + return_mask: bool = False, + **show_kwargs, +): + """ + Background subtraction using bi-variate Bernstein (Bezier) polynomial fitting + with iterative background pixel selection. + + Parameters + ---------- + image : np.ndarray or Dataset2d + Input 2D image. + mask : np.ndarray or Dataset2d, optional + Boolean mask selecting valid pixels (True = valid). If None, all pixels valid. + thresh_bg : float, optional + Threshold on residual (image - background) to classify background pixels. + If None, initialized to median(image[mask]) and reused each iteration. + order : (int, int), default (2,2) + Polynomial orders (row_order, col_order) for the Bernstein basis. + sigma : float, optional + Gaussian sigma (in pixels) for smoothing residuals before thresholding. + num_iter : int, default 10 + Number of fit/update iterations. + plot_result : bool, default True + If True, displays input, background, and background-subtracted images using show_2d. + cmap : str, default "turbo" + Colormap for plotting. + return_background : bool, default False + If True, also return the background image. + return_mask : bool, default False + If True, also return the final background mask (numpy bool array). + **show_kwargs + Passed through to `show_2d` (e.g., to enable scalebars if supported). + + Returns + ------- + image_sub : same type as `image` (np.ndarray or Dataset2d) + Background-subtracted image. + image_bg : same type as `image` (optional) + Estimated background image (returned if `return_background=True`). + mask_bg : np.ndarray of bool (optional) + Final background mask (returned if `return_mask=True`). + """ + # --- normalize inputs --- + is_dataset = isinstance(image, Dataset2d) + im = _as_array(image).astype(float, copy=True) + + if im.ndim != 2: + raise ValueError("`image` must be a 2D numpy array or Dataset2d") + + if mask is None: + mask_arr = np.ones_like(im, dtype=bool) + else: + mask_arr = _as_array(mask).astype(bool, copy=False) + if mask_arr.shape != im.shape: + raise ValueError("`mask` must have the same shape as `image`.") + + # --- build basis once --- + order = (int(order[0]), int(order[1])) + A_full = _build_basis_matrix(im.shape, order) # (H*W, K) + H, W = im.shape + im_flat = im.ravel() + + # --- initialize background & thresholds --- + im_bg = np.zeros_like(im) + if thresh_bg is None: + thresh_val = np.median(im[mask_arr]) + else: + thresh_val = float(thresh_bg) + + resid = im - im_bg + if sigma is not None and sigma > 0: + resid = gaussian_filter(resid, sigma=sigma, mode="nearest") + mask_bg = resid < thresh_val + mask_bg &= mask_arr + + # --- iterate fit/update --- + for _ in range(int(num_iter)): + idx = mask_bg.ravel() + if not np.any(idx): + idx = mask_arr.ravel() # ensure solvable if mask collapses + + coefs, *_ = np.linalg.lstsq(A_full[idx, :], im_flat[idx], rcond=None) + im_bg = (A_full @ coefs).reshape(H, W) + + resid = im - im_bg + if sigma is not None and sigma > 0: + resid = gaussian_filter(resid, sigma=sigma, mode="nearest") + + thr = thresh_val if thresh_bg is None else float(thresh_bg) + mask_bg = (resid < thr) + mask_bg &= mask_arr + + # --- final subtraction --- + im_sub = im - im_bg + + # --- plotting with quantem.show_2d (for scalebar support when available) --- + if plot_result: + vmin = float(np.min(im_sub[mask_arr])) + vmax = float(np.max(im_sub[mask_arr])) + disp = [ + im - np.mean(im_bg), + (im_bg - np.mean(im_bg)) * mask_bg, + im_sub, + ] + # Add default titles only if the caller didn't specify titles + local_kwargs = dict(vmin=vmin, vmax=vmax, cmap=cmap) + local_kwargs.update(show_kwargs) + if "title" not in local_kwargs: + local_kwargs["title"] = [ + "Input Image", + "Background Image (masked)", + "Background Subtracted", + ] + show_2d(disp, **local_kwargs) + + # --- package outputs in the same type as input --- + if is_dataset: + sub_ds = _like_dataset2d(im_sub, image, name=getattr(image, "name", "image") + " (background subtracted)") + bg_ds = _like_dataset2d(im_bg, image, name=getattr(image, "name", "image") + " (background)") + if return_background and return_mask: + return sub_ds, bg_ds, mask_bg + elif return_background: + return sub_ds, bg_ds + elif return_mask: + return sub_ds, mask_bg + else: + return sub_ds + else: + if return_background and return_mask: + return im_sub, im_bg, mask_bg + elif return_background: + return im_sub, im_bg + elif return_mask: + return im_sub, mask_bg + else: + return im_sub From 0d09a393b57cbac0e45d2b268cdcf8b6049c3e86 Mon Sep 17 00:00:00 2001 From: cophus Date: Tue, 7 Oct 2025 14:04:10 -0700 Subject: [PATCH 015/335] Updating plots for background subtraction --- src/quantem/core/utils/imaging_utils.py | 170 ++++++++++-------------- 1 file changed, 68 insertions(+), 102 deletions(-) diff --git a/src/quantem/core/utils/imaging_utils.py b/src/quantem/core/utils/imaging_utils.py index 125b0d30..c5f96b77 100644 --- a/src/quantem/core/utils/imaging_utils.py +++ b/src/quantem/core/utils/imaging_utils.py @@ -6,6 +6,7 @@ from numpy.typing import NDArray from scipy.ndimage import gaussian_filter from scipy.special import comb +from matplotlib import cm from quantem.core.utils.utils import generate_batches from quantem.core.datastructures.dataset2d import Dataset2d @@ -367,33 +368,16 @@ def fourier_cropping( return result -def _as_array(x: ArrayOrDS) -> NDArray: +def _as_array(x: NDArray | Dataset2d) -> NDArray: return x.array if isinstance(x, Dataset2d) else np.asarray(x) -def _like_dataset2d(arr: NDArray, template: Dataset2d, name: str) -> Dataset2d: - return Dataset2d.from_array( - arr, - name=name, - origin=getattr(template, "origin", None), - sampling=getattr(template, "sampling", None), - units=getattr(template, "units", None), - ) - - def _bernstein_basis_1d(n: int, t: NDArray) -> NDArray: - """ - Bernstein basis B_k^n(t) for k=0..n, evaluated at t in [0,1]. - Returns shape (t.size, n+1). - """ k = np.arange(n + 1, dtype=int) return comb(n, k)[None, :] * (t[:, None] ** k[None, :]) * ((1.0 - t)[:, None] ** (n - k)[None, :]) def _build_basis_matrix(im_shape: Tuple[int, int], order: Tuple[int, int]) -> NDArray: - """ - Builds A with shape (H*W, (ou+1)*(ov+1)) via a Kronecker product of 1D Bernstein bases. - """ H, W = im_shape ou, ov = int(order[0]), int(order[1]) u = np.linspace(0.0, 1.0, H) @@ -401,88 +385,53 @@ def _build_basis_matrix(im_shape: Tuple[int, int], order: Tuple[int, int]) -> ND Bu = _bernstein_basis_1d(ou, u) # (H, ou+1) Bv = _bernstein_basis_1d(ov, v) # (W, ov+1) basis_cube = np.einsum("ik,jl->ijkl", Bu, Bv) # (H, W, ou+1, ov+1) - A = basis_cube.reshape(H * W, (ou + 1) * (ov + 1)) - return A + return basis_cube.reshape(H * W, (ou + 1) * (ov + 1)) def background_subtract( - image: ArrayOrDS, - mask: Optional[ArrayOrDS] = None, + image: NDArray | Dataset2d, + mask: Optional[NDArray] = None, # boolean numpy array or None thresh_bg: Optional[float] = None, order: Tuple[int, int] = (2, 2), sigma: Optional[float] = None, num_iter: int = 10, plot_result: bool = True, + axsize: Tuple[int, int] = (3.1,3), cmap: str = "turbo", return_background: bool = False, return_mask: bool = False, **show_kwargs, ): """ - Background subtraction using bi-variate Bernstein (Bezier) polynomial fitting - with iterative background pixel selection. - - Parameters - ---------- - image : np.ndarray or Dataset2d - Input 2D image. - mask : np.ndarray or Dataset2d, optional - Boolean mask selecting valid pixels (True = valid). If None, all pixels valid. - thresh_bg : float, optional - Threshold on residual (image - background) to classify background pixels. - If None, initialized to median(image[mask]) and reused each iteration. - order : (int, int), default (2,2) - Polynomial orders (row_order, col_order) for the Bernstein basis. - sigma : float, optional - Gaussian sigma (in pixels) for smoothing residuals before thresholding. - num_iter : int, default 10 - Number of fit/update iterations. - plot_result : bool, default True - If True, displays input, background, and background-subtracted images using show_2d. - cmap : str, default "turbo" - Colormap for plotting. - return_background : bool, default False - If True, also return the background image. - return_mask : bool, default False - If True, also return the final background mask (numpy bool array). - **show_kwargs - Passed through to `show_2d` (e.g., to enable scalebars if supported). + Background subtraction via bi-variate Bernstein (Bezier) polynomial fitting. Returns ------- - image_sub : same type as `image` (np.ndarray or Dataset2d) - Background-subtracted image. - image_bg : same type as `image` (optional) - Estimated background image (returned if `return_background=True`). - mask_bg : np.ndarray of bool (optional) - Final background mask (returned if `return_mask=True`). + im_sub : np.ndarray + im_bg : np.ndarray (optional, if return_background=True) + mask_bg: np.ndarray bool (optional, if return_mask=True) """ - # --- normalize inputs --- - is_dataset = isinstance(image, Dataset2d) + # --- inputs --- im = _as_array(image).astype(float, copy=True) - if im.ndim != 2: raise ValueError("`image` must be a 2D numpy array or Dataset2d") if mask is None: mask_arr = np.ones_like(im, dtype=bool) else: - mask_arr = _as_array(mask).astype(bool, copy=False) + mask_arr = np.asarray(mask, dtype=bool) if mask_arr.shape != im.shape: raise ValueError("`mask` must have the same shape as `image`.") - # --- build basis once --- + # --- basis --- order = (int(order[0]), int(order[1])) A_full = _build_basis_matrix(im.shape, order) # (H*W, K) H, W = im.shape im_flat = im.ravel() - # --- initialize background & thresholds --- + # --- init background & mask --- im_bg = np.zeros_like(im) - if thresh_bg is None: - thresh_val = np.median(im[mask_arr]) - else: - thresh_val = float(thresh_bg) + thresh_val = np.median(im[mask_arr]) if thresh_bg is None else float(thresh_bg) resid = im - im_bg if sigma is not None and sigma > 0: @@ -490,7 +439,7 @@ def background_subtract( mask_bg = resid < thresh_val mask_bg &= mask_arr - # --- iterate fit/update --- + # --- iterations --- for _ in range(int(num_iter)): idx = mask_bg.ravel() if not np.any(idx): @@ -510,44 +459,61 @@ def background_subtract( # --- final subtraction --- im_sub = im - im_bg - # --- plotting with quantem.show_2d (for scalebar support when available) --- + # --- plotting (right panel: zero-centered RdBu_r with symmetric ±min(|min|,|max|) from im_sub[mask]) --- if plot_result: - vmin = float(np.min(im_sub[mask_arr])) - vmax = float(np.max(im_sub[mask_arr])) + # Use only the masked region to set the range + vals = im_sub[mask_arr] + vals = vals[np.isfinite(vals)] + if vals.size == 0: + vals = np.array([0.0]) + + vmin_sub = float(np.min(vals)) + vmax_sub = float(np.max(vals)) + # vrange = float(np.minimum(abs(vmin_sub), abs(vmax_sub))) + vrange = float(np.maximum(abs(vmin_sub), abs(vmax_sub))) + if vrange <= 0: + vrange = 1e-12 # avoid zero-width range + + # Background panel: show only fitted region; black elsewhere + bg_disp = (im_bg - np.mean(im_bg)).copy() + bg_disp[~mask_bg] = np.nan + + # Colormaps + cmap_base = cm.get_cmap(cmap).with_extremes(bad="black") # NaNs -> black + cmap_div = "RdBu_r" # diverging; center (white) at 0 via centered interval + + # Panels: input, background(masked), background-subtracted disp = [ im - np.mean(im_bg), - (im_bg - np.mean(im_bg)) * mask_bg, + bg_disp, im_sub, ] - # Add default titles only if the caller didn't specify titles - local_kwargs = dict(vmin=vmin, vmax=vmax, cmap=cmap) - local_kwargs.update(show_kwargs) - if "title" not in local_kwargs: - local_kwargs["title"] = [ - "Input Image", - "Background Image (masked)", - "Background Subtracted", - ] - show_2d(disp, **local_kwargs) - - # --- package outputs in the same type as input --- - if is_dataset: - sub_ds = _like_dataset2d(im_sub, image, name=getattr(image, "name", "image") + " (background subtracted)") - bg_ds = _like_dataset2d(im_bg, image, name=getattr(image, "name", "image") + " (background)") - if return_background and return_mask: - return sub_ds, bg_ds, mask_bg - elif return_background: - return sub_ds, bg_ds - elif return_mask: - return sub_ds, mask_bg - else: - return sub_ds + + # Per-panel normalization: + # - Panels 1 & 2: manual min/max from masked im_sub + # - Panel 3 : centered at 0.0 with half_range = vrange (white == 0.0) + norm = [ + {"interval_type": "manual", "stretch_type": "linear", "vmin": vmin_sub, "vmax": vmax_sub}, # input + {"interval_type": "manual", "stretch_type": "linear", "vmin": vmin_sub, "vmax": vmax_sub}, # background + {"interval_type": "centered", "stretch_type": "linear", "vcenter": 0.0, "half_range": vrange}, # zero-centered + ] + + show_2d( + disp, + cmap=[cmap_base, cmap_base, cmap_div], + norm=norm, + cbar=[False, False, True], # colorbar only on right panel + title=["Input Image", "Background (fit region)", "Background Subtracted"], + axsize=axsize, + **show_kwargs, + ) + + # --- outputs (numpy arrays) --- + if return_background and return_mask: + return im_sub, im_bg, mask_bg + elif return_background: + return im_sub, im_bg + elif return_mask: + return im_sub, mask_bg else: - if return_background and return_mask: - return im_sub, im_bg, mask_bg - elif return_background: - return im_sub, im_bg - elif return_mask: - return im_sub, mask_bg - else: - return im_sub + return im_sub \ No newline at end of file From f34b9e158a56268135a0bd5ec07e255b17c215eb Mon Sep 17 00:00:00 2001 From: cophus Date: Tue, 7 Oct 2025 14:16:19 -0700 Subject: [PATCH 016/335] Adding function overloading --- src/quantem/core/utils/imaging_utils.py | 153 ++++++++++++++++++++---- 1 file changed, 129 insertions(+), 24 deletions(-) diff --git a/src/quantem/core/utils/imaging_utils.py b/src/quantem/core/utils/imaging_utils.py index c5f96b77..89459b74 100644 --- a/src/quantem/core/utils/imaging_utils.py +++ b/src/quantem/core/utils/imaging_utils.py @@ -1,6 +1,6 @@ # Utilities for processing images from __future__ import annotations -from typing import Optional, Tuple, Union +from typing import Optional, Tuple, Union, Literal, overload import numpy as np from numpy.typing import NDArray @@ -388,6 +388,66 @@ def _build_basis_matrix(im_shape: Tuple[int, int], order: Tuple[int, int]) -> ND return basis_cube.reshape(H * W, (ou + 1) * (ov + 1)) +# --- Overloads: NDArray input --- +@overload +def background_subtract( + image: NDArray, + mask: Optional[NDArray] = ..., + thresh_bg: Optional[float] = ..., + order: Tuple[int, int] = ..., + sigma: Optional[float] = ..., + num_iter: int = ..., + plot_result: bool = ..., + axsize: Tuple[int, int] = ..., + cmap: str = ..., + return_background_and_mask: Literal[False] = ..., + **show_kwargs, +) -> NDArray: ... +@overload +def background_subtract( + image: NDArray, + mask: Optional[NDArray] = ..., + thresh_bg: Optional[float] = ..., + order: Tuple[int, int] = ..., + sigma: Optional[float] = ..., + num_iter: int = ..., + plot_result: bool = ..., + axsize: Tuple[int, int] = ..., + cmap: str = ..., + return_background_and_mask: Literal[True] = ..., + **show_kwargs, +) -> Tuple[NDArray, NDArray, NDArray]: ... + +# --- Overloads: Dataset2d input (mask stays NDArray) --- +@overload +def background_subtract( + image: Dataset2d, + mask: Optional[NDArray] = ..., + thresh_bg: Optional[float] = ..., + order: Tuple[int, int] = ..., + sigma: Optional[float] = ..., + num_iter: int = ..., + plot_result: bool = ..., + axsize: Tuple[int, int] = ..., + cmap: str = ..., + return_background_and_mask: Literal[False] = ..., + **show_kwargs, +) -> Dataset2d: ... +@overload +def background_subtract( + image: Dataset2d, + mask: Optional[NDArray] = ..., + thresh_bg: Optional[float] = ..., + order: Tuple[int, int] = ..., + sigma: Optional[float] = ..., + num_iter: int = ..., + plot_result: bool = ..., + axsize: Tuple[int, int] = ..., + cmap: str = ..., + return_background_and_mask: Literal[True] = ..., + **show_kwargs, +) -> Tuple[Dataset2d, Dataset2d, NDArray]: ... + def background_subtract( image: NDArray | Dataset2d, mask: Optional[NDArray] = None, # boolean numpy array or None @@ -396,20 +456,62 @@ def background_subtract( sigma: Optional[float] = None, num_iter: int = 10, plot_result: bool = True, - axsize: Tuple[int, int] = (3.1,3), + axsize: Tuple[int, int] = (3.1, 3), cmap: str = "turbo", - return_background: bool = False, - return_mask: bool = False, + return_background_and_mask: bool = False, **show_kwargs, ): """ - Background subtraction via bi-variate Bernstein (Bezier) polynomial fitting. + Background subtraction via bi-variate Bernstein (Bézier) polynomial fitting. + + Parameters + ---------- + image : numpy.ndarray or Dataset2d + 2D input image. If a Dataset2d is provided, only its array is used for the fit; + outputs are returned as Dataset2d (same metadata preserved). + mask : numpy.ndarray of bool, optional + Boolean mask (same shape as `image`) indicating valid pixels for fitting and for + range calculations in the diagnostic plot. If None, all pixels are valid. + thresh_bg : float, optional + Background threshold to classify pixels as background during robust iterations. + If None, initialized from the median of `image[mask]`. + order : tuple[int, int], default (2, 2) + Polynomial order (u, v) for the Bernstein basis along the two axes. + Number of basis terms = (order[0] + 1) * (order[1] + 1). + sigma : float, optional + Gaussian sigma (pixels) to smooth residuals before thresholding at each iteration. + If None or 0, no smoothing is applied. + num_iter : int, default 10 + Number of robust fitting iterations (refit background, update mask). + plot_result : bool, default True + If True, displays a 3-panel diagnostic: + (1) Input (mean-centered by background mean), + (2) Fitted background shown only where `mask_bg` is True (else black), + (3) Background-subtracted image with `RdBu_r`, zero-centered, symmetric ±min(|min|,|max|) + computed from `im_sub[mask]`. Colorbar is shown on panel (3). + axsize : tuple[int, int], default (3.1, 3) + Panel size in inches, forwarded to `show_2d`. + cmap : str, default "turbo" + Colormap for panels (1) and (2). Panel (3) always uses "RdBu_r". + return_background_and_mask : bool, default False + If True, also return the fitted background and the final background mask. + + **show_kwargs + Additional arguments passed to `quantem.core.visualization.show_2d`. Returns ------- - im_sub : np.ndarray - im_bg : np.ndarray (optional, if return_background=True) - mask_bg: np.ndarray bool (optional, if return_mask=True) + im_sub : numpy.ndarray or Dataset2d + Background-subtracted image (same type as `image`). + im_bg : numpy.ndarray or Dataset2d, optional + Fitted background (same type as `image`). Returned if `return_background_and_mask=True`. + mask_bg : numpy.ndarray of bool, optional + Final background mask (always NumPy). Returned if `return_background_and_mask=True`. + + Notes + ----- + - The right-panel plot uses `im_sub[mask]` to compute a symmetric, zero-centered range + so white corresponds to 0.0 in RdBu_r. """ # --- inputs --- im = _as_array(image).astype(float, copy=True) @@ -459,9 +561,9 @@ def background_subtract( # --- final subtraction --- im_sub = im - im_bg - # --- plotting (right panel: zero-centered RdBu_r with symmetric ±min(|min|,|max|) from im_sub[mask]) --- + # --- plotting (right panel: zero-centered RdBu_r; limits from im_sub[mask]) --- if plot_result: - # Use only the masked region to set the range + # Range from masked region vals = im_sub[mask_arr] vals = vals[np.isfinite(vals)] if vals.size == 0: @@ -469,10 +571,9 @@ def background_subtract( vmin_sub = float(np.min(vals)) vmax_sub = float(np.max(vals)) - # vrange = float(np.minimum(abs(vmin_sub), abs(vmax_sub))) - vrange = float(np.maximum(abs(vmin_sub), abs(vmax_sub))) + vrange = float(np.maximum(abs(vmin_sub), abs(vmax_sub))) # ±min(|min|,|max|) if vrange <= 0: - vrange = 1e-12 # avoid zero-width range + vrange = 1e-12 # Background panel: show only fitted region; black elsewhere bg_disp = (im_bg - np.mean(im_bg)).copy() @@ -480,7 +581,7 @@ def background_subtract( # Colormaps cmap_base = cm.get_cmap(cmap).with_extremes(bad="black") # NaNs -> black - cmap_div = "RdBu_r" # diverging; center (white) at 0 via centered interval + cmap_div = "RdBu_r" # Panels: input, background(masked), background-subtracted disp = [ @@ -490,8 +591,6 @@ def background_subtract( ] # Per-panel normalization: - # - Panels 1 & 2: manual min/max from masked im_sub - # - Panel 3 : centered at 0.0 with half_range = vrange (white == 0.0) norm = [ {"interval_type": "manual", "stretch_type": "linear", "vmin": vmin_sub, "vmax": vmax_sub}, # input {"interval_type": "manual", "stretch_type": "linear", "vmin": vmin_sub, "vmax": vmax_sub}, # background @@ -508,12 +607,18 @@ def background_subtract( **show_kwargs, ) - # --- outputs (numpy arrays) --- - if return_background and return_mask: - return im_sub, im_bg, mask_bg - elif return_background: - return im_sub, im_bg - elif return_mask: - return im_sub, mask_bg + # --- outputs (match image type for images; mask always ndarray) --- + if isinstance(image, Dataset2d): + meta = dict(origin=image.origin, sampling=image.sampling, units=image.units) + name_base = getattr(image, "name", "image") + im_sub_ds = Dataset2d.from_array(im_sub, name=f"{name_base} (bg-sub)", **meta) + im_bg_ds = Dataset2d.from_array(im_bg, name=f"{name_base} (bg-fit)", **meta) + if return_background_and_mask: + return im_sub_ds, im_bg_ds, mask_bg + else: + return im_sub_ds else: - return im_sub \ No newline at end of file + if return_background_and_mask: + return im_sub, im_bg, mask_bg + else: + return im_sub From 9f9ef88a6051f2b6746efb1a038a633c17775df4 Mon Sep 17 00:00:00 2001 From: cophus Date: Tue, 7 Oct 2025 14:18:11 -0700 Subject: [PATCH 017/335] More conservative fitting order --- src/quantem/core/utils/imaging_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quantem/core/utils/imaging_utils.py b/src/quantem/core/utils/imaging_utils.py index 89459b74..c6f893cb 100644 --- a/src/quantem/core/utils/imaging_utils.py +++ b/src/quantem/core/utils/imaging_utils.py @@ -452,7 +452,7 @@ def background_subtract( image: NDArray | Dataset2d, mask: Optional[NDArray] = None, # boolean numpy array or None thresh_bg: Optional[float] = None, - order: Tuple[int, int] = (2, 2), + order: Tuple[int, int] = (1, 1), sigma: Optional[float] = None, num_iter: int = 10, plot_result: bool = True, From da1055139acf4490a7c8dc72c0acf7eabd21e05a Mon Sep 17 00:00:00 2001 From: cophus Date: Fri, 10 Oct 2025 15:52:31 -0700 Subject: [PATCH 018/335] Switching from @overload to TypeVar --- src/quantem/core/utils/imaging_utils.py | 221 +++++------------------- 1 file changed, 44 insertions(+), 177 deletions(-) diff --git a/src/quantem/core/utils/imaging_utils.py b/src/quantem/core/utils/imaging_utils.py index c6f893cb..4e3ca537 100644 --- a/src/quantem/core/utils/imaging_utils.py +++ b/src/quantem/core/utils/imaging_utils.py @@ -1,6 +1,6 @@ # Utilities for processing images from __future__ import annotations -from typing import Optional, Tuple, Union, Literal, overload +from typing import Any, Optional, Tuple, TypeVar import numpy as np from numpy.typing import NDArray @@ -8,11 +8,12 @@ from scipy.special import comb from matplotlib import cm -from quantem.core.utils.utils import generate_batches from quantem.core.datastructures.dataset2d import Dataset2d from quantem.core.visualization import show_2d -ArrayOrDS = Union[NDArray, Dataset2d] +# single TypeVar: works for both numpy and Dataset2d +ImageType = TypeVar("ImageType", NDArray[Any], Dataset2d) +BoolArray = NDArray[np.bool_] def dft_upsample( @@ -368,89 +369,27 @@ def fourier_cropping( return result -def _as_array(x: NDArray | Dataset2d) -> NDArray: +def _as_array(x: ImageType) -> NDArray[Any]: return x.array if isinstance(x, Dataset2d) else np.asarray(x) - -def _bernstein_basis_1d(n: int, t: NDArray) -> NDArray: +def _bernstein_basis_1d(n: int, t: NDArray[Any]) -> NDArray[Any]: k = np.arange(n + 1, dtype=int) return comb(n, k)[None, :] * (t[:, None] ** k[None, :]) * ((1.0 - t)[:, None] ** (n - k)[None, :]) - -def _build_basis_matrix(im_shape: Tuple[int, int], order: Tuple[int, int]) -> NDArray: +def _build_basis_matrix(im_shape: Tuple[int, int], order: Tuple[int, int]) -> NDArray[Any]: H, W = im_shape ou, ov = int(order[0]), int(order[1]) u = np.linspace(0.0, 1.0, H) v = np.linspace(0.0, 1.0, W) - Bu = _bernstein_basis_1d(ou, u) # (H, ou+1) - Bv = _bernstein_basis_1d(ov, v) # (W, ov+1) - basis_cube = np.einsum("ik,jl->ijkl", Bu, Bv) # (H, W, ou+1, ov+1) + Bu = _bernstein_basis_1d(ou, u) + Bv = _bernstein_basis_1d(ov, v) + basis_cube = np.einsum("ik,jl->ijkl", Bu, Bv) return basis_cube.reshape(H * W, (ou + 1) * (ov + 1)) -# --- Overloads: NDArray input --- -@overload -def background_subtract( - image: NDArray, - mask: Optional[NDArray] = ..., - thresh_bg: Optional[float] = ..., - order: Tuple[int, int] = ..., - sigma: Optional[float] = ..., - num_iter: int = ..., - plot_result: bool = ..., - axsize: Tuple[int, int] = ..., - cmap: str = ..., - return_background_and_mask: Literal[False] = ..., - **show_kwargs, -) -> NDArray: ... -@overload -def background_subtract( - image: NDArray, - mask: Optional[NDArray] = ..., - thresh_bg: Optional[float] = ..., - order: Tuple[int, int] = ..., - sigma: Optional[float] = ..., - num_iter: int = ..., - plot_result: bool = ..., - axsize: Tuple[int, int] = ..., - cmap: str = ..., - return_background_and_mask: Literal[True] = ..., - **show_kwargs, -) -> Tuple[NDArray, NDArray, NDArray]: ... - -# --- Overloads: Dataset2d input (mask stays NDArray) --- -@overload -def background_subtract( - image: Dataset2d, - mask: Optional[NDArray] = ..., - thresh_bg: Optional[float] = ..., - order: Tuple[int, int] = ..., - sigma: Optional[float] = ..., - num_iter: int = ..., - plot_result: bool = ..., - axsize: Tuple[int, int] = ..., - cmap: str = ..., - return_background_and_mask: Literal[False] = ..., - **show_kwargs, -) -> Dataset2d: ... -@overload -def background_subtract( - image: Dataset2d, - mask: Optional[NDArray] = ..., - thresh_bg: Optional[float] = ..., - order: Tuple[int, int] = ..., - sigma: Optional[float] = ..., - num_iter: int = ..., - plot_result: bool = ..., - axsize: Tuple[int, int] = ..., - cmap: str = ..., - return_background_and_mask: Literal[True] = ..., - **show_kwargs, -) -> Tuple[Dataset2d, Dataset2d, NDArray]: ... - def background_subtract( - image: NDArray | Dataset2d, - mask: Optional[NDArray] = None, # boolean numpy array or None + image: ImageType, + mask: Optional[BoolArray] = None, thresh_bg: Optional[float] = None, order: Tuple[int, int] = (1, 1), sigma: Optional[float] = None, @@ -460,165 +399,93 @@ def background_subtract( cmap: str = "turbo", return_background_and_mask: bool = False, **show_kwargs, -): +) -> ImageType | Tuple[ImageType, NDArray[Any], BoolArray]: """ - Background subtraction via bi-variate Bernstein (Bézier) polynomial fitting. - - Parameters - ---------- - image : numpy.ndarray or Dataset2d - 2D input image. If a Dataset2d is provided, only its array is used for the fit; - outputs are returned as Dataset2d (same metadata preserved). - mask : numpy.ndarray of bool, optional - Boolean mask (same shape as `image`) indicating valid pixels for fitting and for - range calculations in the diagnostic plot. If None, all pixels are valid. - thresh_bg : float, optional - Background threshold to classify pixels as background during robust iterations. - If None, initialized from the median of `image[mask]`. - order : tuple[int, int], default (2, 2) - Polynomial order (u, v) for the Bernstein basis along the two axes. - Number of basis terms = (order[0] + 1) * (order[1] + 1). - sigma : float, optional - Gaussian sigma (pixels) to smooth residuals before thresholding at each iteration. - If None or 0, no smoothing is applied. - num_iter : int, default 10 - Number of robust fitting iterations (refit background, update mask). - plot_result : bool, default True - If True, displays a 3-panel diagnostic: - (1) Input (mean-centered by background mean), - (2) Fitted background shown only where `mask_bg` is True (else black), - (3) Background-subtracted image with `RdBu_r`, zero-centered, symmetric ±min(|min|,|max|) - computed from `im_sub[mask]`. Colorbar is shown on panel (3). - axsize : tuple[int, int], default (3.1, 3) - Panel size in inches, forwarded to `show_2d`. - cmap : str, default "turbo" - Colormap for panels (1) and (2). Panel (3) always uses "RdBu_r". - return_background_and_mask : bool, default False - If True, also return the fitted background and the final background mask. - - **show_kwargs - Additional arguments passed to `quantem.core.visualization.show_2d`. + Background subtraction via bivariate Bernstein polynomial fitting. Returns ------- - im_sub : numpy.ndarray or Dataset2d - Background-subtracted image (same type as `image`). - im_bg : numpy.ndarray or Dataset2d, optional - Fitted background (same type as `image`). Returned if `return_background_and_mask=True`. - mask_bg : numpy.ndarray of bool, optional - Final background mask (always NumPy). Returned if `return_background_and_mask=True`. - - Notes - ----- - - The right-panel plot uses `im_sub[mask]` to compute a symmetric, zero-centered range - so white corresponds to 0.0 in RdBu_r. + - If `return_background_and_mask=False`: ImageType (same as input) + - If `True`: (ImageType, numpy.ndarray, numpy.ndarray[bool]) + where background and mask are always NumPy. """ - # --- inputs --- im = _as_array(image).astype(float, copy=True) if im.ndim != 2: - raise ValueError("`image` must be a 2D numpy array or Dataset2d") + raise ValueError("`image` must be 2D") - if mask is None: - mask_arr = np.ones_like(im, dtype=bool) - else: - mask_arr = np.asarray(mask, dtype=bool) - if mask_arr.shape != im.shape: - raise ValueError("`mask` must have the same shape as `image`.") + mask_arr: BoolArray = np.ones_like(im, dtype=bool) if mask is None else np.asarray(mask, dtype=bool) + if mask_arr.shape != im.shape: + raise ValueError("`mask` must match `image` shape") - # --- basis --- order = (int(order[0]), int(order[1])) - A_full = _build_basis_matrix(im.shape, order) # (H*W, K) + A_full = _build_basis_matrix(im.shape, order) H, W = im.shape im_flat = im.ravel() - # --- init background & mask --- im_bg = np.zeros_like(im) thresh_val = np.median(im[mask_arr]) if thresh_bg is None else float(thresh_bg) resid = im - im_bg - if sigma is not None and sigma > 0: + if sigma and sigma > 0: resid = gaussian_filter(resid, sigma=sigma, mode="nearest") - mask_bg = resid < thresh_val - mask_bg &= mask_arr + mask_bg: BoolArray = (resid < thresh_val) & mask_arr - # --- iterations --- for _ in range(int(num_iter)): idx = mask_bg.ravel() if not np.any(idx): - idx = mask_arr.ravel() # ensure solvable if mask collapses - + idx = mask_arr.ravel() coefs, *_ = np.linalg.lstsq(A_full[idx, :], im_flat[idx], rcond=None) im_bg = (A_full @ coefs).reshape(H, W) resid = im - im_bg - if sigma is not None and sigma > 0: + if sigma and sigma > 0: resid = gaussian_filter(resid, sigma=sigma, mode="nearest") thr = thresh_val if thresh_bg is None else float(thresh_bg) - mask_bg = (resid < thr) - mask_bg &= mask_arr + mask_bg = (resid < thr) & mask_arr - # --- final subtraction --- - im_sub = im - im_bg + im_sub_np = im - im_bg - # --- plotting (right panel: zero-centered RdBu_r; limits from im_sub[mask]) --- if plot_result: - # Range from masked region - vals = im_sub[mask_arr] + vals = im_sub_np[mask_arr] vals = vals[np.isfinite(vals)] if vals.size == 0: vals = np.array([0.0]) - vmin_sub = float(np.min(vals)) vmax_sub = float(np.max(vals)) - vrange = float(np.maximum(abs(vmin_sub), abs(vmax_sub))) # ±min(|min|,|max|) - if vrange <= 0: - vrange = 1e-12 + vrange = float(max(abs(vmin_sub), abs(vmax_sub))) or 1e-12 - # Background panel: show only fitted region; black elsewhere bg_disp = (im_bg - np.mean(im_bg)).copy() bg_disp[~mask_bg] = np.nan - # Colormaps - cmap_base = cm.get_cmap(cmap).with_extremes(bad="black") # NaNs -> black - cmap_div = "RdBu_r" + cmap_base = cm.get_cmap(cmap).with_extremes(bad="black") + cmap_div = "RdBu_r" - # Panels: input, background(masked), background-subtracted - disp = [ - im - np.mean(im_bg), - bg_disp, - im_sub, - ] - - # Per-panel normalization: + disp = [im - np.mean(im_bg), bg_disp, im_sub_np] norm = [ - {"interval_type": "manual", "stretch_type": "linear", "vmin": vmin_sub, "vmax": vmax_sub}, # input - {"interval_type": "manual", "stretch_type": "linear", "vmin": vmin_sub, "vmax": vmax_sub}, # background - {"interval_type": "centered", "stretch_type": "linear", "vcenter": 0.0, "half_range": vrange}, # zero-centered + {"interval_type": "manual", "stretch_type": "linear", "vmin": vmin_sub, "vmax": vmax_sub}, + {"interval_type": "manual", "stretch_type": "linear", "vmin": vmin_sub, "vmax": vmax_sub}, + {"interval_type": "centered", "stretch_type": "linear", "vcenter": 0.0, "half_range": vrange}, ] show_2d( disp, cmap=[cmap_base, cmap_base, cmap_div], norm=norm, - cbar=[False, False, True], # colorbar only on right panel + cbar=[False, False, True], title=["Input Image", "Background (fit region)", "Background Subtracted"], axsize=axsize, **show_kwargs, ) - # --- outputs (match image type for images; mask always ndarray) --- + # preserve Dataset2d if needed if isinstance(image, Dataset2d): meta = dict(origin=image.origin, sampling=image.sampling, units=image.units) name_base = getattr(image, "name", "image") - im_sub_ds = Dataset2d.from_array(im_sub, name=f"{name_base} (bg-sub)", **meta) - im_bg_ds = Dataset2d.from_array(im_bg, name=f"{name_base} (bg-fit)", **meta) - if return_background_and_mask: - return im_sub_ds, im_bg_ds, mask_bg - else: - return im_sub_ds + im_sub: ImageType = Dataset2d.from_array(im_sub_np, name=f"{name_base} (bg-sub)", **meta) # type: ignore[assignment] else: - if return_background_and_mask: - return im_sub, im_bg, mask_bg - else: - return im_sub + im_sub = im_sub_np # type: ignore[assignment] + + if return_background_and_mask: + return im_sub, im_bg, mask_bg + return im_sub From f1dc677e600b7ff38667b643f5523d5170be169e Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 14 Oct 2025 09:43:55 -0700 Subject: [PATCH 019/335] Tomo changes --- src/quantem/tomography/tilt_series_dataset.py | 153 ---- src/quantem/tomography/tomography.py | 35 +- src/quantem/tomography/tomography_base.py | 2 +- src/quantem/tomography/tomography_nerf_old.py | 658 ------------------ 4 files changed, 30 insertions(+), 818 deletions(-) delete mode 100644 src/quantem/tomography/tilt_series_dataset.py delete mode 100644 src/quantem/tomography/tomography_nerf_old.py diff --git a/src/quantem/tomography/tilt_series_dataset.py b/src/quantem/tomography/tilt_series_dataset.py deleted file mode 100644 index f6bff836..00000000 --- a/src/quantem/tomography/tilt_series_dataset.py +++ /dev/null @@ -1,153 +0,0 @@ -from typing import Any, List, Self - -import numpy as np -import torch -from numpy.typing import NDArray - -from quantem.core.datastructures.dataset3d import Dataset2d, Dataset3d -from quantem.core.utils.validators import ensure_valid_array - -# from quantem.tomography.alignment import tilt_series_cross_cor_align, compute_com_tilt_series - - -# DEPRECATED: Use TomographyDataset instead. -class TiltSeries(Dataset3d): - def __init__( - self, - array: NDArray | Any, # Assumes a input tilt series [phis, x, y] - name: str, - origin: NDArray | tuple | list | float | int, - sampling: NDArray | tuple | list | float | int, - units: list[str] | tuple | list, - tilt_angles: list | NDArray, - z1_angles: list | NDArray, - z3_angles: list | NDArray, - shifts: list[tuple[float, float]] | NDArray, - signal_units: str = "arb. units", - _token: object | None = None, - ): - super().__init__( - array=array, - name=name, - origin=origin, - sampling=sampling, - units=units, - signal_units=signal_units, - _token=_token, - ) - self._tilt_angles = tilt_angles - self._z1_angles = z1_angles - self._z3_angles = z3_angles - self._shifts = shifts - - @classmethod - def from_array( - cls, - array: NDArray | List[Dataset2d] | Any, - tilt_angles: list | NDArray = None, - z1_angles: list | NDArray = None, - z3_angles: list | NDArray = None, - shifts: list[tuple[float, float]] | NDArray = None, - name: str | None = None, - origin: NDArray | tuple | list | float | int | None = None, - sampling: NDArray | tuple | list | float | int | None = None, - units: list[str] | tuple | list | None = None, - signal_units: str = "arb. units", - ) -> Self: - if tilt_angles is not None: - validated_tilt_angles = ensure_valid_array(tilt_angles, ndim=1) - else: - validated_tilt_angles = None - - # array = np.transpose(array, axes=(2, 0, 1)) - - if z1_angles is not None: - validated_z1_angles = ensure_valid_array(z1_angles, ndim=1) - else: - validated_z1_angles = torch.zeros(len(validated_tilt_angles)) - - if z3_angles is not None: - validated_z3_angles = ensure_valid_array(z3_angles, ndim=1) - else: - validated_z3_angles = torch.zeros(len(validated_tilt_angles)) - - if shifts is not None: - validated_shifts = ensure_valid_array(shifts, ndim=2) - else: - validated_shifts = torch.zeros((len(validated_tilt_angles), 2)) - - array = torch.from_numpy(array) - - return cls( - array=array, - tilt_angles=validated_tilt_angles - if validated_tilt_angles is not None - else ["duck" for _ in range(array.shape[0])], - z1_angles=validated_z1_angles, - z3_angles=validated_z3_angles, - shifts=validated_shifts, - name=name if name is not None else "Tilt Series Dataset", - origin=origin if origin is not None else np.zeros(3), - sampling=sampling if sampling is not None else np.ones(3), - units=units if units is not None else ["index", "pixels", "pixels"], - signal_units=signal_units, - _token=cls._token, - ) - - # --- Properties --- - - @property - def tilt_angles(self) -> NDArray: - """Get the tilt angles of the dataset.""" - return self._tilt_angles - - @property - def tilt_angles_rad(self) -> NDArray: - """Get the tilt angles of the dataset in radians.""" - return np.deg2rad(self._tilt_angles) - - @tilt_angles.setter - def tilt_angles(self, angles: NDArray | list) -> None: - """Set the tilt angles of the dataset.""" - if len(angles) != self.shape[0]: - raise ValueError("Tilt angles must match the number of projections.") - - # Convert to numpy array if not already - if isinstance(self._tilt_angles, NDArray): - self._tilt_angles = np.array(angles) - else: - self._tilt_angles = angles - - @property - def z1_angles(self) -> NDArray: - """Get the z1 angles of the dataset.""" - return self._z1_angles - - @z1_angles.setter - def z1_angles(self, angles: NDArray | list) -> None: - """Set the z1 angles of the dataset.""" - if len(angles) != self.shape[0]: - raise ValueError("Z1 angles must match the number of projections.") - self._z1_angles = angles - - @property - def z3_angles(self) -> NDArray: - """Get the z3 angles of the dataset.""" - return self._z3_angles - - @z3_angles.setter - def z3_angles(self, angles: NDArray | list) -> None: - """Set the z3 angles of the dataset.""" - if len(angles) != self.shape[0]: - raise ValueError("Z3 angles must match the number of projections.") - self._z3_angles = angles - - @property - def shifts(self) -> NDArray: - """Get the shifts of the dataset.""" - return self._shifts - - @shifts.setter - def shifts(self, shifts: list[tuple[float, float]] | NDArray) -> None: - """Set the shifts of the dataset.""" - self._shifts = shifts diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index d31964b4..ffb16d15 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -27,6 +27,7 @@ def get_num_samples_per_ray(epoch): 2: 50, 4: 100, 6: 200, + 8: 350, } num_samples = 64 @@ -100,6 +101,8 @@ def sirt_recon( else: gaussian_kernel = None + print("Devices", sirt_tilt_series.device, proj_forward.device, self.dataset.tilt_angles.device) + for iter in pbar: proj_forward, loss = self._sirt_run_epoch( tilt_series=sirt_tilt_series, @@ -196,7 +199,8 @@ def setup_logger(self): self.temp_logger = SummaryWriter() else: self.temp_logger = None - + + def recon( self, obj: ObjectINN, @@ -260,13 +264,19 @@ def recon( for epoch in range(epochs): num_samples_per_ray = get_num_samples_per_ray(self.global_epochs) + # num_samples_per_ray = get_num_samples_per_ray(epoch) # Log the change if it happens - if self.global_epochs >= 0: - prev_samples = get_num_samples_per_ray(self.global_epochs) + if self.global_rank == 0: + print(f"Epoch {epoch}: num_samples_per_ray = {num_samples_per_ray}") - if num_samples_per_ray != prev_samples and self.global_rank == 0: + + if self.global_rank == 0 and self.global_epochs > 0: + prev_samples = get_num_samples_per_ray(self.global_epochs - 1) + + if num_samples_per_ray != prev_samples: print(f"Epoch {epoch}: Changing num_samples_per_ray from {prev_samples} to {num_samples_per_ray}") - + + if self.sampler is not None: self.sampler.set_epoch(epoch) @@ -379,7 +389,7 @@ def recon( self.temp_logger.add_scalar("train/lr", current_lr, self.global_epochs) self.temp_logger.add_scalar("train/num_samples_per_ray", num_samples_per_ray, self.global_epochs) - fig, axes = plt.subplots(1, 3, figsize=(36, 12)) + fig, axes = plt.subplots(1, 5, figsize=(36, 12)) axes[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap='turbo', vmin=0) axes[0].set_title('Sum over Z-axis') @@ -391,9 +401,22 @@ def recon( thick_slice = pred_full[slice_start:slice_end].sum(dim=0).cpu().numpy() axes[2].matshow(thick_slice, cmap='turbo', vmin=0) axes[2].set_title(f'Thick slice sum (Z={slice_start}:{slice_end-1})') + + axes[3].matshow(pred_full.sum(dim = 1).cpu().numpy(), cmap='turbo', vmin=0) + axes[3].set_title('Sum over Y-axis') + + axes[4].matshow(pred_full.sum(dim = 2).cpu().numpy(), cmap='turbo', vmin=0) + axes[4].set_title('Sum over X-axis') self.temp_logger.add_figure("train/viz", fig, self.global_epochs, close=True) plt.close(fig) + + if epoch % checkpoint_freq == 0 or self.global_epochs == epochs - 1: + with torch.no_grad(): + if self.global_rank == 0: + save_path = f"new_dataset_logs/volume_epoch_{self.global_epochs:04d}.pt" + torch.save(pred_full.cpu(), save_path) + self.global_epochs += 1 if self.global_rank == 0: diff --git a/src/quantem/tomography/tomography_base.py b/src/quantem/tomography/tomography_base.py index 61fb7937..35c69b34 100644 --- a/src/quantem/tomography/tomography_base.py +++ b/src/quantem/tomography/tomography_base.py @@ -85,7 +85,7 @@ def from_data( shifts=shifts, ) - # dataset.to(device) + dataset.to(device) if volume_obj is None: max_shape = max(dataset.tilt_series.shape) diff --git a/src/quantem/tomography/tomography_nerf_old.py b/src/quantem/tomography/tomography_nerf_old.py deleted file mode 100644 index adecfd6f..00000000 --- a/src/quantem/tomography/tomography_nerf_old.py +++ /dev/null @@ -1,658 +0,0 @@ -from torch.utils.data import Dataset, DataLoader, DistributedSampler -import torch -import torch.distributed as dist -import numpy as np - -from quantem.tomography.tomography_dataset import TomographyDataset -from quantem.tomography.models import HSiren - -from torch.utils.tensorboard import SummaryWriter -import matplotlib.pyplot as plt - - -import os - -def get_accumulation_steps(epoch): - """Increase accumulation steps at specific epochs.""" - # schedule = config.get("accumulation_schedule", { - # 0: 1, - # 200: 4, - # 500: 28, - # }) - - schedule = { - 0: 1, - 200: 4, - 500: 28, - } - - accumulation_steps = 1 - for epoch_threshold, steps in sorted(schedule.items()): - if epoch >= epoch_threshold: - accumulation_steps = steps - - return accumulation_steps - -def get_num_samples_per_ray(epoch): - """Increase number of samples per ray at specific epochs.""" - # schedule = config.get("num_samples_schedule", { - # 0: 64, - # 200: 128, - # 500: 256, - # }) - - # TODO: Maybe delete - schedule = { - 0: 25, - 2: 50, - 4: 100, - 6: 200, - } - - num_samples = 64 - for epoch_threshold, samples in sorted(schedule.items()): - if epoch >= epoch_threshold: - num_samples = samples - - return num_samples - -class AuxiliaryParams(torch.nn.Module): - def __init__(self, num_tilts, device, zero_tilt_idx=None): - super().__init__() - - if zero_tilt_idx is None: - # If not provided, assume first projection is reference - zero_tilt_idx = 0 - - self.zero_tilt_idx = zero_tilt_idx - self.num_tilts = num_tilts - - # Shifts: only parameterize non-reference tilts - num_param_tilts = num_tilts - 1 - self.shifts_param = torch.nn.Parameter(torch.zeros(num_param_tilts, 2, device=device)) - - # Fixed zero shifts for reference - self.shifts_ref = torch.zeros(1, 2, device=device) - - # Z1 and Z3: parameterize all tilts EXCEPT the reference - self.z1_param = torch.nn.Parameter(torch.zeros(num_param_tilts, device=device)) - # self.z3_param = torch.nn.Parameter(torch.zeros(num_param_tilts, device=device)) - - # Fixed zeros for reference tilt - self.z1_ref = torch.zeros(1, device=device) - # self.z3_ref = torch.zeros(1, device=device) - - def forward(self, dummy_input=None): - # Reconstruct full arrays with zeros at reference position - before_shifts = self.shifts_param[:self.zero_tilt_idx] - after_shifts = self.shifts_param[self.zero_tilt_idx:] - shifts = torch.cat([before_shifts, self.shifts_ref, after_shifts], dim=0) - - before_z1 = self.z1_param[:self.zero_tilt_idx] - after_z1 = self.z1_param[self.zero_tilt_idx:] - z1 = torch.cat([before_z1, self.z1_ref, after_z1], dim=0) - - # before_z3 = self.z3_param[:self.zero_tilt_idx] - # after_z3 = self.z3_param[self.zero_tilt_idx:] - # z3 = torch.cat([before_z3, self.z3_ref, after_z3], dim=0) - - return shifts, z1, -z1 - - -class TomographyNerf(): - - def __init__( - self, - tomo_dataset: TomographyDataset, - - ): - - self.setup_distributed() - self.tomo_dataset = tomo_dataset - self.setup_logging() - - def setup_logging(self): - """Setup logging directory and TensorBoard writer.""" - - # base_logdir = self.config.get("logdir", "exp-logs") - - # hidden_features = self.config["hidden_features"] - # hidden_layers = self.config["hidden_layers"] - # lr = self.config["train_lr"] - # aux_lr = self.config["aux_lr"] - # bs = self.config["ray_batch_size"] - # fbs = self.config["fbs"] - # tv = self.config["tv_factor"] - - # lr_str = f"{lr:.1e}" - # tv_str = f"{tv:.1e}" - # aux_lr_str = f"{aux_lr:.1e}" - - - # descriptive_name = f"h{hidden_features}_l{hidden_layers}_lr{lr_str}_auxlr{aux_lr_str}_bs{bs}_tv{tv_str}_omega{fbs}" - # self.logdir = os.path.join(base_logdir, descriptive_name) - - if self.global_rank == 0: - os.makedirs("exp-logs", exist_ok=True) - self.writer = SummaryWriter() # Should put a logdir here - - # config_path = os.path.join(self.logdir, "config.yaml") - # with open(config_path, "w") as f: - # yaml.dump(self.config, f) - - print(f"Logging to: Need to fix") - else: - self.writer = None - def setup_distributed(self): - - """Setup distributed training if available, otherwise use single GPU/CPU.""" - - # Check if we're in a distributed environment - - - if "RANK" in os.environ: - # Distributed training - if not dist.is_initialized(): - dist.init_process_group(backend='nccl' if torch.cuda.is_available() else 'gloo', init_method='env://') - - self.world_size = dist.get_world_size() - self.global_rank = dist.get_rank() - print(f"Global rank: {self.global_rank}") - self.local_rank = int(os.environ["LOCAL_RANK"]) - - torch.cuda.set_device(self.local_rank) - self.device = torch.device("cuda", self.local_rank) - - else: - # Single GPU/CPU training - self.world_size = 1 - self.global_rank = 0 - self.local_rank = 0 - - if torch.cuda.is_available(): - self.device = torch.device("cuda:0") - torch.cuda.set_device(0) - print("Single GPU training") - else: - self.device = torch.device("cpu") - print("CPU training") - - # Optional performance optimizations (only for CUDA) - if self.device.type == "cuda": - torch.backends.cudnn.benchmark = True - torch.backends.cuda.matmul.allow_tf32 = True - torch.backends.cudnn.allow_tf32 = True - - def create_volume(self): - N = self.tomo_dataset.dims[1] - with torch.no_grad(): - coords_1d = torch.linspace(-1, 1, N) - x, y, z = torch.meshgrid(coords_1d, coords_1d, coords_1d, indexing='ij') - inputs = torch.stack([x, y, z], dim=-1).reshape(-1, 3) - - # Get the underlying model if using DDP - model = self.model.module if hasattr(self.model, 'module') else self.model - - samples_per_gpu = N**3 // self.world_size - start_idx = self.global_rank * samples_per_gpu - end_idx = start_idx + samples_per_gpu - - inputs_subset = inputs[start_idx:end_idx].to(self.device) - outputs = model(inputs_subset) - - if outputs.dim() > 1: - outputs = outputs.squeeze(-1) - - if self.world_size > 1: - # Ensure consistent tensor sizes across all ranks - gathered_outputs = [torch.empty_like(outputs) for _ in range(self.world_size)] - dist.all_gather(gathered_outputs, outputs.contiguous()) - - # Explicitly order by rank - pred_full = torch.cat(gathered_outputs, dim=0).reshape(N, N, N).float() - else: - pred_full = outputs.reshape(N, N, N).float() - - return pred_full - - def get_scaled_lr(self, base_lr, scaling_rule="sqrt"): - """Scale learning rate based on world size.""" - if self.world_size == 1: - return base_lr - - if scaling_rule == "linear": - # Linear scaling: lr = base_lr * world_size - return base_lr * self.world_size - elif scaling_rule == "sqrt": - # Square root scaling: lr = base_lr * sqrt(world_size) - return base_lr * np.sqrt(self.world_size) - else: - return base_lr - - - @torch.compile(mode="reduce-overhead") - def transform_batch_ray_coordinates(self, rays, z1, x, z3, shifts, N, sampling_rate): - - # Step 1: Apply shifts - shift_x_norm = (shifts[:, 0:1] * sampling_rate * 2) / (N - 1) - shift_y_norm = (shifts[:, 1:2] * sampling_rate * 2) / (N - 1) - - rays_x = rays[:, :, 0] - shift_x_norm - rays_y = rays[:, :, 1] - shift_y_norm - rays_z = rays[:, :, 2] - - # Rotation 1: Z(-z3) - theta = torch.deg2rad(-z3).view(-1, 1) - cos_t = torch.cos(theta) - sin_t = torch.sin(theta) - - rays_x_rot1 = cos_t * rays_x - sin_t * rays_y - rays_y_rot1 = sin_t * rays_x + cos_t * rays_y - rays_z_rot1 = rays_z - - # Rotation 2: X(x) - theta = torch.deg2rad(x).view(-1, 1) - cos_t = torch.cos(theta) - sin_t = torch.sin(theta) - - rays_x_rot2 = rays_x_rot1 - rays_y_rot2 = cos_t * rays_y_rot1 - sin_t * rays_z_rot1 - rays_z_rot2 = sin_t * rays_y_rot1 + cos_t * rays_z_rot1 - - # Rotation 3: Z(-z1) - theta = torch.deg2rad(-z1).view(-1, 1) - cos_t = torch.cos(theta) - sin_t = torch.sin(theta) - - rays_x_final = cos_t * rays_x_rot2 - sin_t * rays_y_rot2 - rays_y_final = sin_t * rays_x_rot2 + cos_t * rays_y_rot2 - rays_z_final = rays_z_rot2 - - # Stack the final result - transformed_rays = torch.stack([rays_x_final, rays_y_final, rays_z_final], dim=2) - - return transformed_rays - - def save_checkpoint(self, epoch, aux_params): - """Save model and auxiliary parameters checkpoint (rank 0 only).""" - if self.global_rank == 0: - model_to_save = self.model.module if hasattr(self.model, 'module') else self.model - aux_params_to_save = aux_params.module if hasattr(aux_params, 'module') else aux_params - - checkpoint = { - "model_state_dict": model_to_save.state_dict(), - "aux_params_state_dict": aux_params_to_save.state_dict(), - "epoch": epoch, - # "config": self.config, - } - checkpoint_path = os.path.join("exp-logs", f"checkpoint_epoch_{epoch:04d}.pt") - torch.save(checkpoint, checkpoint_path) - print(f"Checkpoint saved at epoch {epoch}: {checkpoint_path}") - - def create_batch_projection_rays(self, pixel_i, pixel_j, N, num_samples_per_ray): - """Create projection rays for entire batch simultaneously.""" - batch_size = len(pixel_i) - - # Convert all pixels to normalized coordinates - x_coords = (pixel_j / (N - 1)) * 2 - 1 - y_coords = (pixel_i / (N - 1)) * 2 - 1 - - # Create z coordinates - z_coords = torch.linspace(-1, 1, num_samples_per_ray, device=self.device) - - # Create rays for all pixels: [batch_size, num_samples_per_ray, 3] - rays = torch.zeros(batch_size, num_samples_per_ray, 3, device=self.device) - - # Fill coordinates efficiently - rays[:, :, 0] = x_coords.unsqueeze(1) # x constant per ray - rays[:, :, 1] = y_coords.unsqueeze(1) # y constant per ray - rays[:, :, 2] = z_coords.unsqueeze(0) # z varies along ray - - return rays - - def train( - self, - batch_size: int, - num_workers: int, - omega_0 : int, - hidden_omega_0 : int, - train_lr : float, - aux_lr : float, - warmup_epochs : int, - epochs : int, - use_amp : bool, - sampling_rate : float, - tv_weight : float, - viz_freq : int, - checkpoint_freq : int, - # hidden_layers: int, - # hidden_features: int, - # alpha: float, - ): - - ### DataLoader setup - batch_size = batch_size - num_workers = num_workers - - if self.world_size > 1: - self.train_sampler = DistributedSampler( - self.tomo_dataset, - num_replicas=self.world_size, - rank=self.global_rank, - shuffle=False - ) - shuffle = False - else: - self.train_sampler = None - shuffle = True # Need this!! - - self.dataloader = DataLoader( - self.tomo_dataset, - batch_size=batch_size, - sampler=self.train_sampler, - shuffle=shuffle, - num_workers=num_workers, - pin_memory=True if self.device.type == "cuda" else False, - drop_last=True, - persistent_workers=False if num_workers == 0 else True - ) - - # Print statistics - if self.global_rank == 0: - # total_pixels = len(self.tomo_dataset.tilt_angles) * self.config["N"] ** 2 - train_pixels = self.tomo_dataset.num_pixels - effective_batch_size = batch_size * self.world_size - - print(f"Dataloader setup complete:") - print(f" Total projections: {len(self.tomo_dataset.tilt_angles)}") - print(f" Grid size: {self.tomo_dataset.dims[1]}{self.tomo_dataset.dims[2]}") - print(f" Total pixels: {train_pixels:,}") - - print(f" Local batch size (train): {batch_size}") - print(f" Global batch size: {effective_batch_size}") - print(f" Train batches per GPU per epoch: {len(self.dataloader)}") - - - ### - - ### Model Setup - - self.model = HSiren( - in_features = 3, - out_features = 1, - hidden_features = 512, - hidden_layers = 4, - first_omega_0 = 30, - hidden_omega_0 = 30, # Remove hidden_omega_0. - ).to(self.device) - - if self.world_size > 1: - self.model = torch.nn.parallel.DistributedDataParallel( - self.model, - device_ids=[self.local_rank], - output_device=self.local_rank, - find_unused_parameters=False, - bucket_cap_mb=100, - gradient_as_bucket_view=True, # TODO: Check if this is needed. - ) - if self.global_rank == 0: - print("Model wrapped with DDP") - - if self.world_size > 1: - - if self.global_rank == 0: - print("Model built, distributed, and compiled successfully.") - else: - print("Model built and compiled successfully.") - - ### Initializing optimizers, and auxiliary parameters - - # Torch module on z1, z3, and shifts as module - - phis = self.tomo_dataset.tilt_angles - zero_tilt_idx = torch.argmin(torch.abs(phis)).item() - - if self.global_rank == 0: - print(f"Using projection {zero_tilt_idx} (angle={phis[zero_tilt_idx]:.2f}°) as reference") - - - self.aux_params = AuxiliaryParams( - num_tilts = len(phis), - device = self.device, - zero_tilt_idx = zero_tilt_idx - ) - - - if self.world_size > 1: - self.aux_params = torch.nn.parallel.DistributedDataParallel( - self.aux_params, - device_ids=[self.local_rank], - output_device=self.local_rank, - find_unused_parameters=False, - broadcast_buffers=True - ) - if self.global_rank == 0: - print("Auxiliary parameters wrapped with DDP") - - # Learning rates - scaled_train_lr = train_lr*np.sqrt(self.world_size) - scaled_aux_lr = aux_lr*np.sqrt(self.world_size) - - # Optimizers - optimizer = torch.optim.Adam(self.model.parameters(), lr=scaled_train_lr, fused = True) - aux_optimizer = torch.optim.Adam(self.aux_params.parameters(), lr=scaled_aux_lr) - - aux_norm = torch.tensor(0.0, device=self.device) - model_norm = torch.tensor(0.0, device=self.device) - - optimizer.zero_grad() - aux_optimizer.zero_grad() - - warmup_scheduler_model = torch.optim.lr_scheduler.LinearLR(optimizer, start_factor=0.01, total_iters=warmup_epochs) # TODO: Start factor play around with this. - cosine_scheduler_model = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs - warmup_epochs, eta_min=scaled_train_lr/100) - scheduler_model = torch.optim.lr_scheduler.SequentialLR(optimizer, schedulers=[warmup_scheduler_model, cosine_scheduler_model], milestones=[warmup_epochs]) - - scheduler_aux = torch.optim.lr_scheduler.CosineAnnealingLR(aux_optimizer, T_max=epochs, eta_min=scaled_aux_lr/100) - - device_type = self.device.type - autocast_dtype = torch.bfloat16 if use_amp else None - - for epoch in range(epochs): - current_accumulation_steps = get_accumulation_steps(epoch) - num_samples_per_ray = get_num_samples_per_ray(epoch) - - if epoch > 0: - prev_steps = get_accumulation_steps(epoch - 1) - prev_samples = get_num_samples_per_ray(epoch - 1) - - if current_accumulation_steps != prev_steps and self.global_rank == 0: - print(f"Epoch {epoch}: Changing accumulation steps from {prev_steps} to {current_accumulation_steps}") - if num_samples_per_ray != prev_samples and self.global_rank == 0: - print(f"Epoch {epoch}: Changing num_samples_per_ray from {prev_samples} to {num_samples_per_ray}") - - if self.train_sampler is not None: - self.train_sampler.set_epoch(epoch) - - self.model.train() - epoch_loss = 0.0 - epoch_mse_loss = 0.0 - epoch_tv_loss = 0.0 - epoch_z1_loss = 0.0 - - num_batches = 0 - - for batch_idx, batch in enumerate(self.dataloader): - - projection_indices = batch['projection_idx'] - - pixel_i = batch['pixel_i'].float().to(self.device, non_blocking=True) - pixel_j = batch['pixel_j'].float().to(self.device, non_blocking=True) - target_values = batch['target_value'].to(self.device, non_blocking=True) - phis = batch['phi'].to(self.device, non_blocking=True) - projection_indices = batch['projection_idx'].to(self.device, non_blocking=True) - - shifts, z1_params, z3_params = self.aux_params(None) # NONE just for DDP - batch_shifts = torch.index_select(shifts, 0, projection_indices) - batch_z1 = torch.index_select(z1_params, 0, projection_indices) - batch_z3 = torch.index_select(z3_params, 0, projection_indices) - - with torch.autocast(device_type=device_type, dtype=autocast_dtype, enabled=use_amp): - - with torch.no_grad(): # For every value of the tilts, shifts, and phi figure out where ray enters and exits and sample along that line. Ray has to be -1 to 1 - batch_ray_coords = self.create_batch_projection_rays( - pixel_i, pixel_j, self.tomo_dataset.dims[1], num_samples_per_ray - ) - - transformed_rays = self.transform_batch_ray_coordinates( - batch_ray_coords, - z1=batch_z1, - x=phis, - z3=batch_z3, - shifts=batch_shifts, - N=self.tomo_dataset.dims[1], - sampling_rate=1 # TODO: make input into train - ) - - all_coords = transformed_rays.view(-1, 3) - all_densities = self.model(all_coords) - - if all_densities.dim() > 1: - all_densities = all_densities.squeeze(-1) # [N, 1] → [N] - - valid_mask = ( # TODO: Needs to be fixed edge of boxes - (all_coords[:, 0] >= -1) & (all_coords[:, 0] <= 1) & - (all_coords[:, 1] >= -1) & (all_coords[:, 1] <= 1) & - (all_coords[:, 2] >= -1) & (all_coords[:, 2] <= 1) - ).float() - - all_densities = all_densities * valid_mask - if tv_weight > 0: - - num_tv_samples = min(10000, all_coords.shape[0]) - tv_indices = torch.randperm(all_coords.shape[0], device=all_coords.device)[:num_tv_samples] - - tv_coords = all_coords[tv_indices].detach().requires_grad_(True) - tv_densities_recomputed = self.model(tv_coords) - - if tv_densities_recomputed.dim() > 1: - tv_densities_recomputed = tv_densities_recomputed.squeeze(-1) - - grad_outputs = torch.autograd.grad( - outputs=tv_densities_recomputed, - inputs=tv_coords, - grad_outputs=torch.ones_like(tv_densities_recomputed), - create_graph=True - )[0] - - - grad_norm = torch.norm(grad_outputs, dim=1) - tv_loss = tv_weight * grad_norm.mean() - else: - tv_loss = torch.tensor(0.0, device=self.device) - - ray_densities = all_densities.view(len(target_values), num_samples_per_ray) - - step_size = 2.0 / (num_samples_per_ray - 1) - - predicted_values = ray_densities.sum(dim=1) * step_size - - mse_loss = torch.nn.functional.mse_loss(predicted_values, target_values) - - tv_loss_z1 = torch.tensor(0.0, device=self.device) - - loss = mse_loss + tv_loss_z1 + tv_loss - loss = loss / current_accumulation_steps - - loss.backward() - - epoch_loss += loss.detach() - epoch_mse_loss += mse_loss.detach() - epoch_tv_loss += tv_loss.detach() - epoch_z1_loss += tv_loss_z1.detach() - - num_batches += 1 - if epoch >= warmup_epochs: - - model_norm = torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm = 1) - optimizer.step() - optimizer.zero_grad() - - aux_norm = torch.nn.utils.clip_grad_norm_(self.aux_params.parameters(), max_norm = 1) - aux_optimizer.step() - aux_optimizer.zero_grad() - else: - model_norm = torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm = 1) - optimizer.step() - optimizer.zero_grad() - - aux_optimizer.zero_grad() - - scheduler_model.step() - if epoch >= warmup_epochs: - scheduler_aux.step() - - if self.global_rank == 0: - print(f"Epoch Complete {epoch}") - - - if epoch % viz_freq == 0 or epoch == epochs - 1 or epoch % checkpoint_freq == 0 : - with torch.no_grad(): - pred_full = self.create_volume().cpu() - avg_loss = epoch_loss.item() / num_batches - avg_mse_loss = epoch_mse_loss.item() / num_batches - avg_tv_loss = epoch_tv_loss.item() / num_batches - avg_z1_loss = epoch_z1_loss.item() / num_batches - - - metrics = torch.tensor([avg_loss, avg_mse_loss, avg_tv_loss, avg_z1_loss], device=self.device) - if self.world_size > 1: - torch.distributed.all_reduce(metrics, op=torch.distributed.ReduceOp.AVG) - avg_loss, avg_mse_loss, avg_tv_loss, avg_z1_loss = metrics.tolist() - - - if self.global_rank == 0 and (epoch % viz_freq == 0 or epoch == epochs - 1): - with torch.no_grad(): - current_lr = scheduler_model.get_last_lr()[0] - - # Log metrics - self.writer.add_scalar("train/mse_loss", avg_mse_loss, epoch) - - - self.writer.add_scalar("train/z1_loss", avg_z1_loss, epoch) - if tv_weight > 0: - self.writer.add_scalar("train/tv_loss", avg_tv_loss, epoch) - self.writer.add_scalar("train/total_loss", avg_loss, epoch) - self.writer.add_scalar("train/model_grad_norm", model_norm.item(), epoch) - self.writer.add_scalar("train/aux_grad_norm", aux_norm.item(), epoch) - self.writer.add_scalar("train/lr", current_lr, epoch) - self.writer.add_scalar("train/num_samples_per_ray", num_samples_per_ray, epoch) - - fig, axes = plt.subplots(1, 3, figsize=(36, 12)) - axes[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap='turbo', vmin=0) - axes[0].set_title('Sum over Z-axis') - - axes[1].matshow(pred_full[self.tomo_dataset.dims[1]//2].cpu().numpy(), cmap='turbo', vmin=0) - axes[1].set_title(f'Slice at Z={self.tomo_dataset.dims[1]//2}') - - slice_start = max(0, self.tomo_dataset.dims[1]//2 - 5) - slice_end = min(self.tomo_dataset.dims[1], self.tomo_dataset.dims[1]//2 + 6) - thick_slice = pred_full[slice_start:slice_end].sum(dim=0).cpu().numpy() - axes[2].matshow(thick_slice, cmap='turbo', vmin=0) - axes[2].set_title(f'Thick slice sum (Z={slice_start}:{slice_end-1})') - - self.writer.add_figure("train/viz", fig, epoch, close=True) - plt.close(fig) - - if epoch % checkpoint_freq == 0 or epoch == epochs - 1: - with torch.no_grad(): - if self.global_rank == 0: - save_path = os.path.join("exp-logs", f"volume_epoch_{epoch:04d}.pt") - torch.save(pred_full.cpu(), save_path) - - self.save_checkpoint(epoch, self.aux_params) - - if self.global_rank == 0: - print("Training complete.") - - - \ No newline at end of file From a57d885060dcc3be315803a5f389f555c216e683 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Thu, 16 Oct 2025 15:41:12 -0700 Subject: [PATCH 020/335] Enforcing positivity optional in tomography_dataset with clamp flag --- src/quantem/core/utils/imaging_utils.py | 35 ++++++-- src/quantem/tomography/tomography_base.py | 17 ++-- src/quantem/tomography/tomography_dataset.py | 92 ++++++++++---------- 3 files changed, 83 insertions(+), 61 deletions(-) diff --git a/src/quantem/core/utils/imaging_utils.py b/src/quantem/core/utils/imaging_utils.py index 4e3ca537..25d1610d 100644 --- a/src/quantem/core/utils/imaging_utils.py +++ b/src/quantem/core/utils/imaging_utils.py @@ -1,14 +1,16 @@ # Utilities for processing images from __future__ import annotations + from typing import Any, Optional, Tuple, TypeVar import numpy as np +from matplotlib import cm from numpy.typing import NDArray from scipy.ndimage import gaussian_filter from scipy.special import comb -from matplotlib import cm from quantem.core.datastructures.dataset2d import Dataset2d +from quantem.core.utils.utils import generate_batches from quantem.core.visualization import show_2d # single TypeVar: works for both numpy and Dataset2d @@ -372,9 +374,13 @@ def fourier_cropping( def _as_array(x: ImageType) -> NDArray[Any]: return x.array if isinstance(x, Dataset2d) else np.asarray(x) + def _bernstein_basis_1d(n: int, t: NDArray[Any]) -> NDArray[Any]: k = np.arange(n + 1, dtype=int) - return comb(n, k)[None, :] * (t[:, None] ** k[None, :]) * ((1.0 - t)[:, None] ** (n - k)[None, :]) + return ( + comb(n, k)[None, :] * (t[:, None] ** k[None, :]) * ((1.0 - t)[:, None] ** (n - k)[None, :]) + ) + def _build_basis_matrix(im_shape: Tuple[int, int], order: Tuple[int, int]) -> NDArray[Any]: H, W = im_shape @@ -413,7 +419,9 @@ def background_subtract( if im.ndim != 2: raise ValueError("`image` must be 2D") - mask_arr: BoolArray = np.ones_like(im, dtype=bool) if mask is None else np.asarray(mask, dtype=bool) + mask_arr: BoolArray = ( + np.ones_like(im, dtype=bool) if mask is None else np.asarray(mask, dtype=bool) + ) if mask_arr.shape != im.shape: raise ValueError("`mask` must match `image` shape") @@ -463,9 +471,24 @@ def background_subtract( disp = [im - np.mean(im_bg), bg_disp, im_sub_np] norm = [ - {"interval_type": "manual", "stretch_type": "linear", "vmin": vmin_sub, "vmax": vmax_sub}, - {"interval_type": "manual", "stretch_type": "linear", "vmin": vmin_sub, "vmax": vmax_sub}, - {"interval_type": "centered", "stretch_type": "linear", "vcenter": 0.0, "half_range": vrange}, + { + "interval_type": "manual", + "stretch_type": "linear", + "vmin": vmin_sub, + "vmax": vmax_sub, + }, + { + "interval_type": "manual", + "stretch_type": "linear", + "vmin": vmin_sub, + "vmax": vmax_sub, + }, + { + "interval_type": "centered", + "stretch_type": "linear", + "vcenter": 0.0, + "half_range": vrange, + }, ] show_2d( diff --git a/src/quantem/tomography/tomography_base.py b/src/quantem/tomography/tomography_base.py index 35c69b34..b05e51fe 100644 --- a/src/quantem/tomography/tomography_base.py +++ b/src/quantem/tomography/tomography_base.py @@ -1,10 +1,11 @@ +from typing import Tuple + import matplotlib.pyplot as plt import numpy as np import torch from numpy.typing import NDArray from torch._tensor import Tensor from tqdm.auto import tqdm -from typing import Tuple from quantem.core.datastructures.dataset3d import Dataset3d from quantem.core.io.serialize import AutoSerialize @@ -74,6 +75,7 @@ def from_data( shifts: NDArray | Tensor | None = None, volume_obj: NDArray | Dataset3d | ObjectModelType | None = None, device: str = "cpu", + clamp: bool = True, ): device = device.lower() @@ -83,6 +85,7 @@ def from_data( z1_angles=z1_angles, z3_angles=z3_angles, shifts=shifts, + clamp=clamp, ) dataset.to(device) @@ -436,8 +439,8 @@ def plot_projections( cmap=cmap, title="Y-X Projection FFT", # norm=norm, - vmin = fft_vmax[0], - vmax = fft_vmax[1], + vmin=fft_vmax[0], + vmax=fft_vmax[1], ) show_2d( @@ -445,8 +448,8 @@ def plot_projections( figax=(fig, ax[1]), cmap=cmap, title="Z-X Projection FFT", - vmin = fft_vmax[0], - vmax = fft_vmax[1], + vmin=fft_vmax[0], + vmax=fft_vmax[1], # norm=norm, ) show_2d( @@ -454,8 +457,8 @@ def plot_projections( figax=(fig, ax[2]), cmap=cmap, title="Z-Y Projection FFT", - vmin = fft_vmax[0], - vmax = fft_vmax[1], + vmin=fft_vmax[0], + vmax=fft_vmax[1], # norm=norm, ) diff --git a/src/quantem/tomography/tomography_dataset.py b/src/quantem/tomography/tomography_dataset.py index c66b9e61..fe5dab7c 100644 --- a/src/quantem/tomography/tomography_dataset.py +++ b/src/quantem/tomography/tomography_dataset.py @@ -1,6 +1,8 @@ +import numpy as np import torch from numpy.typing import NDArray from torch._tensor import Tensor +from torch.utils.data import Dataset from quantem.core.datastructures.dataset3d import Dataset3d from quantem.core.io.serialize import AutoSerialize @@ -9,9 +11,6 @@ validate_tensor, ) -from torch.utils.data import Dataset - -import numpy as np class AuxiliaryParams(torch.nn.Module): def __init__(self, num_tilts, device, zero_tilt_idx=None): @@ -41,12 +40,12 @@ def __init__(self, num_tilts, device, zero_tilt_idx=None): def forward(self, dummy_input=None): # Reconstruct full arrays with zeros at reference position - before_shifts = self.shifts_param[:self.zero_tilt_idx] - after_shifts = self.shifts_param[self.zero_tilt_idx:] + before_shifts = self.shifts_param[: self.zero_tilt_idx] + after_shifts = self.shifts_param[self.zero_tilt_idx :] shifts = torch.cat([before_shifts, self.shifts_ref, after_shifts], dim=0) - before_z1 = self.z1_param[:self.zero_tilt_idx] - after_z1 = self.z1_param[self.zero_tilt_idx:] + before_z1 = self.z1_param[: self.zero_tilt_idx] + after_z1 = self.z1_param[self.zero_tilt_idx :] z1 = torch.cat([before_z1, self.z1_ref, after_z1], dim=0) # before_z3 = self.z3_param[:self.zero_tilt_idx] @@ -54,8 +53,8 @@ def forward(self, dummy_input=None): # z3 = torch.cat([before_z3, self.z3_ref, after_z3], dim=0) return shifts, z1, -z1 - - + + class TomographyDataset(AutoSerialize, Dataset): _token = object() @@ -74,10 +73,11 @@ def __init__( z1_angles: Tensor, z3_angles: Tensor, shifts: Tensor, + clamp: bool, ): self._tilt_series = tilt_series # Enforce Positivity - + self._tilt_angles = tilt_angles self._z1_angles = z1_angles self._z3_angles = z3_angles @@ -87,21 +87,21 @@ def __init__( self._initial_z1_angles = z1_angles.clone() self._initial_z3_angles = z3_angles.clone() self._initial_shifts = shifts.clone() - + # Move everything to CPU self.to("cpu") - + # Number of indices (?) - - + # Enforce normalization of tilt series try: - tilt_percentile = torch.quantile(self._tilt_series, .95) - except: - tilt_percentile = np.quantile(self._tilt_series, .95) + tilt_percentile = torch.quantile(self._tilt_series, 0.95) + except (AttributeError, RuntimeError): + tilt_percentile = np.quantile(self._tilt_series, 0.95) + self._tilt_series = self._tilt_series / tilt_percentile - self._tilt_series = torch.clamp(self._tilt_series, min=0) - + if clamp: + self._tilt_series = torch.clamp(self._tilt_series, min=0) # --- Class Methods --- @classmethod @@ -112,6 +112,7 @@ def from_data( z1_angles: NDArray | Tensor | None = None, z3_angles: NDArray | Tensor | None = None, shifts: NDArray | Tensor | None = None, + clamp: bool = True, ): """ tilt_series: (N, H, W) @@ -151,6 +152,7 @@ def from_data( z1_angles=validated_z1_angles, z3_angles=validated_z3_angles, shifts=validated_shifts, + clamp=clamp, # name=name, # origin=origin, # sampling=sampling, @@ -202,50 +204,45 @@ def initial_z3_angles(self) -> Tensor: @property def initial_shifts(self) -> Tensor: return self._initial_shifts - + @property def num_projections(self) -> int: - return self._tilt_series.shape[0] - + @property def num_pixels(self) -> int: - return self._tilt_series.shape[0] * self._tilt_series.shape[1] * self._tilt_series.shape[2] - + @property def dims(self) -> tuple[int, int, int]: - return self._tilt_series.shape[0], self._tilt_series.shape[1], self._tilt_series.shape[2] - + def __len__(self) -> int: - return self.num_pixels - + def __getitem__(self, idx): - actual_idx = idx - + projection_idx = actual_idx // (self.dims[1] * self.dims[2]) remaining = actual_idx % (self.dims[1] * self.dims[2]) - + # TODO: What if non-square tilt images? if self.dims[1] != self.dims[2]: raise NotImplementedError("Non-square tilt images are not supported yet.") - - #TODO: row, column + + # TODO: row, column pixel_i = remaining // self.dims[1] pixel_j = remaining % self.dims[1] - + target_value = self._tilt_series[projection_idx, pixel_i, pixel_j] phi = self._tilt_angles[projection_idx] - + return { - 'projection_idx': projection_idx, - 'pixel_i': pixel_i, - 'pixel_j': pixel_j, - 'target_value': target_value, - 'phi': phi + "projection_idx": projection_idx, + "pixel_i": pixel_i, + "pixel_j": pixel_j, + "target_value": target_value, + "phi": phi, } # --- Setters --- @@ -320,23 +317,22 @@ def initial_shifts(self, shifts: NDArray | Tensor) -> None: self._initial_shifts = shifts # TODO: Temp auxiliary params - + def setup_auxiliary_params(self, zero_tilt_idx: int = None, device: str = "cpu") -> None: - if not hasattr(self, "_auxiliary_params"): self._auxiliary_params = AuxiliaryParams( - num_tilts = len(self.tilt_angles), - device = device, - zero_tilt_idx = zero_tilt_idx, + num_tilts=len(self.tilt_angles), + device=device, + zero_tilt_idx=zero_tilt_idx, ) - + else: print("Auxiliary params already set") - + @property def auxiliary_params(self) -> AuxiliaryParams: return self._auxiliary_params - + # --- RESET --- def reset(self) -> None: From 078f3519d1fe27a3e28aaa4c04abefbdf88479f4 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 14 Oct 2025 14:39:40 -0700 Subject: [PATCH 021/335] Added SIREN, HSIREN, and Finer with allowed complex inputs --- src/quantem/core/ml/activation_functions.py | 32 ++++ src/quantem/core/ml/blocks.py | 129 ++++++++++++-- src/quantem/core/ml/{cnn3d.py => cnn.py} | 188 +++++++++++++++++++- src/quantem/core/ml/cnn2d.py | 187 ------------------- src/quantem/core/ml/inr.py | 87 +++++++++ 5 files changed, 419 insertions(+), 204 deletions(-) rename src/quantem/core/ml/{cnn3d.py => cnn.py} (50%) delete mode 100644 src/quantem/core/ml/cnn2d.py create mode 100644 src/quantem/core/ml/inr.py diff --git a/src/quantem/core/ml/activation_functions.py b/src/quantem/core/ml/activation_functions.py index 1f21fdd1..a4bea716 100644 --- a/src/quantem/core/ml/activation_functions.py +++ b/src/quantem/core/ml/activation_functions.py @@ -83,6 +83,37 @@ def PhaseReLU( return f.type(torch.complex64) +class FinerActivation(nn.Module): + """ + Finer Activation function for complex and real inputs. + + - Similar to sine activation function. + """ + def __init__( + self, + omega: float = 1, + ): + super().__init__() + self.omega = omega + + def generate_alpha(self, x): + + with torch.no_grad(): + return torch.abs(x) + 1 + + def forward(self, x): + if x.is_complex(): # Complex Input Check + with torch.no_grad(): + alpha_real = torch.abs(x.real) + 1 + alpha_imag = torch.abs(x.imag) + 1 + x.real = x.real * alpha_real + x.imag = x.imag * alpha_imag + return torch.sin(self.omega * self.generate_alpha(x) * x) + else: + return torch.sin(self.omega * self.generate_alpha(x) * x) + + + def get_activation_function( activation_type: str | Callable, @@ -151,6 +182,7 @@ def forward(self, x): activation = nn.Sigmoid() elif activation_type in ["softplus"]: activation = nn.Softplus() + elif activation_type in ["finer"]: else: raise ValueError(f"Unknown activation type {activation_type}") diff --git a/src/quantem/core/ml/blocks.py b/src/quantem/core/ml/blocks.py index bea0f8d2..6dd1ffa8 100644 --- a/src/quantem/core/ml/blocks.py +++ b/src/quantem/core/ml/blocks.py @@ -2,9 +2,12 @@ import numpy as np +import math + + from quantem.core import config -from .activation_functions import Complex_ReLU +from .activation_functions import Complex_ReLU, FinerActivation if TYPE_CHECKING: import torch @@ -17,6 +20,9 @@ import torch.nn.functional as F + +# ---- Convolutional Layers ---- + def complex_pool(z, m, **kwargs): return m(z.real) + 1.0j * m(z.imag) @@ -111,18 +117,6 @@ def forward(self, x): return output -class ComplexLinear(nn.Module): - def __init__(self, in_features: int, out_features: int): - super().__init__() - linear = nn.Linear(in_features, out_features, dtype=torch.cfloat) - self.weight = nn.Parameter(torch.view_as_real(linear.weight)) - self.bias = nn.Parameter(torch.view_as_real(linear.bias)) - - def forward(self, x): - weight = torch.view_as_complex(self.weight) - bias = torch.view_as_complex(self.bias) - return F.linear(x, weight, bias) - class Upsample2dBlock(nn.Module): """ @@ -337,3 +331,112 @@ def __init__(self, num_features): def forward(self, x): return torch.complex(self.real_bn(x.real), self.imag_bn(x.imag)) + +# ---- Linear Layers ---- + +class ComplexLinear(nn.Module): + def __init__(self, in_features: int, out_features: int): + super().__init__() + linear = nn.Linear(in_features, out_features, dtype=torch.cfloat) + self.weight = nn.Parameter(torch.view_as_real(linear.weight)) + self.bias = nn.Parameter(torch.view_as_real(linear.bias)) + + def forward(self, x): + weight = torch.view_as_complex(self.weight) + bias = torch.view_as_complex(self.bias) + return F.linear(x, weight, bias) + +## ---- Siren Family of Layers ---- + +def init_weights(m: nn.Module, omega: float = 1., c: float = 1., is_first: bool = False): + if hasattr(m, 'weight'): + fan_in = m.weight.size(-1) + if is_first: + bound = 1 / fan_in # SIREN + else: + bound = math.sqrt(c / fan_in) / omega + nn.init.uniform_(m.weight, -bound, bound) + +def init_bias(m: nn.Module, k: float): + if hasattr(m, 'bias'): + nn.init.uniform_(m.bias, -k, k) + +class SineLayer(nn.Module): + """ + + Sine layer for H-Siren, and SIREN implementations. + + Note: H-Siren uses the hyperbolic sine function only for the first layer. + """ + def __init__( + self, + in_features: int, + out_features: int, + bias: bool = True, + is_first: bool = False, + omega_0: float = 30, + hsiren: bool = False, + alpha: float = 1.0, + ): + super().__init__() + self.omega_0 = omega_0 + self.is_first = is_first + self.hsiren = hsiren + self.in_features = in_features + self.alpha = alpha + self.linear = nn.Linear(in_features, out_features, bias=bias) + self.init_weights() + + def init_weights(self): + with torch.no_grad(): + if self.is_first: + # Scale the first layer initialization by alpha + self.linear.weight.uniform_(-self.alpha / self.in_features, + self.alpha / self.in_features) + else: + # Scale the hidden layer initialization by alpha + self.linear.weight.uniform_(-self.alpha * np.sqrt(6 / self.in_features) / self.omega_0, + self.alpha * np.sqrt(6 / self.in_features) / self.omega_0) + + def forward(self, input): + if self.is_first and self.hsiren: + out = torch.sin(self.omega_0 * torch.sinh(2*self.linear(input))) + else: + out = torch.sin(self.omega_0 * self.linear(input)) + return out + +class FinerLayer(nn.Module): + + def __init__( + self, + in_features: int, + out_features: int, + bias: bool = True, + omega: float = 30, + is_first: bool = False, + is_last: bool = False, + init_method: str = 'sine', + init_gain: float = 1, + fbs: bool = None, + hbs = None, + alphaType = None, + alphaReqGrad = False, + ): + + super().__init__() + self.omega = omega + self.is_last = is_last + self.alphaType = alphaType + self.alphaReqGrad = alphaReqGrad + self.linear = nn.Linear(in_features, out_features, bias=bias) + + # init weights + init_weights(self.linear, omega, init_gain, is_first) + # init bias + init_bias(self.linear, fbs, is_first) + + def forward(self, input): + wx_b = self.linear(input) + if not self.is_last: + return FinerActivation(wx_b, self.omega) + return wx_b # is_last==True \ No newline at end of file diff --git a/src/quantem/core/ml/cnn3d.py b/src/quantem/core/ml/cnn.py similarity index 50% rename from src/quantem/core/ml/cnn3d.py rename to src/quantem/core/ml/cnn.py index 8247b05b..b2f6d56b 100644 --- a/src/quantem/core/ml/cnn3d.py +++ b/src/quantem/core/ml/cnn.py @@ -1,10 +1,190 @@ -from typing import Callable +from typing import TYPE_CHECKING, Callable -import torch -import torch.nn as nn +from quantem.core import config from .activation_functions import get_activation_function -from .blocks import Conv3dBlock, Upsample3dBlock, complex_pool, passfunc +from .blocks import Conv2dBlock, Upsample2dBlock, Conv3dBlock, Upsample3dBlock, complex_pool, passfunc + +if TYPE_CHECKING: + import torch + import torch.nn as nn +else: + if config.get("has_torch"): + import torch + import torch.nn as nn + + +class CNN2d(nn.Module): + """ """ + + def __init__( + self, + in_channels: int, # input channels (C_in, H, W) + out_channels: int | None = None, # output channels (C_out, H, W) + start_filters: int = 16, + num_layers: int = 3, # num_layers + num_per_layer: int = 2, # number conv per layer + use_skip_connections: bool = True, + dtype: torch.dtype = torch.float32, + dropout: float = 0, + activation: str | Callable = "relu", + final_activation: str | Callable = nn.Identity(), + use_batchnorm: bool = True, + ): + super().__init__() + self.in_channels = int(in_channels) + self.out_channels = int(out_channels) if out_channels is not None else int(in_channels) + self.start_filters = start_filters + self.num_layers = num_layers + self._num_per_layer = num_per_layer + if use_skip_connections and num_per_layer < 2: + raise ValueError( + "If using skip connections, num_per_layer must be at least 2 to allow for " + + "channel concatenation." + ) + self.use_skip_connections = use_skip_connections + self.dtype = dtype + self.dropout = dropout + self._use_batchnorm = use_batchnorm + + if self.dtype.is_complex: + self.pool = complex_pool + else: + self.pool = passfunc + self._pooler = nn.MaxPool2d(kernel_size=2, stride=2) + + self.concat = torch.cat + self.flatten = nn.Flatten() + + self.activation = activation + self.final_activation = final_activation + + self._build() + + @property + def activation(self) -> Callable: + return self._activation + + @activation.setter + def activation(self, act: str | Callable): + if callable(act): + self._activation = act + else: + self._activation = get_activation_function(act, self.dtype) + + @property + def final_activation(self) -> Callable: + return self._final_activation + + @final_activation.setter + def final_activation(self, act: str | Callable): + if callable(act): + self._final_activation = act + else: + self._final_activation = get_activation_function(act, self.dtype) + + def _build(self): + self.down_conv_blocks = nn.ModuleList() + self.up_conv_blocks = nn.ModuleList() + self.upsample_blocks = nn.ModuleList() + + in_channels = self.in_channels + out_channels = self.start_filters + for a0 in range(self.num_layers): + if a0 != 0: + out_channels = in_channels * 2 + self.down_conv_blocks.append( + Conv2dBlock( + nb_layers=self._num_per_layer, + input_channels=in_channels, + output_channels=out_channels, + use_batchnorm=self._use_batchnorm, + dropout=self.dropout, + dtype=self.dtype, + activation=self.activation, + ) + ) + in_channels = out_channels + + out_channels = in_channels * 2 + self.bottleneck = Conv2dBlock( + nb_layers=self._num_per_layer, + input_channels=in_channels, + output_channels=out_channels, + use_batchnorm=self._use_batchnorm, + dropout=self.dropout, + dtype=self.dtype, + activation=self.activation, + ) + in_channels = out_channels + + for a0 in range(self.num_layers): + out_channels = self.start_filters if a0 == self.num_layers - 1 else in_channels // 2 + + in_channels2 = in_channels if self.use_skip_connections else out_channels + + self.upsample_blocks.append( + Upsample2dBlock( + in_channels, out_channels, use_batchnorm=self._use_batchnorm, dtype=self.dtype + ) + ) + + self.up_conv_blocks.append( + Conv2dBlock( + nb_layers=self._num_per_layer, + input_channels=in_channels2, + output_channels=out_channels, + use_batchnorm=self._use_batchnorm, + dropout=self.dropout, + dtype=self.dtype, + activation=self.activation, + ) + ) + + in_channels = out_channels + + self.final_conv = Conv2dBlock( + nb_layers=1, + input_channels=self.start_filters, + output_channels=self.out_channels, + use_batchnorm=False, + dropout=self.dropout, + dtype=self.dtype, + activation=self.final_activation, + ) + return + + def forward(self, x: torch.Tensor) -> torch.Tensor: + skips = [] + for down_block in self.down_conv_blocks: + x = down_block(x) + if self.use_skip_connections: + skips.append(x) + x = self.pool(x, self._pooler) + + x = self.bottleneck(x) + for upsample_block, up_conv_block in zip(self.upsample_blocks, self.up_conv_blocks): + x = upsample_block(x) + if self.use_skip_connections: + skip = skips.pop() + x = torch.cat((x, skip), dim=1) + x = up_conv_block(x) + + y = self.final_conv(x) + + return y + + def reset_weights(self): + """ + Reset all weights. + """ + + def _reset(m: nn.Module) -> None: + reset_parameters = getattr(m, "reset_parameters", None) + if callable(reset_parameters): + reset_parameters() + + self.apply(_reset) class CNN3d(nn.Module): diff --git a/src/quantem/core/ml/cnn2d.py b/src/quantem/core/ml/cnn2d.py deleted file mode 100644 index ce7fa53f..00000000 --- a/src/quantem/core/ml/cnn2d.py +++ /dev/null @@ -1,187 +0,0 @@ -from typing import TYPE_CHECKING, Callable - -from quantem.core import config - -from .activation_functions import get_activation_function -from .blocks import Conv2dBlock, Upsample2dBlock, complex_pool, passfunc - -if TYPE_CHECKING: - import torch - import torch.nn as nn -else: - if config.get("has_torch"): - import torch - import torch.nn as nn - - -class CNN2d(nn.Module): - """ """ - - def __init__( - self, - in_channels: int, # input channels (C_in, H, W) - out_channels: int | None = None, # output channels (C_out, H, W) - start_filters: int = 16, - num_layers: int = 3, # num_layers - num_per_layer: int = 2, # number conv per layer - use_skip_connections: bool = True, - dtype: torch.dtype = torch.float32, - dropout: float = 0, - activation: str | Callable = "relu", - final_activation: str | Callable = nn.Identity(), - use_batchnorm: bool = True, - ): - super().__init__() - self.in_channels = int(in_channels) - self.out_channels = int(out_channels) if out_channels is not None else int(in_channels) - self.start_filters = start_filters - self.num_layers = num_layers - self._num_per_layer = num_per_layer - if use_skip_connections and num_per_layer < 2: - raise ValueError( - "If using skip connections, num_per_layer must be at least 2 to allow for " - + "channel concatenation." - ) - self.use_skip_connections = use_skip_connections - self.dtype = dtype - self.dropout = dropout - self._use_batchnorm = use_batchnorm - - if self.dtype.is_complex: - self.pool = complex_pool - else: - self.pool = passfunc - self._pooler = nn.MaxPool2d(kernel_size=2, stride=2) - - self.concat = torch.cat - self.flatten = nn.Flatten() - - self.activation = activation - self.final_activation = final_activation - - self._build() - - @property - def activation(self) -> Callable: - return self._activation - - @activation.setter - def activation(self, act: str | Callable): - if callable(act): - self._activation = act - else: - self._activation = get_activation_function(act, self.dtype) - - @property - def final_activation(self) -> Callable: - return self._final_activation - - @final_activation.setter - def final_activation(self, act: str | Callable): - if callable(act): - self._final_activation = act - else: - self._final_activation = get_activation_function(act, self.dtype) - - def _build(self): - self.down_conv_blocks = nn.ModuleList() - self.up_conv_blocks = nn.ModuleList() - self.upsample_blocks = nn.ModuleList() - - in_channels = self.in_channels - out_channels = self.start_filters - for a0 in range(self.num_layers): - if a0 != 0: - out_channels = in_channels * 2 - self.down_conv_blocks.append( - Conv2dBlock( - nb_layers=self._num_per_layer, - input_channels=in_channels, - output_channels=out_channels, - use_batchnorm=self._use_batchnorm, - dropout=self.dropout, - dtype=self.dtype, - activation=self.activation, - ) - ) - in_channels = out_channels - - out_channels = in_channels * 2 - self.bottleneck = Conv2dBlock( - nb_layers=self._num_per_layer, - input_channels=in_channels, - output_channels=out_channels, - use_batchnorm=self._use_batchnorm, - dropout=self.dropout, - dtype=self.dtype, - activation=self.activation, - ) - in_channels = out_channels - - for a0 in range(self.num_layers): - out_channels = self.start_filters if a0 == self.num_layers - 1 else in_channels // 2 - - in_channels2 = in_channels if self.use_skip_connections else out_channels - - self.upsample_blocks.append( - Upsample2dBlock( - in_channels, out_channels, use_batchnorm=self._use_batchnorm, dtype=self.dtype - ) - ) - - self.up_conv_blocks.append( - Conv2dBlock( - nb_layers=self._num_per_layer, - input_channels=in_channels2, - output_channels=out_channels, - use_batchnorm=self._use_batchnorm, - dropout=self.dropout, - dtype=self.dtype, - activation=self.activation, - ) - ) - - in_channels = out_channels - - self.final_conv = Conv2dBlock( - nb_layers=1, - input_channels=self.start_filters, - output_channels=self.out_channels, - use_batchnorm=False, - dropout=self.dropout, - dtype=self.dtype, - activation=self.final_activation, - ) - return - - def forward(self, x: torch.Tensor) -> torch.Tensor: - skips = [] - for down_block in self.down_conv_blocks: - x = down_block(x) - if self.use_skip_connections: - skips.append(x) - x = self.pool(x, self._pooler) - - x = self.bottleneck(x) - for upsample_block, up_conv_block in zip(self.upsample_blocks, self.up_conv_blocks): - x = upsample_block(x) - if self.use_skip_connections: - skip = skips.pop() - x = torch.cat((x, skip), dim=1) - x = up_conv_block(x) - - y = self.final_conv(x) - - return y - - def reset_weights(self): - """ - Reset all weights. - """ - - def _reset(m: nn.Module) -> None: - reset_parameters = getattr(m, "reset_parameters", None) - if callable(reset_parameters): - reset_parameters() - - self.apply(_reset) diff --git a/src/quantem/core/ml/inr.py b/src/quantem/core/ml/inr.py new file mode 100644 index 00000000..2c0bcd19 --- /dev/null +++ b/src/quantem/core/ml/inr.py @@ -0,0 +1,87 @@ +from .blocks import SineLayer, FinerLayer +from torch import nn +import torch + +class Siren(nn.Module): + def __init__( + self, + in_features: int = 2, + out_features: int = 3, + hidden_layers=3, + hidden_features: int = 256, + first_omega_0: float = 30.0, + hidden_omega_0: float = 30.0, + alpha: float = 1.0, + ): + super().__init__() + self.net = [] + self.net.append(SineLayer(in_features, hidden_features, is_first=True, + omega_0=first_omega_0, alpha=alpha)) + + for i in range(hidden_layers): + self.net.append(SineLayer(hidden_features, hidden_features, is_first=False, + omega_0=hidden_omega_0, alpha=alpha)) + + final_linear = nn.Linear(hidden_features, out_features) + with torch.no_grad(): + # Final layer keeps original initialization (no alpha scaling) + final_linear.weight.uniform_(-torch.sqrt(6 / hidden_features) / hidden_omega_0, + torch.sqrt(6 / hidden_features) / hidden_omega_0) + self.net.append(final_linear) + + self.net = nn.Sequential(*self.net) + + def forward(self, coords): + output = self.net(coords) + return output + +class HSiren(nn.Module): + def __init__(self, in_features=2, out_features=3, hidden_layers=3, hidden_features=256, + first_omega_0=30, hidden_omega_0=30, alpha=1.0): + super().__init__() + self.net = [] + self.net.append(SineLayer(in_features, hidden_features, is_first=True, + omega_0=first_omega_0, hsiren=True, alpha=alpha)) + + for i in range(hidden_layers): + self.net.append(SineLayer(hidden_features, hidden_features, is_first=False, + omega_0=hidden_omega_0, alpha=alpha)) + + final_linear = nn.Linear(hidden_features, out_features) + with torch.no_grad(): + # Final layer keeps original initialization (no alpha scaling) + final_linear.weight.uniform_(-torch.sqrt(6 / hidden_features) / hidden_omega_0, + torch.sqrt(6 / hidden_features) / hidden_omega_0) + self.net.append(final_linear) + + self.net = nn.Sequential(*self.net) + + def forward(self, coords): + output = self.net(coords) + return output + +class Finer(nn.Module): + def __init__(self, in_features=2, out_features=3, hidden_layers=3, hidden_features=256, + first_omega=30, hidden_omega=30, + init_method='sine', init_gain=1, fbs=None, hbs=None, + alphaType=None, alphaReqGrad=False): + super().__init__() + self.net = [] + self.net.append(FinerLayer(in_features, hidden_features, is_first=True, + omega=first_omega, + init_method=init_method, init_gain=init_gain, fbs=fbs, + alphaType=alphaType, alphaReqGrad=alphaReqGrad)) + + for i in range(hidden_layers): + self.net.append(FinerLayer(hidden_features, hidden_features, + omega=hidden_omega, + init_method=init_method, init_gain=init_gain, hbs=hbs, + alphaType=alphaType, alphaReqGrad=alphaReqGrad)) + + self.net.append(FinerLayer(hidden_features, out_features, is_last=True, + omega=hidden_omega, + init_method=init_method, init_gain=init_gain, hbs=hbs)) # omega: For weight init + self.net = nn.Sequential(*self.net) + + def forward(self, coords): + return self.net(coords) \ No newline at end of file From d83deb1f87f425d69b4a7d55593c8a781a5fce9c Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 14 Oct 2025 14:53:25 -0700 Subject: [PATCH 022/335] Added FinerActivation to get_activation_function --- src/quantem/core/ml/activation_functions.py | 1 + src/quantem/core/ml/blocks.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quantem/core/ml/activation_functions.py b/src/quantem/core/ml/activation_functions.py index a4bea716..c9c27896 100644 --- a/src/quantem/core/ml/activation_functions.py +++ b/src/quantem/core/ml/activation_functions.py @@ -183,6 +183,7 @@ def forward(self, x): elif activation_type in ["softplus"]: activation = nn.Softplus() elif activation_type in ["finer"]: + activation = FinerActivation() else: raise ValueError(f"Unknown activation type {activation_type}") diff --git a/src/quantem/core/ml/blocks.py b/src/quantem/core/ml/blocks.py index 6dd1ffa8..f9fa81ad 100644 --- a/src/quantem/core/ml/blocks.py +++ b/src/quantem/core/ml/blocks.py @@ -4,7 +4,6 @@ import math - from quantem.core import config from .activation_functions import Complex_ReLU, FinerActivation From 4227e582e0afbbdfb1e95019c9d772a0e268ad76 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 14 Oct 2025 14:57:42 -0700 Subject: [PATCH 023/335] Fixed PtychoLite to call from cnn.py instead of cnn2d (now removed) --- src/quantem/diffractive_imaging/ptychography_lite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quantem/diffractive_imaging/ptychography_lite.py b/src/quantem/diffractive_imaging/ptychography_lite.py index 985e80ef..ebf1caf0 100644 --- a/src/quantem/diffractive_imaging/ptychography_lite.py +++ b/src/quantem/diffractive_imaging/ptychography_lite.py @@ -7,7 +7,7 @@ from quantem.core import config from quantem.core.datastructures import Dataset4dstem -from quantem.core.ml.cnn2d import CNN2d +from quantem.core.ml.cnn import CNN2d from quantem.diffractive_imaging.dataset_models import PtychographyDatasetRaster from quantem.diffractive_imaging.detector_models import DetectorPixelated from quantem.diffractive_imaging.logger_ptychography import LoggerPtychography From 2e70de35494aff9ea7d96bc6bfdda1d4309b76b7 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 14 Oct 2025 15:00:22 -0700 Subject: [PATCH 024/335] Siren + HSiren need np.sqrt instead of torch.sqrt for .uniform_ --- src/quantem/core/ml/inr.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/quantem/core/ml/inr.py b/src/quantem/core/ml/inr.py index 2c0bcd19..68976f8a 100644 --- a/src/quantem/core/ml/inr.py +++ b/src/quantem/core/ml/inr.py @@ -1,6 +1,7 @@ from .blocks import SineLayer, FinerLayer from torch import nn import torch +import numpy as np class Siren(nn.Module): def __init__( @@ -25,8 +26,8 @@ def __init__( final_linear = nn.Linear(hidden_features, out_features) with torch.no_grad(): # Final layer keeps original initialization (no alpha scaling) - final_linear.weight.uniform_(-torch.sqrt(6 / hidden_features) / hidden_omega_0, - torch.sqrt(6 / hidden_features) / hidden_omega_0) + final_linear.weight.uniform_(-np.sqrt(6 / hidden_features) / hidden_omega_0, + np.sqrt(6 / hidden_features) / hidden_omega_0) self.net.append(final_linear) self.net = nn.Sequential(*self.net) @@ -50,8 +51,8 @@ def __init__(self, in_features=2, out_features=3, hidden_layers=3, hidden_featur final_linear = nn.Linear(hidden_features, out_features) with torch.no_grad(): # Final layer keeps original initialization (no alpha scaling) - final_linear.weight.uniform_(-torch.sqrt(6 / hidden_features) / hidden_omega_0, - torch.sqrt(6 / hidden_features) / hidden_omega_0) + final_linear.weight.uniform_(-np.sqrt(6 / hidden_features) / hidden_omega_0, + np.sqrt(6 / hidden_features) / hidden_omega_0) self.net.append(final_linear) self.net = nn.Sequential(*self.net) From 9bbb54efde65e3f09ae77eb269870ac50b080b7b Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 14 Oct 2025 15:18:59 -0700 Subject: [PATCH 025/335] net_list fix on Siren models --- src/quantem/core/ml/inr.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/quantem/core/ml/inr.py b/src/quantem/core/ml/inr.py index 68976f8a..d1a4a9b2 100644 --- a/src/quantem/core/ml/inr.py +++ b/src/quantem/core/ml/inr.py @@ -15,12 +15,12 @@ def __init__( alpha: float = 1.0, ): super().__init__() - self.net = [] - self.net.append(SineLayer(in_features, hidden_features, is_first=True, + self.net_list = [] + self.net_list.append(SineLayer(in_features, hidden_features, is_first=True, omega_0=first_omega_0, alpha=alpha)) for i in range(hidden_layers): - self.net.append(SineLayer(hidden_features, hidden_features, is_first=False, + self.net_list.append(SineLayer(hidden_features, hidden_features, is_first=False, omega_0=hidden_omega_0, alpha=alpha)) final_linear = nn.Linear(hidden_features, out_features) @@ -28,9 +28,9 @@ def __init__( # Final layer keeps original initialization (no alpha scaling) final_linear.weight.uniform_(-np.sqrt(6 / hidden_features) / hidden_omega_0, np.sqrt(6 / hidden_features) / hidden_omega_0) - self.net.append(final_linear) + self.net_list.append(final_linear) - self.net = nn.Sequential(*self.net) + self.net = nn.Sequential(*self.net_list) def forward(self, coords): output = self.net(coords) @@ -40,12 +40,12 @@ class HSiren(nn.Module): def __init__(self, in_features=2, out_features=3, hidden_layers=3, hidden_features=256, first_omega_0=30, hidden_omega_0=30, alpha=1.0): super().__init__() - self.net = [] - self.net.append(SineLayer(in_features, hidden_features, is_first=True, + self.net_list = [] + self.net_list.append(SineLayer(in_features, hidden_features, is_first=True, omega_0=first_omega_0, hsiren=True, alpha=alpha)) for i in range(hidden_layers): - self.net.append(SineLayer(hidden_features, hidden_features, is_first=False, + self.net_list.append(SineLayer(hidden_features, hidden_features, is_first=False, omega_0=hidden_omega_0, alpha=alpha)) final_linear = nn.Linear(hidden_features, out_features) @@ -53,9 +53,9 @@ def __init__(self, in_features=2, out_features=3, hidden_layers=3, hidden_featur # Final layer keeps original initialization (no alpha scaling) final_linear.weight.uniform_(-np.sqrt(6 / hidden_features) / hidden_omega_0, np.sqrt(6 / hidden_features) / hidden_omega_0) - self.net.append(final_linear) + self.net_list.append(final_linear) - self.net = nn.Sequential(*self.net) + self.net = nn.Sequential(*self.net_list) def forward(self, coords): output = self.net(coords) @@ -67,22 +67,22 @@ def __init__(self, in_features=2, out_features=3, hidden_layers=3, hidden_featur init_method='sine', init_gain=1, fbs=None, hbs=None, alphaType=None, alphaReqGrad=False): super().__init__() - self.net = [] - self.net.append(FinerLayer(in_features, hidden_features, is_first=True, + self.net_list = [] + self.net_list.append(FinerLayer(in_features, hidden_features, is_first=True, omega=first_omega, init_method=init_method, init_gain=init_gain, fbs=fbs, alphaType=alphaType, alphaReqGrad=alphaReqGrad)) for i in range(hidden_layers): - self.net.append(FinerLayer(hidden_features, hidden_features, + self.net_list.append(FinerLayer(hidden_features, hidden_features, omega=hidden_omega, init_method=init_method, init_gain=init_gain, hbs=hbs, alphaType=alphaType, alphaReqGrad=alphaReqGrad)) - self.net.append(FinerLayer(hidden_features, out_features, is_last=True, + self.net_list.append(FinerLayer(hidden_features, out_features, is_last=True, omega=hidden_omega, init_method=init_method, init_gain=init_gain, hbs=hbs)) # omega: For weight init - self.net = nn.Sequential(*self.net) + self.net = nn.Sequential(*self.net_list) def forward(self, coords): return self.net(coords) \ No newline at end of file From 0aec97528049e0266e63d5af012eed855bac18a8 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 14 Oct 2025 15:43:50 -0700 Subject: [PATCH 026/335] Softplus missing from self.net_list --- src/quantem/core/ml/inr.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/quantem/core/ml/inr.py b/src/quantem/core/ml/inr.py index d1a4a9b2..8b5a4239 100644 --- a/src/quantem/core/ml/inr.py +++ b/src/quantem/core/ml/inr.py @@ -29,7 +29,7 @@ def __init__( final_linear.weight.uniform_(-np.sqrt(6 / hidden_features) / hidden_omega_0, np.sqrt(6 / hidden_features) / hidden_omega_0) self.net_list.append(final_linear) - + self.net_list.append(nn.Softplus()) self.net = nn.Sequential(*self.net_list) def forward(self, coords): @@ -54,7 +54,7 @@ def __init__(self, in_features=2, out_features=3, hidden_layers=3, hidden_featur final_linear.weight.uniform_(-np.sqrt(6 / hidden_features) / hidden_omega_0, np.sqrt(6 / hidden_features) / hidden_omega_0) self.net_list.append(final_linear) - + self.net_list.append(nn.Softplus()) self.net = nn.Sequential(*self.net_list) def forward(self, coords): From 35eae4444e416becca52f0901600858ba0eacedf Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Thu, 23 Oct 2025 12:51:08 -0700 Subject: [PATCH 027/335] Print pred on cross_Correlation align stack --- src/quantem/tomography/tomography.py | 21 ++++++++++++--------- src/quantem/tomography/utils.py | 4 +++- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index ffb16d15..457aa89a 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -23,11 +23,12 @@ def get_num_samples_per_ray(epoch): """Increase number of samples per ray at specific epochs.""" schedule = { - 0: 25, - 2: 50, - 4: 100, - 6: 200, - 8: 350, + 0: 500, + # 2: 500, + # 4: 500, + # 6: 500, + # 8: 500, + 10: 500, } num_samples = 64 @@ -194,9 +195,9 @@ def transform_batch_ray_coordinates(self, rays, z1, x, z3, shifts, N, sampling_r return transformed_rays # TODO: Temp logger - def setup_logger(self): + def setup_logger(self, log_path): if self.global_rank == 0: - self.temp_logger = SummaryWriter() + self.temp_logger = SummaryWriter(log_dir=log_path) else: self.temp_logger = None @@ -213,6 +214,8 @@ def recon( optimizer_params: dict = None, scheduler_params: dict = None, soft_constraints: dict = None, + vol_save_path: str = None, # TODO: TEMPORARY + log_path = None, ): if not self.ddp_instantiated: @@ -227,7 +230,7 @@ def recon( self.obj.soft_constraints = soft_constraints if not hasattr(self, "temp_logger"): - self.setup_logger() + self.setup_logger(log_path=log_path) zero_tilt_idx = torch.argmin(torch.abs(self.dataset.tilt_angles)).item() @@ -414,7 +417,7 @@ def recon( if epoch % checkpoint_freq == 0 or self.global_epochs == epochs - 1: with torch.no_grad(): if self.global_rank == 0: - save_path = f"new_dataset_logs/volume_epoch_{self.global_epochs:04d}.pt" + save_path = f"{vol_save_path}/volume_epoch_{self.global_epochs:04d}.pt" torch.save(pred_full.cpu(), save_path) self.global_epochs += 1 diff --git a/src/quantem/tomography/utils.py b/src/quantem/tomography/utils.py index d93f15e0..2725fe20 100644 --- a/src/quantem/tomography/utils.py +++ b/src/quantem/tomography/utils.py @@ -249,7 +249,7 @@ def estimate_background( return mu -def cross_correlation_align_stack(ref_img, stack): +def cross_correlation_align_stack(ref_img, stack, print_pred = False): """ Aligns a stack of images to a reference image using cross-correlation. @@ -264,6 +264,8 @@ def cross_correlation_align_stack(ref_img, stack): prev_img = ref_img for img in tqdm(stack): shift_pred = cross_correlation_shift(prev_img, img) + if print_pred: + print(f'Shift prediction: {shift_pred}') shifted_image = shift(img, shift=shift_pred, mode="constant", cval=0.0) pred_shifts.append(shift_pred) From 0a99af5f9967be5a054a1f6862684d1ac882534c Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 11 Nov 2025 22:22:36 -0800 Subject: [PATCH 028/335] Adding back in validation set --- src/quantem/core/ml/blocks.py | 4 +- src/quantem/core/ml/inr.py | 6 +- src/quantem/tomography/tomography.py | 108 +++++++++++++++++-- src/quantem/tomography/tomography_dataset.py | 65 +++++++++++ src/quantem/tomography/tomography_ddp.py | 100 +++++++++++++---- 5 files changed, 248 insertions(+), 35 deletions(-) diff --git a/src/quantem/core/ml/blocks.py b/src/quantem/core/ml/blocks.py index f9fa81ad..955b8ed7 100644 --- a/src/quantem/core/ml/blocks.py +++ b/src/quantem/core/ml/blocks.py @@ -376,6 +376,7 @@ def __init__( omega_0: float = 30, hsiren: bool = False, alpha: float = 1.0, + r: int = 2, ): super().__init__() self.omega_0 = omega_0 @@ -384,6 +385,7 @@ def __init__( self.in_features = in_features self.alpha = alpha self.linear = nn.Linear(in_features, out_features, bias=bias) + self.r = r self.init_weights() def init_weights(self): @@ -399,7 +401,7 @@ def init_weights(self): def forward(self, input): if self.is_first and self.hsiren: - out = torch.sin(self.omega_0 * torch.sinh(2*self.linear(input))) + out = torch.sin(self.omega_0 * torch.sinh(self.r*self.linear(input))) else: out = torch.sin(self.omega_0 * self.linear(input)) return out diff --git a/src/quantem/core/ml/inr.py b/src/quantem/core/ml/inr.py index 8b5a4239..8c831f0d 100644 --- a/src/quantem/core/ml/inr.py +++ b/src/quantem/core/ml/inr.py @@ -38,15 +38,15 @@ def forward(self, coords): class HSiren(nn.Module): def __init__(self, in_features=2, out_features=3, hidden_layers=3, hidden_features=256, - first_omega_0=30, hidden_omega_0=30, alpha=1.0): + first_omega_0=30, hidden_omega_0=30, alpha=1.0, r_first_layer = 3, r_hidden_layers = 2): super().__init__() self.net_list = [] self.net_list.append(SineLayer(in_features, hidden_features, is_first=True, - omega_0=first_omega_0, hsiren=True, alpha=alpha)) + omega_0=first_omega_0, hsiren=True, alpha=alpha, r=r_first_layer)) for i in range(hidden_layers): self.net_list.append(SineLayer(hidden_features, hidden_features, is_first=False, - omega_0=hidden_omega_0, alpha=alpha)) + omega_0=hidden_omega_0, alpha=alpha, r=r_hidden_layers)) final_linear = nn.Linear(hidden_features, out_features) with torch.no_grad(): diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 457aa89a..6ee5688e 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -23,13 +23,21 @@ def get_num_samples_per_ray(epoch): """Increase number of samples per ray at specific epochs.""" schedule = { - 0: 500, - # 2: 500, - # 4: 500, - # 6: 500, + 0: 20, + 2: 100, + 4: 200, + 6: 400, # 8: 500, 10: 500, } + # schedule = { + # 0: 20, + # 2: 50, + # 4: 100, + # 6: 150, + # 8: 200, + # 10: 200, + # } num_samples = 64 for epoch_threshold, samples in sorted(schedule.items()): @@ -216,11 +224,12 @@ def recon( soft_constraints: dict = None, vol_save_path: str = None, # TODO: TEMPORARY log_path = None, + val_fraction: float = 0.0, ): if not self.ddp_instantiated: self.setup_distributed() - self.setup_dataloader(self.dataset, batch_size, num_workers) + self.setup_dataloader(self.dataset, batch_size, num_workers, val_fraction) self.ddp_instantiated = True obj.model = self.build_model(obj._model) self.obj = obj @@ -370,19 +379,27 @@ def recon( avg_mse_loss = epoch_mse_loss.item() / num_batches avg_tv_loss = epoch_tv_loss.item() / num_batches avg_z1_loss = epoch_z1_loss.item() / num_batches - + shifts, z1, z3 = self.dataset.auxiliary_params.forward() + shifts = shifts.detach().cpu() + z1 = z1.detach().cpu() + z3 = z3.detach().cpu() metrics = torch.tensor([avg_loss, avg_mse_loss, avg_tv_loss, avg_z1_loss], device=self.device) if self.world_size > 1: dist.all_reduce(metrics, op=dist.ReduceOp.AVG) avg_loss, avg_mse_loss, avg_tv_loss, avg_z1_loss = metrics.tolist() + + if hasattr(self, "val_dataloader") and self.val_dataloader is not None: + val_loss = self.validate(aux_params, num_samples_per_ray, device_type, autocast_dtype, use_amp, N) + if self.global_rank == 0 and (epoch % viz_freq == 0 or epoch == epochs - 1): with torch.no_grad(): current_lr = self.schedulers["model"].get_last_lr()[0] # Log metrics self.temp_logger.add_scalar("train/mse_loss", avg_mse_loss, self.global_epochs) - + if hasattr(self, "val_dataloader") and self.val_dataloader is not None: + self.temp_logger.add_scalar("val/mse_loss", val_loss, self.global_epochs) self.temp_logger.add_scalar("train/z1_loss", avg_z1_loss, self.global_epochs) # if tv_weight > 0: self.temp_logger.add_scalar("train/tv_loss", avg_tv_loss, self.global_epochs) @@ -412,6 +429,14 @@ def recon( axes[4].set_title('Sum over X-axis') self.temp_logger.add_figure("train/viz", fig, self.global_epochs, close=True) + + fig, axes = plt.subplots(ncols = 3, figsize = (10, 5)) + axes[0].plot(shifts[:, 0].cpu().numpy(), label = 'Shifts X') + axes[0].plot(shifts[:, 1].cpu().numpy(), label = 'Shifts Y') + axes[0].legend() + axes[1].plot(z1.cpu().numpy(), label = 'Z1') + axes[2].plot(z3.cpu().numpy(), label = 'Z3') + self.temp_logger.add_figure("train/auxiliary_params", fig, self.global_epochs, close=True) plt.close(fig) if epoch % checkpoint_freq == 0 or self.global_epochs == epochs - 1: @@ -424,9 +449,74 @@ def recon( if self.global_rank == 0: print("Training complete.") - - # print("Successfully setup DDP and dataloader") + torch.save(self.dataset.auxiliary_params.forward(), f"{log_path}/auxiliary_params.pt") + # print("Successfully setup DDP and dataloader") + def validate(self, aux_params, num_samples_per_ray, device_type, autocast_dtype, use_amp, N): + """Validate the model on the validation set.""" + self.obj.model.eval() + aux_params.eval() + + val_loss = 0.0 + num_batches = 0 + + with torch.no_grad(): + for batch in self.val_dataloader: + pixel_i = batch['pixel_i'].float().to(self.device, non_blocking=True) + pixel_j = batch['pixel_j'].float().to(self.device, non_blocking=True) + target_values = batch['target_value'].to(self.device, non_blocking=True) + phis = batch['phi'].to(self.device, non_blocking=True) + projection_indices = batch['projection_idx'].to(self.device, non_blocking=True) + + shifts, z1_params, z3_params = aux_params(None) + batch_shifts = torch.index_select(shifts, 0, projection_indices) + batch_z1 = torch.index_select(z1_params, 0, projection_indices) + batch_z3 = torch.index_select(z3_params, 0, projection_indices) + + with torch.autocast(device_type=device_type, dtype=autocast_dtype, enabled=use_amp): + with torch.no_grad(): + batch_ray_coords = self.create_batch_projection_rays( + pixel_i, pixel_j, N, num_samples_per_ray + ) + + transformed_rays = self.transform_batch_ray_coordinates( + batch_ray_coords, + z1=batch_z1, + x=phis, + z3=batch_z3, + shifts=batch_shifts, + N=N, + sampling_rate=1.0, + ) + + all_coords = transformed_rays.view(-1, 3) + + all_densities = self.obj.forward(all_coords) + + ray_densities = all_densities.view(len(target_values), num_samples_per_ray) # Reshape rays and integarte + step_size = 2.0 / (num_samples_per_ray - 1) + + predicted_values = ray_densities.sum(dim=1) * step_size + + mse_loss = torch.nn.functional.mse_loss(predicted_values, target_values) + + loss = mse_loss + + val_loss += loss.detach() + num_batches += 1 + + avg_val_loss = val_loss / num_batches if num_batches > 0 else 0.0 + + if self.world_size > 1: + val_loss_tensor = avg_val_loss.detach().clone() + dist.all_reduce(val_loss_tensor, op=dist.ReduceOp.AVG) + avg_val_loss = val_loss_tensor.item() + + self.obj.model.train() + aux_params.train() + + return avg_val_loss + def ad_recon( self, optimizer_params: dict, diff --git a/src/quantem/tomography/tomography_dataset.py b/src/quantem/tomography/tomography_dataset.py index fe5dab7c..8992d57b 100644 --- a/src/quantem/tomography/tomography_dataset.py +++ b/src/quantem/tomography/tomography_dataset.py @@ -167,6 +167,7 @@ def to(self, device: str): self._z3_angles = self._z3_angles.to(device) self._shifts = self._shifts.to(device) + # --- Properties --- @property @@ -340,3 +341,67 @@ def reset(self) -> None: self._z1_angles = self._initial_z1_angles.clone() self._z3_angles = self._initial_z3_angles.clone() self._shifts = self._initial_shifts.clone() + +# TODO: Temporary for use when only doing validation +class TomographyRayDataset(Dataset): + """ + Dataset for ray-based tomography training. + Each sample contains: target pixel value, pixel coordinates, phi angle, and auxiliary info. + """ + + def __init__(self, tilt_series, phis, N, val_ratio=0.0, mode='train', seed=42): + """ + Args: + tilt_series: [M, N, N] tensor of projections + phis: [M] tensor of tilt angles + N: Grid size + """ + self.tilt_series = tilt_series.cpu() + + self.tilt_series = torch.clamp(self.tilt_series, min=0.0) + + # tilt_percentile = torch.quantile(self.tilt_series, .95) + tilt_percentile = np.quantile(self.tilt_series.numpy(), 0.95) + + self.tilt_series = self.tilt_series / tilt_percentile + + self.phis = phis.cpu() + self.N = N + self.M = tilt_series.shape[0] + self.total_pixels = self.M * self.N * self.N + + + # Create train/val split + torch.manual_seed(seed) + all_indices = torch.randperm(self.total_pixels) + + num_val = int(self.total_pixels * val_ratio) + if mode == 'val': + self.indices = all_indices[:num_val] + else: # train + self.indices = all_indices[num_val:] + + self.mode = mode + + def __len__(self): + return len(self.indices) + + def __getitem__(self, idx): + + actual_idx = self.indices[idx].item() + + projection_idx = actual_idx // (self.N * self.N) + remaining = actual_idx % (self.N * self.N) + pixel_i = remaining // self.N + pixel_j = remaining % self.N + + target_value = self.tilt_series[projection_idx, pixel_i, pixel_j] + phi = self.phis[projection_idx] + + return { + 'projection_idx': projection_idx, + 'pixel_i': pixel_i, + 'pixel_j': pixel_j, + 'target_value': target_value, + 'phi': phi + } \ No newline at end of file diff --git a/src/quantem/tomography/tomography_ddp.py b/src/quantem/tomography/tomography_ddp.py index 58e4158d..8ecedaad 100644 --- a/src/quantem/tomography/tomography_ddp.py +++ b/src/quantem/tomography/tomography_ddp.py @@ -4,7 +4,9 @@ import torch.nn as nn import os import numpy as np -from quantem.tomography.tomography_dataset import TomographyDataset +from quantem.tomography.tomography_dataset import TomographyDataset, TomographyRayDataset + +from torch.utils.data import random_split class TomographyDDP: """ @@ -90,40 +92,94 @@ def setup_dataloader( tomo_dataset: TomographyDataset, batch_size: int, num_workers: int = 0, + val_fraction: float = 0.0, ): - + pin_mem = self.device.type == "cuda" + persist = num_workers > 0 + + # Split dataset if validation fraction > 0 + if val_fraction > 0.0: + # TODO: Temporary for when only doing validation, current TomographyDataset doesn't work correctly + train_dataset = TomographyRayDataset( + tomo_dataset.tilt_series.detach().clone(), + tomo_dataset.tilt_angles.detach().clone(), + 500, # TODO: TEMPORARY + val_ratio=val_fraction, + mode='train', + seed=42, + ) + val_dataset = TomographyRayDataset( + tomo_dataset.tilt_series.detach().clone(), + tomo_dataset.tilt_angles.detach().clone(), + 500, # TODO: TEMPORARY + val_ratio=val_fraction, + mode='val', + seed=42, + ) + else: + train_dataset, val_dataset = tomo_dataset, None + + # Samplers for distributed training if self.world_size > 1: - sampler = DistributedSampler( - tomo_dataset, - num_replicas = self.world_size, - rank = self.global_rank, - shuffle = True, - ) + train_sampler = DistributedSampler( + train_dataset, + num_replicas=self.world_size, + rank=self.global_rank, + shuffle=True, + ) + if val_dataset: + val_sampler = DistributedSampler( + val_dataset, + num_replicas=self.world_size, + rank=self.global_rank, + shuffle=False, + ) shuffle = False - else: - sampler = None + train_sampler = None + val_sampler = None shuffle = True - + + # Main dataloader self.dataloader = DataLoader( - tomo_dataset, - batch_size = batch_size, - num_workers = num_workers, - sampler = sampler, - shuffle = shuffle, - pin_memory = True if self.device.type == "cuda" else False, - drop_last = True, - persistent_workers = False if num_workers == 0 else True, + train_dataset, + batch_size=batch_size, + num_workers=num_workers, + sampler=train_sampler, + shuffle=shuffle, + pin_memory=pin_mem, + drop_last=True, + persistent_workers=persist, ) - - self.sampler = sampler + + # Validation dataloader if applicable + if val_dataset: + self.val_dataloader = DataLoader( + val_dataset, + batch_size=batch_size * 4, + num_workers=num_workers, + sampler=val_sampler, + shuffle=False, + pin_memory=pin_mem, + drop_last=False, + persistent_workers=persist, + ) + self.val_sampler = val_sampler + + + self.sampler = train_sampler + if self.global_rank == 0: print(f"Dataloader setup complete:") print(f" Total projections: {len(tomo_dataset.tilt_angles)}") + if val_fraction > 0.0: + print(f" Total projections (val): {len(val_dataset)}") print(f" Grid size: {tomo_dataset.dims[1]}{tomo_dataset.dims[2]}") print(f" Total pixels: {tomo_dataset.num_pixels:,}") - + if val_fraction > 0.0: + print(f" Total pixels (val): {len(val_dataset):,}") + print(f" Total pixels (train): {len(train_dataset):,}") print(f" Local batch size (train): {batch_size}") print(f" Global batch size: {batch_size*self.world_size}") print(f" Train batches per GPU per epoch: {len(self.dataloader)}") From 397148944a560cb07e1e58a3047d554348952c79 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Fri, 21 Nov 2025 15:41:00 -0800 Subject: [PATCH 029/335] Added pretraininig functionality, in core ML added custom loss functions more torchlike (need to ask Arthur's opinion) --- src/quantem/core/ml/loss_functions.py | 50 ++++++ src/quantem/tomography/object_models.py | 15 ++ src/quantem/tomography/tomography.py | 180 ++++++++++++++++--- src/quantem/tomography/tomography_base.py | 4 + src/quantem/tomography/tomography_dataset.py | 83 +++++++-- src/quantem/tomography/tomography_ddp.py | 50 +++++- 6 files changed, 350 insertions(+), 32 deletions(-) diff --git a/src/quantem/core/ml/loss_functions.py b/src/quantem/core/ml/loss_functions.py index daf96919..720d087c 100644 --- a/src/quantem/core/ml/loss_functions.py +++ b/src/quantem/core/ml/loss_functions.py @@ -2,6 +2,8 @@ from quantem.core import config +import torch.nn as nn + if TYPE_CHECKING: import torch else: @@ -65,3 +67,51 @@ def combined_l2(pred: torch.Tensor, target: torch.Tensor, alpha: float = 0.7) -> comp_l2 = complex_l2(pred, target) amp_ph_l2 = amp_phase_l2(pred, target) return alpha * amp_ph_l2 + (1 - alpha) * comp_l2 + + +# TODO: Better loss function implementation? More torch-like. + +class L1Loss(nn.Module): + + def __init__( + self, + reduction: str = "mean", + ): + super(L1Loss, self).__init__() + self.reduction = reduction + + def forward(self, pred: torch.Tensor, target: torch.Tensor) -> torch.Tensor: + return torch.nn.functional.l1_loss(pred, target, reduction=self.reduction) + + +class MSELoss(nn.Module): + + def __init__( + self, + reduction: str = "mean", + ): + super(MSELoss, self).__init__() + self.reduction = reduction + + def forward(self, pred: torch.Tensor, target: torch.Tensor) -> torch.Tensor: + return torch.nn.functional.mse_loss(pred, target, reduction=self.reduction) + +class MSELogMSELoss(nn.Module): + + def __init__( + self, + eps: float = 1e-8, + reduction: str = 'mean', + ): + super(MSELogMSELoss, self).__init__() + self.eps = eps + self.reduction = reduction + + def forward(self, pred: torch.Tensor, target: torch.Tensor) -> torch.Tensor: + mse = (pred - target) ** 2 + log_mse = mse * torch.log(pred + self.eps) + if self.reduction == 'mean': + return log_mse.mean() + elif self.reduction == 'sum': + return log_mse.sum() + return log_mse \ No newline at end of file diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index bec78138..ff36eb54 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -1,5 +1,6 @@ from abc import abstractmethod from copy import deepcopy +from pathlib import Path from typing import Any, Callable, Union import numpy as np @@ -539,6 +540,13 @@ def _pretrain( self._pretrain_lrs.append(optimizer.param_groups[0]["lr"]) pbar.set_description(f"Epoch {a0 + 1}/{num_epochs}, Loss: {loss.item():.4f}, ") + + +# INR stuff + + + + class ObjectINN(ObjectConstraints): """ Object model for INN objects. @@ -713,7 +721,14 @@ def forward( all_densities = all_densities * valid_mask return all_densities + ObjectModelType = ObjectVoxelwise | ObjectINN # | ObjectDIP | ObjectImplicit (ObjectFFN?) + + +# Pretrain Volume Dataset + + + \ No newline at end of file diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 6ee5688e..a42191bd 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -7,9 +7,10 @@ from quantem.tomography.tomography_base import TomographyBase from quantem.tomography.tomography_conv import TomographyConv from quantem.tomography.tomography_ml import TomographyML -from quantem.tomography.tomography_ddp import TomographyDDP +from quantem.tomography.tomography_ddp import PretrainVolumeDataset, TomographyDDP from quantem.tomography.utils import differentiable_shift_2d, gaussian_kernel_1d, rot_ZXZ +from quantem.core.ml.loss_functions import L1Loss, MSELoss, MSELogMSELoss # Temporary imports for TomographyNERF from quantem.tomography.models import HSiren from quantem.tomography.object_models import ObjectINN @@ -22,13 +23,21 @@ # TODO: Maybe put this in INN? def get_num_samples_per_ray(epoch): """Increase number of samples per ray at specific epochs.""" + # schedule = { + # 0: 20, + # 2: 100, + # 4: 200, + # 6: 400, + # # 8: 500, + # 10: 500, + # } schedule = { 0: 20, 2: 100, - 4: 200, - 6: 400, + 4: 150, + 6: 250, # 8: 500, - 10: 500, + 10: 300, } # schedule = { # 0: 20, @@ -66,6 +75,7 @@ def __init__( # TODO: More elegant way of doing this. self.global_epochs = 0 self.ddp_instantiated = False + self.pretraining_instantiated = False # --- Reconstruction Method --- @@ -209,7 +219,119 @@ def setup_logger(self, log_path): else: self.temp_logger = None + + def pretrain( + self, + volume_dataset: PretrainVolumeDataset, + obj: ObjectINN, + batch_size: int, + soft_constraints: dict = None, + log_path: str = None, + optimizer_params: dict = None, + epochs: int = 100, + viz_freq: int = 1, + l1_loss: bool = False, + ): + if not self.pretraining_instantiated: + self.setup_distributed() + self.setup_pretraining_dataloader(volume_dataset, batch_size) + self.pretraining_instantiated = True + obj.model = self.build_model(obj._model) + self.obj = obj + + if soft_constraints is not None: + self.obj.soft_constraints = soft_constraints + + if not hasattr(self, "temp_logger"): + self.setup_logger(log_path=log_path) + + if optimizer_params is not None: + optimizer_params = self.scale_lr(optimizer_params) + self.optimizer_params = optimizer_params + self.set_optimizers() + device_type = self.device.type + + for epoch in range(epochs): + if self.pretraining_sampler is not None: + self.pretraining_sampler.set_epoch(epoch) + + self.obj.model.train() + + epoch_loss = 0.0 + epoch_consistency_loss = 0.0 + epoch_tv_loss = 0.0 + num_batches = 0 + + for batch_idx, batch in enumerate(self.pretraining_dataloader): + coords = batch['coords'].to(self.device, non_blocking=True) + target = batch['target'].to(self.device, non_blocking=True) + + for _, opt in self.optimizers.items(): + opt.zero_grad() + + with torch.autocast(device_type=device_type, dtype=torch.bfloat16, enabled=True): + + outputs = self.obj.forward(coords) + + if l1_loss: + consistency_loss = torch.nn.functional.l1_loss(outputs, target) + else: + consistency_loss = torch.nn.functional.mse_loss(outputs, target) + tv_loss = self.obj.apply_soft_constraints(coords) + + loss = consistency_loss + tv_loss + + loss.backward() + + epoch_loss += loss.detach() + epoch_consistency_loss += consistency_loss.detach() + epoch_tv_loss += tv_loss.detach() + + torch.nn.utils.clip_grad_norm_(self.obj.model.parameters(), max_norm = 1.0) + + for _, opt in self.optimizers.items(): + opt.step() + epoch_loss += loss.detach() + num_batches +=1 + if epoch % viz_freq == 0 or epoch == epochs - 1: + avg_loss = epoch_loss / num_batches + with torch.no_grad(): + self.obj.create_volume(world_size = self.world_size, global_rank = self.global_rank, ray_size = volume_dataset.N) + pred_full = self.obj.obj + loss_tensor = avg_loss.clone().detach() + avg_loss = loss_tensor.item() + avg_tv_loss = epoch_tv_loss / num_batches + avg_consistency_loss = epoch_consistency_loss / num_batches + + metrics = torch.tensor([avg_loss, avg_consistency_loss, avg_tv_loss], device=self.device) + if self.world_size > 1: + dist.all_reduce(metrics, op = dist.ReduceOp.AVG) + avg_loss, avg_consistency_loss, avg_tv_loss = metrics.tolist() + + if self.global_rank == 0: + self.temp_logger.add_scalar("Pretrain/total_loss", avg_loss, epoch) + self.temp_logger.add_scalar("Pretrain/consistency_loss", avg_consistency_loss, epoch) + self.temp_logger.add_scalar("Pretrain/tv_loss", avg_tv_loss, epoch) + + # current_lr = self.schedulers["model"].get_last_lr()[0] + # self.temp_logger.add_scalar("Pretrain/lr", current_lr, epoch) + + fig, ax = plt.subplots(ncols = 4, figsize = (36, 12)) + ax[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap='turbo', vmin=0) + ax[0].set_title('Sum over Z-axis') + ax[1].matshow(pred_full[volume_dataset.N//2].cpu().numpy(), cmap='turbo', vmin=0) + ax[1].set_title(f'Slice at Z={volume_dataset.N//2}') + ax[2].matshow(pred_full.sum(dim=1).cpu().numpy(), cmap='turbo', vmin=0) + ax[2].set_title('Sum over Y-axis') + ax[3].matshow(pred_full.sum(dim=2).cpu().numpy(), cmap='turbo', vmin=0) + ax[3].set_title('Sum over X-axis') + self.temp_logger.add_figure("Pretrain/viz", fig, epoch, close=True) + + plt.close(fig) + print(f"Epoch [{epoch}/{epochs}] Pretrain Total Loss: {avg_loss:.6f}, Consistency Loss: {avg_consistency_loss:.6f}, TV Loss: {avg_tv_loss:.6f}") + + def recon( self, obj: ObjectINN, @@ -225,14 +347,19 @@ def recon( vol_save_path: str = None, # TODO: TEMPORARY log_path = None, val_fraction: float = 0.0, + learn_shifts: bool = True, + # l1_loss: bool = False, + consistency_criterion: str = 'mse', ): if not self.ddp_instantiated: - self.setup_distributed() + if self.pretraining_instantiated == False: + self.setup_distributed() + obj.model = self.build_model(obj._model) + self.obj = obj + self.setup_dataloader(self.dataset, batch_size, num_workers, val_fraction) self.ddp_instantiated = True - obj.model = self.build_model(obj._model) - self.obj = obj if soft_constraints is not None: @@ -245,9 +372,10 @@ def recon( if self.global_rank == 0: print(f"Using projection {zero_tilt_idx} (angle={self.dataset.tilt_angles[zero_tilt_idx]:.2f}°) as reference") + print(f"Using consistency criterion: {consistency_criterion}") # Auxiliary params setup - self.dataset.setup_auxiliary_params(zero_tilt_idx, self.device) + self.dataset.setup_auxiliary_params(zero_tilt_idx, self.device, learn_shifts) aux_params = self.dataset.auxiliary_params # Scaling learning rates to account for distributed training @@ -294,12 +422,21 @@ def recon( self.sampler.set_epoch(epoch) epoch_loss = 0.0 - epoch_mse_loss = 0.0 + epoch_consistency_loss = 0.0 epoch_tv_loss = 0.0 epoch_z1_loss = 0.0 num_batches = 0 - + consistency_loss_fn = None + if consistency_criterion == 'mse': + consistency_loss_fn = MSELoss() + elif consistency_criterion == 'l1': + consistency_loss_fn = L1Loss() + elif consistency_criterion == 'mse_log': + consistency_loss_fn = MSELogMSELoss() + else: + raise ValueError(f"Invalid consistency criterion: {consistency_criterion}") + for batch_idx, batch in enumerate(self.dataloader): projection_indices = batch['projection_idx'] @@ -343,15 +480,15 @@ def recon( predicted_values = ray_densities.sum(dim=1) * step_size - mse_loss = torch.nn.functional.mse_loss(predicted_values, target_values) + consistency_loss = consistency_loss_fn(predicted_values, target_values) tv_loss_z1 = torch.tensor(0.0, device=self.device) - loss = mse_loss + tv_loss + tv_loss_z1 + loss = consistency_loss + tv_loss + tv_loss_z1 loss.backward() epoch_loss += loss.detach() - epoch_mse_loss += mse_loss.detach() + epoch_consistency_loss += consistency_loss.detach() epoch_tv_loss += tv_loss.detach() epoch_z1_loss += tv_loss_z1.detach() num_batches += 1 @@ -365,9 +502,12 @@ def recon( aux_norm = torch.nn.utils.clip_grad_norm_(self.dataset.auxiliary_params.parameters(), max_norm = 1) opt.step() opt.zero_grad() - + for key, sched in self.schedulers.items(): - sched.step() + if isinstance(sched, torch.optim.lr_scheduler.ReduceLROnPlateau): + sched.step(epoch_loss) + else: + sched.step() if epoch % viz_freq == 0 or epoch == epochs - 1 or epoch % checkpoint_freq == 0 : @@ -376,17 +516,17 @@ def recon( self.obj.create_volume(world_size = self.world_size, global_rank = self.global_rank, ray_size = num_samples_per_ray) pred_full = self.obj.obj avg_loss = epoch_loss.item() / num_batches - avg_mse_loss = epoch_mse_loss.item() / num_batches + avg_consistency_loss = epoch_consistency_loss.item() / num_batches avg_tv_loss = epoch_tv_loss.item() / num_batches avg_z1_loss = epoch_z1_loss.item() / num_batches shifts, z1, z3 = self.dataset.auxiliary_params.forward() shifts = shifts.detach().cpu() z1 = z1.detach().cpu() z3 = z3.detach().cpu() - metrics = torch.tensor([avg_loss, avg_mse_loss, avg_tv_loss, avg_z1_loss], device=self.device) + metrics = torch.tensor([avg_loss, avg_consistency_loss, avg_tv_loss, avg_z1_loss], device=self.device) if self.world_size > 1: dist.all_reduce(metrics, op=dist.ReduceOp.AVG) - avg_loss, avg_mse_loss, avg_tv_loss, avg_z1_loss = metrics.tolist() + avg_loss, avg_consistency_loss, avg_tv_loss, avg_z1_loss = metrics.tolist() if hasattr(self, "val_dataloader") and self.val_dataloader is not None: @@ -397,9 +537,9 @@ def recon( current_lr = self.schedulers["model"].get_last_lr()[0] # Log metrics - self.temp_logger.add_scalar("train/mse_loss", avg_mse_loss, self.global_epochs) + self.temp_logger.add_scalar(f"train/consistency_loss_{consistency_criterion}", avg_consistency_loss, self.global_epochs) if hasattr(self, "val_dataloader") and self.val_dataloader is not None: - self.temp_logger.add_scalar("val/mse_loss", val_loss, self.global_epochs) + self.temp_logger.add_scalar(f"val/consistency_loss_{consistency_criterion}", val_loss, self.global_epochs) self.temp_logger.add_scalar("train/z1_loss", avg_z1_loss, self.global_epochs) # if tv_weight > 0: self.temp_logger.add_scalar("train/tv_loss", avg_tv_loss, self.global_epochs) diff --git a/src/quantem/tomography/tomography_base.py b/src/quantem/tomography/tomography_base.py index b05e51fe..d3b94427 100644 --- a/src/quantem/tomography/tomography_base.py +++ b/src/quantem/tomography/tomography_base.py @@ -392,6 +392,7 @@ def plot_projections( norm: str = "log_auto", figax: tuple[plt.Figure, plt.Axes] | None = None, fft_vmax: Tuple[float, float] = (0, 40), + vmin: float = 0, **kwargs, ): """ @@ -416,18 +417,21 @@ def plot_projections( figax=(fig, ax[0]), cmap=cmap, title="Y-X Projection", + vmin=vmin, ) show_2d( volume_obj_np.sum(axis=1), figax=(fig, ax[1]), cmap=cmap, title="Z-X Projection", + vmin=vmin, ) show_2d( volume_obj_np.sum(axis=2), figax=(fig, ax[2]), cmap=cmap, title="Z-Y Projection", + vmin=vmin, ) if fft: diff --git a/src/quantem/tomography/tomography_dataset.py b/src/quantem/tomography/tomography_dataset.py index 8992d57b..31898db7 100644 --- a/src/quantem/tomography/tomography_dataset.py +++ b/src/quantem/tomography/tomography_dataset.py @@ -11,11 +11,15 @@ validate_tensor, ) +from pathlib import Path + class AuxiliaryParams(torch.nn.Module): - def __init__(self, num_tilts, device, zero_tilt_idx=None): + def __init__(self, num_tilts, device, zero_tilt_idx=None, learn_shifts: bool = True): super().__init__() + self.learn_shifts = learn_shifts + if zero_tilt_idx is None: # If not provided, assume first projection is reference zero_tilt_idx = 0 @@ -25,18 +29,18 @@ def __init__(self, num_tilts, device, zero_tilt_idx=None): # Shifts: only parameterize non-reference tilts num_param_tilts = num_tilts - 1 - self.shifts_param = torch.nn.Parameter(torch.zeros(num_param_tilts, 2, device=device)) + self.shifts_param = torch.nn.Parameter(torch.zeros(num_param_tilts, 2, device=device), requires_grad = learn_shifts) # Fixed zero shifts for reference self.shifts_ref = torch.zeros(1, 2, device=device) # Z1 and Z3: parameterize all tilts EXCEPT the reference self.z1_param = torch.nn.Parameter(torch.zeros(num_param_tilts, device=device)) - # self.z3_param = torch.nn.Parameter(torch.zeros(num_param_tilts, device=device)) + self.z3_param = torch.nn.Parameter(torch.zeros(num_param_tilts, device=device)) # Fixed zeros for reference tilt self.z1_ref = torch.zeros(1, device=device) - # self.z3_ref = torch.zeros(1, device=device) + self.z3_ref = torch.zeros(1, device=device) def forward(self, dummy_input=None): # Reconstruct full arrays with zeros at reference position @@ -48,11 +52,11 @@ def forward(self, dummy_input=None): after_z1 = self.z1_param[self.zero_tilt_idx :] z1 = torch.cat([before_z1, self.z1_ref, after_z1], dim=0) - # before_z3 = self.z3_param[:self.zero_tilt_idx] - # after_z3 = self.z3_param[self.zero_tilt_idx:] - # z3 = torch.cat([before_z3, self.z3_ref, after_z3], dim=0) + before_z3 = self.z3_param[:self.zero_tilt_idx] + after_z3 = self.z3_param[self.zero_tilt_idx:] + z3 = torch.cat([before_z3, self.z3_ref, after_z3], dim=0) - return shifts, z1, -z1 + return shifts, z1, z3 class TomographyDataset(AutoSerialize, Dataset): @@ -319,12 +323,13 @@ def initial_shifts(self, shifts: NDArray | Tensor) -> None: # TODO: Temp auxiliary params - def setup_auxiliary_params(self, zero_tilt_idx: int = None, device: str = "cpu") -> None: + def setup_auxiliary_params(self, zero_tilt_idx: int = None, device: str = "cpu", learn_shifts: bool = True) -> None: if not hasattr(self, "_auxiliary_params"): self._auxiliary_params = AuxiliaryParams( num_tilts=len(self.tilt_angles), device=device, zero_tilt_idx=zero_tilt_idx, + learn_shifts=learn_shifts, ) else: @@ -404,4 +409,62 @@ def __getitem__(self, idx): 'pixel_j': pixel_j, 'target_value': target_value, 'phi': phi - } \ No newline at end of file + } + + +# Pretrain Volume Dataset +class PretrainVolumeDataset(Dataset): + + def __init__( + self, + volume_path: Path | str, + N: int, + ): + self._N = N + if str(volume_path).endswith(".npy"): + data = torch.from_numpy(np.load(volume_path)).float() + elif str(volume_path).endswith(".pt"): + data = torch.load(volume_path).float() + else: + raise ValueError(f"Unsupported file format: {volume_path}") + + # Normalize data + total_elements = data.numel() + if total_elements > 1e6: + sample_size = min(int(1e6), total_elements) + flat_data = data.flatten() + indices = torch.randperm(total_elements)[:sample_size] + sampled_data = flat_data[indices] + data_quantile = torch.quantile(sampled_data, 0.95) + else: + data_quantile = torch.quantile(data, 0.95) + + data = data / data_quantile + data = torch.permute(data, (2, 1, 0)) + # data = torch.flip(data, dims=(2,)) + + self.volume = data.cpu() + self.N = N + self.total_samples = N**3 + + coords_1d = torch.linspace(-1, 1, N) + x, y, z = torch.meshgrid(coords_1d, coords_1d, coords_1d, indexing='ij') + self.coords = torch.stack([x, y, z], dim=-1).reshape(-1, 3).cpu() + self.targets = self.volume.reshape(-1).cpu() + + def __len__(self): + return self.total_samples + + def __getitem__(self, idx): + return { + 'coords': self.coords[idx], + 'target': self.targets[idx] + } + + @property + def N(self): + return self._N + + @N.setter + def N(self, N: int): + self._N = N \ No newline at end of file diff --git a/src/quantem/tomography/tomography_ddp.py b/src/quantem/tomography/tomography_ddp.py index 8ecedaad..f4f652fc 100644 --- a/src/quantem/tomography/tomography_ddp.py +++ b/src/quantem/tomography/tomography_ddp.py @@ -4,7 +4,7 @@ import torch.nn as nn import os import numpy as np -from quantem.tomography.tomography_dataset import TomographyDataset, TomographyRayDataset +from quantem.tomography.tomography_dataset import TomographyDataset, TomographyRayDataset, PretrainVolumeDataset from torch.utils.data import random_split @@ -86,7 +86,7 @@ def build_model( return model - + # Setup Tomo DataLoader def setup_dataloader( self, tomo_dataset: TomographyDataset, @@ -183,6 +183,52 @@ def setup_dataloader( print(f" Local batch size (train): {batch_size}") print(f" Global batch size: {batch_size*self.world_size}") print(f" Train batches per GPU per epoch: {len(self.dataloader)}") + + + # Setup pretraining dataloader + + def setup_pretraining_dataloader( + self, + volume_dataset: PretrainVolumeDataset, + batch_size: int, + ): + #TODO: temp + + num_workers = 0 + if self.world_size > 1: + sampler = DistributedSampler( + volume_dataset, + num_replicas = self.world_size, + rank = self.global_rank, + shuffle = True, + drop_last = True, + ) + shuffle = False + else: + sampler = None + shuffle = True + + self.pretraining_dataloader = DataLoader( + volume_dataset, + batch_size = batch_size, + sampler = sampler, + shuffle = shuffle, + pin_memory = self.device.type == "cuda", + drop_last = True, + persistent_workers = num_workers > 0, + ) + + self.pretraining_sampler = sampler + + if self.global_rank == 0: + print(f"Pretraining dataloader setup complete:") + print(f" Total samples: {len(volume_dataset)}") + print(f" Grid size: {volume_dataset.N**3}") + print(f" Local batch size: {batch_size}") + print(f" Global batch size: {batch_size*self.world_size}") + print(f" Pretraining batches per GPU per epoch: {len(self.pretraining_dataloader)}") + + def get_scaled_lr(self, base_lr, scaling_rule="sqrt"): if scaling_rule == "sqrt": From 69efcfdde10b6294dca1d992d46b3d871b86ad14 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Fri, 5 Dec 2025 21:17:59 -0800 Subject: [PATCH 030/335] Updates --- src/quantem/core/ml/loss_functions.py | 51 +++++++++- src/quantem/tomography/tomography.py | 117 ++++++++++++++--------- src/quantem/tomography/tomography_ddp.py | 59 +++++++++++- 3 files changed, 180 insertions(+), 47 deletions(-) diff --git a/src/quantem/core/ml/loss_functions.py b/src/quantem/core/ml/loss_functions.py index 720d087c..d136ca20 100644 --- a/src/quantem/core/ml/loss_functions.py +++ b/src/quantem/core/ml/loss_functions.py @@ -109,9 +109,56 @@ def __init__( def forward(self, pred: torch.Tensor, target: torch.Tensor) -> torch.Tensor: mse = (pred - target) ** 2 - log_mse = mse * torch.log(pred + self.eps) + log_mse = -mse * torch.log(mse + self.eps) if self.reduction == 'mean': return log_mse.mean() elif self.reduction == 'sum': return log_mse.sum() - return log_mse \ No newline at end of file + return log_mse + +class LLMSELoss(nn.Module): + """ + Logarithmic Linear Mean Squared Error (LLMSE) loss: + L = -log(1 - |y - y_hat| / max(|y - y_hat|)) + """ + + def __init__(self, eps: float = 1e-8, reduction: str = 'mean'): + super().__init__() + self.eps = eps + self.reduction = reduction + + def forward(self, pred: torch.Tensor, target: torch.Tensor) -> torch.Tensor: + # Absolute residual + abs_diff = torch.abs(pred - target) + + # Normalization by max error in batch (avoid div-by-zero) + max_diff = torch.max(abs_diff.detach()) + self.eps + norm_diff = abs_diff / max_diff + + # Apply -log(1 - normalized_error) + loss = -torch.log(1.0 - norm_diff + self.eps) + + # Reduce + if self.reduction == 'mean': + return loss.mean() + elif self.reduction == 'sum': + return loss.sum() + return loss + + +class CharbonnierLoss(nn.Module): + def __init__(self, epsilon=1e-12, reduction='mean'): + super(CharbonnierLoss, self).__init__() + self.epsilon = epsilon + self.reduction = reduction + + def forward(self, prediction, target): + diff = prediction - target + loss = torch.sqrt(diff * diff + self.epsilon**2) + + if self.reduction == 'mean': + return torch.mean(loss) + elif self.reduction == 'sum': + return torch.sum(loss) + else: # 'none' + return loss \ No newline at end of file diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index a42191bd..3e630a91 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -1,3 +1,6 @@ +from typing import Any + + import torch # from torch_radon.radon import ParallelBeam as Radon @@ -7,10 +10,11 @@ from quantem.tomography.tomography_base import TomographyBase from quantem.tomography.tomography_conv import TomographyConv from quantem.tomography.tomography_ml import TomographyML -from quantem.tomography.tomography_ddp import PretrainVolumeDataset, TomographyDDP +from quantem.tomography.tomography_ddp import PretrainVolumeDataset, TomographyDDP, AdaptiveSmoothL1LossDDP from quantem.tomography.utils import differentiable_shift_2d, gaussian_kernel_1d, rot_ZXZ -from quantem.core.ml.loss_functions import L1Loss, MSELoss, MSELogMSELoss +from quantem.core.ml.loss_functions import L1Loss, MSELoss, MSELogMSELoss, LLMSELoss, CharbonnierLoss +from torch.nn import SmoothL1Loss # Temporary imports for TomographyNERF from quantem.tomography.models import HSiren from quantem.tomography.object_models import ObjectINN @@ -21,35 +25,21 @@ # Temporary aux class for TomographyNERF # TODO: Maybe put this in INN? -def get_num_samples_per_ray(epoch): +def get_num_samples_per_ray(N: int, epoch: int): """Increase number of samples per ray at specific epochs.""" - # schedule = { - # 0: 20, - # 2: 100, - # 4: 200, - # 6: 400, - # # 8: 500, - # 10: 500, - # } - schedule = { - 0: 20, - 2: 100, - 4: 150, - 6: 250, - # 8: 500, - 10: 300, - } - # schedule = { - # 0: 20, - # 2: 50, - # 4: 100, - # 6: 150, - # 8: 200, - # 10: 200, - # } - - num_samples = 64 - for epoch_threshold, samples in sorted(schedule.items()): + # Exponential schedule + + epochs = np.linspace(0, 10, 5, dtype=int) + # schedule = np.linspace(20, N, 5, dtype=int) + # schedule = np.array([20, 100, 150, 200, 200]) + # schedule = np.array([200, 200, 200, 200, 200]) + schedule = np.array([20, 100, 250, 500, 500]) + # schedule = np.array([500, 500, 500, 500, 500]) + # schedule = np.array([300, 300, 300, 300, 300]) + # schedule = np.exp(schedule) + schedule_warmup = dict[int, int](zip(epochs, schedule)) + + for epoch_threshold, samples in schedule_warmup.items(): if epoch >= epoch_threshold: num_samples = samples @@ -230,7 +220,7 @@ def pretrain( optimizer_params: dict = None, epochs: int = 100, viz_freq: int = 1, - l1_loss: bool = False, + consistency_criterion: str = 'mse', ): if not self.pretraining_instantiated: self.setup_distributed() @@ -250,6 +240,23 @@ def pretrain( self.optimizer_params = optimizer_params self.set_optimizers() device_type = self.device.type + consistency_loss_fn = None + if consistency_criterion[0].lower() == 'mse': + consistency_loss_fn = MSELoss() + elif consistency_criterion[0].lower() == 'l1': + consistency_loss_fn = L1Loss() + elif consistency_criterion[0].lower() == 'mse_log': + consistency_loss_fn = MSELogMSELoss() + elif consistency_criterion[0].lower() == 'llmse': + consistency_loss_fn = LLMSELoss() + elif consistency_criterion[0].lower() == 'smooth_l1': + consistency_loss_fn = SmoothL1Loss(beta = consistency_criterion[1]) + elif consistency_criterion[0].lower() == 'adaptive_smooth_l1': + consistency_loss_fn = AdaptiveSmoothL1LossDDP(beta_init = consistency_criterion[1], ema_factor = 0.99, eps = 1e-8) + elif consistency_criterion[0].lower() == 'charbonnier': + consistency_loss_fn = CharbonnierLoss(epsilon = 1e-12, reduction = 'mean') + else: + raise ValueError(f"Invalid consistency criterion: {consistency_criterion}") for epoch in range(epochs): if self.pretraining_sampler is not None: @@ -272,11 +279,7 @@ def pretrain( with torch.autocast(device_type=device_type, dtype=torch.bfloat16, enabled=True): outputs = self.obj.forward(coords) - - if l1_loss: - consistency_loss = torch.nn.functional.l1_loss(outputs, target) - else: - consistency_loss = torch.nn.functional.mse_loss(outputs, target) + consistency_loss = consistency_loss_fn(outputs, target) tv_loss = self.obj.apply_soft_constraints(coords) loss = consistency_loss + tv_loss @@ -330,7 +333,11 @@ def pretrain( plt.close(fig) print(f"Epoch [{epoch}/{epochs}] Pretrain Total Loss: {avg_loss:.6f}, Consistency Loss: {avg_consistency_loss:.6f}, TV Loss: {avg_tv_loss:.6f}") + self.global_epochs += 1 + if self.global_rank == 0: + print(f"Pretraining Completed, saving model weights to {log_path}/model_weights.pth") + torch.save(self.obj.model.state_dict(), f"{log_path}/model_weights.pth") def recon( self, @@ -350,11 +357,24 @@ def recon( learn_shifts: bool = True, # l1_loss: bool = False, consistency_criterion: str = 'mse', + model_weights_path: str = None, ): if not self.ddp_instantiated: if self.pretraining_instantiated == False: self.setup_distributed() + if model_weights_path is not None: + print(f"Loading model weights from {model_weights_path}") + state_dict = torch.load(model_weights_path, map_location='cpu') + + # Handle DataParallel/DDP checkpoints + if any(k.startswith('module.') for k in state_dict.keys()): + new_state_dict = {k.replace('module.', ''): v for k, v in state_dict.items()} + else: + new_state_dict = state_dict + + obj.model.load_state_dict(new_state_dict) + print("Model weights loaded successfully") obj.model = self.build_model(obj._model) self.obj = obj @@ -402,7 +422,7 @@ def recon( for epoch in range(epochs): - num_samples_per_ray = get_num_samples_per_ray(self.global_epochs) + num_samples_per_ray = get_num_samples_per_ray(N = N, epoch = self.global_epochs) # num_samples_per_ray = get_num_samples_per_ray(epoch) # Log the change if it happens @@ -411,7 +431,7 @@ def recon( if self.global_rank == 0 and self.global_epochs > 0: - prev_samples = get_num_samples_per_ray(self.global_epochs - 1) + prev_samples = get_num_samples_per_ray(N = N, epoch = self.global_epochs - 1) if num_samples_per_ray != prev_samples: print(f"Epoch {epoch}: Changing num_samples_per_ray from {prev_samples} to {num_samples_per_ray}") @@ -428,12 +448,20 @@ def recon( num_batches = 0 consistency_loss_fn = None - if consistency_criterion == 'mse': + if consistency_criterion[0].lower() == 'mse': consistency_loss_fn = MSELoss() - elif consistency_criterion == 'l1': + elif consistency_criterion[0].lower() == 'l1': consistency_loss_fn = L1Loss() - elif consistency_criterion == 'mse_log': + elif consistency_criterion[0].lower() == 'mse_log': consistency_loss_fn = MSELogMSELoss() + elif consistency_criterion[0].lower() == 'llmse': + consistency_loss_fn = LLMSELoss() + elif consistency_criterion[0].lower() == 'smooth_l1': + consistency_loss_fn = SmoothL1Loss(beta = consistency_criterion[1]) + elif consistency_criterion[0].lower() == 'adaptive_smooth_l1': + consistency_loss_fn = AdaptiveSmoothL1LossDDP(beta_init = consistency_criterion[1], ema_factor = 0.99, eps = 1e-8) + elif consistency_criterion[0].lower() == 'charbonnier': + consistency_loss_fn = CharbonnierLoss(epsilon = 1e-12, reduction = 'mean') else: raise ValueError(f"Invalid consistency criterion: {consistency_criterion}") @@ -537,9 +565,9 @@ def recon( current_lr = self.schedulers["model"].get_last_lr()[0] # Log metrics - self.temp_logger.add_scalar(f"train/consistency_loss_{consistency_criterion}", avg_consistency_loss, self.global_epochs) + self.temp_logger.add_scalar(f"train/consistency_loss_{consistency_criterion[0] if isinstance(consistency_criterion, tuple) else consistency_criterion}", avg_consistency_loss, self.global_epochs) if hasattr(self, "val_dataloader") and self.val_dataloader is not None: - self.temp_logger.add_scalar(f"val/consistency_loss_{consistency_criterion}", val_loss, self.global_epochs) + self.temp_logger.add_scalar(f"val/consistency_loss_{consistency_criterion[0] if isinstance(consistency_criterion, tuple) else consistency_criterion}", val_loss, self.global_epochs) self.temp_logger.add_scalar("train/z1_loss", avg_z1_loss, self.global_epochs) # if tv_weight > 0: self.temp_logger.add_scalar("train/tv_loss", avg_tv_loss, self.global_epochs) @@ -548,7 +576,8 @@ def recon( self.temp_logger.add_scalar("train/aux_grad_norm", aux_norm.item(), self.global_epochs) self.temp_logger.add_scalar("train/lr", current_lr, self.global_epochs) self.temp_logger.add_scalar("train/num_samples_per_ray", num_samples_per_ray, self.global_epochs) - + if consistency_criterion[0].lower() == 'adaptive_smooth_l1': + self.temp_logger.add_scalar("train/beta_2", consistency_loss_fn.beta2.item(), self.global_epochs) fig, axes = plt.subplots(1, 5, figsize=(36, 12)) axes[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap='turbo', vmin=0) axes[0].set_title('Sum over Z-axis') diff --git a/src/quantem/tomography/tomography_ddp.py b/src/quantem/tomography/tomography_ddp.py index f4f652fc..86c4f59e 100644 --- a/src/quantem/tomography/tomography_ddp.py +++ b/src/quantem/tomography/tomography_ddp.py @@ -261,4 +261,61 @@ def scale_lr( return new_optimizer_params - \ No newline at end of file + + + # TODO: Temporary Adaptive L1 Smooth Loss + +class AdaptiveSmoothL1Loss(nn.Module): + def __init__(self, beta_init=None, ema_factor=0.99, eps=1e-8): + """ + Adaptive smooth L1 loss with EMA-based beta adaptation. + + Args: + beta_init (float): optional initial β value; if None, starts as 1.0 + ema_factor (float): smoothing factor for EMA (1 - 1/N_b in Eq. 38) + eps (float): small constant for numerical stability + """ + super().__init__() + self.register_buffer('beta2', torch.tensor(beta_init**2 if beta_init else 1.0)) + self.ema_factor = ema_factor + self.eps = eps + + def forward(self, pred, target): + diff = pred - target + abs_diff = diff.abs() + + # compute current batch MSE (Eq. 38) + mse_batch = torch.mean(diff ** 2) + + # update β² adaptively using Eq. (39) + with torch.no_grad(): + self.beta2 = self.ema_factor * self.beta2 + (1 - self.ema_factor) * torch.min(self.beta2, mse_batch) + + beta = torch.sqrt(self.beta2 + self.eps) + + # Smooth L1 (Eq. 36) + loss = torch.where( + abs_diff < beta, + 0.5 * (diff ** 2) / (beta + self.eps), + abs_diff - 0.5 * beta + ) + return loss.mean() + +class AdaptiveSmoothL1LossDDP(AdaptiveSmoothL1Loss): + def forward(self, pred, target): + diff = pred - target + abs_diff = diff.abs() + mse_batch = torch.mean(diff ** 2) + + # Synchronize β across all GPUs + if dist.is_initialized(): + mse_batch_all = mse_batch.clone() + dist.all_reduce(mse_batch_all, op=dist.ReduceOp.AVG) + mse_batch = mse_batch_all / dist.get_world_size() + + with torch.no_grad(): + self.beta2 = self.ema_factor * self.beta2 + (1 - self.ema_factor) * torch.min(self.beta2, mse_batch) + + beta = torch.sqrt(self.beta2 + self.eps) + loss = torch.where(abs_diff < beta, 0.5 * (diff ** 2) / (beta + self.eps), abs_diff - 0.5 * beta) + return loss.mean() From 9937bc55cf968a6b141da2aebcd147b9ef756e41 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 15 Dec 2025 15:25:47 -0800 Subject: [PATCH 031/335] Test --- src/quantem/tomography/tomography.py | 406 +++++++++++++---------- src/quantem/tomography/tomography_ddp.py | 134 ++++---- 2 files changed, 297 insertions(+), 243 deletions(-) diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 3e630a91..5064cd3f 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -1,27 +1,33 @@ -from typing import Any - - +import matplotlib.pyplot as plt +import numpy as np import torch +import torch.distributed as dist +from torch.nn import SmoothL1Loss +from torch.utils.tensorboard import SummaryWriter # from torch_radon.radon import ParallelBeam as Radon from tqdm.auto import tqdm -from quantem.tomography.object_models import ObjectVoxelwise +from quantem.core.ml.loss_functions import ( + CharbonnierLoss, + L1Loss, + LLMSELoss, + MSELogMSELoss, + MSELoss, +) + +# Temporary imports for TomographyNERF +from quantem.tomography.object_models import ObjectINN, ObjectVoxelwise from quantem.tomography.tomography_base import TomographyBase from quantem.tomography.tomography_conv import TomographyConv +from quantem.tomography.tomography_ddp import ( + AdaptiveSmoothL1LossDDP, + PretrainVolumeDataset, + TomographyDDP, +) from quantem.tomography.tomography_ml import TomographyML -from quantem.tomography.tomography_ddp import PretrainVolumeDataset, TomographyDDP, AdaptiveSmoothL1LossDDP from quantem.tomography.utils import differentiable_shift_2d, gaussian_kernel_1d, rot_ZXZ -from quantem.core.ml.loss_functions import L1Loss, MSELoss, MSELogMSELoss, LLMSELoss, CharbonnierLoss -from torch.nn import SmoothL1Loss -# Temporary imports for TomographyNERF -from quantem.tomography.models import HSiren -from quantem.tomography.object_models import ObjectINN -from torch.utils.tensorboard import SummaryWriter -import numpy as np -import matplotlib.pyplot as plt -import torch.distributed as dist # Temporary aux class for TomographyNERF # TODO: Maybe put this in INN? @@ -31,9 +37,9 @@ def get_num_samples_per_ray(N: int, epoch: int): epochs = np.linspace(0, 10, 5, dtype=int) # schedule = np.linspace(20, N, 5, dtype=int) - # schedule = np.array([20, 100, 150, 200, 200]) + schedule = np.array([20, 100, 150, 200, 200]) # schedule = np.array([200, 200, 200, 200, 200]) - schedule = np.array([20, 100, 250, 500, 500]) + # schedule = np.array([20, 100, 250, 500, 500]) # schedule = np.array([500, 500, 500, 500, 500]) # schedule = np.array([300, 300, 300, 300, 300]) # schedule = np.exp(schedule) @@ -46,7 +52,6 @@ def get_num_samples_per_ray(N: int, epoch: int): return num_samples - class Tomography(TomographyConv, TomographyML, TomographyBase, TomographyDDP): """ Top level class for either using conventional or ML-based reconstruction methods @@ -61,12 +66,12 @@ def __init__( _token, ): super().__init__(dataset, volume_obj, device, _token) - + # TODO: More elegant way of doing this. self.global_epochs = 0 self.ddp_instantiated = False self.pretraining_instantiated = False - + # --- Reconstruction Method --- def sirt_recon( @@ -110,8 +115,13 @@ def sirt_recon( else: gaussian_kernel = None - print("Devices", sirt_tilt_series.device, proj_forward.device, self.dataset.tilt_angles.device) - + print( + "Devices", + sirt_tilt_series.device, + proj_forward.device, + self.dataset.tilt_angles.device, + ) + for iter in pbar: proj_forward, loss = self._sirt_run_epoch( tilt_series=sirt_tilt_series, @@ -134,10 +144,10 @@ def sirt_recon( if plot_loss: self.plot_loss() - + # TODO: ML Recon which has NeRF and AD depending on the object type. - # TODO: Temporary - + # TODO: Temporary + def create_batch_projection_rays(self, pixel_i, pixel_j, N, num_samples_per_ray): """Create projection rays for entire batch simultaneously.""" batch_size = len(pixel_i) @@ -158,10 +168,9 @@ def create_batch_projection_rays(self, pixel_i, pixel_j, N, num_samples_per_ray) rays[:, :, 2] = z_coords.unsqueeze(0) # z varies along ray return rays - - @torch.compile(mode="reduce-overhead") - def transform_batch_ray_coordinates(self, rays, z1, x, z3, shifts, N, sampling_rate): + @torch.compile(mode="reduce-overhead") + def transform_batch_ray_coordinates(self, rays, z1, x, z3, shifts, N, sampling_rate): # Step 1: Apply shifts shift_x_norm = (shifts[:, 0:1] * sampling_rate * 2) / (N - 1) shift_y_norm = (shifts[:, 1:2] * sampling_rate * 2) / (N - 1) @@ -208,7 +217,6 @@ def setup_logger(self, log_path): self.temp_logger = SummaryWriter(log_dir=log_path) else: self.temp_logger = None - def pretrain( self, @@ -220,7 +228,7 @@ def pretrain( optimizer_params: dict = None, epochs: int = 100, viz_freq: int = 1, - consistency_criterion: str = 'mse', + consistency_criterion: str = "mse", ): if not self.pretraining_instantiated: self.setup_distributed() @@ -231,30 +239,32 @@ def pretrain( if soft_constraints is not None: self.obj.soft_constraints = soft_constraints - + if not hasattr(self, "temp_logger"): self.setup_logger(log_path=log_path) - + if optimizer_params is not None: optimizer_params = self.scale_lr(optimizer_params) self.optimizer_params = optimizer_params self.set_optimizers() device_type = self.device.type consistency_loss_fn = None - if consistency_criterion[0].lower() == 'mse': + if consistency_criterion[0].lower() == "mse": consistency_loss_fn = MSELoss() - elif consistency_criterion[0].lower() == 'l1': + elif consistency_criterion[0].lower() == "l1": consistency_loss_fn = L1Loss() - elif consistency_criterion[0].lower() == 'mse_log': + elif consistency_criterion[0].lower() == "mse_log": consistency_loss_fn = MSELogMSELoss() - elif consistency_criterion[0].lower() == 'llmse': + elif consistency_criterion[0].lower() == "llmse": consistency_loss_fn = LLMSELoss() - elif consistency_criterion[0].lower() == 'smooth_l1': - consistency_loss_fn = SmoothL1Loss(beta = consistency_criterion[1]) - elif consistency_criterion[0].lower() == 'adaptive_smooth_l1': - consistency_loss_fn = AdaptiveSmoothL1LossDDP(beta_init = consistency_criterion[1], ema_factor = 0.99, eps = 1e-8) - elif consistency_criterion[0].lower() == 'charbonnier': - consistency_loss_fn = CharbonnierLoss(epsilon = 1e-12, reduction = 'mean') + elif consistency_criterion[0].lower() == "smooth_l1": + consistency_loss_fn = SmoothL1Loss(beta=consistency_criterion[1]) + elif consistency_criterion[0].lower() == "adaptive_smooth_l1": + consistency_loss_fn = AdaptiveSmoothL1LossDDP( + beta_init=consistency_criterion[1], ema_factor=0.99, eps=1e-8 + ) + elif consistency_criterion[0].lower() == "charbonnier": + consistency_loss_fn = CharbonnierLoss(epsilon=1e-12, reduction="mean") else: raise ValueError(f"Invalid consistency criterion: {consistency_criterion}") @@ -270,18 +280,17 @@ def pretrain( num_batches = 0 for batch_idx, batch in enumerate(self.pretraining_dataloader): - coords = batch['coords'].to(self.device, non_blocking=True) - target = batch['target'].to(self.device, non_blocking=True) + coords = batch["coords"].to(self.device, non_blocking=True) + target = batch["target"].to(self.device, non_blocking=True) for _, opt in self.optimizers.items(): opt.zero_grad() with torch.autocast(device_type=device_type, dtype=torch.bfloat16, enabled=True): - outputs = self.obj.forward(coords) consistency_loss = consistency_loss_fn(outputs, target) tv_loss = self.obj.apply_soft_constraints(coords) - + loss = consistency_loss + tv_loss loss.backward() @@ -290,52 +299,63 @@ def pretrain( epoch_consistency_loss += consistency_loss.detach() epoch_tv_loss += tv_loss.detach() - torch.nn.utils.clip_grad_norm_(self.obj.model.parameters(), max_norm = 1.0) + torch.nn.utils.clip_grad_norm_(self.obj.model.parameters(), max_norm=1.0) for _, opt in self.optimizers.items(): opt.step() epoch_loss += loss.detach() - num_batches +=1 - + num_batches += 1 + if epoch % viz_freq == 0 or epoch == epochs - 1: avg_loss = epoch_loss / num_batches with torch.no_grad(): - self.obj.create_volume(world_size = self.world_size, global_rank = self.global_rank, ray_size = volume_dataset.N) + self.obj.create_volume( + world_size=self.world_size, + global_rank=self.global_rank, + ray_size=volume_dataset.N, + ) pred_full = self.obj.obj loss_tensor = avg_loss.clone().detach() avg_loss = loss_tensor.item() avg_tv_loss = epoch_tv_loss / num_batches avg_consistency_loss = epoch_consistency_loss / num_batches - metrics = torch.tensor([avg_loss, avg_consistency_loss, avg_tv_loss], device=self.device) + metrics = torch.tensor( + [avg_loss, avg_consistency_loss, avg_tv_loss], device=self.device + ) if self.world_size > 1: - dist.all_reduce(metrics, op = dist.ReduceOp.AVG) + dist.all_reduce(metrics, op=dist.ReduceOp.AVG) avg_loss, avg_consistency_loss, avg_tv_loss = metrics.tolist() - + if self.global_rank == 0: self.temp_logger.add_scalar("Pretrain/total_loss", avg_loss, epoch) - self.temp_logger.add_scalar("Pretrain/consistency_loss", avg_consistency_loss, epoch) + self.temp_logger.add_scalar( + "Pretrain/consistency_loss", avg_consistency_loss, epoch + ) self.temp_logger.add_scalar("Pretrain/tv_loss", avg_tv_loss, epoch) - + # current_lr = self.schedulers["model"].get_last_lr()[0] # self.temp_logger.add_scalar("Pretrain/lr", current_lr, epoch) - fig, ax = plt.subplots(ncols = 4, figsize = (36, 12)) - ax[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap='turbo', vmin=0) - ax[0].set_title('Sum over Z-axis') - ax[1].matshow(pred_full[volume_dataset.N//2].cpu().numpy(), cmap='turbo', vmin=0) - ax[1].set_title(f'Slice at Z={volume_dataset.N//2}') - ax[2].matshow(pred_full.sum(dim=1).cpu().numpy(), cmap='turbo', vmin=0) - ax[2].set_title('Sum over Y-axis') - ax[3].matshow(pred_full.sum(dim=2).cpu().numpy(), cmap='turbo', vmin=0) - ax[3].set_title('Sum over X-axis') + fig, ax = plt.subplots(ncols=4, figsize=(36, 12)) + ax[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap="turbo", vmin=0) + ax[0].set_title("Sum over Z-axis") + ax[1].matshow( + pred_full[volume_dataset.N // 2].cpu().numpy(), cmap="turbo", vmin=0 + ) + ax[1].set_title(f"Slice at Z={volume_dataset.N // 2}") + ax[2].matshow(pred_full.sum(dim=1).cpu().numpy(), cmap="turbo", vmin=0) + ax[2].set_title("Sum over Y-axis") + ax[3].matshow(pred_full.sum(dim=2).cpu().numpy(), cmap="turbo", vmin=0) + ax[3].set_title("Sum over X-axis") self.temp_logger.add_figure("Pretrain/viz", fig, epoch, close=True) plt.close(fig) - print(f"Epoch [{epoch}/{epochs}] Pretrain Total Loss: {avg_loss:.6f}, Consistency Loss: {avg_consistency_loss:.6f}, TV Loss: {avg_tv_loss:.6f}") + print( + f"Epoch [{epoch}/{epochs}] Pretrain Total Loss: {avg_loss:.6f}, Consistency Loss: {avg_consistency_loss:.6f}, TV Loss: {avg_tv_loss:.6f}" + ) self.global_epochs += 1 if self.global_rank == 0: - print(f"Pretraining Completed, saving model weights to {log_path}/model_weights.pth") torch.save(self.obj.model.state_dict(), f"{log_path}/model_weights.pth") @@ -344,32 +364,34 @@ def recon( obj: ObjectINN, batch_size: int, num_workers: int = 0, - epochs = 20, - use_amp = True, - viz_freq = 1, - checkpoint_freq = 5, + epochs=20, + use_amp=True, + viz_freq=1, + checkpoint_freq=5, optimizer_params: dict = None, scheduler_params: dict = None, soft_constraints: dict = None, - vol_save_path: str = None, # TODO: TEMPORARY - log_path = None, + vol_save_path: str = None, # TODO: TEMPORARY + log_path=None, val_fraction: float = 0.0, learn_shifts: bool = True, # l1_loss: bool = False, - consistency_criterion: str = 'mse', + consistency_criterion: str = "mse", model_weights_path: str = None, - ): - + force_cpu: bool = False, + ): if not self.ddp_instantiated: - if self.pretraining_instantiated == False: + if not self.pretraining_instantiated: self.setup_distributed() if model_weights_path is not None: print(f"Loading model weights from {model_weights_path}") - state_dict = torch.load(model_weights_path, map_location='cpu') + state_dict = torch.load(model_weights_path, map_location="cpu") # Handle DataParallel/DDP checkpoints - if any(k.startswith('module.') for k in state_dict.keys()): - new_state_dict = {k.replace('module.', ''): v for k, v in state_dict.items()} + if any(k.startswith("module.") for k in state_dict.keys()): + new_state_dict = { + k.replace("module.", ""): v for k, v in state_dict.items() + } else: new_state_dict = state_dict @@ -380,64 +402,61 @@ def recon( self.setup_dataloader(self.dataset, batch_size, num_workers, val_fraction) self.ddp_instantiated = True - - + if soft_constraints is not None: self.obj.soft_constraints = soft_constraints - + if not hasattr(self, "temp_logger"): self.setup_logger(log_path=log_path) zero_tilt_idx = torch.argmin(torch.abs(self.dataset.tilt_angles)).item() - + if self.global_rank == 0: - print(f"Using projection {zero_tilt_idx} (angle={self.dataset.tilt_angles[zero_tilt_idx]:.2f}°) as reference") + print( + f"Using projection {zero_tilt_idx} (angle={self.dataset.tilt_angles[zero_tilt_idx]:.2f}°) as reference" + ) print(f"Using consistency criterion: {consistency_criterion}") - + # Auxiliary params setup self.dataset.setup_auxiliary_params(zero_tilt_idx, self.device, learn_shifts) aux_params = self.dataset.auxiliary_params - # Scaling learning rates to account for distributed training if optimizer_params is not None: optimizer_params = self.scale_lr(optimizer_params) - self.optimizer_params = optimizer_params + self.optimizer_params = optimizer_params self.set_optimizers() if scheduler_params is not None: self.scheduler_params = scheduler_params - self.set_schedulers(self.scheduler_params, num_iter = epochs) - - + self.set_schedulers(self.scheduler_params, num_iter=epochs) + aux_norm = torch.tensor(0.0, device=self.device) model_norm = torch.tensor(0.0, device=self.device) - + for _, opt in self.optimizers.items(): opt.zero_grad() - + N = max(self.dataset.dims) - + device_type = self.device.type autocast_dtype = torch.bfloat16 if use_amp else None - - + for epoch in range(epochs): - num_samples_per_ray = get_num_samples_per_ray(N = N, epoch = self.global_epochs) - + num_samples_per_ray = get_num_samples_per_ray(N=N, epoch=self.global_epochs) + # num_samples_per_ray = get_num_samples_per_ray(epoch) # Log the change if it happens if self.global_rank == 0: print(f"Epoch {epoch}: num_samples_per_ray = {num_samples_per_ray}") - - + if self.global_rank == 0 and self.global_epochs > 0: - prev_samples = get_num_samples_per_ray(N = N, epoch = self.global_epochs - 1) - + prev_samples = get_num_samples_per_ray(N=N, epoch=self.global_epochs - 1) + if num_samples_per_ray != prev_samples: - print(f"Epoch {epoch}: Changing num_samples_per_ray from {prev_samples} to {num_samples_per_ray}") - - - + print( + f"Epoch {epoch}: Changing num_samples_per_ray from {prev_samples} to {num_samples_per_ray}" + ) + if self.sampler is not None: self.sampler.set_epoch(epoch) @@ -448,45 +467,47 @@ def recon( num_batches = 0 consistency_loss_fn = None - if consistency_criterion[0].lower() == 'mse': + if consistency_criterion[0].lower() == "mse": consistency_loss_fn = MSELoss() - elif consistency_criterion[0].lower() == 'l1': + elif consistency_criterion[0].lower() == "l1": consistency_loss_fn = L1Loss() - elif consistency_criterion[0].lower() == 'mse_log': + elif consistency_criterion[0].lower() == "mse_log": consistency_loss_fn = MSELogMSELoss() - elif consistency_criterion[0].lower() == 'llmse': + elif consistency_criterion[0].lower() == "llmse": consistency_loss_fn = LLMSELoss() - elif consistency_criterion[0].lower() == 'smooth_l1': - consistency_loss_fn = SmoothL1Loss(beta = consistency_criterion[1]) - elif consistency_criterion[0].lower() == 'adaptive_smooth_l1': - consistency_loss_fn = AdaptiveSmoothL1LossDDP(beta_init = consistency_criterion[1], ema_factor = 0.99, eps = 1e-8) - elif consistency_criterion[0].lower() == 'charbonnier': - consistency_loss_fn = CharbonnierLoss(epsilon = 1e-12, reduction = 'mean') + elif consistency_criterion[0].lower() == "smooth_l1": + consistency_loss_fn = SmoothL1Loss(beta=consistency_criterion[1]) + elif consistency_criterion[0].lower() == "adaptive_smooth_l1": + consistency_loss_fn = AdaptiveSmoothL1LossDDP( + beta_init=consistency_criterion[1], ema_factor=0.99, eps=1e-8 + ) + elif consistency_criterion[0].lower() == "charbonnier": + consistency_loss_fn = CharbonnierLoss(epsilon=1e-12, reduction="mean") else: raise ValueError(f"Invalid consistency criterion: {consistency_criterion}") for batch_idx, batch in enumerate(self.dataloader): + projection_indices = batch["projection_idx"] - projection_indices = batch['projection_idx'] - - pixel_i = batch['pixel_i'].float().to(self.device, non_blocking=True) - pixel_j = batch['pixel_j'].float().to(self.device, non_blocking=True) - target_values = batch['target_value'].to(self.device, non_blocking=True) - phis = batch['phi'].to(self.device, non_blocking=True) - projection_indices = batch['projection_idx'].to(self.device, non_blocking=True) + pixel_i = batch["pixel_i"].float().to(self.device, non_blocking=True) + pixel_j = batch["pixel_j"].float().to(self.device, non_blocking=True) + target_values = batch["target_value"].to(self.device, non_blocking=True) + phis = batch["phi"].to(self.device, non_blocking=True) + projection_indices = batch["projection_idx"].to(self.device, non_blocking=True) shifts, z1_params, z3_params = aux_params(None) batch_shifts = torch.index_select(shifts, 0, projection_indices) batch_z1 = torch.index_select(z1_params, 0, projection_indices) batch_z3 = torch.index_select(z3_params, 0, projection_indices) - - with torch.autocast(device_type=device_type, dtype=autocast_dtype, enabled=use_amp): + with torch.autocast( + device_type=device_type, dtype=autocast_dtype, enabled=use_amp + ): with torch.no_grad(): batch_ray_coords = self.create_batch_projection_rays( pixel_i, pixel_j, N, num_samples_per_ray ) - + # TODO: .forward passing z1, x, z3, shifts, N, sampling_rate transformed_rays = self.transform_batch_ray_coordinates( batch_ray_coords, @@ -503,7 +524,9 @@ def recon( all_densities = self.obj.forward(all_coords) tv_loss = self.obj.apply_soft_constraints(all_coords) - ray_densities = all_densities.view(len(target_values), num_samples_per_ray) # Reshape rays and integarte + ray_densities = all_densities.view( + len(target_values), num_samples_per_ray + ) # Reshape rays and integarte step_size = 2.0 / (num_samples_per_ray - 1) predicted_values = ray_densities.sum(dim=1) * step_size @@ -512,7 +535,7 @@ def recon( tv_loss_z1 = torch.tensor(0.0, device=self.device) loss = consistency_loss + tv_loss + tv_loss_z1 - + loss.backward() epoch_loss += loss.detach() @@ -520,28 +543,32 @@ def recon( epoch_tv_loss += tv_loss.detach() epoch_z1_loss += tv_loss_z1.detach() num_batches += 1 - for key, opt in self.optimizers.items(): - if key == "model": - model_norm = torch.nn.utils.clip_grad_norm_(self.obj.model.parameters(), max_norm = 1) + model_norm = torch.nn.utils.clip_grad_norm_( + self.obj.model.parameters(), max_norm=1 + ) elif key == "aux_params": - aux_norm = torch.nn.utils.clip_grad_norm_(self.dataset.auxiliary_params.parameters(), max_norm = 1) + aux_norm = torch.nn.utils.clip_grad_norm_( + self.dataset.auxiliary_params.parameters(), max_norm=1 + ) opt.step() opt.zero_grad() - + for key, sched in self.schedulers.items(): if isinstance(sched, torch.optim.lr_scheduler.ReduceLROnPlateau): sched.step(epoch_loss) - else: + else: sched.step() - - if epoch % viz_freq == 0 or epoch == epochs - 1 or epoch % checkpoint_freq == 0 : + if epoch % viz_freq == 0 or epoch == epochs - 1 or epoch % checkpoint_freq == 0: with torch.no_grad(): - - self.obj.create_volume(world_size = self.world_size, global_rank = self.global_rank, ray_size = num_samples_per_ray) + self.obj.create_volume( + world_size=self.world_size, + global_rank=self.global_rank, + ray_size=num_samples_per_ray, + ) pred_full = self.obj.obj avg_loss = epoch_loss.item() / num_batches avg_consistency_loss = epoch_consistency_loss.item() / num_batches @@ -551,76 +578,99 @@ def recon( shifts = shifts.detach().cpu() z1 = z1.detach().cpu() z3 = z3.detach().cpu() - metrics = torch.tensor([avg_loss, avg_consistency_loss, avg_tv_loss, avg_z1_loss], device=self.device) + metrics = torch.tensor( + [avg_loss, avg_consistency_loss, avg_tv_loss, avg_z1_loss], + device=self.device, + ) if self.world_size > 1: dist.all_reduce(metrics, op=dist.ReduceOp.AVG) avg_loss, avg_consistency_loss, avg_tv_loss, avg_z1_loss = metrics.tolist() - if hasattr(self, "val_dataloader") and self.val_dataloader is not None: - val_loss = self.validate(aux_params, num_samples_per_ray, device_type, autocast_dtype, use_amp, N) + val_loss = self.validate( + aux_params, num_samples_per_ray, device_type, autocast_dtype, use_amp, N + ) if self.global_rank == 0 and (epoch % viz_freq == 0 or epoch == epochs - 1): with torch.no_grad(): current_lr = self.schedulers["model"].get_last_lr()[0] # Log metrics - self.temp_logger.add_scalar(f"train/consistency_loss_{consistency_criterion[0] if isinstance(consistency_criterion, tuple) else consistency_criterion}", avg_consistency_loss, self.global_epochs) + self.temp_logger.add_scalar( + f"train/consistency_loss_{consistency_criterion[0] if isinstance(consistency_criterion, tuple) else consistency_criterion}", + avg_consistency_loss, + self.global_epochs, + ) if hasattr(self, "val_dataloader") and self.val_dataloader is not None: - self.temp_logger.add_scalar(f"val/consistency_loss_{consistency_criterion[0] if isinstance(consistency_criterion, tuple) else consistency_criterion}", val_loss, self.global_epochs) + self.temp_logger.add_scalar( + f"val/consistency_loss_{consistency_criterion[0] if isinstance(consistency_criterion, tuple) else consistency_criterion}", + val_loss, + self.global_epochs, + ) self.temp_logger.add_scalar("train/z1_loss", avg_z1_loss, self.global_epochs) # if tv_weight > 0: self.temp_logger.add_scalar("train/tv_loss", avg_tv_loss, self.global_epochs) self.temp_logger.add_scalar("train/total_loss", avg_loss, self.global_epochs) - self.temp_logger.add_scalar("train/model_grad_norm", model_norm.item(), self.global_epochs) - self.temp_logger.add_scalar("train/aux_grad_norm", aux_norm.item(), self.global_epochs) + self.temp_logger.add_scalar( + "train/model_grad_norm", model_norm.item(), self.global_epochs + ) + self.temp_logger.add_scalar( + "train/aux_grad_norm", aux_norm.item(), self.global_epochs + ) self.temp_logger.add_scalar("train/lr", current_lr, self.global_epochs) - self.temp_logger.add_scalar("train/num_samples_per_ray", num_samples_per_ray, self.global_epochs) - if consistency_criterion[0].lower() == 'adaptive_smooth_l1': - self.temp_logger.add_scalar("train/beta_2", consistency_loss_fn.beta2.item(), self.global_epochs) + self.temp_logger.add_scalar( + "train/num_samples_per_ray", num_samples_per_ray, self.global_epochs + ) + if consistency_criterion[0].lower() == "adaptive_smooth_l1": + self.temp_logger.add_scalar( + "train/beta_2", consistency_loss_fn.beta2.item(), self.global_epochs + ) fig, axes = plt.subplots(1, 5, figsize=(36, 12)) - axes[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap='turbo', vmin=0) - axes[0].set_title('Sum over Z-axis') + axes[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap="turbo", vmin=0) + axes[0].set_title("Sum over Z-axis") - axes[1].matshow(pred_full[N//2].cpu().numpy(), cmap='turbo', vmin=0) - axes[1].set_title(f'Slice at Z={N//2}') + axes[1].matshow(pred_full[N // 2].cpu().numpy(), cmap="turbo", vmin=0) + axes[1].set_title(f"Slice at Z={N // 2}") - slice_start = max(0, N//2 - 5) - slice_end = min(N, N//2 + 6) + slice_start = max(0, N // 2 - 5) + slice_end = min(N, N // 2 + 6) thick_slice = pred_full[slice_start:slice_end].sum(dim=0).cpu().numpy() - axes[2].matshow(thick_slice, cmap='turbo', vmin=0) - axes[2].set_title(f'Thick slice sum (Z={slice_start}:{slice_end-1})') - - axes[3].matshow(pred_full.sum(dim = 1).cpu().numpy(), cmap='turbo', vmin=0) - axes[3].set_title('Sum over Y-axis') - - axes[4].matshow(pred_full.sum(dim = 2).cpu().numpy(), cmap='turbo', vmin=0) - axes[4].set_title('Sum over X-axis') + axes[2].matshow(thick_slice, cmap="turbo", vmin=0) + axes[2].set_title(f"Thick slice sum (Z={slice_start}:{slice_end - 1})") + + axes[3].matshow(pred_full.sum(dim=1).cpu().numpy(), cmap="turbo", vmin=0) + axes[3].set_title("Sum over Y-axis") + + axes[4].matshow(pred_full.sum(dim=2).cpu().numpy(), cmap="turbo", vmin=0) + axes[4].set_title("Sum over X-axis") self.temp_logger.add_figure("train/viz", fig, self.global_epochs, close=True) - fig, axes = plt.subplots(ncols = 3, figsize = (10, 5)) - axes[0].plot(shifts[:, 0].cpu().numpy(), label = 'Shifts X') - axes[0].plot(shifts[:, 1].cpu().numpy(), label = 'Shifts Y') + fig, axes = plt.subplots(ncols=3, figsize=(10, 5)) + axes[0].plot(shifts[:, 0].cpu().numpy(), label="Shifts X") + axes[0].plot(shifts[:, 1].cpu().numpy(), label="Shifts Y") axes[0].legend() - axes[1].plot(z1.cpu().numpy(), label = 'Z1') - axes[2].plot(z3.cpu().numpy(), label = 'Z3') - self.temp_logger.add_figure("train/auxiliary_params", fig, self.global_epochs, close=True) + axes[1].plot(z1.cpu().numpy(), label="Z1") + axes[2].plot(z3.cpu().numpy(), label="Z3") + self.temp_logger.add_figure( + "train/auxiliary_params", fig, self.global_epochs, close=True + ) plt.close(fig) - + if epoch % checkpoint_freq == 0 or self.global_epochs == epochs - 1: with torch.no_grad(): if self.global_rank == 0: save_path = f"{vol_save_path}/volume_epoch_{self.global_epochs:04d}.pt" torch.save(pred_full.cpu(), save_path) - + self.global_epochs += 1 if self.global_rank == 0: print("Training complete.") - + torch.save(self.dataset.auxiliary_params.forward(), f"{log_path}/auxiliary_params.pt") # print("Successfully setup DDP and dataloader") + def validate(self, aux_params, num_samples_per_ray, device_type, autocast_dtype, use_amp, N): """Validate the model on the validation set.""" self.obj.model.eval() @@ -631,18 +681,20 @@ def validate(self, aux_params, num_samples_per_ray, device_type, autocast_dtype, with torch.no_grad(): for batch in self.val_dataloader: - pixel_i = batch['pixel_i'].float().to(self.device, non_blocking=True) - pixel_j = batch['pixel_j'].float().to(self.device, non_blocking=True) - target_values = batch['target_value'].to(self.device, non_blocking=True) - phis = batch['phi'].to(self.device, non_blocking=True) - projection_indices = batch['projection_idx'].to(self.device, non_blocking=True) + pixel_i = batch["pixel_i"].float().to(self.device, non_blocking=True) + pixel_j = batch["pixel_j"].float().to(self.device, non_blocking=True) + target_values = batch["target_value"].to(self.device, non_blocking=True) + phis = batch["phi"].to(self.device, non_blocking=True) + projection_indices = batch["projection_idx"].to(self.device, non_blocking=True) shifts, z1_params, z3_params = aux_params(None) batch_shifts = torch.index_select(shifts, 0, projection_indices) batch_z1 = torch.index_select(z1_params, 0, projection_indices) batch_z3 = torch.index_select(z3_params, 0, projection_indices) - with torch.autocast(device_type=device_type, dtype=autocast_dtype, enabled=use_amp): + with torch.autocast( + device_type=device_type, dtype=autocast_dtype, enabled=use_amp + ): with torch.no_grad(): batch_ray_coords = self.create_batch_projection_rays( pixel_i, pixel_j, N, num_samples_per_ray @@ -662,7 +714,9 @@ def validate(self, aux_params, num_samples_per_ray, device_type, autocast_dtype, all_densities = self.obj.forward(all_coords) - ray_densities = all_densities.view(len(target_values), num_samples_per_ray) # Reshape rays and integarte + ray_densities = all_densities.view( + len(target_values), num_samples_per_ray + ) # Reshape rays and integarte step_size = 2.0 / (num_samples_per_ray - 1) predicted_values = ray_densities.sum(dim=1) * step_size diff --git a/src/quantem/tomography/tomography_ddp.py b/src/quantem/tomography/tomography_ddp.py index 86c4f59e..cfb59117 100644 --- a/src/quantem/tomography/tomography_ddp.py +++ b/src/quantem/tomography/tomography_ddp.py @@ -1,31 +1,36 @@ -import torch.distributed as dist -import torch -from torch.utils.data import Dataset, DataLoader, DistributedSampler -import torch.nn as nn import os + import numpy as np -from quantem.tomography.tomography_dataset import TomographyDataset, TomographyRayDataset, PretrainVolumeDataset +import torch +import torch.distributed as dist +import torch.nn as nn +from torch.utils.data import DataLoader, DistributedSampler + +from quantem.tomography.tomography_dataset import ( + PretrainVolumeDataset, + TomographyDataset, + TomographyRayDataset, +) -from torch.utils.data import random_split class TomographyDDP: """ Initializing DDP stuff for tomo class. """ + def __init__( self, ): - self.setup_distributed() - - + def setup_distributed(self): - # Check if in distributed env if "RANK" in os.environ: # Distributed training if not dist.is_initialized(): - dist.init_process_group(backend='nccl' if torch.cuda.is_available() else 'gloo', init_method='env://') + dist.init_process_group( + backend="nccl" if torch.cuda.is_available() else "gloo", init_method="env://" + ) self.world_size = dist.get_world_size() self.global_rank = dist.get_rank() @@ -33,7 +38,7 @@ def setup_distributed(self): torch.cuda.set_device(self.local_rank) self.device = torch.device("cuda", self.local_rank) - + else: # Single GPU/CPU training self.world_size = 1 @@ -41,49 +46,47 @@ def setup_distributed(self): self.local_rank = 0 if torch.cuda.is_available(): - self.device = torch.device("cuda:0") + self.device = torch.device("cuda") torch.cuda.set_device(0) print("Single GPU training") else: self.device = torch.device("cpu") print("CPU training") - + # Optional performance optimizations (only for CUDA) if self.device.type == "cuda": torch.backends.cudnn.benchmark = True torch.backends.cuda.matmul.allow_tf32 = True torch.backends.cudnn.allow_tf32 = True - + def build_model( self, model: nn.Module, ): - # TODO: Generalized model --> Should be instantiated in the object? Where does `HSIREN` get instantiated? model = model.to(self.device) - + if self.world_size > 1: model = torch.nn.parallel.DistributedDataParallel( model, - device_ids = [self.local_rank], - output_device = self.local_rank, - find_unused_parameters = False, - broadcast_buffers = True, - bucket_cap_mb = 100, - gradient_as_bucket_view = True, + device_ids=[self.local_rank], + output_device=self.local_rank, + find_unused_parameters=False, + broadcast_buffers=True, + bucket_cap_mb=100, + gradient_as_bucket_view=True, ) - + if self.global_rank == 0: print("Model wrapped with DDP") - + if self.world_size > 1: - if self.global_rank == 0: print("Model built, distributed, and compiled successfully") - + else: print("Model built, compiled successfully") - + return model # Setup Tomo DataLoader @@ -103,17 +106,17 @@ def setup_dataloader( train_dataset = TomographyRayDataset( tomo_dataset.tilt_series.detach().clone(), tomo_dataset.tilt_angles.detach().clone(), - 500, # TODO: TEMPORARY + 500, # TODO: TEMPORARY val_ratio=val_fraction, - mode='train', + mode="train", seed=42, ) val_dataset = TomographyRayDataset( tomo_dataset.tilt_series.detach().clone(), tomo_dataset.tilt_angles.detach().clone(), - 500, # TODO: TEMPORARY + 500, # TODO: TEMPORARY val_ratio=val_fraction, - mode='val', + mode="val", seed=42, ) else: @@ -166,12 +169,10 @@ def setup_dataloader( ) self.val_sampler = val_sampler - self.sampler = train_sampler - if self.global_rank == 0: - print(f"Dataloader setup complete:") + print("Dataloader setup complete:") print(f" Total projections: {len(tomo_dataset.tilt_angles)}") if val_fraction > 0.0: print(f" Total projections (val): {len(val_dataset)}") @@ -181,10 +182,9 @@ def setup_dataloader( print(f" Total pixels (val): {len(val_dataset):,}") print(f" Total pixels (train): {len(train_dataset):,}") print(f" Local batch size (train): {batch_size}") - print(f" Global batch size: {batch_size*self.world_size}") + print(f" Global batch size: {batch_size * self.world_size}") print(f" Train batches per GPU per epoch: {len(self.dataloader)}") - # Setup pretraining dataloader def setup_pretraining_dataloader( @@ -192,16 +192,16 @@ def setup_pretraining_dataloader( volume_dataset: PretrainVolumeDataset, batch_size: int, ): - #TODO: temp + # TODO: temp num_workers = 0 if self.world_size > 1: sampler = DistributedSampler( volume_dataset, - num_replicas = self.world_size, - rank = self.global_rank, - shuffle = True, - drop_last = True, + num_replicas=self.world_size, + rank=self.global_rank, + shuffle=True, + drop_last=True, ) shuffle = False else: @@ -210,26 +210,24 @@ def setup_pretraining_dataloader( self.pretraining_dataloader = DataLoader( volume_dataset, - batch_size = batch_size, - sampler = sampler, - shuffle = shuffle, - pin_memory = self.device.type == "cuda", - drop_last = True, - persistent_workers = num_workers > 0, + batch_size=batch_size, + sampler=sampler, + shuffle=shuffle, + pin_memory=self.device.type == "cuda", + drop_last=True, + persistent_workers=num_workers > 0, ) self.pretraining_sampler = sampler if self.global_rank == 0: - print(f"Pretraining dataloader setup complete:") + print("Pretraining dataloader setup complete:") print(f" Total samples: {len(volume_dataset)}") print(f" Grid size: {volume_dataset.N**3}") print(f" Local batch size: {batch_size}") - print(f" Global batch size: {batch_size*self.world_size}") + print(f" Global batch size: {batch_size * self.world_size}") print(f" Pretraining batches per GPU per epoch: {len(self.pretraining_dataloader)}") - - def get_scaled_lr(self, base_lr, scaling_rule="sqrt"): if scaling_rule == "sqrt": return base_lr * np.sqrt(self.world_size) @@ -237,15 +235,13 @@ def get_scaled_lr(self, base_lr, scaling_rule="sqrt"): return base_lr * self.world_size else: raise ValueError(f"Invalid scaling rule: {scaling_rule}") - + def scale_lr( self, optimizer_params: dict, ): - new_optimizer_params = {} for key, value in optimizer_params.items(): - if "original_lr" in value: new_optimizer_params[key] = { "type": value["type"], @@ -258,13 +254,12 @@ def scale_lr( "lr": self.get_scaled_lr(value["lr"]), "original_lr": value["lr"], } - + return new_optimizer_params - - # TODO: Temporary Adaptive L1 Smooth Loss + class AdaptiveSmoothL1Loss(nn.Module): def __init__(self, beta_init=None, ema_factor=0.99, eps=1e-8): """ @@ -276,7 +271,7 @@ def __init__(self, beta_init=None, ema_factor=0.99, eps=1e-8): eps (float): small constant for numerical stability """ super().__init__() - self.register_buffer('beta2', torch.tensor(beta_init**2 if beta_init else 1.0)) + self.register_buffer("beta2", torch.tensor(beta_init**2 if beta_init else 1.0)) self.ema_factor = ema_factor self.eps = eps @@ -285,27 +280,28 @@ def forward(self, pred, target): abs_diff = diff.abs() # compute current batch MSE (Eq. 38) - mse_batch = torch.mean(diff ** 2) + mse_batch = torch.mean(diff**2) # update β² adaptively using Eq. (39) with torch.no_grad(): - self.beta2 = self.ema_factor * self.beta2 + (1 - self.ema_factor) * torch.min(self.beta2, mse_batch) + self.beta2 = self.ema_factor * self.beta2 + (1 - self.ema_factor) * torch.min( + self.beta2, mse_batch + ) beta = torch.sqrt(self.beta2 + self.eps) # Smooth L1 (Eq. 36) loss = torch.where( - abs_diff < beta, - 0.5 * (diff ** 2) / (beta + self.eps), - abs_diff - 0.5 * beta + abs_diff < beta, 0.5 * (diff**2) / (beta + self.eps), abs_diff - 0.5 * beta ) return loss.mean() + class AdaptiveSmoothL1LossDDP(AdaptiveSmoothL1Loss): def forward(self, pred, target): diff = pred - target abs_diff = diff.abs() - mse_batch = torch.mean(diff ** 2) + mse_batch = torch.mean(diff**2) # Synchronize β across all GPUs if dist.is_initialized(): @@ -314,8 +310,12 @@ def forward(self, pred, target): mse_batch = mse_batch_all / dist.get_world_size() with torch.no_grad(): - self.beta2 = self.ema_factor * self.beta2 + (1 - self.ema_factor) * torch.min(self.beta2, mse_batch) + self.beta2 = self.ema_factor * self.beta2 + (1 - self.ema_factor) * torch.min( + self.beta2, mse_batch + ) beta = torch.sqrt(self.beta2 + self.eps) - loss = torch.where(abs_diff < beta, 0.5 * (diff ** 2) / (beta + self.eps), abs_diff - 0.5 * beta) + loss = torch.where( + abs_diff < beta, 0.5 * (diff**2) / (beta + self.eps), abs_diff - 0.5 * beta + ) return loss.mean() From 700f2055474f760f8ea6e75ed4bac6333cc4d8a0 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 16 Dec 2025 15:49:17 -0800 Subject: [PATCH 032/335] Outlining dataset_models.py, and the top level Tomography class --- src/quantem/core/ml/constraints.py | 99 ++ src/quantem/tomography/dataset_models.py | 156 +++ src/quantem/tomography/models.py | 59 -- src/quantem/tomography/object_models.py | 734 --------------- src/quantem/tomography/tomography.py | 890 +----------------- src/quantem/tomography/tomography_base.py | 513 ---------- src/quantem/tomography/tomography_conv.py | 235 ----- src/quantem/tomography/tomography_ddp.py | 321 ------- src/quantem/tomography/tomography_ml.py | 330 ------- src/quantem/tomography/utils.py | 300 ------ .../preprocess => tomography_old}/__init__.py | 0 src/quantem/tomography_old/models.py | 96 ++ src/quantem/tomography_old/object_models.py | 723 ++++++++++++++ .../preprocess}/__init__.py | 0 .../preprocess/drift.py | 0 src/quantem/tomography_old/radon/__init__.py | 0 .../radon/radon.py | 0 src/quantem/tomography_old/tomography.py | 886 +++++++++++++++++ src/quantem/tomography_old/tomography_base.py | 514 ++++++++++ src/quantem/tomography_old/tomography_conv.py | 235 +++++ .../tomography_dataset.py | 50 +- src/quantem/tomography_old/tomography_ddp.py | 321 +++++++ .../tomography_logger.py | 0 src/quantem/tomography_old/tomography_ml.py | 338 +++++++ .../tomography_nerf.py | 331 +++---- src/quantem/tomography_old/utils.py | 300 ++++++ 26 files changed, 3871 insertions(+), 3560 deletions(-) create mode 100644 src/quantem/core/ml/constraints.py create mode 100644 src/quantem/tomography/dataset_models.py delete mode 100644 src/quantem/tomography/models.py rename src/quantem/{tomography/preprocess => tomography_old}/__init__.py (100%) create mode 100644 src/quantem/tomography_old/models.py create mode 100644 src/quantem/tomography_old/object_models.py rename src/quantem/{tomography/radon => tomography_old/preprocess}/__init__.py (100%) rename src/quantem/{tomography => tomography_old}/preprocess/drift.py (100%) create mode 100644 src/quantem/tomography_old/radon/__init__.py rename src/quantem/{tomography => tomography_old}/radon/radon.py (100%) create mode 100644 src/quantem/tomography_old/tomography.py create mode 100644 src/quantem/tomography_old/tomography_base.py create mode 100644 src/quantem/tomography_old/tomography_conv.py rename src/quantem/{tomography => tomography_old}/tomography_dataset.py (95%) create mode 100644 src/quantem/tomography_old/tomography_ddp.py rename src/quantem/{tomography => tomography_old}/tomography_logger.py (100%) create mode 100644 src/quantem/tomography_old/tomography_ml.py rename src/quantem/{tomography => tomography_old}/tomography_nerf.py (78%) create mode 100644 src/quantem/tomography_old/utils.py diff --git a/src/quantem/core/ml/constraints.py b/src/quantem/core/ml/constraints.py new file mode 100644 index 00000000..755122c6 --- /dev/null +++ b/src/quantem/core/ml/constraints.py @@ -0,0 +1,99 @@ +from abc import ABC, abstractmethod +from typing import Any + +import torch + +from quantem.core import config + + +class BaseConstraints(ABC): + """Base class for constraint management with common functionality.""" + + # Subclasses should define their own DEFAULT_CONSTRAINTS + DEFAULT_CONSTRAINTS: dict[str, Any] = {} + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._soft_constraint_loss = {} + self._constraints = self.DEFAULT_CONSTRAINTS.copy() + self._epoch_constraint_losses = {} + + @property + def constraints(self) -> dict[str, Any]: + return self._constraints + + @constraints.setter + def constraints(self, c: dict[str, Any]): + allowed_keys = self.DEFAULT_CONSTRAINTS.keys() + constraint_type = self.__class__.__name__.lower().replace("constraints", "") + + for key, value in c.items(): + if key not in allowed_keys: + raise KeyError( + f"Invalid {constraint_type} constraint key '{key}', allowed keys are {list(allowed_keys)}" + ) + self._constraints[key] = value + + @property + def soft_constraint_loss(self) -> dict[str, torch.Tensor | float]: + return self._soft_constraint_loss + + def add_constraint(self, key: str, value: Any): + allowed_keys = self.DEFAULT_CONSTRAINTS.keys() + constraint_type = self.__class__.__name__.lower().replace("constraints", "") + + if key not in allowed_keys: + raise KeyError( + f"Invalid {constraint_type} constraint key '{key}', allowed keys are {list(allowed_keys)}" + ) + self._constraints[key] = value + + @abstractmethod + def apply_soft_constraints(self, *args, **kwargs) -> torch.Tensor: + """Apply soft constraints and return total constraint loss.""" + pass + + def _get_zero_loss_tensor(self) -> torch.Tensor: + """Helper method to create a zero loss tensor with proper device and dtype.""" + device = getattr(self, "device", "cpu") + return torch.tensor(0, device=device, dtype=getattr(torch, config.get("dtype_real"))) + + # --- helpers for consistent loss logging --- + def reset_soft_constraint_losses(self) -> None: + self._soft_constraint_loss = {} + + def add_soft_constraint_loss(self, name: str, value: torch.Tensor | float) -> None: + """Record a single soft-constraint loss for logging without holding the graph.""" + if isinstance(value, torch.Tensor): + val = value.detach() + if val.ndim != 0: + val = val.mean() + self._soft_constraint_loss[name] = val + else: + self._soft_constraint_loss[name] = float(value) + + def accumulate_constraint_losses( + self, batch_constraint_losses: dict[str, torch.Tensor | float] | None = None + ) -> None: + """Accumulate constraint losses across batches.""" + if batch_constraint_losses is None: + batch_constraint_losses = self.soft_constraint_loss + + for loss_name, loss_value in batch_constraint_losses.items(): + if isinstance(loss_value, torch.Tensor): + try: + v = loss_value.item() + except Exception: + print("loss value not singular: ", loss_value) # TODO remove + v = loss_value.detach().mean().item() + else: + v = float(loss_value) + self._epoch_constraint_losses[loss_name] = ( + self._epoch_constraint_losses.get(loss_name, 0.0) + v + ) + + def get_epoch_constraint_losses(self) -> dict[str, float]: + return getattr(self, "_epoch_constraint_losses", {}) # TODO clean this up + + def reset_epoch_constraint_losses(self) -> None: + self._epoch_constraint_losses = {} diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py new file mode 100644 index 00000000..994d3356 --- /dev/null +++ b/src/quantem/tomography/dataset_models.py @@ -0,0 +1,156 @@ +from abc import abstractmethod +from dataclasses import dataclass +from typing import Any + +import torch +import torch.nn as nn +from numpy.typing import NDArray +from torch.utils.data import Dataset + +from quantem.core.datastructures.dataset3d import Dataset3d +from quantem.core.io.serialize import AutoSerialize +from quantem.core.ml.optimizer_mixin import OptimizerMixin + + +@dataclass +class DatasetValue: + """ + Class for storing the forward call for both PixDataset and INRDataset. + """ + + target: torch.Tensor + tilt_angle: int | float + pixel_loc: tuple[int, int] | None = None # Only for INRDataset + + +class TomographyDatasetBase(AutoSerialize, OptimizerMixin, nn.Module): + """ + Base tomography dataset class for all tomography datasets to inherit from. + """ + + _token = object() + + DEFAULT_LRS = { + "pose_lr": 5e-2, + } + + def __init__( + self, + tilt_stack: Dataset3d, + tilt_angles: NDArray | torch.Tensor, + learn_pose: bool = True, + _token: object | None = None, + ): + AutoSerialize.__init__(self) + OptimizerMixin.__init__(self) + nn.Module.__init__(self) + + if _token is not self._token: + raise RuntimeError("Use TomographyDatasetBase.from_* to instantiate this class.") + + if not ( + tilt_stack.shape[0] < tilt_stack.shape[1] or tilt_stack.shape[0] < tilt_stack.shape[2] + ): + raise ValueError( + "The number of tilt projections should be in the first dimension of the dataset." + ) + + self.tilt_stack = tilt_stack + self.tilt_angles = tilt_angles + self.learn_pose = learn_pose + + self._reference_tilt_angle_idx = torch.argmin(torch.abs(self.tilt_angles)) + + @abstractmethod + def forward( + self, + dummy_input: Any = None, # Note all nn.Modules require some input. + ): + """ + Forward pass should be implemented in subclasses. + """ + raise NotImplementedError("This method should be implemented in subclasses.") + + +class TomographyPixDataset( + TomographyDatasetBase +): # Dataset): TODO: Does this need to be a dataset? + """ + Dataset class for pixel-based tomography, i.e AD, SIRT, WBP, etc... + + TODO: + - What should the forward pass return? In AD, it should be both the tilt image and the pose. + In SIRT, it's only the tilt image. + """ + + pass + + def __init__( + self, + ): + pass + + def forward( + self, + proj_idx: int, + ): + return DatasetValue( + target=self.tilt_stack[proj_idx], + tilt_angle=self.tilt_angles[proj_idx], + pixel_loc=None, + ) + + +class TomographyINRDataset(TomographyDatasetBase, Dataset): + """ + Dataset class for INR-based tomography. + + The two main methods here are that the `forward` call will return the relative pose parameters, + while `__getitem__` will actually return the pixel values of the tilt stack. + """ + + def __init__( + self, + ): + pass + + def forward(self, dummy_input: Any = None): + """ + Forward pass for INR-based tomography. In the forward pass, the only parameters that + are passed will be the shifts, z1 and z3 Euler angles. + """ + pass + + def __getitem__( + self, + idx: int, + ) -> dict[str, Any]: + """ + Gets the item for INR i.e, the project index, pixel value at (i, j), and the tilt angle. + """ + pass + + actual_idx = idx + + projection_idx = actual_idx // (self.tilt_stack.shape[1] * self.tilt_stack.shape[2]) + remaining = actual_idx % (self.tilt_stack.shape[1] * self.tilt_stack.shape[2]) + + pixel_i = remaining // self.tilt_stack.shape[1] + pixel_j = remaining % self.tilt_stack.shape[1] + + return DatasetValue( + target=self.tilt_stack[projection_idx, pixel_i, pixel_j], + tilt_angle=self.tilt_angles[projection_idx], + pixel_loc=(pixel_i, pixel_j), + ) + + def __len__( + self, + ): + """ + Returns the number of pixels in the tilt stack. + """ + pass + + +DatasetModelType = TomographyINRDataset diff --git a/src/quantem/tomography/models.py b/src/quantem/tomography/models.py deleted file mode 100644 index 212385ee..00000000 --- a/src/quantem/tomography/models.py +++ /dev/null @@ -1,59 +0,0 @@ -import torch -from torch import nn -import numpy as np -import math -from torch.nn import init - -class SineLayer(nn.Module): - def __init__(self, in_features, out_features, bias=True, is_first=False, omega_0=30, hsiren=False, alpha=1.0): - super().__init__() - self.omega_0 = omega_0 - self.is_first = is_first - self.hsiren = hsiren - self.in_features = in_features - self.alpha = alpha - self.linear = nn.Linear(in_features, out_features, bias=bias) - self.init_weights() - - def init_weights(self): - with torch.no_grad(): - if self.is_first: - # Scale the first layer initialization by alpha - self.linear.weight.uniform_(-self.alpha / self.in_features, - self.alpha / self.in_features) - else: - # Scale the hidden layer initialization by alpha - self.linear.weight.uniform_(-self.alpha * np.sqrt(6 / self.in_features) / self.omega_0, - self.alpha * np.sqrt(6 / self.in_features) / self.omega_0) - - def forward(self, input): - if self.is_first and self.hsiren: - out = torch.sin(self.omega_0 * torch.sinh(2*self.linear(input))) - else: - out = torch.sin(self.omega_0 * self.linear(input)) - return out - -class HSiren(nn.Module): - def __init__(self, in_features=2, out_features=3, hidden_layers=3, hidden_features=256, - first_omega_0=30, hidden_omega_0=30, alpha=1.0): - super().__init__() - self.net_list = [] - self.net_list.append(SineLayer(in_features, hidden_features, is_first=True, - omega_0=first_omega_0, hsiren=True, alpha=alpha)) - - for i in range(hidden_layers): - self.net_list.append(SineLayer(hidden_features, hidden_features, is_first=False, - omega_0=hidden_omega_0, alpha=alpha)) - - final_linear = nn.Linear(hidden_features, out_features) - with torch.no_grad(): - # Final layer keeps original initialization (no alpha scaling) - final_linear.weight.uniform_(-np.sqrt(6 / hidden_features) / hidden_omega_0, - np.sqrt(6 / hidden_features) / hidden_omega_0) - self.net_list.append(final_linear) - self.net_list.append(nn.Softplus()) - self.net = nn.Sequential(*self.net_list) - - def forward(self, coords): - output = self.net(coords) - return output diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index ff36eb54..e69de29b 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -1,734 +0,0 @@ -from abc import abstractmethod -from copy import deepcopy -from pathlib import Path -from typing import Any, Callable, Union - -import numpy as np -import torch -from tqdm.auto import tqdm - -from quantem.core.io.serialize import AutoSerialize -from quantem.core.ml.blocks import reset_weights -from quantem.core.utils.validators import validate_gt, validate_tensor -from quantem.tomography.utils import get_TV_loss - -import torch.nn as nn -import torch.distributed as dist - - -class ObjectBase(AutoSerialize): - """ - Base class for all ObjectModels to inherit from. - """ - - def __init__( - self, - volume_shape: tuple[int, int, int], - device: str, - offset_obj: float = 1e-5, - ): - self._shape = volume_shape - - self._obj = torch.zeros(self._shape, device=device, dtype=torch.float32) + offset_obj - self._offset_obj = offset_obj - self._device = device - self._hard_constraints = {} - self._soft_constraints = {} # One big dicitonary - - @property - def shape(self) -> tuple[int, int, int]: - return self._shape - - @shape.setter - def shape(self, shape: tuple[int, int, int]): - self._shape = shape - - @property - def offset_obj(self) -> float: - return self._offset_obj - - @offset_obj.setter - def offset_obj(self, offset_obj: float): - self._offset_obj = offset_obj - - @property - def obj(self) -> torch.Tensor: - pass - - @obj.setter - def obj(self, obj: torch.Tensor): - self._obj = obj - - @property - def device(self) -> str: - return self._device - - @device.setter - def device(self, device: str): - self._device = device - - @abstractmethod - def forward( - self, z1: torch.Tensor, z3: torch.Tensor, shift_x: torch.Tensor, shift_y: torch.Tensor - ): - pass - - @abstractmethod - def obj(self): - pass - - @abstractmethod - def reset(self): - pass - - @abstractmethod - def to(self, device: str): - pass - - @abstractmethod - def name(self) -> str: - pass - - @abstractmethod - def params(self) -> torch.Tensor: - pass - - -class ObjectConstraints(ObjectBase): - DEFAULT_HARD_CONSTRAINTS = { - "fourier_filter": False, - "positivity": False, - "shrinkage": False, - "circular_mask": False, - } - - DEFAULT_SOFT_CONSTRAINTS = { - "tv_vol": 0, - } - - @property - def hard_constraints(self) -> dict[str, Any]: - return self._hard_constraints - - @hard_constraints.setter - def hard_constraints(self, hard_constraints: dict[str, Any]): - gkeys = self.DEFAULT_HARD_CONSTRAINTS.keys() - for key, value in hard_constraints.items(): - if key not in gkeys: # This might be redundant since add_constraint is checking. - raise KeyError(f"Invalid object constraint key '{key}', allowed keys are {gkeys}") - self._hard_constraints[key] = value - - @property - def soft_constraints(self) -> dict[str, Any]: - return self._soft_constraints - - @soft_constraints.setter - def soft_constraints(self, soft_constraints: dict[str, Any]): - gkeys = self.DEFAULT_SOFT_CONSTRAINTS.keys() - for key, value in soft_constraints.items(): - if key not in gkeys: - raise KeyError(f"Invalid object constraint key '{key}', allowed keys are {gkeys}") - self._soft_constraints[key] = value - - def add_hard_constraint(self, constraint: str, value: Any): - """Add constraints to the object model.""" - gkeys = self.DEFAULT_HARD_CONSTRAINTS.keys() - if constraint not in gkeys: - raise KeyError( - f"Invalid object constraint key '{constraint}', allowed keys are {gkeys}" - ) - self._hard_constraints[constraint] = value - - def add_soft_constraint(self, constraint: str, value: Any): - """Add constraints to the object model.""" - gkeys = self.DEFAULT_SOFT_CONSTRAINTS.keys() - if constraint not in gkeys: - raise KeyError( - f"Invalid object constraint key '{constraint}', allowed keys are {gkeys}" - ) - self._soft_constraints[constraint] = value - - def apply_hard_constraints( - self, - obj: torch.Tensor, - ) -> torch.Tensor: - """ - Apply constraints to the object model. - """ - obj2 = obj.clone() - if self.hard_constraints["positivity"]: - obj2 = torch.clamp(obj, min=0.0, max=None) - if self.hard_constraints["shrinkage"]: - obj2 = torch.max(obj2 - self.hard_constraints["shrinkage"], torch.zeros_like(obj2)) - - return obj2 - - def apply_soft_constraints( - self, - obj: torch.Tensor, - ) -> torch.Tensor: - """ - 'Applies' soft constraints to the object model. This will return additional loss terms. - """ - soft_loss = torch.tensor(0.0, device=obj.device, dtype=obj.dtype, requires_grad=True) - if self.soft_constraints["tv_vol"] > 0: - tv_loss = get_TV_loss( - obj.unsqueeze(0).unsqueeze(0), factor=self.soft_constraints["tv_vol"] - ) - - soft_loss += tv_loss - - return soft_loss - - -class ObjectVoxelwise(ObjectConstraints): - """ - Object model for voxelwise objects. - """ - - def __init__( - self, - volume_shape: tuple[int, int, int], - device: str, - initial_volume: torch.Tensor | None = None, - ): - super().__init__( - volume_shape=volume_shape, - device=device, - ) - self.hard_constraints = self.DEFAULT_HARD_CONSTRAINTS.copy() - self.soft_constraints = self.DEFAULT_SOFT_CONSTRAINTS.copy() - - if initial_volume is not None: - self._initial_obj = initial_volume - else: - self.initial_obj = ( - torch.zeros(self._shape, device=self._device, dtype=torch.float32) - + self.offset_obj - ) - - @property - def obj(self): - return self.apply_hard_constraints(self._obj) - - @obj.setter - def obj(self, obj: torch.Tensor): - self._obj = obj - - @property - def initial_obj(self): - return self._initial_obj - - @initial_obj.setter - def initial_obj(self, initial_obj: torch.Tensor): - if not isinstance(initial_obj, torch.Tensor): - raise ValueError("initial_obj must be a torch.Tensor") - - self._initial_obj = initial_obj - - def forward(self): - return self.obj - - def reset(self): - self._obj = ( - torch.zeros(self._shape, device=self._device, dtype=torch.float32) + self.offset_obj - ) - - def to(self, device: str): - self._device = device - self._obj = self._obj.to(self._device) - - @property - def name(self) -> str: - return "ObjectVoxelwise" - - @property - def params(self) -> torch.Tensor: - return self._obj - - @property - def soft_loss(self) -> torch.Tensor: - return self.apply_soft_constraints(self._obj) - - -class ObjectDIP(ObjectConstraints): - """ - Object model for DIP objects. - """ - - def __init__( - self, - model: torch.nn.Module, - volume_shape: tuple[int, int, int], - model_input: torch.Tensor - | None = None, # Determines output size, model input pretraining target - input_noise_std: float = 0.0, - device: str = "cpu", - ): - super().__init__( - volume_shape=volume_shape, - device=device, - ) - self.hard_constraints = self.DEFAULT_HARD_CONSTRAINTS.copy() - self.soft_constraints = self.DEFAULT_SOFT_CONSTRAINTS.copy() - - if model_input is None: - self.model_input = torch.randn(1, 1, volume_shape[0], volume_shape[1], volume_shape[2]) - else: - self.model_input = model_input.clone().detach() - - self.pretrain_target = model_input.clone().detach() - - self._model = model - self._optimizer = None - self._scheduler = None - self._pretrain_losses = [] - self._pretrain_lrs = [] - self._model_input_noise_std = input_noise_std - - @property - def name(self) -> str: - return "ObjectDIP" - - @property - def model(self) -> torch.nn.Module: - return self._model - - @model.setter - def model(self, model: torch.nn.Module): - if not isinstance(model, torch.nn.Module): - raise TypeError(f"Model must be a torch.nn.Module, got {type(model)}") - self._model = model.to(self._device) - self.set_pretrained_weights(self._model) - - @property - def pretrained_weights(self) -> dict[str, torch.Tensor]: - return self._pretrained_weights - - def set_pretrained_weights(self, model: torch.nn.Module): - if not isinstance(model, torch.nn.Module): - raise TypeError(f"Pretrained model must be a torch.nn.Module, got {type(model)}") - self._pretrained_weights = deepcopy(model.state_dict()) - - @property - def model_input(self) -> torch.Tensor: - return self._model_input - - @model_input.setter - def model_input(self, input_tensor: torch.Tensor): - inp = validate_tensor( - input_tensor, - name="model_input", - dtype=torch.float32, - ndim=5, - expand_dims=True, - ) - self._model_input = inp.to(self._device) - - @property - def pretrain_target(self) -> torch.Tensor: - return self._pretrain_target - - @pretrain_target.setter - def pretrain_target(self, target: torch.Tensor): - if target.ndim == 5: - target = target.squeeze(0).squeeze(0) - - target = validate_tensor( - target, - name="pretrain_target", - ndim=3, - dtype=torch.float32, - expand_dims=True, - ) - if target.shape[-3:] != self.model_input.shape[-3:]: - raise ValueError( - f"Pretrain target shape {target.shape} does not match model input shape {self.model_input.shape}" - ) - self._pretrain_target = target.to(self._device) - - @property - def _model_input_noise_std(self) -> float: - """standard deviation of the gaussian noise added to the model input each forward call""" - return self._input_noise_std - - @_model_input_noise_std.setter - def _model_input_noise_std(self, std: float): - validate_gt(std, 0.0, "input_noise_std", geq=True) - self._input_noise_std = std - - @property - def optimizer(self) -> torch.optim.Optimizer: - """get the optimizer for the DIP model""" - if self._optimizer is None: - raise ValueError("Optimizer is not set. Use set_optimizer() to set it.") - return self._optimizer - - def set_optimizer(self, opt_params: dict): - opt_type = opt_params.pop("type") - if isinstance(opt_type, torch.optim.Optimizer): - self._optimizer = opt_type - elif isinstance(opt_type, type): - self._optimizer = opt_type(self.model.parameters(), **opt_params) - elif opt_type == "adam": - self._optimizer = torch.optim.Adam(self.model.parameters(), **opt_params) - elif opt_type == "adamw": - self._optimizer = torch.optim.AdamW(self.model.parameters(), **opt_params) - elif opt_type == "sgd": - self._optimizer = torch.optim.SGD(self.model.parameters(), **opt_params) - else: - raise NotImplementedError(f"Unknown optimizer type: {opt_params['type']}") - - @property - def scheduler( - self, - ) -> ( - torch.optim.lr_scheduler._LRScheduler - | torch.optim.lr_scheduler.CyclicLR - | torch.optim.lr_scheduler.ReduceLROnPlateau - | torch.optim.lr_scheduler.ExponentialLR - | None - ): - return self._scheduler - - def set_scheduler(self, params: dict, num_iter: int | None = None) -> None: - sched_type: str = params["type"].lower() - optimizer = self.optimizer - base_LR = optimizer.param_groups[0]["lr"] - if sched_type == "none": - scheduler = None - elif sched_type == "cyclic": - scheduler = torch.optim.lr_scheduler.CyclicLR( - optimizer, - base_lr=params.get("base_lr", base_LR / 4), - max_lr=params.get("max_lr", base_LR * 4), - step_size_up=params.get("step_size_up", 100), - mode=params.get("mode", "triangular2"), - cycle_momentum=params.get("momentum", False), - ) - elif sched_type.startswith(("plat", "reducelronplat")): - scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau( - optimizer, - mode="min", - factor=params.get("factor", 0.5), - patience=params.get("patience", 10), - threshold=params.get("threshold", 1e-3), - min_lr=params.get("min_lr", base_LR / 20), - cooldown=params.get("cooldown", 20), - ) - elif sched_type in ["exp", "gamma", "exponential"]: - if "gamma" in params.keys(): - gamma = params["gamma"] - elif num_iter is not None: - fac = params.get("factor", 0.01) - gamma = fac ** (1.0 / num_iter) - else: - gamma = 0.999 - scheduler = torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma=gamma) - else: - raise ValueError(f"Unknown scheduler type: {sched_type}") - self._scheduler = scheduler - - @property - def pretrain_losses(self) -> np.ndarray: - return np.array(self._pretrain_losses) - - @property - def pretrain_lrs(self) -> np.ndarray: - return np.array(self._pretrain_lrs) - - @property - def obj(self): - obj = self.model(self._model_input)[0] - return self.apply_hard_constraints(obj) - - def forward(self): - return self.model(self._model_input) - - def to(self, device: str): - self.device = device - self._model = self._model.to(self.device) - self._model_input = self._model_input.to(self.device) - self._pretrain_target = self._pretrain_target.to(self.device) - - @property - def params(self): - return self._model.parameters() - - def reset(self): - self.model.load_state_dict(self.pretrained_weights.copy()) - - def pretrain( - self, - model_input: torch.Tensor, - pretrain_target: torch.Tensor, - reset: bool = True, - num_epochs: int = 100, - optimizer_params: dict | None = None, - scheduler_params: dict | None = None, - loss_fn: Callable | str = "l2", - apply_constraints: bool = False, - show: bool = True, - ): - model_input.to(self.device) - pretrain_target.to(self.device) - - if optimizer_params is not None: - self.set_optimizer(optimizer_params) - - if scheduler_params is not None: - self.set_scheduler(scheduler_params, num_epochs) - - if reset: - self._model.apply(reset_weights) - self._pretrain_losses = [] - self._pretrain_lrs = [] - - if model_input is not None: - self.model_input = model_input - - if pretrain_target.shape[-3:] != self.model_input.shape[-3:]: - raise ValueError( - f"Pretrain target shape {pretrain_target.shape} does not match model input shape {self.model_input.shape}" - ) - self.pretrain_target = pretrain_target.clone().detach().to(self.device) - - loss_fn = torch.nn.functional.mse_loss - - self._pretrain( - num_epochs=num_epochs, - loss_fn=loss_fn, - apply_constraints=apply_constraints, - show=show, - ) - self.set_pretrained_weights(self.model) - - def _pretrain( - self, - num_epochs: int, - loss_fn: Callable, - apply_constraints: bool = False, - show: bool = False, - ): - if not hasattr(self, "pretrain_target"): - raise ValueError("Pretrain target is not set. Use pretrain_target to set it.") - - self.model.train() - optimizer = self.optimizer - sch = self.scheduler - pbar = tqdm(range(num_epochs)) - output = self.obj - - for a0 in pbar: - if apply_constraints: - output = self.obj - else: - output = self.model(self.model_input).squeeze(0).squeeze(0) - - loss = loss_fn(output, self.pretrain_target) - loss.backward() - optimizer.step() - optimizer.zero_grad() - - if sch is not None: - if isinstance(sch, torch.optim.lr_scheduler.ReduceLROnPlateau): - sch.step(loss.item()) - else: - sch.step() - - self._pretrain_losses.append(loss.item()) - self._pretrain_lrs.append(optimizer.param_groups[0]["lr"]) - pbar.set_description(f"Epoch {a0 + 1}/{num_epochs}, Loss: {loss.item():.4f}, ") - - - -# INR stuff - - - - -class ObjectINN(ObjectConstraints): - """ - Object model for INN objects. - - - VolumeDataset (?) dependent on the object - """ - - def __init__( - self, - model: nn.Module, - volume_shape = tuple[int, int, int], - device: str = "cuda", - ): - - super().__init__( - volume_shape = volume_shape, - device = device, - offset_obj = 0, - ) - - self._model = model - - - - # --- Properties --- - @property - def obj(self): - - return self._obj - - def create_volume( - self, - world_size: int, - global_rank: int, - ray_size: int, - ): - N = max(self._shape) - with torch.no_grad(): - coords_1d = torch.linspace(-1, 1, N) - x, y, z = torch.meshgrid(coords_1d, coords_1d, coords_1d, indexing='ij') - inputs = torch.stack([x, y, z], dim=-1).reshape(-1, 3) - - # Underlying model if using DDP - model = self.model.module if hasattr(self.model, 'module') else self.model - - # Batch size for inference - # 5x larger batches no gradients needed - - inference_batch_size = 5 * N * ray_size - - # Distribute work across GPUs - total_samples = N**3 - samples_per_gpu = total_samples // world_size - remainder = total_samples % world_size - - # Handle uneven distribution - if global_rank < remainder: - start_idx = global_rank * (samples_per_gpu + 1) - end_idx = start_idx + samples_per_gpu + 1 - else: - start_idx = global_rank * samples_per_gpu + remainder - end_idx = start_idx + samples_per_gpu - - inputs_subset = inputs[start_idx:end_idx] - num_samples = inputs_subset.shape[0] - outputs_list = [] - - for batch_start in range(0, num_samples, inference_batch_size): - batch_end = min(batch_start + inference_batch_size, num_samples) - batch_coords = inputs_subset[batch_start:batch_end].to(self.device, non_blocking=True) - - batch_outputs = model(batch_coords) - if batch_outputs.dim() > 1: - batch_outputs = batch_outputs.squeeze(-1) - - outputs_list.append(batch_outputs.cpu()) - - outputs = torch.cat(outputs_list, dim=0) - - if world_size > 1: - # Gather from all ranks - # handle potentially different sizes - output_size = torch.tensor(outputs.shape[0], device=self.device, dtype=torch.long) - all_sizes = [torch.zeros(1, device=self.device, dtype=torch.long) for _ in range(world_size)] - dist.all_gather(all_sizes, output_size) - - # Create gather list with correct sizes - max_size = max(size.item() for size in all_sizes) - - # Pad if necessary for gathering - if outputs.shape[0] < max_size: - padding = torch.zeros(max_size - outputs.shape[0], device=outputs.device, dtype=outputs.dtype) - outputs_padded = torch.cat([outputs, padding], dim=0).to(self.device) - else: - outputs_padded = outputs.to(self.device) - - gathered_outputs = [torch.empty(max_size, device=self.device, dtype=outputs.dtype) - for _ in range(world_size)] - dist.all_gather(gathered_outputs, outputs_padded.contiguous()) - - # Trim padding and concatenate - trimmed_outputs = [] - for rank, size in enumerate(all_sizes): - trimmed_outputs.append(gathered_outputs[rank][:size.item()]) - - pred_full = torch.cat(trimmed_outputs, dim=0).reshape(N, N, N).float() - else: - pred_full = outputs.reshape(N, N, N).float() - - - self._obj = pred_full.detach().cpu() - - - @property - def model(self): - - return self._model - - @model.setter - def model(self, model): - - self._model = model - - - def apply_soft_constraints( - self, - coords: torch.Tensor, - ): - - soft_loss = torch.tensor(0.0, device = coords.device) - if self.soft_constraints["tv_vol"] > 0: - - num_tv_samples = min(10000, coords.shape[0]) - tv_indices = torch.randperm(coords.shape[0], device = coords.device)[:num_tv_samples] - - # Rerun forward for gradient tracking - tv_coords = coords[tv_indices].detach().requires_grad_(True) - - tv_densities_recomputed = self.model(tv_coords) - if tv_densities_recomputed.dim() > 1: - tv_densities_recomputed = tv_densities_recomputed.squeeze(-1) - - # Compute gradients - grad_outputs = torch.autograd.grad( - outputs=tv_densities_recomputed, - inputs=tv_coords, - grad_outputs=torch.ones_like(tv_densities_recomputed), - create_graph=True - )[0] - - grad_norm = torch.norm(grad_outputs, dim = 1) - soft_loss += self.soft_constraints["tv_vol"] * grad_norm.mean() - - return soft_loss - - def forward( - self, - all_coords: torch.Tensor, - ): - - all_densities = self.model(all_coords) - - if all_densities.dim() > 1: - all_densities = all_densities.squeeze(-1) - - valid_mask = ( - (all_coords[:, 0] >= -1) & (all_coords[:, 0] <= 1) & - (all_coords[:, 1] >= -1) & (all_coords[:, 1] <= 1) & - (all_coords[:, 2] >= -1) & (all_coords[:, 2] <= 1) - ).float() - - all_densities = all_densities * valid_mask - - return all_densities - - - - -ObjectModelType = ObjectVoxelwise | ObjectINN # | ObjectDIP | ObjectImplicit (ObjectFFN?) - - -# Pretrain Volume Dataset - - - \ No newline at end of file diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 5064cd3f..79525364 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -1,886 +1,16 @@ -import matplotlib.pyplot as plt -import numpy as np -import torch -import torch.distributed as dist -from torch.nn import SmoothL1Loss -from torch.utils.tensorboard import SummaryWriter - -# from torch_radon.radon import ParallelBeam as Radon -from tqdm.auto import tqdm - -from quantem.core.ml.loss_functions import ( - CharbonnierLoss, - L1Loss, - LLMSELoss, - MSELogMSELoss, - MSELoss, -) - -# Temporary imports for TomographyNERF -from quantem.tomography.object_models import ObjectINN, ObjectVoxelwise -from quantem.tomography.tomography_base import TomographyBase -from quantem.tomography.tomography_conv import TomographyConv -from quantem.tomography.tomography_ddp import ( - AdaptiveSmoothL1LossDDP, - PretrainVolumeDataset, - TomographyDDP, -) -from quantem.tomography.tomography_ml import TomographyML -from quantem.tomography.utils import differentiable_shift_2d, gaussian_kernel_1d, rot_ZXZ - - -# Temporary aux class for TomographyNERF -# TODO: Maybe put this in INN? -def get_num_samples_per_ray(N: int, epoch: int): - """Increase number of samples per ray at specific epochs.""" - # Exponential schedule - - epochs = np.linspace(0, 10, 5, dtype=int) - # schedule = np.linspace(20, N, 5, dtype=int) - schedule = np.array([20, 100, 150, 200, 200]) - # schedule = np.array([200, 200, 200, 200, 200]) - # schedule = np.array([20, 100, 250, 500, 500]) - # schedule = np.array([500, 500, 500, 500, 500]) - # schedule = np.array([300, 300, 300, 300, 300]) - # schedule = np.exp(schedule) - schedule_warmup = dict[int, int](zip(epochs, schedule)) - - for epoch_threshold, samples in schedule_warmup.items(): - if epoch >= epoch_threshold: - num_samples = samples - - return num_samples - - -class Tomography(TomographyConv, TomographyML, TomographyBase, TomographyDDP): +class Tomography: """ - Top level class for either using conventional or ML-based reconstruction methods - for tomography. + Class for handling all ML tomography reconstruction methods. + Automatic handling between AD and INR-based tomography. """ - def __init__( - self, - dataset, - volume_obj, - device, - _token, - ): - super().__init__(dataset, volume_obj, device, _token) - - # TODO: More elegant way of doing this. - self.global_epochs = 0 - self.ddp_instantiated = False - self.pretraining_instantiated = False - - # --- Reconstruction Method --- - - def sirt_recon( - self, - num_iterations: int = 10, - inline_alignment: bool = False, - enforce_positivity: bool = True, - volume_shape: tuple = None, - reset: bool = True, - smoothing_sigma: float = None, - shrinkage: float = None, - filter_name: str = "hamming", - circle: bool = True, - plot_loss: bool = False, - ): - num_angles, num_rows, num_cols = self.dataset.tilt_series.shape - sirt_tilt_series = self.dataset.tilt_series.clone() - sirt_tilt_series = sirt_tilt_series.permute(2, 0, 1) - - hard_constraints = { - "positivity": enforce_positivity, - "shrinkage": shrinkage, - } - self.volume_obj.hard_constraints = hard_constraints - - if volume_shape is None: - volume_shape = (num_rows, num_rows, num_rows) - else: - D, H, W = volume_shape - - if reset: - self.volume_obj.reset() - self.loss = [] - - proj_forward = torch.zeros_like(self.dataset.tilt_series) - - pbar = tqdm(range(num_iterations), desc="SIRT Reconstruction") - - if smoothing_sigma is not None: - gaussian_kernel = gaussian_kernel_1d(smoothing_sigma).to(self.device) - else: - gaussian_kernel = None - - print( - "Devices", - sirt_tilt_series.device, - proj_forward.device, - self.dataset.tilt_angles.device, - ) - - for iter in pbar: - proj_forward, loss = self._sirt_run_epoch( - tilt_series=sirt_tilt_series, - proj_forward=proj_forward, - angles=self.dataset.tilt_angles, - inline_alignment=iter > 0 and inline_alignment, - filter_name=filter_name, - gaussian_kernel=gaussian_kernel, - circle=circle, - ) - - pbar.set_description(f"SIRT Reconstruction | Loss: {loss.item():.4f}") - - self.loss.append(loss.item()) - - self.sirt_recon_vol = self.volume_obj - - # Permutation due to sinogram ordering. - self.sirt_recon_vol.obj = self.sirt_recon_vol.obj.permute(1, 2, 0) - - if plot_loss: - self.plot_loss() - - # TODO: ML Recon which has NeRF and AD depending on the object type. - # TODO: Temporary - - def create_batch_projection_rays(self, pixel_i, pixel_j, N, num_samples_per_ray): - """Create projection rays for entire batch simultaneously.""" - batch_size = len(pixel_i) - - # Convert all pixels to normalized coordinates - x_coords = (pixel_j / (N - 1)) * 2 - 1 - y_coords = (pixel_i / (N - 1)) * 2 - 1 - # TODO: maybe pixel_j.device? - # Create z coordinates - z_coords = torch.linspace(-1, 1, num_samples_per_ray, device=self.device) - - # Create rays for all pixels: [batch_size, num_samples_per_ray, 3] - rays = torch.zeros(batch_size, num_samples_per_ray, 3, device=self.device) - - # Fill coordinates efficiently - rays[:, :, 0] = x_coords.unsqueeze(1) # x constant per ray - rays[:, :, 1] = y_coords.unsqueeze(1) # y constant per ray - rays[:, :, 2] = z_coords.unsqueeze(0) # z varies along ray - - return rays - - @torch.compile(mode="reduce-overhead") - def transform_batch_ray_coordinates(self, rays, z1, x, z3, shifts, N, sampling_rate): - # Step 1: Apply shifts - shift_x_norm = (shifts[:, 0:1] * sampling_rate * 2) / (N - 1) - shift_y_norm = (shifts[:, 1:2] * sampling_rate * 2) / (N - 1) - - rays_x = rays[:, :, 0] - shift_x_norm - rays_y = rays[:, :, 1] - shift_y_norm - rays_z = rays[:, :, 2] - - # Rotation 1: Z(-z3) - theta = torch.deg2rad(-z3).view(-1, 1) - cos_t = torch.cos(theta) - sin_t = torch.sin(theta) - - rays_x_rot1 = cos_t * rays_x - sin_t * rays_y - rays_y_rot1 = sin_t * rays_x + cos_t * rays_y - rays_z_rot1 = rays_z - - # Rotation 2: X(x) - theta = torch.deg2rad(x).view(-1, 1) - cos_t = torch.cos(theta) - sin_t = torch.sin(theta) - - rays_x_rot2 = rays_x_rot1 - rays_y_rot2 = cos_t * rays_y_rot1 - sin_t * rays_z_rot1 - rays_z_rot2 = sin_t * rays_y_rot1 + cos_t * rays_z_rot1 - - # Rotation 3: Z(-z1) - theta = torch.deg2rad(-z1).view(-1, 1) - cos_t = torch.cos(theta) - sin_t = torch.sin(theta) - - rays_x_final = cos_t * rays_x_rot2 - sin_t * rays_y_rot2 - rays_y_final = sin_t * rays_x_rot2 + cos_t * rays_y_rot2 - rays_z_final = rays_z_rot2 - - # Stack the final result - transformed_rays = torch.stack([rays_x_final, rays_y_final, rays_z_final], dim=2) - - return transformed_rays - - # TODO: Temp logger - def setup_logger(self, log_path): - if self.global_rank == 0: - self.temp_logger = SummaryWriter(log_dir=log_path) - else: - self.temp_logger = None - - def pretrain( - self, - volume_dataset: PretrainVolumeDataset, - obj: ObjectINN, - batch_size: int, - soft_constraints: dict = None, - log_path: str = None, - optimizer_params: dict = None, - epochs: int = 100, - viz_freq: int = 1, - consistency_criterion: str = "mse", - ): - if not self.pretraining_instantiated: - self.setup_distributed() - self.setup_pretraining_dataloader(volume_dataset, batch_size) - self.pretraining_instantiated = True - obj.model = self.build_model(obj._model) - self.obj = obj - - if soft_constraints is not None: - self.obj.soft_constraints = soft_constraints - - if not hasattr(self, "temp_logger"): - self.setup_logger(log_path=log_path) - - if optimizer_params is not None: - optimizer_params = self.scale_lr(optimizer_params) - self.optimizer_params = optimizer_params - self.set_optimizers() - device_type = self.device.type - consistency_loss_fn = None - if consistency_criterion[0].lower() == "mse": - consistency_loss_fn = MSELoss() - elif consistency_criterion[0].lower() == "l1": - consistency_loss_fn = L1Loss() - elif consistency_criterion[0].lower() == "mse_log": - consistency_loss_fn = MSELogMSELoss() - elif consistency_criterion[0].lower() == "llmse": - consistency_loss_fn = LLMSELoss() - elif consistency_criterion[0].lower() == "smooth_l1": - consistency_loss_fn = SmoothL1Loss(beta=consistency_criterion[1]) - elif consistency_criterion[0].lower() == "adaptive_smooth_l1": - consistency_loss_fn = AdaptiveSmoothL1LossDDP( - beta_init=consistency_criterion[1], ema_factor=0.99, eps=1e-8 - ) - elif consistency_criterion[0].lower() == "charbonnier": - consistency_loss_fn = CharbonnierLoss(epsilon=1e-12, reduction="mean") - else: - raise ValueError(f"Invalid consistency criterion: {consistency_criterion}") - - for epoch in range(epochs): - if self.pretraining_sampler is not None: - self.pretraining_sampler.set_epoch(epoch) - - self.obj.model.train() - - epoch_loss = 0.0 - epoch_consistency_loss = 0.0 - epoch_tv_loss = 0.0 - num_batches = 0 - - for batch_idx, batch in enumerate(self.pretraining_dataloader): - coords = batch["coords"].to(self.device, non_blocking=True) - target = batch["target"].to(self.device, non_blocking=True) - - for _, opt in self.optimizers.items(): - opt.zero_grad() - - with torch.autocast(device_type=device_type, dtype=torch.bfloat16, enabled=True): - outputs = self.obj.forward(coords) - consistency_loss = consistency_loss_fn(outputs, target) - tv_loss = self.obj.apply_soft_constraints(coords) - - loss = consistency_loss + tv_loss - - loss.backward() - - epoch_loss += loss.detach() - epoch_consistency_loss += consistency_loss.detach() - epoch_tv_loss += tv_loss.detach() - - torch.nn.utils.clip_grad_norm_(self.obj.model.parameters(), max_norm=1.0) - - for _, opt in self.optimizers.items(): - opt.step() - epoch_loss += loss.detach() - num_batches += 1 - - if epoch % viz_freq == 0 or epoch == epochs - 1: - avg_loss = epoch_loss / num_batches - with torch.no_grad(): - self.obj.create_volume( - world_size=self.world_size, - global_rank=self.global_rank, - ray_size=volume_dataset.N, - ) - pred_full = self.obj.obj - loss_tensor = avg_loss.clone().detach() - avg_loss = loss_tensor.item() - avg_tv_loss = epoch_tv_loss / num_batches - avg_consistency_loss = epoch_consistency_loss / num_batches - - metrics = torch.tensor( - [avg_loss, avg_consistency_loss, avg_tv_loss], device=self.device - ) - if self.world_size > 1: - dist.all_reduce(metrics, op=dist.ReduceOp.AVG) - avg_loss, avg_consistency_loss, avg_tv_loss = metrics.tolist() - - if self.global_rank == 0: - self.temp_logger.add_scalar("Pretrain/total_loss", avg_loss, epoch) - self.temp_logger.add_scalar( - "Pretrain/consistency_loss", avg_consistency_loss, epoch - ) - self.temp_logger.add_scalar("Pretrain/tv_loss", avg_tv_loss, epoch) - - # current_lr = self.schedulers["model"].get_last_lr()[0] - # self.temp_logger.add_scalar("Pretrain/lr", current_lr, epoch) - - fig, ax = plt.subplots(ncols=4, figsize=(36, 12)) - ax[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap="turbo", vmin=0) - ax[0].set_title("Sum over Z-axis") - ax[1].matshow( - pred_full[volume_dataset.N // 2].cpu().numpy(), cmap="turbo", vmin=0 - ) - ax[1].set_title(f"Slice at Z={volume_dataset.N // 2}") - ax[2].matshow(pred_full.sum(dim=1).cpu().numpy(), cmap="turbo", vmin=0) - ax[2].set_title("Sum over Y-axis") - ax[3].matshow(pred_full.sum(dim=2).cpu().numpy(), cmap="turbo", vmin=0) - ax[3].set_title("Sum over X-axis") - self.temp_logger.add_figure("Pretrain/viz", fig, epoch, close=True) - - plt.close(fig) - print( - f"Epoch [{epoch}/{epochs}] Pretrain Total Loss: {avg_loss:.6f}, Consistency Loss: {avg_consistency_loss:.6f}, TV Loss: {avg_tv_loss:.6f}" - ) - self.global_epochs += 1 - if self.global_rank == 0: - print(f"Pretraining Completed, saving model weights to {log_path}/model_weights.pth") - torch.save(self.obj.model.state_dict(), f"{log_path}/model_weights.pth") - - def recon( - self, - obj: ObjectINN, - batch_size: int, - num_workers: int = 0, - epochs=20, - use_amp=True, - viz_freq=1, - checkpoint_freq=5, - optimizer_params: dict = None, - scheduler_params: dict = None, - soft_constraints: dict = None, - vol_save_path: str = None, # TODO: TEMPORARY - log_path=None, - val_fraction: float = 0.0, - learn_shifts: bool = True, - # l1_loss: bool = False, - consistency_criterion: str = "mse", - model_weights_path: str = None, - force_cpu: bool = False, - ): - if not self.ddp_instantiated: - if not self.pretraining_instantiated: - self.setup_distributed() - if model_weights_path is not None: - print(f"Loading model weights from {model_weights_path}") - state_dict = torch.load(model_weights_path, map_location="cpu") - - # Handle DataParallel/DDP checkpoints - if any(k.startswith("module.") for k in state_dict.keys()): - new_state_dict = { - k.replace("module.", ""): v for k, v in state_dict.items() - } - else: - new_state_dict = state_dict - - obj.model.load_state_dict(new_state_dict) - print("Model weights loaded successfully") - obj.model = self.build_model(obj._model) - self.obj = obj - - self.setup_dataloader(self.dataset, batch_size, num_workers, val_fraction) - self.ddp_instantiated = True - - if soft_constraints is not None: - self.obj.soft_constraints = soft_constraints - - if not hasattr(self, "temp_logger"): - self.setup_logger(log_path=log_path) - - zero_tilt_idx = torch.argmin(torch.abs(self.dataset.tilt_angles)).item() - - if self.global_rank == 0: - print( - f"Using projection {zero_tilt_idx} (angle={self.dataset.tilt_angles[zero_tilt_idx]:.2f}°) as reference" - ) - print(f"Using consistency criterion: {consistency_criterion}") - - # Auxiliary params setup - self.dataset.setup_auxiliary_params(zero_tilt_idx, self.device, learn_shifts) - aux_params = self.dataset.auxiliary_params - # Scaling learning rates to account for distributed training - if optimizer_params is not None: - optimizer_params = self.scale_lr(optimizer_params) - self.optimizer_params = optimizer_params - self.set_optimizers() - - if scheduler_params is not None: - self.scheduler_params = scheduler_params - self.set_schedulers(self.scheduler_params, num_iter=epochs) - - aux_norm = torch.tensor(0.0, device=self.device) - model_norm = torch.tensor(0.0, device=self.device) - - for _, opt in self.optimizers.items(): - opt.zero_grad() - - N = max(self.dataset.dims) - - device_type = self.device.type - autocast_dtype = torch.bfloat16 if use_amp else None - - for epoch in range(epochs): - num_samples_per_ray = get_num_samples_per_ray(N=N, epoch=self.global_epochs) + pass - # num_samples_per_ray = get_num_samples_per_ray(epoch) - # Log the change if it happens - if self.global_rank == 0: - print(f"Epoch {epoch}: num_samples_per_ray = {num_samples_per_ray}") - if self.global_rank == 0 and self.global_epochs > 0: - prev_samples = get_num_samples_per_ray(N=N, epoch=self.global_epochs - 1) - - if num_samples_per_ray != prev_samples: - print( - f"Epoch {epoch}: Changing num_samples_per_ray from {prev_samples} to {num_samples_per_ray}" - ) - - if self.sampler is not None: - self.sampler.set_epoch(epoch) - - epoch_loss = 0.0 - epoch_consistency_loss = 0.0 - epoch_tv_loss = 0.0 - epoch_z1_loss = 0.0 - - num_batches = 0 - consistency_loss_fn = None - if consistency_criterion[0].lower() == "mse": - consistency_loss_fn = MSELoss() - elif consistency_criterion[0].lower() == "l1": - consistency_loss_fn = L1Loss() - elif consistency_criterion[0].lower() == "mse_log": - consistency_loss_fn = MSELogMSELoss() - elif consistency_criterion[0].lower() == "llmse": - consistency_loss_fn = LLMSELoss() - elif consistency_criterion[0].lower() == "smooth_l1": - consistency_loss_fn = SmoothL1Loss(beta=consistency_criterion[1]) - elif consistency_criterion[0].lower() == "adaptive_smooth_l1": - consistency_loss_fn = AdaptiveSmoothL1LossDDP( - beta_init=consistency_criterion[1], ema_factor=0.99, eps=1e-8 - ) - elif consistency_criterion[0].lower() == "charbonnier": - consistency_loss_fn = CharbonnierLoss(epsilon=1e-12, reduction="mean") - else: - raise ValueError(f"Invalid consistency criterion: {consistency_criterion}") - - for batch_idx, batch in enumerate(self.dataloader): - projection_indices = batch["projection_idx"] - - pixel_i = batch["pixel_i"].float().to(self.device, non_blocking=True) - pixel_j = batch["pixel_j"].float().to(self.device, non_blocking=True) - target_values = batch["target_value"].to(self.device, non_blocking=True) - phis = batch["phi"].to(self.device, non_blocking=True) - projection_indices = batch["projection_idx"].to(self.device, non_blocking=True) - - shifts, z1_params, z3_params = aux_params(None) - batch_shifts = torch.index_select(shifts, 0, projection_indices) - batch_z1 = torch.index_select(z1_params, 0, projection_indices) - batch_z3 = torch.index_select(z3_params, 0, projection_indices) - - with torch.autocast( - device_type=device_type, dtype=autocast_dtype, enabled=use_amp - ): - with torch.no_grad(): - batch_ray_coords = self.create_batch_projection_rays( - pixel_i, pixel_j, N, num_samples_per_ray - ) - - # TODO: .forward passing z1, x, z3, shifts, N, sampling_rate - transformed_rays = self.transform_batch_ray_coordinates( - batch_ray_coords, - z1=batch_z1, - x=phis, - z3=batch_z3, - shifts=batch_shifts, - N=N, - sampling_rate=1.0, - ) - - all_coords = transformed_rays.view(-1, 3) - - all_densities = self.obj.forward(all_coords) - - tv_loss = self.obj.apply_soft_constraints(all_coords) - ray_densities = all_densities.view( - len(target_values), num_samples_per_ray - ) # Reshape rays and integarte - step_size = 2.0 / (num_samples_per_ray - 1) - - predicted_values = ray_densities.sum(dim=1) * step_size - - consistency_loss = consistency_loss_fn(predicted_values, target_values) - tv_loss_z1 = torch.tensor(0.0, device=self.device) - - loss = consistency_loss + tv_loss + tv_loss_z1 - - loss.backward() - - epoch_loss += loss.detach() - epoch_consistency_loss += consistency_loss.detach() - epoch_tv_loss += tv_loss.detach() - epoch_z1_loss += tv_loss_z1.detach() - num_batches += 1 - - for key, opt in self.optimizers.items(): - if key == "model": - model_norm = torch.nn.utils.clip_grad_norm_( - self.obj.model.parameters(), max_norm=1 - ) - elif key == "aux_params": - aux_norm = torch.nn.utils.clip_grad_norm_( - self.dataset.auxiliary_params.parameters(), max_norm=1 - ) - opt.step() - opt.zero_grad() - - for key, sched in self.schedulers.items(): - if isinstance(sched, torch.optim.lr_scheduler.ReduceLROnPlateau): - sched.step(epoch_loss) - else: - sched.step() - - if epoch % viz_freq == 0 or epoch == epochs - 1 or epoch % checkpoint_freq == 0: - with torch.no_grad(): - self.obj.create_volume( - world_size=self.world_size, - global_rank=self.global_rank, - ray_size=num_samples_per_ray, - ) - pred_full = self.obj.obj - avg_loss = epoch_loss.item() / num_batches - avg_consistency_loss = epoch_consistency_loss.item() / num_batches - avg_tv_loss = epoch_tv_loss.item() / num_batches - avg_z1_loss = epoch_z1_loss.item() / num_batches - shifts, z1, z3 = self.dataset.auxiliary_params.forward() - shifts = shifts.detach().cpu() - z1 = z1.detach().cpu() - z3 = z3.detach().cpu() - metrics = torch.tensor( - [avg_loss, avg_consistency_loss, avg_tv_loss, avg_z1_loss], - device=self.device, - ) - if self.world_size > 1: - dist.all_reduce(metrics, op=dist.ReduceOp.AVG) - avg_loss, avg_consistency_loss, avg_tv_loss, avg_z1_loss = metrics.tolist() - - if hasattr(self, "val_dataloader") and self.val_dataloader is not None: - val_loss = self.validate( - aux_params, num_samples_per_ray, device_type, autocast_dtype, use_amp, N - ) - - if self.global_rank == 0 and (epoch % viz_freq == 0 or epoch == epochs - 1): - with torch.no_grad(): - current_lr = self.schedulers["model"].get_last_lr()[0] - - # Log metrics - self.temp_logger.add_scalar( - f"train/consistency_loss_{consistency_criterion[0] if isinstance(consistency_criterion, tuple) else consistency_criterion}", - avg_consistency_loss, - self.global_epochs, - ) - if hasattr(self, "val_dataloader") and self.val_dataloader is not None: - self.temp_logger.add_scalar( - f"val/consistency_loss_{consistency_criterion[0] if isinstance(consistency_criterion, tuple) else consistency_criterion}", - val_loss, - self.global_epochs, - ) - self.temp_logger.add_scalar("train/z1_loss", avg_z1_loss, self.global_epochs) - # if tv_weight > 0: - self.temp_logger.add_scalar("train/tv_loss", avg_tv_loss, self.global_epochs) - self.temp_logger.add_scalar("train/total_loss", avg_loss, self.global_epochs) - self.temp_logger.add_scalar( - "train/model_grad_norm", model_norm.item(), self.global_epochs - ) - self.temp_logger.add_scalar( - "train/aux_grad_norm", aux_norm.item(), self.global_epochs - ) - self.temp_logger.add_scalar("train/lr", current_lr, self.global_epochs) - self.temp_logger.add_scalar( - "train/num_samples_per_ray", num_samples_per_ray, self.global_epochs - ) - if consistency_criterion[0].lower() == "adaptive_smooth_l1": - self.temp_logger.add_scalar( - "train/beta_2", consistency_loss_fn.beta2.item(), self.global_epochs - ) - fig, axes = plt.subplots(1, 5, figsize=(36, 12)) - axes[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap="turbo", vmin=0) - axes[0].set_title("Sum over Z-axis") - - axes[1].matshow(pred_full[N // 2].cpu().numpy(), cmap="turbo", vmin=0) - axes[1].set_title(f"Slice at Z={N // 2}") - - slice_start = max(0, N // 2 - 5) - slice_end = min(N, N // 2 + 6) - thick_slice = pred_full[slice_start:slice_end].sum(dim=0).cpu().numpy() - axes[2].matshow(thick_slice, cmap="turbo", vmin=0) - axes[2].set_title(f"Thick slice sum (Z={slice_start}:{slice_end - 1})") - - axes[3].matshow(pred_full.sum(dim=1).cpu().numpy(), cmap="turbo", vmin=0) - axes[3].set_title("Sum over Y-axis") - - axes[4].matshow(pred_full.sum(dim=2).cpu().numpy(), cmap="turbo", vmin=0) - axes[4].set_title("Sum over X-axis") - - self.temp_logger.add_figure("train/viz", fig, self.global_epochs, close=True) - - fig, axes = plt.subplots(ncols=3, figsize=(10, 5)) - axes[0].plot(shifts[:, 0].cpu().numpy(), label="Shifts X") - axes[0].plot(shifts[:, 1].cpu().numpy(), label="Shifts Y") - axes[0].legend() - axes[1].plot(z1.cpu().numpy(), label="Z1") - axes[2].plot(z3.cpu().numpy(), label="Z3") - self.temp_logger.add_figure( - "train/auxiliary_params", fig, self.global_epochs, close=True - ) - plt.close(fig) - - if epoch % checkpoint_freq == 0 or self.global_epochs == epochs - 1: - with torch.no_grad(): - if self.global_rank == 0: - save_path = f"{vol_save_path}/volume_epoch_{self.global_epochs:04d}.pt" - torch.save(pred_full.cpu(), save_path) - - self.global_epochs += 1 - - if self.global_rank == 0: - print("Training complete.") - - torch.save(self.dataset.auxiliary_params.forward(), f"{log_path}/auxiliary_params.pt") - # print("Successfully setup DDP and dataloader") - - def validate(self, aux_params, num_samples_per_ray, device_type, autocast_dtype, use_amp, N): - """Validate the model on the validation set.""" - self.obj.model.eval() - aux_params.eval() - - val_loss = 0.0 - num_batches = 0 - - with torch.no_grad(): - for batch in self.val_dataloader: - pixel_i = batch["pixel_i"].float().to(self.device, non_blocking=True) - pixel_j = batch["pixel_j"].float().to(self.device, non_blocking=True) - target_values = batch["target_value"].to(self.device, non_blocking=True) - phis = batch["phi"].to(self.device, non_blocking=True) - projection_indices = batch["projection_idx"].to(self.device, non_blocking=True) - - shifts, z1_params, z3_params = aux_params(None) - batch_shifts = torch.index_select(shifts, 0, projection_indices) - batch_z1 = torch.index_select(z1_params, 0, projection_indices) - batch_z3 = torch.index_select(z3_params, 0, projection_indices) - - with torch.autocast( - device_type=device_type, dtype=autocast_dtype, enabled=use_amp - ): - with torch.no_grad(): - batch_ray_coords = self.create_batch_projection_rays( - pixel_i, pixel_j, N, num_samples_per_ray - ) - - transformed_rays = self.transform_batch_ray_coordinates( - batch_ray_coords, - z1=batch_z1, - x=phis, - z3=batch_z3, - shifts=batch_shifts, - N=N, - sampling_rate=1.0, - ) - - all_coords = transformed_rays.view(-1, 3) - - all_densities = self.obj.forward(all_coords) - - ray_densities = all_densities.view( - len(target_values), num_samples_per_ray - ) # Reshape rays and integarte - step_size = 2.0 / (num_samples_per_ray - 1) - - predicted_values = ray_densities.sum(dim=1) * step_size - - mse_loss = torch.nn.functional.mse_loss(predicted_values, target_values) - - loss = mse_loss - - val_loss += loss.detach() - num_batches += 1 - - avg_val_loss = val_loss / num_batches if num_batches > 0 else 0.0 - - if self.world_size > 1: - val_loss_tensor = avg_val_loss.detach().clone() - dist.all_reduce(val_loss_tensor, op=dist.ReduceOp.AVG) - avg_val_loss = val_loss_tensor.item() - - self.obj.model.train() - aux_params.train() - - return avg_val_loss - - def ad_recon( - self, - optimizer_params: dict, - num_iter: int = 0, - reset: bool = False, - scheduler_params: dict | None = None, - hard_constraints: dict | None = None, - soft_constraints: dict | None = None, - # store_iterations: bool | None = None, - # store_iterations_every: int | None = None, - # autograd: bool = True, - ): - if reset: - self.reset_recon() - - self.hard_constraints = hard_constraints - self.soft_constraints = soft_constraints - - # Make sure everything is in the correct device, might be redundant/cleaner way to do this - self.dataset.to(self.device) - self.volume_obj.to(self.device) - - # Making optimizable parameters into leaf tensors. - self.dataset.shifts = self.dataset.shifts.detach().to(self.device).requires_grad_(True) - self.dataset.z1_angles = ( - self.dataset.z1_angles.detach().to(self.device).requires_grad_(True) - ) - self.dataset.z3_angles = ( - self.dataset.z3_angles.detach().to(self.device).requires_grad_(True) - ) - - if optimizer_params is not None: - self.optimizer_params = optimizer_params - self.set_optimizers() - - if scheduler_params is not None: - self.scheduler_params = scheduler_params - self.set_schedulers(self.scheduler_params, num_iter=num_iter) - - if hard_constraints is not None: - self.volume_obj.hard_constraints = hard_constraints - if soft_constraints is not None: - self.volume_obj.soft_constraints = soft_constraints - - pbar = tqdm(range(num_iter), desc="AD Reconstruction") - - for a0 in pbar: - total_loss = 0.0 - tilt_series_loss = 0.0 - - pred_volume = self.volume_obj.forward() - - for i in range(len(self.dataset.tilt_series)): - forward_projection = self.projection_operator( - vol=pred_volume, - z1=self.dataset.z1_angles[i], - x=self.dataset.tilt_angles[i], - z3=self.dataset.z3_angles[i], - shift_x=self.dataset.shifts[i, 0], - shift_y=self.dataset.shifts[i, 1], - device=self.device, - ) - - tilt_series_loss += torch.nn.functional.mse_loss( - forward_projection, self.dataset.tilt_series[i] - ) - tilt_series_loss /= len(self.dataset.tilt_series) - - total_loss = tilt_series_loss + self.volume_obj.soft_loss - self.loss.append(total_loss.item()) - - total_loss.backward() - - for opt in self.optimizers.values(): - opt.step() - opt.zero_grad() - - if self.schedulers is not None: - for sch in self.schedulers.values(): - if isinstance(sch, torch.optim.lr_scheduler.ReduceLROnPlateau): - sch.step(total_loss) - elif sch is not None: - sch.step() - - pbar.set_description(f"AD Reconstruction | Loss: {total_loss:.4f}") - - if self.logger is not None: - self.logger.log_scalar("loss/total", total_loss.item(), a0) - self.logger.log_scalar("loss/tilt_series", tilt_series_loss.item(), a0) - self.logger.log_scalar( - "loss/soft constraints", self.volume_obj.soft_loss.item(), a0 - ) - - if a0 % self.logger.log_images_every == 0: - self.logger.projection_images( - volume_obj=self.volume_obj, - epoch=a0, - ) - self.logger.tilt_angles_figure(dataset=self.dataset, step=a0) - - self.logger.flush() - - self.ad_recon_vol = self.volume_obj.forward() - - return self - - def reset_recon(self) -> None: - if isinstance(self.volume_obj, ObjectVoxelwise): - self.volume_obj.reset() - - self.ad_recon_vol = None - - # --- Projection Operators ---- - def projection_operator( - self, - vol, - z1, - x, - z3, - shift_x, - shift_y, - device, - ): - projection = ( - rot_ZXZ( - mags=vol.unsqueeze(0), # Add batch dimension - z1=z1, - x=-x, - z3=z3, - device=device, - mode="bilinear", - ) - .squeeze() - .sum(axis=0) - ) - - shifted_projection = differentiable_shift_2d( - image=projection, - shift_x=shift_x, - shift_y=shift_y, - sampling_rate=1.0, # Assuming 1 pixel = 1 physical unit - ) +class TomographyConventional: + """ + Class for handling all conventional tomography reconstruction methods. + Will also handle choosing the appropriate dataset model to use. + """ - return shifted_projection + pass diff --git a/src/quantem/tomography/tomography_base.py b/src/quantem/tomography/tomography_base.py index d3b94427..8b137891 100644 --- a/src/quantem/tomography/tomography_base.py +++ b/src/quantem/tomography/tomography_base.py @@ -1,514 +1 @@ -from typing import Tuple -import matplotlib.pyplot as plt -import numpy as np -import torch -from numpy.typing import NDArray -from torch._tensor import Tensor -from tqdm.auto import tqdm - -from quantem.core.datastructures.dataset3d import Dataset3d -from quantem.core.io.serialize import AutoSerialize -from quantem.core.visualization.visualization import show_2d -from quantem.imaging.drift import cross_correlation_shift -from quantem.tomography.object_models import ObjectModelType, ObjectVoxelwise -from quantem.tomography.tomography_dataset import TomographyDataset -from quantem.tomography.tomography_logger import LoggerTomography - - -class TomographyBase(AutoSerialize): - _token = object() - - DEFAULT_HARD_CONSTRAINTS = { - "positivity": False, - "shrinkage": False, - "circular_mask": False, - "fourier_filter": False, - } - - DEFAULT_SOFT_CONSTRAINTS = { - "tv_vol": 0.0, - } - - def __init__( - self, - dataset: TomographyDataset, - volume_obj: ObjectModelType, # ObjectDIP? - device: str = "cuda", - # ABF/HAADF property - logger: LoggerTomography | None = None, - _token: object | None = None, - ): - """Initialize a Tomography object. - - Parameters - ---------- - array : NDArray | Any - The underlying 3D array data - name : str - A descriptive name for the dataset - """ - - # if _token is not self._token: - # raise RuntimeError( - # "This class is not meant to be instantiated directly. Use the from_data method." - # ) - - self._device = device - self._dataset = dataset - self._volume_obj = volume_obj - self._loss = [] - self._mode = [] - - self._hard_constraints = self.DEFAULT_HARD_CONSTRAINTS.copy() - self._soft_constraints = self.DEFAULT_SOFT_CONSTRAINTS.copy() - - self._logger = None - - @classmethod - def from_data( - cls, - tilt_series: Dataset3d | NDArray | Tensor, - tilt_angles: NDArray | Tensor, - z1_angles: NDArray | Tensor | None = None, - z3_angles: NDArray | Tensor | None = None, - shifts: NDArray | Tensor | None = None, - volume_obj: NDArray | Dataset3d | ObjectModelType | None = None, - device: str = "cpu", - clamp: bool = True, - ): - device = device.lower() - - dataset = TomographyDataset.from_data( - tilt_series=tilt_series, - tilt_angles=tilt_angles, - z1_angles=z1_angles, - z3_angles=z3_angles, - shifts=shifts, - clamp=clamp, - ) - - dataset.to(device) - - if volume_obj is None: - max_shape = max(dataset.tilt_series.shape) - volume_obj = ObjectVoxelwise( - volume_shape=(max_shape, max_shape, max_shape), - device=device, - ) - elif isinstance(volume_obj, Dataset3d): - volume = torch.from_numpy(volume_obj.array) - volume_obj = ObjectVoxelwise( - volume_shape=volume_obj.shape, device=device, initial_volume=volume - ) - volume_obj.obj = volume - elif isinstance(volume_obj, np.ndarray): - volume = torch.from_numpy(volume_obj) - volume_obj = ObjectVoxelwise( - volume_shape=volume_obj.shape, device=device, initial_volume=volume - ) - volume_obj.obj = volume - else: - raise ValueError("volume_obj must be a Dataset3d, NDArray ObjectModelType") - - return cls( - dataset=dataset, - volume_obj=volume_obj, - device=device, - _token=cls._token, - ) - - # --- Properties --- - @property - def dataset(self) -> TomographyDataset: - """Tomography dataset.""" - - return self._dataset - - @dataset.setter - def dataset( - self, - tilt_series: Dataset3d | NDArray | TomographyDataset, - tilt_angles: NDArray | Tensor, - z1_angles: NDArray | Tensor | None = None, - z3_angles: NDArray | Tensor | None = None, - shifts: NDArray | Tensor | None = None, - # name: str | None = None, - # origin: NDArray | tuple | list | float | int | None = None, - # sampling: NDArray | tuple | list | float | int | None = None, - # units: list[str] | tuple | list | None = None, - # signal_units: str = "arb. units", - ): - """Set the tilt series dataset.""" - - if not isinstance(tilt_series, TomographyDataset): - dataset = TomographyDataset.from_array( - array=tilt_series, - tilt_angles=tilt_angles, - z1_angles=z1_angles, - z3_angles=z3_angles, - shifts=shifts, - ) - - self._dataset = dataset - - @property - def volume_obj(self) -> Dataset3d | ObjectModelType | None: - """Reconstruction volume dataset.""" - - return self._volume_obj - - @volume_obj.setter - # TODO: add support for ObjectModelType - def volume_obj(self, volume_obj: Dataset3d | NDArray): - """Set the reconstruction volume dataset.""" - if isinstance(volume_obj, ObjectModelType): - self._volume_obj = volume_obj - elif not isinstance(volume_obj, Dataset3d): - volume_obj = Dataset3d.from_array( - array=volume_obj, - # name=self._tilt_series.name, - # origin=self._tilt_series.origin, - # sampling=self._tilt_series.sampling, - # units=self._tilt_series.units, - # signal_units=self._tilt_series.signal_units, - ) - elif isinstance(volume_obj, Dataset3d): - self._volume_obj = volume_obj - else: - raise ValueError("volume_obj must be a Dataset3d or ObjectModelType") - - @property - def device(self) -> str: - """Computation device.""" - - return self._device - - @device.setter - def device(self, device: str): - """Set the computation device.""" - - # if "cuda" not in device or "gpu" not in device: - # raise NotImplementedError("Tomography not currently supported on CPU.") - - self._device = device - - @property - def loss(self) -> list: - """List of loss values during reconstruction.""" - - return self._loss - - @loss.setter - def loss(self, loss: list): - """Set the loss values during reconstruction.""" - - if not isinstance(loss, list): - raise TypeError("Loss must be a list.") - - self._loss = loss - - @property - def mode(self) -> list: - """List of modes used during reconstruction.""" - - return self._mode - - @property - def epochs(self) -> int: - """Number of epochs used during reconstruction.""" - return len(self.loss) - - @property - def logger(self) -> LoggerTomography: - return self._logger - - @logger.setter - def logger(self, logger: LoggerTomography): - if not isinstance(logger, LoggerTomography): - raise TypeError("Logger must be a LoggerTomography") - - self._logger = logger - - # --- Constraints --- - - @property - def hard_constraints(self) -> dict: - """Hard constraints for the reconstruction.""" - return self._hard_constraints - - @hard_constraints.setter - def hard_constraints(self, hard_constraints: dict): - """Set the hard constraints for the reconstruction.""" - - gkeys = self.DEFAULT_HARD_CONSTRAINTS.keys() - for key, value in hard_constraints.items(): - if key not in gkeys: - raise KeyError(f"Invalid object constraint key '{key}', allowed keys are {gkeys}") - self._hard_constraints[key] = value - - self._hard_constraints = hard_constraints - - @property - def soft_constraints(self) -> dict: - """Soft constraints for the reconstruction.""" - return self._soft_constraints - - @soft_constraints.setter - def soft_constraints(self, soft_constraints: dict): - """Set the soft constraints for the reconstruction.""" - - gkeys = self.DEFAULT_SOFT_CONSTRAINTS.keys() - for key, value in soft_constraints.items(): - if key not in gkeys: - raise KeyError(f"Invalid object constraint key '{key}', allowed keys are {gkeys}") - self._soft_constraints[key] = value - - self._soft_constraints = soft_constraints - - # --- RESET --- - - def reset_recon(self) -> None: - self.volume_obj.reset() - self.dataset.reset() - self.loss = [] - self.hard_constraints = self.DEFAULT_HARD_CONSTRAINTS.copy() - self.soft_constraints = self.DEFAULT_SOFT_CONSTRAINTS.copy() - - self._optimizers = {} - self._schedulers = {} - - # --- Preprocessing --- - - """ - TODO - 1. Implement tilt series cross-correlation alignment - 2. Background subtraction (for ABF) - 3. COM Alignment - 4. Masking - 5. Drift Correction - """ - - def cross_corr_alignment( - self, - upsample_factor: int = 1, - overwrite: bool = False, - ): - # TODO: This needs to be able to work with torch tensors. - - placeholder_tilt_series = self.dataset.tilt_series.clone().detach().cpu().numpy() - - aligned_tilt_series = np.zeros_like(placeholder_tilt_series) - aligned_tilt_series[0] = placeholder_tilt_series[0] - shifts = [] - num_imgs = placeholder_tilt_series.shape[1] - - pbar = tqdm(range(num_imgs - 1), desc="Cross-correlation alignment") - - for i in pbar: - shift, aligned_img = cross_correlation_shift( - placeholder_tilt_series[i], - placeholder_tilt_series[i + 1], - upsample_factor=upsample_factor, - return_shifted_image=True, - ) - - aligned_tilt_series[i + 1] = aligned_img - shifts.append(shift) - - if overwrite: - # TODO: Check this overwrite idea, maybe also need to save the relative shifts? - self.dataset.tilt_series = np.array(aligned_tilt_series) - - return np.array(aligned_tilt_series), np.array(shifts) - - # --- Postprocessing --- - - """ - TODO - 1. Apply circular mask - """ - - def circular_mask(self, shape, radius, center=None, dtype=torch.float32, device="cpu"): - """Generate a 2D circular mask of given shape and radius.""" - H, W = shape - - if center is None: - center = (H // 2, W // 2) - y = torch.arange(H, dtype=dtype, device=device).view(-1, 1) - x = torch.arange(W, dtype=dtype, device=device).view(1, -1) - dist_sq = (x - center[1]) ** 2 + (y - center[0]) ** 2 - return (dist_sq <= radius**2).to(dtype) - - def recon_vol_circular_mask(self, radii): - """ - Apply 2D circular masks along all three axes of a 3D volume. - - Args: - volume (torch.Tensor): 3D tensor of shape (H, W, D) - radii (tuple): (r0, r1, r2) for axes 0, 1, 2 - Returns: - masked_volume: tensor with all masks applied - """ - H, W, D = self.volume_obj.array.shape - device = self.device - dtype = torch.float32 - volume_obj = torch.tensor( - self.volume_obj.array, - device=self.device, - dtype=dtype, - ) - # Masks for each axis - mask0 = self.circular_mask((W, D), radii[0], dtype=dtype, device=device).unsqueeze( - 0 - ) # shape (1, W, D) - mask1 = self.circular_mask((H, D), radii[1], dtype=dtype, device=device).unsqueeze( - 1 - ) # shape (H, 1, D) - mask2 = self.circular_mask((H, W), radii[2], dtype=dtype, device=device).unsqueeze( - 2 - ) # shape (H, W, 1) - - # Broadcast and multiply all masks together - total_mask = mask0 * mask1 * mask2 # shape (H, W, D) - - volume_obj = volume_obj * total_mask - volume_obj = volume_obj.detach().cpu().numpy() - self.volume_obj = Dataset3d.from_array( - array=volume_obj, - # name=self.volume_obj.name, - # origin=self.volume_obj.origin, - # sampling=self.volume_obj.sampling, - # units=self.volume_obj.units, - # signal_units=self.volume_obj.signal_units, - ) - - # --- Visualizations --- - - def plot_projections( - self, - cmap: str = "turbo", - fft: bool = False, - norm: str = "log_auto", - figax: tuple[plt.Figure, plt.Axes] | None = None, - fft_vmax: Tuple[float, float] = (0, 40), - vmin: float = 0, - **kwargs, - ): - """ - Plots the projections of the volume object. - Note that the volume object is in the order of (z, y, x). - Parameters - ---------- - cmap : str - The colormap to use for the projections. - fft : bool - """ - - volume_obj_np = self.volume_obj.obj.detach().cpu().numpy() - - if figax is None: - fig, ax = plt.subplots(ncols=3, figsize=(20, 8)) - else: - fig, ax = figax - - show_2d( - volume_obj_np.sum(axis=0), - figax=(fig, ax[0]), - cmap=cmap, - title="Y-X Projection", - vmin=vmin, - ) - show_2d( - volume_obj_np.sum(axis=1), - figax=(fig, ax[1]), - cmap=cmap, - title="Z-X Projection", - vmin=vmin, - ) - show_2d( - volume_obj_np.sum(axis=2), - figax=(fig, ax[2]), - cmap=cmap, - title="Z-Y Projection", - vmin=vmin, - ) - - if fft: - fig, ax = plt.subplots(ncols=3, figsize=(25, 8)) - print(fft_vmax) - show_2d( - np.abs(np.fft.fftshift(np.fft.fftn(volume_obj_np.sum(axis=0)))), - figax=(fig, ax[0]), - cmap=cmap, - title="Y-X Projection FFT", - # norm=norm, - vmin=fft_vmax[0], - vmax=fft_vmax[1], - ) - - show_2d( - np.abs(np.fft.fftshift(np.fft.fftn(volume_obj_np.sum(axis=1)))), - figax=(fig, ax[1]), - cmap=cmap, - title="Z-X Projection FFT", - vmin=fft_vmax[0], - vmax=fft_vmax[1], - # norm=norm, - ) - show_2d( - np.abs(np.fft.fftshift(np.fft.fftn(volume_obj_np.sum(axis=2)))), - figax=(fig, ax[2]), - cmap=cmap, - title="Z-Y Projection FFT", - vmin=fft_vmax[0], - vmax=fft_vmax[1], - # norm=norm, - ) - - def plot_slice( - self, - cmap="turbo", - slice_index: int = 0, - vmin: float = 0, - figax: tuple[plt.Figure, plt.Axes] | None = None, - ): - if figax is None: - fig, ax = plt.subplots(figsize=(15, 8), ncols=3) - else: - fig, ax = figax - - show_2d( - self.volume_obj.obj[slice_index, :, :], - figax=(fig, ax[0]), - cmap=cmap, - vmin=vmin, - ) - show_2d( - self.volume_obj.obj[:, slice_index, :], - figax=(fig, ax[1]), - cmap=cmap, - vmin=vmin, - ) - - show_2d( - self.volume_obj.obj[:, :, slice_index], - figax=(fig, ax[2]), - cmap=cmap, - vmin=vmin, - ) - - def plot_loss( - self, - figsize: tuple = (8, 4), - figax: tuple[plt.Figure, plt.Axes] | None = None, - ): - if figax is None: - fig, ax = plt.subplots(figsize=figsize) - else: - fig, ax = figax - - ax.semilogy( - self.loss, - label="Loss", - ) diff --git a/src/quantem/tomography/tomography_conv.py b/src/quantem/tomography/tomography_conv.py index 1056da17..e69de29b 100644 --- a/src/quantem/tomography/tomography_conv.py +++ b/src/quantem/tomography/tomography_conv.py @@ -1,235 +0,0 @@ -import numpy as np -import torch - -# from torch_radon.radon import ParallelBeam as Radon -from quantem.tomography.radon.radon import iradon_torch, radon_torch -from quantem.tomography.tomography_base import TomographyBase -from quantem.tomography.utils import gaussian_filter_2d_stack, torch_phase_cross_correlation - - -class TomographyConv(TomographyBase): - """ - Class for handling conventional reconstruction methods of tomography data. - """ - - # --- Reconstruction Methods --- - - def _sirt_run_epoch( - self, - tilt_series: torch.Tensor, - proj_forward: torch.Tensor, - angles: torch.Tensor, - inline_alignment: bool, - filter_name: str, - circle: bool, - gaussian_kernel: torch.Tensor | None, - ): - loss = 0 - - if inline_alignment: - for ind in range(len(self.dataset.tilt_angles)): - im_proj = proj_forward[ind] - im_meas = tilt_series[ind] - - shift = torch_phase_cross_correlation(im_proj, im_meas) - if torch.linalg.norm(shift) <= 32: - shifted = torch.fft.ifft2( - torch.fft.fft2(im_meas) - * torch.exp( - -2j - * np.pi - * ( - shift[0] - * torch.fft.fftfreq( - im_meas.shape[0], device=im_meas.device - ).unsqueeze(1) - + shift[1] - * torch.fft.fftfreq(im_meas.shape[1], device=im_meas.device) - ) - ) - ).real - - proj_forward[ind] = shifted - - # Forward projection - - sinogram_est = radon_torch(self.volume_obj.obj, theta=angles, device=self.device) - # proj_forward = sinogram_est.permute(1, 2, 0) - # error = (tilt_series - proj_forward).permute(2, 0, 1) - proj_forward = sinogram_est - error = tilt_series - proj_forward - - correction = iradon_torch( - error, theta=angles, device=self.device, filter_name=filter_name, circle=circle - ) - - normalization = iradon_torch( - torch.ones_like(error), - theta=angles, - device=self.device, - filter_name=None, - circle=circle, - ) - - normalization[normalization == 0] = 1e-6 - - correction /= normalization - - self.volume_obj._obj += correction - - loss = torch.mean(torch.abs(error)) - - # for z in tqdm(range(self.volume_obj.obj.shape[0]), desc="SIRT Reconstruction"): - # slice_estimate = self.volume_obj.obj[z] - # sinogram_est = radon_torch(slice_estimate, theta=angles, device=self.device) - - # sinogram_true = tilt_series[:, :, z] - - # error = sinogram_true - sinogram_est - - # correction = iradon_torch( - # error, theta=angles, device=self.device, filter_name=filter_name, circle=circle - # ) - - # # I'm pretty sure this implementation of normalization is wrong - # normalization = iradon_torch( - # torch.ones_like(error), - # theta=angles, - # device=self.device, - # filter_name=None, - # circle=circle, - # ) - # normalization[normalization == 0] = 1e-6 - - # correction /= normalization - - # self.volume_obj._obj[z] += correction - - # proj_forward[:, :, z] = sinogram_est - - # loss += torch.mean(torch.abs(error)) - - # loss /= self.volume_obj._obj.shape[0] - - if gaussian_kernel is not None: - self.volume_obj.obj = gaussian_filter_2d_stack(self.volume_obj.obj, gaussian_kernel) - - return proj_forward, loss - - # Deprecated torch_radon implementations - # def _sirt_run_epoch( - # self, - # radon: Radon, - # stack_recon: torch.Tensor, - # stack_torch: torch.Tensor, - # proj_forward: torch.Tensor, - # step_size: float = 0.25, - # gaussian_kernel: torch.Tensor = None, - # inline_alignment=True, - # enforce_positivity=True, - # shrinkage: float = None, - # ): - # loss = 0 - - # if inline_alignment: - # for ind in range(len(self.tilt_series.tilt_angles)): - # im_proj = proj_forward[:, ind, :] - # im_meas = stack_torch[:, ind, :] - - # shift = torch_phase_cross_correlation(im_proj, im_meas) - # if torch.linalg.norm(shift) <= 32: - # shifted = torch.fft.ifft2( - # torch.fft.fft2(im_meas) - # * torch.exp( - # -2j - # * np.pi - # * ( - # shift[0] - # * torch.fft.fftfreq( - # im_meas.shape[0], device=im_meas.device - # ).unsqueeze(1) - # + shift[1] - # * torch.fft.fftfreq(im_meas.shape[1], device=im_meas.device) - # ) - # ) - # ).real - - # stack_torch[:, ind, :] = shifted - - # proj_forward = radon.forward(stack_recon) - - # proj_diff = stack_torch - proj_forward - - # loss = torch.mean(torch.abs(proj_diff)) - - # recon_slice_update = radon.backward( - # radon.filter_sinogram( - # proj_diff, - # ) - # ) - - # stack_recon += step_size * recon_slice_update - # if enforce_positivity: - # stack_recon = torch.clamp(stack_recon, min=0) - - # if gaussian_kernel is not None: - # stack_recon = gaussian_filter_2d_stack( - # stack_recon, - # gaussian_kernel, - # ) - - # if shrinkage is not None: - # stack_recon = torch.max( - # stack_recon - shrinkage, - # torch.zeros_like(stack_recon), - # ) - - # return stack_recon, loss - - # def _sirt_serial_run_epoch( - # self, - # radon: Radon, - # stack_recon: torch.Tensor, - # stack_torch: torch.Tensor, - # proj_forward: torch.Tensor, - # step_size: float = 0.25, - # gaussian_kernel: torch.Tensor = None, - # inline_alignment=True, - # enforce_positivity=True, - # ): - # recon_slice_update = torch.zeros_like(stack_recon).to(self.device) - - # loss = 0 - - # for i in range(stack_recon.shape[0]): - # proj_forward[i] = radon.forward(stack_recon[i]) - - # proj_diff = stack_torch - proj_forward - - # loss = torch.mean(torch.abs(proj_diff)) - - # for i in range(stack_recon.shape[0]): - # recon_slice_update[i] = radon.backward( - # radon.filter_sinogram( - # proj_diff[i], - # ) - # ) - - # stack_recon += step_size * recon_slice_update - - # if enforce_positivity: - # stack_recon = torch.clamp(stack_recon, min=0) - - # return stack_recon, loss - - # --- Properties --- - # @property - # def reconstruction_method(self) -> str: - # """Get the reconstruction method.""" - # return self._reconstruction_method - # @reconstruction_method.setter - # def reconstruction_method(self, value: str): - # """Set the reconstruction method.""" - # if value not in ["SIRT", "FBP"]: - # raise ValueError("Invalid reconstruction method. Choose 'SIRT' or 'FBP'.") - # self._reconstruction_method = value diff --git a/src/quantem/tomography/tomography_ddp.py b/src/quantem/tomography/tomography_ddp.py index cfb59117..e69de29b 100644 --- a/src/quantem/tomography/tomography_ddp.py +++ b/src/quantem/tomography/tomography_ddp.py @@ -1,321 +0,0 @@ -import os - -import numpy as np -import torch -import torch.distributed as dist -import torch.nn as nn -from torch.utils.data import DataLoader, DistributedSampler - -from quantem.tomography.tomography_dataset import ( - PretrainVolumeDataset, - TomographyDataset, - TomographyRayDataset, -) - - -class TomographyDDP: - """ - Initializing DDP stuff for tomo class. - """ - - def __init__( - self, - ): - self.setup_distributed() - - def setup_distributed(self): - # Check if in distributed env - if "RANK" in os.environ: - # Distributed training - if not dist.is_initialized(): - dist.init_process_group( - backend="nccl" if torch.cuda.is_available() else "gloo", init_method="env://" - ) - - self.world_size = dist.get_world_size() - self.global_rank = dist.get_rank() - self.local_rank = int(os.environ["LOCAL_RANK"]) - - torch.cuda.set_device(self.local_rank) - self.device = torch.device("cuda", self.local_rank) - - else: - # Single GPU/CPU training - self.world_size = 1 - self.global_rank = 0 - self.local_rank = 0 - - if torch.cuda.is_available(): - self.device = torch.device("cuda") - torch.cuda.set_device(0) - print("Single GPU training") - else: - self.device = torch.device("cpu") - print("CPU training") - - # Optional performance optimizations (only for CUDA) - if self.device.type == "cuda": - torch.backends.cudnn.benchmark = True - torch.backends.cuda.matmul.allow_tf32 = True - torch.backends.cudnn.allow_tf32 = True - - def build_model( - self, - model: nn.Module, - ): - # TODO: Generalized model --> Should be instantiated in the object? Where does `HSIREN` get instantiated? - model = model.to(self.device) - - if self.world_size > 1: - model = torch.nn.parallel.DistributedDataParallel( - model, - device_ids=[self.local_rank], - output_device=self.local_rank, - find_unused_parameters=False, - broadcast_buffers=True, - bucket_cap_mb=100, - gradient_as_bucket_view=True, - ) - - if self.global_rank == 0: - print("Model wrapped with DDP") - - if self.world_size > 1: - if self.global_rank == 0: - print("Model built, distributed, and compiled successfully") - - else: - print("Model built, compiled successfully") - - return model - - # Setup Tomo DataLoader - def setup_dataloader( - self, - tomo_dataset: TomographyDataset, - batch_size: int, - num_workers: int = 0, - val_fraction: float = 0.0, - ): - pin_mem = self.device.type == "cuda" - persist = num_workers > 0 - - # Split dataset if validation fraction > 0 - if val_fraction > 0.0: - # TODO: Temporary for when only doing validation, current TomographyDataset doesn't work correctly - train_dataset = TomographyRayDataset( - tomo_dataset.tilt_series.detach().clone(), - tomo_dataset.tilt_angles.detach().clone(), - 500, # TODO: TEMPORARY - val_ratio=val_fraction, - mode="train", - seed=42, - ) - val_dataset = TomographyRayDataset( - tomo_dataset.tilt_series.detach().clone(), - tomo_dataset.tilt_angles.detach().clone(), - 500, # TODO: TEMPORARY - val_ratio=val_fraction, - mode="val", - seed=42, - ) - else: - train_dataset, val_dataset = tomo_dataset, None - - # Samplers for distributed training - if self.world_size > 1: - train_sampler = DistributedSampler( - train_dataset, - num_replicas=self.world_size, - rank=self.global_rank, - shuffle=True, - ) - if val_dataset: - val_sampler = DistributedSampler( - val_dataset, - num_replicas=self.world_size, - rank=self.global_rank, - shuffle=False, - ) - shuffle = False - else: - train_sampler = None - val_sampler = None - shuffle = True - - # Main dataloader - self.dataloader = DataLoader( - train_dataset, - batch_size=batch_size, - num_workers=num_workers, - sampler=train_sampler, - shuffle=shuffle, - pin_memory=pin_mem, - drop_last=True, - persistent_workers=persist, - ) - - # Validation dataloader if applicable - if val_dataset: - self.val_dataloader = DataLoader( - val_dataset, - batch_size=batch_size * 4, - num_workers=num_workers, - sampler=val_sampler, - shuffle=False, - pin_memory=pin_mem, - drop_last=False, - persistent_workers=persist, - ) - self.val_sampler = val_sampler - - self.sampler = train_sampler - - if self.global_rank == 0: - print("Dataloader setup complete:") - print(f" Total projections: {len(tomo_dataset.tilt_angles)}") - if val_fraction > 0.0: - print(f" Total projections (val): {len(val_dataset)}") - print(f" Grid size: {tomo_dataset.dims[1]}{tomo_dataset.dims[2]}") - print(f" Total pixels: {tomo_dataset.num_pixels:,}") - if val_fraction > 0.0: - print(f" Total pixels (val): {len(val_dataset):,}") - print(f" Total pixels (train): {len(train_dataset):,}") - print(f" Local batch size (train): {batch_size}") - print(f" Global batch size: {batch_size * self.world_size}") - print(f" Train batches per GPU per epoch: {len(self.dataloader)}") - - # Setup pretraining dataloader - - def setup_pretraining_dataloader( - self, - volume_dataset: PretrainVolumeDataset, - batch_size: int, - ): - # TODO: temp - - num_workers = 0 - if self.world_size > 1: - sampler = DistributedSampler( - volume_dataset, - num_replicas=self.world_size, - rank=self.global_rank, - shuffle=True, - drop_last=True, - ) - shuffle = False - else: - sampler = None - shuffle = True - - self.pretraining_dataloader = DataLoader( - volume_dataset, - batch_size=batch_size, - sampler=sampler, - shuffle=shuffle, - pin_memory=self.device.type == "cuda", - drop_last=True, - persistent_workers=num_workers > 0, - ) - - self.pretraining_sampler = sampler - - if self.global_rank == 0: - print("Pretraining dataloader setup complete:") - print(f" Total samples: {len(volume_dataset)}") - print(f" Grid size: {volume_dataset.N**3}") - print(f" Local batch size: {batch_size}") - print(f" Global batch size: {batch_size * self.world_size}") - print(f" Pretraining batches per GPU per epoch: {len(self.pretraining_dataloader)}") - - def get_scaled_lr(self, base_lr, scaling_rule="sqrt"): - if scaling_rule == "sqrt": - return base_lr * np.sqrt(self.world_size) - elif scaling_rule == "linear": - return base_lr * self.world_size - else: - raise ValueError(f"Invalid scaling rule: {scaling_rule}") - - def scale_lr( - self, - optimizer_params: dict, - ): - new_optimizer_params = {} - for key, value in optimizer_params.items(): - if "original_lr" in value: - new_optimizer_params[key] = { - "type": value["type"], - "lr": self.get_scaled_lr(value["original_lr"]), - "original_lr": value["original_lr"], - } - else: - new_optimizer_params[key] = { - "type": value["type"], - "lr": self.get_scaled_lr(value["lr"]), - "original_lr": value["lr"], - } - - return new_optimizer_params - - # TODO: Temporary Adaptive L1 Smooth Loss - - -class AdaptiveSmoothL1Loss(nn.Module): - def __init__(self, beta_init=None, ema_factor=0.99, eps=1e-8): - """ - Adaptive smooth L1 loss with EMA-based beta adaptation. - - Args: - beta_init (float): optional initial β value; if None, starts as 1.0 - ema_factor (float): smoothing factor for EMA (1 - 1/N_b in Eq. 38) - eps (float): small constant for numerical stability - """ - super().__init__() - self.register_buffer("beta2", torch.tensor(beta_init**2 if beta_init else 1.0)) - self.ema_factor = ema_factor - self.eps = eps - - def forward(self, pred, target): - diff = pred - target - abs_diff = diff.abs() - - # compute current batch MSE (Eq. 38) - mse_batch = torch.mean(diff**2) - - # update β² adaptively using Eq. (39) - with torch.no_grad(): - self.beta2 = self.ema_factor * self.beta2 + (1 - self.ema_factor) * torch.min( - self.beta2, mse_batch - ) - - beta = torch.sqrt(self.beta2 + self.eps) - - # Smooth L1 (Eq. 36) - loss = torch.where( - abs_diff < beta, 0.5 * (diff**2) / (beta + self.eps), abs_diff - 0.5 * beta - ) - return loss.mean() - - -class AdaptiveSmoothL1LossDDP(AdaptiveSmoothL1Loss): - def forward(self, pred, target): - diff = pred - target - abs_diff = diff.abs() - mse_batch = torch.mean(diff**2) - - # Synchronize β across all GPUs - if dist.is_initialized(): - mse_batch_all = mse_batch.clone() - dist.all_reduce(mse_batch_all, op=dist.ReduceOp.AVG) - mse_batch = mse_batch_all / dist.get_world_size() - - with torch.no_grad(): - self.beta2 = self.ema_factor * self.beta2 + (1 - self.ema_factor) * torch.min( - self.beta2, mse_batch - ) - - beta = torch.sqrt(self.beta2 + self.eps) - loss = torch.where( - abs_diff < beta, 0.5 * (diff**2) / (beta + self.eps), abs_diff - 0.5 * beta - ) - return loss.mean() diff --git a/src/quantem/tomography/tomography_ml.py b/src/quantem/tomography/tomography_ml.py index 47e479d8..e69de29b 100644 --- a/src/quantem/tomography/tomography_ml.py +++ b/src/quantem/tomography/tomography_ml.py @@ -1,330 +0,0 @@ -from typing import Any, Generator, Iterator, Sequence - -import torch - -from quantem.tomography.tomography_base import TomographyBase - - -class TomographyML(TomographyBase): - """ - Class for handling conventional reconstruction methods of tomography data. - """ - - OPTIMIZABLE_VALS = ["volume", "z1", "x", "z3", "shifts", "model", "aux_params"] - DEFAULT_LRS = { - "volume": 1e-2, - "z1": 1e-1, - "x": 1e-1, - "z3": 1e-1, - "shifts": 1e-1, - "tv_weight_vol": 0, - "tv_weight_z1": 0, - "tv_weight_x": 0, - "tv_weight_z3": 0, - } - DEFAULT_OPTIMIZER_TYPE = "adam" - - # --- Properties --- - - @property - def optimizer_params(self) -> dict[str, dict]: - """Returns the parameters used to set the optimizers.""" - return self._optimizer_params - - @optimizer_params.setter - def optimizer_params(self, d: dict) -> None: - """ - # Takes a dictionary {key: torch.optim.Adam(params=[blah], lr=[blah]), ...} - Takes a dictionary: - { - "key1": { - "type": "adam", - "lr": 0.001, - }, - "key2": { - ... - }, - ... - } - """ - # resets _optimizers as well - self._optimizers = {} - self._optimizer_params = {} - if isinstance(d, (tuple, list)): - d = {k: {} for k in d} - - for k, v in d.items(): - if k not in self.OPTIMIZABLE_VALS: - raise ValueError( - f"key to be optimized, {k}, not in allowed keys: {self.OPTIMIZABLE_VALS}" - ) - if "type" not in v.keys(): - v["type"] = self.DEFAULT_OPTIMIZER_TYPE - if "lr" not in v.keys(): - v["lr"] = self.DEFAULT_LRS[k] - self._optimizer_params[k] = v - - @property - def optimizers(self) -> dict[str, torch.optim.Adam | torch.optim.AdamW]: - return self._optimizers - - def set_optimizers(self): - """Reset all optimizers and set them according to the optimizer_params.""" - for key, _ in self._optimizer_params.items(): - if key == "volume": - self._add_optimizer(key, self.volume_obj.params, self._optimizer_params[key]) - elif key == "shifts": - self._add_optimizer(key, self.dataset.shifts, self._optimizer_params[key]) - elif key == "z1": - self._add_optimizer(key, self.dataset.z1_angles, self._optimizer_params[key]) - elif key == "x": - self._add_optimizer(key, self.dataset.tilt_angles, self._optimizer_params[key]) - elif key == "z3": - self._add_optimizer(key, self.dataset.z3_angles, self._optimizer_params[key]) - elif key == "model": - - if self._optimizer_params[key]["original_lr"] is not None: - optimizer_params = self._optimizer_params[key].copy() - optimizer_params.pop("original_lr", None) - self._add_optimizer(key, self.obj.model.parameters(), optimizer_params) - elif key == "aux_params": - - if self._optimizer_params[key]["original_lr"] is not None: - optimizer_params = self._optimizer_params[key].copy() - optimizer_params.pop("original_lr", None) - self._add_optimizer(key, self.dataset.auxiliary_params.parameters(), optimizer_params) - else: - raise ValueError( - f"key to be optimized, {key}, not in allowed keys: {self.OPTIMIZABLE_VALS}" - ) - - def remove_optimizer(self, key: str) -> None: - self._optimizers.pop(key, None) - self._optimizer_params.pop(key, None) - return - - def _add_optimizer( - self, - key: str, - params: "torch.Tensor|Sequence[torch.Tensor]|Iterator[torch.Tensor]", - opt_params: dict, - ) -> None: - """Can be used to add an optimizer without resetting the other optimizers.""" - - if key not in self.OPTIMIZABLE_VALS: - raise ValueError( - f"key to be optimized, {key}, not in allowed keys: {self.OPTIMIZABLE_VALS}" - ) - if isinstance(params, torch.Tensor): - params = [params] - elif isinstance(params, Generator): - params = list(params) - [p.requires_grad_(True) for p in params] - self.optimizer_params[key] = opt_params - opt_params = opt_params.copy() - opt_type = opt_params.pop("type") - if isinstance(opt_type, type): - opt = opt_type(params, **opt_params) - elif opt_type == "adam": - opt = torch.optim.Adam(params, **opt_params) - elif opt_type == "adamw": - opt = torch.optim.AdamW(params, **opt_params) # TODO pass all other kwargs - else: - raise NotImplementedError(f"Unknown optimizer type: {opt_params['type']}") - # if key in self.optimizers.keys(): - # self.vprint(f"Key {key} is already in optimizers, overwriting.") - self._optimizers[key] = opt - - @property - def scheduler_params(self) -> dict[str, dict]: - """Returns the parameters used to set the schedulers.""" - return self._scheduler_params - - @scheduler_params.setter - def scheduler_params(self, d: dict) -> None: - """ - Takes a dictionary: - { - "key1": { - "type": "cyclic", - "base_lr": 0.001, - }, - "key2": { - ... - }, - ... - } - """ - self._schedulers = {} - for k, v in d.items(): - if not any(v): - continue - if k not in self.OPTIMIZABLE_VALS: - raise ValueError( - f"key to be optimized, {k}, not in allowed keys: {self.OPTIMIZABLE_VALS}" - ) - if v["type"] not in ["cyclic", "plateau", "exp", "gamma", "linear", "cosine_annealing", "none"]: - raise ValueError( - f"Unknown scheduler type: {v['type']}, expected one of ['cyclic', 'plateau', 'exp', 'gamma', 'none']" - ) - self._scheduler_params = d - - @property - def schedulers( - self, - ) -> dict[ - str, - ( - torch.optim.lr_scheduler.CyclicLR - | torch.optim.lr_scheduler.ReduceLROnPlateau - | torch.optim.lr_scheduler.ExponentialLR - | torch.optim.lr_scheduler.LinearLR - | torch.optim.lr_scheduler.CosineAnnealingLR - | None - ), - ]: - return self._schedulers - - def set_schedulers( - self, - params: dict[str, dict], - num_iter: int | None = None, - ): - """ - TODO allow for new schedulers to be passed in when adding new optimizers without - removing the old schedulers or overwrtiting them. Not entirely sure what usecases there - will be for this. - - Sets the schedulers for the optimizer from a dictionary. Expects a dictionary of the form: - { - "optimizable_key1": { - "type": "scheduler_type", - "scheduler_kwarg": scheduler_kwarg_value, - ... - }, - "optimizable_key2": { - "type": "scheduler_type", - "scheduler_kwarg": scheduler_kwarg_value, - ... - }, - ... - } - where the keys are the same as the keys in self.OPTIMIZABLE_VALS. - - The scheduler type can be one of the following: - - "cyclic" - - "plateau" or "reducelronplateau" - - "exponential" - - None - - The num_iter kwarg is only used for exponential schedulers and if a "factor" is given - as a scheduler_kwarg instead of gamma. In that case, the gamma is calculated from num_iter - and the factor. - - TODO could update this to allow passing key:optimizer directly, would likely need to - rewrite get_schedulers to check the tpye - """ - if not any(self.optimizers): - raise NameError("self.optimizers have not yet been set.") - self._schedulers = self._get_schedulers( - params=params, - optimizers=self.optimizers, - num_iter=num_iter, - ) - - def _get_schedulers( - self, - params: dict[str, dict], - optimizers: dict, - num_iter: int | None = None, - ) -> dict[ - str, - ( - torch.optim.lr_scheduler.CyclicLR - | torch.optim.lr_scheduler.ReduceLROnPlateau - | torch.optim.lr_scheduler.ExponentialLR - | None - ), - ]: - """ - return schedulers for a given set of optimizers. Kept seperate from schedulers.setter so - that it can be called for pre-training - """ - schedulers = {} - for opt_key, p in params.items(): - if not any(p): - continue - elif opt_key not in self.OPTIMIZABLE_VALS: - raise KeyError( - f"Scheduler got bad key {opt_key}, schedulers can only be attached to one of {self.OPTIMIZABLE_VALS}" - ) - elif opt_key not in optimizers.keys(): - raise KeyError(f"optimizers does not have an optimizer for: {opt_key}") - else: - schedulers[opt_key] = self._get_scheduler( - optimizer=optimizers[opt_key], params=p, num_iter=num_iter - ) - return schedulers - - def _get_scheduler( - self, - optimizer: torch.optim.Adam, - params: dict[str, Any] | torch.optim.lr_scheduler._LRScheduler, - num_iter: int | None = None, - ) -> ( - torch.optim.lr_scheduler._LRScheduler - | torch.optim.lr_scheduler.CyclicLR - | torch.optim.lr_scheduler.ReduceLROnPlateau - | torch.optim.lr_scheduler.ExponentialLR - | None - ): - if isinstance(params, torch.optim.lr_scheduler._LRScheduler): - return params - - sched_type: str = params["type"].lower() - base_LR = optimizer.param_groups[0]["lr"] - if sched_type == "none": - scheduler = None - elif sched_type == "linear": - scheduler = torch.optim.lr_scheduler.LinearLR( - optimizer, - start_factor=params.get("start_factor", 0.1), - total_iters=params.get("total_iters", num_iter), - ) - elif sched_type == "cosine_annealing": - scheduler = torch.optim.lr_scheduler.CosineAnnealingLR( - optimizer, - T_max=params.get("T_max", num_iter), - eta_min=params.get("eta_min", base_LR / 100), - ) - elif sched_type == "cyclic": - scheduler = torch.optim.lr_scheduler.CyclicLR( - optimizer, - base_lr=params.get("base_lr", base_LR / 4), - max_lr=params.get("max_lr", base_LR * 4), - step_size_up=params.get("step_size_up", 100), - mode=params.get("mode", "triangular2"), - cycle_momentum=params.get("momentum", False), - ) - elif sched_type.startswith(("plat", "reducelronplat")): - scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau( - optimizer, - mode="min", - factor=params.get("factor", 0.5), - patience=params.get("patience", 10), - threshold=params.get("threshold", 1e-3), - min_lr=params.get("min_lr", base_LR / 20), - cooldown=params.get("cooldown", 50), - ) - elif sched_type in ["exp", "gamma", "exponential"]: - if "gamma" in params.keys(): - gamma = params["gamma"] - elif num_iter is not None: - fac = params.get("factor", 0.01) - gamma = fac ** (1.0 / num_iter) - else: - gamma = 0.999 - scheduler = torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma=gamma) - else: - raise ValueError(f"Unknown scheduler type: {sched_type}") - return scheduler diff --git a/src/quantem/tomography/utils.py b/src/quantem/tomography/utils.py index 2725fe20..e69de29b 100644 --- a/src/quantem/tomography/utils.py +++ b/src/quantem/tomography/utils.py @@ -1,300 +0,0 @@ -import numpy as np -import torch -import torch.nn.functional as F -from scipy.ndimage import center_of_mass, gaussian_filter, shift -from scipy.stats import norm -from tqdm.auto import tqdm - -from quantem.core.utils.imaging_utils import cross_correlation_shift - -# --- Projection Operator Utils --- - - -def rot_ZXZ(mags, z1, x, z3, device, mode="bilinear"): - if not isinstance(x, torch.Tensor) or not isinstance(z1, torch.Tensor): - z1 = torch.tensor(z1, dtype=torch.float32, device=device) - x = torch.tensor(x, dtype=torch.float32, device=device) - z3 = torch.tensor(z3, dtype=torch.float32, device=device) - curr_mags = mags - - curr_mags = differentiable_rotz_vectorized(curr_mags, z1, mode) - curr_mags = differentiable_rotx_vectorized(curr_mags, x, mode) - - curr_mags = differentiable_rotz_vectorized(curr_mags, z3, mode) - - return curr_mags - - -def differentiable_rotz_vectorized(mags, theta, mode="bilinear"): - _, dimz, dimy, dimx = mags.shape - - if theta.dim() == 0: - theta = theta.unsqueeze(0) - - theta_rad = torch.deg2rad(theta) - - cos_t, sin_t = torch.cos(theta_rad), torch.sin(theta_rad) - affine_matrix = torch.stack( - [cos_t, -sin_t, torch.zeros_like(theta), sin_t, cos_t, torch.zeros_like(theta)], dim=-1 - ).view(-1, 2, 3) - - mags = mags.permute(1, 0, 2, 3) - - def transform_slice(mag_slice): - grid = F.affine_grid(affine_matrix, mag_slice.unsqueeze(0).shape, align_corners=False) - return F.grid_sample(mag_slice.unsqueeze(0), grid, mode=mode, align_corners=False).squeeze( - 0 - ) - - rotated_mags = torch.vmap(transform_slice)(mags) - return rotated_mags.permute(1, 0, 2, 3) - - -def differentiable_rotx_vectorized(mags, theta, mode="bilinear"): - _, dimz, dimy, dimx = mags.shape - - if theta.dim() == 0: - theta = theta.unsqueeze(0) - - theta_rad = torch.deg2rad(theta) - - cos_t, sin_t = torch.cos(theta_rad), torch.sin(theta_rad) - affine_matrix = torch.stack( - [cos_t, -sin_t, torch.zeros_like(theta), sin_t, cos_t, torch.zeros_like(theta)], dim=-1 - ).view(-1, 2, 3) - - mags = mags.permute(3, 0, 1, 2) - - def transform_slice(mag_slice): - grid = F.affine_grid(affine_matrix, mag_slice.unsqueeze(0).shape, align_corners=False) - return F.grid_sample(mag_slice.unsqueeze(0), grid, mode=mode, align_corners=False).squeeze( - 0 - ) - - rotated_mags = torch.vmap(transform_slice)(mags) - return rotated_mags.permute(1, 2, 3, 0) - - -def differentiable_shift_2d(image, shift_x, shift_y, sampling_rate): - """ - Shifts a 2D image using grid_sample in a differentiable manner. - - Args: - image: Tensor of shape [H, W] - shift_x: Scalar tensor (dx) for shift in x-direction (in physical units) - shift_y: Scalar tensor (dy) for shift in y-direction (in physical units) - sampling_rate: Scalar value (physical units per pixel) to correctly normalize shifts - - Returns: - Shifted image of shape [H, W] - """ - H, W = image.shape - - # Convert physical shift to pixel shift - shift_x_pixel = shift_x - shift_y_pixel = shift_y - - # Normalize shift for grid_sample (assuming align_corners=True) - normalized_shift_x = shift_x_pixel * 2 / (W - 1) - normalized_shift_y = shift_y_pixel * 2 / (H - 1) - - # Create normalized grid - grid_y, grid_x = torch.meshgrid( - torch.linspace(-1, 1, H, device=image.device), - torch.linspace(-1, 1, W, device=image.device), - indexing="ij", - ) - - grid = torch.stack((grid_x, grid_y), dim=-1).unsqueeze(0) # [1, H, W, 2] - - # Apply shift (ensure it's differentiable) - grid[:, :, :, 0] -= normalized_shift_x - grid[:, :, :, 1] -= normalized_shift_y - - # Add batch and channel dimensions - image = image.unsqueeze(0).unsqueeze(0) # [1, 1, H, W] - - # Sample using grid_sample (fully differentiable) - shifted_image = F.grid_sample( - image, grid, mode="bicubic", padding_mode="zeros", align_corners=True - ) - - return shifted_image.squeeze(0).squeeze(0) # Back to [H, W] - - -# --- TV loss --- - - -def get_TV_loss(tensor, factor=1e-3): - tv_d = torch.pow(tensor[:, :, 1:, :, :] - tensor[:, :, :-1, :, :], 2).sum() - tv_h = torch.pow(tensor[:, :, :, 1:, :] - tensor[:, :, :, :-1, :], 2).sum() - tv_w = torch.pow(tensor[:, :, :, :, 1:] - tensor[:, :, :, :, :-1], 2).sum() - tv_loss = tv_d + tv_h + tv_w - - return tv_loss * factor / (torch.prod(torch.tensor(tensor.shape))) - - -# --- Gaussian filters --- - - -def gaussian_kernel_1d(sigma: float, num_sigmas: float = 3.0) -> torch.Tensor: - radius = np.ceil(num_sigmas * sigma) - support = torch.arange(-radius, radius + 1, dtype=torch.float) - kernel = torch.distributions.Normal(loc=0, scale=sigma).log_prob(support).exp_() - # Ensure kernel weights sum to 1, so that image brightness is not altered - return kernel.mul_(1 / kernel.sum()) - - -def gaussian_filter_2d( - img: torch.Tensor, sigma: float, kernel_1d: torch.Tensor -) -> torch.Tensor: # Add kernel_1d as an argument - # kernel_1d = gaussian_kernel_1d(sigma) # Create 1D Gaussian kernel - Moved outside function - padding = len(kernel_1d) // 2 # Ensure that image size does not change - img = img.unsqueeze(0).unsqueeze_(0) # Make copy, make 4D for ``conv2d()`` - # Convolve along columns and rows - img = torch.nn.functional.conv2d(img, weight=kernel_1d.view(1, 1, -1, 1), padding=(padding, 0)) - img = torch.nn.functional.conv2d(img, weight=kernel_1d.view(1, 1, 1, -1), padding=(0, padding)) - return img.squeeze_(0).squeeze_(0) # Make 2D again - - -def gaussian_filter_2d_stack(stack: torch.Tensor, kernel_1d: torch.Tensor) -> torch.Tensor: - """ - Apply 2D Gaussian blur to each slice stack[:, i, :] in a vectorized way. - - Args: - stack (torch.Tensor): Tensor of shape (H, N, W) where N is num_sinograms - kernel_1d (torch.Tensor): 1D Gaussian kernel - - Returns: - torch.Tensor: Blurred stack of same shape (H, N, W) - """ - H, N, W = stack.shape - padding = len(kernel_1d) // 2 - - # Reshape to (N, 1, H, W) for conv2d - stack_reshaped = stack.permute(1, 0, 2).unsqueeze(1) # (N, 1, H, W) - - # Apply separable conv2d: vertical then horizontal - out = torch.nn.functional.conv2d( - stack_reshaped, kernel_1d.view(1, 1, -1, 1), padding=(padding, 0) - ) - out = torch.nn.functional.conv2d(out, kernel_1d.view(1, 1, 1, -1), padding=(0, padding)) - - # Restore shape to (H, N, W) - return out.squeeze(1).permute(1, 0, 2) - - -# Circular mask - - -def torch_phase_cross_correlation(im1, im2): - f1 = torch.fft.fft2(im1) - f2 = torch.fft.fft2(im2) - cc = torch.fft.ifft2(f1 * torch.conj(f2)) - cc_abs = torch.abs(cc) - - max_idx = torch.argmax(cc_abs) - shifts = torch.tensor(np.unravel_index(max_idx.item(), im1.shape), device=im1.device).float() - - for i, dim in enumerate(im1.shape): - if shifts[i] > dim // 2: - shifts[i] -= dim - - # return shifts.flip(0) # (dx, dy) - return shifts - - -# --- Tilt Series Processing Utility Functions --- - - -def fourier_cropping(img, crop_size): - """ - Crop the img in Fourier space to the specified size. - """ - center = np.array(img.shape) // 2 - - fft_img = np.fft.fftshift(np.fft.fft2(img)) - - cropped_fft = fft_img[ - center[0] - crop_size[0] // 2 : center[0] + crop_size[0] // 2, - center[1] - crop_size[1] // 2 : center[1] + crop_size[1] // 2, - ] - cropped_img = np.fft.ifft2(np.fft.ifftshift(cropped_fft)).real - return cropped_img - - -def estimate_background( - img, - num_iterations=10, - cutoff=3, - smoothing_sigma=1.0, -): - """ - Estimate the background of the image using a Gaussian filter. - """ - if smoothing_sigma > 0: - img = gaussian_filter(img, sigma=smoothing_sigma) - pixel_vals = img.ravel() - - for i in range(num_iterations): - mu, std = norm.fit(pixel_vals) - - # Set cutoff threshold (e.g., 3 standard deviations) - lower = mu - cutoff * std - upper = mu + cutoff * std - - # Mask pixel values within ±3σ - pixel_vals = pixel_vals[(pixel_vals >= lower) & (pixel_vals <= upper)] - - return mu - - -def cross_correlation_align_stack(ref_img, stack, print_pred = False): - """ - Aligns a stack of images to a reference image using cross-correlation. - - This function assumes the stack does not contain the reference image itself. - - Stack shape should be (N, H, W) where N is the number of images. - """ - - new_images = [] - pred_shifts = [] - - prev_img = ref_img - for img in tqdm(stack): - shift_pred = cross_correlation_shift(prev_img, img) - if print_pred: - print(f'Shift prediction: {shift_pred}') - shifted_image = shift(img, shift=shift_pred, mode="constant", cval=0.0) - - pred_shifts.append(shift_pred) - new_images.append(shifted_image) - - prev_img = shifted_image - - return new_images, pred_shifts - - -def centering_com_alignment(image_stack): - """ - Aligns the image stack to the center of mass of the whole image_stack to the - image center. This is useful for aligning the tilt series to the invariant line. - """ - - aligned_stack = np.zeros_like(image_stack) - h, w = image_stack.shape[1:] - image_center = np.array([h // 2, w // 2]) - - com_reference = np.array(center_of_mass(image_stack.mean(axis=0))) - - for i, img in enumerate(image_stack): - com_img = np.array(center_of_mass(img)) - shift_vec = com_reference - com_img - aligned_stack[i] = shift(img, shift=shift_vec, mode="constant", cval=0.0) - - final_shift = image_center - com_reference - for i in range(aligned_stack.shape[0]): - aligned_stack[i] = shift(aligned_stack[i], shift=final_shift, mode="constant", cval=0.0) - - return aligned_stack diff --git a/src/quantem/tomography/preprocess/__init__.py b/src/quantem/tomography_old/__init__.py similarity index 100% rename from src/quantem/tomography/preprocess/__init__.py rename to src/quantem/tomography_old/__init__.py diff --git a/src/quantem/tomography_old/models.py b/src/quantem/tomography_old/models.py new file mode 100644 index 00000000..a972400c --- /dev/null +++ b/src/quantem/tomography_old/models.py @@ -0,0 +1,96 @@ +import numpy as np +import torch +from torch import nn + + +class SineLayer(nn.Module): + def __init__( + self, + in_features, + out_features, + bias=True, + is_first=False, + omega_0=30, + hsiren=False, + alpha=1.0, + ): + super().__init__() + self.omega_0 = omega_0 + self.is_first = is_first + self.hsiren = hsiren + self.in_features = in_features + self.alpha = alpha + self.linear = nn.Linear(in_features, out_features, bias=bias) + self.init_weights() + + def init_weights(self): + with torch.no_grad(): + if self.is_first: + # Scale the first layer initialization by alpha + self.linear.weight.uniform_( + -self.alpha / self.in_features, self.alpha / self.in_features + ) + else: + # Scale the hidden layer initialization by alpha + self.linear.weight.uniform_( + -self.alpha * np.sqrt(6 / self.in_features) / self.omega_0, + self.alpha * np.sqrt(6 / self.in_features) / self.omega_0, + ) + + def forward(self, input): + if self.is_first and self.hsiren: + out = torch.sin(self.omega_0 * torch.sinh(2 * self.linear(input))) + else: + out = torch.sin(self.omega_0 * self.linear(input)) + return out + + +class HSiren(nn.Module): + def __init__( + self, + in_features=2, + out_features=3, + hidden_layers=3, + hidden_features=256, + first_omega_0=30, + hidden_omega_0=30, + alpha=1.0, + ): + super().__init__() + self.net_list = [] + self.net_list.append( + SineLayer( + in_features, + hidden_features, + is_first=True, + omega_0=first_omega_0, + hsiren=True, + alpha=alpha, + ) + ) + + for i in range(hidden_layers): + self.net_list.append( + SineLayer( + hidden_features, + hidden_features, + is_first=False, + omega_0=hidden_omega_0, + alpha=alpha, + ) + ) + + final_linear = nn.Linear(hidden_features, out_features) + with torch.no_grad(): + # Final layer keeps original initialization (no alpha scaling) + final_linear.weight.uniform_( + -np.sqrt(6 / hidden_features) / hidden_omega_0, + np.sqrt(6 / hidden_features) / hidden_omega_0, + ) + self.net_list.append(final_linear) + self.net_list.append(nn.Softplus()) + self.net = nn.Sequential(*self.net_list) + + def forward(self, coords): + output = self.net(coords) + return output diff --git a/src/quantem/tomography_old/object_models.py b/src/quantem/tomography_old/object_models.py new file mode 100644 index 00000000..57192e27 --- /dev/null +++ b/src/quantem/tomography_old/object_models.py @@ -0,0 +1,723 @@ +from abc import abstractmethod +from copy import deepcopy +from typing import Any, Callable + +import numpy as np +import torch +import torch.distributed as dist +import torch.nn as nn +from tqdm.auto import tqdm + +from quantem.core.io.serialize import AutoSerialize +from quantem.core.ml.blocks import reset_weights +from quantem.core.utils.validators import validate_gt, validate_tensor +from quantem.tomography.utils import get_TV_loss + + +class ObjectBase(AutoSerialize): + """ + Base class for all ObjectModels to inherit from. + """ + + def __init__( + self, + volume_shape: tuple[int, int, int], + device: str, + offset_obj: float = 1e-5, + ): + self._shape = volume_shape + + self._obj = torch.zeros(self._shape, device=device, dtype=torch.float32) + offset_obj + self._offset_obj = offset_obj + self._device = device + self._hard_constraints = {} + self._soft_constraints = {} # One big dicitonary + + @property + def shape(self) -> tuple[int, int, int]: + return self._shape + + @shape.setter + def shape(self, shape: tuple[int, int, int]): + self._shape = shape + + @property + def offset_obj(self) -> float: + return self._offset_obj + + @offset_obj.setter + def offset_obj(self, offset_obj: float): + self._offset_obj = offset_obj + + @property + def obj(self) -> torch.Tensor: + pass + + @obj.setter + def obj(self, obj: torch.Tensor): + self._obj = obj + + @property + def device(self) -> str: + return self._device + + @device.setter + def device(self, device: str): + self._device = device + + @abstractmethod + def forward( + self, z1: torch.Tensor, z3: torch.Tensor, shift_x: torch.Tensor, shift_y: torch.Tensor + ): + pass + + @abstractmethod + def obj(self): + pass + + @abstractmethod + def reset(self): + pass + + @abstractmethod + def to(self, device: str): + pass + + @abstractmethod + def name(self) -> str: + pass + + @abstractmethod + def params(self) -> torch.Tensor: + pass + + +class ObjectConstraints(ObjectBase): + DEFAULT_HARD_CONSTRAINTS = { + "fourier_filter": False, + "positivity": False, + "shrinkage": False, + "circular_mask": False, + } + + DEFAULT_SOFT_CONSTRAINTS = { + "tv_vol": 0, + } + + @property + def hard_constraints(self) -> dict[str, Any]: + return self._hard_constraints + + @hard_constraints.setter + def hard_constraints(self, hard_constraints: dict[str, Any]): + gkeys = self.DEFAULT_HARD_CONSTRAINTS.keys() + for key, value in hard_constraints.items(): + if key not in gkeys: # This might be redundant since add_constraint is checking. + raise KeyError(f"Invalid object constraint key '{key}', allowed keys are {gkeys}") + self._hard_constraints[key] = value + + @property + def soft_constraints(self) -> dict[str, Any]: + return self._soft_constraints + + @soft_constraints.setter + def soft_constraints(self, soft_constraints: dict[str, Any]): + gkeys = self.DEFAULT_SOFT_CONSTRAINTS.keys() + for key, value in soft_constraints.items(): + if key not in gkeys: + raise KeyError(f"Invalid object constraint key '{key}', allowed keys are {gkeys}") + self._soft_constraints[key] = value + + def add_hard_constraint(self, constraint: str, value: Any): + """Add constraints to the object model.""" + gkeys = self.DEFAULT_HARD_CONSTRAINTS.keys() + if constraint not in gkeys: + raise KeyError( + f"Invalid object constraint key '{constraint}', allowed keys are {gkeys}" + ) + self._hard_constraints[constraint] = value + + def add_soft_constraint(self, constraint: str, value: Any): + """Add constraints to the object model.""" + gkeys = self.DEFAULT_SOFT_CONSTRAINTS.keys() + if constraint not in gkeys: + raise KeyError( + f"Invalid object constraint key '{constraint}', allowed keys are {gkeys}" + ) + self._soft_constraints[constraint] = value + + def apply_hard_constraints( + self, + obj: torch.Tensor, + ) -> torch.Tensor: + """ + Apply constraints to the object model. + """ + obj2 = obj.clone() + if self.hard_constraints["positivity"]: + obj2 = torch.clamp(obj, min=0.0, max=None) + if self.hard_constraints["shrinkage"]: + obj2 = torch.max(obj2 - self.hard_constraints["shrinkage"], torch.zeros_like(obj2)) + + return obj2 + + def apply_soft_constraints( + self, + obj: torch.Tensor, + ) -> torch.Tensor: + """ + 'Applies' soft constraints to the object model. This will return additional loss terms. + """ + soft_loss = torch.tensor(0.0, device=obj.device, dtype=obj.dtype, requires_grad=True) + if self.soft_constraints["tv_vol"] > 0: + tv_loss = get_TV_loss( + obj.unsqueeze(0).unsqueeze(0), factor=self.soft_constraints["tv_vol"] + ) + + soft_loss += tv_loss + + return soft_loss + + +class ObjectVoxelwise(ObjectConstraints): + """ + Object model for voxelwise objects. + """ + + def __init__( + self, + volume_shape: tuple[int, int, int], + device: str, + initial_volume: torch.Tensor | None = None, + ): + super().__init__( + volume_shape=volume_shape, + device=device, + ) + self.hard_constraints = self.DEFAULT_HARD_CONSTRAINTS.copy() + self.soft_constraints = self.DEFAULT_SOFT_CONSTRAINTS.copy() + + if initial_volume is not None: + self._initial_obj = initial_volume + else: + self.initial_obj = ( + torch.zeros(self._shape, device=self._device, dtype=torch.float32) + + self.offset_obj + ) + + @property + def obj(self): + return self.apply_hard_constraints(self._obj) + + @obj.setter + def obj(self, obj: torch.Tensor): + self._obj = obj + + @property + def initial_obj(self): + return self._initial_obj + + @initial_obj.setter + def initial_obj(self, initial_obj: torch.Tensor): + if not isinstance(initial_obj, torch.Tensor): + raise ValueError("initial_obj must be a torch.Tensor") + + self._initial_obj = initial_obj + + def forward(self): + return self.obj + + def reset(self): + self._obj = ( + torch.zeros(self._shape, device=self._device, dtype=torch.float32) + self.offset_obj + ) + + def to(self, device: str): + self._device = device + self._obj = self._obj.to(self._device) + + @property + def name(self) -> str: + return "ObjectVoxelwise" + + @property + def params(self) -> torch.Tensor: + return self._obj + + @property + def soft_loss(self) -> torch.Tensor: + return self.apply_soft_constraints(self._obj) + + +class ObjectDIP(ObjectConstraints): + """ + Object model for DIP objects. + """ + + def __init__( + self, + model: torch.nn.Module, + volume_shape: tuple[int, int, int], + model_input: torch.Tensor + | None = None, # Determines output size, model input pretraining target + input_noise_std: float = 0.0, + device: str = "cpu", + ): + super().__init__( + volume_shape=volume_shape, + device=device, + ) + self.hard_constraints = self.DEFAULT_HARD_CONSTRAINTS.copy() + self.soft_constraints = self.DEFAULT_SOFT_CONSTRAINTS.copy() + + if model_input is None: + self.model_input = torch.randn(1, 1, volume_shape[0], volume_shape[1], volume_shape[2]) + else: + self.model_input = model_input.clone().detach() + + self.pretrain_target = model_input.clone().detach() + + self._model = model + self._optimizer = None + self._scheduler = None + self._pretrain_losses = [] + self._pretrain_lrs = [] + self._model_input_noise_std = input_noise_std + + @property + def name(self) -> str: + return "ObjectDIP" + + @property + def model(self) -> torch.nn.Module: + return self._model + + @model.setter + def model(self, model: torch.nn.Module): + if not isinstance(model, torch.nn.Module): + raise TypeError(f"Model must be a torch.nn.Module, got {type(model)}") + self._model = model.to(self._device) + self.set_pretrained_weights(self._model) + + @property + def pretrained_weights(self) -> dict[str, torch.Tensor]: + return self._pretrained_weights + + def set_pretrained_weights(self, model: torch.nn.Module): + if not isinstance(model, torch.nn.Module): + raise TypeError(f"Pretrained model must be a torch.nn.Module, got {type(model)}") + self._pretrained_weights = deepcopy(model.state_dict()) + + @property + def model_input(self) -> torch.Tensor: + return self._model_input + + @model_input.setter + def model_input(self, input_tensor: torch.Tensor): + inp = validate_tensor( + input_tensor, + name="model_input", + dtype=torch.float32, + ndim=5, + expand_dims=True, + ) + self._model_input = inp.to(self._device) + + @property + def pretrain_target(self) -> torch.Tensor: + return self._pretrain_target + + @pretrain_target.setter + def pretrain_target(self, target: torch.Tensor): + if target.ndim == 5: + target = target.squeeze(0).squeeze(0) + + target = validate_tensor( + target, + name="pretrain_target", + ndim=3, + dtype=torch.float32, + expand_dims=True, + ) + if target.shape[-3:] != self.model_input.shape[-3:]: + raise ValueError( + f"Pretrain target shape {target.shape} does not match model input shape {self.model_input.shape}" + ) + self._pretrain_target = target.to(self._device) + + @property + def _model_input_noise_std(self) -> float: + """standard deviation of the gaussian noise added to the model input each forward call""" + return self._input_noise_std + + @_model_input_noise_std.setter + def _model_input_noise_std(self, std: float): + validate_gt(std, 0.0, "input_noise_std", geq=True) + self._input_noise_std = std + + @property + def optimizer(self) -> torch.optim.Optimizer: + """get the optimizer for the DIP model""" + if self._optimizer is None: + raise ValueError("Optimizer is not set. Use set_optimizer() to set it.") + return self._optimizer + + def set_optimizer(self, opt_params: dict): + opt_type = opt_params.pop("type") + if isinstance(opt_type, torch.optim.Optimizer): + self._optimizer = opt_type + elif isinstance(opt_type, type): + self._optimizer = opt_type(self.model.parameters(), **opt_params) + elif opt_type == "adam": + self._optimizer = torch.optim.Adam(self.model.parameters(), **opt_params) + elif opt_type == "adamw": + self._optimizer = torch.optim.AdamW(self.model.parameters(), **opt_params) + elif opt_type == "sgd": + self._optimizer = torch.optim.SGD(self.model.parameters(), **opt_params) + else: + raise NotImplementedError(f"Unknown optimizer type: {opt_params['type']}") + + @property + def scheduler( + self, + ) -> ( + torch.optim.lr_scheduler._LRScheduler + | torch.optim.lr_scheduler.CyclicLR + | torch.optim.lr_scheduler.ReduceLROnPlateau + | torch.optim.lr_scheduler.ExponentialLR + | None + ): + return self._scheduler + + def set_scheduler(self, params: dict, num_iter: int | None = None) -> None: + sched_type: str = params["type"].lower() + optimizer = self.optimizer + base_LR = optimizer.param_groups[0]["lr"] + if sched_type == "none": + scheduler = None + elif sched_type == "cyclic": + scheduler = torch.optim.lr_scheduler.CyclicLR( + optimizer, + base_lr=params.get("base_lr", base_LR / 4), + max_lr=params.get("max_lr", base_LR * 4), + step_size_up=params.get("step_size_up", 100), + mode=params.get("mode", "triangular2"), + cycle_momentum=params.get("momentum", False), + ) + elif sched_type.startswith(("plat", "reducelronplat")): + scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau( + optimizer, + mode="min", + factor=params.get("factor", 0.5), + patience=params.get("patience", 10), + threshold=params.get("threshold", 1e-3), + min_lr=params.get("min_lr", base_LR / 20), + cooldown=params.get("cooldown", 20), + ) + elif sched_type in ["exp", "gamma", "exponential"]: + if "gamma" in params.keys(): + gamma = params["gamma"] + elif num_iter is not None: + fac = params.get("factor", 0.01) + gamma = fac ** (1.0 / num_iter) + else: + gamma = 0.999 + scheduler = torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma=gamma) + else: + raise ValueError(f"Unknown scheduler type: {sched_type}") + self._scheduler = scheduler + + @property + def pretrain_losses(self) -> np.ndarray: + return np.array(self._pretrain_losses) + + @property + def pretrain_lrs(self) -> np.ndarray: + return np.array(self._pretrain_lrs) + + @property + def obj(self): + obj = self.model(self._model_input)[0] + return self.apply_hard_constraints(obj) + + def forward(self): + return self.model(self._model_input) + + def to(self, device: str): + self.device = device + self._model = self._model.to(self.device) + self._model_input = self._model_input.to(self.device) + self._pretrain_target = self._pretrain_target.to(self.device) + + @property + def params(self): + return self._model.parameters() + + def reset(self): + self.model.load_state_dict(self.pretrained_weights.copy()) + + def pretrain( + self, + model_input: torch.Tensor, + pretrain_target: torch.Tensor, + reset: bool = True, + num_epochs: int = 100, + optimizer_params: dict | None = None, + scheduler_params: dict | None = None, + loss_fn: Callable | str = "l2", + apply_constraints: bool = False, + show: bool = True, + ): + model_input.to(self.device) + pretrain_target.to(self.device) + + if optimizer_params is not None: + self.set_optimizer(optimizer_params) + + if scheduler_params is not None: + self.set_scheduler(scheduler_params, num_epochs) + + if reset: + self._model.apply(reset_weights) + self._pretrain_losses = [] + self._pretrain_lrs = [] + + if model_input is not None: + self.model_input = model_input + + if pretrain_target.shape[-3:] != self.model_input.shape[-3:]: + raise ValueError( + f"Pretrain target shape {pretrain_target.shape} does not match model input shape {self.model_input.shape}" + ) + self.pretrain_target = pretrain_target.clone().detach().to(self.device) + + loss_fn = torch.nn.functional.mse_loss + + self._pretrain( + num_epochs=num_epochs, + loss_fn=loss_fn, + apply_constraints=apply_constraints, + show=show, + ) + self.set_pretrained_weights(self.model) + + def _pretrain( + self, + num_epochs: int, + loss_fn: Callable, + apply_constraints: bool = False, + show: bool = False, + ): + if not hasattr(self, "pretrain_target"): + raise ValueError("Pretrain target is not set. Use pretrain_target to set it.") + + self.model.train() + optimizer = self.optimizer + sch = self.scheduler + pbar = tqdm(range(num_epochs)) + output = self.obj + + for a0 in pbar: + if apply_constraints: + output = self.obj + else: + output = self.model(self.model_input).squeeze(0).squeeze(0) + + loss = loss_fn(output, self.pretrain_target) + loss.backward() + optimizer.step() + optimizer.zero_grad() + + if sch is not None: + if isinstance(sch, torch.optim.lr_scheduler.ReduceLROnPlateau): + sch.step(loss.item()) + else: + sch.step() + + self._pretrain_losses.append(loss.item()) + self._pretrain_lrs.append(optimizer.param_groups[0]["lr"]) + pbar.set_description(f"Epoch {a0 + 1}/{num_epochs}, Loss: {loss.item():.4f}, ") + + +# INR stuff + + +class ObjectINN(ObjectConstraints): + """ + Object model for INN objects. + + - VolumeDataset (?) dependent on the object + """ + + def __init__( + self, + model: nn.Module, + volume_shape=tuple[int, int, int], + device: str = "cuda", + ): + super().__init__( + volume_shape=volume_shape, + device=device, + offset_obj=0, + ) + + self._model = model + + # --- Properties --- + @property + def obj(self): + return self._obj + + def create_volume( + self, + world_size: int, + global_rank: int, + ray_size: int, + ): + N = max(self._shape) + with torch.no_grad(): + coords_1d = torch.linspace(-1, 1, N) + x, y, z = torch.meshgrid(coords_1d, coords_1d, coords_1d, indexing="ij") + inputs = torch.stack([x, y, z], dim=-1).reshape(-1, 3) + + # Underlying model if using DDP + model = self.model.module if hasattr(self.model, "module") else self.model + + # Batch size for inference + # 5x larger batches no gradients needed + + inference_batch_size = 5 * N * ray_size + + # Distribute work across GPUs + total_samples = N**3 + samples_per_gpu = total_samples // world_size + remainder = total_samples % world_size + + # Handle uneven distribution + if global_rank < remainder: + start_idx = global_rank * (samples_per_gpu + 1) + end_idx = start_idx + samples_per_gpu + 1 + else: + start_idx = global_rank * samples_per_gpu + remainder + end_idx = start_idx + samples_per_gpu + + inputs_subset = inputs[start_idx:end_idx] + num_samples = inputs_subset.shape[0] + outputs_list = [] + + for batch_start in range(0, num_samples, inference_batch_size): + batch_end = min(batch_start + inference_batch_size, num_samples) + batch_coords = inputs_subset[batch_start:batch_end].to( + self.device, non_blocking=True + ) + + batch_outputs = model(batch_coords) + if batch_outputs.dim() > 1: + batch_outputs = batch_outputs.squeeze(-1) + + outputs_list.append(batch_outputs.cpu()) + + outputs = torch.cat(outputs_list, dim=0) + + if world_size > 1: + # Gather from all ranks + # handle potentially different sizes + output_size = torch.tensor(outputs.shape[0], device=self.device, dtype=torch.long) + all_sizes = [ + torch.zeros(1, device=self.device, dtype=torch.long) for _ in range(world_size) + ] + dist.all_gather(all_sizes, output_size) + + # Create gather list with correct sizes + max_size = max(size.item() for size in all_sizes) + + # Pad if necessary for gathering + if outputs.shape[0] < max_size: + padding = torch.zeros( + max_size - outputs.shape[0], device=outputs.device, dtype=outputs.dtype + ) + outputs_padded = torch.cat([outputs, padding], dim=0).to(self.device) + else: + outputs_padded = outputs.to(self.device) + + gathered_outputs = [ + torch.empty(max_size, device=self.device, dtype=outputs.dtype) + for _ in range(world_size) + ] + dist.all_gather(gathered_outputs, outputs_padded.contiguous()) + + # Trim padding and concatenate + trimmed_outputs = [] + for rank, size in enumerate(all_sizes): + trimmed_outputs.append(gathered_outputs[rank][: size.item()]) + + pred_full = torch.cat(trimmed_outputs, dim=0).reshape(N, N, N).float() + else: + pred_full = outputs.reshape(N, N, N).float() + + self._obj = pred_full.detach().cpu() + + @property + def model(self): + return self._model + + @model.setter + def model(self, model): + self._model = model + + def apply_soft_constraints( + self, + coords: torch.Tensor, + ): + soft_loss = torch.tensor(0.0, device=coords.device) + if self.soft_constraints["tv_vol"] > 0: + num_tv_samples = min(10000, coords.shape[0]) + tv_indices = torch.randperm(coords.shape[0], device=coords.device)[:num_tv_samples] + + # Rerun forward for gradient tracking + tv_coords = coords[tv_indices].detach().requires_grad_(True) + + tv_densities_recomputed = self.model(tv_coords) + if tv_densities_recomputed.dim() > 1: + tv_densities_recomputed = tv_densities_recomputed.squeeze(-1) + + # Compute gradients + grad_outputs = torch.autograd.grad( + outputs=tv_densities_recomputed, + inputs=tv_coords, + grad_outputs=torch.ones_like(tv_densities_recomputed), + create_graph=True, + )[0] + + grad_norm = torch.norm(grad_outputs, dim=1) + soft_loss += self.soft_constraints["tv_vol"] * grad_norm.mean() + + return soft_loss + + def forward( + self, + all_coords: torch.Tensor, + ): + all_densities = self.model(all_coords) + + if all_densities.dim() > 1: + all_densities = all_densities.squeeze(-1) + + valid_mask = ( + (all_coords[:, 0] >= -1) + & (all_coords[:, 0] <= 1) + & (all_coords[:, 1] >= -1) + & (all_coords[:, 1] <= 1) + & (all_coords[:, 2] >= -1) + & (all_coords[:, 2] <= 1) + ).float() + + all_densities = all_densities * valid_mask + + return all_densities + + +ObjectModelType = ObjectVoxelwise | ObjectINN # | ObjectDIP | ObjectImplicit (ObjectFFN?) + + +# Pretrain Volume Dataset diff --git a/src/quantem/tomography/radon/__init__.py b/src/quantem/tomography_old/preprocess/__init__.py similarity index 100% rename from src/quantem/tomography/radon/__init__.py rename to src/quantem/tomography_old/preprocess/__init__.py diff --git a/src/quantem/tomography/preprocess/drift.py b/src/quantem/tomography_old/preprocess/drift.py similarity index 100% rename from src/quantem/tomography/preprocess/drift.py rename to src/quantem/tomography_old/preprocess/drift.py diff --git a/src/quantem/tomography_old/radon/__init__.py b/src/quantem/tomography_old/radon/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/quantem/tomography/radon/radon.py b/src/quantem/tomography_old/radon/radon.py similarity index 100% rename from src/quantem/tomography/radon/radon.py rename to src/quantem/tomography_old/radon/radon.py diff --git a/src/quantem/tomography_old/tomography.py b/src/quantem/tomography_old/tomography.py new file mode 100644 index 00000000..5064cd3f --- /dev/null +++ b/src/quantem/tomography_old/tomography.py @@ -0,0 +1,886 @@ +import matplotlib.pyplot as plt +import numpy as np +import torch +import torch.distributed as dist +from torch.nn import SmoothL1Loss +from torch.utils.tensorboard import SummaryWriter + +# from torch_radon.radon import ParallelBeam as Radon +from tqdm.auto import tqdm + +from quantem.core.ml.loss_functions import ( + CharbonnierLoss, + L1Loss, + LLMSELoss, + MSELogMSELoss, + MSELoss, +) + +# Temporary imports for TomographyNERF +from quantem.tomography.object_models import ObjectINN, ObjectVoxelwise +from quantem.tomography.tomography_base import TomographyBase +from quantem.tomography.tomography_conv import TomographyConv +from quantem.tomography.tomography_ddp import ( + AdaptiveSmoothL1LossDDP, + PretrainVolumeDataset, + TomographyDDP, +) +from quantem.tomography.tomography_ml import TomographyML +from quantem.tomography.utils import differentiable_shift_2d, gaussian_kernel_1d, rot_ZXZ + + +# Temporary aux class for TomographyNERF +# TODO: Maybe put this in INN? +def get_num_samples_per_ray(N: int, epoch: int): + """Increase number of samples per ray at specific epochs.""" + # Exponential schedule + + epochs = np.linspace(0, 10, 5, dtype=int) + # schedule = np.linspace(20, N, 5, dtype=int) + schedule = np.array([20, 100, 150, 200, 200]) + # schedule = np.array([200, 200, 200, 200, 200]) + # schedule = np.array([20, 100, 250, 500, 500]) + # schedule = np.array([500, 500, 500, 500, 500]) + # schedule = np.array([300, 300, 300, 300, 300]) + # schedule = np.exp(schedule) + schedule_warmup = dict[int, int](zip(epochs, schedule)) + + for epoch_threshold, samples in schedule_warmup.items(): + if epoch >= epoch_threshold: + num_samples = samples + + return num_samples + + +class Tomography(TomographyConv, TomographyML, TomographyBase, TomographyDDP): + """ + Top level class for either using conventional or ML-based reconstruction methods + for tomography. + """ + + def __init__( + self, + dataset, + volume_obj, + device, + _token, + ): + super().__init__(dataset, volume_obj, device, _token) + + # TODO: More elegant way of doing this. + self.global_epochs = 0 + self.ddp_instantiated = False + self.pretraining_instantiated = False + + # --- Reconstruction Method --- + + def sirt_recon( + self, + num_iterations: int = 10, + inline_alignment: bool = False, + enforce_positivity: bool = True, + volume_shape: tuple = None, + reset: bool = True, + smoothing_sigma: float = None, + shrinkage: float = None, + filter_name: str = "hamming", + circle: bool = True, + plot_loss: bool = False, + ): + num_angles, num_rows, num_cols = self.dataset.tilt_series.shape + sirt_tilt_series = self.dataset.tilt_series.clone() + sirt_tilt_series = sirt_tilt_series.permute(2, 0, 1) + + hard_constraints = { + "positivity": enforce_positivity, + "shrinkage": shrinkage, + } + self.volume_obj.hard_constraints = hard_constraints + + if volume_shape is None: + volume_shape = (num_rows, num_rows, num_rows) + else: + D, H, W = volume_shape + + if reset: + self.volume_obj.reset() + self.loss = [] + + proj_forward = torch.zeros_like(self.dataset.tilt_series) + + pbar = tqdm(range(num_iterations), desc="SIRT Reconstruction") + + if smoothing_sigma is not None: + gaussian_kernel = gaussian_kernel_1d(smoothing_sigma).to(self.device) + else: + gaussian_kernel = None + + print( + "Devices", + sirt_tilt_series.device, + proj_forward.device, + self.dataset.tilt_angles.device, + ) + + for iter in pbar: + proj_forward, loss = self._sirt_run_epoch( + tilt_series=sirt_tilt_series, + proj_forward=proj_forward, + angles=self.dataset.tilt_angles, + inline_alignment=iter > 0 and inline_alignment, + filter_name=filter_name, + gaussian_kernel=gaussian_kernel, + circle=circle, + ) + + pbar.set_description(f"SIRT Reconstruction | Loss: {loss.item():.4f}") + + self.loss.append(loss.item()) + + self.sirt_recon_vol = self.volume_obj + + # Permutation due to sinogram ordering. + self.sirt_recon_vol.obj = self.sirt_recon_vol.obj.permute(1, 2, 0) + + if plot_loss: + self.plot_loss() + + # TODO: ML Recon which has NeRF and AD depending on the object type. + # TODO: Temporary + + def create_batch_projection_rays(self, pixel_i, pixel_j, N, num_samples_per_ray): + """Create projection rays for entire batch simultaneously.""" + batch_size = len(pixel_i) + + # Convert all pixels to normalized coordinates + x_coords = (pixel_j / (N - 1)) * 2 - 1 + y_coords = (pixel_i / (N - 1)) * 2 - 1 + # TODO: maybe pixel_j.device? + # Create z coordinates + z_coords = torch.linspace(-1, 1, num_samples_per_ray, device=self.device) + + # Create rays for all pixels: [batch_size, num_samples_per_ray, 3] + rays = torch.zeros(batch_size, num_samples_per_ray, 3, device=self.device) + + # Fill coordinates efficiently + rays[:, :, 0] = x_coords.unsqueeze(1) # x constant per ray + rays[:, :, 1] = y_coords.unsqueeze(1) # y constant per ray + rays[:, :, 2] = z_coords.unsqueeze(0) # z varies along ray + + return rays + + @torch.compile(mode="reduce-overhead") + def transform_batch_ray_coordinates(self, rays, z1, x, z3, shifts, N, sampling_rate): + # Step 1: Apply shifts + shift_x_norm = (shifts[:, 0:1] * sampling_rate * 2) / (N - 1) + shift_y_norm = (shifts[:, 1:2] * sampling_rate * 2) / (N - 1) + + rays_x = rays[:, :, 0] - shift_x_norm + rays_y = rays[:, :, 1] - shift_y_norm + rays_z = rays[:, :, 2] + + # Rotation 1: Z(-z3) + theta = torch.deg2rad(-z3).view(-1, 1) + cos_t = torch.cos(theta) + sin_t = torch.sin(theta) + + rays_x_rot1 = cos_t * rays_x - sin_t * rays_y + rays_y_rot1 = sin_t * rays_x + cos_t * rays_y + rays_z_rot1 = rays_z + + # Rotation 2: X(x) + theta = torch.deg2rad(x).view(-1, 1) + cos_t = torch.cos(theta) + sin_t = torch.sin(theta) + + rays_x_rot2 = rays_x_rot1 + rays_y_rot2 = cos_t * rays_y_rot1 - sin_t * rays_z_rot1 + rays_z_rot2 = sin_t * rays_y_rot1 + cos_t * rays_z_rot1 + + # Rotation 3: Z(-z1) + theta = torch.deg2rad(-z1).view(-1, 1) + cos_t = torch.cos(theta) + sin_t = torch.sin(theta) + + rays_x_final = cos_t * rays_x_rot2 - sin_t * rays_y_rot2 + rays_y_final = sin_t * rays_x_rot2 + cos_t * rays_y_rot2 + rays_z_final = rays_z_rot2 + + # Stack the final result + transformed_rays = torch.stack([rays_x_final, rays_y_final, rays_z_final], dim=2) + + return transformed_rays + + # TODO: Temp logger + def setup_logger(self, log_path): + if self.global_rank == 0: + self.temp_logger = SummaryWriter(log_dir=log_path) + else: + self.temp_logger = None + + def pretrain( + self, + volume_dataset: PretrainVolumeDataset, + obj: ObjectINN, + batch_size: int, + soft_constraints: dict = None, + log_path: str = None, + optimizer_params: dict = None, + epochs: int = 100, + viz_freq: int = 1, + consistency_criterion: str = "mse", + ): + if not self.pretraining_instantiated: + self.setup_distributed() + self.setup_pretraining_dataloader(volume_dataset, batch_size) + self.pretraining_instantiated = True + obj.model = self.build_model(obj._model) + self.obj = obj + + if soft_constraints is not None: + self.obj.soft_constraints = soft_constraints + + if not hasattr(self, "temp_logger"): + self.setup_logger(log_path=log_path) + + if optimizer_params is not None: + optimizer_params = self.scale_lr(optimizer_params) + self.optimizer_params = optimizer_params + self.set_optimizers() + device_type = self.device.type + consistency_loss_fn = None + if consistency_criterion[0].lower() == "mse": + consistency_loss_fn = MSELoss() + elif consistency_criterion[0].lower() == "l1": + consistency_loss_fn = L1Loss() + elif consistency_criterion[0].lower() == "mse_log": + consistency_loss_fn = MSELogMSELoss() + elif consistency_criterion[0].lower() == "llmse": + consistency_loss_fn = LLMSELoss() + elif consistency_criterion[0].lower() == "smooth_l1": + consistency_loss_fn = SmoothL1Loss(beta=consistency_criterion[1]) + elif consistency_criterion[0].lower() == "adaptive_smooth_l1": + consistency_loss_fn = AdaptiveSmoothL1LossDDP( + beta_init=consistency_criterion[1], ema_factor=0.99, eps=1e-8 + ) + elif consistency_criterion[0].lower() == "charbonnier": + consistency_loss_fn = CharbonnierLoss(epsilon=1e-12, reduction="mean") + else: + raise ValueError(f"Invalid consistency criterion: {consistency_criterion}") + + for epoch in range(epochs): + if self.pretraining_sampler is not None: + self.pretraining_sampler.set_epoch(epoch) + + self.obj.model.train() + + epoch_loss = 0.0 + epoch_consistency_loss = 0.0 + epoch_tv_loss = 0.0 + num_batches = 0 + + for batch_idx, batch in enumerate(self.pretraining_dataloader): + coords = batch["coords"].to(self.device, non_blocking=True) + target = batch["target"].to(self.device, non_blocking=True) + + for _, opt in self.optimizers.items(): + opt.zero_grad() + + with torch.autocast(device_type=device_type, dtype=torch.bfloat16, enabled=True): + outputs = self.obj.forward(coords) + consistency_loss = consistency_loss_fn(outputs, target) + tv_loss = self.obj.apply_soft_constraints(coords) + + loss = consistency_loss + tv_loss + + loss.backward() + + epoch_loss += loss.detach() + epoch_consistency_loss += consistency_loss.detach() + epoch_tv_loss += tv_loss.detach() + + torch.nn.utils.clip_grad_norm_(self.obj.model.parameters(), max_norm=1.0) + + for _, opt in self.optimizers.items(): + opt.step() + epoch_loss += loss.detach() + num_batches += 1 + + if epoch % viz_freq == 0 or epoch == epochs - 1: + avg_loss = epoch_loss / num_batches + with torch.no_grad(): + self.obj.create_volume( + world_size=self.world_size, + global_rank=self.global_rank, + ray_size=volume_dataset.N, + ) + pred_full = self.obj.obj + loss_tensor = avg_loss.clone().detach() + avg_loss = loss_tensor.item() + avg_tv_loss = epoch_tv_loss / num_batches + avg_consistency_loss = epoch_consistency_loss / num_batches + + metrics = torch.tensor( + [avg_loss, avg_consistency_loss, avg_tv_loss], device=self.device + ) + if self.world_size > 1: + dist.all_reduce(metrics, op=dist.ReduceOp.AVG) + avg_loss, avg_consistency_loss, avg_tv_loss = metrics.tolist() + + if self.global_rank == 0: + self.temp_logger.add_scalar("Pretrain/total_loss", avg_loss, epoch) + self.temp_logger.add_scalar( + "Pretrain/consistency_loss", avg_consistency_loss, epoch + ) + self.temp_logger.add_scalar("Pretrain/tv_loss", avg_tv_loss, epoch) + + # current_lr = self.schedulers["model"].get_last_lr()[0] + # self.temp_logger.add_scalar("Pretrain/lr", current_lr, epoch) + + fig, ax = plt.subplots(ncols=4, figsize=(36, 12)) + ax[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap="turbo", vmin=0) + ax[0].set_title("Sum over Z-axis") + ax[1].matshow( + pred_full[volume_dataset.N // 2].cpu().numpy(), cmap="turbo", vmin=0 + ) + ax[1].set_title(f"Slice at Z={volume_dataset.N // 2}") + ax[2].matshow(pred_full.sum(dim=1).cpu().numpy(), cmap="turbo", vmin=0) + ax[2].set_title("Sum over Y-axis") + ax[3].matshow(pred_full.sum(dim=2).cpu().numpy(), cmap="turbo", vmin=0) + ax[3].set_title("Sum over X-axis") + self.temp_logger.add_figure("Pretrain/viz", fig, epoch, close=True) + + plt.close(fig) + print( + f"Epoch [{epoch}/{epochs}] Pretrain Total Loss: {avg_loss:.6f}, Consistency Loss: {avg_consistency_loss:.6f}, TV Loss: {avg_tv_loss:.6f}" + ) + self.global_epochs += 1 + if self.global_rank == 0: + print(f"Pretraining Completed, saving model weights to {log_path}/model_weights.pth") + torch.save(self.obj.model.state_dict(), f"{log_path}/model_weights.pth") + + def recon( + self, + obj: ObjectINN, + batch_size: int, + num_workers: int = 0, + epochs=20, + use_amp=True, + viz_freq=1, + checkpoint_freq=5, + optimizer_params: dict = None, + scheduler_params: dict = None, + soft_constraints: dict = None, + vol_save_path: str = None, # TODO: TEMPORARY + log_path=None, + val_fraction: float = 0.0, + learn_shifts: bool = True, + # l1_loss: bool = False, + consistency_criterion: str = "mse", + model_weights_path: str = None, + force_cpu: bool = False, + ): + if not self.ddp_instantiated: + if not self.pretraining_instantiated: + self.setup_distributed() + if model_weights_path is not None: + print(f"Loading model weights from {model_weights_path}") + state_dict = torch.load(model_weights_path, map_location="cpu") + + # Handle DataParallel/DDP checkpoints + if any(k.startswith("module.") for k in state_dict.keys()): + new_state_dict = { + k.replace("module.", ""): v for k, v in state_dict.items() + } + else: + new_state_dict = state_dict + + obj.model.load_state_dict(new_state_dict) + print("Model weights loaded successfully") + obj.model = self.build_model(obj._model) + self.obj = obj + + self.setup_dataloader(self.dataset, batch_size, num_workers, val_fraction) + self.ddp_instantiated = True + + if soft_constraints is not None: + self.obj.soft_constraints = soft_constraints + + if not hasattr(self, "temp_logger"): + self.setup_logger(log_path=log_path) + + zero_tilt_idx = torch.argmin(torch.abs(self.dataset.tilt_angles)).item() + + if self.global_rank == 0: + print( + f"Using projection {zero_tilt_idx} (angle={self.dataset.tilt_angles[zero_tilt_idx]:.2f}°) as reference" + ) + print(f"Using consistency criterion: {consistency_criterion}") + + # Auxiliary params setup + self.dataset.setup_auxiliary_params(zero_tilt_idx, self.device, learn_shifts) + aux_params = self.dataset.auxiliary_params + # Scaling learning rates to account for distributed training + if optimizer_params is not None: + optimizer_params = self.scale_lr(optimizer_params) + self.optimizer_params = optimizer_params + self.set_optimizers() + + if scheduler_params is not None: + self.scheduler_params = scheduler_params + self.set_schedulers(self.scheduler_params, num_iter=epochs) + + aux_norm = torch.tensor(0.0, device=self.device) + model_norm = torch.tensor(0.0, device=self.device) + + for _, opt in self.optimizers.items(): + opt.zero_grad() + + N = max(self.dataset.dims) + + device_type = self.device.type + autocast_dtype = torch.bfloat16 if use_amp else None + + for epoch in range(epochs): + num_samples_per_ray = get_num_samples_per_ray(N=N, epoch=self.global_epochs) + + # num_samples_per_ray = get_num_samples_per_ray(epoch) + # Log the change if it happens + if self.global_rank == 0: + print(f"Epoch {epoch}: num_samples_per_ray = {num_samples_per_ray}") + + if self.global_rank == 0 and self.global_epochs > 0: + prev_samples = get_num_samples_per_ray(N=N, epoch=self.global_epochs - 1) + + if num_samples_per_ray != prev_samples: + print( + f"Epoch {epoch}: Changing num_samples_per_ray from {prev_samples} to {num_samples_per_ray}" + ) + + if self.sampler is not None: + self.sampler.set_epoch(epoch) + + epoch_loss = 0.0 + epoch_consistency_loss = 0.0 + epoch_tv_loss = 0.0 + epoch_z1_loss = 0.0 + + num_batches = 0 + consistency_loss_fn = None + if consistency_criterion[0].lower() == "mse": + consistency_loss_fn = MSELoss() + elif consistency_criterion[0].lower() == "l1": + consistency_loss_fn = L1Loss() + elif consistency_criterion[0].lower() == "mse_log": + consistency_loss_fn = MSELogMSELoss() + elif consistency_criterion[0].lower() == "llmse": + consistency_loss_fn = LLMSELoss() + elif consistency_criterion[0].lower() == "smooth_l1": + consistency_loss_fn = SmoothL1Loss(beta=consistency_criterion[1]) + elif consistency_criterion[0].lower() == "adaptive_smooth_l1": + consistency_loss_fn = AdaptiveSmoothL1LossDDP( + beta_init=consistency_criterion[1], ema_factor=0.99, eps=1e-8 + ) + elif consistency_criterion[0].lower() == "charbonnier": + consistency_loss_fn = CharbonnierLoss(epsilon=1e-12, reduction="mean") + else: + raise ValueError(f"Invalid consistency criterion: {consistency_criterion}") + + for batch_idx, batch in enumerate(self.dataloader): + projection_indices = batch["projection_idx"] + + pixel_i = batch["pixel_i"].float().to(self.device, non_blocking=True) + pixel_j = batch["pixel_j"].float().to(self.device, non_blocking=True) + target_values = batch["target_value"].to(self.device, non_blocking=True) + phis = batch["phi"].to(self.device, non_blocking=True) + projection_indices = batch["projection_idx"].to(self.device, non_blocking=True) + + shifts, z1_params, z3_params = aux_params(None) + batch_shifts = torch.index_select(shifts, 0, projection_indices) + batch_z1 = torch.index_select(z1_params, 0, projection_indices) + batch_z3 = torch.index_select(z3_params, 0, projection_indices) + + with torch.autocast( + device_type=device_type, dtype=autocast_dtype, enabled=use_amp + ): + with torch.no_grad(): + batch_ray_coords = self.create_batch_projection_rays( + pixel_i, pixel_j, N, num_samples_per_ray + ) + + # TODO: .forward passing z1, x, z3, shifts, N, sampling_rate + transformed_rays = self.transform_batch_ray_coordinates( + batch_ray_coords, + z1=batch_z1, + x=phis, + z3=batch_z3, + shifts=batch_shifts, + N=N, + sampling_rate=1.0, + ) + + all_coords = transformed_rays.view(-1, 3) + + all_densities = self.obj.forward(all_coords) + + tv_loss = self.obj.apply_soft_constraints(all_coords) + ray_densities = all_densities.view( + len(target_values), num_samples_per_ray + ) # Reshape rays and integarte + step_size = 2.0 / (num_samples_per_ray - 1) + + predicted_values = ray_densities.sum(dim=1) * step_size + + consistency_loss = consistency_loss_fn(predicted_values, target_values) + tv_loss_z1 = torch.tensor(0.0, device=self.device) + + loss = consistency_loss + tv_loss + tv_loss_z1 + + loss.backward() + + epoch_loss += loss.detach() + epoch_consistency_loss += consistency_loss.detach() + epoch_tv_loss += tv_loss.detach() + epoch_z1_loss += tv_loss_z1.detach() + num_batches += 1 + + for key, opt in self.optimizers.items(): + if key == "model": + model_norm = torch.nn.utils.clip_grad_norm_( + self.obj.model.parameters(), max_norm=1 + ) + elif key == "aux_params": + aux_norm = torch.nn.utils.clip_grad_norm_( + self.dataset.auxiliary_params.parameters(), max_norm=1 + ) + opt.step() + opt.zero_grad() + + for key, sched in self.schedulers.items(): + if isinstance(sched, torch.optim.lr_scheduler.ReduceLROnPlateau): + sched.step(epoch_loss) + else: + sched.step() + + if epoch % viz_freq == 0 or epoch == epochs - 1 or epoch % checkpoint_freq == 0: + with torch.no_grad(): + self.obj.create_volume( + world_size=self.world_size, + global_rank=self.global_rank, + ray_size=num_samples_per_ray, + ) + pred_full = self.obj.obj + avg_loss = epoch_loss.item() / num_batches + avg_consistency_loss = epoch_consistency_loss.item() / num_batches + avg_tv_loss = epoch_tv_loss.item() / num_batches + avg_z1_loss = epoch_z1_loss.item() / num_batches + shifts, z1, z3 = self.dataset.auxiliary_params.forward() + shifts = shifts.detach().cpu() + z1 = z1.detach().cpu() + z3 = z3.detach().cpu() + metrics = torch.tensor( + [avg_loss, avg_consistency_loss, avg_tv_loss, avg_z1_loss], + device=self.device, + ) + if self.world_size > 1: + dist.all_reduce(metrics, op=dist.ReduceOp.AVG) + avg_loss, avg_consistency_loss, avg_tv_loss, avg_z1_loss = metrics.tolist() + + if hasattr(self, "val_dataloader") and self.val_dataloader is not None: + val_loss = self.validate( + aux_params, num_samples_per_ray, device_type, autocast_dtype, use_amp, N + ) + + if self.global_rank == 0 and (epoch % viz_freq == 0 or epoch == epochs - 1): + with torch.no_grad(): + current_lr = self.schedulers["model"].get_last_lr()[0] + + # Log metrics + self.temp_logger.add_scalar( + f"train/consistency_loss_{consistency_criterion[0] if isinstance(consistency_criterion, tuple) else consistency_criterion}", + avg_consistency_loss, + self.global_epochs, + ) + if hasattr(self, "val_dataloader") and self.val_dataloader is not None: + self.temp_logger.add_scalar( + f"val/consistency_loss_{consistency_criterion[0] if isinstance(consistency_criterion, tuple) else consistency_criterion}", + val_loss, + self.global_epochs, + ) + self.temp_logger.add_scalar("train/z1_loss", avg_z1_loss, self.global_epochs) + # if tv_weight > 0: + self.temp_logger.add_scalar("train/tv_loss", avg_tv_loss, self.global_epochs) + self.temp_logger.add_scalar("train/total_loss", avg_loss, self.global_epochs) + self.temp_logger.add_scalar( + "train/model_grad_norm", model_norm.item(), self.global_epochs + ) + self.temp_logger.add_scalar( + "train/aux_grad_norm", aux_norm.item(), self.global_epochs + ) + self.temp_logger.add_scalar("train/lr", current_lr, self.global_epochs) + self.temp_logger.add_scalar( + "train/num_samples_per_ray", num_samples_per_ray, self.global_epochs + ) + if consistency_criterion[0].lower() == "adaptive_smooth_l1": + self.temp_logger.add_scalar( + "train/beta_2", consistency_loss_fn.beta2.item(), self.global_epochs + ) + fig, axes = plt.subplots(1, 5, figsize=(36, 12)) + axes[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap="turbo", vmin=0) + axes[0].set_title("Sum over Z-axis") + + axes[1].matshow(pred_full[N // 2].cpu().numpy(), cmap="turbo", vmin=0) + axes[1].set_title(f"Slice at Z={N // 2}") + + slice_start = max(0, N // 2 - 5) + slice_end = min(N, N // 2 + 6) + thick_slice = pred_full[slice_start:slice_end].sum(dim=0).cpu().numpy() + axes[2].matshow(thick_slice, cmap="turbo", vmin=0) + axes[2].set_title(f"Thick slice sum (Z={slice_start}:{slice_end - 1})") + + axes[3].matshow(pred_full.sum(dim=1).cpu().numpy(), cmap="turbo", vmin=0) + axes[3].set_title("Sum over Y-axis") + + axes[4].matshow(pred_full.sum(dim=2).cpu().numpy(), cmap="turbo", vmin=0) + axes[4].set_title("Sum over X-axis") + + self.temp_logger.add_figure("train/viz", fig, self.global_epochs, close=True) + + fig, axes = plt.subplots(ncols=3, figsize=(10, 5)) + axes[0].plot(shifts[:, 0].cpu().numpy(), label="Shifts X") + axes[0].plot(shifts[:, 1].cpu().numpy(), label="Shifts Y") + axes[0].legend() + axes[1].plot(z1.cpu().numpy(), label="Z1") + axes[2].plot(z3.cpu().numpy(), label="Z3") + self.temp_logger.add_figure( + "train/auxiliary_params", fig, self.global_epochs, close=True + ) + plt.close(fig) + + if epoch % checkpoint_freq == 0 or self.global_epochs == epochs - 1: + with torch.no_grad(): + if self.global_rank == 0: + save_path = f"{vol_save_path}/volume_epoch_{self.global_epochs:04d}.pt" + torch.save(pred_full.cpu(), save_path) + + self.global_epochs += 1 + + if self.global_rank == 0: + print("Training complete.") + + torch.save(self.dataset.auxiliary_params.forward(), f"{log_path}/auxiliary_params.pt") + # print("Successfully setup DDP and dataloader") + + def validate(self, aux_params, num_samples_per_ray, device_type, autocast_dtype, use_amp, N): + """Validate the model on the validation set.""" + self.obj.model.eval() + aux_params.eval() + + val_loss = 0.0 + num_batches = 0 + + with torch.no_grad(): + for batch in self.val_dataloader: + pixel_i = batch["pixel_i"].float().to(self.device, non_blocking=True) + pixel_j = batch["pixel_j"].float().to(self.device, non_blocking=True) + target_values = batch["target_value"].to(self.device, non_blocking=True) + phis = batch["phi"].to(self.device, non_blocking=True) + projection_indices = batch["projection_idx"].to(self.device, non_blocking=True) + + shifts, z1_params, z3_params = aux_params(None) + batch_shifts = torch.index_select(shifts, 0, projection_indices) + batch_z1 = torch.index_select(z1_params, 0, projection_indices) + batch_z3 = torch.index_select(z3_params, 0, projection_indices) + + with torch.autocast( + device_type=device_type, dtype=autocast_dtype, enabled=use_amp + ): + with torch.no_grad(): + batch_ray_coords = self.create_batch_projection_rays( + pixel_i, pixel_j, N, num_samples_per_ray + ) + + transformed_rays = self.transform_batch_ray_coordinates( + batch_ray_coords, + z1=batch_z1, + x=phis, + z3=batch_z3, + shifts=batch_shifts, + N=N, + sampling_rate=1.0, + ) + + all_coords = transformed_rays.view(-1, 3) + + all_densities = self.obj.forward(all_coords) + + ray_densities = all_densities.view( + len(target_values), num_samples_per_ray + ) # Reshape rays and integarte + step_size = 2.0 / (num_samples_per_ray - 1) + + predicted_values = ray_densities.sum(dim=1) * step_size + + mse_loss = torch.nn.functional.mse_loss(predicted_values, target_values) + + loss = mse_loss + + val_loss += loss.detach() + num_batches += 1 + + avg_val_loss = val_loss / num_batches if num_batches > 0 else 0.0 + + if self.world_size > 1: + val_loss_tensor = avg_val_loss.detach().clone() + dist.all_reduce(val_loss_tensor, op=dist.ReduceOp.AVG) + avg_val_loss = val_loss_tensor.item() + + self.obj.model.train() + aux_params.train() + + return avg_val_loss + + def ad_recon( + self, + optimizer_params: dict, + num_iter: int = 0, + reset: bool = False, + scheduler_params: dict | None = None, + hard_constraints: dict | None = None, + soft_constraints: dict | None = None, + # store_iterations: bool | None = None, + # store_iterations_every: int | None = None, + # autograd: bool = True, + ): + if reset: + self.reset_recon() + + self.hard_constraints = hard_constraints + self.soft_constraints = soft_constraints + + # Make sure everything is in the correct device, might be redundant/cleaner way to do this + self.dataset.to(self.device) + self.volume_obj.to(self.device) + + # Making optimizable parameters into leaf tensors. + self.dataset.shifts = self.dataset.shifts.detach().to(self.device).requires_grad_(True) + self.dataset.z1_angles = ( + self.dataset.z1_angles.detach().to(self.device).requires_grad_(True) + ) + self.dataset.z3_angles = ( + self.dataset.z3_angles.detach().to(self.device).requires_grad_(True) + ) + + if optimizer_params is not None: + self.optimizer_params = optimizer_params + self.set_optimizers() + + if scheduler_params is not None: + self.scheduler_params = scheduler_params + self.set_schedulers(self.scheduler_params, num_iter=num_iter) + + if hard_constraints is not None: + self.volume_obj.hard_constraints = hard_constraints + if soft_constraints is not None: + self.volume_obj.soft_constraints = soft_constraints + + pbar = tqdm(range(num_iter), desc="AD Reconstruction") + + for a0 in pbar: + total_loss = 0.0 + tilt_series_loss = 0.0 + + pred_volume = self.volume_obj.forward() + + for i in range(len(self.dataset.tilt_series)): + forward_projection = self.projection_operator( + vol=pred_volume, + z1=self.dataset.z1_angles[i], + x=self.dataset.tilt_angles[i], + z3=self.dataset.z3_angles[i], + shift_x=self.dataset.shifts[i, 0], + shift_y=self.dataset.shifts[i, 1], + device=self.device, + ) + + tilt_series_loss += torch.nn.functional.mse_loss( + forward_projection, self.dataset.tilt_series[i] + ) + tilt_series_loss /= len(self.dataset.tilt_series) + + total_loss = tilt_series_loss + self.volume_obj.soft_loss + self.loss.append(total_loss.item()) + + total_loss.backward() + + for opt in self.optimizers.values(): + opt.step() + opt.zero_grad() + + if self.schedulers is not None: + for sch in self.schedulers.values(): + if isinstance(sch, torch.optim.lr_scheduler.ReduceLROnPlateau): + sch.step(total_loss) + elif sch is not None: + sch.step() + + pbar.set_description(f"AD Reconstruction | Loss: {total_loss:.4f}") + + if self.logger is not None: + self.logger.log_scalar("loss/total", total_loss.item(), a0) + self.logger.log_scalar("loss/tilt_series", tilt_series_loss.item(), a0) + self.logger.log_scalar( + "loss/soft constraints", self.volume_obj.soft_loss.item(), a0 + ) + + if a0 % self.logger.log_images_every == 0: + self.logger.projection_images( + volume_obj=self.volume_obj, + epoch=a0, + ) + self.logger.tilt_angles_figure(dataset=self.dataset, step=a0) + + self.logger.flush() + + self.ad_recon_vol = self.volume_obj.forward() + + return self + + def reset_recon(self) -> None: + if isinstance(self.volume_obj, ObjectVoxelwise): + self.volume_obj.reset() + + self.ad_recon_vol = None + + # --- Projection Operators ---- + def projection_operator( + self, + vol, + z1, + x, + z3, + shift_x, + shift_y, + device, + ): + projection = ( + rot_ZXZ( + mags=vol.unsqueeze(0), # Add batch dimension + z1=z1, + x=-x, + z3=z3, + device=device, + mode="bilinear", + ) + .squeeze() + .sum(axis=0) + ) + + shifted_projection = differentiable_shift_2d( + image=projection, + shift_x=shift_x, + shift_y=shift_y, + sampling_rate=1.0, # Assuming 1 pixel = 1 physical unit + ) + + return shifted_projection diff --git a/src/quantem/tomography_old/tomography_base.py b/src/quantem/tomography_old/tomography_base.py new file mode 100644 index 00000000..d3b94427 --- /dev/null +++ b/src/quantem/tomography_old/tomography_base.py @@ -0,0 +1,514 @@ +from typing import Tuple + +import matplotlib.pyplot as plt +import numpy as np +import torch +from numpy.typing import NDArray +from torch._tensor import Tensor +from tqdm.auto import tqdm + +from quantem.core.datastructures.dataset3d import Dataset3d +from quantem.core.io.serialize import AutoSerialize +from quantem.core.visualization.visualization import show_2d +from quantem.imaging.drift import cross_correlation_shift +from quantem.tomography.object_models import ObjectModelType, ObjectVoxelwise +from quantem.tomography.tomography_dataset import TomographyDataset +from quantem.tomography.tomography_logger import LoggerTomography + + +class TomographyBase(AutoSerialize): + _token = object() + + DEFAULT_HARD_CONSTRAINTS = { + "positivity": False, + "shrinkage": False, + "circular_mask": False, + "fourier_filter": False, + } + + DEFAULT_SOFT_CONSTRAINTS = { + "tv_vol": 0.0, + } + + def __init__( + self, + dataset: TomographyDataset, + volume_obj: ObjectModelType, # ObjectDIP? + device: str = "cuda", + # ABF/HAADF property + logger: LoggerTomography | None = None, + _token: object | None = None, + ): + """Initialize a Tomography object. + + Parameters + ---------- + array : NDArray | Any + The underlying 3D array data + name : str + A descriptive name for the dataset + """ + + # if _token is not self._token: + # raise RuntimeError( + # "This class is not meant to be instantiated directly. Use the from_data method." + # ) + + self._device = device + self._dataset = dataset + self._volume_obj = volume_obj + self._loss = [] + self._mode = [] + + self._hard_constraints = self.DEFAULT_HARD_CONSTRAINTS.copy() + self._soft_constraints = self.DEFAULT_SOFT_CONSTRAINTS.copy() + + self._logger = None + + @classmethod + def from_data( + cls, + tilt_series: Dataset3d | NDArray | Tensor, + tilt_angles: NDArray | Tensor, + z1_angles: NDArray | Tensor | None = None, + z3_angles: NDArray | Tensor | None = None, + shifts: NDArray | Tensor | None = None, + volume_obj: NDArray | Dataset3d | ObjectModelType | None = None, + device: str = "cpu", + clamp: bool = True, + ): + device = device.lower() + + dataset = TomographyDataset.from_data( + tilt_series=tilt_series, + tilt_angles=tilt_angles, + z1_angles=z1_angles, + z3_angles=z3_angles, + shifts=shifts, + clamp=clamp, + ) + + dataset.to(device) + + if volume_obj is None: + max_shape = max(dataset.tilt_series.shape) + volume_obj = ObjectVoxelwise( + volume_shape=(max_shape, max_shape, max_shape), + device=device, + ) + elif isinstance(volume_obj, Dataset3d): + volume = torch.from_numpy(volume_obj.array) + volume_obj = ObjectVoxelwise( + volume_shape=volume_obj.shape, device=device, initial_volume=volume + ) + volume_obj.obj = volume + elif isinstance(volume_obj, np.ndarray): + volume = torch.from_numpy(volume_obj) + volume_obj = ObjectVoxelwise( + volume_shape=volume_obj.shape, device=device, initial_volume=volume + ) + volume_obj.obj = volume + else: + raise ValueError("volume_obj must be a Dataset3d, NDArray ObjectModelType") + + return cls( + dataset=dataset, + volume_obj=volume_obj, + device=device, + _token=cls._token, + ) + + # --- Properties --- + @property + def dataset(self) -> TomographyDataset: + """Tomography dataset.""" + + return self._dataset + + @dataset.setter + def dataset( + self, + tilt_series: Dataset3d | NDArray | TomographyDataset, + tilt_angles: NDArray | Tensor, + z1_angles: NDArray | Tensor | None = None, + z3_angles: NDArray | Tensor | None = None, + shifts: NDArray | Tensor | None = None, + # name: str | None = None, + # origin: NDArray | tuple | list | float | int | None = None, + # sampling: NDArray | tuple | list | float | int | None = None, + # units: list[str] | tuple | list | None = None, + # signal_units: str = "arb. units", + ): + """Set the tilt series dataset.""" + + if not isinstance(tilt_series, TomographyDataset): + dataset = TomographyDataset.from_array( + array=tilt_series, + tilt_angles=tilt_angles, + z1_angles=z1_angles, + z3_angles=z3_angles, + shifts=shifts, + ) + + self._dataset = dataset + + @property + def volume_obj(self) -> Dataset3d | ObjectModelType | None: + """Reconstruction volume dataset.""" + + return self._volume_obj + + @volume_obj.setter + # TODO: add support for ObjectModelType + def volume_obj(self, volume_obj: Dataset3d | NDArray): + """Set the reconstruction volume dataset.""" + if isinstance(volume_obj, ObjectModelType): + self._volume_obj = volume_obj + elif not isinstance(volume_obj, Dataset3d): + volume_obj = Dataset3d.from_array( + array=volume_obj, + # name=self._tilt_series.name, + # origin=self._tilt_series.origin, + # sampling=self._tilt_series.sampling, + # units=self._tilt_series.units, + # signal_units=self._tilt_series.signal_units, + ) + elif isinstance(volume_obj, Dataset3d): + self._volume_obj = volume_obj + else: + raise ValueError("volume_obj must be a Dataset3d or ObjectModelType") + + @property + def device(self) -> str: + """Computation device.""" + + return self._device + + @device.setter + def device(self, device: str): + """Set the computation device.""" + + # if "cuda" not in device or "gpu" not in device: + # raise NotImplementedError("Tomography not currently supported on CPU.") + + self._device = device + + @property + def loss(self) -> list: + """List of loss values during reconstruction.""" + + return self._loss + + @loss.setter + def loss(self, loss: list): + """Set the loss values during reconstruction.""" + + if not isinstance(loss, list): + raise TypeError("Loss must be a list.") + + self._loss = loss + + @property + def mode(self) -> list: + """List of modes used during reconstruction.""" + + return self._mode + + @property + def epochs(self) -> int: + """Number of epochs used during reconstruction.""" + return len(self.loss) + + @property + def logger(self) -> LoggerTomography: + return self._logger + + @logger.setter + def logger(self, logger: LoggerTomography): + if not isinstance(logger, LoggerTomography): + raise TypeError("Logger must be a LoggerTomography") + + self._logger = logger + + # --- Constraints --- + + @property + def hard_constraints(self) -> dict: + """Hard constraints for the reconstruction.""" + return self._hard_constraints + + @hard_constraints.setter + def hard_constraints(self, hard_constraints: dict): + """Set the hard constraints for the reconstruction.""" + + gkeys = self.DEFAULT_HARD_CONSTRAINTS.keys() + for key, value in hard_constraints.items(): + if key not in gkeys: + raise KeyError(f"Invalid object constraint key '{key}', allowed keys are {gkeys}") + self._hard_constraints[key] = value + + self._hard_constraints = hard_constraints + + @property + def soft_constraints(self) -> dict: + """Soft constraints for the reconstruction.""" + return self._soft_constraints + + @soft_constraints.setter + def soft_constraints(self, soft_constraints: dict): + """Set the soft constraints for the reconstruction.""" + + gkeys = self.DEFAULT_SOFT_CONSTRAINTS.keys() + for key, value in soft_constraints.items(): + if key not in gkeys: + raise KeyError(f"Invalid object constraint key '{key}', allowed keys are {gkeys}") + self._soft_constraints[key] = value + + self._soft_constraints = soft_constraints + + # --- RESET --- + + def reset_recon(self) -> None: + self.volume_obj.reset() + self.dataset.reset() + self.loss = [] + self.hard_constraints = self.DEFAULT_HARD_CONSTRAINTS.copy() + self.soft_constraints = self.DEFAULT_SOFT_CONSTRAINTS.copy() + + self._optimizers = {} + self._schedulers = {} + + # --- Preprocessing --- + + """ + TODO + 1. Implement tilt series cross-correlation alignment + 2. Background subtraction (for ABF) + 3. COM Alignment + 4. Masking + 5. Drift Correction + """ + + def cross_corr_alignment( + self, + upsample_factor: int = 1, + overwrite: bool = False, + ): + # TODO: This needs to be able to work with torch tensors. + + placeholder_tilt_series = self.dataset.tilt_series.clone().detach().cpu().numpy() + + aligned_tilt_series = np.zeros_like(placeholder_tilt_series) + aligned_tilt_series[0] = placeholder_tilt_series[0] + shifts = [] + num_imgs = placeholder_tilt_series.shape[1] + + pbar = tqdm(range(num_imgs - 1), desc="Cross-correlation alignment") + + for i in pbar: + shift, aligned_img = cross_correlation_shift( + placeholder_tilt_series[i], + placeholder_tilt_series[i + 1], + upsample_factor=upsample_factor, + return_shifted_image=True, + ) + + aligned_tilt_series[i + 1] = aligned_img + shifts.append(shift) + + if overwrite: + # TODO: Check this overwrite idea, maybe also need to save the relative shifts? + self.dataset.tilt_series = np.array(aligned_tilt_series) + + return np.array(aligned_tilt_series), np.array(shifts) + + # --- Postprocessing --- + + """ + TODO + 1. Apply circular mask + """ + + def circular_mask(self, shape, radius, center=None, dtype=torch.float32, device="cpu"): + """Generate a 2D circular mask of given shape and radius.""" + H, W = shape + + if center is None: + center = (H // 2, W // 2) + y = torch.arange(H, dtype=dtype, device=device).view(-1, 1) + x = torch.arange(W, dtype=dtype, device=device).view(1, -1) + dist_sq = (x - center[1]) ** 2 + (y - center[0]) ** 2 + return (dist_sq <= radius**2).to(dtype) + + def recon_vol_circular_mask(self, radii): + """ + Apply 2D circular masks along all three axes of a 3D volume. + + Args: + volume (torch.Tensor): 3D tensor of shape (H, W, D) + radii (tuple): (r0, r1, r2) for axes 0, 1, 2 + Returns: + masked_volume: tensor with all masks applied + """ + H, W, D = self.volume_obj.array.shape + device = self.device + dtype = torch.float32 + volume_obj = torch.tensor( + self.volume_obj.array, + device=self.device, + dtype=dtype, + ) + # Masks for each axis + mask0 = self.circular_mask((W, D), radii[0], dtype=dtype, device=device).unsqueeze( + 0 + ) # shape (1, W, D) + mask1 = self.circular_mask((H, D), radii[1], dtype=dtype, device=device).unsqueeze( + 1 + ) # shape (H, 1, D) + mask2 = self.circular_mask((H, W), radii[2], dtype=dtype, device=device).unsqueeze( + 2 + ) # shape (H, W, 1) + + # Broadcast and multiply all masks together + total_mask = mask0 * mask1 * mask2 # shape (H, W, D) + + volume_obj = volume_obj * total_mask + volume_obj = volume_obj.detach().cpu().numpy() + self.volume_obj = Dataset3d.from_array( + array=volume_obj, + # name=self.volume_obj.name, + # origin=self.volume_obj.origin, + # sampling=self.volume_obj.sampling, + # units=self.volume_obj.units, + # signal_units=self.volume_obj.signal_units, + ) + + # --- Visualizations --- + + def plot_projections( + self, + cmap: str = "turbo", + fft: bool = False, + norm: str = "log_auto", + figax: tuple[plt.Figure, plt.Axes] | None = None, + fft_vmax: Tuple[float, float] = (0, 40), + vmin: float = 0, + **kwargs, + ): + """ + Plots the projections of the volume object. + Note that the volume object is in the order of (z, y, x). + Parameters + ---------- + cmap : str + The colormap to use for the projections. + fft : bool + """ + + volume_obj_np = self.volume_obj.obj.detach().cpu().numpy() + + if figax is None: + fig, ax = plt.subplots(ncols=3, figsize=(20, 8)) + else: + fig, ax = figax + + show_2d( + volume_obj_np.sum(axis=0), + figax=(fig, ax[0]), + cmap=cmap, + title="Y-X Projection", + vmin=vmin, + ) + show_2d( + volume_obj_np.sum(axis=1), + figax=(fig, ax[1]), + cmap=cmap, + title="Z-X Projection", + vmin=vmin, + ) + show_2d( + volume_obj_np.sum(axis=2), + figax=(fig, ax[2]), + cmap=cmap, + title="Z-Y Projection", + vmin=vmin, + ) + + if fft: + fig, ax = plt.subplots(ncols=3, figsize=(25, 8)) + print(fft_vmax) + show_2d( + np.abs(np.fft.fftshift(np.fft.fftn(volume_obj_np.sum(axis=0)))), + figax=(fig, ax[0]), + cmap=cmap, + title="Y-X Projection FFT", + # norm=norm, + vmin=fft_vmax[0], + vmax=fft_vmax[1], + ) + + show_2d( + np.abs(np.fft.fftshift(np.fft.fftn(volume_obj_np.sum(axis=1)))), + figax=(fig, ax[1]), + cmap=cmap, + title="Z-X Projection FFT", + vmin=fft_vmax[0], + vmax=fft_vmax[1], + # norm=norm, + ) + show_2d( + np.abs(np.fft.fftshift(np.fft.fftn(volume_obj_np.sum(axis=2)))), + figax=(fig, ax[2]), + cmap=cmap, + title="Z-Y Projection FFT", + vmin=fft_vmax[0], + vmax=fft_vmax[1], + # norm=norm, + ) + + def plot_slice( + self, + cmap="turbo", + slice_index: int = 0, + vmin: float = 0, + figax: tuple[plt.Figure, plt.Axes] | None = None, + ): + if figax is None: + fig, ax = plt.subplots(figsize=(15, 8), ncols=3) + else: + fig, ax = figax + + show_2d( + self.volume_obj.obj[slice_index, :, :], + figax=(fig, ax[0]), + cmap=cmap, + vmin=vmin, + ) + show_2d( + self.volume_obj.obj[:, slice_index, :], + figax=(fig, ax[1]), + cmap=cmap, + vmin=vmin, + ) + + show_2d( + self.volume_obj.obj[:, :, slice_index], + figax=(fig, ax[2]), + cmap=cmap, + vmin=vmin, + ) + + def plot_loss( + self, + figsize: tuple = (8, 4), + figax: tuple[plt.Figure, plt.Axes] | None = None, + ): + if figax is None: + fig, ax = plt.subplots(figsize=figsize) + else: + fig, ax = figax + + ax.semilogy( + self.loss, + label="Loss", + ) diff --git a/src/quantem/tomography_old/tomography_conv.py b/src/quantem/tomography_old/tomography_conv.py new file mode 100644 index 00000000..1056da17 --- /dev/null +++ b/src/quantem/tomography_old/tomography_conv.py @@ -0,0 +1,235 @@ +import numpy as np +import torch + +# from torch_radon.radon import ParallelBeam as Radon +from quantem.tomography.radon.radon import iradon_torch, radon_torch +from quantem.tomography.tomography_base import TomographyBase +from quantem.tomography.utils import gaussian_filter_2d_stack, torch_phase_cross_correlation + + +class TomographyConv(TomographyBase): + """ + Class for handling conventional reconstruction methods of tomography data. + """ + + # --- Reconstruction Methods --- + + def _sirt_run_epoch( + self, + tilt_series: torch.Tensor, + proj_forward: torch.Tensor, + angles: torch.Tensor, + inline_alignment: bool, + filter_name: str, + circle: bool, + gaussian_kernel: torch.Tensor | None, + ): + loss = 0 + + if inline_alignment: + for ind in range(len(self.dataset.tilt_angles)): + im_proj = proj_forward[ind] + im_meas = tilt_series[ind] + + shift = torch_phase_cross_correlation(im_proj, im_meas) + if torch.linalg.norm(shift) <= 32: + shifted = torch.fft.ifft2( + torch.fft.fft2(im_meas) + * torch.exp( + -2j + * np.pi + * ( + shift[0] + * torch.fft.fftfreq( + im_meas.shape[0], device=im_meas.device + ).unsqueeze(1) + + shift[1] + * torch.fft.fftfreq(im_meas.shape[1], device=im_meas.device) + ) + ) + ).real + + proj_forward[ind] = shifted + + # Forward projection + + sinogram_est = radon_torch(self.volume_obj.obj, theta=angles, device=self.device) + # proj_forward = sinogram_est.permute(1, 2, 0) + # error = (tilt_series - proj_forward).permute(2, 0, 1) + proj_forward = sinogram_est + error = tilt_series - proj_forward + + correction = iradon_torch( + error, theta=angles, device=self.device, filter_name=filter_name, circle=circle + ) + + normalization = iradon_torch( + torch.ones_like(error), + theta=angles, + device=self.device, + filter_name=None, + circle=circle, + ) + + normalization[normalization == 0] = 1e-6 + + correction /= normalization + + self.volume_obj._obj += correction + + loss = torch.mean(torch.abs(error)) + + # for z in tqdm(range(self.volume_obj.obj.shape[0]), desc="SIRT Reconstruction"): + # slice_estimate = self.volume_obj.obj[z] + # sinogram_est = radon_torch(slice_estimate, theta=angles, device=self.device) + + # sinogram_true = tilt_series[:, :, z] + + # error = sinogram_true - sinogram_est + + # correction = iradon_torch( + # error, theta=angles, device=self.device, filter_name=filter_name, circle=circle + # ) + + # # I'm pretty sure this implementation of normalization is wrong + # normalization = iradon_torch( + # torch.ones_like(error), + # theta=angles, + # device=self.device, + # filter_name=None, + # circle=circle, + # ) + # normalization[normalization == 0] = 1e-6 + + # correction /= normalization + + # self.volume_obj._obj[z] += correction + + # proj_forward[:, :, z] = sinogram_est + + # loss += torch.mean(torch.abs(error)) + + # loss /= self.volume_obj._obj.shape[0] + + if gaussian_kernel is not None: + self.volume_obj.obj = gaussian_filter_2d_stack(self.volume_obj.obj, gaussian_kernel) + + return proj_forward, loss + + # Deprecated torch_radon implementations + # def _sirt_run_epoch( + # self, + # radon: Radon, + # stack_recon: torch.Tensor, + # stack_torch: torch.Tensor, + # proj_forward: torch.Tensor, + # step_size: float = 0.25, + # gaussian_kernel: torch.Tensor = None, + # inline_alignment=True, + # enforce_positivity=True, + # shrinkage: float = None, + # ): + # loss = 0 + + # if inline_alignment: + # for ind in range(len(self.tilt_series.tilt_angles)): + # im_proj = proj_forward[:, ind, :] + # im_meas = stack_torch[:, ind, :] + + # shift = torch_phase_cross_correlation(im_proj, im_meas) + # if torch.linalg.norm(shift) <= 32: + # shifted = torch.fft.ifft2( + # torch.fft.fft2(im_meas) + # * torch.exp( + # -2j + # * np.pi + # * ( + # shift[0] + # * torch.fft.fftfreq( + # im_meas.shape[0], device=im_meas.device + # ).unsqueeze(1) + # + shift[1] + # * torch.fft.fftfreq(im_meas.shape[1], device=im_meas.device) + # ) + # ) + # ).real + + # stack_torch[:, ind, :] = shifted + + # proj_forward = radon.forward(stack_recon) + + # proj_diff = stack_torch - proj_forward + + # loss = torch.mean(torch.abs(proj_diff)) + + # recon_slice_update = radon.backward( + # radon.filter_sinogram( + # proj_diff, + # ) + # ) + + # stack_recon += step_size * recon_slice_update + # if enforce_positivity: + # stack_recon = torch.clamp(stack_recon, min=0) + + # if gaussian_kernel is not None: + # stack_recon = gaussian_filter_2d_stack( + # stack_recon, + # gaussian_kernel, + # ) + + # if shrinkage is not None: + # stack_recon = torch.max( + # stack_recon - shrinkage, + # torch.zeros_like(stack_recon), + # ) + + # return stack_recon, loss + + # def _sirt_serial_run_epoch( + # self, + # radon: Radon, + # stack_recon: torch.Tensor, + # stack_torch: torch.Tensor, + # proj_forward: torch.Tensor, + # step_size: float = 0.25, + # gaussian_kernel: torch.Tensor = None, + # inline_alignment=True, + # enforce_positivity=True, + # ): + # recon_slice_update = torch.zeros_like(stack_recon).to(self.device) + + # loss = 0 + + # for i in range(stack_recon.shape[0]): + # proj_forward[i] = radon.forward(stack_recon[i]) + + # proj_diff = stack_torch - proj_forward + + # loss = torch.mean(torch.abs(proj_diff)) + + # for i in range(stack_recon.shape[0]): + # recon_slice_update[i] = radon.backward( + # radon.filter_sinogram( + # proj_diff[i], + # ) + # ) + + # stack_recon += step_size * recon_slice_update + + # if enforce_positivity: + # stack_recon = torch.clamp(stack_recon, min=0) + + # return stack_recon, loss + + # --- Properties --- + # @property + # def reconstruction_method(self) -> str: + # """Get the reconstruction method.""" + # return self._reconstruction_method + # @reconstruction_method.setter + # def reconstruction_method(self, value: str): + # """Set the reconstruction method.""" + # if value not in ["SIRT", "FBP"]: + # raise ValueError("Invalid reconstruction method. Choose 'SIRT' or 'FBP'.") + # self._reconstruction_method = value diff --git a/src/quantem/tomography/tomography_dataset.py b/src/quantem/tomography_old/tomography_dataset.py similarity index 95% rename from src/quantem/tomography/tomography_dataset.py rename to src/quantem/tomography_old/tomography_dataset.py index 31898db7..c66f1d9d 100644 --- a/src/quantem/tomography/tomography_dataset.py +++ b/src/quantem/tomography_old/tomography_dataset.py @@ -1,3 +1,5 @@ +from pathlib import Path + import numpy as np import torch from numpy.typing import NDArray @@ -11,8 +13,6 @@ validate_tensor, ) -from pathlib import Path - class AuxiliaryParams(torch.nn.Module): def __init__(self, num_tilts, device, zero_tilt_idx=None, learn_shifts: bool = True): @@ -29,7 +29,9 @@ def __init__(self, num_tilts, device, zero_tilt_idx=None, learn_shifts: bool = T # Shifts: only parameterize non-reference tilts num_param_tilts = num_tilts - 1 - self.shifts_param = torch.nn.Parameter(torch.zeros(num_param_tilts, 2, device=device), requires_grad = learn_shifts) + self.shifts_param = torch.nn.Parameter( + torch.zeros(num_param_tilts, 2, device=device), requires_grad=learn_shifts + ) # Fixed zero shifts for reference self.shifts_ref = torch.zeros(1, 2, device=device) @@ -52,8 +54,8 @@ def forward(self, dummy_input=None): after_z1 = self.z1_param[self.zero_tilt_idx :] z1 = torch.cat([before_z1, self.z1_ref, after_z1], dim=0) - before_z3 = self.z3_param[:self.zero_tilt_idx] - after_z3 = self.z3_param[self.zero_tilt_idx:] + before_z3 = self.z3_param[: self.zero_tilt_idx] + after_z3 = self.z3_param[self.zero_tilt_idx :] z3 = torch.cat([before_z3, self.z3_ref, after_z3], dim=0) return shifts, z1, z3 @@ -171,7 +173,6 @@ def to(self, device: str): self._z3_angles = self._z3_angles.to(device) self._shifts = self._shifts.to(device) - # --- Properties --- @property @@ -323,7 +324,9 @@ def initial_shifts(self, shifts: NDArray | Tensor) -> None: # TODO: Temp auxiliary params - def setup_auxiliary_params(self, zero_tilt_idx: int = None, device: str = "cpu", learn_shifts: bool = True) -> None: + def setup_auxiliary_params( + self, zero_tilt_idx: int = None, device: str = "cpu", learn_shifts: bool = True + ) -> None: if not hasattr(self, "_auxiliary_params"): self._auxiliary_params = AuxiliaryParams( num_tilts=len(self.tilt_angles), @@ -347,6 +350,7 @@ def reset(self) -> None: self._z3_angles = self._initial_z3_angles.clone() self._shifts = self._initial_shifts.clone() + # TODO: Temporary for use when only doing validation class TomographyRayDataset(Dataset): """ @@ -354,7 +358,7 @@ class TomographyRayDataset(Dataset): Each sample contains: target pixel value, pixel coordinates, phi angle, and auxiliary info. """ - def __init__(self, tilt_series, phis, N, val_ratio=0.0, mode='train', seed=42): + def __init__(self, tilt_series, phis, N, val_ratio=0.0, mode="train", seed=42): """ Args: tilt_series: [M, N, N] tensor of projections @@ -375,13 +379,12 @@ def __init__(self, tilt_series, phis, N, val_ratio=0.0, mode='train', seed=42): self.M = tilt_series.shape[0] self.total_pixels = self.M * self.N * self.N - # Create train/val split torch.manual_seed(seed) all_indices = torch.randperm(self.total_pixels) num_val = int(self.total_pixels * val_ratio) - if mode == 'val': + if mode == "val": self.indices = all_indices[:num_val] else: # train self.indices = all_indices[num_val:] @@ -392,7 +395,6 @@ def __len__(self): return len(self.indices) def __getitem__(self, idx): - actual_idx = self.indices[idx].item() projection_idx = actual_idx // (self.N * self.N) @@ -404,17 +406,16 @@ def __getitem__(self, idx): phi = self.phis[projection_idx] return { - 'projection_idx': projection_idx, - 'pixel_i': pixel_i, - 'pixel_j': pixel_j, - 'target_value': target_value, - 'phi': phi + "projection_idx": projection_idx, + "pixel_i": pixel_i, + "pixel_j": pixel_j, + "target_value": target_value, + "phi": phi, } # Pretrain Volume Dataset class PretrainVolumeDataset(Dataset): - def __init__( self, volume_path: Path | str, @@ -443,23 +444,20 @@ def __init__( data = torch.permute(data, (2, 1, 0)) # data = torch.flip(data, dims=(2,)) - self.volume = data.cpu() + self.volume = data.cpu() self.N = N self.total_samples = N**3 coords_1d = torch.linspace(-1, 1, N) - x, y, z = torch.meshgrid(coords_1d, coords_1d, coords_1d, indexing='ij') + x, y, z = torch.meshgrid(coords_1d, coords_1d, coords_1d, indexing="ij") self.coords = torch.stack([x, y, z], dim=-1).reshape(-1, 3).cpu() self.targets = self.volume.reshape(-1).cpu() - + def __len__(self): return self.total_samples - + def __getitem__(self, idx): - return { - 'coords': self.coords[idx], - 'target': self.targets[idx] - } + return {"coords": self.coords[idx], "target": self.targets[idx]} @property def N(self): @@ -467,4 +465,4 @@ def N(self): @N.setter def N(self, N: int): - self._N = N \ No newline at end of file + self._N = N diff --git a/src/quantem/tomography_old/tomography_ddp.py b/src/quantem/tomography_old/tomography_ddp.py new file mode 100644 index 00000000..cfb59117 --- /dev/null +++ b/src/quantem/tomography_old/tomography_ddp.py @@ -0,0 +1,321 @@ +import os + +import numpy as np +import torch +import torch.distributed as dist +import torch.nn as nn +from torch.utils.data import DataLoader, DistributedSampler + +from quantem.tomography.tomography_dataset import ( + PretrainVolumeDataset, + TomographyDataset, + TomographyRayDataset, +) + + +class TomographyDDP: + """ + Initializing DDP stuff for tomo class. + """ + + def __init__( + self, + ): + self.setup_distributed() + + def setup_distributed(self): + # Check if in distributed env + if "RANK" in os.environ: + # Distributed training + if not dist.is_initialized(): + dist.init_process_group( + backend="nccl" if torch.cuda.is_available() else "gloo", init_method="env://" + ) + + self.world_size = dist.get_world_size() + self.global_rank = dist.get_rank() + self.local_rank = int(os.environ["LOCAL_RANK"]) + + torch.cuda.set_device(self.local_rank) + self.device = torch.device("cuda", self.local_rank) + + else: + # Single GPU/CPU training + self.world_size = 1 + self.global_rank = 0 + self.local_rank = 0 + + if torch.cuda.is_available(): + self.device = torch.device("cuda") + torch.cuda.set_device(0) + print("Single GPU training") + else: + self.device = torch.device("cpu") + print("CPU training") + + # Optional performance optimizations (only for CUDA) + if self.device.type == "cuda": + torch.backends.cudnn.benchmark = True + torch.backends.cuda.matmul.allow_tf32 = True + torch.backends.cudnn.allow_tf32 = True + + def build_model( + self, + model: nn.Module, + ): + # TODO: Generalized model --> Should be instantiated in the object? Where does `HSIREN` get instantiated? + model = model.to(self.device) + + if self.world_size > 1: + model = torch.nn.parallel.DistributedDataParallel( + model, + device_ids=[self.local_rank], + output_device=self.local_rank, + find_unused_parameters=False, + broadcast_buffers=True, + bucket_cap_mb=100, + gradient_as_bucket_view=True, + ) + + if self.global_rank == 0: + print("Model wrapped with DDP") + + if self.world_size > 1: + if self.global_rank == 0: + print("Model built, distributed, and compiled successfully") + + else: + print("Model built, compiled successfully") + + return model + + # Setup Tomo DataLoader + def setup_dataloader( + self, + tomo_dataset: TomographyDataset, + batch_size: int, + num_workers: int = 0, + val_fraction: float = 0.0, + ): + pin_mem = self.device.type == "cuda" + persist = num_workers > 0 + + # Split dataset if validation fraction > 0 + if val_fraction > 0.0: + # TODO: Temporary for when only doing validation, current TomographyDataset doesn't work correctly + train_dataset = TomographyRayDataset( + tomo_dataset.tilt_series.detach().clone(), + tomo_dataset.tilt_angles.detach().clone(), + 500, # TODO: TEMPORARY + val_ratio=val_fraction, + mode="train", + seed=42, + ) + val_dataset = TomographyRayDataset( + tomo_dataset.tilt_series.detach().clone(), + tomo_dataset.tilt_angles.detach().clone(), + 500, # TODO: TEMPORARY + val_ratio=val_fraction, + mode="val", + seed=42, + ) + else: + train_dataset, val_dataset = tomo_dataset, None + + # Samplers for distributed training + if self.world_size > 1: + train_sampler = DistributedSampler( + train_dataset, + num_replicas=self.world_size, + rank=self.global_rank, + shuffle=True, + ) + if val_dataset: + val_sampler = DistributedSampler( + val_dataset, + num_replicas=self.world_size, + rank=self.global_rank, + shuffle=False, + ) + shuffle = False + else: + train_sampler = None + val_sampler = None + shuffle = True + + # Main dataloader + self.dataloader = DataLoader( + train_dataset, + batch_size=batch_size, + num_workers=num_workers, + sampler=train_sampler, + shuffle=shuffle, + pin_memory=pin_mem, + drop_last=True, + persistent_workers=persist, + ) + + # Validation dataloader if applicable + if val_dataset: + self.val_dataloader = DataLoader( + val_dataset, + batch_size=batch_size * 4, + num_workers=num_workers, + sampler=val_sampler, + shuffle=False, + pin_memory=pin_mem, + drop_last=False, + persistent_workers=persist, + ) + self.val_sampler = val_sampler + + self.sampler = train_sampler + + if self.global_rank == 0: + print("Dataloader setup complete:") + print(f" Total projections: {len(tomo_dataset.tilt_angles)}") + if val_fraction > 0.0: + print(f" Total projections (val): {len(val_dataset)}") + print(f" Grid size: {tomo_dataset.dims[1]}{tomo_dataset.dims[2]}") + print(f" Total pixels: {tomo_dataset.num_pixels:,}") + if val_fraction > 0.0: + print(f" Total pixels (val): {len(val_dataset):,}") + print(f" Total pixels (train): {len(train_dataset):,}") + print(f" Local batch size (train): {batch_size}") + print(f" Global batch size: {batch_size * self.world_size}") + print(f" Train batches per GPU per epoch: {len(self.dataloader)}") + + # Setup pretraining dataloader + + def setup_pretraining_dataloader( + self, + volume_dataset: PretrainVolumeDataset, + batch_size: int, + ): + # TODO: temp + + num_workers = 0 + if self.world_size > 1: + sampler = DistributedSampler( + volume_dataset, + num_replicas=self.world_size, + rank=self.global_rank, + shuffle=True, + drop_last=True, + ) + shuffle = False + else: + sampler = None + shuffle = True + + self.pretraining_dataloader = DataLoader( + volume_dataset, + batch_size=batch_size, + sampler=sampler, + shuffle=shuffle, + pin_memory=self.device.type == "cuda", + drop_last=True, + persistent_workers=num_workers > 0, + ) + + self.pretraining_sampler = sampler + + if self.global_rank == 0: + print("Pretraining dataloader setup complete:") + print(f" Total samples: {len(volume_dataset)}") + print(f" Grid size: {volume_dataset.N**3}") + print(f" Local batch size: {batch_size}") + print(f" Global batch size: {batch_size * self.world_size}") + print(f" Pretraining batches per GPU per epoch: {len(self.pretraining_dataloader)}") + + def get_scaled_lr(self, base_lr, scaling_rule="sqrt"): + if scaling_rule == "sqrt": + return base_lr * np.sqrt(self.world_size) + elif scaling_rule == "linear": + return base_lr * self.world_size + else: + raise ValueError(f"Invalid scaling rule: {scaling_rule}") + + def scale_lr( + self, + optimizer_params: dict, + ): + new_optimizer_params = {} + for key, value in optimizer_params.items(): + if "original_lr" in value: + new_optimizer_params[key] = { + "type": value["type"], + "lr": self.get_scaled_lr(value["original_lr"]), + "original_lr": value["original_lr"], + } + else: + new_optimizer_params[key] = { + "type": value["type"], + "lr": self.get_scaled_lr(value["lr"]), + "original_lr": value["lr"], + } + + return new_optimizer_params + + # TODO: Temporary Adaptive L1 Smooth Loss + + +class AdaptiveSmoothL1Loss(nn.Module): + def __init__(self, beta_init=None, ema_factor=0.99, eps=1e-8): + """ + Adaptive smooth L1 loss with EMA-based beta adaptation. + + Args: + beta_init (float): optional initial β value; if None, starts as 1.0 + ema_factor (float): smoothing factor for EMA (1 - 1/N_b in Eq. 38) + eps (float): small constant for numerical stability + """ + super().__init__() + self.register_buffer("beta2", torch.tensor(beta_init**2 if beta_init else 1.0)) + self.ema_factor = ema_factor + self.eps = eps + + def forward(self, pred, target): + diff = pred - target + abs_diff = diff.abs() + + # compute current batch MSE (Eq. 38) + mse_batch = torch.mean(diff**2) + + # update β² adaptively using Eq. (39) + with torch.no_grad(): + self.beta2 = self.ema_factor * self.beta2 + (1 - self.ema_factor) * torch.min( + self.beta2, mse_batch + ) + + beta = torch.sqrt(self.beta2 + self.eps) + + # Smooth L1 (Eq. 36) + loss = torch.where( + abs_diff < beta, 0.5 * (diff**2) / (beta + self.eps), abs_diff - 0.5 * beta + ) + return loss.mean() + + +class AdaptiveSmoothL1LossDDP(AdaptiveSmoothL1Loss): + def forward(self, pred, target): + diff = pred - target + abs_diff = diff.abs() + mse_batch = torch.mean(diff**2) + + # Synchronize β across all GPUs + if dist.is_initialized(): + mse_batch_all = mse_batch.clone() + dist.all_reduce(mse_batch_all, op=dist.ReduceOp.AVG) + mse_batch = mse_batch_all / dist.get_world_size() + + with torch.no_grad(): + self.beta2 = self.ema_factor * self.beta2 + (1 - self.ema_factor) * torch.min( + self.beta2, mse_batch + ) + + beta = torch.sqrt(self.beta2 + self.eps) + loss = torch.where( + abs_diff < beta, 0.5 * (diff**2) / (beta + self.eps), abs_diff - 0.5 * beta + ) + return loss.mean() diff --git a/src/quantem/tomography/tomography_logger.py b/src/quantem/tomography_old/tomography_logger.py similarity index 100% rename from src/quantem/tomography/tomography_logger.py rename to src/quantem/tomography_old/tomography_logger.py diff --git a/src/quantem/tomography_old/tomography_ml.py b/src/quantem/tomography_old/tomography_ml.py new file mode 100644 index 00000000..5b130e7f --- /dev/null +++ b/src/quantem/tomography_old/tomography_ml.py @@ -0,0 +1,338 @@ +from typing import Any, Generator, Iterator, Sequence + +import torch + +from quantem.tomography.tomography_base import TomographyBase + + +class TomographyML(TomographyBase): + """ + Class for handling conventional reconstruction methods of tomography data. + """ + + OPTIMIZABLE_VALS = ["volume", "z1", "x", "z3", "shifts", "model", "aux_params"] + DEFAULT_LRS = { + "volume": 1e-2, + "z1": 1e-1, + "x": 1e-1, + "z3": 1e-1, + "shifts": 1e-1, + "tv_weight_vol": 0, + "tv_weight_z1": 0, + "tv_weight_x": 0, + "tv_weight_z3": 0, + } + DEFAULT_OPTIMIZER_TYPE = "adam" + + # --- Properties --- + + @property + def optimizer_params(self) -> dict[str, dict]: + """Returns the parameters used to set the optimizers.""" + return self._optimizer_params + + @optimizer_params.setter + def optimizer_params(self, d: dict) -> None: + """ + # Takes a dictionary {key: torch.optim.Adam(params=[blah], lr=[blah]), ...} + Takes a dictionary: + { + "key1": { + "type": "adam", + "lr": 0.001, + }, + "key2": { + ... + }, + ... + } + """ + # resets _optimizers as well + self._optimizers = {} + self._optimizer_params = {} + if isinstance(d, (tuple, list)): + d = {k: {} for k in d} + + for k, v in d.items(): + if k not in self.OPTIMIZABLE_VALS: + raise ValueError( + f"key to be optimized, {k}, not in allowed keys: {self.OPTIMIZABLE_VALS}" + ) + if "type" not in v.keys(): + v["type"] = self.DEFAULT_OPTIMIZER_TYPE + if "lr" not in v.keys(): + v["lr"] = self.DEFAULT_LRS[k] + self._optimizer_params[k] = v + + @property + def optimizers(self) -> dict[str, torch.optim.Adam | torch.optim.AdamW]: + return self._optimizers + + def set_optimizers(self): + """Reset all optimizers and set them according to the optimizer_params.""" + for key, _ in self._optimizer_params.items(): + if key == "volume": + self._add_optimizer(key, self.volume_obj.params, self._optimizer_params[key]) + elif key == "shifts": + self._add_optimizer(key, self.dataset.shifts, self._optimizer_params[key]) + elif key == "z1": + self._add_optimizer(key, self.dataset.z1_angles, self._optimizer_params[key]) + elif key == "x": + self._add_optimizer(key, self.dataset.tilt_angles, self._optimizer_params[key]) + elif key == "z3": + self._add_optimizer(key, self.dataset.z3_angles, self._optimizer_params[key]) + elif key == "model": + if self._optimizer_params[key]["original_lr"] is not None: + optimizer_params = self._optimizer_params[key].copy() + optimizer_params.pop("original_lr", None) + self._add_optimizer(key, self.obj.model.parameters(), optimizer_params) + elif key == "aux_params": + if self._optimizer_params[key]["original_lr"] is not None: + optimizer_params = self._optimizer_params[key].copy() + optimizer_params.pop("original_lr", None) + self._add_optimizer( + key, self.dataset.auxiliary_params.parameters(), optimizer_params + ) + else: + raise ValueError( + f"key to be optimized, {key}, not in allowed keys: {self.OPTIMIZABLE_VALS}" + ) + + def remove_optimizer(self, key: str) -> None: + self._optimizers.pop(key, None) + self._optimizer_params.pop(key, None) + return + + def _add_optimizer( + self, + key: str, + params: "torch.Tensor|Sequence[torch.Tensor]|Iterator[torch.Tensor]", + opt_params: dict, + ) -> None: + """Can be used to add an optimizer without resetting the other optimizers.""" + + if key not in self.OPTIMIZABLE_VALS: + raise ValueError( + f"key to be optimized, {key}, not in allowed keys: {self.OPTIMIZABLE_VALS}" + ) + if isinstance(params, torch.Tensor): + params = [params] + elif isinstance(params, Generator): + params = list(params) + [p.requires_grad_(True) for p in params] + self.optimizer_params[key] = opt_params + opt_params = opt_params.copy() + opt_type = opt_params.pop("type") + if isinstance(opt_type, type): + opt = opt_type(params, **opt_params) + elif opt_type == "adam": + opt = torch.optim.Adam(params, **opt_params) + elif opt_type == "adamw": + opt = torch.optim.AdamW(params, **opt_params) # TODO pass all other kwargs + else: + raise NotImplementedError(f"Unknown optimizer type: {opt_params['type']}") + # if key in self.optimizers.keys(): + # self.vprint(f"Key {key} is already in optimizers, overwriting.") + self._optimizers[key] = opt + + @property + def scheduler_params(self) -> dict[str, dict]: + """Returns the parameters used to set the schedulers.""" + return self._scheduler_params + + @scheduler_params.setter + def scheduler_params(self, d: dict) -> None: + """ + Takes a dictionary: + { + "key1": { + "type": "cyclic", + "base_lr": 0.001, + }, + "key2": { + ... + }, + ... + } + """ + self._schedulers = {} + for k, v in d.items(): + if not any(v): + continue + if k not in self.OPTIMIZABLE_VALS: + raise ValueError( + f"key to be optimized, {k}, not in allowed keys: {self.OPTIMIZABLE_VALS}" + ) + if v["type"] not in [ + "cyclic", + "plateau", + "exp", + "gamma", + "linear", + "cosine_annealing", + "none", + ]: + raise ValueError( + f"Unknown scheduler type: {v['type']}, expected one of ['cyclic', 'plateau', 'exp', 'gamma', 'none']" + ) + self._scheduler_params = d + + @property + def schedulers( + self, + ) -> dict[ + str, + ( + torch.optim.lr_scheduler.CyclicLR + | torch.optim.lr_scheduler.ReduceLROnPlateau + | torch.optim.lr_scheduler.ExponentialLR + | torch.optim.lr_scheduler.LinearLR + | torch.optim.lr_scheduler.CosineAnnealingLR + | None + ), + ]: + return self._schedulers + + def set_schedulers( + self, + params: dict[str, dict], + num_iter: int | None = None, + ): + """ + TODO allow for new schedulers to be passed in when adding new optimizers without + removing the old schedulers or overwrtiting them. Not entirely sure what usecases there + will be for this. + + Sets the schedulers for the optimizer from a dictionary. Expects a dictionary of the form: + { + "optimizable_key1": { + "type": "scheduler_type", + "scheduler_kwarg": scheduler_kwarg_value, + ... + }, + "optimizable_key2": { + "type": "scheduler_type", + "scheduler_kwarg": scheduler_kwarg_value, + ... + }, + ... + } + where the keys are the same as the keys in self.OPTIMIZABLE_VALS. + + The scheduler type can be one of the following: + - "cyclic" + - "plateau" or "reducelronplateau" + - "exponential" + - None + + The num_iter kwarg is only used for exponential schedulers and if a "factor" is given + as a scheduler_kwarg instead of gamma. In that case, the gamma is calculated from num_iter + and the factor. + + TODO could update this to allow passing key:optimizer directly, would likely need to + rewrite get_schedulers to check the tpye + """ + if not any(self.optimizers): + raise NameError("self.optimizers have not yet been set.") + self._schedulers = self._get_schedulers( + params=params, + optimizers=self.optimizers, + num_iter=num_iter, + ) + + def _get_schedulers( + self, + params: dict[str, dict], + optimizers: dict, + num_iter: int | None = None, + ) -> dict[ + str, + ( + torch.optim.lr_scheduler.CyclicLR + | torch.optim.lr_scheduler.ReduceLROnPlateau + | torch.optim.lr_scheduler.ExponentialLR + | None + ), + ]: + """ + return schedulers for a given set of optimizers. Kept seperate from schedulers.setter so + that it can be called for pre-training + """ + schedulers = {} + for opt_key, p in params.items(): + if not any(p): + continue + elif opt_key not in self.OPTIMIZABLE_VALS: + raise KeyError( + f"Scheduler got bad key {opt_key}, schedulers can only be attached to one of {self.OPTIMIZABLE_VALS}" + ) + elif opt_key not in optimizers.keys(): + raise KeyError(f"optimizers does not have an optimizer for: {opt_key}") + else: + schedulers[opt_key] = self._get_scheduler( + optimizer=optimizers[opt_key], params=p, num_iter=num_iter + ) + return schedulers + + def _get_scheduler( + self, + optimizer: torch.optim.Adam, + params: dict[str, Any] | torch.optim.lr_scheduler._LRScheduler, + num_iter: int | None = None, + ) -> ( + torch.optim.lr_scheduler._LRScheduler + | torch.optim.lr_scheduler.CyclicLR + | torch.optim.lr_scheduler.ReduceLROnPlateau + | torch.optim.lr_scheduler.ExponentialLR + | None + ): + if isinstance(params, torch.optim.lr_scheduler._LRScheduler): + return params + + sched_type: str = params["type"].lower() + base_LR = optimizer.param_groups[0]["lr"] + if sched_type == "none": + scheduler = None + elif sched_type == "linear": + scheduler = torch.optim.lr_scheduler.LinearLR( + optimizer, + start_factor=params.get("start_factor", 0.1), + total_iters=params.get("total_iters", num_iter), + ) + elif sched_type == "cosine_annealing": + scheduler = torch.optim.lr_scheduler.CosineAnnealingLR( + optimizer, + T_max=params.get("T_max", num_iter), + eta_min=params.get("eta_min", base_LR / 100), + ) + elif sched_type == "cyclic": + scheduler = torch.optim.lr_scheduler.CyclicLR( + optimizer, + base_lr=params.get("base_lr", base_LR / 4), + max_lr=params.get("max_lr", base_LR * 4), + step_size_up=params.get("step_size_up", 100), + mode=params.get("mode", "triangular2"), + cycle_momentum=params.get("momentum", False), + ) + elif sched_type.startswith(("plat", "reducelronplat")): + scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau( + optimizer, + mode="min", + factor=params.get("factor", 0.5), + patience=params.get("patience", 10), + threshold=params.get("threshold", 1e-3), + min_lr=params.get("min_lr", base_LR / 20), + cooldown=params.get("cooldown", 50), + ) + elif sched_type in ["exp", "gamma", "exponential"]: + if "gamma" in params.keys(): + gamma = params["gamma"] + elif num_iter is not None: + fac = params.get("factor", 0.01) + gamma = fac ** (1.0 / num_iter) + else: + gamma = 0.999 + scheduler = torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma=gamma) + else: + raise ValueError(f"Unknown scheduler type: {sched_type}") + return scheduler diff --git a/src/quantem/tomography/tomography_nerf.py b/src/quantem/tomography_old/tomography_nerf.py similarity index 78% rename from src/quantem/tomography/tomography_nerf.py rename to src/quantem/tomography_old/tomography_nerf.py index 5251d2d9..2aa27dc8 100644 --- a/src/quantem/tomography/tomography_nerf.py +++ b/src/quantem/tomography_old/tomography_nerf.py @@ -1,17 +1,15 @@ -from torch.utils.data import Dataset, DataLoader, DistributedSampler +import os +from pathlib import Path + +import matplotlib.pyplot as plt +import numpy as np import torch import torch.distributed as dist -import numpy as np - -from quantem.tomography.tomography_dataset import TomographyDataset -from quantem.tomography.models import HSiren - +from torch.utils.data import DataLoader, DistributedSampler from torch.utils.tensorboard import SummaryWriter -import matplotlib.pyplot as plt - -import os -from pathlib import Path +from quantem.tomography.models import HSiren +from quantem.tomography.tomography_dataset import TomographyDataset def get_num_samples_per_ray(epoch): @@ -30,6 +28,7 @@ def get_num_samples_per_ray(epoch): return num_samples + class AuxiliaryParams(torch.nn.Module): def __init__(self, num_tilts, device, zero_tilt_idx=None): super().__init__() @@ -58,12 +57,12 @@ def __init__(self, num_tilts, device, zero_tilt_idx=None): def forward(self, dummy_input=None): # Reconstruct full arrays with zeros at reference position - before_shifts = self.shifts_param[:self.zero_tilt_idx] - after_shifts = self.shifts_param[self.zero_tilt_idx:] + before_shifts = self.shifts_param[: self.zero_tilt_idx] + after_shifts = self.shifts_param[self.zero_tilt_idx :] shifts = torch.cat([before_shifts, self.shifts_ref, after_shifts], dim=0) - before_z1 = self.z1_param[:self.zero_tilt_idx] - after_z1 = self.z1_param[self.zero_tilt_idx:] + before_z1 = self.z1_param[: self.zero_tilt_idx] + after_z1 = self.z1_param[self.zero_tilt_idx :] z1 = torch.cat([before_z1, self.z1_ref, after_z1], dim=0) # before_z3 = self.z3_param[:self.zero_tilt_idx] @@ -73,25 +72,23 @@ def forward(self, dummy_input=None): return shifts, z1, -z1 -class TomographyNerf(): - +class TomographyNerf: def __init__( self, tomo_dataset: TomographyDataset, batch_size: int, num_workers: int = 0, ): - self.tomo_dataset = tomo_dataset self.setup_distributed() self.setup_dataloader( - batch_size = batch_size, - num_workers = num_workers, + batch_size=batch_size, + num_workers=num_workers, ) self.build_model() - + # --- Setup ---- - + def setup_distributed(self): """Setup distributed training if available, otherwise use single GPU/CPU.""" @@ -99,7 +96,9 @@ def setup_distributed(self): if "RANK" in os.environ: # Distributed training if not dist.is_initialized(): - dist.init_process_group(backend='nccl' if torch.cuda.is_available() else 'gloo', init_method='env://') + dist.init_process_group( + backend="nccl" if torch.cuda.is_available() else "gloo", init_method="env://" + ) self.world_size = dist.get_world_size() self.global_rank = dist.get_rank() @@ -129,50 +128,47 @@ def setup_distributed(self): torch.backends.cudnn.allow_tf32 = True def build_model(self): - self.model = HSiren( - in_features = 3, - out_features = 1, - hidden_layers = 4, - hidden_features = 512, - first_omega_0 = 30, - alpha = 1.0, + in_features=3, + out_features=1, + hidden_layers=4, + hidden_features=512, + first_omega_0=30, + alpha=1.0, ).to(self.device) - + if self.world_size > 1: self.model = torch.nn.parallel.DistributedDataParallel( self.model, - device_ids = [self.local_rank], - output_device = self.local_rank, - find_unused_parameters = False, - broadcast_buffers = True, - bucket_cap_mb = 100, - gradient_as_bucket_view = True, + device_ids=[self.local_rank], + output_device=self.local_rank, + find_unused_parameters=False, + broadcast_buffers=True, + bucket_cap_mb=100, + gradient_as_bucket_view=True, ) - + if self.global_rank == 0: print("Model wrapped with DDP") - + if self.world_size > 1: - if self.global_rank == 0: print("Model built, distributed, and compiled successfully") - + else: print("Model built, compiled successfully") - + def setup_dataloader( self, batch_size: int, num_workers: int = 0, ): - if self.world_size > 1: sampler = DistributedSampler( self.tomo_dataset, - num_replicas = self.world_size, - rank = self.global_rank, - shuffle = True, + num_replicas=self.world_size, + rank=self.global_rank, + shuffle=True, ) shuffle = False else: @@ -180,64 +176,61 @@ def setup_dataloader( shuffle = True self.dataloader = DataLoader( self.tomo_dataset, - batch_size = batch_size, - num_workers = num_workers, - sampler = sampler, - shuffle = shuffle, - pin_memory = True if self.device.type == "cuda" else False, - drop_last = True, - persistent_workers = False if num_workers == 0 else True, + batch_size=batch_size, + num_workers=num_workers, + sampler=sampler, + shuffle=shuffle, + pin_memory=True if self.device.type == "cuda" else False, + drop_last=True, + persistent_workers=False if num_workers == 0 else True, ) - + self.sampler = sampler - - + if self.global_rank == 0: - print(f"Dataloader setup complete:") + print("Dataloader setup complete:") print(f" Total projections: {len(self.tomo_dataset.tilt_angles)}") print(f" Grid size: {self.tomo_dataset.dims[1]}{self.tomo_dataset.dims[2]}") print(f" Total pixels: {self.tomo_dataset.num_pixels:,}") - + print(f" Local batch size (train): {batch_size}") - print(f" Global batch size: {batch_size*self.world_size}") + print(f" Global batch size: {batch_size * self.world_size}") print(f" Train batches per GPU per epoch: {len(self.dataloader)}") - # --- Creating Volume --- def create_volume(self): - N = max(self.tomo_dataset.dims) - + with torch.no_grad(): coords_1d = torch.linspace(-1, 1, N) - x, y, z = torch.meshgrid(coords_1d, coords_1d, coords_1d, indexing='ij') + x, y, z = torch.meshgrid(coords_1d, coords_1d, coords_1d, indexing="ij") inputs = torch.stack([x, y, z], dim=-1).reshape(-1, 3) - - model = self.model.module if hasattr(self.model, 'module') else self.model - + + model = self.model.module if hasattr(self.model, "module") else self.model + samples_per_gpu = N**3 // self.world_size start_idx = self.global_rank * samples_per_gpu end_idx = start_idx + samples_per_gpu - + inputs_subset = inputs[start_idx:end_idx].to(self.device) - + # TODO: torch.nn.functional.softplus here # outputs = torch.nn.functional.softplus(model(inputs_subset)) outputs = model(inputs_subset) - + if outputs.dim() > 1: outputs = outputs.squeeze(-1) - + if self.world_size > 1: gathered_outputs = [torch.empty_like(outputs) for _ in range(self.world_size)] dist.all_gather(gathered_outputs, outputs.contiguous()) - + pred_full = torch.cat(gathered_outputs, dim=0).reshape(N, N, N).float() else: pred_full = outputs.reshape(N, N, N).float() - + return pred_full - + # --- Scaling LR --- def get_scaled_lr(self, base_lr, scaling_rule="sqrt"): """Scale learning rate based on world size.""" @@ -252,23 +245,22 @@ def get_scaled_lr(self, base_lr, scaling_rule="sqrt"): return base_lr * np.sqrt(self.world_size) else: raise ValueError(f"Invalid scaling rule: {scaling_rule}") - + # --- Creating Optimizer --- def create_optimizer( self, params, lr, - fused = True, + fused=True, ): - return torch.optim.Adam( params, - lr = lr, - fused = fused, + lr=lr, + fused=fused, ) - + # Batch Projection Rays --- - + def create_batch_projection_rays(self, pixel_i, pixel_j, N, num_samples_per_ray): """Create projection rays for entire batch simultaneously.""" batch_size = len(pixel_i) @@ -289,10 +281,9 @@ def create_batch_projection_rays(self, pixel_i, pixel_j, N, num_samples_per_ray) rays[:, :, 2] = z_coords.unsqueeze(0) # z varies along ray return rays - - @torch.compile(mode="reduce-overhead") - def transform_batch_ray_coordinates(self, rays, z1, x, z3, shifts, N, sampling_rate): + @torch.compile(mode="reduce-overhead") + def transform_batch_ray_coordinates(self, rays, z1, x, z3, shifts, N, sampling_rate): # Step 1: Apply shifts shift_x_norm = (shifts[:, 0:1] * sampling_rate * 2) / (N - 1) shift_y_norm = (shifts[:, 1:2] * sampling_rate * 2) / (N - 1) @@ -332,7 +323,7 @@ def transform_batch_ray_coordinates(self, rays, z1, x, z3, shifts, N, sampling_r transformed_rays = torch.stack([rays_x_final, rays_y_final, rays_z_final], dim=2) return transformed_rays - + # --- Training --- def train( self, @@ -348,90 +339,93 @@ def train( if not log_path.exists(): log_path.mkdir(parents=True, exist_ok=True) self.writer = SummaryWriter(log_dir=log_path) - + # Find index closest to zero degrees zero_tilt_idx = torch.argmin(torch.abs(self.tomo_dataset.tilt_angles)).item() - + if self.global_rank == 0: - print(f"Using projection {zero_tilt_idx} (angle={self.tomo_dataset.tilt_angles[zero_tilt_idx]:.2f}°) as reference") - + print( + f"Using projection {zero_tilt_idx} (angle={self.tomo_dataset.tilt_angles[zero_tilt_idx]:.2f}°) as reference" + ) aux_params = AuxiliaryParams( - num_tilts = len(self.tomo_dataset.tilt_angles), - device = self.device, - zero_tilt_idx = zero_tilt_idx, + num_tilts=len(self.tomo_dataset.tilt_angles), + device=self.device, + zero_tilt_idx=zero_tilt_idx, ) - + if self.world_size > 1: aux_params = torch.nn.parallel.DistributedDataParallel( aux_params, - device_ids = [self.local_rank], - output_device = self.local_rank, - find_unused_parameters = False, - broadcast_buffers = True, + device_ids=[self.local_rank], + output_device=self.local_rank, + find_unused_parameters=False, + broadcast_buffers=True, ) - + if self.global_rank == 0: print("Auxiliary parameters wrapped with DDP") - + scaled_train_lr = self.get_scaled_lr(train_lr, scaling_rule="sqrt") scaled_aux_lr = self.get_scaled_lr(train_lr, scaling_rule="sqrt") - + optimizer = self.create_optimizer( self.model.parameters(), scaled_train_lr, - fused = True, + fused=True, ) - + aux_optimizer = self.create_optimizer( aux_params.parameters(), scaled_aux_lr, - fused = True, + fused=True, ) - + aux_norm = torch.tensor(0.0, device=self.device) model_norm = torch.tensor(0.0, device=self.device) - + optimizer.zero_grad() aux_optimizer.zero_grad() - + warmup_scheduler_model = torch.optim.lr_scheduler.LinearLR( optimizer, - start_factor = 0.001, - total_iters = warmup_epochs, + start_factor=0.001, + total_iters=warmup_epochs, ) cosine_scheduler_model = torch.optim.lr_scheduler.CosineAnnealingLR( optimizer, - T_max = epochs - warmup_epochs, - eta_min = scaled_train_lr / 100, + T_max=epochs - warmup_epochs, + eta_min=scaled_train_lr / 100, ) - + scheduler_model = torch.optim.lr_scheduler.SequentialLR( optimizer, - schedulers = [warmup_scheduler_model, cosine_scheduler_model], - milestones = [warmup_epochs], + schedulers=[warmup_scheduler_model, cosine_scheduler_model], + milestones=[warmup_epochs], ) - + scheduler_aux = torch.optim.lr_scheduler.CosineAnnealingLR( aux_optimizer, - T_max = epochs, - eta_min = scaled_aux_lr / 100, + T_max=epochs, + eta_min=scaled_aux_lr / 100, ) - + N = max(self.tomo_dataset.dims) - + device_type = self.device.type autocast_dtype = torch.bfloat16 if use_amp else None - + for epoch in range(epochs): num_samples_per_ray = get_num_samples_per_ray(epoch) - + # Log the change if it happens if epoch > 0: prev_samples = get_num_samples_per_ray(epoch - 1) - + if num_samples_per_ray != prev_samples and self.global_rank == 0: - print(f"Epoch {epoch}: Changing num_samples_per_ray from {prev_samples} to {num_samples_per_ray}") + print( + f"Epoch {epoch}: Changing num_samples_per_ray from {prev_samples} to {num_samples_per_ray}" + ) if self.sampler is not None: self.sampler.set_epoch(epoch) @@ -442,24 +436,24 @@ def train( epoch_z1_loss = 0.0 num_batches = 0 - - for batch_idx, batch in enumerate(self.dataloader): - projection_indices = batch['projection_idx'] + for batch_idx, batch in enumerate(self.dataloader): + projection_indices = batch["projection_idx"] - pixel_i = batch['pixel_i'].float().to(self.device, non_blocking=True) - pixel_j = batch['pixel_j'].float().to(self.device, non_blocking=True) - target_values = batch['target_value'].to(self.device, non_blocking=True) - phis = batch['phi'].to(self.device, non_blocking=True) - projection_indices = batch['projection_idx'].to(self.device, non_blocking=True) + pixel_i = batch["pixel_i"].float().to(self.device, non_blocking=True) + pixel_j = batch["pixel_j"].float().to(self.device, non_blocking=True) + target_values = batch["target_value"].to(self.device, non_blocking=True) + phis = batch["phi"].to(self.device, non_blocking=True) + projection_indices = batch["projection_idx"].to(self.device, non_blocking=True) shifts, z1_params, z3_params = aux_params(None) batch_shifts = torch.index_select(shifts, 0, projection_indices) batch_z1 = torch.index_select(z1_params, 0, projection_indices) batch_z3 = torch.index_select(z3_params, 0, projection_indices) - - with torch.autocast(device_type=device_type, dtype=autocast_dtype, enabled=use_amp): + with torch.autocast( + device_type=device_type, dtype=autocast_dtype, enabled=use_amp + ): with torch.no_grad(): batch_ray_coords = self.create_batch_projection_rays( pixel_i, pixel_j, N, num_samples_per_ray @@ -472,7 +466,7 @@ def train( z3=batch_z3, shifts=batch_shifts, N=N, - sampling_rate=1.0 + sampling_rate=1.0, ) all_coords = transformed_rays.view(-1, 3) @@ -485,20 +479,25 @@ def train( # Create mask for valid coordinates (within [-1, 1]^3) valid_mask = ( - (all_coords[:, 0] >= -1) & (all_coords[:, 0] <= 1) & - (all_coords[:, 1] >= -1) & (all_coords[:, 1] <= 1) & - (all_coords[:, 2] >= -1) & (all_coords[:, 2] <= 1) + (all_coords[:, 0] >= -1) + & (all_coords[:, 0] <= 1) + & (all_coords[:, 1] >= -1) + & (all_coords[:, 1] <= 1) + & (all_coords[:, 2] >= -1) + & (all_coords[:, 2] <= 1) ).float() - + all_densities = all_densities * valid_mask - + if tv_weight > 0: num_tv_samples = min(10000, all_coords.shape[0]) - tv_indices = torch.randperm(all_coords.shape[0], device=all_coords.device)[:num_tv_samples] + tv_indices = torch.randperm(all_coords.shape[0], device=all_coords.device)[ + :num_tv_samples + ] # Rerun forward for gradient tracking tv_coords = all_coords[tv_indices].detach().requires_grad_(True) - + # TODO: torch.nn.functional.softplus here # tv_densities_recomputed = torch.nn.functional.softplus(self.model(tv_coords)) # Get rid of this tv_densities_recomputed = self.model(tv_coords) @@ -510,15 +509,17 @@ def train( outputs=tv_densities_recomputed, inputs=tv_coords, grad_outputs=torch.ones_like(tv_densities_recomputed), - create_graph=True + create_graph=True, )[0] - + grad_norm = torch.norm(grad_outputs, dim=1) tv_loss = tv_weight * grad_norm.mean() else: tv_loss = torch.tensor(0.0, device=self.device) - ray_densities = all_densities.view(len(target_values), num_samples_per_ray) # Reshape rays and integarte + ray_densities = all_densities.view( + len(target_values), num_samples_per_ray + ) # Reshape rays and integarte step_size = 2.0 / (num_samples_per_ray - 1) predicted_values = ray_densities.sum(dim=1) * step_size @@ -542,34 +543,40 @@ def train( num_batches += 1 if epoch >= warmup_epochs: - model_norm = torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm = 1) + model_norm = torch.nn.utils.clip_grad_norm_( + self.model.parameters(), max_norm=1 + ) optimizer.step() optimizer.zero_grad() - - aux_norm = torch.nn.utils.clip_grad_norm_(aux_params.parameters(), max_norm = 1) + + aux_norm = torch.nn.utils.clip_grad_norm_(aux_params.parameters(), max_norm=1) aux_optimizer.step() aux_optimizer.zero_grad() else: - model_norm = torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm = 1) + model_norm = torch.nn.utils.clip_grad_norm_( + self.model.parameters(), max_norm=1 + ) optimizer.step() optimizer.zero_grad() - + aux_optimizer.zero_grad() - + scheduler_model.step() - + if epoch >= warmup_epochs: scheduler_aux.step() - if epoch % viz_freq == 0 or epoch == epochs - 1 or epoch % checkpoint_freq == 0 : + if epoch % viz_freq == 0 or epoch == epochs - 1 or epoch % checkpoint_freq == 0: with torch.no_grad(): pred_full = self.create_volume().cpu() avg_loss = epoch_loss.item() / num_batches avg_mse_loss = epoch_mse_loss.item() / num_batches avg_tv_loss = epoch_tv_loss.item() / num_batches avg_z1_loss = epoch_z1_loss.item() / num_batches - - metrics = torch.tensor([avg_loss, avg_mse_loss, avg_tv_loss, avg_z1_loss], device=self.device) + + metrics = torch.tensor( + [avg_loss, avg_mse_loss, avg_tv_loss, avg_z1_loss], device=self.device + ) if self.world_size > 1: torch.distributed.all_reduce(metrics, op=torch.distributed.ReduceOp.AVG) avg_loss, avg_mse_loss, avg_tv_loss, avg_z1_loss = metrics.tolist() @@ -592,17 +599,17 @@ def train( self.writer.add_scalar("train/num_samples_per_ray", num_samples_per_ray, epoch) fig, axes = plt.subplots(1, 3, figsize=(36, 12)) - axes[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap='turbo', vmin=0) - axes[0].set_title('Sum over Z-axis') + axes[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap="turbo", vmin=0) + axes[0].set_title("Sum over Z-axis") - axes[1].matshow(pred_full[N//2].cpu().numpy(), cmap='turbo', vmin=0) - axes[1].set_title(f'Slice at Z={N//2}') + axes[1].matshow(pred_full[N // 2].cpu().numpy(), cmap="turbo", vmin=0) + axes[1].set_title(f"Slice at Z={N // 2}") - slice_start = max(0, N//2 - 5) - slice_end = min(N, N//2 + 6) + slice_start = max(0, N // 2 - 5) + slice_end = min(N, N // 2 + 6) thick_slice = pred_full[slice_start:slice_end].sum(dim=0).cpu().numpy() - axes[2].matshow(thick_slice, cmap='turbo', vmin=0) - axes[2].set_title(f'Thick slice sum (Z={slice_start}:{slice_end-1})') + axes[2].matshow(thick_slice, cmap="turbo", vmin=0) + axes[2].set_title(f"Thick slice sum (Z={slice_start}:{slice_end - 1})") self.writer.add_figure("train/viz", fig, epoch, close=True) plt.close(fig) @@ -616,8 +623,8 @@ def train( def save_checkpoint(self, epoch, aux_params, log_path): """Save model and auxiliary parameters checkpoint (rank 0 only).""" if self.global_rank == 0: - model_to_save = self.model.module if hasattr(self.model, 'module') else self.model - aux_params_to_save = aux_params.module if hasattr(aux_params, 'module') else aux_params + model_to_save = self.model.module if hasattr(self.model, "module") else self.model + aux_params_to_save = aux_params.module if hasattr(aux_params, "module") else aux_params checkpoint = { "model_state_dict": model_to_save.state_dict(), @@ -626,4 +633,4 @@ def save_checkpoint(self, epoch, aux_params, log_path): } checkpoint_path = os.path.join(log_path, f"checkpoint_epoch_{epoch:04d}.pt") torch.save(checkpoint, checkpoint_path) - print(f"Checkpoint saved at epoch {epoch}: {checkpoint_path}") \ No newline at end of file + print(f"Checkpoint saved at epoch {epoch}: {checkpoint_path}") diff --git a/src/quantem/tomography_old/utils.py b/src/quantem/tomography_old/utils.py new file mode 100644 index 00000000..82c1c9d5 --- /dev/null +++ b/src/quantem/tomography_old/utils.py @@ -0,0 +1,300 @@ +import numpy as np +import torch +import torch.nn.functional as F +from scipy.ndimage import center_of_mass, gaussian_filter, shift +from scipy.stats import norm +from tqdm.auto import tqdm + +from quantem.core.utils.imaging_utils import cross_correlation_shift + +# --- Projection Operator Utils --- + + +def rot_ZXZ(mags, z1, x, z3, device, mode="bilinear"): + if not isinstance(x, torch.Tensor) or not isinstance(z1, torch.Tensor): + z1 = torch.tensor(z1, dtype=torch.float32, device=device) + x = torch.tensor(x, dtype=torch.float32, device=device) + z3 = torch.tensor(z3, dtype=torch.float32, device=device) + curr_mags = mags + + curr_mags = differentiable_rotz_vectorized(curr_mags, z1, mode) + curr_mags = differentiable_rotx_vectorized(curr_mags, x, mode) + + curr_mags = differentiable_rotz_vectorized(curr_mags, z3, mode) + + return curr_mags + + +def differentiable_rotz_vectorized(mags, theta, mode="bilinear"): + _, dimz, dimy, dimx = mags.shape + + if theta.dim() == 0: + theta = theta.unsqueeze(0) + + theta_rad = torch.deg2rad(theta) + + cos_t, sin_t = torch.cos(theta_rad), torch.sin(theta_rad) + affine_matrix = torch.stack( + [cos_t, -sin_t, torch.zeros_like(theta), sin_t, cos_t, torch.zeros_like(theta)], dim=-1 + ).view(-1, 2, 3) + + mags = mags.permute(1, 0, 2, 3) + + def transform_slice(mag_slice): + grid = F.affine_grid(affine_matrix, mag_slice.unsqueeze(0).shape, align_corners=False) + return F.grid_sample(mag_slice.unsqueeze(0), grid, mode=mode, align_corners=False).squeeze( + 0 + ) + + rotated_mags = torch.vmap(transform_slice)(mags) + return rotated_mags.permute(1, 0, 2, 3) + + +def differentiable_rotx_vectorized(mags, theta, mode="bilinear"): + _, dimz, dimy, dimx = mags.shape + + if theta.dim() == 0: + theta = theta.unsqueeze(0) + + theta_rad = torch.deg2rad(theta) + + cos_t, sin_t = torch.cos(theta_rad), torch.sin(theta_rad) + affine_matrix = torch.stack( + [cos_t, -sin_t, torch.zeros_like(theta), sin_t, cos_t, torch.zeros_like(theta)], dim=-1 + ).view(-1, 2, 3) + + mags = mags.permute(3, 0, 1, 2) + + def transform_slice(mag_slice): + grid = F.affine_grid(affine_matrix, mag_slice.unsqueeze(0).shape, align_corners=False) + return F.grid_sample(mag_slice.unsqueeze(0), grid, mode=mode, align_corners=False).squeeze( + 0 + ) + + rotated_mags = torch.vmap(transform_slice)(mags) + return rotated_mags.permute(1, 2, 3, 0) + + +def differentiable_shift_2d(image, shift_x, shift_y, sampling_rate): + """ + Shifts a 2D image using grid_sample in a differentiable manner. + + Args: + image: Tensor of shape [H, W] + shift_x: Scalar tensor (dx) for shift in x-direction (in physical units) + shift_y: Scalar tensor (dy) for shift in y-direction (in physical units) + sampling_rate: Scalar value (physical units per pixel) to correctly normalize shifts + + Returns: + Shifted image of shape [H, W] + """ + H, W = image.shape + + # Convert physical shift to pixel shift + shift_x_pixel = shift_x + shift_y_pixel = shift_y + + # Normalize shift for grid_sample (assuming align_corners=True) + normalized_shift_x = shift_x_pixel * 2 / (W - 1) + normalized_shift_y = shift_y_pixel * 2 / (H - 1) + + # Create normalized grid + grid_y, grid_x = torch.meshgrid( + torch.linspace(-1, 1, H, device=image.device), + torch.linspace(-1, 1, W, device=image.device), + indexing="ij", + ) + + grid = torch.stack((grid_x, grid_y), dim=-1).unsqueeze(0) # [1, H, W, 2] + + # Apply shift (ensure it's differentiable) + grid[:, :, :, 0] -= normalized_shift_x + grid[:, :, :, 1] -= normalized_shift_y + + # Add batch and channel dimensions + image = image.unsqueeze(0).unsqueeze(0) # [1, 1, H, W] + + # Sample using grid_sample (fully differentiable) + shifted_image = F.grid_sample( + image, grid, mode="bicubic", padding_mode="zeros", align_corners=True + ) + + return shifted_image.squeeze(0).squeeze(0) # Back to [H, W] + + +# --- TV loss --- + + +def get_TV_loss(tensor, factor=1e-3): + tv_d = torch.pow(tensor[:, :, 1:, :, :] - tensor[:, :, :-1, :, :], 2).sum() + tv_h = torch.pow(tensor[:, :, :, 1:, :] - tensor[:, :, :, :-1, :], 2).sum() + tv_w = torch.pow(tensor[:, :, :, :, 1:] - tensor[:, :, :, :, :-1], 2).sum() + tv_loss = tv_d + tv_h + tv_w + + return tv_loss * factor / (torch.prod(torch.tensor(tensor.shape))) + + +# --- Gaussian filters --- + + +def gaussian_kernel_1d(sigma: float, num_sigmas: float = 3.0) -> torch.Tensor: + radius = np.ceil(num_sigmas * sigma) + support = torch.arange(-radius, radius + 1, dtype=torch.float) + kernel = torch.distributions.Normal(loc=0, scale=sigma).log_prob(support).exp_() + # Ensure kernel weights sum to 1, so that image brightness is not altered + return kernel.mul_(1 / kernel.sum()) + + +def gaussian_filter_2d( + img: torch.Tensor, sigma: float, kernel_1d: torch.Tensor +) -> torch.Tensor: # Add kernel_1d as an argument + # kernel_1d = gaussian_kernel_1d(sigma) # Create 1D Gaussian kernel - Moved outside function + padding = len(kernel_1d) // 2 # Ensure that image size does not change + img = img.unsqueeze(0).unsqueeze_(0) # Make copy, make 4D for ``conv2d()`` + # Convolve along columns and rows + img = torch.nn.functional.conv2d(img, weight=kernel_1d.view(1, 1, -1, 1), padding=(padding, 0)) + img = torch.nn.functional.conv2d(img, weight=kernel_1d.view(1, 1, 1, -1), padding=(0, padding)) + return img.squeeze_(0).squeeze_(0) # Make 2D again + + +def gaussian_filter_2d_stack(stack: torch.Tensor, kernel_1d: torch.Tensor) -> torch.Tensor: + """ + Apply 2D Gaussian blur to each slice stack[:, i, :] in a vectorized way. + + Args: + stack (torch.Tensor): Tensor of shape (H, N, W) where N is num_sinograms + kernel_1d (torch.Tensor): 1D Gaussian kernel + + Returns: + torch.Tensor: Blurred stack of same shape (H, N, W) + """ + H, N, W = stack.shape + padding = len(kernel_1d) // 2 + + # Reshape to (N, 1, H, W) for conv2d + stack_reshaped = stack.permute(1, 0, 2).unsqueeze(1) # (N, 1, H, W) + + # Apply separable conv2d: vertical then horizontal + out = torch.nn.functional.conv2d( + stack_reshaped, kernel_1d.view(1, 1, -1, 1), padding=(padding, 0) + ) + out = torch.nn.functional.conv2d(out, kernel_1d.view(1, 1, 1, -1), padding=(0, padding)) + + # Restore shape to (H, N, W) + return out.squeeze(1).permute(1, 0, 2) + + +# Circular mask + + +def torch_phase_cross_correlation(im1, im2): + f1 = torch.fft.fft2(im1) + f2 = torch.fft.fft2(im2) + cc = torch.fft.ifft2(f1 * torch.conj(f2)) + cc_abs = torch.abs(cc) + + max_idx = torch.argmax(cc_abs) + shifts = torch.tensor(np.unravel_index(max_idx.item(), im1.shape), device=im1.device).float() + + for i, dim in enumerate(im1.shape): + if shifts[i] > dim // 2: + shifts[i] -= dim + + # return shifts.flip(0) # (dx, dy) + return shifts + + +# --- Tilt Series Processing Utility Functions --- + + +def fourier_cropping(img, crop_size): + """ + Crop the img in Fourier space to the specified size. + """ + center = np.array(img.shape) // 2 + + fft_img = np.fft.fftshift(np.fft.fft2(img)) + + cropped_fft = fft_img[ + center[0] - crop_size[0] // 2 : center[0] + crop_size[0] // 2, + center[1] - crop_size[1] // 2 : center[1] + crop_size[1] // 2, + ] + cropped_img = np.fft.ifft2(np.fft.ifftshift(cropped_fft)).real + return cropped_img + + +def estimate_background( + img, + num_iterations=10, + cutoff=3, + smoothing_sigma=1.0, +): + """ + Estimate the background of the image using a Gaussian filter. + """ + if smoothing_sigma > 0: + img = gaussian_filter(img, sigma=smoothing_sigma) + pixel_vals = img.ravel() + + for i in range(num_iterations): + mu, std = norm.fit(pixel_vals) + + # Set cutoff threshold (e.g., 3 standard deviations) + lower = mu - cutoff * std + upper = mu + cutoff * std + + # Mask pixel values within ±3σ + pixel_vals = pixel_vals[(pixel_vals >= lower) & (pixel_vals <= upper)] + + return mu + + +def cross_correlation_align_stack(ref_img, stack, print_pred=False): + """ + Aligns a stack of images to a reference image using cross-correlation. + + This function assumes the stack does not contain the reference image itself. + + Stack shape should be (N, H, W) where N is the number of images. + """ + + new_images = [] + pred_shifts = [] + + prev_img = ref_img + for img in tqdm(stack): + shift_pred = cross_correlation_shift(prev_img, img) + if print_pred: + print(f"Shift prediction: {shift_pred}") + shifted_image = shift(img, shift=shift_pred, mode="constant", cval=0.0) + + pred_shifts.append(shift_pred) + new_images.append(shifted_image) + + prev_img = shifted_image + + return new_images, pred_shifts + + +def centering_com_alignment(image_stack): + """ + Aligns the image stack to the center of mass of the whole image_stack to the + image center. This is useful for aligning the tilt series to the invariant line. + """ + + aligned_stack = np.zeros_like(image_stack) + h, w = image_stack.shape[1:] + image_center = np.array([h // 2, w // 2]) + + com_reference = np.array(center_of_mass(image_stack.mean(axis=0))) + + for i, img in enumerate(image_stack): + com_img = np.array(center_of_mass(img)) + shift_vec = com_reference - com_img + aligned_stack[i] = shift(img, shift=shift_vec, mode="constant", cval=0.0) + + final_shift = image_center - com_reference + for i in range(aligned_stack.shape[0]): + aligned_stack[i] = shift(aligned_stack[i], shift=final_shift, mode="constant", cval=0.0) + + return aligned_stack From b90499d2ffcf8222d61c87f42b425f19ff57f281 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 16 Dec 2025 16:28:35 -0800 Subject: [PATCH 033/335] Need to think about tomography_ddp a little bit more, also what should live in tomography_base. Need to implement object_models for incorporating different forward calls --- src/quantem/tomography/dataset_models.py | 52 +++- src/quantem/tomography/logger_tomography.py | 21 ++ src/quantem/tomography/tomography_base.py | 25 ++ src/quantem/tomography/tomography_ddp.py | 327 ++++++++++++++++++++ 4 files changed, 418 insertions(+), 7 deletions(-) create mode 100644 src/quantem/tomography/logger_tomography.py diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index 994d3356..b83d89d7 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -1,6 +1,6 @@ from abc import abstractmethod from dataclasses import dataclass -from typing import Any +from typing import Any, Literal import torch import torch.nn as nn @@ -21,6 +21,7 @@ class DatasetValue: target: torch.Tensor tilt_angle: int | float pixel_loc: tuple[int, int] | None = None # Only for INRDataset + # pose: tuple[torch.nn.Parameter, torch.nn.Parameter, torch.nn.Parameter] | None = None # If there is pose optimization. class TomographyDatasetBase(AutoSerialize, OptimizerMixin, nn.Module): @@ -36,7 +37,7 @@ class TomographyDatasetBase(AutoSerialize, OptimizerMixin, nn.Module): def __init__( self, - tilt_stack: Dataset3d, + tilt_stack: Dataset3d | NDArray | torch.Tensor, tilt_angles: NDArray | torch.Tensor, learn_pose: bool = True, _token: object | None = None, @@ -56,10 +57,14 @@ def __init__( ) self.tilt_stack = tilt_stack + self.volume_size = (int(tilt_stack.shape.max()), tilt_stack.shape[1], tilt_stack.shape[2]) self.tilt_angles = tilt_angles self.learn_pose = learn_pose + # The reference tilt angle is the one with the smallest absolute tilt angle. + # I.e, the pose will not be optimized for the reference tilt angle. self._reference_tilt_angle_idx = torch.argmin(torch.abs(self.tilt_angles)) + # TODO: Implement AuxParams from old tomography_dataset.py here. @abstractmethod def forward( @@ -93,7 +98,11 @@ def __init__( def forward( self, proj_idx: int, - ): + ) -> DatasetValue: + """ + Forward pass for pixel-based tomography. + Returns the full tilt image for the given projection index, and the tilt angle. + """ return DatasetValue( target=self.tilt_stack[proj_idx], tilt_angle=self.tilt_angles[proj_idx], @@ -111,8 +120,29 @@ class TomographyINRDataset(TomographyDatasetBase, Dataset): def __init__( self, + tilt_stack: Dataset3d | NDArray | torch.Tensor, + tilt_angles: NDArray | torch.Tensor, + learn_pose: bool = True, + val_ratio: float = 0.0, + mode: Literal["train", "val"] = "train", + seed: int = 42, + _token: object | None = None, ): - pass + super().__init__(tilt_stack, tilt_angles, learn_pose, _token) + + self._total_pixels = self.volume_size[0] * self.volume_size[1] * self.volume_size[2] + + # Create train/val split + torch.manual_seed(seed) + all_indices = torch.randperm(self.total_pixels) + + num_val = int(self.total_pixels * val_ratio) + if mode == "val": + self.indices = all_indices[:num_val] + else: # train + self.indices = all_indices[num_val:] + + self._mode = mode def forward(self, dummy_input: Any = None): """ @@ -124,7 +154,7 @@ def forward(self, dummy_input: Any = None): def __getitem__( self, idx: int, - ) -> dict[str, Any]: + ) -> DatasetValue: """ Gets the item for INR i.e, the project index, pixel value at (i, j), and the tilt angle. """ @@ -150,7 +180,15 @@ def __len__( """ Returns the number of pixels in the tilt stack. """ - pass + + return self._total_pixels + + @property + def mode(self) -> Literal["train", "val"]: + """ + Returns the mode of the dataset. + """ + return self._mode -DatasetModelType = TomographyINRDataset +DatasetModelType = TomographyINRDataset | TomographyPixDataset diff --git a/src/quantem/tomography/logger_tomography.py b/src/quantem/tomography/logger_tomography.py new file mode 100644 index 00000000..d9f0f8bb --- /dev/null +++ b/src/quantem/tomography/logger_tomography.py @@ -0,0 +1,21 @@ +from quantem.core.ml.logger import LoggerBase + + +class LoggerTomography(LoggerBase): + """ + Logger for ML-based tomography reconstructions. + """ + + def __init__( + self, + log_dir: str, + run_prefix: str, + run_suffix: str = None, + log_images_every: int = 10, + ): + super().__init__(log_dir, run_prefix, run_suffix, log_images_every) + + def log_epoch(self, epoch: int, loss: float, tilt_series_loss: float, soft_loss: float): + self.log_scalar("loss/total", loss, epoch) + self.log_scalar("loss/tilt_series", tilt_series_loss, epoch) + self.log_scalar("loss/soft", soft_loss, epoch) diff --git a/src/quantem/tomography/tomography_base.py b/src/quantem/tomography/tomography_base.py index 8b137891..cb70c15c 100644 --- a/src/quantem/tomography/tomography_base.py +++ b/src/quantem/tomography/tomography_base.py @@ -1 +1,26 @@ +import numpy as np +from quantem.core.io.serialize import AutoSerialize +from quantem.core.utils.rng import RNGMixin +from quantem.tomography.dataset_models import DatasetModelType +from quantem.tomography.logger_tomography import LoggerTomography + + +class TomographyBase(AutoSerialize, RNGMixin): + """ + A base class for performing electron tomography reconstructions. + + Should have all the default attributes needed for tomography reconstructions. + """ + + _token = object() + + def __init__( + self, + dset: DatasetModelType, + logger: LoggerTomography | None = None, + device: str = "cuda", + rng: np.random.Generator | int | None = None, + _token: object | None = None, + ): + pass diff --git a/src/quantem/tomography/tomography_ddp.py b/src/quantem/tomography/tomography_ddp.py index e69de29b..52d2e783 100644 --- a/src/quantem/tomography/tomography_ddp.py +++ b/src/quantem/tomography/tomography_ddp.py @@ -0,0 +1,327 @@ +import os + +import numpy as np +import torch +import torch.distributed as dist +import torch.nn as nn +from torch.utils.data import DataLoader, DistributedSampler + +from quantem.tomography.dataset_models import ( + TomographyINRDataset, # Only INR dataset is supported for DDP +) + +# from quantem.tomography.tomography_dataset import ( +# PretrainVolumeDataset, +# TomographyDataset, +# TomographyRayDataset, +# ) + + +class TomographyDDP: + """ + Initializing DDP stuff for tomo class. + """ + + def __init__( + self, + ): + self.setup_distributed() + + def setup_distributed(self): + # Check if in distributed env + if "RANK" in os.environ: + # Distributed training + if not dist.is_initialized(): + dist.init_process_group( + backend="nccl" if torch.cuda.is_available() else "gloo", init_method="env://" + ) + + self.world_size = dist.get_world_size() + self.global_rank = dist.get_rank() + self.local_rank = int(os.environ["LOCAL_RANK"]) + + torch.cuda.set_device(self.local_rank) + self.device = torch.device("cuda", self.local_rank) + + else: + # Single GPU/CPU training + self.world_size = 1 + self.global_rank = 0 + self.local_rank = 0 + + if torch.cuda.is_available(): + self.device = torch.device("cuda") + torch.cuda.set_device(0) + print("Single GPU training") + else: + self.device = torch.device("cpu") + print("CPU training") + + # Optional performance optimizations (only for CUDA) + if self.device.type == "cuda": + torch.backends.cudnn.benchmark = True + torch.backends.cuda.matmul.allow_tf32 = True + torch.backends.cudnn.allow_tf32 = True + + def build_model( + self, + model: nn.Module, + ): + # TODO: Generalized model --> Should be instantiated in the object? Where does `HSIREN` get instantiated? + model = model.to(self.device) + + if self.world_size > 1: + model = torch.nn.parallel.DistributedDataParallel( + model, + device_ids=[self.local_rank], + output_device=self.local_rank, + find_unused_parameters=False, + broadcast_buffers=True, + bucket_cap_mb=100, + gradient_as_bucket_view=True, + ) + + if self.global_rank == 0: + print("Model wrapped with DDP") + + if self.world_size > 1: + if self.global_rank == 0: + print("Model built, distributed, and compiled successfully") + + else: + print("Model built, compiled successfully") + + return model + + # Setup Tomo DataLoader + def setup_dataloader( + self, + tomo_dataset: TomographyINRDataset, + batch_size: int, + num_workers: int = 0, + val_fraction: float = 0.0, + volume_size: int = 500, # Assuming N^3 volume size + ): + pin_mem = self.device.type == "cuda" + persist = num_workers > 0 + + # Split dataset if validation fraction > 0 + if val_fraction > 0.0: + # TODO: Temporary for when only doing validation, current TomographyDataset doesn't work correctly + train_dataset = TomographyINRDataset( + tomo_dataset.tilt_series.detach().clone(), + tomo_dataset.tilt_angles.detach().clone(), + volume_size, # TODO: TEMPORARY + val_ratio=val_fraction, + mode="train", + seed=42, + ) + val_dataset = TomographyINRDataset( + tomo_dataset.tilt_series.detach().clone(), + tomo_dataset.tilt_angles.detach().clone(), + volume_size, # TODO: TEMPORARY + val_ratio=val_fraction, + mode="val", + seed=42, + ) + else: + train_dataset, val_dataset = tomo_dataset, None + + # Samplers for distributed training + if self.world_size > 1: + train_sampler = DistributedSampler( + train_dataset, + num_replicas=self.world_size, + rank=self.global_rank, + shuffle=True, + ) + if val_dataset: + val_sampler = DistributedSampler( + val_dataset, + num_replicas=self.world_size, + rank=self.global_rank, + shuffle=False, + ) + shuffle = False + else: + train_sampler = None + val_sampler = None + shuffle = True + + # Main dataloader + self.dataloader = DataLoader( + train_dataset, + batch_size=batch_size, + num_workers=num_workers, + sampler=train_sampler, + shuffle=shuffle, + pin_memory=pin_mem, + drop_last=True, + persistent_workers=persist, + ) + + # Validation dataloader if applicable + if val_dataset: + self.val_dataloader = DataLoader( + val_dataset, + batch_size=batch_size * 4, + num_workers=num_workers, + sampler=val_sampler, + shuffle=False, + pin_memory=pin_mem, + drop_last=False, + persistent_workers=persist, + ) + self.val_sampler = val_sampler + + self.sampler = train_sampler + + if self.global_rank == 0: + print("Dataloader setup complete:") + print(f" Total projections: {len(tomo_dataset.tilt_angles)}") + if val_fraction > 0.0: + print(f" Total projections (val): {len(val_dataset)}") + print(f" Grid size: {tomo_dataset.dims[1]}{tomo_dataset.dims[2]}") + print(f" Total pixels: {tomo_dataset.num_pixels:,}") + if val_fraction > 0.0: + print(f" Total pixels (val): {len(val_dataset):,}") + print(f" Total pixels (train): {len(train_dataset):,}") + print(f" Local batch size (train): {batch_size}") + print(f" Global batch size: {batch_size * self.world_size}") + print(f" Train batches per GPU per epoch: {len(self.dataloader)}") + + # Setup pretraining dataloader + + # def setup_pretraining_dataloader( + # self, + # volume_dataset: PretrainVolumeDataset, + # batch_size: int, + # ): + # # TODO: temp + + # num_workers = 0 + # if self.world_size > 1: + # sampler = DistributedSampler( + # volume_dataset, + # num_replicas=self.world_size, + # rank=self.global_rank, + # shuffle=True, + # drop_last=True, + # ) + # shuffle = False + # else: + # sampler = None + # shuffle = True + + # self.pretraining_dataloader = DataLoader( + # volume_dataset, + # batch_size=batch_size, + # sampler=sampler, + # shuffle=shuffle, + # pin_memory=self.device.type == "cuda", + # drop_last=True, + # persistent_workers=num_workers > 0, + # ) + + # self.pretraining_sampler = sampler + + # if self.global_rank == 0: + # print("Pretraining dataloader setup complete:") + # print(f" Total samples: {len(volume_dataset)}") + # print(f" Grid size: {volume_dataset.N**3}") + # print(f" Local batch size: {batch_size}") + # print(f" Global batch size: {batch_size * self.world_size}") + # print(f" Pretraining batches per GPU per epoch: {len(self.pretraining_dataloader)}") + + def get_scaled_lr(self, base_lr, scaling_rule="sqrt"): + if scaling_rule == "sqrt": + return base_lr * np.sqrt(self.world_size) + elif scaling_rule == "linear": + return base_lr * self.world_size + else: + raise ValueError(f"Invalid scaling rule: {scaling_rule}") + + def scale_lr( + self, + optimizer_params: dict, + ): + new_optimizer_params = {} + for key, value in optimizer_params.items(): + if "original_lr" in value: + new_optimizer_params[key] = { + "type": value["type"], + "lr": self.get_scaled_lr(value["original_lr"]), + "original_lr": value["original_lr"], + } + else: + new_optimizer_params[key] = { + "type": value["type"], + "lr": self.get_scaled_lr(value["lr"]), + "original_lr": value["lr"], + } + + return new_optimizer_params + + +# TODO: Temporary Adaptive L1 Smooth Loss + + +class AdaptiveSmoothL1Loss(nn.Module): + def __init__(self, beta_init=None, ema_factor=0.99, eps=1e-8): + """ + Adaptive smooth L1 loss with EMA-based beta adaptation. + + Args: + beta_init (float): optional initial β value; if None, starts as 1.0 + ema_factor (float): smoothing factor for EMA (1 - 1/N_b in Eq. 38) + eps (float): small constant for numerical stability + """ + super().__init__() + self.register_buffer("beta2", torch.tensor(beta_init**2 if beta_init else 1.0)) + self.ema_factor = ema_factor + self.eps = eps + + def forward(self, pred, target): + diff = pred - target + abs_diff = diff.abs() + + # compute current batch MSE (Eq. 38) + mse_batch = torch.mean(diff**2) + + # update β² adaptively using Eq. (39) + with torch.no_grad(): + self.beta2 = self.ema_factor * self.beta2 + (1 - self.ema_factor) * torch.min( + self.beta2, mse_batch + ) + + beta = torch.sqrt(self.beta2 + self.eps) + + # Smooth L1 (Eq. 36) + loss = torch.where( + abs_diff < beta, 0.5 * (diff**2) / (beta + self.eps), abs_diff - 0.5 * beta + ) + return loss.mean() + + +class AdaptiveSmoothL1LossDDP(AdaptiveSmoothL1Loss): + def forward(self, pred, target): + diff = pred - target + abs_diff = diff.abs() + mse_batch = torch.mean(diff**2) + + # Synchronize β across all GPUs + if dist.is_initialized(): + mse_batch_all = mse_batch.clone() + dist.all_reduce(mse_batch_all, op=dist.ReduceOp.AVG) + mse_batch = mse_batch_all / dist.get_world_size() + + with torch.no_grad(): + self.beta2 = self.ema_factor * self.beta2 + (1 - self.ema_factor) * torch.min( + self.beta2, mse_batch + ) + + beta = torch.sqrt(self.beta2 + self.eps) + loss = torch.where( + abs_diff < beta, 0.5 * (diff**2) / (beta + self.eps), abs_diff - 0.5 * beta + ) + return loss.mean() From d73c49869d46e8a8c1033ba32885c58b4f78cfcc Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Thu, 18 Dec 2025 16:16:26 -0800 Subject: [PATCH 034/335] SIRT Reconstructions working. TomographyConventional looks a little clunky --- src/quantem/core/ml/constraints.py | 126 +++----- .../diffractive_imaging/object_models.py | 3 +- src/quantem/tomography/dataset_models.py | 52 ++- src/quantem/tomography/object_models.py | 267 ++++++++++++++++ .../radon/__init__.py | 0 .../radon/radon.py | 0 src/quantem/tomography/tomography.py | 145 ++++++++- src/quantem/tomography/tomography_base.py | 96 +++++- src/quantem/tomography/utils.py | 300 ++++++++++++++++++ 9 files changed, 898 insertions(+), 91 deletions(-) rename src/quantem/{tomography_old => tomography}/radon/__init__.py (100%) rename src/quantem/{tomography_old => tomography}/radon/radon.py (100%) diff --git a/src/quantem/core/ml/constraints.py b/src/quantem/core/ml/constraints.py index 755122c6..e6d995f2 100644 --- a/src/quantem/core/ml/constraints.py +++ b/src/quantem/core/ml/constraints.py @@ -1,99 +1,75 @@ from abc import ABC, abstractmethod +from dataclasses import dataclass from typing import Any import torch -from quantem.core import config + +@dataclass +class Constraints(ABC): + """ + Needs to be implemented in all object models that inherit from BaseConstraints. + """ + + pass class BaseConstraints(ABC): - """Base class for constraint management with common functionality.""" + """ + Base class for constraints. + """ - # Subclasses should define their own DEFAULT_CONSTRAINTS - DEFAULT_CONSTRAINTS: dict[str, Any] = {} + # Default constraints are the dataclasses themselves. + DEFAULT_CONSTRAINTS = Constraints() def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._soft_constraint_loss = {} - self._constraints = self.DEFAULT_CONSTRAINTS.copy() self._epoch_constraint_losses = {} + self.constraints = self.DEFAULT_CONSTRAINTS.copy() @property - def constraints(self) -> dict[str, Any]: + def constraints(self) -> Constraints: + """ + Constraints for the object model. + """ return self._constraints @constraints.setter - def constraints(self, c: dict[str, Any]): - allowed_keys = self.DEFAULT_CONSTRAINTS.keys() - constraint_type = self.__class__.__name__.lower().replace("constraints", "") - - for key, value in c.items(): - if key not in allowed_keys: - raise KeyError( - f"Invalid {constraint_type} constraint key '{key}', allowed keys are {list(allowed_keys)}" - ) - self._constraints[key] = value - - @property - def soft_constraint_loss(self) -> dict[str, torch.Tensor | float]: - return self._soft_constraint_loss + def constraints(self, constraints: Constraints | dict[str, Any]): + """ + Setter for constraints class, can be a Constraints instance or a dictionary. + """ + if isinstance(constraints, Constraints): + self._constraints = constraints + elif isinstance(constraints, dict): + for key, value in constraints.items(): + if key not in self._constraints.allowed_keys: + raise ValueError(f"Invalid constraint key: {key}") + setattr(self._constraints, key, value) + else: + raise ValueError(f"Invalid constraints type: {type(constraints)}") def add_constraint(self, key: str, value: Any): - allowed_keys = self.DEFAULT_CONSTRAINTS.keys() - constraint_type = self.__class__.__name__.lower().replace("constraints", "") - - if key not in allowed_keys: - raise KeyError( - f"Invalid {constraint_type} constraint key '{key}', allowed keys are {list(allowed_keys)}" - ) - self._constraints[key] = value + """ + Add a constraint to the constraints class. + Note the allowed keys should be implemented for each constraint subclass. + """ + if key not in self._constraints.allowed_keys: + raise ValueError(f"Invalid constraint key: {key}") + setattr(self._constraints, key, value) + + # --- Required methods tha tneeds to implemented in subclasses --- + @abstractmethod + def apply_hard_constraints(self, *args, **kwargs) -> torch.Tensor: + """ + Apply hard constraints to the object model. + """ + raise NotImplementedError @abstractmethod def apply_soft_constraints(self, *args, **kwargs) -> torch.Tensor: - """Apply soft constraints and return total constraint loss.""" - pass - - def _get_zero_loss_tensor(self) -> torch.Tensor: - """Helper method to create a zero loss tensor with proper device and dtype.""" - device = getattr(self, "device", "cpu") - return torch.tensor(0, device=device, dtype=getattr(torch, config.get("dtype_real"))) - - # --- helpers for consistent loss logging --- - def reset_soft_constraint_losses(self) -> None: - self._soft_constraint_loss = {} - - def add_soft_constraint_loss(self, name: str, value: torch.Tensor | float) -> None: - """Record a single soft-constraint loss for logging without holding the graph.""" - if isinstance(value, torch.Tensor): - val = value.detach() - if val.ndim != 0: - val = val.mean() - self._soft_constraint_loss[name] = val - else: - self._soft_constraint_loss[name] = float(value) - - def accumulate_constraint_losses( - self, batch_constraint_losses: dict[str, torch.Tensor | float] | None = None - ) -> None: - """Accumulate constraint losses across batches.""" - if batch_constraint_losses is None: - batch_constraint_losses = self.soft_constraint_loss - - for loss_name, loss_value in batch_constraint_losses.items(): - if isinstance(loss_value, torch.Tensor): - try: - v = loss_value.item() - except Exception: - print("loss value not singular: ", loss_value) # TODO remove - v = loss_value.detach().mean().item() - else: - v = float(loss_value) - self._epoch_constraint_losses[loss_name] = ( - self._epoch_constraint_losses.get(loss_name, 0.0) + v - ) - - def get_epoch_constraint_losses(self) -> dict[str, float]: - return getattr(self, "_epoch_constraint_losses", {}) # TODO clean this up - - def reset_epoch_constraint_losses(self) -> None: - self._epoch_constraint_losses = {} + """ + Apply soft constraints to the object model. + """ + raise NotImplementedError diff --git a/src/quantem/diffractive_imaging/object_models.py b/src/quantem/diffractive_imaging/object_models.py index f3a225d6..c1d533f5 100644 --- a/src/quantem/diffractive_imaging/object_models.py +++ b/src/quantem/diffractive_imaging/object_models.py @@ -7,6 +7,7 @@ import numpy as np import torch import torch.nn as nn +from torch.nn.parameter import Parameter from tqdm.auto import tqdm from quantem.core import config @@ -927,7 +928,7 @@ def get_optimization_parameters(self): """Get the parameters that should be optimized for this model.""" # Since model is registered as a submodule, we can use the parent's parameters() method # which will automatically include all submodule parameters - return list(self.parameters()) + return list[Parameter](self.parameters()) def reset(self): """Reset the object model to its initial or pre-trained state""" diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index b83d89d7..0530df0f 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -2,6 +2,7 @@ from dataclasses import dataclass from typing import Any, Literal +import numpy as np import torch import torch.nn as nn from numpy.typing import NDArray @@ -21,7 +22,9 @@ class DatasetValue: target: torch.Tensor tilt_angle: int | float pixel_loc: tuple[int, int] | None = None # Only for INRDataset - # pose: tuple[torch.nn.Parameter, torch.nn.Parameter, torch.nn.Parameter] | None = None # If there is pose optimization. + pose: tuple[torch.nn.Parameter, torch.nn.Parameter, torch.nn.Parameter] | None = ( + None # If there is pose optimization. + ) class TomographyDatasetBase(AutoSerialize, OptimizerMixin, nn.Module): @@ -45,9 +48,8 @@ def __init__( AutoSerialize.__init__(self) OptimizerMixin.__init__(self) nn.Module.__init__(self) - - if _token is not self._token: - raise RuntimeError("Use TomographyDatasetBase.from_* to instantiate this class.") + # if _token is not self._token: TODO: Idk why this isn't working. + # raise RuntimeError("Use TomographyPixDataset.from_* to instantiate this class.") if not ( tilt_stack.shape[0] < tilt_stack.shape[1] or tilt_stack.shape[0] < tilt_stack.shape[2] @@ -56,8 +58,19 @@ def __init__( "The number of tilt projections should be in the first dimension of the dataset." ) + self.volume_size = (int(max(tilt_stack.shape)), tilt_stack.shape[1], tilt_stack.shape[2]) + + # TODO: Maybe have the validation in here too. + max_val = np.quantile(tilt_stack, 0.95) + if type(tilt_stack) is not torch.Tensor: + tilt_stack = torch.from_numpy(tilt_stack) + if type(tilt_angles) is not torch.Tensor: + tilt_angles = torch.from_numpy(tilt_angles) + + # Tilt stack normalization + tilt_stack = tilt_stack / max_val + self.tilt_stack = tilt_stack - self.volume_size = (int(tilt_stack.shape.max()), tilt_stack.shape[1], tilt_stack.shape[2]) self.tilt_angles = tilt_angles self.learn_pose = learn_pose @@ -76,6 +89,11 @@ def forward( """ raise NotImplementedError("This method should be implemented in subclasses.") + # --- Helper Functions --- + def to(self, device: str): + self.tilt_stack = self.tilt_stack.to(device) + self.tilt_angles = self.tilt_angles.to(device) + class TomographyPixDataset( TomographyDatasetBase @@ -88,12 +106,16 @@ class TomographyPixDataset( In SIRT, it's only the tilt image. """ - pass - def __init__( self, + tilt_stack: Dataset3d | NDArray | torch.Tensor, + tilt_angles: NDArray | torch.Tensor, + learn_pose: bool = False, + _token: object | None = None, ): - pass + super().__init__( + tilt_stack=tilt_stack, tilt_angles=tilt_angles, learn_pose=learn_pose, _token=_token + ) def forward( self, @@ -103,12 +125,22 @@ def forward( Forward pass for pixel-based tomography. Returns the full tilt image for the given projection index, and the tilt angle. """ + return DatasetValue( target=self.tilt_stack[proj_idx], tilt_angle=self.tilt_angles[proj_idx], pixel_loc=None, ) + @classmethod + def from_data( + cls, + tilt_stack: Dataset3d | NDArray | torch.Tensor, + tilt_angles: NDArray | torch.Tensor, + learn_pose: bool = False, + ): + return cls(tilt_stack=tilt_stack, tilt_angles=tilt_angles, learn_pose=learn_pose) + class TomographyINRDataset(TomographyDatasetBase, Dataset): """ @@ -126,9 +158,9 @@ def __init__( val_ratio: float = 0.0, mode: Literal["train", "val"] = "train", seed: int = 42, - _token: object | None = None, + token: object | None = None, ): - super().__init__(tilt_stack, tilt_angles, learn_pose, _token) + super().__init__(tilt_stack, tilt_angles, learn_pose, token) self._total_pixels = self.volume_size[0] * self.volume_size[1] * self.volume_size[2] diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index e69de29b..d59f2b5d 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -0,0 +1,267 @@ +from abc import abstractmethod +from copy import deepcopy +from dataclasses import dataclass +from typing import Self + +import numpy as np +import torch +import torch.nn as nn + +from quantem.core.io.serialize import AutoSerialize +from quantem.core.ml.constraints import BaseConstraints, Constraints +from quantem.core.ml.optimizer_mixin import OptimizerMixin +from quantem.core.utils.rng import RNGMixin + + +@dataclass +class ConstraintsTomography(Constraints): + """ + Data class for all constraints that can be applied to the object model. + """ + + # Hard Constraints + positivity: bool = True + shrinkage: float = 0.0 + circular_mask: bool = False + fourier_filter: str | None = None # Hamming, etc... + + # Soft Constraints + tv_vol: float = 0.0 + + @property + def hard_constraint_keys(self) -> list[str]: + """ + List of hard constraint keys. + """ + return ["positivity", "shrinkage", "circular_mask", "fourier_filter"] + + @property + def soft_constraint_keys(self) -> list[str]: + """ + List of soft constraint keys. + """ + return ["tv_vol"] + + @property + def allowed_keys(self) -> list[str]: + """ + List of all allowed keys. + """ + return self.hard_constraint_keys + self.soft_constraint_keys + + def copy(self) -> Self: + """ + Copy the constraints. + """ + return deepcopy(self) + + def __str__(self) -> str: + return f"""Constraints: + Positivity: {self.positivity} + Shrinkage: {self.shrinkage} + Circular Mask: {self.circular_mask} + Fourier Filter: {self.fourier_filter} + TV Volume: {self.tv_vol} + """ + + +class ObjectBase(AutoSerialize, nn.Module, RNGMixin, OptimizerMixin): + DEFAULT_LRS = { + "object": 8e-6, + } + _token = object() + """ + Base class for all ObjectModels to inherit from. + """ + + def __init__( + self, + shape: tuple[int, int, int], + device: str = "cpu", + rng: np.random.Generator | int | None = None, + _token: object | None = None, + ): + if _token is not self._token: + raise RuntimeError("Use a factory method to instantiate this class.") + + self._shape = shape + + # Initialize dependencies + nn.Module.__init__(self) + RNGMixin.__init__(self, rng=rng, device=device) + OptimizerMixin.__init__(self) + + # Initialize a torch.zeros volume with the given shape + self._obj = torch.zeros(self._shape, device=device, dtype=torch.float32) + + # --- Properties --- + @property + def shape(self) -> tuple[int, int, int]: + """ + Shape of the object (x, y, z). + """ + return self._shape + + @shape.setter + def shape(self, shape: tuple[int, int, int]): + self._shape = shape + + @property + def obj(self) -> torch.Tensor: + """ + Returns the object, should be implemented in subclasses. + """ + raise NotImplementedError + + @abstractmethod + def forward(self, *args, **kwargs) -> torch.Tensor: + """ + Forward pass, should be implemented in subclasses. Note for any nn.Module this is + a required method. + """ + raise NotImplementedError + + @abstractmethod + def reset(self) -> None: + """ + Reset the object, should be implemented in subclasses. + """ + raise NotImplementedError + + # --- Helper Functions --- + def get_optimization_parameters(self) -> list[nn.Parameter]: + """ + Get the parameters that should be optimized for this model. + + TODO: I have no idea what this does. + """ + return list[nn.Parameter](self.parameters()) + + @abstractmethod # Each subclass should implement this. + def to(self, *args, **kwargs): + """ + Move the object to a device + """ + + raise NotImplementedError + + +class ObjectConstraints(ObjectBase, BaseConstraints): + DEFAULT_CONSTRAINTS = ConstraintsTomography() + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.constraints = self.DEFAULT_CONSTRAINTS.copy() + + def apply_hard_constraints( + self, + obj: torch.Tensor, + ) -> torch.Tensor: + """ + Apply hard constraints to the object model. + + Only hard constraint here is the positivity and shrinkage. TODO: Add the other hard constraints. + """ + obj2 = obj.clone() + if self.constraints.positivity: + obj2 = torch.clamp(obj2, min=0.0, max=None) + if self.constraints.shrinkage: + obj2 = torch.max(obj2 - self.constraints.shrinkage, torch.zeros_like(obj2)) + + # TODO: Need to implement the other hard constraints: Fourier Filter and Circular Mask. + return obj2 + + def apply_soft_constraints( + self, + obj: torch.Tensor, + ) -> torch.Tensor: + """ + Apply soft constraints to the object model. + + Only soft constraint here is the TV loss. + """ + + soft_loss = torch.tensor(0.0, device=obj.device, dtype=obj.dtype, requires_grad=True) + if self.constraints.tv_vol > 0: + tv_loss = self.get_tv_loss( + obj.unsqueeze(0).unsqueeze(0), factor=self.constraints.tv_vol + ) + soft_loss += tv_loss + return soft_loss + + @abstractmethod + def get_tv_loss(self, obj: torch.Tensor, tv_weight: float = 0.0) -> torch.Tensor: + """ + Get the TV loss for the object model. Must be implemented in each subclass. + """ + raise NotImplementedError + + +class ObjectPixelated(ObjectConstraints): + """ + Object model for pixelated objects. + + Supports: Conventional algorithms (SIRT, FBP), and AD-based reconstructions. + """ + + def __init__( + self, + shape: tuple[int, int, int], + device: str = "cpu", + rng: np.random.Generator | int | None = None, + ): + super().__init__( + shape=shape, + device=device, + rng=rng, + _token=self._token, + ) + + # --- Properties ---- + @property + def obj(self) -> torch.Tensor: + return self.apply_hard_constraints(self._obj) + + @obj.setter + def obj(self, obj: torch.Tensor): + self._obj = obj + + @property + def shape(self) -> tuple[int, int, int]: + return self._shape + + @shape.setter + def shape(self, shape: tuple[int, int, int]): + self._shape = shape + + @property + def soft_loss(self) -> torch.Tensor: + return self.apply_soft_constraints(self._obj) + + @property + def name(self) -> str: + return "ObjectPixelated" + + @property + def obj_type(self) -> str: + return "pixelated" + + # --- Forward method --- + def forward(self, dummy_input=None) -> torch.Tensor: + return self.obj + + # --- Defining the TV loss --- + def get_tv_loss(self, obj: torch.Tensor, tv_weight: float = 1e-3) -> torch.Tensor: + tv_d = torch.pow(obj[:, :, 1:, :, :] - obj[:, :, :-1, :, :], 2).sum() + tv_h = torch.pow(obj[:, :, :, 1:, :] - obj[:, :, :, :-1, :], 2).sum() + tv_w = torch.pow(obj[:, :, :, :, 1:] - obj[:, :, :, :, :-1], 2).sum() + tv_loss = tv_d + tv_h + tv_w + + return tv_loss * tv_weight / (torch.prod(torch.tensor(obj.shape))) + + # --- Helper Functions --- + def to(self, device: str): + self._obj = self._obj.to(device) + + +ObjectModelType = ObjectPixelated diff --git a/src/quantem/tomography_old/radon/__init__.py b/src/quantem/tomography/radon/__init__.py similarity index 100% rename from src/quantem/tomography_old/radon/__init__.py rename to src/quantem/tomography/radon/__init__.py diff --git a/src/quantem/tomography_old/radon/radon.py b/src/quantem/tomography/radon/radon.py similarity index 100% rename from src/quantem/tomography_old/radon/radon.py rename to src/quantem/tomography/radon/radon.py diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 79525364..609cbe5c 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -1,16 +1,155 @@ +from typing import Literal, Self + +import numpy as np +import torch +from tqdm.auto import tqdm + +from quantem.tomography.dataset_models import DatasetModelType +from quantem.tomography.logger_tomography import LoggerTomography +from quantem.tomography.object_models import ObjectModelType +from quantem.tomography.radon.radon import iradon_torch, radon_torch +from quantem.tomography.tomography_base import TomographyBase +from quantem.tomography.utils import torch_phase_cross_correlation + + class Tomography: """ Class for handling all ML tomography reconstruction methods. Automatic handling between AD and INR-based tomography. """ - pass + @classmethod + def from_models( + cls, + dset: DatasetModelType, + obj_model: ObjectModelType, + logger: LoggerTomography | None = None, + device: str = "cuda", + rng: np.random.Generator | int | None = None, + ) -> Self: + return cls( + dset=dset, + obj_model=obj_model, + logger=logger, + device=device, + ) -class TomographyConventional: +class TomographyConventional(TomographyBase): """ Class for handling all conventional tomography reconstruction methods. Will also handle choosing the appropriate dataset model to use. """ - pass + @classmethod + def from_models( + cls, + dset: DatasetModelType, + obj_model: ObjectModelType, + logger: LoggerTomography | None = None, + device: str = "cuda", + rng: np.random.Generator | int | None = None, + ) -> Self: + return cls( + dset=dset, + obj_model=obj_model, + logger=logger, + device=device, + rng=rng, + ) + + def reconstruct( + self, + num_iter: int = 10, + mode: Literal["sirt", "fbp"] = "sirt", + reset: bool = False, + inline_alignment: bool = False, + ): + pbar = tqdm(range(num_iter), desc=f"{mode} Reconstruction") + if mode == "sirt" or mode == "fbp": + proj_forward = torch.zeros_like(self.dset.tilt_stack).permute(2, 0, 1) + else: + proj_forward = torch.zeros_like(self.dset.tilt_stack) + + for iter in pbar: + proj_forward, loss = self._reconstruction_epoch( + inline_alignment=inline_alignment, + mode=mode, + proj_forward=proj_forward, + ) + + pbar.set_description(f"{mode} Reconstruction | Loss: {loss.item():.4f}") + + self._epoch_losses.append(loss.item()) + + if mode == "fbp": + break + + # --- Conventional reconstruction method --- + + def _reconstruction_epoch( + self, + inline_alignment: bool, + mode: Literal["sirt", "fbp"], + proj_forward: torch.Tensor, + ): + loss = 0 + + if inline_alignment: + for ind in range(len(self.dset.tilt_angles)): + im_proj = proj_forward[:, ind, :] + im_meas = self.dset.forward(ind).target + shift = torch_phase_cross_correlation(im_proj, im_meas) + if torch.linalg.norm(shift) <= 32: + shifted = torch.fft.ifft2( + torch.fft.fft2(im_meas) + * torch.exp( + -2j + * np.pi + * ( + shift[0] + * torch.fft.fftfreq( + im_meas.shape[0], device=im_meas.device + ).unsqueeze(1) + + shift[1] + * torch.fft.fftfreq(im_meas.shape[1], device=im_meas.device) + ) + ) + ).real + + proj_forward[:, ind, :] = shifted + + if mode == "sirt" or mode == "fbp": + proj_forward = radon_torch( + self.obj_model.obj, + theta=self.dset.tilt_angles, + device=self.device, + ) + + error = self.dset.tilt_stack.permute(2, 0, 1) - proj_forward + + correction = iradon_torch( + error, + theta=self.dset.tilt_angles, + device=self.device, + filter_name="ramp", + circle=True, + ) + + normalization = iradon_torch( + torch.ones_like(error), + theta=self.dset.tilt_angles, + device=self.device, + filter_name=None, + circle=True, + ) + + normalization[normalization == 0] = 1e-6 + + correction /= normalization + + self.obj_model.obj += correction + + loss = torch.mean(torch.abs(error)) + + return proj_forward, loss diff --git a/src/quantem/tomography/tomography_base.py b/src/quantem/tomography/tomography_base.py index cb70c15c..fec0852b 100644 --- a/src/quantem/tomography/tomography_base.py +++ b/src/quantem/tomography/tomography_base.py @@ -1,9 +1,11 @@ import numpy as np +from numpy.typing import NDArray from quantem.core.io.serialize import AutoSerialize from quantem.core.utils.rng import RNGMixin -from quantem.tomography.dataset_models import DatasetModelType +from quantem.tomography.dataset_models import DatasetModelType, TomographyDatasetBase from quantem.tomography.logger_tomography import LoggerTomography +from quantem.tomography.object_models import ConstraintsTomography, ObjectModelType class TomographyBase(AutoSerialize, RNGMixin): @@ -18,9 +20,99 @@ class TomographyBase(AutoSerialize, RNGMixin): def __init__( self, dset: DatasetModelType, + obj_model: ObjectModelType, logger: LoggerTomography | None = None, device: str = "cuda", rng: np.random.Generator | int | None = None, _token: object | None = None, ): - pass + # if _token is not self._token: # TODO: Idk why this isn't working. + # raise RuntimeError("Use Dataset.from_* to instantiate this class.") + + super().__init__() + + self.dset = dset + self.obj_model = obj_model + self.rng = rng + self.device = device + self.logger = logger + + # Loss + self._epoch_losses: list[float] = [] + + # --- Properties --- + @property + def dset(self) -> DatasetModelType: + return self._dset + + @dset.setter + def dset(self, new_dset: DatasetModelType): + if not isinstance(new_dset, TomographyDatasetBase) and "TomographyDataset" not in str( + type(new_dset) + ): + raise TypeError(f"dset should be a TomographyDataset, got {type(new_dset)}") + self._dset = new_dset + + @property + def obj_type(self) -> str: + return self.obj_model.obj_type + + @property + def obj_model(self) -> ObjectModelType: + return self._obj_model + + @obj_model.setter + def obj_model(self, obj_model: ObjectModelType): + if not isinstance(obj_model, ObjectModelType): + raise TypeError(f"obj_model should be a ObjectModelType, got {type(obj_model)}") + self._obj_model = obj_model + + @property + def constraints(self) -> ConstraintsTomography: + return self.obj_model.constraints + + @constraints.setter + def constraints(self, constraints: ConstraintsTomography): + if not isinstance(constraints, ConstraintsTomography): + raise TypeError( + f"constraints should be a ConstraintsTomography, got {type(constraints)}" + ) + self.obj_model.constraints = constraints + + @property + def logger(self) -> LoggerTomography | None: + return self._logger + + @logger.setter + def logger(self, logger: LoggerTomography | None): + if not isinstance(logger, LoggerTomography) and logger is not None: + raise TypeError(f"logger should be a LoggerTomography, got {type(logger)}") + self._logger = logger + + @property + def device(self) -> str: + return self._device + + @device.setter + def device(self, device: str): + if not isinstance(device, str): + raise TypeError(f"device should be a str, got {type(device)}") + self._device = device + self.to(device) + + @property + def epoch_losses(self) -> NDArray: + """ + Returns the fidelity loss for each epoch ran. + """ + return np.array(self._epoch_losses) + + @property + def num_epochs(self) -> int: + return len(self._epoch_losses) + + # --- Helper Functions --- + + def to(self, device: str): + self.obj_model.to(device) + self.dset.to(device) diff --git a/src/quantem/tomography/utils.py b/src/quantem/tomography/utils.py index e69de29b..82c1c9d5 100644 --- a/src/quantem/tomography/utils.py +++ b/src/quantem/tomography/utils.py @@ -0,0 +1,300 @@ +import numpy as np +import torch +import torch.nn.functional as F +from scipy.ndimage import center_of_mass, gaussian_filter, shift +from scipy.stats import norm +from tqdm.auto import tqdm + +from quantem.core.utils.imaging_utils import cross_correlation_shift + +# --- Projection Operator Utils --- + + +def rot_ZXZ(mags, z1, x, z3, device, mode="bilinear"): + if not isinstance(x, torch.Tensor) or not isinstance(z1, torch.Tensor): + z1 = torch.tensor(z1, dtype=torch.float32, device=device) + x = torch.tensor(x, dtype=torch.float32, device=device) + z3 = torch.tensor(z3, dtype=torch.float32, device=device) + curr_mags = mags + + curr_mags = differentiable_rotz_vectorized(curr_mags, z1, mode) + curr_mags = differentiable_rotx_vectorized(curr_mags, x, mode) + + curr_mags = differentiable_rotz_vectorized(curr_mags, z3, mode) + + return curr_mags + + +def differentiable_rotz_vectorized(mags, theta, mode="bilinear"): + _, dimz, dimy, dimx = mags.shape + + if theta.dim() == 0: + theta = theta.unsqueeze(0) + + theta_rad = torch.deg2rad(theta) + + cos_t, sin_t = torch.cos(theta_rad), torch.sin(theta_rad) + affine_matrix = torch.stack( + [cos_t, -sin_t, torch.zeros_like(theta), sin_t, cos_t, torch.zeros_like(theta)], dim=-1 + ).view(-1, 2, 3) + + mags = mags.permute(1, 0, 2, 3) + + def transform_slice(mag_slice): + grid = F.affine_grid(affine_matrix, mag_slice.unsqueeze(0).shape, align_corners=False) + return F.grid_sample(mag_slice.unsqueeze(0), grid, mode=mode, align_corners=False).squeeze( + 0 + ) + + rotated_mags = torch.vmap(transform_slice)(mags) + return rotated_mags.permute(1, 0, 2, 3) + + +def differentiable_rotx_vectorized(mags, theta, mode="bilinear"): + _, dimz, dimy, dimx = mags.shape + + if theta.dim() == 0: + theta = theta.unsqueeze(0) + + theta_rad = torch.deg2rad(theta) + + cos_t, sin_t = torch.cos(theta_rad), torch.sin(theta_rad) + affine_matrix = torch.stack( + [cos_t, -sin_t, torch.zeros_like(theta), sin_t, cos_t, torch.zeros_like(theta)], dim=-1 + ).view(-1, 2, 3) + + mags = mags.permute(3, 0, 1, 2) + + def transform_slice(mag_slice): + grid = F.affine_grid(affine_matrix, mag_slice.unsqueeze(0).shape, align_corners=False) + return F.grid_sample(mag_slice.unsqueeze(0), grid, mode=mode, align_corners=False).squeeze( + 0 + ) + + rotated_mags = torch.vmap(transform_slice)(mags) + return rotated_mags.permute(1, 2, 3, 0) + + +def differentiable_shift_2d(image, shift_x, shift_y, sampling_rate): + """ + Shifts a 2D image using grid_sample in a differentiable manner. + + Args: + image: Tensor of shape [H, W] + shift_x: Scalar tensor (dx) for shift in x-direction (in physical units) + shift_y: Scalar tensor (dy) for shift in y-direction (in physical units) + sampling_rate: Scalar value (physical units per pixel) to correctly normalize shifts + + Returns: + Shifted image of shape [H, W] + """ + H, W = image.shape + + # Convert physical shift to pixel shift + shift_x_pixel = shift_x + shift_y_pixel = shift_y + + # Normalize shift for grid_sample (assuming align_corners=True) + normalized_shift_x = shift_x_pixel * 2 / (W - 1) + normalized_shift_y = shift_y_pixel * 2 / (H - 1) + + # Create normalized grid + grid_y, grid_x = torch.meshgrid( + torch.linspace(-1, 1, H, device=image.device), + torch.linspace(-1, 1, W, device=image.device), + indexing="ij", + ) + + grid = torch.stack((grid_x, grid_y), dim=-1).unsqueeze(0) # [1, H, W, 2] + + # Apply shift (ensure it's differentiable) + grid[:, :, :, 0] -= normalized_shift_x + grid[:, :, :, 1] -= normalized_shift_y + + # Add batch and channel dimensions + image = image.unsqueeze(0).unsqueeze(0) # [1, 1, H, W] + + # Sample using grid_sample (fully differentiable) + shifted_image = F.grid_sample( + image, grid, mode="bicubic", padding_mode="zeros", align_corners=True + ) + + return shifted_image.squeeze(0).squeeze(0) # Back to [H, W] + + +# --- TV loss --- + + +def get_TV_loss(tensor, factor=1e-3): + tv_d = torch.pow(tensor[:, :, 1:, :, :] - tensor[:, :, :-1, :, :], 2).sum() + tv_h = torch.pow(tensor[:, :, :, 1:, :] - tensor[:, :, :, :-1, :], 2).sum() + tv_w = torch.pow(tensor[:, :, :, :, 1:] - tensor[:, :, :, :, :-1], 2).sum() + tv_loss = tv_d + tv_h + tv_w + + return tv_loss * factor / (torch.prod(torch.tensor(tensor.shape))) + + +# --- Gaussian filters --- + + +def gaussian_kernel_1d(sigma: float, num_sigmas: float = 3.0) -> torch.Tensor: + radius = np.ceil(num_sigmas * sigma) + support = torch.arange(-radius, radius + 1, dtype=torch.float) + kernel = torch.distributions.Normal(loc=0, scale=sigma).log_prob(support).exp_() + # Ensure kernel weights sum to 1, so that image brightness is not altered + return kernel.mul_(1 / kernel.sum()) + + +def gaussian_filter_2d( + img: torch.Tensor, sigma: float, kernel_1d: torch.Tensor +) -> torch.Tensor: # Add kernel_1d as an argument + # kernel_1d = gaussian_kernel_1d(sigma) # Create 1D Gaussian kernel - Moved outside function + padding = len(kernel_1d) // 2 # Ensure that image size does not change + img = img.unsqueeze(0).unsqueeze_(0) # Make copy, make 4D for ``conv2d()`` + # Convolve along columns and rows + img = torch.nn.functional.conv2d(img, weight=kernel_1d.view(1, 1, -1, 1), padding=(padding, 0)) + img = torch.nn.functional.conv2d(img, weight=kernel_1d.view(1, 1, 1, -1), padding=(0, padding)) + return img.squeeze_(0).squeeze_(0) # Make 2D again + + +def gaussian_filter_2d_stack(stack: torch.Tensor, kernel_1d: torch.Tensor) -> torch.Tensor: + """ + Apply 2D Gaussian blur to each slice stack[:, i, :] in a vectorized way. + + Args: + stack (torch.Tensor): Tensor of shape (H, N, W) where N is num_sinograms + kernel_1d (torch.Tensor): 1D Gaussian kernel + + Returns: + torch.Tensor: Blurred stack of same shape (H, N, W) + """ + H, N, W = stack.shape + padding = len(kernel_1d) // 2 + + # Reshape to (N, 1, H, W) for conv2d + stack_reshaped = stack.permute(1, 0, 2).unsqueeze(1) # (N, 1, H, W) + + # Apply separable conv2d: vertical then horizontal + out = torch.nn.functional.conv2d( + stack_reshaped, kernel_1d.view(1, 1, -1, 1), padding=(padding, 0) + ) + out = torch.nn.functional.conv2d(out, kernel_1d.view(1, 1, 1, -1), padding=(0, padding)) + + # Restore shape to (H, N, W) + return out.squeeze(1).permute(1, 0, 2) + + +# Circular mask + + +def torch_phase_cross_correlation(im1, im2): + f1 = torch.fft.fft2(im1) + f2 = torch.fft.fft2(im2) + cc = torch.fft.ifft2(f1 * torch.conj(f2)) + cc_abs = torch.abs(cc) + + max_idx = torch.argmax(cc_abs) + shifts = torch.tensor(np.unravel_index(max_idx.item(), im1.shape), device=im1.device).float() + + for i, dim in enumerate(im1.shape): + if shifts[i] > dim // 2: + shifts[i] -= dim + + # return shifts.flip(0) # (dx, dy) + return shifts + + +# --- Tilt Series Processing Utility Functions --- + + +def fourier_cropping(img, crop_size): + """ + Crop the img in Fourier space to the specified size. + """ + center = np.array(img.shape) // 2 + + fft_img = np.fft.fftshift(np.fft.fft2(img)) + + cropped_fft = fft_img[ + center[0] - crop_size[0] // 2 : center[0] + crop_size[0] // 2, + center[1] - crop_size[1] // 2 : center[1] + crop_size[1] // 2, + ] + cropped_img = np.fft.ifft2(np.fft.ifftshift(cropped_fft)).real + return cropped_img + + +def estimate_background( + img, + num_iterations=10, + cutoff=3, + smoothing_sigma=1.0, +): + """ + Estimate the background of the image using a Gaussian filter. + """ + if smoothing_sigma > 0: + img = gaussian_filter(img, sigma=smoothing_sigma) + pixel_vals = img.ravel() + + for i in range(num_iterations): + mu, std = norm.fit(pixel_vals) + + # Set cutoff threshold (e.g., 3 standard deviations) + lower = mu - cutoff * std + upper = mu + cutoff * std + + # Mask pixel values within ±3σ + pixel_vals = pixel_vals[(pixel_vals >= lower) & (pixel_vals <= upper)] + + return mu + + +def cross_correlation_align_stack(ref_img, stack, print_pred=False): + """ + Aligns a stack of images to a reference image using cross-correlation. + + This function assumes the stack does not contain the reference image itself. + + Stack shape should be (N, H, W) where N is the number of images. + """ + + new_images = [] + pred_shifts = [] + + prev_img = ref_img + for img in tqdm(stack): + shift_pred = cross_correlation_shift(prev_img, img) + if print_pred: + print(f"Shift prediction: {shift_pred}") + shifted_image = shift(img, shift=shift_pred, mode="constant", cval=0.0) + + pred_shifts.append(shift_pred) + new_images.append(shifted_image) + + prev_img = shifted_image + + return new_images, pred_shifts + + +def centering_com_alignment(image_stack): + """ + Aligns the image stack to the center of mass of the whole image_stack to the + image center. This is useful for aligning the tilt series to the invariant line. + """ + + aligned_stack = np.zeros_like(image_stack) + h, w = image_stack.shape[1:] + image_center = np.array([h // 2, w // 2]) + + com_reference = np.array(center_of_mass(image_stack.mean(axis=0))) + + for i, img in enumerate(image_stack): + com_img = np.array(center_of_mass(img)) + shift_vec = com_reference - com_img + aligned_stack[i] = shift(img, shift=shift_vec, mode="constant", cval=0.0) + + final_shift = image_center - com_reference + for i in range(aligned_stack.shape[0]): + aligned_stack[i] = shift(aligned_stack[i], shift=final_shift, mode="constant", cval=0.0) + + return aligned_stack From bc98f4b1b12a65ae68d69076d4ef52fe5164f8f4 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Thu, 18 Dec 2025 16:28:34 -0800 Subject: [PATCH 035/335] Implemented tomography_opt.py --- src/quantem/tomography/tomography.py | 3 +- src/quantem/tomography/tomography_opt.py | 146 +++++++++++++++++++++++ 2 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 src/quantem/tomography/tomography_opt.py diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 609cbe5c..32af65a0 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -9,10 +9,11 @@ from quantem.tomography.object_models import ObjectModelType from quantem.tomography.radon.radon import iradon_torch, radon_torch from quantem.tomography.tomography_base import TomographyBase +from quantem.tomography.tomography_opt import TomographyOpt from quantem.tomography.utils import torch_phase_cross_correlation -class Tomography: +class Tomography(TomographyBase, TomographyOpt): """ Class for handling all ML tomography reconstruction methods. Automatic handling between AD and INR-based tomography. diff --git a/src/quantem/tomography/tomography_opt.py b/src/quantem/tomography/tomography_opt.py new file mode 100644 index 00000000..77b19cfc --- /dev/null +++ b/src/quantem/tomography/tomography_opt.py @@ -0,0 +1,146 @@ +import torch + +from quantem.tomography.tomography_base import TomographyBase + + +class TomographyOpt(TomographyBase): + """ + Class for handling all the optimizers and schedulers for the tomography reconstruction. + """ + + OPTIMIZABLE_VALS = ["object", "pose"] + DEFAULT_OPTIMIZER_TYPE = "adam" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def _get_default_lr(self, key: str) -> float: + """Get default learning rate for a given optimization key.""" + if key == "object": + return self.obj_model.DEFAULT_LRS.get("object", 1e-5) + elif key == "pose": + return self.dset.DEFAULT_LRS.get("pose", 5e-2) + else: + raise ValueError(f"Unknown optimization key: {key}") + + @property + def optimizer_params(self) -> dict[str, dict]: + return { + key: params + for key, params in [ + ("object", self.obj_model.optimizer_params), + ("pose", self.dset.optimizer_params), + ] + if params + } + + @optimizer_params.setter + def optimizer_params(self, d: dict): + """Set the optimizer parameters.""" + self._optimizer_params = d.copy() if d else {} + + for key in self.OPTIMIZABLE_VALS: + if key not in d: + d[key] = {"type": "none"} + + for k, v in d.items(): + if "type" not in v.keys(): + v["type"] = self.DEFAULT_OPTIMIZER_TYPE + if "lr" not in v.keys(): + v["lr"] = self._get_default_lr(k) + if k == "object": + self.obj_model.optimizer_params = v + elif k == "pose": + self.dset.optimizer_params = v + else: + raise ValueError(f"Unknown optimization key: {k}") + + @property + def optimizers(self) -> dict[str, torch.optim.Optimizer]: + return { + "object": self.obj_model.optimizer, + "pose": self.dset.optimizer, + } + + def set_optimizers(self): + for key, params in self.optimizer_params.items(): + if key == "object": + self.obj_model.set_optimizer(params) + elif key == "pose": + self.dset.set_optimizer(params) + else: + raise ValueError(f"Unknown optimization key: {key}") + + def remove_optimizer(self, key: str): + if key == "object": + self.obj_model.remove_optimizer() + elif key == "pose": + self.dset.remove_optimizer() + else: + raise ValueError(f"Unknown optimization key: {key}") + + @property + def scheduler_params(self) -> dict[str, dict]: + """Returns the parameters used to set the schedulers.""" + return { + "object": self.obj_model.scheduler_params, + "pose": self.dset.scheduler_params, + } + + @scheduler_params.setter + def scheduler_params(self, d: dict): + """Set the scheduler parameters.""" + self._scheduler_params = d.copy() if d else {} + + for key in self.OPTIMIZABLE_VALS: + if key not in d: + d[key] = {} + + for k, v in d.items(): + if k == "object": + self.obj_model.scheduler_params = v + elif k == "pose": + self.dset.scheduler_params = v + else: + raise ValueError(f"Unknown optimization key: {k}") + + @property + def schedulers(self) -> dict[str, torch.optim.lr_scheduler._LRScheduler]: + return { + "object": self.obj_model.scheduler, + "pose": self.dset.scheduler, + } + + def set_schedulers(self, params: dict[str, dict], num_iter: int | None = None): + for key, scheduler_params in params.items(): + if key == "object": + self.obj_model.set_scheduler(scheduler_params, num_iter) + elif key == "pose": + self.dset.set_scheduler(scheduler_params, num_iter) + + def step_optimizers(self): + for key in self.optimizer_params.keys(): + if key == "object" and self.obj_model.has_optimizer(): + self.obj_model.step_optimizer() + elif key == "pose" and self.dset.has_optimizer(): + self.dset.step_optimizer() + else: + raise ValueError(f"Unknown optimization key: {key}") + + def zero_grad_all(self): + for key in self.optimizer_params.keys(): + if key == "object" and self.obj_model.has_optimizer(): + self.obj_model.zero_optimizer_grad() + elif key == "pose" and self.dset.has_optimizer(): + self.dset.zero_optimizer_grad() + else: + raise ValueError(f"Unknown optimization key: {key}") + + def step_schedulers(self, loss: float | None = None): + for key in self.scheduler_params.keys(): + if key == "object" and self.obj_model.scheduler is not None: + self.obj_model.step_scheduler(loss) + elif key == "pose" and self.dset.scheduler is not None: + self.dset.step_scheduler(loss) + else: + raise ValueError(f"Unknown optimization key: {key}") From 0fa3740905431c7ba2a640c0638e9b5a6dc7eeff Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Thu, 18 Dec 2025 16:42:50 -0800 Subject: [PATCH 036/335] Starting to write the ML methods for Tomography; Need to figure out how to nicely do DDP i.e, implementations in both Dataset and Object. --- src/quantem/tomography/object_models.py | 6 +++++- src/quantem/tomography/tomography.py | 19 ++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index d59f2b5d..69263a2d 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -264,4 +264,8 @@ def to(self, device: str): self._obj = self._obj.to(device) -ObjectModelType = ObjectPixelated +class ObjectINR(ObjectConstraints): + pass + + +ObjectModelType = ObjectPixelated | ObjectINR diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 32af65a0..ebc36738 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -1,4 +1,4 @@ -from typing import Literal, Self +from typing import Literal, Optional, Self, Tuple import numpy as np import torch @@ -35,6 +35,23 @@ def from_models( device=device, ) + def reconstruct( + self, + obj_model: ObjectModelType, + dset: DatasetModelType, + num_iter: int = 10, + reset: bool = False, + optimizer_params: dict | None = None, + scheduler_params: dict | None = None, + constraints=None, # TODO: What to pass into the constraints? + loss_func: Tuple[str, Optional[float]] = ("smooth_l1", 0.07), + ): + raise NotImplementedError + """ + This function should be able to handle both AD and INR-based tomography reconstruction methods. + I.e, auto-detection through the obj model type, while both share the same pose optimization. + """ + class TomographyConventional(TomographyBase): """ From f65d5d485472ea9976af81e17fbce1c39afbf2db Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 8 Jan 2026 10:26:35 -0800 Subject: [PATCH 037/335] add pull request template --- .../pull_request_template.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE/pull_request_template.md diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md new file mode 100644 index 00000000..1099d862 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -0,0 +1,15 @@ +### What problem does this PR address? + + + +### What should the reviewer(s) do? + + + + From 9e83cbc9ee5125820a3a0d8c88fd3622fe142e46 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 10 Jan 2026 00:30:08 -0800 Subject: [PATCH 038/335] docs: explicit add test for functional/algorithmic changes per feedback --- .github/PULL_REQUEST_TEMPLATE/pull_request_template.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md index 1099d862..e3c0d0d0 100644 --- a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -9,6 +9,7 @@ Should be instantiated in the object? Where does `HSIREN` get instantiated? - model = model.to(self.device) - - if self.world_size > 1: - model = torch.nn.parallel.DistributedDataParallel( - model, - device_ids=[self.local_rank], - output_device=self.local_rank, - find_unused_parameters=False, - broadcast_buffers=True, - bucket_cap_mb=100, - gradient_as_bucket_view=True, - ) - - if self.global_rank == 0: - print("Model wrapped with DDP") - - if self.world_size > 1: - if self.global_rank == 0: - print("Model built, distributed, and compiled successfully") - - else: - print("Model built, compiled successfully") - - return model - - # Setup Tomo DataLoader - def setup_dataloader( - self, - tomo_dataset: TomographyINRDataset, - batch_size: int, - num_workers: int = 0, - val_fraction: float = 0.0, - volume_size: int = 500, # Assuming N^3 volume size - ): - pin_mem = self.device.type == "cuda" - persist = num_workers > 0 - - # Split dataset if validation fraction > 0 - if val_fraction > 0.0: - # TODO: Temporary for when only doing validation, current TomographyDataset doesn't work correctly - train_dataset = TomographyINRDataset( - tomo_dataset.tilt_series.detach().clone(), - tomo_dataset.tilt_angles.detach().clone(), - volume_size, # TODO: TEMPORARY - val_ratio=val_fraction, - mode="train", - seed=42, - ) - val_dataset = TomographyINRDataset( - tomo_dataset.tilt_series.detach().clone(), - tomo_dataset.tilt_angles.detach().clone(), - volume_size, # TODO: TEMPORARY - val_ratio=val_fraction, - mode="val", - seed=42, - ) - else: - train_dataset, val_dataset = tomo_dataset, None - - # Samplers for distributed training - if self.world_size > 1: - train_sampler = DistributedSampler( - train_dataset, - num_replicas=self.world_size, - rank=self.global_rank, - shuffle=True, - ) - if val_dataset: - val_sampler = DistributedSampler( - val_dataset, - num_replicas=self.world_size, - rank=self.global_rank, - shuffle=False, - ) - shuffle = False - else: - train_sampler = None - val_sampler = None - shuffle = True - - # Main dataloader - self.dataloader = DataLoader( - train_dataset, - batch_size=batch_size, - num_workers=num_workers, - sampler=train_sampler, - shuffle=shuffle, - pin_memory=pin_mem, - drop_last=True, - persistent_workers=persist, - ) - - # Validation dataloader if applicable - if val_dataset: - self.val_dataloader = DataLoader( - val_dataset, - batch_size=batch_size * 4, - num_workers=num_workers, - sampler=val_sampler, - shuffle=False, - pin_memory=pin_mem, - drop_last=False, - persistent_workers=persist, - ) - self.val_sampler = val_sampler - - self.sampler = train_sampler - - if self.global_rank == 0: - print("Dataloader setup complete:") - print(f" Total projections: {len(tomo_dataset.tilt_angles)}") - if val_fraction > 0.0: - print(f" Total projections (val): {len(val_dataset)}") - print(f" Grid size: {tomo_dataset.dims[1]}{tomo_dataset.dims[2]}") - print(f" Total pixels: {tomo_dataset.num_pixels:,}") - if val_fraction > 0.0: - print(f" Total pixels (val): {len(val_dataset):,}") - print(f" Total pixels (train): {len(train_dataset):,}") - print(f" Local batch size (train): {batch_size}") - print(f" Global batch size: {batch_size * self.world_size}") - print(f" Train batches per GPU per epoch: {len(self.dataloader)}") - - # Setup pretraining dataloader - - # def setup_pretraining_dataloader( - # self, - # volume_dataset: PretrainVolumeDataset, - # batch_size: int, - # ): - # # TODO: temp - - # num_workers = 0 - # if self.world_size > 1: - # sampler = DistributedSampler( - # volume_dataset, - # num_replicas=self.world_size, - # rank=self.global_rank, - # shuffle=True, - # drop_last=True, - # ) - # shuffle = False - # else: - # sampler = None - # shuffle = True - - # self.pretraining_dataloader = DataLoader( - # volume_dataset, - # batch_size=batch_size, - # sampler=sampler, - # shuffle=shuffle, - # pin_memory=self.device.type == "cuda", - # drop_last=True, - # persistent_workers=num_workers > 0, - # ) - - # self.pretraining_sampler = sampler - - # if self.global_rank == 0: - # print("Pretraining dataloader setup complete:") - # print(f" Total samples: {len(volume_dataset)}") - # print(f" Grid size: {volume_dataset.N**3}") - # print(f" Local batch size: {batch_size}") - # print(f" Global batch size: {batch_size * self.world_size}") - # print(f" Pretraining batches per GPU per epoch: {len(self.pretraining_dataloader)}") - - def get_scaled_lr(self, base_lr, scaling_rule="sqrt"): - if scaling_rule == "sqrt": - return base_lr * np.sqrt(self.world_size) - elif scaling_rule == "linear": - return base_lr * self.world_size - else: - raise ValueError(f"Invalid scaling rule: {scaling_rule}") - - def scale_lr( - self, - optimizer_params: dict, - ): - new_optimizer_params = {} - for key, value in optimizer_params.items(): - if "original_lr" in value: - new_optimizer_params[key] = { - "type": value["type"], - "lr": self.get_scaled_lr(value["original_lr"]), - "original_lr": value["original_lr"], - } - else: - new_optimizer_params[key] = { - "type": value["type"], - "lr": self.get_scaled_lr(value["lr"]), - "original_lr": value["lr"], - } - - return new_optimizer_params - - -# TODO: Temporary Adaptive L1 Smooth Loss - - -class AdaptiveSmoothL1Loss(nn.Module): - def __init__(self, beta_init=None, ema_factor=0.99, eps=1e-8): - """ - Adaptive smooth L1 loss with EMA-based beta adaptation. - - Args: - beta_init (float): optional initial β value; if None, starts as 1.0 - ema_factor (float): smoothing factor for EMA (1 - 1/N_b in Eq. 38) - eps (float): small constant for numerical stability - """ - super().__init__() - self.register_buffer("beta2", torch.tensor(beta_init**2 if beta_init else 1.0)) - self.ema_factor = ema_factor - self.eps = eps - - def forward(self, pred, target): - diff = pred - target - abs_diff = diff.abs() - - # compute current batch MSE (Eq. 38) - mse_batch = torch.mean(diff**2) - - # update β² adaptively using Eq. (39) - with torch.no_grad(): - self.beta2 = self.ema_factor * self.beta2 + (1 - self.ema_factor) * torch.min( - self.beta2, mse_batch - ) - - beta = torch.sqrt(self.beta2 + self.eps) - - # Smooth L1 (Eq. 36) - loss = torch.where( - abs_diff < beta, 0.5 * (diff**2) / (beta + self.eps), abs_diff - 0.5 * beta - ) - return loss.mean() - - -class AdaptiveSmoothL1LossDDP(AdaptiveSmoothL1Loss): - def forward(self, pred, target): - diff = pred - target - abs_diff = diff.abs() - mse_batch = torch.mean(diff**2) - - # Synchronize β across all GPUs - if dist.is_initialized(): - mse_batch_all = mse_batch.clone() - dist.all_reduce(mse_batch_all, op=dist.ReduceOp.AVG) - mse_batch = mse_batch_all / dist.get_world_size() - - with torch.no_grad(): - self.beta2 = self.ema_factor * self.beta2 + (1 - self.ema_factor) * torch.min( - self.beta2, mse_batch - ) - - beta = torch.sqrt(self.beta2 + self.eps) - loss = torch.where( - abs_diff < beta, 0.5 * (diff**2) / (beta + self.eps), abs_diff - 0.5 * beta - ) - return loss.mean() From 70ffb317b8ad48142a46aea45dfb77ebb0d3b341 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 20 Jan 2026 12:04:54 -0800 Subject: [PATCH 040/335] Implementing pretraining for object_models, along with added functionality in TomographyDDP (i.e, setting up distributed stuff for dataloaders and models). Dataset models also has an included INRPretrainDataset which I'm not sure is a good idea ~ check with Arthur --- src/quantem/tomography/dataset_models.py | 41 +++++++ src/quantem/tomography/object_models.py | 140 ++++++++++++++++++++++- src/quantem/tomography/tomography_ddp.py | 134 ++++++++++++++++++++++ 3 files changed, 312 insertions(+), 3 deletions(-) diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index 0530df0f..c3b59a56 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -223,4 +223,45 @@ def mode(self) -> Literal["train", "val"]: return self._mode +class TomographyINRPretrainDataset(Dataset): + """ + Dataset class for pretraining INR models. + """ + + def __init__( + self, + pretrain_target: torch.Tensor, + ): + data = pretrain_target.float() + + total_elements = data.numel() + if total_elements > 1e6: + sample_size = min(int(1e6), total_elements) + flat_data = data.flatten() + indices = torch.randperm(total_elements)[:sample_size] + sampled_data = flat_data[indices] + data_quantile = torch.quantile(sampled_data, 0.95) + else: + data_quantile = torch.quantile(data, 0.95) + + data = data / data_quantile + data = torch.permute(data, (2, 1, 0)) + # data = torch.flip(data, dims=(2,)) + + self.volume = data.cpu() + self.N = pretrain_target.shape[0] # Assumes cubic volume. + self.total_samples = pretrain_target.shape[0] ** 3 + + coords_1d = torch.linspace(-1, 1, self.N) + x, y, z = torch.meshgrid(coords_1d, coords_1d, coords_1d, indexing="ij") + self.coords = torch.stack([x, y, z], dim=-1).reshape(-1, 3).cpu() + self.targets = self.volume.reshape(-1).cpu() + + def __len__(self): + return self.total_samples + + def __getitem__(self, idx): + return {"coords": self.coords[idx], "target": self.targets[idx]} + + DatasetModelType = TomographyINRDataset | TomographyPixDataset diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 69263a2d..cc6ce585 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -1,7 +1,7 @@ from abc import abstractmethod from copy import deepcopy from dataclasses import dataclass -from typing import Self +from typing import Callable, Self import numpy as np import torch @@ -11,6 +11,8 @@ from quantem.core.ml.constraints import BaseConstraints, Constraints from quantem.core.ml.optimizer_mixin import OptimizerMixin from quantem.core.utils.rng import RNGMixin +from quantem.tomography.dataset_models import TomographyINRPretrainDataset +from quantem.tomography.tomography_ddp import TomographyDDP @dataclass @@ -264,8 +266,140 @@ def to(self, device: str): self._obj = self._obj.to(device) -class ObjectINR(ObjectConstraints): - pass +class ObjectINR(ObjectConstraints, TomographyDDP): + """ + Object model for INR objects. + """ + + def __init__( + self, + model: nn.Module, + volume_shape: tuple[int, int, int], + device: str = "cpu", + rng: np.random.Generator | int | None = None, + ): + pass + + @classmethod + def from_model( + cls, + model: "torch.nn.Module", + volume_shape: tuple[int, int, int], + device: str = None, # Have to make device a requirement here in distinguishing GPU vs CPU training + rng: np.random.Generator | int | None = None, + ): + obj_model = cls( + model=model, + volume_shape=volume_shape, + device=device, + rng=rng, + _token=cls._token, + ) + + # Initialize DDP params and build the model. TODO: Not sure if this is the best way to do this? But to pretrain we need the model to be wrapped in DDP. + obj_model.setup_distributed(device=device) + obj_model._model = obj_model.build_model(model) + + return obj_model + + # --- Properties --- + + @property + def model(self) -> "nn.Module": + """ + Returns the INR model. + """ + return self._model + + @model.setter + def model(self, model: "nn.Module"): + """ + This doesn't work -- can't have setters for torch sub modules + https://github.com/pytorch/pytorch/issues/52664 + + For now, upon initialization private variable `._model` is set to the built model. + """ + raise RuntimeError("\n\n\nsetting model, this shouldn't be reachable???\n\n\n") + + # Pretraining + @property + def pretrained_weights(self) -> dict[str, torch.Tensor]: + """get the pretrained weights of the INR model""" + return self._pretrained_weights + + def _set_pretrained_weights(self, model: "torch.nn.Module"): + """set the pretrained weights of the INR model""" + if not isinstance(model, torch.nn.Module): + raise TypeError(f"Pretrained model must be a torch.nn.Module, got {type(model)}") + self._pretrained_weights = deepcopy(model.state_dict()) + + @property + def pretrain_target(self) -> TomographyINRPretrainDataset: + """get the pretrain target""" + return self._pretrain_target + + @pretrain_target.setter + def pretrain_target(self, target: TomographyINRPretrainDataset): + """set the pretrain target""" + self._pretrain_target = target + + # --- Helper Functions --- + + # Reset method that goes back to the pretrained weights. + def reset(self): + """reset the model to the pretrained weights""" + self._model = self.build_model( + self._model, self._pretrained_weights + ) # Since loading the pretrained weights needs to be done in build_model. + + # --- Forward Method --- + + def forward(self, coords: torch.Tensor) -> torch.Tensor: + """forward pass for the INR model""" + + all_densities = self.model(coords) + + if all_densities.dim() > 1: + all_densities = all_densities.squeeze(-1) + + valid_mask = ( + (coords[:, 0] >= -1) & (coords[:, 0] <= 1) & (coords[:, 1] >= -1) & (coords[:, 1] <= 1) + ).float() + + all_densities = all_densities * valid_mask + + return all_densities + + # Pretrain Loop + + def pretrain( + self, + pretrain_dataset: TomographyINRPretrainDataset, + batch_size: int, + reset: bool = False, + num_iters: int = 10, + num_workers: int = 0, + optimizer_params: dict | None = None, + scheduler_params: dict | None = None, + loss_fn: Callable | str = "l1", + show: bool = True, + ): + """ + Pretrain the INR model to fit target volume. + """ + + if ( + pretrain_dataset is not None + ): # Need to make a check if there's already a pretrain dataset to not go through with the setup again. + self.pretrain_dataset = pretrain_dataset + self.pretraining_dataloader = self.setup_dataloader( + pretrain_dataset, batch_size, num_workers=num_workers + ) + + if optimizer_params is not None: + self.set_optimizer(optimizer_params) + if scheduler_params is not None: + self.set_scheduler(scheduler_params, num_iters) ObjectModelType = ObjectPixelated | ObjectINR diff --git a/src/quantem/tomography/tomography_ddp.py b/src/quantem/tomography/tomography_ddp.py index 8b137891..9a8a4a3f 100644 --- a/src/quantem/tomography/tomography_ddp.py +++ b/src/quantem/tomography/tomography_ddp.py @@ -1 +1,135 @@ +import os +import torch +import torch.distributed as dist +import torch.nn as nn +from torch.utils.data import DataLoader, Dataset, DistributedSampler + + +class TomographyDDP: + """ + Class for setting up all distributed training. + + - + """ + + def __init__( + self, + ): + self.setup_distributed() + + def setup_distributed(self, device: str | None = None): + """ + Initializes parameters depending if multiple-GPU training, single-GPU training, or CPU training. + """ + if "RANK" in os.environ: + if not dist.is_initialized(): + dist.init_process_group( + backend="nccl" if torch.cuda.is_available() else "gloo", init_method="env://" + ) + + self.world_size = dist.get_world_size() + self.global_rank = dist.get_rank() + self.local_rank = int(os.environ["LOCAL_RANK"]) + + torch.cuda.set_device(self.local_rank) + self.device = torch.device("cuda", self.local_rank) + else: + self.world_size = 1 + self.global_rank = 0 + self.local_rank = 0 + + if torch.cuda.is_available(): + self.device = torch.device("cuda:0" if device is None else device) + torch.cuda.set_device(self.device.index) + print("Single GPU training") + else: + self.device = torch.device("cpu") + print("CPU training") + + if self.device.type == "cuda": + torch.backends.cudnn.benchmark = True + torch.backends.cuda.matmul.allow_tf32 = True + torch.backends.cudnn.allow_tf32 = True + + def setup_dataloader( + self, + dataset: Dataset, + batch_size: int, + num_workers: int = 0, + # val_fraction: float = 0.0, + ): + pin_mem = self.device.type == "cuda" + persist = num_workers > 0 + + # TODO: Implement validation dataloader + + if self.world_size > 1: + train_sampler = DistributedSampler( + dataset, + num_replicas=self.world_size, + rank=self.global_rank, + shuffle=True, + ) + + else: + train_sampler = None + shuffle = True + + train_dataloader = DataLoader( + dataset, + batch_size=batch_size, + num_workers=num_workers, + sampler=train_sampler, + shuffle=shuffle, + pin_memory=pin_mem, + drop_last=True, + persistent_workers=persist, + ) + + if self.global_rank == 0: + print("Dataloader setup complete:") + print(f" Total samples: {len(dataset)}") + print(f" Local batch size: {batch_size}") + print(f" Global batch size: {batch_size * self.world_size}") + print(f" Train batches per GPU per epoch: {len(train_dataloader)}") + + return train_dataloader + + def build_model( + self, + model: nn.Module, + pretrained_weights: dict[str, torch.Tensor] | None = None, + ) -> nn.Module | nn.parallel.DistributedDataParallel: + """ + Wraps the model with DistributedDataParallel if mulitple GPUs are available. + + Returns the model. + """ + + model = model.to(self.device) + if pretrained_weights is not None: + model.load_state_dict(pretrained_weights.copy()) + + if self.world_size > 1: + model = torch.nn.parallel.DistributedDataParallel( + model, + device_ids=[self.local_rank], + output_device=self.local_rank, + find_unused_parameters=False, + broadcast_buffers=True, + bucket_cap_mb=100, + gradient_as_bucket_view=True, + ) + + if self.global_rank == 0: + print("Model wrapped with DDP") + + if self.world_size > 1: + if self.global_rank == 0: + print("Model built, distributed, and compiled successfully") + + else: + print("Model built, compiled successfully") + + return model From 0ed758a412f9329668ea8cd4e1fabac97c59c273 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 20 Jan 2026 14:11:59 -0800 Subject: [PATCH 041/335] Object pretraining INR working --- src/quantem/tomography/object_models.py | 156 ++++++++++++++++++++++-- src/quantem/tomography/tomography.py | 5 +- 2 files changed, 152 insertions(+), 9 deletions(-) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index cc6ce585..dca9a78a 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -1,10 +1,11 @@ from abc import abstractmethod from copy import deepcopy from dataclasses import dataclass -from typing import Callable, Self +from typing import Any, Callable, Self import numpy as np import torch +import torch.distributed as dist import torch.nn as nn from quantem.core.io.serialize import AutoSerialize @@ -130,14 +131,19 @@ def reset(self) -> None: """ raise NotImplementedError + @property + def params(self) -> list[nn.Parameter]: + """ + Get the parameters that should be optimized for this model. + """ + raise NotImplementedError + # --- Helper Functions --- def get_optimization_parameters(self) -> list[nn.Parameter]: """ Get the parameters that should be optimized for this model. - - TODO: I have no idea what this does. """ - return list[nn.Parameter](self.parameters()) + return list[nn.Parameter](self.params()) @abstractmethod # Each subclass should implement this. def to(self, *args, **kwargs): @@ -278,12 +284,19 @@ def __init__( device: str = "cpu", rng: np.random.Generator | int | None = None, ): - pass + super().__init__( + shape=volume_shape, + device=device, + rng=rng, + _token=self._token, + ) + self._pretrain_losses = [] + self._pretrain_lrs = [] @classmethod def from_model( cls, - model: "torch.nn.Module", + model: "nn.Module", volume_shape: tuple[int, int, int], device: str = None, # Have to make device a requirement here in distinguishing GPU vs CPU training rng: np.random.Generator | int | None = None, @@ -293,7 +306,6 @@ def from_model( volume_shape=volume_shape, device=device, rng=rng, - _token=cls._token, ) # Initialize DDP params and build the model. TODO: Not sure if this is the best way to do this? But to pretrain we need the model to be wrapped in DDP. @@ -321,6 +333,10 @@ def model(self, model: "nn.Module"): """ raise RuntimeError("\n\n\nsetting model, this shouldn't be reachable???\n\n\n") + @property + def params(self): + return self.model.parameters() + # Pretraining @property def pretrained_weights(self) -> dict[str, torch.Tensor]: @@ -352,6 +368,9 @@ def reset(self): self._model, self._pretrained_weights ) # Since loading the pretrained weights needs to be done in build_model. + def get_optimization_parameters(self): + return self.params + # --- Forward Method --- def forward(self, coords: torch.Tensor) -> torch.Tensor: @@ -382,6 +401,7 @@ def pretrain( optimizer_params: dict | None = None, scheduler_params: dict | None = None, loss_fn: Callable | str = "l1", + apply_constraints: bool = False, show: bool = True, ): """ @@ -401,5 +421,127 @@ def pretrain( if scheduler_params is not None: self.set_scheduler(scheduler_params, num_iters) + if reset: + raise NotImplementedError( + "TODO: Resseting the model to the pretrained weights is not implemented yet. To make this work I would have to reinstantiate the model I think." + ) + + if loss_fn == "l1": + loss_fn = nn.functional.l1_loss + + self._pretrain( + num_iters=num_iters, + loss_fn=loss_fn, + apply_constraints=apply_constraints, + ) + + def _pretrain( + self, + num_iters: int, + loss_fn: Callable, + apply_constraints: bool, + ): + self.model.train() + optimizer = self.optimizer + scheduler = self.scheduler + + for a0 in range(num_iters): + for batch_idx, batch in enumerate[Any](self.pretraining_dataloader): + coords = batch["coords"].to(self.device, non_blocking=True) + target = batch["target"].to(self.device, non_blocking=True) + + with torch.autocast( + device_type=self.device.type, dtype=torch.bfloat16, enabled=True + ): + outputs = self.forward(coords) + loss = loss_fn(outputs, target) + + loss.backward() + optimizer.step() + optimizer.zero_grad() + + if scheduler is not None: + if isinstance(scheduler, torch.optim.lr_scheduler.ReduceLROnPlateau): + scheduler.step(loss.item()) + else: + scheduler.step() + + self._pretrain_losses.append(loss.item()) + print(f"Pretrain Loss: {loss.item():.4f}") + self._pretrain_lrs.append(optimizer.param_groups[0]["lr"]) + + def create_volume( + self, + ): + N = max(self._shape) + with torch.no_grad(): + coords_1d = torch.linspace(-1, 1, N) + x, y, z = torch.meshgrid(coords_1d, coords_1d, coords_1d, indexing="ij") + inputs = torch.stack([x, y, z], dim=-1).reshape(-1, 3) + + model = self.model.module if hasattr(self.model, "module") else self.model + + inference_batch_size = 5 * N * N + total_samples = N**3 + samples_per_gpu = total_samples // self.world_size + remainder = total_samples % self.world_size + if self.global_rank < remainder: + start_idx = self.global_rank * (samples_per_gpu + 1) + end_idx = start_idx + samples_per_gpu + 1 + else: + start_idx = self.global_rank * samples_per_gpu + remainder + end_idx = start_idx + samples_per_gpu + inputs_subset = inputs[start_idx:end_idx] + num_samples = inputs_subset.shape[0] + outputs_list = [] + for batch_start in range(0, num_samples, inference_batch_size): + batch_end = min(batch_start + inference_batch_size, num_samples) + batch_coords = inputs_subset[batch_start:batch_end].to( + self.device, non_blocking=True + ) + batch_outputs = model(batch_coords) + if batch_outputs.dim() > 1: + batch_outputs = batch_outputs.squeeze(-1) + outputs_list.append(batch_outputs.cpu()) + + outputs = torch.cat(outputs_list, dim=0) + + if self.world_size > 1: + output_size = torch.tensor(outputs.shape[0], device=self.device, dtype=torch.long) + all_sizes = [ + torch.zeros(1, device=self.device, dtype=torch.long) + for _ in range(self.world_size) + ] + dist.all_gather(all_sizes, output_size) + + max_size = max(size.item() for size in all_sizes) + + if outputs.shape[0] < max_size: + padding = torch.zeros( + max_size - outputs.shape[0], device=outputs.device, dtype=outputs.dtype + ) + outputs_padded = torch.cat([outputs, padding], dim=0).to(self.device) + else: + outputs_padded = outputs.to(self.device) + + gathered_outputs = [ + torch.empty(max_size, device=self.device, dtype=outputs.dtype) + for _ in range(self.world_size) + ] + dist.all_gather(gathered_outputs, outputs_padded.contiguous()) + + trimmed_outputs = [] + for rank, size in enumerate(all_sizes): + trimmed_outputs.append(gathered_outputs[rank][: size.item()]) + + pred_full = torch.cat(trimmed_outputs, dim=0).reshape(N, N, N).float() + else: + pred_full = outputs.reshape(N, N, N).float() + + self._obj = pred_full.detach().cpu() + + def get_tv_loss(self): + pass + ObjectModelType = ObjectPixelated | ObjectINR diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index ebc36738..0595c402 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -13,7 +13,7 @@ from quantem.tomography.utils import torch_phase_cross_correlation -class Tomography(TomographyBase, TomographyOpt): +class Tomography(TomographyOpt, TomographyBase): """ Class for handling all ML tomography reconstruction methods. Automatic handling between AD and INR-based tomography. @@ -88,7 +88,8 @@ def reconstruct( proj_forward = torch.zeros_like(self.dset.tilt_stack).permute(2, 0, 1) else: proj_forward = torch.zeros_like(self.dset.tilt_stack) - + print("proj_forward.shape", proj_forward.shape) + print("self.dset.tilt_stack.shape", self.dset.tilt_stack.shape) for iter in pbar: proj_forward, loss = self._reconstruction_epoch( inline_alignment=inline_alignment, From c90984c6c9d9f55a1789b7c3c1d01519fb945e12 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Wed, 21 Jan 2026 14:53:48 -0800 Subject: [PATCH 042/335] DDPMixin in ML, pretraining working for objects --- .../tomography_ddp.py => core/ml/ddp.py} | 3 ++- src/quantem/core/ml/inr.py | 14 ++++++++++- src/quantem/tomography/object_models.py | 24 ++++++++++++------- src/quantem/tomography/tomography.py | 14 +++++++++++ 4 files changed, 44 insertions(+), 11 deletions(-) rename src/quantem/{tomography/tomography_ddp.py => core/ml/ddp.py} (99%) diff --git a/src/quantem/tomography/tomography_ddp.py b/src/quantem/core/ml/ddp.py similarity index 99% rename from src/quantem/tomography/tomography_ddp.py rename to src/quantem/core/ml/ddp.py index 9a8a4a3f..7b42a641 100644 --- a/src/quantem/tomography/tomography_ddp.py +++ b/src/quantem/core/ml/ddp.py @@ -6,7 +6,8 @@ from torch.utils.data import DataLoader, Dataset, DistributedSampler -class TomographyDDP: +# Rename DDPMixin +class DDPMixin: """ Class for setting up all distributed training. diff --git a/src/quantem/core/ml/inr.py b/src/quantem/core/ml/inr.py index 24ee45e4..3091a9f0 100644 --- a/src/quantem/core/ml/inr.py +++ b/src/quantem/core/ml/inr.py @@ -23,6 +23,7 @@ def __init__( hsiren: bool = False, dtype: torch.dtype = torch.float32, final_activation: str | Callable = "identity", + winner_initialization: bool = False, ) -> None: """Initialize Siren. @@ -59,7 +60,7 @@ def __init__( self.alpha = alpha self.hsiren = hsiren self.dtype = dtype - + self.winner_initialization = winner_initialization self.final_activation = final_activation self._build() @@ -109,6 +110,15 @@ def _build(self) -> None: net_list.append(self._final_activation) self.net = nn.Sequential(*net_list) + if self.winner_initialization: + with torch.no_grad(): + self.net[0].linear.weight += ( + torch.randn_like(self.net[0].linear.weight) * 1 / self.first_omega_0 + ) + self.net[1].linear.weight += ( + torch.randn_like(self.net[1].linear.weight) * 0.01 / self.hidden_omega_0 + ) + def forward(self, coords: torch.Tensor) -> torch.Tensor: output = self.net(coords) return output @@ -182,6 +192,7 @@ def __init__( alpha: float = 1.0, dtype: torch.dtype = torch.float32, final_activation: str | Callable = "identity", + winner_initialization: bool = False, ) -> None: """Initialize HSiren. @@ -217,4 +228,5 @@ def __init__( hsiren=True, dtype=dtype, final_activation=final_activation, + winner_initialization=winner_initialization, ) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index dca9a78a..67276433 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -10,10 +10,10 @@ from quantem.core.io.serialize import AutoSerialize from quantem.core.ml.constraints import BaseConstraints, Constraints +from quantem.core.ml.ddp import DDPMixin from quantem.core.ml.optimizer_mixin import OptimizerMixin from quantem.core.utils.rng import RNGMixin from quantem.tomography.dataset_models import TomographyINRPretrainDataset -from quantem.tomography.tomography_ddp import TomographyDDP @dataclass @@ -272,7 +272,7 @@ def to(self, device: str): self._obj = self._obj.to(device) -class ObjectINR(ObjectConstraints, TomographyDDP): +class ObjectINR(ObjectConstraints, DDPMixin): """ Object model for INR objects. """ @@ -428,6 +428,8 @@ def pretrain( if loss_fn == "l1": loss_fn = nn.functional.l1_loss + elif loss_fn == "l2": + loss_fn = nn.functional.mse_loss self._pretrain( num_iters=num_iters, @@ -446,6 +448,7 @@ def _pretrain( scheduler = self.scheduler for a0 in range(num_iters): + epoch_loss = 0 for batch_idx, batch in enumerate[Any](self.pretraining_dataloader): coords = batch["coords"].to(self.device, non_blocking=True) target = batch["target"].to(self.device, non_blocking=True) @@ -457,17 +460,20 @@ def _pretrain( loss = loss_fn(outputs, target) loss.backward() + epoch_loss += loss.item() optimizer.step() optimizer.zero_grad() - if scheduler is not None: - if isinstance(scheduler, torch.optim.lr_scheduler.ReduceLROnPlateau): - scheduler.step(loss.item()) - else: - scheduler.step() + if scheduler is not None: + if isinstance(scheduler, torch.optim.lr_scheduler.ReduceLROnPlateau): + scheduler.step(epoch_loss) + else: + scheduler.step() - self._pretrain_losses.append(loss.item()) - print(f"Pretrain Loss: {loss.item():.4f}") + self._pretrain_losses.append(epoch_loss / len(self.pretraining_dataloader)) + print( + f"Epoch {a0 + 1}/{num_iters}, Pretrain Loss: {epoch_loss / len(self.pretraining_dataloader):.4f}" + ) self._pretrain_lrs.append(optimizer.param_groups[0]["lr"]) def create_volume( diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 0595c402..97dd2bf2 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -11,6 +11,7 @@ from quantem.tomography.tomography_base import TomographyBase from quantem.tomography.tomography_opt import TomographyOpt from quantem.tomography.utils import torch_phase_cross_correlation +from quantem.tomography_old.utils import gaussian_filter_2d_stack, gaussian_kernel_1d class Tomography(TomographyOpt, TomographyBase): @@ -82,6 +83,7 @@ def reconstruct( mode: Literal["sirt", "fbp"] = "sirt", reset: bool = False, inline_alignment: bool = False, + smoothing_sigma: float | None = None, ): pbar = tqdm(range(num_iter), desc=f"{mode} Reconstruction") if mode == "sirt" or mode == "fbp": @@ -90,11 +92,18 @@ def reconstruct( proj_forward = torch.zeros_like(self.dset.tilt_stack) print("proj_forward.shape", proj_forward.shape) print("self.dset.tilt_stack.shape", self.dset.tilt_stack.shape) + + if smoothing_sigma is not None: + gaussian_kernel = gaussian_kernel_1d(smoothing_sigma).to(self.device) + else: + gaussian_kernel = None + for iter in pbar: proj_forward, loss = self._reconstruction_epoch( inline_alignment=inline_alignment, mode=mode, proj_forward=proj_forward, + gaussian_kernel=gaussian_kernel, ) pbar.set_description(f"{mode} Reconstruction | Loss: {loss.item():.4f}") @@ -111,6 +120,7 @@ def _reconstruction_epoch( inline_alignment: bool, mode: Literal["sirt", "fbp"], proj_forward: torch.Tensor, + gaussian_kernel: torch.Tensor | None = None, ): loss = 0 @@ -169,6 +179,10 @@ def _reconstruction_epoch( self.obj_model.obj += correction + if gaussian_kernel is not None: + print(self.obj_model.obj.shape) + self.obj_model.obj = gaussian_filter_2d_stack(self.obj_model.obj, gaussian_kernel) + loss = torch.mean(torch.abs(error)) return proj_forward, loss From 97c80d6df8e392f73d7a8b2571f5f032e05b29cb Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Wed, 21 Jan 2026 15:06:11 -0800 Subject: [PATCH 043/335] Added cosine annealing to set_scheduler in OptimizerMixin --- src/quantem/core/ml/optimizer_mixin.py | 6 ++++++ src/quantem/tomography/object_models.py | 21 +++++++++++++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index e2d2e89a..7c39e39d 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -179,6 +179,12 @@ def set_scheduler( end_factor=params.get("end_factor", 1.0), total_iters=params.get("total_iters", num_iter), ) + elif sched_type == "cosine_annealing": + self._scheduler = torch.optim.lr_scheduler.CosineAnnealingLR( + optimizer, + T_max=params.get("T_max", num_iter), + eta_min=params.get("eta_min", base_LR), + ) else: raise ValueError(f"Unknown scheduler type: {sched_type}") diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 67276433..b21759fa 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -11,6 +11,7 @@ from quantem.core.io.serialize import AutoSerialize from quantem.core.ml.constraints import BaseConstraints, Constraints from quantem.core.ml.ddp import DDPMixin +from quantem.core.ml.loss_functions import get_loss_function from quantem.core.ml.optimizer_mixin import OptimizerMixin from quantem.core.utils.rng import RNGMixin from quantem.tomography.dataset_models import TomographyINRPretrainDataset @@ -116,6 +117,13 @@ def obj(self) -> torch.Tensor: """ raise NotImplementedError + @abstractmethod + def dtype(self) -> torch.dtype: + """ + Returns the dtype of the object. + """ + raise NotImplementedError + @abstractmethod def forward(self, *args, **kwargs) -> torch.Tensor: """ @@ -359,6 +367,14 @@ def pretrain_target(self, target: TomographyINRPretrainDataset): """set the pretrain target""" self._pretrain_target = target + @property + def dtype(self) -> torch.dtype: + """ + Returns the dtype of the object. + """ + # TODO: This is a temporary solution to get the dtype of the object. + return torch.float32 + # --- Helper Functions --- # Reset method that goes back to the pretrained weights. @@ -426,10 +442,7 @@ def pretrain( "TODO: Resseting the model to the pretrained weights is not implemented yet. To make this work I would have to reinstantiate the model I think." ) - if loss_fn == "l1": - loss_fn = nn.functional.l1_loss - elif loss_fn == "l2": - loss_fn = nn.functional.mse_loss + loss_fn = get_loss_function(loss_fn, self.dtype) self._pretrain( num_iters=num_iters, From 9900fc3fb9e710e62cd7145ed9cb3785e91e357a Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Wed, 21 Jan 2026 17:09:17 -0800 Subject: [PATCH 044/335] Reworking TomographyINRDatasets; need to figure out what to do for aux params --- src/quantem/core/ml/inr.py | 2 +- src/quantem/tomography/dataset_models.py | 46 +- src/quantem/tomography/object_models.py | 29 +- src/quantem/tomography_old/tomography.py | 1769 +++++++++++----------- 4 files changed, 942 insertions(+), 904 deletions(-) diff --git a/src/quantem/core/ml/inr.py b/src/quantem/core/ml/inr.py index 3091a9f0..4cf4fc70 100644 --- a/src/quantem/core/ml/inr.py +++ b/src/quantem/core/ml/inr.py @@ -116,7 +116,7 @@ def _build(self) -> None: torch.randn_like(self.net[0].linear.weight) * 1 / self.first_omega_0 ) self.net[1].linear.weight += ( - torch.randn_like(self.net[1].linear.weight) * 0.01 / self.hidden_omega_0 + torch.randn_like(self.net[1].linear.weight) * 0.1 / self.hidden_omega_0 ) def forward(self, coords: torch.Tensor) -> torch.Tensor: diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index c3b59a56..656520da 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -79,6 +79,8 @@ def __init__( self._reference_tilt_angle_idx = torch.argmin(torch.abs(self.tilt_angles)) # TODO: Implement AuxParams from old tomography_dataset.py here. + self.z1_param = torch + @abstractmethod def forward( self, @@ -89,6 +91,32 @@ def forward( """ raise NotImplementedError("This method should be implemented in subclasses.") + # --- Properties --- + @property + def reference_tilt_idx(self) -> int: + return self._reference_tilt_angle_idx + + @reference_tilt_idx.setter + def reference_tilt_idx(self, reference_tilt_idx: int): + self._reference_tilt_angle_idx = reference_tilt_idx + + @property + def learnable_tilts(self) -> int: + return self.tilt_angles.shape[0] - 1 + + @learnable_tilts.setter + def learnable_tilts(self, learnable_tilts: int): + self._learnable_tilts = learnable_tilts + + @property + def params(self) -> dict[str, torch.nn.Parameter]: + """ + Returns the parameters that should be optimized for this dataset. + + Should be implemented in subclasses. + """ + raise NotImplementedError("This method should be implemented in subclasses.") + # --- Helper Functions --- def to(self, device: str): self.tilt_stack = self.tilt_stack.to(device) @@ -148,6 +176,8 @@ class TomographyINRDataset(TomographyDatasetBase, Dataset): The two main methods here are that the `forward` call will return the relative pose parameters, while `__getitem__` will actually return the pixel values of the tilt stack. + + TODO: I think TomographyINRDataset shouldn't handle the train/val split and will be handled later? Yea this is handled in setup_dataloader in DDP """ def __init__( @@ -155,26 +185,12 @@ def __init__( tilt_stack: Dataset3d | NDArray | torch.Tensor, tilt_angles: NDArray | torch.Tensor, learn_pose: bool = True, - val_ratio: float = 0.0, - mode: Literal["train", "val"] = "train", seed: int = 42, token: object | None = None, ): super().__init__(tilt_stack, tilt_angles, learn_pose, token) - self._total_pixels = self.volume_size[0] * self.volume_size[1] * self.volume_size[2] - - # Create train/val split - torch.manual_seed(seed) - all_indices = torch.randperm(self.total_pixels) - - num_val = int(self.total_pixels * val_ratio) - if mode == "val": - self.indices = all_indices[:num_val] - else: # train - self.indices = all_indices[num_val:] - - self._mode = mode + self.z1_param = torch.nn.Parameter(torch.zeros(self.learnable_tilts)) def forward(self, dummy_input: Any = None): """ diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index b21759fa..d4d8aab1 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -559,8 +559,33 @@ def create_volume( self._obj = pred_full.detach().cpu() - def get_tv_loss(self): - pass + def get_tv_loss( + self, + coords: torch.Tensor, + ): + tv_loss = torch.tensor(0.0, device=coords.device) + + num_tv_samples = min(10000, coords.shape[0]) + tv_indices = torch.randperm(coords.shape[0], device=coords.device)[:num_tv_samples] + + tv_coords = coords[tv_indices].detach().requires_grad_(True) + + tv_densities_recomputed = self.forward(tv_coords) + + if tv_densities_recomputed.dim() > 1: + tv_densities_recomputed = tv_densities_recomputed.squeeze(-1) + + grad_outputs = torch.autograd.grad( + outputs=tv_densities_recomputed, + inputs=tv_coords, + grad_outputs=torch.ones_like(tv_densities_recomputed), + create_graph=True, + )[0] + + grad_norm = torch.norm(grad_outputs, dim=1) + + tv_loss += self.constraints.tv_vol * grad_norm.mean() + return tv_loss ObjectModelType = ObjectPixelated | ObjectINR diff --git a/src/quantem/tomography_old/tomography.py b/src/quantem/tomography_old/tomography.py index 5064cd3f..7be24397 100644 --- a/src/quantem/tomography_old/tomography.py +++ b/src/quantem/tomography_old/tomography.py @@ -1,886 +1,883 @@ -import matplotlib.pyplot as plt -import numpy as np -import torch -import torch.distributed as dist -from torch.nn import SmoothL1Loss -from torch.utils.tensorboard import SummaryWriter - -# from torch_radon.radon import ParallelBeam as Radon -from tqdm.auto import tqdm - -from quantem.core.ml.loss_functions import ( - CharbonnierLoss, - L1Loss, - LLMSELoss, - MSELogMSELoss, - MSELoss, -) - -# Temporary imports for TomographyNERF -from quantem.tomography.object_models import ObjectINN, ObjectVoxelwise -from quantem.tomography.tomography_base import TomographyBase -from quantem.tomography.tomography_conv import TomographyConv -from quantem.tomography.tomography_ddp import ( - AdaptiveSmoothL1LossDDP, - PretrainVolumeDataset, - TomographyDDP, -) -from quantem.tomography.tomography_ml import TomographyML -from quantem.tomography.utils import differentiable_shift_2d, gaussian_kernel_1d, rot_ZXZ - - -# Temporary aux class for TomographyNERF -# TODO: Maybe put this in INN? -def get_num_samples_per_ray(N: int, epoch: int): - """Increase number of samples per ray at specific epochs.""" - # Exponential schedule - - epochs = np.linspace(0, 10, 5, dtype=int) - # schedule = np.linspace(20, N, 5, dtype=int) - schedule = np.array([20, 100, 150, 200, 200]) - # schedule = np.array([200, 200, 200, 200, 200]) - # schedule = np.array([20, 100, 250, 500, 500]) - # schedule = np.array([500, 500, 500, 500, 500]) - # schedule = np.array([300, 300, 300, 300, 300]) - # schedule = np.exp(schedule) - schedule_warmup = dict[int, int](zip(epochs, schedule)) - - for epoch_threshold, samples in schedule_warmup.items(): - if epoch >= epoch_threshold: - num_samples = samples - - return num_samples - - -class Tomography(TomographyConv, TomographyML, TomographyBase, TomographyDDP): - """ - Top level class for either using conventional or ML-based reconstruction methods - for tomography. - """ - - def __init__( - self, - dataset, - volume_obj, - device, - _token, - ): - super().__init__(dataset, volume_obj, device, _token) - - # TODO: More elegant way of doing this. - self.global_epochs = 0 - self.ddp_instantiated = False - self.pretraining_instantiated = False - - # --- Reconstruction Method --- - - def sirt_recon( - self, - num_iterations: int = 10, - inline_alignment: bool = False, - enforce_positivity: bool = True, - volume_shape: tuple = None, - reset: bool = True, - smoothing_sigma: float = None, - shrinkage: float = None, - filter_name: str = "hamming", - circle: bool = True, - plot_loss: bool = False, - ): - num_angles, num_rows, num_cols = self.dataset.tilt_series.shape - sirt_tilt_series = self.dataset.tilt_series.clone() - sirt_tilt_series = sirt_tilt_series.permute(2, 0, 1) - - hard_constraints = { - "positivity": enforce_positivity, - "shrinkage": shrinkage, - } - self.volume_obj.hard_constraints = hard_constraints - - if volume_shape is None: - volume_shape = (num_rows, num_rows, num_rows) - else: - D, H, W = volume_shape - - if reset: - self.volume_obj.reset() - self.loss = [] - - proj_forward = torch.zeros_like(self.dataset.tilt_series) - - pbar = tqdm(range(num_iterations), desc="SIRT Reconstruction") - - if smoothing_sigma is not None: - gaussian_kernel = gaussian_kernel_1d(smoothing_sigma).to(self.device) - else: - gaussian_kernel = None - - print( - "Devices", - sirt_tilt_series.device, - proj_forward.device, - self.dataset.tilt_angles.device, - ) - - for iter in pbar: - proj_forward, loss = self._sirt_run_epoch( - tilt_series=sirt_tilt_series, - proj_forward=proj_forward, - angles=self.dataset.tilt_angles, - inline_alignment=iter > 0 and inline_alignment, - filter_name=filter_name, - gaussian_kernel=gaussian_kernel, - circle=circle, - ) - - pbar.set_description(f"SIRT Reconstruction | Loss: {loss.item():.4f}") - - self.loss.append(loss.item()) - - self.sirt_recon_vol = self.volume_obj - - # Permutation due to sinogram ordering. - self.sirt_recon_vol.obj = self.sirt_recon_vol.obj.permute(1, 2, 0) - - if plot_loss: - self.plot_loss() - - # TODO: ML Recon which has NeRF and AD depending on the object type. - # TODO: Temporary - - def create_batch_projection_rays(self, pixel_i, pixel_j, N, num_samples_per_ray): - """Create projection rays for entire batch simultaneously.""" - batch_size = len(pixel_i) - - # Convert all pixels to normalized coordinates - x_coords = (pixel_j / (N - 1)) * 2 - 1 - y_coords = (pixel_i / (N - 1)) * 2 - 1 - # TODO: maybe pixel_j.device? - # Create z coordinates - z_coords = torch.linspace(-1, 1, num_samples_per_ray, device=self.device) - - # Create rays for all pixels: [batch_size, num_samples_per_ray, 3] - rays = torch.zeros(batch_size, num_samples_per_ray, 3, device=self.device) - - # Fill coordinates efficiently - rays[:, :, 0] = x_coords.unsqueeze(1) # x constant per ray - rays[:, :, 1] = y_coords.unsqueeze(1) # y constant per ray - rays[:, :, 2] = z_coords.unsqueeze(0) # z varies along ray - - return rays - - @torch.compile(mode="reduce-overhead") - def transform_batch_ray_coordinates(self, rays, z1, x, z3, shifts, N, sampling_rate): - # Step 1: Apply shifts - shift_x_norm = (shifts[:, 0:1] * sampling_rate * 2) / (N - 1) - shift_y_norm = (shifts[:, 1:2] * sampling_rate * 2) / (N - 1) - - rays_x = rays[:, :, 0] - shift_x_norm - rays_y = rays[:, :, 1] - shift_y_norm - rays_z = rays[:, :, 2] - - # Rotation 1: Z(-z3) - theta = torch.deg2rad(-z3).view(-1, 1) - cos_t = torch.cos(theta) - sin_t = torch.sin(theta) - - rays_x_rot1 = cos_t * rays_x - sin_t * rays_y - rays_y_rot1 = sin_t * rays_x + cos_t * rays_y - rays_z_rot1 = rays_z - - # Rotation 2: X(x) - theta = torch.deg2rad(x).view(-1, 1) - cos_t = torch.cos(theta) - sin_t = torch.sin(theta) - - rays_x_rot2 = rays_x_rot1 - rays_y_rot2 = cos_t * rays_y_rot1 - sin_t * rays_z_rot1 - rays_z_rot2 = sin_t * rays_y_rot1 + cos_t * rays_z_rot1 - - # Rotation 3: Z(-z1) - theta = torch.deg2rad(-z1).view(-1, 1) - cos_t = torch.cos(theta) - sin_t = torch.sin(theta) - - rays_x_final = cos_t * rays_x_rot2 - sin_t * rays_y_rot2 - rays_y_final = sin_t * rays_x_rot2 + cos_t * rays_y_rot2 - rays_z_final = rays_z_rot2 - - # Stack the final result - transformed_rays = torch.stack([rays_x_final, rays_y_final, rays_z_final], dim=2) - - return transformed_rays - - # TODO: Temp logger - def setup_logger(self, log_path): - if self.global_rank == 0: - self.temp_logger = SummaryWriter(log_dir=log_path) - else: - self.temp_logger = None - - def pretrain( - self, - volume_dataset: PretrainVolumeDataset, - obj: ObjectINN, - batch_size: int, - soft_constraints: dict = None, - log_path: str = None, - optimizer_params: dict = None, - epochs: int = 100, - viz_freq: int = 1, - consistency_criterion: str = "mse", - ): - if not self.pretraining_instantiated: - self.setup_distributed() - self.setup_pretraining_dataloader(volume_dataset, batch_size) - self.pretraining_instantiated = True - obj.model = self.build_model(obj._model) - self.obj = obj - - if soft_constraints is not None: - self.obj.soft_constraints = soft_constraints - - if not hasattr(self, "temp_logger"): - self.setup_logger(log_path=log_path) - - if optimizer_params is not None: - optimizer_params = self.scale_lr(optimizer_params) - self.optimizer_params = optimizer_params - self.set_optimizers() - device_type = self.device.type - consistency_loss_fn = None - if consistency_criterion[0].lower() == "mse": - consistency_loss_fn = MSELoss() - elif consistency_criterion[0].lower() == "l1": - consistency_loss_fn = L1Loss() - elif consistency_criterion[0].lower() == "mse_log": - consistency_loss_fn = MSELogMSELoss() - elif consistency_criterion[0].lower() == "llmse": - consistency_loss_fn = LLMSELoss() - elif consistency_criterion[0].lower() == "smooth_l1": - consistency_loss_fn = SmoothL1Loss(beta=consistency_criterion[1]) - elif consistency_criterion[0].lower() == "adaptive_smooth_l1": - consistency_loss_fn = AdaptiveSmoothL1LossDDP( - beta_init=consistency_criterion[1], ema_factor=0.99, eps=1e-8 - ) - elif consistency_criterion[0].lower() == "charbonnier": - consistency_loss_fn = CharbonnierLoss(epsilon=1e-12, reduction="mean") - else: - raise ValueError(f"Invalid consistency criterion: {consistency_criterion}") - - for epoch in range(epochs): - if self.pretraining_sampler is not None: - self.pretraining_sampler.set_epoch(epoch) - - self.obj.model.train() - - epoch_loss = 0.0 - epoch_consistency_loss = 0.0 - epoch_tv_loss = 0.0 - num_batches = 0 - - for batch_idx, batch in enumerate(self.pretraining_dataloader): - coords = batch["coords"].to(self.device, non_blocking=True) - target = batch["target"].to(self.device, non_blocking=True) - - for _, opt in self.optimizers.items(): - opt.zero_grad() - - with torch.autocast(device_type=device_type, dtype=torch.bfloat16, enabled=True): - outputs = self.obj.forward(coords) - consistency_loss = consistency_loss_fn(outputs, target) - tv_loss = self.obj.apply_soft_constraints(coords) - - loss = consistency_loss + tv_loss - - loss.backward() - - epoch_loss += loss.detach() - epoch_consistency_loss += consistency_loss.detach() - epoch_tv_loss += tv_loss.detach() - - torch.nn.utils.clip_grad_norm_(self.obj.model.parameters(), max_norm=1.0) - - for _, opt in self.optimizers.items(): - opt.step() - epoch_loss += loss.detach() - num_batches += 1 - - if epoch % viz_freq == 0 or epoch == epochs - 1: - avg_loss = epoch_loss / num_batches - with torch.no_grad(): - self.obj.create_volume( - world_size=self.world_size, - global_rank=self.global_rank, - ray_size=volume_dataset.N, - ) - pred_full = self.obj.obj - loss_tensor = avg_loss.clone().detach() - avg_loss = loss_tensor.item() - avg_tv_loss = epoch_tv_loss / num_batches - avg_consistency_loss = epoch_consistency_loss / num_batches - - metrics = torch.tensor( - [avg_loss, avg_consistency_loss, avg_tv_loss], device=self.device - ) - if self.world_size > 1: - dist.all_reduce(metrics, op=dist.ReduceOp.AVG) - avg_loss, avg_consistency_loss, avg_tv_loss = metrics.tolist() - - if self.global_rank == 0: - self.temp_logger.add_scalar("Pretrain/total_loss", avg_loss, epoch) - self.temp_logger.add_scalar( - "Pretrain/consistency_loss", avg_consistency_loss, epoch - ) - self.temp_logger.add_scalar("Pretrain/tv_loss", avg_tv_loss, epoch) - - # current_lr = self.schedulers["model"].get_last_lr()[0] - # self.temp_logger.add_scalar("Pretrain/lr", current_lr, epoch) - - fig, ax = plt.subplots(ncols=4, figsize=(36, 12)) - ax[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap="turbo", vmin=0) - ax[0].set_title("Sum over Z-axis") - ax[1].matshow( - pred_full[volume_dataset.N // 2].cpu().numpy(), cmap="turbo", vmin=0 - ) - ax[1].set_title(f"Slice at Z={volume_dataset.N // 2}") - ax[2].matshow(pred_full.sum(dim=1).cpu().numpy(), cmap="turbo", vmin=0) - ax[2].set_title("Sum over Y-axis") - ax[3].matshow(pred_full.sum(dim=2).cpu().numpy(), cmap="turbo", vmin=0) - ax[3].set_title("Sum over X-axis") - self.temp_logger.add_figure("Pretrain/viz", fig, epoch, close=True) - - plt.close(fig) - print( - f"Epoch [{epoch}/{epochs}] Pretrain Total Loss: {avg_loss:.6f}, Consistency Loss: {avg_consistency_loss:.6f}, TV Loss: {avg_tv_loss:.6f}" - ) - self.global_epochs += 1 - if self.global_rank == 0: - print(f"Pretraining Completed, saving model weights to {log_path}/model_weights.pth") - torch.save(self.obj.model.state_dict(), f"{log_path}/model_weights.pth") - - def recon( - self, - obj: ObjectINN, - batch_size: int, - num_workers: int = 0, - epochs=20, - use_amp=True, - viz_freq=1, - checkpoint_freq=5, - optimizer_params: dict = None, - scheduler_params: dict = None, - soft_constraints: dict = None, - vol_save_path: str = None, # TODO: TEMPORARY - log_path=None, - val_fraction: float = 0.0, - learn_shifts: bool = True, - # l1_loss: bool = False, - consistency_criterion: str = "mse", - model_weights_path: str = None, - force_cpu: bool = False, - ): - if not self.ddp_instantiated: - if not self.pretraining_instantiated: - self.setup_distributed() - if model_weights_path is not None: - print(f"Loading model weights from {model_weights_path}") - state_dict = torch.load(model_weights_path, map_location="cpu") - - # Handle DataParallel/DDP checkpoints - if any(k.startswith("module.") for k in state_dict.keys()): - new_state_dict = { - k.replace("module.", ""): v for k, v in state_dict.items() - } - else: - new_state_dict = state_dict - - obj.model.load_state_dict(new_state_dict) - print("Model weights loaded successfully") - obj.model = self.build_model(obj._model) - self.obj = obj - - self.setup_dataloader(self.dataset, batch_size, num_workers, val_fraction) - self.ddp_instantiated = True - - if soft_constraints is not None: - self.obj.soft_constraints = soft_constraints - - if not hasattr(self, "temp_logger"): - self.setup_logger(log_path=log_path) - - zero_tilt_idx = torch.argmin(torch.abs(self.dataset.tilt_angles)).item() - - if self.global_rank == 0: - print( - f"Using projection {zero_tilt_idx} (angle={self.dataset.tilt_angles[zero_tilt_idx]:.2f}°) as reference" - ) - print(f"Using consistency criterion: {consistency_criterion}") - - # Auxiliary params setup - self.dataset.setup_auxiliary_params(zero_tilt_idx, self.device, learn_shifts) - aux_params = self.dataset.auxiliary_params - # Scaling learning rates to account for distributed training - if optimizer_params is not None: - optimizer_params = self.scale_lr(optimizer_params) - self.optimizer_params = optimizer_params - self.set_optimizers() - - if scheduler_params is not None: - self.scheduler_params = scheduler_params - self.set_schedulers(self.scheduler_params, num_iter=epochs) - - aux_norm = torch.tensor(0.0, device=self.device) - model_norm = torch.tensor(0.0, device=self.device) - - for _, opt in self.optimizers.items(): - opt.zero_grad() - - N = max(self.dataset.dims) - - device_type = self.device.type - autocast_dtype = torch.bfloat16 if use_amp else None - - for epoch in range(epochs): - num_samples_per_ray = get_num_samples_per_ray(N=N, epoch=self.global_epochs) - - # num_samples_per_ray = get_num_samples_per_ray(epoch) - # Log the change if it happens - if self.global_rank == 0: - print(f"Epoch {epoch}: num_samples_per_ray = {num_samples_per_ray}") - - if self.global_rank == 0 and self.global_epochs > 0: - prev_samples = get_num_samples_per_ray(N=N, epoch=self.global_epochs - 1) - - if num_samples_per_ray != prev_samples: - print( - f"Epoch {epoch}: Changing num_samples_per_ray from {prev_samples} to {num_samples_per_ray}" - ) - - if self.sampler is not None: - self.sampler.set_epoch(epoch) - - epoch_loss = 0.0 - epoch_consistency_loss = 0.0 - epoch_tv_loss = 0.0 - epoch_z1_loss = 0.0 - - num_batches = 0 - consistency_loss_fn = None - if consistency_criterion[0].lower() == "mse": - consistency_loss_fn = MSELoss() - elif consistency_criterion[0].lower() == "l1": - consistency_loss_fn = L1Loss() - elif consistency_criterion[0].lower() == "mse_log": - consistency_loss_fn = MSELogMSELoss() - elif consistency_criterion[0].lower() == "llmse": - consistency_loss_fn = LLMSELoss() - elif consistency_criterion[0].lower() == "smooth_l1": - consistency_loss_fn = SmoothL1Loss(beta=consistency_criterion[1]) - elif consistency_criterion[0].lower() == "adaptive_smooth_l1": - consistency_loss_fn = AdaptiveSmoothL1LossDDP( - beta_init=consistency_criterion[1], ema_factor=0.99, eps=1e-8 - ) - elif consistency_criterion[0].lower() == "charbonnier": - consistency_loss_fn = CharbonnierLoss(epsilon=1e-12, reduction="mean") - else: - raise ValueError(f"Invalid consistency criterion: {consistency_criterion}") - - for batch_idx, batch in enumerate(self.dataloader): - projection_indices = batch["projection_idx"] - - pixel_i = batch["pixel_i"].float().to(self.device, non_blocking=True) - pixel_j = batch["pixel_j"].float().to(self.device, non_blocking=True) - target_values = batch["target_value"].to(self.device, non_blocking=True) - phis = batch["phi"].to(self.device, non_blocking=True) - projection_indices = batch["projection_idx"].to(self.device, non_blocking=True) - - shifts, z1_params, z3_params = aux_params(None) - batch_shifts = torch.index_select(shifts, 0, projection_indices) - batch_z1 = torch.index_select(z1_params, 0, projection_indices) - batch_z3 = torch.index_select(z3_params, 0, projection_indices) - - with torch.autocast( - device_type=device_type, dtype=autocast_dtype, enabled=use_amp - ): - with torch.no_grad(): - batch_ray_coords = self.create_batch_projection_rays( - pixel_i, pixel_j, N, num_samples_per_ray - ) - - # TODO: .forward passing z1, x, z3, shifts, N, sampling_rate - transformed_rays = self.transform_batch_ray_coordinates( - batch_ray_coords, - z1=batch_z1, - x=phis, - z3=batch_z3, - shifts=batch_shifts, - N=N, - sampling_rate=1.0, - ) - - all_coords = transformed_rays.view(-1, 3) - - all_densities = self.obj.forward(all_coords) - - tv_loss = self.obj.apply_soft_constraints(all_coords) - ray_densities = all_densities.view( - len(target_values), num_samples_per_ray - ) # Reshape rays and integarte - step_size = 2.0 / (num_samples_per_ray - 1) - - predicted_values = ray_densities.sum(dim=1) * step_size - - consistency_loss = consistency_loss_fn(predicted_values, target_values) - tv_loss_z1 = torch.tensor(0.0, device=self.device) - - loss = consistency_loss + tv_loss + tv_loss_z1 - - loss.backward() - - epoch_loss += loss.detach() - epoch_consistency_loss += consistency_loss.detach() - epoch_tv_loss += tv_loss.detach() - epoch_z1_loss += tv_loss_z1.detach() - num_batches += 1 - - for key, opt in self.optimizers.items(): - if key == "model": - model_norm = torch.nn.utils.clip_grad_norm_( - self.obj.model.parameters(), max_norm=1 - ) - elif key == "aux_params": - aux_norm = torch.nn.utils.clip_grad_norm_( - self.dataset.auxiliary_params.parameters(), max_norm=1 - ) - opt.step() - opt.zero_grad() - - for key, sched in self.schedulers.items(): - if isinstance(sched, torch.optim.lr_scheduler.ReduceLROnPlateau): - sched.step(epoch_loss) - else: - sched.step() - - if epoch % viz_freq == 0 or epoch == epochs - 1 or epoch % checkpoint_freq == 0: - with torch.no_grad(): - self.obj.create_volume( - world_size=self.world_size, - global_rank=self.global_rank, - ray_size=num_samples_per_ray, - ) - pred_full = self.obj.obj - avg_loss = epoch_loss.item() / num_batches - avg_consistency_loss = epoch_consistency_loss.item() / num_batches - avg_tv_loss = epoch_tv_loss.item() / num_batches - avg_z1_loss = epoch_z1_loss.item() / num_batches - shifts, z1, z3 = self.dataset.auxiliary_params.forward() - shifts = shifts.detach().cpu() - z1 = z1.detach().cpu() - z3 = z3.detach().cpu() - metrics = torch.tensor( - [avg_loss, avg_consistency_loss, avg_tv_loss, avg_z1_loss], - device=self.device, - ) - if self.world_size > 1: - dist.all_reduce(metrics, op=dist.ReduceOp.AVG) - avg_loss, avg_consistency_loss, avg_tv_loss, avg_z1_loss = metrics.tolist() - - if hasattr(self, "val_dataloader") and self.val_dataloader is not None: - val_loss = self.validate( - aux_params, num_samples_per_ray, device_type, autocast_dtype, use_amp, N - ) - - if self.global_rank == 0 and (epoch % viz_freq == 0 or epoch == epochs - 1): - with torch.no_grad(): - current_lr = self.schedulers["model"].get_last_lr()[0] - - # Log metrics - self.temp_logger.add_scalar( - f"train/consistency_loss_{consistency_criterion[0] if isinstance(consistency_criterion, tuple) else consistency_criterion}", - avg_consistency_loss, - self.global_epochs, - ) - if hasattr(self, "val_dataloader") and self.val_dataloader is not None: - self.temp_logger.add_scalar( - f"val/consistency_loss_{consistency_criterion[0] if isinstance(consistency_criterion, tuple) else consistency_criterion}", - val_loss, - self.global_epochs, - ) - self.temp_logger.add_scalar("train/z1_loss", avg_z1_loss, self.global_epochs) - # if tv_weight > 0: - self.temp_logger.add_scalar("train/tv_loss", avg_tv_loss, self.global_epochs) - self.temp_logger.add_scalar("train/total_loss", avg_loss, self.global_epochs) - self.temp_logger.add_scalar( - "train/model_grad_norm", model_norm.item(), self.global_epochs - ) - self.temp_logger.add_scalar( - "train/aux_grad_norm", aux_norm.item(), self.global_epochs - ) - self.temp_logger.add_scalar("train/lr", current_lr, self.global_epochs) - self.temp_logger.add_scalar( - "train/num_samples_per_ray", num_samples_per_ray, self.global_epochs - ) - if consistency_criterion[0].lower() == "adaptive_smooth_l1": - self.temp_logger.add_scalar( - "train/beta_2", consistency_loss_fn.beta2.item(), self.global_epochs - ) - fig, axes = plt.subplots(1, 5, figsize=(36, 12)) - axes[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap="turbo", vmin=0) - axes[0].set_title("Sum over Z-axis") - - axes[1].matshow(pred_full[N // 2].cpu().numpy(), cmap="turbo", vmin=0) - axes[1].set_title(f"Slice at Z={N // 2}") - - slice_start = max(0, N // 2 - 5) - slice_end = min(N, N // 2 + 6) - thick_slice = pred_full[slice_start:slice_end].sum(dim=0).cpu().numpy() - axes[2].matshow(thick_slice, cmap="turbo", vmin=0) - axes[2].set_title(f"Thick slice sum (Z={slice_start}:{slice_end - 1})") - - axes[3].matshow(pred_full.sum(dim=1).cpu().numpy(), cmap="turbo", vmin=0) - axes[3].set_title("Sum over Y-axis") - - axes[4].matshow(pred_full.sum(dim=2).cpu().numpy(), cmap="turbo", vmin=0) - axes[4].set_title("Sum over X-axis") - - self.temp_logger.add_figure("train/viz", fig, self.global_epochs, close=True) - - fig, axes = plt.subplots(ncols=3, figsize=(10, 5)) - axes[0].plot(shifts[:, 0].cpu().numpy(), label="Shifts X") - axes[0].plot(shifts[:, 1].cpu().numpy(), label="Shifts Y") - axes[0].legend() - axes[1].plot(z1.cpu().numpy(), label="Z1") - axes[2].plot(z3.cpu().numpy(), label="Z3") - self.temp_logger.add_figure( - "train/auxiliary_params", fig, self.global_epochs, close=True - ) - plt.close(fig) - - if epoch % checkpoint_freq == 0 or self.global_epochs == epochs - 1: - with torch.no_grad(): - if self.global_rank == 0: - save_path = f"{vol_save_path}/volume_epoch_{self.global_epochs:04d}.pt" - torch.save(pred_full.cpu(), save_path) - - self.global_epochs += 1 - - if self.global_rank == 0: - print("Training complete.") - - torch.save(self.dataset.auxiliary_params.forward(), f"{log_path}/auxiliary_params.pt") - # print("Successfully setup DDP and dataloader") - - def validate(self, aux_params, num_samples_per_ray, device_type, autocast_dtype, use_amp, N): - """Validate the model on the validation set.""" - self.obj.model.eval() - aux_params.eval() - - val_loss = 0.0 - num_batches = 0 - - with torch.no_grad(): - for batch in self.val_dataloader: - pixel_i = batch["pixel_i"].float().to(self.device, non_blocking=True) - pixel_j = batch["pixel_j"].float().to(self.device, non_blocking=True) - target_values = batch["target_value"].to(self.device, non_blocking=True) - phis = batch["phi"].to(self.device, non_blocking=True) - projection_indices = batch["projection_idx"].to(self.device, non_blocking=True) - - shifts, z1_params, z3_params = aux_params(None) - batch_shifts = torch.index_select(shifts, 0, projection_indices) - batch_z1 = torch.index_select(z1_params, 0, projection_indices) - batch_z3 = torch.index_select(z3_params, 0, projection_indices) - - with torch.autocast( - device_type=device_type, dtype=autocast_dtype, enabled=use_amp - ): - with torch.no_grad(): - batch_ray_coords = self.create_batch_projection_rays( - pixel_i, pixel_j, N, num_samples_per_ray - ) - - transformed_rays = self.transform_batch_ray_coordinates( - batch_ray_coords, - z1=batch_z1, - x=phis, - z3=batch_z3, - shifts=batch_shifts, - N=N, - sampling_rate=1.0, - ) - - all_coords = transformed_rays.view(-1, 3) - - all_densities = self.obj.forward(all_coords) - - ray_densities = all_densities.view( - len(target_values), num_samples_per_ray - ) # Reshape rays and integarte - step_size = 2.0 / (num_samples_per_ray - 1) - - predicted_values = ray_densities.sum(dim=1) * step_size - - mse_loss = torch.nn.functional.mse_loss(predicted_values, target_values) - - loss = mse_loss - - val_loss += loss.detach() - num_batches += 1 - - avg_val_loss = val_loss / num_batches if num_batches > 0 else 0.0 - - if self.world_size > 1: - val_loss_tensor = avg_val_loss.detach().clone() - dist.all_reduce(val_loss_tensor, op=dist.ReduceOp.AVG) - avg_val_loss = val_loss_tensor.item() - - self.obj.model.train() - aux_params.train() - - return avg_val_loss - - def ad_recon( - self, - optimizer_params: dict, - num_iter: int = 0, - reset: bool = False, - scheduler_params: dict | None = None, - hard_constraints: dict | None = None, - soft_constraints: dict | None = None, - # store_iterations: bool | None = None, - # store_iterations_every: int | None = None, - # autograd: bool = True, - ): - if reset: - self.reset_recon() - - self.hard_constraints = hard_constraints - self.soft_constraints = soft_constraints - - # Make sure everything is in the correct device, might be redundant/cleaner way to do this - self.dataset.to(self.device) - self.volume_obj.to(self.device) - - # Making optimizable parameters into leaf tensors. - self.dataset.shifts = self.dataset.shifts.detach().to(self.device).requires_grad_(True) - self.dataset.z1_angles = ( - self.dataset.z1_angles.detach().to(self.device).requires_grad_(True) - ) - self.dataset.z3_angles = ( - self.dataset.z3_angles.detach().to(self.device).requires_grad_(True) - ) - - if optimizer_params is not None: - self.optimizer_params = optimizer_params - self.set_optimizers() - - if scheduler_params is not None: - self.scheduler_params = scheduler_params - self.set_schedulers(self.scheduler_params, num_iter=num_iter) - - if hard_constraints is not None: - self.volume_obj.hard_constraints = hard_constraints - if soft_constraints is not None: - self.volume_obj.soft_constraints = soft_constraints - - pbar = tqdm(range(num_iter), desc="AD Reconstruction") - - for a0 in pbar: - total_loss = 0.0 - tilt_series_loss = 0.0 - - pred_volume = self.volume_obj.forward() - - for i in range(len(self.dataset.tilt_series)): - forward_projection = self.projection_operator( - vol=pred_volume, - z1=self.dataset.z1_angles[i], - x=self.dataset.tilt_angles[i], - z3=self.dataset.z3_angles[i], - shift_x=self.dataset.shifts[i, 0], - shift_y=self.dataset.shifts[i, 1], - device=self.device, - ) - - tilt_series_loss += torch.nn.functional.mse_loss( - forward_projection, self.dataset.tilt_series[i] - ) - tilt_series_loss /= len(self.dataset.tilt_series) - - total_loss = tilt_series_loss + self.volume_obj.soft_loss - self.loss.append(total_loss.item()) - - total_loss.backward() - - for opt in self.optimizers.values(): - opt.step() - opt.zero_grad() - - if self.schedulers is not None: - for sch in self.schedulers.values(): - if isinstance(sch, torch.optim.lr_scheduler.ReduceLROnPlateau): - sch.step(total_loss) - elif sch is not None: - sch.step() - - pbar.set_description(f"AD Reconstruction | Loss: {total_loss:.4f}") - - if self.logger is not None: - self.logger.log_scalar("loss/total", total_loss.item(), a0) - self.logger.log_scalar("loss/tilt_series", tilt_series_loss.item(), a0) - self.logger.log_scalar( - "loss/soft constraints", self.volume_obj.soft_loss.item(), a0 - ) - - if a0 % self.logger.log_images_every == 0: - self.logger.projection_images( - volume_obj=self.volume_obj, - epoch=a0, - ) - self.logger.tilt_angles_figure(dataset=self.dataset, step=a0) - - self.logger.flush() - - self.ad_recon_vol = self.volume_obj.forward() - - return self - - def reset_recon(self) -> None: - if isinstance(self.volume_obj, ObjectVoxelwise): - self.volume_obj.reset() - - self.ad_recon_vol = None - - # --- Projection Operators ---- - def projection_operator( - self, - vol, - z1, - x, - z3, - shift_x, - shift_y, - device, - ): - projection = ( - rot_ZXZ( - mags=vol.unsqueeze(0), # Add batch dimension - z1=z1, - x=-x, - z3=z3, - device=device, - mode="bilinear", - ) - .squeeze() - .sum(axis=0) - ) - - shifted_projection = differentiable_shift_2d( - image=projection, - shift_x=shift_x, - shift_y=shift_y, - sampling_rate=1.0, # Assuming 1 pixel = 1 physical unit - ) - - return shifted_projection +# import matplotlib.pyplot as plt +# import numpy as np +# import torch +# import torch.distributed as dist +# from torch.nn import SmoothL1Loss +# from torch.utils.tensorboard import SummaryWriter + +# # from torch_radon.radon import ParallelBeam as Radon +# from tqdm.auto import tqdm + +# from quantem.core.ml.loss_functions import ( +# CharbonnierLoss, +# L1Loss, +# LLMSELoss, +# MSELogMSELoss, +# MSELoss, +# ) + +# # Temporary imports for TomographyNERF +# from quantem.tomography.object_models import ObjectINN, ObjectVoxelwise +# from quantem.tomography.tomography_base import TomographyBase +# from quantem.tomography.tomography_conv import TomographyConv +# from quantem.tomography.tomography_ml import TomographyML +# from quantem.tomography.utils import differentiable_shift_2d, gaussian_kernel_1d, rot_ZXZ + + +# # Temporary aux class for TomographyNERF +# # TODO: Maybe put this in INN? +# def get_num_samples_per_ray(N: int, epoch: int): +# """Increase number of samples per ray at specific epochs.""" +# # Exponential schedule + +# epochs = np.linspace(0, 10, 5, dtype=int) +# # schedule = np.linspace(20, N, 5, dtype=int) +# schedule = np.array([20, 100, 150, 200, 200]) +# # schedule = np.array([200, 200, 200, 200, 200]) +# # schedule = np.array([20, 100, 250, 500, 500]) +# # schedule = np.array([500, 500, 500, 500, 500]) +# # schedule = np.array([300, 300, 300, 300, 300]) +# # schedule = np.exp(schedule) +# schedule_warmup = dict[int, int](zip(epochs, schedule)) + +# for epoch_threshold, samples in schedule_warmup.items(): +# if epoch >= epoch_threshold: +# num_samples = samples + +# return num_samples + + +# class Tomography(TomographyConv, TomographyML, TomographyBase, TomographyDDP): +# """ +# Top level class for either using conventional or ML-based reconstruction methods +# for tomography. +# """ + +# def __init__( +# self, +# dataset, +# volume_obj, +# device, +# _token, +# ): +# super().__init__(dataset, volume_obj, device, _token) + +# # TODO: More elegant way of doing this. +# self.global_epochs = 0 +# self.ddp_instantiated = False +# self.pretraining_instantiated = False + +# # --- Reconstruction Method --- + +# def sirt_recon( +# self, +# num_iterations: int = 10, +# inline_alignment: bool = False, +# enforce_positivity: bool = True, +# volume_shape: tuple = None, +# reset: bool = True, +# smoothing_sigma: float = None, +# shrinkage: float = None, +# filter_name: str = "hamming", +# circle: bool = True, +# plot_loss: bool = False, +# ): +# num_angles, num_rows, num_cols = self.dataset.tilt_series.shape +# sirt_tilt_series = self.dataset.tilt_series.clone() +# sirt_tilt_series = sirt_tilt_series.permute(2, 0, 1) + +# hard_constraints = { +# "positivity": enforce_positivity, +# "shrinkage": shrinkage, +# } +# self.volume_obj.hard_constraints = hard_constraints + +# if volume_shape is None: +# volume_shape = (num_rows, num_rows, num_rows) +# else: +# D, H, W = volume_shape + +# if reset: +# self.volume_obj.reset() +# self.loss = [] + +# proj_forward = torch.zeros_like(self.dataset.tilt_series) + +# pbar = tqdm(range(num_iterations), desc="SIRT Reconstruction") + +# if smoothing_sigma is not None: +# gaussian_kernel = gaussian_kernel_1d(smoothing_sigma).to(self.device) +# else: +# gaussian_kernel = None + +# print( +# "Devices", +# sirt_tilt_series.device, +# proj_forward.device, +# self.dataset.tilt_angles.device, +# ) + +# for iter in pbar: +# proj_forward, loss = self._sirt_run_epoch( +# tilt_series=sirt_tilt_series, +# proj_forward=proj_forward, +# angles=self.dataset.tilt_angles, +# inline_alignment=iter > 0 and inline_alignment, +# filter_name=filter_name, +# gaussian_kernel=gaussian_kernel, +# circle=circle, +# ) + +# pbar.set_description(f"SIRT Reconstruction | Loss: {loss.item():.4f}") + +# self.loss.append(loss.item()) + +# self.sirt_recon_vol = self.volume_obj + +# # Permutation due to sinogram ordering. +# self.sirt_recon_vol.obj = self.sirt_recon_vol.obj.permute(1, 2, 0) + +# if plot_loss: +# self.plot_loss() + +# # TODO: ML Recon which has NeRF and AD depending on the object type. +# # TODO: Temporary + +# def create_batch_projection_rays(self, pixel_i, pixel_j, N, num_samples_per_ray): +# """Create projection rays for entire batch simultaneously.""" +# batch_size = len(pixel_i) + +# # Convert all pixels to normalized coordinates +# x_coords = (pixel_j / (N - 1)) * 2 - 1 +# y_coords = (pixel_i / (N - 1)) * 2 - 1 +# # TODO: maybe pixel_j.device? +# # Create z coordinates +# z_coords = torch.linspace(-1, 1, num_samples_per_ray, device=self.device) + +# # Create rays for all pixels: [batch_size, num_samples_per_ray, 3] +# rays = torch.zeros(batch_size, num_samples_per_ray, 3, device=self.device) + +# # Fill coordinates efficiently +# rays[:, :, 0] = x_coords.unsqueeze(1) # x constant per ray +# rays[:, :, 1] = y_coords.unsqueeze(1) # y constant per ray +# rays[:, :, 2] = z_coords.unsqueeze(0) # z varies along ray + +# return rays + +# @torch.compile(mode="reduce-overhead") +# def transform_batch_ray_coordinates(self, rays, z1, x, z3, shifts, N, sampling_rate): +# # Step 1: Apply shifts +# shift_x_norm = (shifts[:, 0:1] * sampling_rate * 2) / (N - 1) +# shift_y_norm = (shifts[:, 1:2] * sampling_rate * 2) / (N - 1) + +# rays_x = rays[:, :, 0] - shift_x_norm +# rays_y = rays[:, :, 1] - shift_y_norm +# rays_z = rays[:, :, 2] + +# # Rotation 1: Z(-z3) +# theta = torch.deg2rad(-z3).view(-1, 1) +# cos_t = torch.cos(theta) +# sin_t = torch.sin(theta) + +# rays_x_rot1 = cos_t * rays_x - sin_t * rays_y +# rays_y_rot1 = sin_t * rays_x + cos_t * rays_y +# rays_z_rot1 = rays_z + +# # Rotation 2: X(x) +# theta = torch.deg2rad(x).view(-1, 1) +# cos_t = torch.cos(theta) +# sin_t = torch.sin(theta) + +# rays_x_rot2 = rays_x_rot1 +# rays_y_rot2 = cos_t * rays_y_rot1 - sin_t * rays_z_rot1 +# rays_z_rot2 = sin_t * rays_y_rot1 + cos_t * rays_z_rot1 + +# # Rotation 3: Z(-z1) +# theta = torch.deg2rad(-z1).view(-1, 1) +# cos_t = torch.cos(theta) +# sin_t = torch.sin(theta) + +# rays_x_final = cos_t * rays_x_rot2 - sin_t * rays_y_rot2 +# rays_y_final = sin_t * rays_x_rot2 + cos_t * rays_y_rot2 +# rays_z_final = rays_z_rot2 + +# # Stack the final result +# transformed_rays = torch.stack([rays_x_final, rays_y_final, rays_z_final], dim=2) + +# return transformed_rays + +# # TODO: Temp logger +# def setup_logger(self, log_path): +# if self.global_rank == 0: +# self.temp_logger = SummaryWriter(log_dir=log_path) +# else: +# self.temp_logger = None + +# def pretrain( +# self, +# # volume_dataset: PretrainVolumeDataset, +# obj: ObjectINN, +# batch_size: int, +# soft_constraints: dict = None, +# log_path: str = None, +# optimizer_params: dict = None, +# epochs: int = 100, +# viz_freq: int = 1, +# consistency_criterion: str = "mse", +# ): +# if not self.pretraining_instantiated: +# self.setup_distributed() +# # self.setup_pretraining_dataloader(volume_dataset, batch_size) +# self.pretraining_instantiated = True +# obj.model = self.build_model(obj._model) +# self.obj = obj + +# if soft_constraints is not None: +# self.obj.soft_constraints = soft_constraints + +# if not hasattr(self, "temp_logger"): +# self.setup_logger(log_path=log_path) + +# if optimizer_params is not None: +# optimizer_params = self.scale_lr(optimizer_params) +# self.optimizer_params = optimizer_params +# self.set_optimizers() +# device_type = self.device.type +# consistency_loss_fn = None +# if consistency_criterion[0].lower() == "mse": +# consistency_loss_fn = MSELoss() +# elif consistency_criterion[0].lower() == "l1": +# consistency_loss_fn = L1Loss() +# elif consistency_criterion[0].lower() == "mse_log": +# consistency_loss_fn = MSELogMSELoss() +# elif consistency_criterion[0].lower() == "llmse": +# consistency_loss_fn = LLMSELoss() +# elif consistency_criterion[0].lower() == "smooth_l1": +# consistency_loss_fn = SmoothL1Loss(beta=consistency_criterion[1]) +# elif consistency_criterion[0].lower() == "adaptive_smooth_l1": +# # consistency_loss_fn = AdaptiveSmoothL1LossDDP( +# # beta_init=consistency_criterion[1], ema_factor=0.99, eps=1e-8 +# # ) +# pass +# elif consistency_criterion[0].lower() == "charbonnier": +# consistency_loss_fn = CharbonnierLoss(epsilon=1e-12, reduction="mean") +# else: +# raise ValueError(f"Invalid consistency criterion: {consistency_criterion}") + +# for epoch in range(epochs): +# if self.pretraining_sampler is not None: +# self.pretraining_sampler.set_epoch(epoch) + +# self.obj.model.train() + +# epoch_loss = 0.0 +# epoch_consistency_loss = 0.0 +# epoch_tv_loss = 0.0 +# num_batches = 0 + +# for batch_idx, batch in enumerate(self.pretraining_dataloader): +# coords = batch["coords"].to(self.device, non_blocking=True) +# target = batch["target"].to(self.device, non_blocking=True) + +# for _, opt in self.optimizers.items(): +# opt.zero_grad() + +# with torch.autocast(device_type=device_type, dtype=torch.bfloat16, enabled=True): +# outputs = self.obj.forward(coords) +# consistency_loss = consistency_loss_fn(outputs, target) +# tv_loss = self.obj.apply_soft_constraints(coords) + +# loss = consistency_loss + tv_loss + +# loss.backward() + +# epoch_loss += loss.detach() +# epoch_consistency_loss += consistency_loss.detach() +# epoch_tv_loss += tv_loss.detach() + +# torch.nn.utils.clip_grad_norm_(self.obj.model.parameters(), max_norm=1.0) + +# for _, opt in self.optimizers.items(): +# opt.step() +# epoch_loss += loss.detach() +# num_batches += 1 + +# if epoch % viz_freq == 0 or epoch == epochs - 1: +# avg_loss = epoch_loss / num_batches +# with torch.no_grad(): +# self.obj.create_volume( +# world_size=self.world_size, +# global_rank=self.global_rank, +# # ray_size=volume_dataset.N, +# ) +# pred_full = self.obj.obj +# loss_tensor = avg_loss.clone().detach() +# avg_loss = loss_tensor.item() +# avg_tv_loss = epoch_tv_loss / num_batches +# avg_consistency_loss = epoch_consistency_loss / num_batches + +# metrics = torch.tensor( +# [avg_loss, avg_consistency_loss, avg_tv_loss], device=self.device +# ) +# if self.world_size > 1: +# dist.all_reduce(metrics, op=dist.ReduceOp.AVG) +# avg_loss, avg_consistency_loss, avg_tv_loss = metrics.tolist() + +# if self.global_rank == 0: +# self.temp_logger.add_scalar("Pretrain/total_loss", avg_loss, epoch) +# self.temp_logger.add_scalar( +# "Pretrain/consistency_loss", avg_consistency_loss, epoch +# ) +# self.temp_logger.add_scalar("Pretrain/tv_loss", avg_tv_loss, epoch) + +# # current_lr = self.schedulers["model"].get_last_lr()[0] +# # self.temp_logger.add_scalar("Pretrain/lr", current_lr, epoch) + +# fig, ax = plt.subplots(ncols=4, figsize=(36, 12)) +# ax[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap="turbo", vmin=0) +# ax[0].set_title("Sum over Z-axis") +# ax[1].matshow( +# # pred_full[volume_dataset.N // 2].cpu().numpy(), cmap="turbo", vmin=0 +# # ) +# # ax[1].set_title(f"Slice at Z={volume_dataset.N // 2}") +# ax[2].matshow(pred_full.sum(dim=1).cpu().numpy(), cmap="turbo", vmin=0) +# ax[2].set_title("Sum over Y-axis") +# ax[3].matshow(pred_full.sum(dim=2).cpu().numpy(), cmap="turbo", vmin=0) +# ax[3].set_title("Sum over X-axis") +# self.temp_logger.add_figure("Pretrain/viz", fig, epoch, close=True) + +# plt.close(fig) +# print( +# f"Epoch [{epoch}/{epochs}] Pretrain Total Loss: {avg_loss:.6f}, Consistency Loss: {avg_consistency_loss:.6f}, TV Loss: {avg_tv_loss:.6f}" +# ) +# self.global_epochs += 1 +# if self.global_rank == 0: +# print(f"Pretraining Completed, saving model weights to {log_path}/model_weights.pth") +# torch.save(self.obj.model.state_dict(), f"{log_path}/model_weights.pth") + +# def recon( +# self, +# obj: ObjectINN, +# batch_size: int, +# num_workers: int = 0, +# epochs=20, +# use_amp=True, +# viz_freq=1, +# checkpoint_freq=5, +# optimizer_params: dict = None, +# scheduler_params: dict = None, +# soft_constraints: dict = None, +# vol_save_path: str = None, # TODO: TEMPORARY +# log_path=None, +# val_fraction: float = 0.0, +# learn_shifts: bool = True, +# # l1_loss: bool = False, +# consistency_criterion: str = "mse", +# model_weights_path: str = None, +# force_cpu: bool = False, +# ): +# if not self.ddp_instantiated: +# if not self.pretraining_instantiated: +# self.setup_distributed() +# if model_weights_path is not None: +# print(f"Loading model weights from {model_weights_path}") +# state_dict = torch.load(model_weights_path, map_location="cpu") + +# # Handle DataParallel/DDP checkpoints +# if any(k.startswith("module.") for k in state_dict.keys()): +# new_state_dict = { +# k.replace("module.", ""): v for k, v in state_dict.items() +# } +# else: +# new_state_dict = state_dict + +# obj.model.load_state_dict(new_state_dict) +# print("Model weights loaded successfully") +# obj.model = self.build_model(obj._model) +# self.obj = obj + +# self.setup_dataloader(self.dataset, batch_size, num_workers, val_fraction) +# self.ddp_instantiated = True + +# if soft_constraints is not None: +# self.obj.soft_constraints = soft_constraints + +# if not hasattr(self, "temp_logger"): +# self.setup_logger(log_path=log_path) + +# zero_tilt_idx = torch.argmin(torch.abs(self.dataset.tilt_angles)).item() + +# if self.global_rank == 0: +# print( +# f"Using projection {zero_tilt_idx} (angle={self.dataset.tilt_angles[zero_tilt_idx]:.2f}°) as reference" +# ) +# print(f"Using consistency criterion: {consistency_criterion}") + +# # Auxiliary params setup +# self.dataset.setup_auxiliary_params(zero_tilt_idx, self.device, learn_shifts) +# aux_params = self.dataset.auxiliary_params +# # Scaling learning rates to account for distributed training +# if optimizer_params is not None: +# optimizer_params = self.scale_lr(optimizer_params) +# self.optimizer_params = optimizer_params +# self.set_optimizers() + +# if scheduler_params is not None: +# self.scheduler_params = scheduler_params +# self.set_schedulers(self.scheduler_params, num_iter=epochs) + +# aux_norm = torch.tensor(0.0, device=self.device) +# model_norm = torch.tensor(0.0, device=self.device) + +# for _, opt in self.optimizers.items(): +# opt.zero_grad() + +# N = max(self.dataset.dims) + +# device_type = self.device.type +# autocast_dtype = torch.bfloat16 if use_amp else None + +# for epoch in range(epochs): +# num_samples_per_ray = get_num_samples_per_ray(N=N, epoch=self.global_epochs) + +# # num_samples_per_ray = get_num_samples_per_ray(epoch) +# # Log the change if it happens +# if self.global_rank == 0: +# print(f"Epoch {epoch}: num_samples_per_ray = {num_samples_per_ray}") + +# if self.global_rank == 0 and self.global_epochs > 0: +# prev_samples = get_num_samples_per_ray(N=N, epoch=self.global_epochs - 1) + +# if num_samples_per_ray != prev_samples: +# print( +# f"Epoch {epoch}: Changing num_samples_per_ray from {prev_samples} to {num_samples_per_ray}" +# ) + +# if self.sampler is not None: +# self.sampler.set_epoch(epoch) + +# epoch_loss = 0.0 +# epoch_consistency_loss = 0.0 +# epoch_tv_loss = 0.0 +# epoch_z1_loss = 0.0 + +# num_batches = 0 +# consistency_loss_fn = None +# if consistency_criterion[0].lower() == "mse": +# consistency_loss_fn = MSELoss() +# elif consistency_criterion[0].lower() == "l1": +# consistency_loss_fn = L1Loss() +# elif consistency_criterion[0].lower() == "mse_log": +# consistency_loss_fn = MSELogMSELoss() +# elif consistency_criterion[0].lower() == "llmse": +# consistency_loss_fn = LLMSELoss() +# elif consistency_criterion[0].lower() == "smooth_l1": +# consistency_loss_fn = SmoothL1Loss(beta=consistency_criterion[1]) +# elif consistency_criterion[0].lower() == "adaptive_smooth_l1": +# # consistency_loss_fn = AdaptiveSmoothL1LossDDP( +# # beta_init=consistency_criterion[1], ema_factor=0.99, eps=1e-8 +# # ) +# pass +# elif consistency_criterion[0].lower() == "charbonnier": +# consistency_loss_fn = CharbonnierLoss(epsilon=1e-12, reduction="mean") +# else: +# raise ValueError(f"Invalid consistency criterion: {consistency_criterion}") + +# for batch_idx, batch in enumerate(self.dataloader): +# projection_indices = batch["projection_idx"] + +# pixel_i = batch["pixel_i"].float().to(self.device, non_blocking=True) +# pixel_j = batch["pixel_j"].float().to(self.device, non_blocking=True) +# target_values = batch["target_value"].to(self.device, non_blocking=True) +# phis = batch["phi"].to(self.device, non_blocking=True) +# projection_indices = batch["projection_idx"].to(self.device, non_blocking=True) + +# shifts, z1_params, z3_params = aux_params(None) +# batch_shifts = torch.index_select(shifts, 0, projection_indices) +# batch_z1 = torch.index_select(z1_params, 0, projection_indices) +# batch_z3 = torch.index_select(z3_params, 0, projection_indices) + +# with torch.autocast( +# device_type=device_type, dtype=autocast_dtype, enabled=use_amp +# ): +# with torch.no_grad(): +# batch_ray_coords = self.create_batch_projection_rays( +# pixel_i, pixel_j, N, num_samples_per_ray +# ) + +# # TODO: .forward passing z1, x, z3, shifts, N, sampling_rate +# transformed_rays = self.transform_batch_ray_coordinates( +# batch_ray_coords, +# z1=batch_z1, +# x=phis, +# z3=batch_z3, +# shifts=batch_shifts, +# N=N, +# sampling_rate=1.0, +# ) + +# all_coords = transformed_rays.view(-1, 3) + +# all_densities = self.obj.forward(all_coords) + +# tv_loss = self.obj.apply_soft_constraints(all_coords) +# ray_densities = all_densities.view( +# len(target_values), num_samples_per_ray +# ) # Reshape rays and integarte +# step_size = 2.0 / (num_samples_per_ray - 1) + +# predicted_values = ray_densities.sum(dim=1) * step_size + +# consistency_loss = consistency_loss_fn(predicted_values, target_values) +# tv_loss_z1 = torch.tensor(0.0, device=self.device) + +# loss = consistency_loss + tv_loss + tv_loss_z1 + +# loss.backward() + +# epoch_loss += loss.detach() +# epoch_consistency_loss += consistency_loss.detach() +# epoch_tv_loss += tv_loss.detach() +# epoch_z1_loss += tv_loss_z1.detach() +# num_batches += 1 + +# for key, opt in self.optimizers.items(): +# if key == "model": +# model_norm = torch.nn.utils.clip_grad_norm_( +# self.obj.model.parameters(), max_norm=1 +# ) +# elif key == "aux_params": +# aux_norm = torch.nn.utils.clip_grad_norm_( +# self.dataset.auxiliary_params.parameters(), max_norm=1 +# ) +# opt.step() +# opt.zero_grad() + +# for key, sched in self.schedulers.items(): +# if isinstance(sched, torch.optim.lr_scheduler.ReduceLROnPlateau): +# sched.step(epoch_loss) +# else: +# sched.step() + +# if epoch % viz_freq == 0 or epoch == epochs - 1 or epoch % checkpoint_freq == 0: +# with torch.no_grad(): +# self.obj.create_volume( +# world_size=self.world_size, +# global_rank=self.global_rank, +# ray_size=num_samples_per_ray, +# ) +# pred_full = self.obj.obj +# avg_loss = epoch_loss.item() / num_batches +# avg_consistency_loss = epoch_consistency_loss.item() / num_batches +# avg_tv_loss = epoch_tv_loss.item() / num_batches +# avg_z1_loss = epoch_z1_loss.item() / num_batches +# shifts, z1, z3 = self.dataset.auxiliary_params.forward() +# shifts = shifts.detach().cpu() +# z1 = z1.detach().cpu() +# z3 = z3.detach().cpu() +# metrics = torch.tensor( +# [avg_loss, avg_consistency_loss, avg_tv_loss, avg_z1_loss], +# device=self.device, +# ) +# if self.world_size > 1: +# dist.all_reduce(metrics, op=dist.ReduceOp.AVG) +# avg_loss, avg_consistency_loss, avg_tv_loss, avg_z1_loss = metrics.tolist() + +# if hasattr(self, "val_dataloader") and self.val_dataloader is not None: +# val_loss = self.validate( +# aux_params, num_samples_per_ray, device_type, autocast_dtype, use_amp, N +# ) + +# if self.global_rank == 0 and (epoch % viz_freq == 0 or epoch == epochs - 1): +# with torch.no_grad(): +# current_lr = self.schedulers["model"].get_last_lr()[0] + +# # Log metrics +# self.temp_logger.add_scalar( +# f"train/consistency_loss_{consistency_criterion[0] if isinstance(consistency_criterion, tuple) else consistency_criterion}", +# avg_consistency_loss, +# self.global_epochs, +# ) +# if hasattr(self, "val_dataloader") and self.val_dataloader is not None: +# self.temp_logger.add_scalar( +# f"val/consistency_loss_{consistency_criterion[0] if isinstance(consistency_criterion, tuple) else consistency_criterion}", +# val_loss, +# self.global_epochs, +# ) +# self.temp_logger.add_scalar("train/z1_loss", avg_z1_loss, self.global_epochs) +# # if tv_weight > 0: +# self.temp_logger.add_scalar("train/tv_loss", avg_tv_loss, self.global_epochs) +# self.temp_logger.add_scalar("train/total_loss", avg_loss, self.global_epochs) +# self.temp_logger.add_scalar( +# "train/model_grad_norm", model_norm.item(), self.global_epochs +# ) +# self.temp_logger.add_scalar( +# "train/aux_grad_norm", aux_norm.item(), self.global_epochs +# ) +# self.temp_logger.add_scalar("train/lr", current_lr, self.global_epochs) +# self.temp_logger.add_scalar( +# "train/num_samples_per_ray", num_samples_per_ray, self.global_epochs +# ) +# if consistency_criterion[0].lower() == "adaptive_smooth_l1": +# self.temp_logger.add_scalar( +# "train/beta_2", consistency_loss_fn.beta2.item(), self.global_epochs +# ) +# fig, axes = plt.subplots(1, 5, figsize=(36, 12)) +# axes[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap="turbo", vmin=0) +# axes[0].set_title("Sum over Z-axis") + +# axes[1].matshow(pred_full[N // 2].cpu().numpy(), cmap="turbo", vmin=0) +# axes[1].set_title(f"Slice at Z={N // 2}") + +# slice_start = max(0, N // 2 - 5) +# slice_end = min(N, N // 2 + 6) +# thick_slice = pred_full[slice_start:slice_end].sum(dim=0).cpu().numpy() +# axes[2].matshow(thick_slice, cmap="turbo", vmin=0) +# axes[2].set_title(f"Thick slice sum (Z={slice_start}:{slice_end - 1})") + +# axes[3].matshow(pred_full.sum(dim=1).cpu().numpy(), cmap="turbo", vmin=0) +# axes[3].set_title("Sum over Y-axis") + +# axes[4].matshow(pred_full.sum(dim=2).cpu().numpy(), cmap="turbo", vmin=0) +# axes[4].set_title("Sum over X-axis") + +# self.temp_logger.add_figure("train/viz", fig, self.global_epochs, close=True) + +# fig, axes = plt.subplots(ncols=3, figsize=(10, 5)) +# axes[0].plot(shifts[:, 0].cpu().numpy(), label="Shifts X") +# axes[0].plot(shifts[:, 1].cpu().numpy(), label="Shifts Y") +# axes[0].legend() +# axes[1].plot(z1.cpu().numpy(), label="Z1") +# axes[2].plot(z3.cpu().numpy(), label="Z3") +# self.temp_logger.add_figure( +# "train/auxiliary_params", fig, self.global_epochs, close=True +# ) +# plt.close(fig) + +# if epoch % checkpoint_freq == 0 or self.global_epochs == epochs - 1: +# with torch.no_grad(): +# if self.global_rank == 0: +# save_path = f"{vol_save_path}/volume_epoch_{self.global_epochs:04d}.pt" +# torch.save(pred_full.cpu(), save_path) + +# self.global_epochs += 1 + +# if self.global_rank == 0: +# print("Training complete.") + +# torch.save(self.dataset.auxiliary_params.forward(), f"{log_path}/auxiliary_params.pt") +# # print("Successfully setup DDP and dataloader") + +# def validate(self, aux_params, num_samples_per_ray, device_type, autocast_dtype, use_amp, N): +# """Validate the model on the validation set.""" +# self.obj.model.eval() +# aux_params.eval() + +# val_loss = 0.0 +# num_batches = 0 + +# with torch.no_grad(): +# for batch in self.val_dataloader: +# pixel_i = batch["pixel_i"].float().to(self.device, non_blocking=True) +# pixel_j = batch["pixel_j"].float().to(self.device, non_blocking=True) +# target_values = batch["target_value"].to(self.device, non_blocking=True) +# phis = batch["phi"].to(self.device, non_blocking=True) +# projection_indices = batch["projection_idx"].to(self.device, non_blocking=True) + +# shifts, z1_params, z3_params = aux_params(None) +# batch_shifts = torch.index_select(shifts, 0, projection_indices) +# batch_z1 = torch.index_select(z1_params, 0, projection_indices) +# batch_z3 = torch.index_select(z3_params, 0, projection_indices) + +# with torch.autocast( +# device_type=device_type, dtype=autocast_dtype, enabled=use_amp +# ): +# with torch.no_grad(): +# batch_ray_coords = self.create_batch_projection_rays( +# pixel_i, pixel_j, N, num_samples_per_ray +# ) + +# transformed_rays = self.transform_batch_ray_coordinates( +# batch_ray_coords, +# z1=batch_z1, +# x=phis, +# z3=batch_z3, +# shifts=batch_shifts, +# N=N, +# sampling_rate=1.0, +# ) + +# all_coords = transformed_rays.view(-1, 3) + +# all_densities = self.obj.forward(all_coords) + +# ray_densities = all_densities.view( +# len(target_values), num_samples_per_ray +# ) # Reshape rays and integarte +# step_size = 2.0 / (num_samples_per_ray - 1) + +# predicted_values = ray_densities.sum(dim=1) * step_size + +# mse_loss = torch.nn.functional.mse_loss(predicted_values, target_values) + +# loss = mse_loss + +# val_loss += loss.detach() +# num_batches += 1 + +# avg_val_loss = val_loss / num_batches if num_batches > 0 else 0.0 + +# if self.world_size > 1: +# val_loss_tensor = avg_val_loss.detach().clone() +# dist.all_reduce(val_loss_tensor, op=dist.ReduceOp.AVG) +# avg_val_loss = val_loss_tensor.item() + +# self.obj.model.train() +# aux_params.train() + +# return avg_val_loss + +# def ad_recon( +# self, +# optimizer_params: dict, +# num_iter: int = 0, +# reset: bool = False, +# scheduler_params: dict | None = None, +# hard_constraints: dict | None = None, +# soft_constraints: dict | None = None, +# # store_iterations: bool | None = None, +# # store_iterations_every: int | None = None, +# # autograd: bool = True, +# ): +# if reset: +# self.reset_recon() + +# self.hard_constraints = hard_constraints +# self.soft_constraints = soft_constraints + +# # Make sure everything is in the correct device, might be redundant/cleaner way to do this +# self.dataset.to(self.device) +# self.volume_obj.to(self.device) + +# # Making optimizable parameters into leaf tensors. +# self.dataset.shifts = self.dataset.shifts.detach().to(self.device).requires_grad_(True) +# self.dataset.z1_angles = ( +# self.dataset.z1_angles.detach().to(self.device).requires_grad_(True) +# ) +# self.dataset.z3_angles = ( +# self.dataset.z3_angles.detach().to(self.device).requires_grad_(True) +# ) + +# if optimizer_params is not None: +# self.optimizer_params = optimizer_params +# self.set_optimizers() + +# if scheduler_params is not None: +# self.scheduler_params = scheduler_params +# self.set_schedulers(self.scheduler_params, num_iter=num_iter) + +# if hard_constraints is not None: +# self.volume_obj.hard_constraints = hard_constraints +# if soft_constraints is not None: +# self.volume_obj.soft_constraints = soft_constraints + +# pbar = tqdm(range(num_iter), desc="AD Reconstruction") + +# for a0 in pbar: +# total_loss = 0.0 +# tilt_series_loss = 0.0 + +# pred_volume = self.volume_obj.forward() + +# for i in range(len(self.dataset.tilt_series)): +# forward_projection = self.projection_operator( +# vol=pred_volume, +# z1=self.dataset.z1_angles[i], +# x=self.dataset.tilt_angles[i], +# z3=self.dataset.z3_angles[i], +# shift_x=self.dataset.shifts[i, 0], +# shift_y=self.dataset.shifts[i, 1], +# device=self.device, +# ) + +# tilt_series_loss += torch.nn.functional.mse_loss( +# forward_projection, self.dataset.tilt_series[i] +# ) +# tilt_series_loss /= len(self.dataset.tilt_series) + +# total_loss = tilt_series_loss + self.volume_obj.soft_loss +# self.loss.append(total_loss.item()) + +# total_loss.backward() + +# for opt in self.optimizers.values(): +# opt.step() +# opt.zero_grad() + +# if self.schedulers is not None: +# for sch in self.schedulers.values(): +# if isinstance(sch, torch.optim.lr_scheduler.ReduceLROnPlateau): +# sch.step(total_loss) +# elif sch is not None: +# sch.step() + +# pbar.set_description(f"AD Reconstruction | Loss: {total_loss:.4f}") + +# if self.logger is not None: +# self.logger.log_scalar("loss/total", total_loss.item(), a0) +# self.logger.log_scalar("loss/tilt_series", tilt_series_loss.item(), a0) +# self.logger.log_scalar( +# "loss/soft constraints", self.volume_obj.soft_loss.item(), a0 +# ) + +# if a0 % self.logger.log_images_every == 0: +# self.logger.projection_images( +# volume_obj=self.volume_obj, +# epoch=a0, +# ) +# self.logger.tilt_angles_figure(dataset=self.dataset, step=a0) + +# self.logger.flush() + +# self.ad_recon_vol = self.volume_obj.forward() + +# return self + +# def reset_recon(self) -> None: +# if isinstance(self.volume_obj, ObjectVoxelwise): +# self.volume_obj.reset() + +# self.ad_recon_vol = None + +# # --- Projection Operators ---- +# def projection_operator( +# self, +# vol, +# z1, +# x, +# z3, +# shift_x, +# shift_y, +# device, +# ): +# projection = ( +# rot_ZXZ( +# mags=vol.unsqueeze(0), # Add batch dimension +# z1=z1, +# x=-x, +# z3=z3, +# device=device, +# mode="bilinear", +# ) +# .squeeze() +# .sum(axis=0) +# ) + +# shifted_projection = differentiable_shift_2d( +# image=projection, +# shift_x=shift_x, +# shift_y=shift_y, +# sampling_rate=1.0, # Assuming 1 pixel = 1 physical unit +# ) + +# return shifted_projection From 4bda57fbad9411c9ef0a419a249998cbae336448 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Thu, 22 Jan 2026 17:02:39 -0800 Subject: [PATCH 045/335] Some device switching bugs that need to be addressed. --- src/quantem/core/ml/ddp.py | 14 +- src/quantem/tomography/dataset_models.py | 243 +++++++++++++++++++--- src/quantem/tomography/object_models.py | 44 ++-- src/quantem/tomography/tomography.py | 85 +++++++- src/quantem/tomography/tomography_base.py | 33 ++- 5 files changed, 350 insertions(+), 69 deletions(-) diff --git a/src/quantem/core/ml/ddp.py b/src/quantem/core/ml/ddp.py index 7b42a641..0d38e67f 100644 --- a/src/quantem/core/ml/ddp.py +++ b/src/quantem/core/ml/ddp.py @@ -34,25 +34,27 @@ def setup_distributed(self, device: str | None = None): self.local_rank = int(os.environ["LOCAL_RANK"]) torch.cuda.set_device(self.local_rank) - self.device = torch.device("cuda", self.local_rank) + device = torch.device("cuda", self.local_rank) else: self.world_size = 1 self.global_rank = 0 self.local_rank = 0 if torch.cuda.is_available(): - self.device = torch.device("cuda:0" if device is None else device) - torch.cuda.set_device(self.device.index) + device = torch.device("cuda:0" if device is None else device) + torch.cuda.set_device(device.index) print("Single GPU training") else: - self.device = torch.device("cpu") + device = torch.device("cpu") print("CPU training") - if self.device.type == "cuda": + if device.type == "cuda": torch.backends.cudnn.benchmark = True torch.backends.cuda.matmul.allow_tf32 = True torch.backends.cudnn.allow_tf32 = True + self.device = device + def setup_dataloader( self, dataset: Dataset, @@ -95,7 +97,7 @@ def setup_dataloader( print(f" Global batch size: {batch_size * self.world_size}") print(f" Train batches per GPU per epoch: {len(train_dataloader)}") - return train_dataloader + return train_dataloader, train_sampler def build_model( self, diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index 656520da..e5b0d009 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -1,6 +1,6 @@ from abc import abstractmethod from dataclasses import dataclass -from typing import Any, Literal +from typing import Any import numpy as np import torch @@ -22,8 +22,9 @@ class DatasetValue: target: torch.Tensor tilt_angle: int | float pixel_loc: tuple[int, int] | None = None # Only for INRDataset + projection_idx: int | None = None # Only for INRDataset pose: tuple[torch.nn.Parameter, torch.nn.Parameter, torch.nn.Parameter] | None = ( - None # If there is pose optimization. + None # If there is pose optimization. # Pose is tuple (shifts, z1, z3) ) @@ -58,8 +59,6 @@ def __init__( "The number of tilt projections should be in the first dimension of the dataset." ) - self.volume_size = (int(max(tilt_stack.shape)), tilt_stack.shape[1], tilt_stack.shape[2]) - # TODO: Maybe have the validation in here too. max_val = np.quantile(tilt_stack, 0.95) if type(tilt_stack) is not torch.Tensor: @@ -79,8 +78,33 @@ def __init__( self._reference_tilt_angle_idx = torch.argmin(torch.abs(self.tilt_angles)) # TODO: Implement AuxParams from old tomography_dataset.py here. - self.z1_param = torch + # TODO: The parameters won't be initialized unless .to(device) is called. + self._z1_angles = torch.zeros(self.learnable_tilts) + self._z3_angles = torch.zeros(self.learnable_tilts) + self._shifts = torch.zeros(self.learnable_tilts, 2) + + # Fixed zeros for reference tilt + self._z1_ref = torch.zeros(1) + self._z3_ref = torch.zeros(1) + self._shifts_ref = torch.zeros(1, 2) + # --- Class methods --- + @classmethod + def from_data( + cls, + tilt_stack: Dataset3d | NDArray | torch.Tensor, + tilt_angles: NDArray | torch.Tensor, + learn_pose: bool = False, + ): + return cls(tilt_stack=tilt_stack, tilt_angles=tilt_angles, learn_pose=learn_pose) + + # --- Optimization Parameters --- + # @property + # def params(self): + # # TODO: Need to double check if this is correct way, also need to implement get_optimization_parameters @Arthur!! + # return self.parameters() + + # --- Forward pass --- @abstractmethod def forward( self, @@ -92,6 +116,30 @@ def forward( raise NotImplementedError("This method should be implemented in subclasses.") # --- Properties --- + @property + def tilt_stack(self) -> torch.Tensor: + return self._tilt_stack + + @tilt_stack.setter + def tilt_stack(self, tilt_stack: torch.Tensor): + self._tilt_stack = tilt_stack + + @property + def tilt_angles(self) -> torch.Tensor: + return self._tilt_angles + + @tilt_angles.setter + def tilt_angles(self, tilt_angles: torch.Tensor): + self._tilt_angles = tilt_angles + + @property + def learn_pose(self) -> bool: + return self._learn_pose + + @learn_pose.setter + def learn_pose(self, learn_pose: bool): + self._learn_pose = learn_pose + @property def reference_tilt_idx(self) -> int: return self._reference_tilt_angle_idx @@ -117,15 +165,48 @@ def params(self) -> dict[str, torch.nn.Parameter]: """ raise NotImplementedError("This method should be implemented in subclasses.") + @property + def z1_params(self) -> torch.nn.Parameter: + return self._z1_params + + @z1_params.setter + def z1_params(self, z1_angles: torch.Tensor, device: str): + self._z1_params = nn.Parameter(z1_angles.to(device)) + + @property + def z3_params(self) -> torch.nn.Parameter: + return self._z3_params + + @z3_params.setter + def z3_params(self, z3_angles: torch.Tensor, device: str): + self._z3_params = nn.Parameter(z3_angles.to(device)) + + @property + def shifts_params(self) -> torch.nn.Parameter: + return self._shifts_params + + @shifts_params.setter + def shifts_params(self, shifts: torch.Tensor, device: str): + self._shifts_params = nn.Parameter(shifts.to(device)) + # --- Helper Functions --- def to(self, device: str): + """ + Moves the dataset to the device, and also insantiates the aux params to the device. + """ self.tilt_stack = self.tilt_stack.to(device) self.tilt_angles = self.tilt_angles.to(device) + self._z1_params = nn.Parameter(self._z1_angles.to(device)) + self._z3_params = nn.Parameter(self._z3_angles.to(device)) + self._shifts_params = nn.Parameter(self._shifts.to(device)) + + self._z1_ref = self._z1_ref.to(device) + self._z3_ref = self._z3_ref.to(device) + self._shifts_ref = self._shifts_ref.to(device) + -class TomographyPixDataset( - TomographyDatasetBase -): # Dataset): TODO: Does this need to be a dataset? +class TomographyPixDataset(TomographyDatasetBase): """ Dataset class for pixel-based tomography, i.e AD, SIRT, WBP, etc... @@ -160,15 +241,6 @@ def forward( pixel_loc=None, ) - @classmethod - def from_data( - cls, - tilt_stack: Dataset3d | NDArray | torch.Tensor, - tilt_angles: NDArray | torch.Tensor, - learn_pose: bool = False, - ): - return cls(tilt_stack=tilt_stack, tilt_angles=tilt_angles, learn_pose=learn_pose) - class TomographyINRDataset(TomographyDatasetBase, Dataset): """ @@ -190,15 +262,137 @@ def __init__( ): super().__init__(tilt_stack, tilt_angles, learn_pose, token) - self.z1_param = torch.nn.Parameter(torch.zeros(self.learnable_tilts)) - + # --- Forward Pass w/ Params Method for OptimizerMixin --- def forward(self, dummy_input: Any = None): """ Forward pass for INR-based tomography. In the forward pass, the only parameters that are passed will be the shifts, z1 and z3 Euler angles. """ - pass + first_half_shifts = self.shifts_params[: self.reference_tilt_idx] + second_half_shifts = self.shifts_params[self.reference_tilt_idx :] + shifts = torch.cat([first_half_shifts, self._shifts_ref, second_half_shifts], dim=0) + + first_half_z1 = self.z1_params[: self.reference_tilt_idx] + second_half_z1 = self.z1_params[self.reference_tilt_idx :] + z1 = torch.cat([first_half_z1, self._z1_ref, second_half_z1], dim=0) + + first_half_z3 = self.z3_params[: self.reference_tilt_idx] + second_half_z3 = self.z3_params[self.reference_tilt_idx :] + z3 = torch.cat([first_half_z3, self._z3_ref, second_half_z3], dim=0) + + return DatasetValue( + target=None, + tilt_angle=None, + pixel_loc=None, + pose=(shifts, z1, z3), + ) + + @property + def params(self): + return self.parameters() + + def get_coords( + self, batch: dict[str, torch.Tensor], N: int, num_samples_per_ray: int + ) -> torch.Tensor: + pixel_i = batch["pixel_i"].float().to(self.device, non_blocking=True) + pixel_j = batch["pixel_j"].float().to(self.device, non_blocking=True) + # target_values = batch["target_value"].to(self.device, non_blocking=True) + phis = batch["phi"].to(self.device, non_blocking=True) + projection_indices = batch["projection_idx"].to(self.device, non_blocking=True) + + with torch.no_grad(): + batch_ray_coords = self.create_batch_rays(pixel_i, pixel_j, N, num_samples_per_ray) + + shifts, z1_params, z3_params = self.forward(None) + batch_shifts = torch.index_select(shifts, 0, projection_indices) + batch_z1 = torch.index_select(z1_params, 0, projection_indices) + batch_z3 = torch.index_select(z3_params, 0, projection_indices) + + transformed_rays = self.transform_batch_rays( + batch_ray_coords, + z1=batch_z1, + x=phis, + z3=batch_z3, + shifts=batch_shifts, + N=N, + sampling_rate=1.0, + ) + all_coords = transformed_rays.view(-1, 3) + return all_coords + + @staticmethod + def create_batch_rays( + pixel_i: torch.Tensor, pixel_j: torch.Tensor, N: int, num_samples_per_ray: int + ) -> torch.Tensor: + batch_size = len(pixel_i) + x_coords = (pixel_j / (N - 1)) * 2 - 1 + y_coords = (pixel_i / (N - 1)) * 2 - 1 + z_coords = torch.linspace(-1, 1, num_samples_per_ray, device=pixel_i.device) + + rays = torch.zeros(batch_size, num_samples_per_ray, 3, device=pixel_i.device) + + rays[:, :, 0] = x_coords.unsqueeze(1) + rays[:, :, 1] = y_coords.unsqueeze(1) + rays[:, :, 2] = z_coords.unsqueeze(0) + + return rays + + @staticmethod + @torch.compile(mode="reduce-overhead") + def transform_batch_rays( + rays: torch.Tensor, + z1: torch.Tensor, + x: torch.Tensor, + z3: torch.Tensor, + shifts: torch.Tensor, + N: int, + sampling_rate: float, + ) -> torch.Tensor: + shift_x_norm = (shifts[:, 0:1] * sampling_rate * 2) / (N - 1) + shift_y_norm = (shifts[:, 1:2] * sampling_rate * 2) / (N - 1) + + rays_x = rays[:, :, 0] - shift_x_norm + rays_y = rays[:, :, 1] - shift_y_norm + rays_z = rays[:, :, 2] + + theta = torch.deg2rad(-z3).view(-1, 1) + cos_t = torch.cos(theta) + sin_t = torch.sin(theta) + + rays_x_rot1 = cos_t * rays_x - sin_t * rays_y + rays_y_rot1 = sin_t * rays_x + cos_t * rays_y + rays_z_rot1 = rays_z + + theta = torch.deg2rad(x).view(-1, 1) + cos_t = torch.cos(theta) + sin_t = torch.sin(theta) + + rays_x_rot2 = rays_x_rot1 + rays_y_rot2 = cos_t * rays_y_rot1 - sin_t * rays_z_rot1 + rays_z_rot2 = sin_t * rays_y_rot1 + cos_t * rays_z_rot1 + + theta = torch.deg2rad(-z1).view(-1, 1) + cos_t = torch.cos(theta) + sin_t = torch.sin(theta) + + rays_x_final = cos_t * rays_x_rot2 - sin_t * rays_y_rot2 + rays_y_final = sin_t * rays_x_rot2 + cos_t * rays_y_rot2 + rays_z_final = rays_z_rot2 + + transformed_rays = torch.stack([rays_x_final, rays_y_final, rays_z_final], dim=2) + + return transformed_rays + + @staticmethod + def integrate_rays(rays: torch.Tensor, num_samples_per_ray: int) -> torch.Tensor: + step_size = 2.0 / (num_samples_per_ray - 1) + + predicted_values = rays.sum(dim=1) * step_size + + return predicted_values + + # --- Torch Dataset Methods --- def __getitem__( self, idx: int, @@ -206,7 +400,6 @@ def __getitem__( """ Gets the item for INR i.e, the project index, pixel value at (i, j), and the tilt angle. """ - pass actual_idx = idx @@ -219,6 +412,7 @@ def __getitem__( return DatasetValue( target=self.tilt_stack[projection_idx, pixel_i, pixel_j], tilt_angle=self.tilt_angles[projection_idx], + projection_idx=projection_idx, pixel_loc=(pixel_i, pixel_j), ) @@ -231,13 +425,6 @@ def __len__( return self._total_pixels - @property - def mode(self) -> Literal["train", "val"]: - """ - Returns the mode of the dataset. - """ - return self._mode - class TomographyINRPretrainDataset(Dataset): """ diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index d4d8aab1..f35aae77 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -281,16 +281,12 @@ def to(self, device: str): class ObjectINR(ObjectConstraints, DDPMixin): - """ - Object model for INR objects. - """ - def __init__( self, - model: nn.Module, volume_shape: tuple[int, int, int], device: str = "cpu", rng: np.random.Generator | int | None = None, + model: nn.Module | None = None, ): super().__init__( shape=volume_shape, @@ -300,26 +296,30 @@ def __init__( ) self._pretrain_losses = [] self._pretrain_lrs = [] + self.device = device + + # Register the network submodule (important: real nn.Module attribute) + if model is not None: + self.setup_distributed(device=device) + self._model = self.build_model(model) @classmethod def from_model( cls, - model: "nn.Module", + model: nn.Module, volume_shape: tuple[int, int, int], - device: str = None, # Have to make device a requirement here in distinguishing GPU vs CPU training + device: str = "cpu", rng: np.random.Generator | int | None = None, ): obj_model = cls( - model=model, volume_shape=volume_shape, device=device, rng=rng, + model=model, # ✅ build/register in __init__ ) - # Initialize DDP params and build the model. TODO: Not sure if this is the best way to do this? But to pretrain we need the model to be wrapped in DDP. obj_model.setup_distributed(device=device) - obj_model._model = obj_model.build_model(model) - + obj_model.to(device) return obj_model # --- Properties --- @@ -331,15 +331,15 @@ def model(self) -> "nn.Module": """ return self._model - @model.setter - def model(self, model: "nn.Module"): - """ - This doesn't work -- can't have setters for torch sub modules - https://github.com/pytorch/pytorch/issues/52664 + # @model.setter + # def model(self, model: "nn.Module"): + # """ + # This doesn't work -- can't have setters for torch sub modules + # https://github.com/pytorch/pytorch/issues/52664 - For now, upon initialization private variable `._model` is set to the built model. - """ - raise RuntimeError("\n\n\nsetting model, this shouldn't be reachable???\n\n\n") + # For now, upon initialization private variable `._model` is set to the built model. + # """ + # raise RuntimeError("\n\n\nsetting model, this shouldn't be reachable???\n\n\n") @property def params(self): @@ -428,7 +428,7 @@ def pretrain( pretrain_dataset is not None ): # Need to make a check if there's already a pretrain dataset to not go through with the setup again. self.pretrain_dataset = pretrain_dataset - self.pretraining_dataloader = self.setup_dataloader( + self.pretraining_dataloader, self.pretraining_sampler = self.setup_dataloader( pretrain_dataset, batch_size, num_workers=num_workers ) @@ -587,5 +587,9 @@ def get_tv_loss( tv_loss += self.constraints.tv_vol * grad_norm.mean() return tv_loss + def to(self, device: str): + # self._model = self._model.to(device) + self._obj = self._obj.to(device) + ObjectModelType = ObjectPixelated | ObjectINR diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 97dd2bf2..e9f45a20 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -4,6 +4,7 @@ import torch from tqdm.auto import tqdm +from quantem.core.ml.ddp import DDPMixin from quantem.tomography.dataset_models import DatasetModelType from quantem.tomography.logger_tomography import LoggerTomography from quantem.tomography.object_models import ObjectModelType @@ -14,7 +15,7 @@ from quantem.tomography_old.utils import gaussian_filter_2d_stack, gaussian_kernel_1d -class Tomography(TomographyOpt, TomographyBase): +class Tomography(TomographyOpt, TomographyBase, DDPMixin): """ Class for handling all ML tomography reconstruction methods. Automatic handling between AD and INR-based tomography. @@ -38,21 +39,95 @@ def from_models( def reconstruct( self, - obj_model: ObjectModelType, - dset: DatasetModelType, + # obj_model: ObjectModelType, + # dset: DatasetModelType, num_iter: int = 10, + batch_size: int = 1024, + num_workers: int = 32, reset: bool = False, optimizer_params: dict | None = None, scheduler_params: dict | None = None, - constraints=None, # TODO: What to pass into the constraints? + constraints: dict = {}, # TODO: What to pass into the constraints? loss_func: Tuple[str, Optional[float]] = ("smooth_l1", 0.07), ): - raise NotImplementedError """ This function should be able to handle both AD and INR-based tomography reconstruction methods. I.e, auto-detection through the obj model type, while both share the same pose optimization. """ + # TODO: Prior to reconstruction, it is assumed that object + dataset are both in the correct devices. Need to implement a way to check this. + + if self.obj_model.device != self.dset.device: + raise ValueError( + f"Should never happen! obj_model and dset must be on the same device, got {self.obj_model.device} and {self.dset.device}" + ) + + if reset: + raise NotImplementedError("Reset is not implemented yet.") + + if optimizer_params is not None: + self.optimizer_params = optimizer_params + self.set_optimizers() + + if scheduler_params is not None: + self.scheduler_params = scheduler_params + self.set_schedulers() + + new_scheduler = reset + if new_scheduler: + raise NotImplementedError("New schedulers are not implemented yet.") + + # Setting up DDP + if not hasattr(self, "dataloader"): + self.dataloader, self.sampler = self.setup_dataloader( + self.dset, batch_size, num_workers=num_workers + ) + + self.obj_model.model.train() + + N = max(self.obj_model.obj.shape) + num_samples_per_ray = max(self.obj_model.obj.shape) + print(f"N: {N}, num_samples_per_ray: {num_samples_per_ray}") + + for a0 in range(num_iter): + consistency_loss = 0.0 + total_loss = 0.0 + # self._reset_iter_constraints() + + if self.sampler is not None: + self.sampler.set_epoch(a0) + + for batch_idx, batch in enumerate(self.dataloader): + self.zero_grad_all() + with torch.autocast( + device_type=self.device.type, + dtype=torch.bfloat16, + enabled=True, + ): + all_coords = self.dset.get_coords(batch, N, num_samples_per_ray) + + all_densities = self.obj_model.forward(all_coords) + + integrated_densities = self.dset.integrate_rays( + all_densities, num_samples_per_ray + ) + + # batch_consistency_loss = loss_func(integrated_densities, batch["target_value"]) + batch_consistency_loss = torch.nn.functional.mse_loss( + integrated_densities, batch["target_value"] + ) + + # soft_constraints_loss = self._soft_constraints() + batch_loss = batch_consistency_loss # + soft_constraints_loss + batch_loss.backward() + self.step_optimizers() + total_loss += batch_loss.item() + consistency_loss += batch_consistency_loss.item() + + total_loss = total_loss / len(self.dataloader) + consistency_loss = consistency_loss / len(self.dataloader) + print(f"Total Loss: {total_loss:.4f}, Consistency Loss: {consistency_loss:.4f}") + class TomographyConventional(TomographyBase): """ diff --git a/src/quantem/tomography/tomography_base.py b/src/quantem/tomography/tomography_base.py index fec0852b..c90d9ba6 100644 --- a/src/quantem/tomography/tomography_base.py +++ b/src/quantem/tomography/tomography_base.py @@ -2,13 +2,14 @@ from numpy.typing import NDArray from quantem.core.io.serialize import AutoSerialize +from quantem.core.ml.ddp import DDPMixin from quantem.core.utils.rng import RNGMixin from quantem.tomography.dataset_models import DatasetModelType, TomographyDatasetBase from quantem.tomography.logger_tomography import LoggerTomography -from quantem.tomography.object_models import ConstraintsTomography, ObjectModelType +from quantem.tomography.object_models import ConstraintsTomography, ObjectBase, ObjectPixelated -class TomographyBase(AutoSerialize, RNGMixin): +class TomographyBase(AutoSerialize, RNGMixin, DDPMixin): """ A base class for performing electron tomography reconstructions. @@ -20,7 +21,7 @@ class TomographyBase(AutoSerialize, RNGMixin): def __init__( self, dset: DatasetModelType, - obj_model: ObjectModelType, + obj_model: ObjectBase, logger: LoggerTomography | None = None, device: str = "cuda", rng: np.random.Generator | int | None = None, @@ -30,15 +31,25 @@ def __init__( # raise RuntimeError("Use Dataset.from_* to instantiate this class.") super().__init__() + self.obj_model = obj_model self.dset = dset - self.obj_model = obj_model self.rng = rng self.device = device self.logger = logger # Loss self._epoch_losses: list[float] = [] + # DDP Initialization + # print("Checking if obj_model is a ObjectPixelated: ", not isinstance(obj_model, ObjectPixelated)) + if not isinstance(obj_model, ObjectPixelated): + print("Setting up DDP for obj_model") + self.setup_distributed(device=device) + # self._obj_model._model = self.build_model(obj_model) # Assuming when object is initialized it's already wrapped in DDP? + print("After DDP Setup", self._obj_model) + + self.dset = dset + self.dset.to(device) # --- Properties --- @property @@ -58,14 +69,15 @@ def obj_type(self) -> str: return self.obj_model.obj_type @property - def obj_model(self) -> ObjectModelType: + def obj_model(self) -> ObjectBase: return self._obj_model @obj_model.setter - def obj_model(self, obj_model: ObjectModelType): - if not isinstance(obj_model, ObjectModelType): - raise TypeError(f"obj_model should be a ObjectModelType, got {type(obj_model)}") + def obj_model(self, obj_model: ObjectBase): + if not isinstance(obj_model, ObjectBase): + raise TypeError(f"obj_model should be a ObjectBase, got {type(obj_model)}") self._obj_model = obj_model + print(self._obj_model) @property def constraints(self) -> ConstraintsTomography: @@ -95,8 +107,9 @@ def device(self) -> str: @device.setter def device(self, device: str): - if not isinstance(device, str): - raise TypeError(f"device should be a str, got {type(device)}") + print("Device trying to set: ", device) + # if not isinstance(device, str): + # raise TypeError(f"device should be a str, got {type(device)}") self._device = device self.to(device) From cc9188f79ecc3168286a4c28f5145c9c42f21711 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Thu, 22 Jan 2026 17:41:19 -0800 Subject: [PATCH 046/335] Working reconstruction loop, need to figure out this device stuff and can start including soft loss and general clean up. --- src/quantem/tomography/dataset_models.py | 65 ++++++++++++++++++------ src/quantem/tomography/object_models.py | 16 ++++-- src/quantem/tomography/tomography.py | 29 ++++++----- src/quantem/tomography/tomography_opt.py | 2 + 4 files changed, 80 insertions(+), 32 deletions(-) diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index e5b0d009..fef3cf9d 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -189,6 +189,14 @@ def shifts_params(self) -> torch.nn.Parameter: def shifts_params(self, shifts: torch.Tensor, device: str): self._shifts_params = nn.Parameter(shifts.to(device)) + @property + def device(self) -> str: + return self._device + + @device.setter + def device(self, device: str): + self._device = device + # --- Helper Functions --- def to(self, device: str): """ @@ -205,6 +213,8 @@ def to(self, device: str): self._z3_ref = self._z3_ref.to(device) self._shifts_ref = self._shifts_ref.to(device) + self.device = device + class TomographyPixDataset(TomographyDatasetBase): """ @@ -281,12 +291,13 @@ def forward(self, dummy_input: Any = None): second_half_z3 = self.z3_params[self.reference_tilt_idx :] z3 = torch.cat([first_half_z3, self._z3_ref, second_half_z3], dim=0) - return DatasetValue( - target=None, - tilt_angle=None, - pixel_loc=None, - pose=(shifts, z1, z3), - ) + # return DatasetValue( + # target=None, + # tilt_angle=None, + # pixel_loc=None, + # pose=(shifts, z1, z3), + # ) + return shifts, z1, z3 @property def params(self): @@ -385,10 +396,16 @@ def transform_batch_rays( return transformed_rays @staticmethod - def integrate_rays(rays: torch.Tensor, num_samples_per_ray: int) -> torch.Tensor: + def integrate_rays( + rays: torch.Tensor, num_samples_per_ray: int, target_values_len: int + ) -> torch.Tensor: + ray_densities = rays.view( + target_values_len, + num_samples_per_ray, + ) step_size = 2.0 / (num_samples_per_ray - 1) - predicted_values = rays.sum(dim=1) * step_size + predicted_values = ray_densities.sum(dim=1) * step_size return predicted_values @@ -409,12 +426,19 @@ def __getitem__( pixel_i = remaining // self.tilt_stack.shape[1] pixel_j = remaining % self.tilt_stack.shape[1] - return DatasetValue( - target=self.tilt_stack[projection_idx, pixel_i, pixel_j], - tilt_angle=self.tilt_angles[projection_idx], - projection_idx=projection_idx, - pixel_loc=(pixel_i, pixel_j), - ) + # return DatasetValue( + # target=self.tilt_stack[projection_idx, pixel_i, pixel_j], + # tilt_angle=self.tilt_angles[projection_idx], + # projection_idx=projection_idx, + # pixel_loc=(pixel_i, pixel_j), + # ) + return { + "projection_idx": torch.tensor(projection_idx), + "pixel_i": torch.tensor(pixel_i), + "pixel_j": torch.tensor(pixel_j), + "phi": self.tilt_angles[projection_idx], # tensor + "target_value": self.tilt_stack[projection_idx, pixel_i, pixel_j], # tensor + } def __len__( self, @@ -422,8 +446,19 @@ def __len__( """ Returns the number of pixels in the tilt stack. """ + N = max(self.tilt_stack.shape) + return self.tilt_stack.shape[0] * N * N + + def to(self, device: str): + self._z1_params = nn.Parameter(self._z1_angles.to(device)) + self._z3_params = nn.Parameter(self._z3_angles.to(device)) + self._shifts_params = nn.Parameter(self._shifts.to(device)) + + self._z1_ref = self._z1_ref.to(device) + self._z3_ref = self._z3_ref.to(device) + self._shifts_ref = self._shifts_ref.to(device) - return self._total_pixels + self.device = device class TomographyINRPretrainDataset(Dataset): diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index f35aae77..cd156a23 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -283,13 +283,13 @@ def to(self, device: str): class ObjectINR(ObjectConstraints, DDPMixin): def __init__( self, - volume_shape: tuple[int, int, int], + shape: tuple[int, int, int], device: str = "cpu", rng: np.random.Generator | int | None = None, model: nn.Module | None = None, ): super().__init__( - shape=volume_shape, + shape=shape, device=device, rng=rng, _token=self._token, @@ -307,12 +307,12 @@ def __init__( def from_model( cls, model: nn.Module, - volume_shape: tuple[int, int, int], + shape: tuple[int, int, int], device: str = "cpu", rng: np.random.Generator | int | None = None, ): obj_model = cls( - volume_shape=volume_shape, + shape=shape, device=device, rng=rng, model=model, # ✅ build/register in __init__ @@ -375,6 +375,14 @@ def dtype(self) -> torch.dtype: # TODO: This is a temporary solution to get the dtype of the object. return torch.float32 + @property + def shape(self) -> tuple[int, int, int]: + return self._shape + + @shape.setter + def shape(self, shape: tuple[int, int, int]): + self._shape = shape + # --- Helper Functions --- # Reset method that goes back to the pretrained weights. diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index e9f45a20..074d5891 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -57,10 +57,10 @@ def reconstruct( # TODO: Prior to reconstruction, it is assumed that object + dataset are both in the correct devices. Need to implement a way to check this. - if self.obj_model.device != self.dset.device: - raise ValueError( - f"Should never happen! obj_model and dset must be on the same device, got {self.obj_model.device} and {self.dset.device}" - ) + # if self.obj_model.device != self.dset.device: + # raise ValueError( + # f"Should never happen! obj_model and dset must be on the same device, got {self.obj_model.device} and {self.dset.device}" + # ) if reset: raise NotImplementedError("Reset is not implemented yet.") @@ -71,7 +71,7 @@ def reconstruct( if scheduler_params is not None: self.scheduler_params = scheduler_params - self.set_schedulers() + self.set_schedulers(scheduler_params) new_scheduler = reset if new_scheduler: @@ -85,8 +85,8 @@ def reconstruct( self.obj_model.model.train() - N = max(self.obj_model.obj.shape) - num_samples_per_ray = max(self.obj_model.obj.shape) + N = max(self.obj_model.shape) + num_samples_per_ray = max(self.obj_model.shape) print(f"N: {N}, num_samples_per_ray: {num_samples_per_ray}") for a0 in range(num_iter): @@ -105,20 +105,23 @@ def reconstruct( enabled=True, ): all_coords = self.dset.get_coords(batch, N, num_samples_per_ray) + all_coords = all_coords.to( + device=self.device, dtype=torch.float32, non_blocking=True + ) all_densities = self.obj_model.forward(all_coords) integrated_densities = self.dset.integrate_rays( - all_densities, num_samples_per_ray + all_densities, num_samples_per_ray, len(batch["target_value"]) ) # batch_consistency_loss = loss_func(integrated_densities, batch["target_value"]) - batch_consistency_loss = torch.nn.functional.mse_loss( - integrated_densities, batch["target_value"] - ) + pred = integrated_densities.float() + target = batch["target_value"].to(self.device, non_blocking=True).float() + batch_consistency_loss = torch.nn.functional.mse_loss(pred, target) - # soft_constraints_loss = self._soft_constraints() - batch_loss = batch_consistency_loss # + soft_constraints_loss + # soft_constraints_loss = self._soft_constraints() + batch_loss = batch_consistency_loss.float() # + soft_constraints_loss batch_loss.backward() self.step_optimizers() total_loss += batch_loss.item() diff --git a/src/quantem/tomography/tomography_opt.py b/src/quantem/tomography/tomography_opt.py index 77b19cfc..2a148433 100644 --- a/src/quantem/tomography/tomography_opt.py +++ b/src/quantem/tomography/tomography_opt.py @@ -117,6 +117,8 @@ def set_schedulers(self, params: dict[str, dict], num_iter: int | None = None): self.obj_model.set_scheduler(scheduler_params, num_iter) elif key == "pose": self.dset.set_scheduler(scheduler_params, num_iter) + else: + raise ValueError(f"Unknown optimization key: {key}") def step_optimizers(self): for key in self.optimizer_params.keys(): From 7a46ff13dbd951e3fb9d6e0f7de63c5d4a454e72 Mon Sep 17 00:00:00 2001 From: smribet Date: Mon, 26 Jan 2026 08:43:13 -0800 Subject: [PATCH 047/335] change kwargs in read4stem --- src/quantem/core/io/file_readers.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/quantem/core/io/file_readers.py b/src/quantem/core/io/file_readers.py index cb36f1de..7cb6fb5e 100644 --- a/src/quantem/core/io/file_readers.py +++ b/src/quantem/core/io/file_readers.py @@ -30,7 +30,7 @@ def read_4dstem( Index of the dataset to load if file contains multiple datasets. If None, automatically selects the first 4D dataset found. **kwargs: dict - Additional keyword arguments to pass to the Dataset4dstem constructor. + Additional keyword arguments to pass to the file reader. Returns -------- @@ -39,8 +39,12 @@ def read_4dstem( if file_type is None: file_type = Path(file_path).suffix.lower().lstrip(".") + sampling_override = kwargs.pop("sampling", None) + origin_override = kwargs.pop("origin", None) + units_override = kwargs.pop("units", None) + file_reader = importlib.import_module(f"rsciio.{file_type}").file_reader - data_list = file_reader(file_path) + data_list = file_reader(file_path, **kwargs) # If specific index provided, use it if dataset_index is not None: @@ -69,17 +73,18 @@ def read_4dstem( imported_axes = imported_data["axes"] - sampling = kwargs.pop( - "sampling", - [ax["scale"] for ax in imported_axes], + sampling = ( + sampling_override + if sampling_override is not None + else [ax["scale"] for ax in imported_axes] ) - origin = kwargs.pop( - "origin", - [ax["offset"] for ax in imported_axes], + origin = ( + origin_override if origin_override is not None else [ax["offset"] for ax in imported_axes] ) - units = kwargs.pop( - "units", - ["pixels" if ax["units"] == "1" else ax["units"] for ax in imported_axes], + units = ( + units_override + if units_override is not None + else ["pixels" if ax["units"] == "1" else ax["units"] for ax in imported_axes] ) dataset = Dataset4dstem.from_array( @@ -87,7 +92,6 @@ def read_4dstem( sampling=sampling, origin=origin, units=units, - **kwargs, ) return dataset From d08c334bd66e52a56bed969c9940bd3a03ad13a7 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 26 Jan 2026 14:05:37 -0800 Subject: [PATCH 048/335] Various u[dates --- src/quantem/core/ml/inr.py | 2 +- src/quantem/tomography/dataset_models.py | 10 +++- src/quantem/tomography/object_models.py | 66 +++++++++++++++++------ src/quantem/tomography/tomography.py | 41 ++++++++++---- src/quantem/tomography/tomography_base.py | 7 ++- 5 files changed, 95 insertions(+), 31 deletions(-) diff --git a/src/quantem/core/ml/inr.py b/src/quantem/core/ml/inr.py index 4cf4fc70..de43b6c4 100644 --- a/src/quantem/core/ml/inr.py +++ b/src/quantem/core/ml/inr.py @@ -113,7 +113,7 @@ def _build(self) -> None: if self.winner_initialization: with torch.no_grad(): self.net[0].linear.weight += ( - torch.randn_like(self.net[0].linear.weight) * 1 / self.first_omega_0 + torch.randn_like(self.net[0].linear.weight) * 5 / self.first_omega_0 ) self.net[1].linear.weight += ( torch.randn_like(self.net[1].linear.weight) * 0.1 / self.hidden_omega_0 diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index fef3cf9d..bf09cb70 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -102,7 +102,7 @@ def from_data( # @property # def params(self): # # TODO: Need to double check if this is correct way, also need to implement get_optimization_parameters @Arthur!! - # return self.parameters() + # raise NotImplementedError("This method should be implemented in subclasses.") # --- Forward pass --- @abstractmethod @@ -215,6 +215,12 @@ def to(self, device: str): self.device = device + def get_optimization_parameters(self) -> list[nn.Parameter]: + """ + Get the parameters that should be optimized for this model. + """ + return list[nn.Parameter](self.params) + class TomographyPixDataset(TomographyDatasetBase): """ @@ -330,6 +336,8 @@ def get_coords( sampling_rate=1.0, ) all_coords = transformed_rays.view(-1, 3) + + all_coords = all_coords.to(self.device, dtype=torch.float32, non_blocking=True) return all_coords @staticmethod diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index cd156a23..0ce879f6 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -187,23 +187,17 @@ def apply_hard_constraints( # TODO: Need to implement the other hard constraints: Fourier Filter and Circular Mask. return obj2 - def apply_soft_constraints( - self, - obj: torch.Tensor, - ) -> torch.Tensor: - """ - Apply soft constraints to the object model. - - Only soft constraint here is the TV loss. - """ + # def apply_soft_constraints( + # self, + # obj: torch.Tensor, + # ) -> torch.Tensor: + # """ + # TODO: Already in BaseConstraints class. + # Apply soft constraints to the object model. - soft_loss = torch.tensor(0.0, device=obj.device, dtype=obj.dtype, requires_grad=True) - if self.constraints.tv_vol > 0: - tv_loss = self.get_tv_loss( - obj.unsqueeze(0).unsqueeze(0), factor=self.constraints.tv_vol - ) - soft_loss += tv_loss - return soft_loss + # Only soft constraint here is the TV loss. + # """ + # return NotImplementedError("Subclasses must implement this method.") @abstractmethod def get_tv_loss(self, obj: torch.Tensor, tv_weight: float = 0.0) -> torch.Tensor: @@ -262,6 +256,15 @@ def name(self) -> str: def obj_type(self) -> str: return "pixelated" + def apply_soft_constraints(self, obj: torch.Tensor) -> torch.Tensor: + soft_loss = torch.tensor(0.0, device=obj.device, dtype=obj.dtype, requires_grad=True) + if self.constraints.tv_vol > 0: + tv_loss = self.get_tv_loss( + obj.unsqueeze(0).unsqueeze(0), factor=self.constraints.tv_vol + ) + soft_loss += tv_loss + return soft_loss + # --- Forward method --- def forward(self, dummy_input=None) -> torch.Tensor: return self.obj @@ -341,6 +344,33 @@ def model(self) -> "nn.Module": # """ # raise RuntimeError("\n\n\nsetting model, this shouldn't be reachable???\n\n\n") + def apply_soft_constraints( + self, + coords: torch.Tensor, + ) -> torch.Tensor: + soft_loss = torch.tensor(0.0, device=coords.device) + if self.constraints.tv_vol > 0: + num_tv_samples = min(10000, coords.shape[0]) + tv_indices = torch.randperm(coords.shape[0], device=coords.device)[:num_tv_samples] + + tv_coords = coords[tv_indices].detach().requires_grad_(True) + + tv_densities_recomputed = self.model(tv_coords) + if tv_densities_recomputed.dim() > 1: + tv_densities_recomputed = tv_densities_recomputed.squeeze(-1) + + grad_outputs = torch.autograd.grad( + outputs=tv_densities_recomputed, + inputs=tv_coords, + grad_outputs=torch.ones_like(tv_densities_recomputed), + create_graph=True, + )[0] + + grad_norm = torch.norm(grad_outputs, dim=1) + soft_loss += self.constraints.tv_vol * grad_norm.mean() + + return soft_loss + @property def params(self): return self.model.parameters() @@ -482,6 +512,10 @@ def _pretrain( loss.backward() epoch_loss += loss.item() + + # Clip gradients + torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm=1.0) + optimizer.step() optimizer.zero_grad() diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 074d5891..1f85b698 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -1,4 +1,4 @@ -from typing import Literal, Optional, Self, Tuple +from typing import List, Literal, Optional, Self, Tuple import numpy as np import torch @@ -49,6 +49,7 @@ def reconstruct( scheduler_params: dict | None = None, constraints: dict = {}, # TODO: What to pass into the constraints? loss_func: Tuple[str, Optional[float]] = ("smooth_l1", 0.07), + num_samples_per_ray: int | List[Tuple[int, int]] = None, ): """ This function should be able to handle both AD and INR-based tomography reconstruction methods. @@ -73,6 +74,9 @@ def reconstruct( self.scheduler_params = scheduler_params self.set_schedulers(scheduler_params) + if constraints is not None: + self.obj_model.constraints = constraints + new_scheduler = reset if new_scheduler: raise NotImplementedError("New schedulers are not implemented yet.") @@ -86,7 +90,15 @@ def reconstruct( self.obj_model.model.train() N = max(self.obj_model.shape) - num_samples_per_ray = max(self.obj_model.shape) + + if num_samples_per_ray is None: + num_samples_per_ray = max(self.obj_model.shape) + else: + if isinstance(num_samples_per_ray, int): + num_samples_per_ray = num_samples_per_ray + else: + print("num_samples_per_ray schedule provided.") + print(f"N: {N}, num_samples_per_ray: {num_samples_per_ray}") for a0 in range(num_iter): @@ -97,6 +109,12 @@ def reconstruct( if self.sampler is not None: self.sampler.set_epoch(a0) + if isinstance(num_samples_per_ray, list): + print(f"num_samples_per_ray[a0][1]: {num_samples_per_ray[a0][1]}") + curr_num_samples_per_ray = num_samples_per_ray[a0][1] + else: + curr_num_samples_per_ray = num_samples_per_ray + print(f"curr_num_samples_per_ray: {curr_num_samples_per_ray}") for batch_idx, batch in enumerate(self.dataloader): self.zero_grad_all() with torch.autocast( @@ -104,15 +122,15 @@ def reconstruct( dtype=torch.bfloat16, enabled=True, ): - all_coords = self.dset.get_coords(batch, N, num_samples_per_ray) - all_coords = all_coords.to( - device=self.device, dtype=torch.float32, non_blocking=True - ) + all_coords = self.dset.get_coords(batch, N, curr_num_samples_per_ray) + # all_coords = all_coords.to( + # device=self.device, dtype=torch.float32, non_blocking=True + # ) all_densities = self.obj_model.forward(all_coords) integrated_densities = self.dset.integrate_rays( - all_densities, num_samples_per_ray, len(batch["target_value"]) + all_densities, curr_num_samples_per_ray, len(batch["target_value"]) ) # batch_consistency_loss = loss_func(integrated_densities, batch["target_value"]) @@ -120,9 +138,14 @@ def reconstruct( target = batch["target_value"].to(self.device, non_blocking=True).float() batch_consistency_loss = torch.nn.functional.mse_loss(pred, target) - # soft_constraints_loss = self._soft_constraints() - batch_loss = batch_consistency_loss.float() # + soft_constraints_loss + soft_constraints_loss = self.obj_model.apply_soft_constraints(all_coords) + print(f"soft_constraints_loss: {soft_constraints_loss.item()}") + batch_loss = batch_consistency_loss.float() + soft_constraints_loss batch_loss.backward() + + # Clip gradients + torch.nn.utils.clip_grad_norm_(self.obj_model.model.parameters(), max_norm=1.0) + self.step_optimizers() total_loss += batch_loss.item() consistency_loss += batch_consistency_loss.item() diff --git a/src/quantem/tomography/tomography_base.py b/src/quantem/tomography/tomography_base.py index c90d9ba6..c4bfcd3d 100644 --- a/src/quantem/tomography/tomography_base.py +++ b/src/quantem/tomography/tomography_base.py @@ -46,7 +46,7 @@ def __init__( print("Setting up DDP for obj_model") self.setup_distributed(device=device) # self._obj_model._model = self.build_model(obj_model) # Assuming when object is initialized it's already wrapped in DDP? - print("After DDP Setup", self._obj_model) + # print("After DDP Setup", self._obj_model) self.dset = dset self.dset.to(device) @@ -74,10 +74,9 @@ def obj_model(self) -> ObjectBase: @obj_model.setter def obj_model(self, obj_model: ObjectBase): - if not isinstance(obj_model, ObjectBase): - raise TypeError(f"obj_model should be a ObjectBase, got {type(obj_model)}") + # if not isinstance(obj_model, ObjectBase): + # raise TypeError(f"obj_model should be a ObjectBase, got {type(obj_model)}") self._obj_model = obj_model - print(self._obj_model) @property def constraints(self) -> ConstraintsTomography: From 9a37e05de8c376b16ee1fc5ac3f7937552caa801 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 26 Jan 2026 17:09:47 -0800 Subject: [PATCH 049/335] Logger implementation --- src/quantem/core/ml/constraints.py | 52 +++++++++++++------ src/quantem/tomography/logger_tomography.py | 57 +++++++++++++++++++++ src/quantem/tomography/object_models.py | 53 +++++-------------- src/quantem/tomography/tomography.py | 30 +++++++++-- src/quantem/tomography/tomography_base.py | 22 ++++++-- 5 files changed, 148 insertions(+), 66 deletions(-) diff --git a/src/quantem/core/ml/constraints.py b/src/quantem/core/ml/constraints.py index e6d995f2..990c240e 100644 --- a/src/quantem/core/ml/constraints.py +++ b/src/quantem/core/ml/constraints.py @@ -1,17 +1,45 @@ from abc import ABC, abstractmethod +from copy import deepcopy from dataclasses import dataclass -from typing import Any +from typing import Any, Self +import numpy as np import torch -@dataclass +@dataclass(slots=True) class Constraints(ABC): """ Needs to be implemented in all object models that inherit from BaseConstraints. """ - pass + soft_constraint_keys = [] + hard_constraint_keys = [] + + @property + def allowed_keys(self) -> list[str]: + """ + List of all allowed keys. + """ + return self.hard_constraint_keys + self.soft_constraint_keys + + def copy(self) -> Self: + """ + Copy the constraints. + """ + return deepcopy(self) + + def __str__(self) -> str: + hard = "\n".join(f"{key}: {getattr(self, key)}" for key in self.hard_constraint_keys) + soft = "\n".join(f"{key}: {getattr(self, key)}" for key in self.soft_constraint_keys) + + return ( + "Constraints:\n" + " Hard constraints:\n" + f" {hard.replace('\n', '\n ')}\n" + " Soft constraints:\n" + f" {soft.replace('\n', '\n ')}" + ) class BaseConstraints(ABC): @@ -24,10 +52,13 @@ class BaseConstraints(ABC): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self._soft_constraint_loss = {} - self._epoch_constraint_losses = {} + self._soft_constraint_losses = [] self.constraints = self.DEFAULT_CONSTRAINTS.copy() + @property + def soft_constraint_losses(self) -> list[float]: + return np.array(self._soft_constraint_losses) + @property def constraints(self) -> Constraints: """ @@ -44,21 +75,10 @@ def constraints(self, constraints: Constraints | dict[str, Any]): self._constraints = constraints elif isinstance(constraints, dict): for key, value in constraints.items(): - if key not in self._constraints.allowed_keys: - raise ValueError(f"Invalid constraint key: {key}") setattr(self._constraints, key, value) else: raise ValueError(f"Invalid constraints type: {type(constraints)}") - def add_constraint(self, key: str, value: Any): - """ - Add a constraint to the constraints class. - Note the allowed keys should be implemented for each constraint subclass. - """ - if key not in self._constraints.allowed_keys: - raise ValueError(f"Invalid constraint key: {key}") - setattr(self._constraints, key, value) - # --- Required methods tha tneeds to implemented in subclasses --- @abstractmethod def apply_hard_constraints(self, *args, **kwargs) -> torch.Tensor: diff --git a/src/quantem/tomography/logger_tomography.py b/src/quantem/tomography/logger_tomography.py index d9f0f8bb..256e47df 100644 --- a/src/quantem/tomography/logger_tomography.py +++ b/src/quantem/tomography/logger_tomography.py @@ -1,4 +1,9 @@ +import matplotlib.pyplot as plt +import torch + from quantem.core.ml.logger import LoggerBase +from quantem.tomography.dataset_models import DatasetModelType +from quantem.tomography.object_models import ObjectModelType class LoggerTomography(LoggerBase): @@ -19,3 +24,55 @@ def log_epoch(self, epoch: int, loss: float, tilt_series_loss: float, soft_loss: self.log_scalar("loss/total", loss, epoch) self.log_scalar("loss/tilt_series", tilt_series_loss, epoch) self.log_scalar("loss/soft", soft_loss, epoch) + + def log_iter( + self, + object_model: ObjectModelType, + iter: int, + consistency_loss: float, + total_loss: float, + num_samples_per_ray: int | None = None, + ): + self.log_scalar("loss/consistency", consistency_loss, iter) + self.log_scalar("loss/total", total_loss, iter) + self.log_scalar("loss/soft", object_model._soft_constraint_losses[-1], iter) + self.log_scalar("num_samples_per_ray", num_samples_per_ray, iter) + + def log_iter_images( + self, + object_model: ObjectModelType, + dataset_model: DatasetModelType, + iter: int, + logger_cmap: str = "turbo", + ): + with torch.no_grad(): + vol = object_model.create_volume(return_vol=True) + z1_vals = dataset_model.z1_params.cpu().numpy() + z3_vals = dataset_model.z3_params.cpu().numpy() + shifts_vals = dataset_model.shifts_params.cpu().numpy() + + self.log_image("volume/sum_z", vol.sum(axis=0), iter, logger_cmap) + self.log_image("volume/sum_y", vol.sum(axis=1), iter, logger_cmap) + self.log_image("volume/sum_x", vol.sum(axis=2), iter, logger_cmap) + + # Plotting z1 and z3 vals + fig, ax = plt.subplots() + ax.plot(z1_vals, label="Z1") + ax.plot(z3_vals, label="Z3") + ax.legend() + ax.set_title("Z1 and Z3 Angles") + ax.set_xlabel("Tilt Image") + ax.set_ylabel("Degree") + self.log_figure("z1_z3_angles", fig, iter) + plt.close(fig) + + # Plotting shifts + fig, ax = plt.subplots() + ax.plot(shifts_vals[:, 0], label="Shifts X") + ax.plot(shifts_vals[:, 1], label="Shifts Y") + ax.legend() + ax.set_title("Shifts") + ax.set_xlabel("Tilt Image") + ax.set_ylabel("Pixel") + self.log_figure("shifts", fig, iter) + plt.close(fig) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 0ce879f6..d01f2918 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -1,7 +1,7 @@ from abc import abstractmethod from copy import deepcopy from dataclasses import dataclass -from typing import Any, Callable, Self +from typing import Any, Callable import numpy as np import torch @@ -17,8 +17,8 @@ from quantem.tomography.dataset_models import TomographyINRPretrainDataset -@dataclass -class ConstraintsTomography(Constraints): +@dataclass(slots=True) +class DefaultConstraintsTomography(Constraints): """ Data class for all constraints that can be applied to the object model. """ @@ -32,41 +32,8 @@ class ConstraintsTomography(Constraints): # Soft Constraints tv_vol: float = 0.0 - @property - def hard_constraint_keys(self) -> list[str]: - """ - List of hard constraint keys. - """ - return ["positivity", "shrinkage", "circular_mask", "fourier_filter"] - - @property - def soft_constraint_keys(self) -> list[str]: - """ - List of soft constraint keys. - """ - return ["tv_vol"] - - @property - def allowed_keys(self) -> list[str]: - """ - List of all allowed keys. - """ - return self.hard_constraint_keys + self.soft_constraint_keys - - def copy(self) -> Self: - """ - Copy the constraints. - """ - return deepcopy(self) - - def __str__(self) -> str: - return f"""Constraints: - Positivity: {self.positivity} - Shrinkage: {self.shrinkage} - Circular Mask: {self.circular_mask} - Fourier Filter: {self.fourier_filter} - TV Volume: {self.tv_vol} - """ + soft_constraint_keys = ["tv_vol"] + hard_constraint_keys = ["positivity", "shrinkage", "circular_mask", "fourier_filter"] class ObjectBase(AutoSerialize, nn.Module, RNGMixin, OptimizerMixin): @@ -162,8 +129,8 @@ def to(self, *args, **kwargs): raise NotImplementedError -class ObjectConstraints(ObjectBase, BaseConstraints): - DEFAULT_CONSTRAINTS = ConstraintsTomography() +class ObjectConstraints(BaseConstraints, ObjectBase): + DEFAULT_CONSTRAINTS = DefaultConstraintsTomography() def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -533,6 +500,7 @@ def _pretrain( def create_volume( self, + return_vol: bool = False, ): N = max(self._shape) with torch.no_grad(): @@ -599,7 +567,10 @@ def create_volume( else: pred_full = outputs.reshape(N, N, N).float() - self._obj = pred_full.detach().cpu() + if return_vol: + return pred_full.detach().cpu() + else: + self._obj = pred_full.detach().cpu() def get_tv_loss( self, diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 1f85b698..49987edd 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -104,13 +104,13 @@ def reconstruct( for a0 in range(num_iter): consistency_loss = 0.0 total_loss = 0.0 + epoch_soft_constraint_loss = 0.0 # self._reset_iter_constraints() if self.sampler is not None: self.sampler.set_epoch(a0) if isinstance(num_samples_per_ray, list): - print(f"num_samples_per_ray[a0][1]: {num_samples_per_ray[a0][1]}") curr_num_samples_per_ray = num_samples_per_ray[a0][1] else: curr_num_samples_per_ray = num_samples_per_ray @@ -136,10 +136,10 @@ def reconstruct( # batch_consistency_loss = loss_func(integrated_densities, batch["target_value"]) pred = integrated_densities.float() target = batch["target_value"].to(self.device, non_blocking=True).float() - batch_consistency_loss = torch.nn.functional.mse_loss(pred, target) + batch_consistency_loss = torch.nn.functional.mse_loss(pred, target) soft_constraints_loss = self.obj_model.apply_soft_constraints(all_coords) - print(f"soft_constraints_loss: {soft_constraints_loss.item()}") + epoch_soft_constraint_loss += soft_constraints_loss.item() batch_loss = batch_consistency_loss.float() + soft_constraints_loss batch_loss.backward() @@ -150,10 +150,33 @@ def reconstruct( total_loss += batch_loss.item() consistency_loss += batch_consistency_loss.item() + # TODO: Maybe reorganize the losses so that the order makes sense lol. total_loss = total_loss / len(self.dataloader) consistency_loss = consistency_loss / len(self.dataloader) + epoch_soft_constraint_loss = epoch_soft_constraint_loss / len(self.dataloader) print(f"Total Loss: {total_loss:.4f}, Consistency Loss: {consistency_loss:.4f}") + self._epoch_losses.append(total_loss) + self._consistency_losses.append(consistency_loss) + self.obj_model._soft_constraint_losses.append(epoch_soft_constraint_loss) + + if self.logger is not None and self.global_rank == 0: + self.logger.log_iter( + object_model=self.obj_model, + iter=a0, + consistency_loss=consistency_loss, + total_loss=total_loss, + num_samples_per_ray=curr_num_samples_per_ray, + ) + if self.logger.log_images_every > 0 and a0 % self.logger.log_images_every == 0: + self.logger.log_iter_images( + object_model=self.obj_model, + dataset_model=self.dset, + iter=a0, + ) + + self.logger.flush() + class TomographyConventional(TomographyBase): """ @@ -281,7 +304,6 @@ def _reconstruction_epoch( self.obj_model.obj += correction if gaussian_kernel is not None: - print(self.obj_model.obj.shape) self.obj_model.obj = gaussian_filter_2d_stack(self.obj_model.obj, gaussian_kernel) loss = torch.mean(torch.abs(error)) diff --git a/src/quantem/tomography/tomography_base.py b/src/quantem/tomography/tomography_base.py index c4bfcd3d..2f8d6fc7 100644 --- a/src/quantem/tomography/tomography_base.py +++ b/src/quantem/tomography/tomography_base.py @@ -6,7 +6,11 @@ from quantem.core.utils.rng import RNGMixin from quantem.tomography.dataset_models import DatasetModelType, TomographyDatasetBase from quantem.tomography.logger_tomography import LoggerTomography -from quantem.tomography.object_models import ConstraintsTomography, ObjectBase, ObjectPixelated +from quantem.tomography.object_models import ( + DefaultConstraintsTomography, + ObjectBase, + ObjectPixelated, +) class TomographyBase(AutoSerialize, RNGMixin, DDPMixin): @@ -40,6 +44,7 @@ def __init__( # Loss self._epoch_losses: list[float] = [] + self._consistency_losses: list[float] = [] # DDP Initialization # print("Checking if obj_model is a ObjectPixelated: ", not isinstance(obj_model, ObjectPixelated)) if not isinstance(obj_model, ObjectPixelated): @@ -79,14 +84,14 @@ def obj_model(self, obj_model: ObjectBase): self._obj_model = obj_model @property - def constraints(self) -> ConstraintsTomography: + def constraints(self) -> DefaultConstraintsTomography: return self.obj_model.constraints @constraints.setter - def constraints(self, constraints: ConstraintsTomography): - if not isinstance(constraints, ConstraintsTomography): + def constraints(self, constraints: DefaultConstraintsTomography): + if not isinstance(constraints, DefaultConstraintsTomography): raise TypeError( - f"constraints should be a ConstraintsTomography, got {type(constraints)}" + f"constraints should be a DefaultConstraintsTomography, got {type(constraints)}" ) self.obj_model.constraints = constraints @@ -119,6 +124,13 @@ def epoch_losses(self) -> NDArray: """ return np.array(self._epoch_losses) + @property + def consistency_losses(self) -> NDArray: + """ + Returns the consistency loss for each epoch ran. + """ + return np.array(self._consistency_losses) + @property def num_epochs(self) -> int: return len(self._epoch_losses) From cfee59a1fc4b63a3cf68f4a6f80a6337c41f6cfe Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 27 Jan 2026 14:24:45 -0800 Subject: [PATCH 050/335] DDP bug where some projection idx's don't get optimized. --- src/quantem/core/ml/ddp.py | 4 +- src/quantem/tomography/dataset_models.py | 2 +- src/quantem/tomography/logger_tomography.py | 19 +- src/quantem/tomography/object_models.py | 2 - src/quantem/tomography/tomography.py | 58 +- src/quantem/tomography_old/tomography.py | 1766 +++++++++---------- 6 files changed, 938 insertions(+), 913 deletions(-) diff --git a/src/quantem/core/ml/ddp.py b/src/quantem/core/ml/ddp.py index 0d38e67f..b6ec536f 100644 --- a/src/quantem/core/ml/ddp.py +++ b/src/quantem/core/ml/ddp.py @@ -68,12 +68,14 @@ def setup_dataloader( # TODO: Implement validation dataloader if self.world_size > 1: + shuffle = True train_sampler = DistributedSampler( dataset, num_replicas=self.world_size, rank=self.global_rank, - shuffle=True, + shuffle=shuffle, ) + shuffle = False else: train_sampler = None diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index bf09cb70..b18090f1 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -317,7 +317,7 @@ def get_coords( # target_values = batch["target_value"].to(self.device, non_blocking=True) phis = batch["phi"].to(self.device, non_blocking=True) projection_indices = batch["projection_idx"].to(self.device, non_blocking=True) - + print(f"projection_indices: {projection_indices}") with torch.no_grad(): batch_ray_coords = self.create_batch_rays(pixel_i, pixel_j, N, num_samples_per_ray) diff --git a/src/quantem/tomography/logger_tomography.py b/src/quantem/tomography/logger_tomography.py index 256e47df..16e6d9f3 100644 --- a/src/quantem/tomography/logger_tomography.py +++ b/src/quantem/tomography/logger_tomography.py @@ -40,22 +40,24 @@ def log_iter( def log_iter_images( self, - object_model: ObjectModelType, + pred_volume: torch.Tensor, dataset_model: DatasetModelType, iter: int, logger_cmap: str = "turbo", ): + with torch.no_grad(): - vol = object_model.create_volume(return_vol=True) - z1_vals = dataset_model.z1_params.cpu().numpy() - z3_vals = dataset_model.z3_params.cpu().numpy() - shifts_vals = dataset_model.shifts_params.cpu().numpy() + z1_vals = dataset_model.z1_params.detach().cpu().numpy() + z3_vals = dataset_model.z3_params.detach().cpu().numpy() + shifts_vals = dataset_model.shifts_params.detach().cpu().numpy() - self.log_image("volume/sum_z", vol.sum(axis=0), iter, logger_cmap) - self.log_image("volume/sum_y", vol.sum(axis=1), iter, logger_cmap) - self.log_image("volume/sum_x", vol.sum(axis=2), iter, logger_cmap) + print("Logging volume...") + self.log_image("volume/sum_z", pred_volume.sum(axis=0), iter, logger_cmap) + self.log_image("volume/sum_y", pred_volume.sum(axis=1), iter, logger_cmap) + self.log_image("volume/sum_x", pred_volume.sum(axis=2), iter, logger_cmap) # Plotting z1 and z3 vals + print("Plotting z1 and z3 angles...") fig, ax = plt.subplots() ax.plot(z1_vals, label="Z1") ax.plot(z3_vals, label="Z3") @@ -67,6 +69,7 @@ def log_iter_images( plt.close(fig) # Plotting shifts + print("Plotting shifts...") fig, ax = plt.subplots() ax.plot(shifts_vals[:, 0], label="Shifts X") ax.plot(shifts_vals[:, 1], label="Shifts Y") diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index d01f2918..6ab64067 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -542,7 +542,6 @@ def create_volume( for _ in range(self.world_size) ] dist.all_gather(all_sizes, output_size) - max_size = max(size.item() for size in all_sizes) if outputs.shape[0] < max_size: @@ -558,7 +557,6 @@ def create_volume( for _ in range(self.world_size) ] dist.all_gather(gathered_outputs, outputs_padded.contiguous()) - trimmed_outputs = [] for rank, size in enumerate(all_sizes): trimmed_outputs.append(gathered_outputs[rank][: size.item()]) diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 49987edd..b0dfc92f 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -13,6 +13,7 @@ from quantem.tomography.tomography_opt import TomographyOpt from quantem.tomography.utils import torch_phase_cross_correlation from quantem.tomography_old.utils import gaussian_filter_2d_stack, gaussian_kernel_1d +import torch.distributed as dist class Tomography(TomographyOpt, TomographyBase, DDPMixin): @@ -100,6 +101,8 @@ def reconstruct( print("num_samples_per_ray schedule provided.") print(f"N: {N}, num_samples_per_ray: {num_samples_per_ray}") + + for a0 in range(num_iter): consistency_loss = 0.0 @@ -114,7 +117,9 @@ def reconstruct( curr_num_samples_per_ray = num_samples_per_ray[a0][1] else: curr_num_samples_per_ray = num_samples_per_ray - print(f"curr_num_samples_per_ray: {curr_num_samples_per_ray}") + + if self.global_rank == 0: + print(f"curr_num_samples_per_ray: {curr_num_samples_per_ray}") for batch_idx, batch in enumerate(self.dataloader): self.zero_grad_all() with torch.autocast( @@ -123,9 +128,6 @@ def reconstruct( enabled=True, ): all_coords = self.dset.get_coords(batch, N, curr_num_samples_per_ray) - # all_coords = all_coords.to( - # device=self.device, dtype=torch.float32, non_blocking=True - # ) all_densities = self.obj_model.forward(all_coords) @@ -133,7 +135,6 @@ def reconstruct( all_densities, curr_num_samples_per_ray, len(batch["target_value"]) ) - # batch_consistency_loss = loss_func(integrated_densities, batch["target_value"]) pred = integrated_densities.float() target = batch["target_value"].to(self.device, non_blocking=True).float() @@ -151,30 +152,51 @@ def reconstruct( consistency_loss += batch_consistency_loss.item() # TODO: Maybe reorganize the losses so that the order makes sense lol. + total_loss = total_loss / len(self.dataloader) consistency_loss = consistency_loss / len(self.dataloader) epoch_soft_constraint_loss = epoch_soft_constraint_loss / len(self.dataloader) - print(f"Total Loss: {total_loss:.4f}, Consistency Loss: {consistency_loss:.4f}") + + metrics = torch.tensor( + [total_loss, consistency_loss, epoch_soft_constraint_loss], device=self.device + ) + + if self.world_size > 1: + dist.all_reduce(metrics, dist.ReduceOp.AVG) + + total_loss, consistency_loss, epoch_soft_constraint_loss = metrics.tolist() + + if self.global_rank == 0: + print(f"Total Loss: {total_loss:.4f}, Consistency Loss: {consistency_loss:.4f}") self._epoch_losses.append(total_loss) self._consistency_losses.append(consistency_loss) self.obj_model._soft_constraint_losses.append(epoch_soft_constraint_loss) - if self.logger is not None and self.global_rank == 0: - self.logger.log_iter( - object_model=self.obj_model, - iter=a0, - consistency_loss=consistency_loss, - total_loss=total_loss, - num_samples_per_ray=curr_num_samples_per_ray, - ) - if self.logger.log_images_every > 0 and a0 % self.logger.log_images_every == 0: - self.logger.log_iter_images( + if self.logger is not None: + if self.logger.log_images_every > 0 and self.num_epochs % self.logger.log_images_every == 0: + print("Creating volume...") + pred_full = self.obj_model.create_volume(return_vol=True) + + if self.global_rank == 0: + print("Logging images...") + self.logger.log_iter_images( + pred_volume=pred_full, + dataset_model=self.dset, + iter=self.num_epochs, + ) + + + if self.global_rank == 0: + self.logger.log_iter( object_model=self.obj_model, - dataset_model=self.dset, - iter=a0, + iter=self.num_epochs, + consistency_loss=consistency_loss, + total_loss=total_loss, + num_samples_per_ray=curr_num_samples_per_ray, ) + self.logger.flush() diff --git a/src/quantem/tomography_old/tomography.py b/src/quantem/tomography_old/tomography.py index 7be24397..ec8c5db3 100644 --- a/src/quantem/tomography_old/tomography.py +++ b/src/quantem/tomography_old/tomography.py @@ -1,883 +1,883 @@ -# import matplotlib.pyplot as plt -# import numpy as np -# import torch -# import torch.distributed as dist -# from torch.nn import SmoothL1Loss -# from torch.utils.tensorboard import SummaryWriter - -# # from torch_radon.radon import ParallelBeam as Radon -# from tqdm.auto import tqdm - -# from quantem.core.ml.loss_functions import ( -# CharbonnierLoss, -# L1Loss, -# LLMSELoss, -# MSELogMSELoss, -# MSELoss, -# ) - -# # Temporary imports for TomographyNERF -# from quantem.tomography.object_models import ObjectINN, ObjectVoxelwise -# from quantem.tomography.tomography_base import TomographyBase -# from quantem.tomography.tomography_conv import TomographyConv -# from quantem.tomography.tomography_ml import TomographyML -# from quantem.tomography.utils import differentiable_shift_2d, gaussian_kernel_1d, rot_ZXZ - - -# # Temporary aux class for TomographyNERF -# # TODO: Maybe put this in INN? -# def get_num_samples_per_ray(N: int, epoch: int): -# """Increase number of samples per ray at specific epochs.""" -# # Exponential schedule - -# epochs = np.linspace(0, 10, 5, dtype=int) -# # schedule = np.linspace(20, N, 5, dtype=int) -# schedule = np.array([20, 100, 150, 200, 200]) -# # schedule = np.array([200, 200, 200, 200, 200]) -# # schedule = np.array([20, 100, 250, 500, 500]) -# # schedule = np.array([500, 500, 500, 500, 500]) -# # schedule = np.array([300, 300, 300, 300, 300]) -# # schedule = np.exp(schedule) -# schedule_warmup = dict[int, int](zip(epochs, schedule)) - -# for epoch_threshold, samples in schedule_warmup.items(): -# if epoch >= epoch_threshold: -# num_samples = samples - -# return num_samples - - -# class Tomography(TomographyConv, TomographyML, TomographyBase, TomographyDDP): -# """ -# Top level class for either using conventional or ML-based reconstruction methods -# for tomography. -# """ - -# def __init__( -# self, -# dataset, -# volume_obj, -# device, -# _token, -# ): -# super().__init__(dataset, volume_obj, device, _token) - -# # TODO: More elegant way of doing this. -# self.global_epochs = 0 -# self.ddp_instantiated = False -# self.pretraining_instantiated = False - -# # --- Reconstruction Method --- - -# def sirt_recon( -# self, -# num_iterations: int = 10, -# inline_alignment: bool = False, -# enforce_positivity: bool = True, -# volume_shape: tuple = None, -# reset: bool = True, -# smoothing_sigma: float = None, -# shrinkage: float = None, -# filter_name: str = "hamming", -# circle: bool = True, -# plot_loss: bool = False, -# ): -# num_angles, num_rows, num_cols = self.dataset.tilt_series.shape -# sirt_tilt_series = self.dataset.tilt_series.clone() -# sirt_tilt_series = sirt_tilt_series.permute(2, 0, 1) - -# hard_constraints = { -# "positivity": enforce_positivity, -# "shrinkage": shrinkage, -# } -# self.volume_obj.hard_constraints = hard_constraints - -# if volume_shape is None: -# volume_shape = (num_rows, num_rows, num_rows) -# else: -# D, H, W = volume_shape - -# if reset: -# self.volume_obj.reset() -# self.loss = [] - -# proj_forward = torch.zeros_like(self.dataset.tilt_series) - -# pbar = tqdm(range(num_iterations), desc="SIRT Reconstruction") - -# if smoothing_sigma is not None: -# gaussian_kernel = gaussian_kernel_1d(smoothing_sigma).to(self.device) -# else: -# gaussian_kernel = None - -# print( -# "Devices", -# sirt_tilt_series.device, -# proj_forward.device, -# self.dataset.tilt_angles.device, -# ) - -# for iter in pbar: -# proj_forward, loss = self._sirt_run_epoch( -# tilt_series=sirt_tilt_series, -# proj_forward=proj_forward, -# angles=self.dataset.tilt_angles, -# inline_alignment=iter > 0 and inline_alignment, -# filter_name=filter_name, -# gaussian_kernel=gaussian_kernel, -# circle=circle, -# ) - -# pbar.set_description(f"SIRT Reconstruction | Loss: {loss.item():.4f}") - -# self.loss.append(loss.item()) - -# self.sirt_recon_vol = self.volume_obj - -# # Permutation due to sinogram ordering. -# self.sirt_recon_vol.obj = self.sirt_recon_vol.obj.permute(1, 2, 0) - -# if plot_loss: -# self.plot_loss() - -# # TODO: ML Recon which has NeRF and AD depending on the object type. -# # TODO: Temporary - -# def create_batch_projection_rays(self, pixel_i, pixel_j, N, num_samples_per_ray): -# """Create projection rays for entire batch simultaneously.""" -# batch_size = len(pixel_i) - -# # Convert all pixels to normalized coordinates -# x_coords = (pixel_j / (N - 1)) * 2 - 1 -# y_coords = (pixel_i / (N - 1)) * 2 - 1 -# # TODO: maybe pixel_j.device? -# # Create z coordinates -# z_coords = torch.linspace(-1, 1, num_samples_per_ray, device=self.device) - -# # Create rays for all pixels: [batch_size, num_samples_per_ray, 3] -# rays = torch.zeros(batch_size, num_samples_per_ray, 3, device=self.device) - -# # Fill coordinates efficiently -# rays[:, :, 0] = x_coords.unsqueeze(1) # x constant per ray -# rays[:, :, 1] = y_coords.unsqueeze(1) # y constant per ray -# rays[:, :, 2] = z_coords.unsqueeze(0) # z varies along ray - -# return rays - -# @torch.compile(mode="reduce-overhead") -# def transform_batch_ray_coordinates(self, rays, z1, x, z3, shifts, N, sampling_rate): -# # Step 1: Apply shifts -# shift_x_norm = (shifts[:, 0:1] * sampling_rate * 2) / (N - 1) -# shift_y_norm = (shifts[:, 1:2] * sampling_rate * 2) / (N - 1) - -# rays_x = rays[:, :, 0] - shift_x_norm -# rays_y = rays[:, :, 1] - shift_y_norm -# rays_z = rays[:, :, 2] - -# # Rotation 1: Z(-z3) -# theta = torch.deg2rad(-z3).view(-1, 1) -# cos_t = torch.cos(theta) -# sin_t = torch.sin(theta) - -# rays_x_rot1 = cos_t * rays_x - sin_t * rays_y -# rays_y_rot1 = sin_t * rays_x + cos_t * rays_y -# rays_z_rot1 = rays_z - -# # Rotation 2: X(x) -# theta = torch.deg2rad(x).view(-1, 1) -# cos_t = torch.cos(theta) -# sin_t = torch.sin(theta) - -# rays_x_rot2 = rays_x_rot1 -# rays_y_rot2 = cos_t * rays_y_rot1 - sin_t * rays_z_rot1 -# rays_z_rot2 = sin_t * rays_y_rot1 + cos_t * rays_z_rot1 - -# # Rotation 3: Z(-z1) -# theta = torch.deg2rad(-z1).view(-1, 1) -# cos_t = torch.cos(theta) -# sin_t = torch.sin(theta) - -# rays_x_final = cos_t * rays_x_rot2 - sin_t * rays_y_rot2 -# rays_y_final = sin_t * rays_x_rot2 + cos_t * rays_y_rot2 -# rays_z_final = rays_z_rot2 - -# # Stack the final result -# transformed_rays = torch.stack([rays_x_final, rays_y_final, rays_z_final], dim=2) - -# return transformed_rays - -# # TODO: Temp logger -# def setup_logger(self, log_path): -# if self.global_rank == 0: -# self.temp_logger = SummaryWriter(log_dir=log_path) -# else: -# self.temp_logger = None - -# def pretrain( -# self, -# # volume_dataset: PretrainVolumeDataset, -# obj: ObjectINN, -# batch_size: int, -# soft_constraints: dict = None, -# log_path: str = None, -# optimizer_params: dict = None, -# epochs: int = 100, -# viz_freq: int = 1, -# consistency_criterion: str = "mse", -# ): -# if not self.pretraining_instantiated: -# self.setup_distributed() -# # self.setup_pretraining_dataloader(volume_dataset, batch_size) -# self.pretraining_instantiated = True -# obj.model = self.build_model(obj._model) -# self.obj = obj - -# if soft_constraints is not None: -# self.obj.soft_constraints = soft_constraints - -# if not hasattr(self, "temp_logger"): -# self.setup_logger(log_path=log_path) - -# if optimizer_params is not None: -# optimizer_params = self.scale_lr(optimizer_params) -# self.optimizer_params = optimizer_params -# self.set_optimizers() -# device_type = self.device.type -# consistency_loss_fn = None -# if consistency_criterion[0].lower() == "mse": -# consistency_loss_fn = MSELoss() -# elif consistency_criterion[0].lower() == "l1": -# consistency_loss_fn = L1Loss() -# elif consistency_criterion[0].lower() == "mse_log": -# consistency_loss_fn = MSELogMSELoss() -# elif consistency_criterion[0].lower() == "llmse": -# consistency_loss_fn = LLMSELoss() -# elif consistency_criterion[0].lower() == "smooth_l1": -# consistency_loss_fn = SmoothL1Loss(beta=consistency_criterion[1]) -# elif consistency_criterion[0].lower() == "adaptive_smooth_l1": -# # consistency_loss_fn = AdaptiveSmoothL1LossDDP( -# # beta_init=consistency_criterion[1], ema_factor=0.99, eps=1e-8 -# # ) -# pass -# elif consistency_criterion[0].lower() == "charbonnier": -# consistency_loss_fn = CharbonnierLoss(epsilon=1e-12, reduction="mean") -# else: -# raise ValueError(f"Invalid consistency criterion: {consistency_criterion}") - -# for epoch in range(epochs): -# if self.pretraining_sampler is not None: -# self.pretraining_sampler.set_epoch(epoch) - -# self.obj.model.train() - -# epoch_loss = 0.0 -# epoch_consistency_loss = 0.0 -# epoch_tv_loss = 0.0 -# num_batches = 0 - -# for batch_idx, batch in enumerate(self.pretraining_dataloader): -# coords = batch["coords"].to(self.device, non_blocking=True) -# target = batch["target"].to(self.device, non_blocking=True) - -# for _, opt in self.optimizers.items(): -# opt.zero_grad() - -# with torch.autocast(device_type=device_type, dtype=torch.bfloat16, enabled=True): -# outputs = self.obj.forward(coords) -# consistency_loss = consistency_loss_fn(outputs, target) -# tv_loss = self.obj.apply_soft_constraints(coords) - -# loss = consistency_loss + tv_loss - -# loss.backward() - -# epoch_loss += loss.detach() -# epoch_consistency_loss += consistency_loss.detach() -# epoch_tv_loss += tv_loss.detach() - -# torch.nn.utils.clip_grad_norm_(self.obj.model.parameters(), max_norm=1.0) - -# for _, opt in self.optimizers.items(): -# opt.step() -# epoch_loss += loss.detach() -# num_batches += 1 - -# if epoch % viz_freq == 0 or epoch == epochs - 1: -# avg_loss = epoch_loss / num_batches -# with torch.no_grad(): -# self.obj.create_volume( -# world_size=self.world_size, -# global_rank=self.global_rank, -# # ray_size=volume_dataset.N, -# ) -# pred_full = self.obj.obj -# loss_tensor = avg_loss.clone().detach() -# avg_loss = loss_tensor.item() -# avg_tv_loss = epoch_tv_loss / num_batches -# avg_consistency_loss = epoch_consistency_loss / num_batches - -# metrics = torch.tensor( -# [avg_loss, avg_consistency_loss, avg_tv_loss], device=self.device -# ) -# if self.world_size > 1: -# dist.all_reduce(metrics, op=dist.ReduceOp.AVG) -# avg_loss, avg_consistency_loss, avg_tv_loss = metrics.tolist() - -# if self.global_rank == 0: -# self.temp_logger.add_scalar("Pretrain/total_loss", avg_loss, epoch) -# self.temp_logger.add_scalar( -# "Pretrain/consistency_loss", avg_consistency_loss, epoch -# ) -# self.temp_logger.add_scalar("Pretrain/tv_loss", avg_tv_loss, epoch) - -# # current_lr = self.schedulers["model"].get_last_lr()[0] -# # self.temp_logger.add_scalar("Pretrain/lr", current_lr, epoch) - -# fig, ax = plt.subplots(ncols=4, figsize=(36, 12)) -# ax[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap="turbo", vmin=0) -# ax[0].set_title("Sum over Z-axis") -# ax[1].matshow( -# # pred_full[volume_dataset.N // 2].cpu().numpy(), cmap="turbo", vmin=0 -# # ) -# # ax[1].set_title(f"Slice at Z={volume_dataset.N // 2}") -# ax[2].matshow(pred_full.sum(dim=1).cpu().numpy(), cmap="turbo", vmin=0) -# ax[2].set_title("Sum over Y-axis") -# ax[3].matshow(pred_full.sum(dim=2).cpu().numpy(), cmap="turbo", vmin=0) -# ax[3].set_title("Sum over X-axis") -# self.temp_logger.add_figure("Pretrain/viz", fig, epoch, close=True) - -# plt.close(fig) -# print( -# f"Epoch [{epoch}/{epochs}] Pretrain Total Loss: {avg_loss:.6f}, Consistency Loss: {avg_consistency_loss:.6f}, TV Loss: {avg_tv_loss:.6f}" -# ) -# self.global_epochs += 1 -# if self.global_rank == 0: -# print(f"Pretraining Completed, saving model weights to {log_path}/model_weights.pth") -# torch.save(self.obj.model.state_dict(), f"{log_path}/model_weights.pth") - -# def recon( -# self, -# obj: ObjectINN, -# batch_size: int, -# num_workers: int = 0, -# epochs=20, -# use_amp=True, -# viz_freq=1, -# checkpoint_freq=5, -# optimizer_params: dict = None, -# scheduler_params: dict = None, -# soft_constraints: dict = None, -# vol_save_path: str = None, # TODO: TEMPORARY -# log_path=None, -# val_fraction: float = 0.0, -# learn_shifts: bool = True, -# # l1_loss: bool = False, -# consistency_criterion: str = "mse", -# model_weights_path: str = None, -# force_cpu: bool = False, -# ): -# if not self.ddp_instantiated: -# if not self.pretraining_instantiated: -# self.setup_distributed() -# if model_weights_path is not None: -# print(f"Loading model weights from {model_weights_path}") -# state_dict = torch.load(model_weights_path, map_location="cpu") - -# # Handle DataParallel/DDP checkpoints -# if any(k.startswith("module.") for k in state_dict.keys()): -# new_state_dict = { -# k.replace("module.", ""): v for k, v in state_dict.items() -# } -# else: -# new_state_dict = state_dict - -# obj.model.load_state_dict(new_state_dict) -# print("Model weights loaded successfully") -# obj.model = self.build_model(obj._model) -# self.obj = obj - -# self.setup_dataloader(self.dataset, batch_size, num_workers, val_fraction) -# self.ddp_instantiated = True - -# if soft_constraints is not None: -# self.obj.soft_constraints = soft_constraints - -# if not hasattr(self, "temp_logger"): -# self.setup_logger(log_path=log_path) - -# zero_tilt_idx = torch.argmin(torch.abs(self.dataset.tilt_angles)).item() - -# if self.global_rank == 0: -# print( -# f"Using projection {zero_tilt_idx} (angle={self.dataset.tilt_angles[zero_tilt_idx]:.2f}°) as reference" -# ) -# print(f"Using consistency criterion: {consistency_criterion}") - -# # Auxiliary params setup -# self.dataset.setup_auxiliary_params(zero_tilt_idx, self.device, learn_shifts) -# aux_params = self.dataset.auxiliary_params -# # Scaling learning rates to account for distributed training -# if optimizer_params is not None: -# optimizer_params = self.scale_lr(optimizer_params) -# self.optimizer_params = optimizer_params -# self.set_optimizers() - -# if scheduler_params is not None: -# self.scheduler_params = scheduler_params -# self.set_schedulers(self.scheduler_params, num_iter=epochs) - -# aux_norm = torch.tensor(0.0, device=self.device) -# model_norm = torch.tensor(0.0, device=self.device) - -# for _, opt in self.optimizers.items(): -# opt.zero_grad() - -# N = max(self.dataset.dims) - -# device_type = self.device.type -# autocast_dtype = torch.bfloat16 if use_amp else None - -# for epoch in range(epochs): -# num_samples_per_ray = get_num_samples_per_ray(N=N, epoch=self.global_epochs) - -# # num_samples_per_ray = get_num_samples_per_ray(epoch) -# # Log the change if it happens -# if self.global_rank == 0: -# print(f"Epoch {epoch}: num_samples_per_ray = {num_samples_per_ray}") - -# if self.global_rank == 0 and self.global_epochs > 0: -# prev_samples = get_num_samples_per_ray(N=N, epoch=self.global_epochs - 1) - -# if num_samples_per_ray != prev_samples: -# print( -# f"Epoch {epoch}: Changing num_samples_per_ray from {prev_samples} to {num_samples_per_ray}" -# ) - -# if self.sampler is not None: -# self.sampler.set_epoch(epoch) - -# epoch_loss = 0.0 -# epoch_consistency_loss = 0.0 -# epoch_tv_loss = 0.0 -# epoch_z1_loss = 0.0 - -# num_batches = 0 -# consistency_loss_fn = None -# if consistency_criterion[0].lower() == "mse": -# consistency_loss_fn = MSELoss() -# elif consistency_criterion[0].lower() == "l1": -# consistency_loss_fn = L1Loss() -# elif consistency_criterion[0].lower() == "mse_log": -# consistency_loss_fn = MSELogMSELoss() -# elif consistency_criterion[0].lower() == "llmse": -# consistency_loss_fn = LLMSELoss() -# elif consistency_criterion[0].lower() == "smooth_l1": -# consistency_loss_fn = SmoothL1Loss(beta=consistency_criterion[1]) -# elif consistency_criterion[0].lower() == "adaptive_smooth_l1": -# # consistency_loss_fn = AdaptiveSmoothL1LossDDP( -# # beta_init=consistency_criterion[1], ema_factor=0.99, eps=1e-8 -# # ) -# pass -# elif consistency_criterion[0].lower() == "charbonnier": -# consistency_loss_fn = CharbonnierLoss(epsilon=1e-12, reduction="mean") -# else: -# raise ValueError(f"Invalid consistency criterion: {consistency_criterion}") - -# for batch_idx, batch in enumerate(self.dataloader): -# projection_indices = batch["projection_idx"] - -# pixel_i = batch["pixel_i"].float().to(self.device, non_blocking=True) -# pixel_j = batch["pixel_j"].float().to(self.device, non_blocking=True) -# target_values = batch["target_value"].to(self.device, non_blocking=True) -# phis = batch["phi"].to(self.device, non_blocking=True) -# projection_indices = batch["projection_idx"].to(self.device, non_blocking=True) - -# shifts, z1_params, z3_params = aux_params(None) -# batch_shifts = torch.index_select(shifts, 0, projection_indices) -# batch_z1 = torch.index_select(z1_params, 0, projection_indices) -# batch_z3 = torch.index_select(z3_params, 0, projection_indices) - -# with torch.autocast( -# device_type=device_type, dtype=autocast_dtype, enabled=use_amp -# ): -# with torch.no_grad(): -# batch_ray_coords = self.create_batch_projection_rays( -# pixel_i, pixel_j, N, num_samples_per_ray -# ) - -# # TODO: .forward passing z1, x, z3, shifts, N, sampling_rate -# transformed_rays = self.transform_batch_ray_coordinates( -# batch_ray_coords, -# z1=batch_z1, -# x=phis, -# z3=batch_z3, -# shifts=batch_shifts, -# N=N, -# sampling_rate=1.0, -# ) - -# all_coords = transformed_rays.view(-1, 3) - -# all_densities = self.obj.forward(all_coords) - -# tv_loss = self.obj.apply_soft_constraints(all_coords) -# ray_densities = all_densities.view( -# len(target_values), num_samples_per_ray -# ) # Reshape rays and integarte -# step_size = 2.0 / (num_samples_per_ray - 1) - -# predicted_values = ray_densities.sum(dim=1) * step_size - -# consistency_loss = consistency_loss_fn(predicted_values, target_values) -# tv_loss_z1 = torch.tensor(0.0, device=self.device) - -# loss = consistency_loss + tv_loss + tv_loss_z1 - -# loss.backward() - -# epoch_loss += loss.detach() -# epoch_consistency_loss += consistency_loss.detach() -# epoch_tv_loss += tv_loss.detach() -# epoch_z1_loss += tv_loss_z1.detach() -# num_batches += 1 - -# for key, opt in self.optimizers.items(): -# if key == "model": -# model_norm = torch.nn.utils.clip_grad_norm_( -# self.obj.model.parameters(), max_norm=1 -# ) -# elif key == "aux_params": -# aux_norm = torch.nn.utils.clip_grad_norm_( -# self.dataset.auxiliary_params.parameters(), max_norm=1 -# ) -# opt.step() -# opt.zero_grad() - -# for key, sched in self.schedulers.items(): -# if isinstance(sched, torch.optim.lr_scheduler.ReduceLROnPlateau): -# sched.step(epoch_loss) -# else: -# sched.step() - -# if epoch % viz_freq == 0 or epoch == epochs - 1 or epoch % checkpoint_freq == 0: -# with torch.no_grad(): -# self.obj.create_volume( -# world_size=self.world_size, -# global_rank=self.global_rank, -# ray_size=num_samples_per_ray, -# ) -# pred_full = self.obj.obj -# avg_loss = epoch_loss.item() / num_batches -# avg_consistency_loss = epoch_consistency_loss.item() / num_batches -# avg_tv_loss = epoch_tv_loss.item() / num_batches -# avg_z1_loss = epoch_z1_loss.item() / num_batches -# shifts, z1, z3 = self.dataset.auxiliary_params.forward() -# shifts = shifts.detach().cpu() -# z1 = z1.detach().cpu() -# z3 = z3.detach().cpu() -# metrics = torch.tensor( -# [avg_loss, avg_consistency_loss, avg_tv_loss, avg_z1_loss], -# device=self.device, -# ) -# if self.world_size > 1: -# dist.all_reduce(metrics, op=dist.ReduceOp.AVG) -# avg_loss, avg_consistency_loss, avg_tv_loss, avg_z1_loss = metrics.tolist() - -# if hasattr(self, "val_dataloader") and self.val_dataloader is not None: -# val_loss = self.validate( -# aux_params, num_samples_per_ray, device_type, autocast_dtype, use_amp, N -# ) - -# if self.global_rank == 0 and (epoch % viz_freq == 0 or epoch == epochs - 1): -# with torch.no_grad(): -# current_lr = self.schedulers["model"].get_last_lr()[0] - -# # Log metrics -# self.temp_logger.add_scalar( -# f"train/consistency_loss_{consistency_criterion[0] if isinstance(consistency_criterion, tuple) else consistency_criterion}", -# avg_consistency_loss, -# self.global_epochs, -# ) -# if hasattr(self, "val_dataloader") and self.val_dataloader is not None: -# self.temp_logger.add_scalar( -# f"val/consistency_loss_{consistency_criterion[0] if isinstance(consistency_criterion, tuple) else consistency_criterion}", -# val_loss, -# self.global_epochs, -# ) -# self.temp_logger.add_scalar("train/z1_loss", avg_z1_loss, self.global_epochs) -# # if tv_weight > 0: -# self.temp_logger.add_scalar("train/tv_loss", avg_tv_loss, self.global_epochs) -# self.temp_logger.add_scalar("train/total_loss", avg_loss, self.global_epochs) -# self.temp_logger.add_scalar( -# "train/model_grad_norm", model_norm.item(), self.global_epochs -# ) -# self.temp_logger.add_scalar( -# "train/aux_grad_norm", aux_norm.item(), self.global_epochs -# ) -# self.temp_logger.add_scalar("train/lr", current_lr, self.global_epochs) -# self.temp_logger.add_scalar( -# "train/num_samples_per_ray", num_samples_per_ray, self.global_epochs -# ) -# if consistency_criterion[0].lower() == "adaptive_smooth_l1": -# self.temp_logger.add_scalar( -# "train/beta_2", consistency_loss_fn.beta2.item(), self.global_epochs -# ) -# fig, axes = plt.subplots(1, 5, figsize=(36, 12)) -# axes[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap="turbo", vmin=0) -# axes[0].set_title("Sum over Z-axis") - -# axes[1].matshow(pred_full[N // 2].cpu().numpy(), cmap="turbo", vmin=0) -# axes[1].set_title(f"Slice at Z={N // 2}") - -# slice_start = max(0, N // 2 - 5) -# slice_end = min(N, N // 2 + 6) -# thick_slice = pred_full[slice_start:slice_end].sum(dim=0).cpu().numpy() -# axes[2].matshow(thick_slice, cmap="turbo", vmin=0) -# axes[2].set_title(f"Thick slice sum (Z={slice_start}:{slice_end - 1})") - -# axes[3].matshow(pred_full.sum(dim=1).cpu().numpy(), cmap="turbo", vmin=0) -# axes[3].set_title("Sum over Y-axis") - -# axes[4].matshow(pred_full.sum(dim=2).cpu().numpy(), cmap="turbo", vmin=0) -# axes[4].set_title("Sum over X-axis") - -# self.temp_logger.add_figure("train/viz", fig, self.global_epochs, close=True) - -# fig, axes = plt.subplots(ncols=3, figsize=(10, 5)) -# axes[0].plot(shifts[:, 0].cpu().numpy(), label="Shifts X") -# axes[0].plot(shifts[:, 1].cpu().numpy(), label="Shifts Y") -# axes[0].legend() -# axes[1].plot(z1.cpu().numpy(), label="Z1") -# axes[2].plot(z3.cpu().numpy(), label="Z3") -# self.temp_logger.add_figure( -# "train/auxiliary_params", fig, self.global_epochs, close=True -# ) -# plt.close(fig) - -# if epoch % checkpoint_freq == 0 or self.global_epochs == epochs - 1: -# with torch.no_grad(): -# if self.global_rank == 0: -# save_path = f"{vol_save_path}/volume_epoch_{self.global_epochs:04d}.pt" -# torch.save(pred_full.cpu(), save_path) - -# self.global_epochs += 1 - -# if self.global_rank == 0: -# print("Training complete.") - -# torch.save(self.dataset.auxiliary_params.forward(), f"{log_path}/auxiliary_params.pt") -# # print("Successfully setup DDP and dataloader") - -# def validate(self, aux_params, num_samples_per_ray, device_type, autocast_dtype, use_amp, N): -# """Validate the model on the validation set.""" -# self.obj.model.eval() -# aux_params.eval() - -# val_loss = 0.0 -# num_batches = 0 - -# with torch.no_grad(): -# for batch in self.val_dataloader: -# pixel_i = batch["pixel_i"].float().to(self.device, non_blocking=True) -# pixel_j = batch["pixel_j"].float().to(self.device, non_blocking=True) -# target_values = batch["target_value"].to(self.device, non_blocking=True) -# phis = batch["phi"].to(self.device, non_blocking=True) -# projection_indices = batch["projection_idx"].to(self.device, non_blocking=True) - -# shifts, z1_params, z3_params = aux_params(None) -# batch_shifts = torch.index_select(shifts, 0, projection_indices) -# batch_z1 = torch.index_select(z1_params, 0, projection_indices) -# batch_z3 = torch.index_select(z3_params, 0, projection_indices) - -# with torch.autocast( -# device_type=device_type, dtype=autocast_dtype, enabled=use_amp -# ): -# with torch.no_grad(): -# batch_ray_coords = self.create_batch_projection_rays( -# pixel_i, pixel_j, N, num_samples_per_ray -# ) - -# transformed_rays = self.transform_batch_ray_coordinates( -# batch_ray_coords, -# z1=batch_z1, -# x=phis, -# z3=batch_z3, -# shifts=batch_shifts, -# N=N, -# sampling_rate=1.0, -# ) - -# all_coords = transformed_rays.view(-1, 3) - -# all_densities = self.obj.forward(all_coords) - -# ray_densities = all_densities.view( -# len(target_values), num_samples_per_ray -# ) # Reshape rays and integarte -# step_size = 2.0 / (num_samples_per_ray - 1) - -# predicted_values = ray_densities.sum(dim=1) * step_size - -# mse_loss = torch.nn.functional.mse_loss(predicted_values, target_values) - -# loss = mse_loss - -# val_loss += loss.detach() -# num_batches += 1 - -# avg_val_loss = val_loss / num_batches if num_batches > 0 else 0.0 - -# if self.world_size > 1: -# val_loss_tensor = avg_val_loss.detach().clone() -# dist.all_reduce(val_loss_tensor, op=dist.ReduceOp.AVG) -# avg_val_loss = val_loss_tensor.item() - -# self.obj.model.train() -# aux_params.train() - -# return avg_val_loss - -# def ad_recon( -# self, -# optimizer_params: dict, -# num_iter: int = 0, -# reset: bool = False, -# scheduler_params: dict | None = None, -# hard_constraints: dict | None = None, -# soft_constraints: dict | None = None, -# # store_iterations: bool | None = None, -# # store_iterations_every: int | None = None, -# # autograd: bool = True, -# ): -# if reset: -# self.reset_recon() - -# self.hard_constraints = hard_constraints -# self.soft_constraints = soft_constraints - -# # Make sure everything is in the correct device, might be redundant/cleaner way to do this -# self.dataset.to(self.device) -# self.volume_obj.to(self.device) - -# # Making optimizable parameters into leaf tensors. -# self.dataset.shifts = self.dataset.shifts.detach().to(self.device).requires_grad_(True) -# self.dataset.z1_angles = ( -# self.dataset.z1_angles.detach().to(self.device).requires_grad_(True) -# ) -# self.dataset.z3_angles = ( -# self.dataset.z3_angles.detach().to(self.device).requires_grad_(True) -# ) - -# if optimizer_params is not None: -# self.optimizer_params = optimizer_params -# self.set_optimizers() - -# if scheduler_params is not None: -# self.scheduler_params = scheduler_params -# self.set_schedulers(self.scheduler_params, num_iter=num_iter) - -# if hard_constraints is not None: -# self.volume_obj.hard_constraints = hard_constraints -# if soft_constraints is not None: -# self.volume_obj.soft_constraints = soft_constraints - -# pbar = tqdm(range(num_iter), desc="AD Reconstruction") - -# for a0 in pbar: -# total_loss = 0.0 -# tilt_series_loss = 0.0 - -# pred_volume = self.volume_obj.forward() - -# for i in range(len(self.dataset.tilt_series)): -# forward_projection = self.projection_operator( -# vol=pred_volume, -# z1=self.dataset.z1_angles[i], -# x=self.dataset.tilt_angles[i], -# z3=self.dataset.z3_angles[i], -# shift_x=self.dataset.shifts[i, 0], -# shift_y=self.dataset.shifts[i, 1], -# device=self.device, -# ) - -# tilt_series_loss += torch.nn.functional.mse_loss( -# forward_projection, self.dataset.tilt_series[i] -# ) -# tilt_series_loss /= len(self.dataset.tilt_series) - -# total_loss = tilt_series_loss + self.volume_obj.soft_loss -# self.loss.append(total_loss.item()) - -# total_loss.backward() - -# for opt in self.optimizers.values(): -# opt.step() -# opt.zero_grad() - -# if self.schedulers is not None: -# for sch in self.schedulers.values(): -# if isinstance(sch, torch.optim.lr_scheduler.ReduceLROnPlateau): -# sch.step(total_loss) -# elif sch is not None: -# sch.step() - -# pbar.set_description(f"AD Reconstruction | Loss: {total_loss:.4f}") - -# if self.logger is not None: -# self.logger.log_scalar("loss/total", total_loss.item(), a0) -# self.logger.log_scalar("loss/tilt_series", tilt_series_loss.item(), a0) -# self.logger.log_scalar( -# "loss/soft constraints", self.volume_obj.soft_loss.item(), a0 -# ) - -# if a0 % self.logger.log_images_every == 0: -# self.logger.projection_images( -# volume_obj=self.volume_obj, -# epoch=a0, -# ) -# self.logger.tilt_angles_figure(dataset=self.dataset, step=a0) - -# self.logger.flush() - -# self.ad_recon_vol = self.volume_obj.forward() - -# return self - -# def reset_recon(self) -> None: -# if isinstance(self.volume_obj, ObjectVoxelwise): -# self.volume_obj.reset() - -# self.ad_recon_vol = None - -# # --- Projection Operators ---- -# def projection_operator( -# self, -# vol, -# z1, -# x, -# z3, -# shift_x, -# shift_y, -# device, -# ): -# projection = ( -# rot_ZXZ( -# mags=vol.unsqueeze(0), # Add batch dimension -# z1=z1, -# x=-x, -# z3=z3, -# device=device, -# mode="bilinear", -# ) -# .squeeze() -# .sum(axis=0) -# ) - -# shifted_projection = differentiable_shift_2d( -# image=projection, -# shift_x=shift_x, -# shift_y=shift_y, -# sampling_rate=1.0, # Assuming 1 pixel = 1 physical unit -# ) - -# return shifted_projection +import matplotlib.pyplot as plt +import numpy as np +import torch +import torch.distributed as dist +from torch.nn import SmoothL1Loss +from torch.utils.tensorboard import SummaryWriter + +# from torch_radon.radon import ParallelBeam as Radon +from tqdm.auto import tqdm + +from quantem.core.ml.loss_functions import ( + CharbonnierLoss, + L1Loss, + LLMSELoss, + MSELogMSELoss, + MSELoss, +) + +# Temporary imports for TomographyNERF +from quantem.tomography.object_models import ObjectINN, ObjectVoxelwise +from quantem.tomography.tomography_base import TomographyBase +from quantem.tomography.tomography_conv import TomographyConv +from quantem.tomography.tomography_ml import TomographyML +from quantem.tomography.utils import differentiable_shift_2d, gaussian_kernel_1d, rot_ZXZ + + +# Temporary aux class for TomographyNERF +# TODO: Maybe put this in INN? +def get_num_samples_per_ray(N: int, epoch: int): + """Increase number of samples per ray at specific epochs.""" + # Exponential schedule + + epochs = np.linspace(0, 10, 5, dtype=int) + # schedule = np.linspace(20, N, 5, dtype=int) + schedule = np.array([20, 100, 150, 200, 200]) + # schedule = np.array([200, 200, 200, 200, 200]) + # schedule = np.array([20, 100, 250, 500, 500]) + # schedule = np.array([500, 500, 500, 500, 500]) + # schedule = np.array([300, 300, 300, 300, 300]) + # schedule = np.exp(schedule) + schedule_warmup = dict[int, int](zip(epochs, schedule)) + + for epoch_threshold, samples in schedule_warmup.items(): + if epoch >= epoch_threshold: + num_samples = samples + + return num_samples + + +class Tomography(TomographyConv, TomographyML, TomographyBase, TomographyDDP): + """ + Top level class for either using conventional or ML-based reconstruction methods + for tomography. + """ + + def __init__( + self, + dataset, + volume_obj, + device, + _token, + ): + super().__init__(dataset, volume_obj, device, _token) + + # TODO: More elegant way of doing this. + self.global_epochs = 0 + self.ddp_instantiated = False + self.pretraining_instantiated = False + + # --- Reconstruction Method --- + + def sirt_recon( + self, + num_iterations: int = 10, + inline_alignment: bool = False, + enforce_positivity: bool = True, + volume_shape: tuple = None, + reset: bool = True, + smoothing_sigma: float = None, + shrinkage: float = None, + filter_name: str = "hamming", + circle: bool = True, + plot_loss: bool = False, + ): + num_angles, num_rows, num_cols = self.dataset.tilt_series.shape + sirt_tilt_series = self.dataset.tilt_series.clone() + sirt_tilt_series = sirt_tilt_series.permute(2, 0, 1) + + hard_constraints = { + "positivity": enforce_positivity, + "shrinkage": shrinkage, + } + self.volume_obj.hard_constraints = hard_constraints + + if volume_shape is None: + volume_shape = (num_rows, num_rows, num_rows) + else: + D, H, W = volume_shape + + if reset: + self.volume_obj.reset() + self.loss = [] + + proj_forward = torch.zeros_like(self.dataset.tilt_series) + + pbar = tqdm(range(num_iterations), desc="SIRT Reconstruction") + + if smoothing_sigma is not None: + gaussian_kernel = gaussian_kernel_1d(smoothing_sigma).to(self.device) + else: + gaussian_kernel = None + + print( + "Devices", + sirt_tilt_series.device, + proj_forward.device, + self.dataset.tilt_angles.device, + ) + + for iter in pbar: + proj_forward, loss = self._sirt_run_epoch( + tilt_series=sirt_tilt_series, + proj_forward=proj_forward, + angles=self.dataset.tilt_angles, + inline_alignment=iter > 0 and inline_alignment, + filter_name=filter_name, + gaussian_kernel=gaussian_kernel, + circle=circle, + ) + + pbar.set_description(f"SIRT Reconstruction | Loss: {loss.item():.4f}") + + self.loss.append(loss.item()) + + self.sirt_recon_vol = self.volume_obj + + # Permutation due to sinogram ordering. + self.sirt_recon_vol.obj = self.sirt_recon_vol.obj.permute(1, 2, 0) + + if plot_loss: + self.plot_loss() + + # TODO: ML Recon which has NeRF and AD depending on the object type. + # TODO: Temporary + + def create_batch_projection_rays(self, pixel_i, pixel_j, N, num_samples_per_ray): + """Create projection rays for entire batch simultaneously.""" + batch_size = len(pixel_i) + + # Convert all pixels to normalized coordinates + x_coords = (pixel_j / (N - 1)) * 2 - 1 + y_coords = (pixel_i / (N - 1)) * 2 - 1 + # TODO: maybe pixel_j.device? + # Create z coordinates + z_coords = torch.linspace(-1, 1, num_samples_per_ray, device=self.device) + + # Create rays for all pixels: [batch_size, num_samples_per_ray, 3] + rays = torch.zeros(batch_size, num_samples_per_ray, 3, device=self.device) + + # Fill coordinates efficiently + rays[:, :, 0] = x_coords.unsqueeze(1) # x constant per ray + rays[:, :, 1] = y_coords.unsqueeze(1) # y constant per ray + rays[:, :, 2] = z_coords.unsqueeze(0) # z varies along ray + + return rays + + @torch.compile(mode="reduce-overhead") + def transform_batch_ray_coordinates(self, rays, z1, x, z3, shifts, N, sampling_rate): + # Step 1: Apply shifts + shift_x_norm = (shifts[:, 0:1] * sampling_rate * 2) / (N - 1) + shift_y_norm = (shifts[:, 1:2] * sampling_rate * 2) / (N - 1) + + rays_x = rays[:, :, 0] - shift_x_norm + rays_y = rays[:, :, 1] - shift_y_norm + rays_z = rays[:, :, 2] + + # Rotation 1: Z(-z3) + theta = torch.deg2rad(-z3).view(-1, 1) + cos_t = torch.cos(theta) + sin_t = torch.sin(theta) + + rays_x_rot1 = cos_t * rays_x - sin_t * rays_y + rays_y_rot1 = sin_t * rays_x + cos_t * rays_y + rays_z_rot1 = rays_z + + # Rotation 2: X(x) + theta = torch.deg2rad(x).view(-1, 1) + cos_t = torch.cos(theta) + sin_t = torch.sin(theta) + + rays_x_rot2 = rays_x_rot1 + rays_y_rot2 = cos_t * rays_y_rot1 - sin_t * rays_z_rot1 + rays_z_rot2 = sin_t * rays_y_rot1 + cos_t * rays_z_rot1 + + # Rotation 3: Z(-z1) + theta = torch.deg2rad(-z1).view(-1, 1) + cos_t = torch.cos(theta) + sin_t = torch.sin(theta) + + rays_x_final = cos_t * rays_x_rot2 - sin_t * rays_y_rot2 + rays_y_final = sin_t * rays_x_rot2 + cos_t * rays_y_rot2 + rays_z_final = rays_z_rot2 + + # Stack the final result + transformed_rays = torch.stack([rays_x_final, rays_y_final, rays_z_final], dim=2) + + return transformed_rays + + # TODO: Temp logger + def setup_logger(self, log_path): + if self.global_rank == 0: + self.temp_logger = SummaryWriter(log_dir=log_path) + else: + self.temp_logger = None + + def pretrain( + self, + # volume_dataset: PretrainVolumeDataset, + obj: ObjectINN, + batch_size: int, + soft_constraints: dict = None, + log_path: str = None, + optimizer_params: dict = None, + epochs: int = 100, + viz_freq: int = 1, + consistency_criterion: str = "mse", + ): + if not self.pretraining_instantiated: + self.setup_distributed() + # self.setup_pretraining_dataloader(volume_dataset, batch_size) + self.pretraining_instantiated = True + obj.model = self.build_model(obj._model) + self.obj = obj + + if soft_constraints is not None: + self.obj.soft_constraints = soft_constraints + + if not hasattr(self, "temp_logger"): + self.setup_logger(log_path=log_path) + + if optimizer_params is not None: + optimizer_params = self.scale_lr(optimizer_params) + self.optimizer_params = optimizer_params + self.set_optimizers() + device_type = self.device.type + consistency_loss_fn = None + if consistency_criterion[0].lower() == "mse": + consistency_loss_fn = MSELoss() + elif consistency_criterion[0].lower() == "l1": + consistency_loss_fn = L1Loss() + elif consistency_criterion[0].lower() == "mse_log": + consistency_loss_fn = MSELogMSELoss() + elif consistency_criterion[0].lower() == "llmse": + consistency_loss_fn = LLMSELoss() + elif consistency_criterion[0].lower() == "smooth_l1": + consistency_loss_fn = SmoothL1Loss(beta=consistency_criterion[1]) + elif consistency_criterion[0].lower() == "adaptive_smooth_l1": + # consistency_loss_fn = AdaptiveSmoothL1LossDDP( + # beta_init=consistency_criterion[1], ema_factor=0.99, eps=1e-8 + # ) + pass + elif consistency_criterion[0].lower() == "charbonnier": + consistency_loss_fn = CharbonnierLoss(epsilon=1e-12, reduction="mean") + else: + raise ValueError(f"Invalid consistency criterion: {consistency_criterion}") + + for epoch in range(epochs): + if self.pretraining_sampler is not None: + self.pretraining_sampler.set_epoch(epoch) + + self.obj.model.train() + + epoch_loss = 0.0 + epoch_consistency_loss = 0.0 + epoch_tv_loss = 0.0 + num_batches = 0 + + for batch_idx, batch in enumerate(self.pretraining_dataloader): + coords = batch["coords"].to(self.device, non_blocking=True) + target = batch["target"].to(self.device, non_blocking=True) + + for _, opt in self.optimizers.items(): + opt.zero_grad() + + with torch.autocast(device_type=device_type, dtype=torch.bfloat16, enabled=True): + outputs = self.obj.forward(coords) + consistency_loss = consistency_loss_fn(outputs, target) + tv_loss = self.obj.apply_soft_constraints(coords) + + loss = consistency_loss + tv_loss + + loss.backward() + + epoch_loss += loss.detach() + epoch_consistency_loss += consistency_loss.detach() + epoch_tv_loss += tv_loss.detach() + + torch.nn.utils.clip_grad_norm_(self.obj.model.parameters(), max_norm=1.0) + + for _, opt in self.optimizers.items(): + opt.step() + epoch_loss += loss.detach() + num_batches += 1 + + if epoch % viz_freq == 0 or epoch == epochs - 1: + avg_loss = epoch_loss / num_batches + with torch.no_grad(): + self.obj.create_volume( + world_size=self.world_size, + global_rank=self.global_rank, + # ray_size=volume_dataset.N, + ) + pred_full = self.obj.obj + loss_tensor = avg_loss.clone().detach() + avg_loss = loss_tensor.item() + avg_tv_loss = epoch_tv_loss / num_batches + avg_consistency_loss = epoch_consistency_loss / num_batches + + metrics = torch.tensor( + [avg_loss, avg_consistency_loss, avg_tv_loss], device=self.device + ) + if self.world_size > 1: + dist.all_reduce(metrics, op=dist.ReduceOp.AVG) + avg_loss, avg_consistency_loss, avg_tv_loss = metrics.tolist() + + if self.global_rank == 0: + self.temp_logger.add_scalar("Pretrain/total_loss", avg_loss, epoch) + self.temp_logger.add_scalar( + "Pretrain/consistency_loss", avg_consistency_loss, epoch + ) + self.temp_logger.add_scalar("Pretrain/tv_loss", avg_tv_loss, epoch) + + # current_lr = self.schedulers["model"].get_last_lr()[0] + # self.temp_logger.add_scalar("Pretrain/lr", current_lr, epoch) + + fig, ax = plt.subplots(ncols=4, figsize=(36, 12)) + ax[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap="turbo", vmin=0) + ax[0].set_title("Sum over Z-axis") + ax[1].matshow( + # pred_full[volume_dataset.N // 2].cpu().numpy(), cmap="turbo", vmin=0 + # ) + # ax[1].set_title(f"Slice at Z={volume_dataset.N // 2}") + ax[2].matshow(pred_full.sum(dim=1).cpu().numpy(), cmap="turbo", vmin=0) + ax[2].set_title("Sum over Y-axis") + ax[3].matshow(pred_full.sum(dim=2).cpu().numpy(), cmap="turbo", vmin=0) + ax[3].set_title("Sum over X-axis") + self.temp_logger.add_figure("Pretrain/viz", fig, epoch, close=True) + + plt.close(fig) + print( + f"Epoch [{epoch}/{epochs}] Pretrain Total Loss: {avg_loss:.6f}, Consistency Loss: {avg_consistency_loss:.6f}, TV Loss: {avg_tv_loss:.6f}" + ) + self.global_epochs += 1 + if self.global_rank == 0: + print(f"Pretraining Completed, saving model weights to {log_path}/model_weights.pth") + torch.save(self.obj.model.state_dict(), f"{log_path}/model_weights.pth") + + def recon( + self, + obj: ObjectINN, + batch_size: int, + num_workers: int = 0, + epochs=20, + use_amp=True, + viz_freq=1, + checkpoint_freq=5, + optimizer_params: dict = None, + scheduler_params: dict = None, + soft_constraints: dict = None, + vol_save_path: str = None, # TODO: TEMPORARY + log_path=None, + val_fraction: float = 0.0, + learn_shifts: bool = True, + # l1_loss: bool = False, + consistency_criterion: str = "mse", + model_weights_path: str = None, + force_cpu: bool = False, + ): + if not self.ddp_instantiated: + if not self.pretraining_instantiated: + self.setup_distributed() + if model_weights_path is not None: + print(f"Loading model weights from {model_weights_path}") + state_dict = torch.load(model_weights_path, map_location="cpu") + + # Handle DataParallel/DDP checkpoints + if any(k.startswith("module.") for k in state_dict.keys()): + new_state_dict = { + k.replace("module.", ""): v for k, v in state_dict.items() + } + else: + new_state_dict = state_dict + + obj.model.load_state_dict(new_state_dict) + print("Model weights loaded successfully") + obj.model = self.build_model(obj._model) + self.obj = obj + + self.setup_dataloader(self.dataset, batch_size, num_workers, val_fraction) + self.ddp_instantiated = True + + if soft_constraints is not None: + self.obj.soft_constraints = soft_constraints + + if not hasattr(self, "temp_logger"): + self.setup_logger(log_path=log_path) + + zero_tilt_idx = torch.argmin(torch.abs(self.dataset.tilt_angles)).item() + + if self.global_rank == 0: + print( + f"Using projection {zero_tilt_idx} (angle={self.dataset.tilt_angles[zero_tilt_idx]:.2f}°) as reference" + ) + print(f"Using consistency criterion: {consistency_criterion}") + + # Auxiliary params setup + self.dataset.setup_auxiliary_params(zero_tilt_idx, self.device, learn_shifts) + aux_params = self.dataset.auxiliary_params + # Scaling learning rates to account for distributed training + if optimizer_params is not None: + optimizer_params = self.scale_lr(optimizer_params) + self.optimizer_params = optimizer_params + self.set_optimizers() + + if scheduler_params is not None: + self.scheduler_params = scheduler_params + self.set_schedulers(self.scheduler_params, num_iter=epochs) + + aux_norm = torch.tensor(0.0, device=self.device) + model_norm = torch.tensor(0.0, device=self.device) + + for _, opt in self.optimizers.items(): + opt.zero_grad() + + N = max(self.dataset.dims) + + device_type = self.device.type + autocast_dtype = torch.bfloat16 if use_amp else None + + for epoch in range(epochs): + num_samples_per_ray = get_num_samples_per_ray(N=N, epoch=self.global_epochs) + + # num_samples_per_ray = get_num_samples_per_ray(epoch) + # Log the change if it happens + if self.global_rank == 0: + print(f"Epoch {epoch}: num_samples_per_ray = {num_samples_per_ray}") + + if self.global_rank == 0 and self.global_epochs > 0: + prev_samples = get_num_samples_per_ray(N=N, epoch=self.global_epochs - 1) + + if num_samples_per_ray != prev_samples: + print( + f"Epoch {epoch}: Changing num_samples_per_ray from {prev_samples} to {num_samples_per_ray}" + ) + + if self.sampler is not None: + self.sampler.set_epoch(epoch) + + epoch_loss = 0.0 + epoch_consistency_loss = 0.0 + epoch_tv_loss = 0.0 + epoch_z1_loss = 0.0 + + num_batches = 0 + consistency_loss_fn = None + if consistency_criterion[0].lower() == "mse": + consistency_loss_fn = MSELoss() + elif consistency_criterion[0].lower() == "l1": + consistency_loss_fn = L1Loss() + elif consistency_criterion[0].lower() == "mse_log": + consistency_loss_fn = MSELogMSELoss() + elif consistency_criterion[0].lower() == "llmse": + consistency_loss_fn = LLMSELoss() + elif consistency_criterion[0].lower() == "smooth_l1": + consistency_loss_fn = SmoothL1Loss(beta=consistency_criterion[1]) + elif consistency_criterion[0].lower() == "adaptive_smooth_l1": + # consistency_loss_fn = AdaptiveSmoothL1LossDDP( + # beta_init=consistency_criterion[1], ema_factor=0.99, eps=1e-8 + # ) + pass + elif consistency_criterion[0].lower() == "charbonnier": + consistency_loss_fn = CharbonnierLoss(epsilon=1e-12, reduction="mean") + else: + raise ValueError(f"Invalid consistency criterion: {consistency_criterion}") + + for batch_idx, batch in enumerate(self.dataloader): + projection_indices = batch["projection_idx"] + + pixel_i = batch["pixel_i"].float().to(self.device, non_blocking=True) + pixel_j = batch["pixel_j"].float().to(self.device, non_blocking=True) + target_values = batch["target_value"].to(self.device, non_blocking=True) + phis = batch["phi"].to(self.device, non_blocking=True) + projection_indices = batch["projection_idx"].to(self.device, non_blocking=True) + + shifts, z1_params, z3_params = aux_params(None) + batch_shifts = torch.index_select(shifts, 0, projection_indices) + batch_z1 = torch.index_select(z1_params, 0, projection_indices) + batch_z3 = torch.index_select(z3_params, 0, projection_indices) + + with torch.autocast( + device_type=device_type, dtype=autocast_dtype, enabled=use_amp + ): + with torch.no_grad(): + batch_ray_coords = self.create_batch_projection_rays( + pixel_i, pixel_j, N, num_samples_per_ray + ) + + # TODO: .forward passing z1, x, z3, shifts, N, sampling_rate + transformed_rays = self.transform_batch_ray_coordinates( + batch_ray_coords, + z1=batch_z1, + x=phis, + z3=batch_z3, + shifts=batch_shifts, + N=N, + sampling_rate=1.0, + ) + + all_coords = transformed_rays.view(-1, 3) + + all_densities = self.obj.forward(all_coords) + + tv_loss = self.obj.apply_soft_constraints(all_coords) + ray_densities = all_densities.view( + len(target_values), num_samples_per_ray + ) # Reshape rays and integarte + step_size = 2.0 / (num_samples_per_ray - 1) + + predicted_values = ray_densities.sum(dim=1) * step_size + + consistency_loss = consistency_loss_fn(predicted_values, target_values) + tv_loss_z1 = torch.tensor(0.0, device=self.device) + + loss = consistency_loss + tv_loss + tv_loss_z1 + + loss.backward() + + epoch_loss += loss.detach() + epoch_consistency_loss += consistency_loss.detach() + epoch_tv_loss += tv_loss.detach() + epoch_z1_loss += tv_loss_z1.detach() + num_batches += 1 + + for key, opt in self.optimizers.items(): + if key == "model": + model_norm = torch.nn.utils.clip_grad_norm_( + self.obj.model.parameters(), max_norm=1 + ) + elif key == "aux_params": + aux_norm = torch.nn.utils.clip_grad_norm_( + self.dataset.auxiliary_params.parameters(), max_norm=1 + ) + opt.step() + opt.zero_grad() + + for key, sched in self.schedulers.items(): + if isinstance(sched, torch.optim.lr_scheduler.ReduceLROnPlateau): + sched.step(epoch_loss) + else: + sched.step() + + if epoch % viz_freq == 0 or epoch == epochs - 1 or epoch % checkpoint_freq == 0: + with torch.no_grad(): + self.obj.create_volume( + world_size=self.world_size, + global_rank=self.global_rank, + ray_size=num_samples_per_ray, + ) + pred_full = self.obj.obj + avg_loss = epoch_loss.item() / num_batches + avg_consistency_loss = epoch_consistency_loss.item() / num_batches + avg_tv_loss = epoch_tv_loss.item() / num_batches + avg_z1_loss = epoch_z1_loss.item() / num_batches + shifts, z1, z3 = self.dataset.auxiliary_params.forward() + shifts = shifts.detach().cpu() + z1 = z1.detach().cpu() + z3 = z3.detach().cpu() + metrics = torch.tensor( + [avg_loss, avg_consistency_loss, avg_tv_loss, avg_z1_loss], + device=self.device, + ) + if self.world_size > 1: + dist.all_reduce(metrics, op=dist.ReduceOp.AVG) + avg_loss, avg_consistency_loss, avg_tv_loss, avg_z1_loss = metrics.tolist() + + if hasattr(self, "val_dataloader") and self.val_dataloader is not None: + val_loss = self.validate( + aux_params, num_samples_per_ray, device_type, autocast_dtype, use_amp, N + ) + + if self.global_rank == 0 and (epoch % viz_freq == 0 or epoch == epochs - 1): + with torch.no_grad(): + current_lr = self.schedulers["model"].get_last_lr()[0] + + # Log metrics + self.temp_logger.add_scalar( + f"train/consistency_loss_{consistency_criterion[0] if isinstance(consistency_criterion, tuple) else consistency_criterion}", + avg_consistency_loss, + self.global_epochs, + ) + if hasattr(self, "val_dataloader") and self.val_dataloader is not None: + self.temp_logger.add_scalar( + f"val/consistency_loss_{consistency_criterion[0] if isinstance(consistency_criterion, tuple) else consistency_criterion}", + val_loss, + self.global_epochs, + ) + self.temp_logger.add_scalar("train/z1_loss", avg_z1_loss, self.global_epochs) + # if tv_weight > 0: + self.temp_logger.add_scalar("train/tv_loss", avg_tv_loss, self.global_epochs) + self.temp_logger.add_scalar("train/total_loss", avg_loss, self.global_epochs) + self.temp_logger.add_scalar( + "train/model_grad_norm", model_norm.item(), self.global_epochs + ) + self.temp_logger.add_scalar( + "train/aux_grad_norm", aux_norm.item(), self.global_epochs + ) + self.temp_logger.add_scalar("train/lr", current_lr, self.global_epochs) + self.temp_logger.add_scalar( + "train/num_samples_per_ray", num_samples_per_ray, self.global_epochs + ) + if consistency_criterion[0].lower() == "adaptive_smooth_l1": + self.temp_logger.add_scalar( + "train/beta_2", consistency_loss_fn.beta2.item(), self.global_epochs + ) + fig, axes = plt.subplots(1, 5, figsize=(36, 12)) + axes[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap="turbo", vmin=0) + axes[0].set_title("Sum over Z-axis") + + axes[1].matshow(pred_full[N // 2].cpu().numpy(), cmap="turbo", vmin=0) + axes[1].set_title(f"Slice at Z={N // 2}") + + slice_start = max(0, N // 2 - 5) + slice_end = min(N, N // 2 + 6) + thick_slice = pred_full[slice_start:slice_end].sum(dim=0).cpu().numpy() + axes[2].matshow(thick_slice, cmap="turbo", vmin=0) + axes[2].set_title(f"Thick slice sum (Z={slice_start}:{slice_end - 1})") + + axes[3].matshow(pred_full.sum(dim=1).cpu().numpy(), cmap="turbo", vmin=0) + axes[3].set_title("Sum over Y-axis") + + axes[4].matshow(pred_full.sum(dim=2).cpu().numpy(), cmap="turbo", vmin=0) + axes[4].set_title("Sum over X-axis") + + self.temp_logger.add_figure("train/viz", fig, self.global_epochs, close=True) + + fig, axes = plt.subplots(ncols=3, figsize=(10, 5)) + axes[0].plot(shifts[:, 0].cpu().numpy(), label="Shifts X") + axes[0].plot(shifts[:, 1].cpu().numpy(), label="Shifts Y") + axes[0].legend() + axes[1].plot(z1.cpu().numpy(), label="Z1") + axes[2].plot(z3.cpu().numpy(), label="Z3") + self.temp_logger.add_figure( + "train/auxiliary_params", fig, self.global_epochs, close=True + ) + plt.close(fig) + + if epoch % checkpoint_freq == 0 or self.global_epochs == epochs - 1: + with torch.no_grad(): + if self.global_rank == 0: + save_path = f"{vol_save_path}/volume_epoch_{self.global_epochs:04d}.pt" + torch.save(pred_full.cpu(), save_path) + + self.global_epochs += 1 + + if self.global_rank == 0: + print("Training complete.") + + torch.save(self.dataset.auxiliary_params.forward(), f"{log_path}/auxiliary_params.pt") + # print("Successfully setup DDP and dataloader") + + def validate(self, aux_params, num_samples_per_ray, device_type, autocast_dtype, use_amp, N): + """Validate the model on the validation set.""" + self.obj.model.eval() + aux_params.eval() + + val_loss = 0.0 + num_batches = 0 + + with torch.no_grad(): + for batch in self.val_dataloader: + pixel_i = batch["pixel_i"].float().to(self.device, non_blocking=True) + pixel_j = batch["pixel_j"].float().to(self.device, non_blocking=True) + target_values = batch["target_value"].to(self.device, non_blocking=True) + phis = batch["phi"].to(self.device, non_blocking=True) + projection_indices = batch["projection_idx"].to(self.device, non_blocking=True) + + shifts, z1_params, z3_params = aux_params(None) + batch_shifts = torch.index_select(shifts, 0, projection_indices) + batch_z1 = torch.index_select(z1_params, 0, projection_indices) + batch_z3 = torch.index_select(z3_params, 0, projection_indices) + + with torch.autocast( + device_type=device_type, dtype=autocast_dtype, enabled=use_amp + ): + with torch.no_grad(): + batch_ray_coords = self.create_batch_projection_rays( + pixel_i, pixel_j, N, num_samples_per_ray + ) + + transformed_rays = self.transform_batch_ray_coordinates( + batch_ray_coords, + z1=batch_z1, + x=phis, + z3=batch_z3, + shifts=batch_shifts, + N=N, + sampling_rate=1.0, + ) + + all_coords = transformed_rays.view(-1, 3) + + all_densities = self.obj.forward(all_coords) + + ray_densities = all_densities.view( + len(target_values), num_samples_per_ray + ) # Reshape rays and integarte + step_size = 2.0 / (num_samples_per_ray - 1) + + predicted_values = ray_densities.sum(dim=1) * step_size + + mse_loss = torch.nn.functional.mse_loss(predicted_values, target_values) + + loss = mse_loss + + val_loss += loss.detach() + num_batches += 1 + + avg_val_loss = val_loss / num_batches if num_batches > 0 else 0.0 + + if self.world_size > 1: + val_loss_tensor = avg_val_loss.detach().clone() + dist.all_reduce(val_loss_tensor, op=dist.ReduceOp.AVG) + avg_val_loss = val_loss_tensor.item() + + self.obj.model.train() + aux_params.train() + + return avg_val_loss + + def ad_recon( + self, + optimizer_params: dict, + num_iter: int = 0, + reset: bool = False, + scheduler_params: dict | None = None, + hard_constraints: dict | None = None, + soft_constraints: dict | None = None, + # store_iterations: bool | None = None, + # store_iterations_every: int | None = None, + # autograd: bool = True, + ): + if reset: + self.reset_recon() + + self.hard_constraints = hard_constraints + self.soft_constraints = soft_constraints + + # Make sure everything is in the correct device, might be redundant/cleaner way to do this + self.dataset.to(self.device) + self.volume_obj.to(self.device) + + # Making optimizable parameters into leaf tensors. + self.dataset.shifts = self.dataset.shifts.detach().to(self.device).requires_grad_(True) + self.dataset.z1_angles = ( + self.dataset.z1_angles.detach().to(self.device).requires_grad_(True) + ) + self.dataset.z3_angles = ( + self.dataset.z3_angles.detach().to(self.device).requires_grad_(True) + ) + + if optimizer_params is not None: + self.optimizer_params = optimizer_params + self.set_optimizers() + + if scheduler_params is not None: + self.scheduler_params = scheduler_params + self.set_schedulers(self.scheduler_params, num_iter=num_iter) + + if hard_constraints is not None: + self.volume_obj.hard_constraints = hard_constraints + if soft_constraints is not None: + self.volume_obj.soft_constraints = soft_constraints + + pbar = tqdm(range(num_iter), desc="AD Reconstruction") + + for a0 in pbar: + total_loss = 0.0 + tilt_series_loss = 0.0 + + pred_volume = self.volume_obj.forward() + + for i in range(len(self.dataset.tilt_series)): + forward_projection = self.projection_operator( + vol=pred_volume, + z1=self.dataset.z1_angles[i], + x=self.dataset.tilt_angles[i], + z3=self.dataset.z3_angles[i], + shift_x=self.dataset.shifts[i, 0], + shift_y=self.dataset.shifts[i, 1], + device=self.device, + ) + + tilt_series_loss += torch.nn.functional.mse_loss( + forward_projection, self.dataset.tilt_series[i] + ) + tilt_series_loss /= len(self.dataset.tilt_series) + + total_loss = tilt_series_loss + self.volume_obj.soft_loss + self.loss.append(total_loss.item()) + + total_loss.backward() + + for opt in self.optimizers.values(): + opt.step() + opt.zero_grad() + + if self.schedulers is not None: + for sch in self.schedulers.values(): + if isinstance(sch, torch.optim.lr_scheduler.ReduceLROnPlateau): + sch.step(total_loss) + elif sch is not None: + sch.step() + + pbar.set_description(f"AD Reconstruction | Loss: {total_loss:.4f}") + + if self.logger is not None: + self.logger.log_scalar("loss/total", total_loss.item(), a0) + self.logger.log_scalar("loss/tilt_series", tilt_series_loss.item(), a0) + self.logger.log_scalar( + "loss/soft constraints", self.volume_obj.soft_loss.item(), a0 + ) + + if a0 % self.logger.log_images_every == 0: + self.logger.projection_images( + volume_obj=self.volume_obj, + epoch=a0, + ) + self.logger.tilt_angles_figure(dataset=self.dataset, step=a0) + + self.logger.flush() + + self.ad_recon_vol = self.volume_obj.forward() + + return self + + def reset_recon(self) -> None: + if isinstance(self.volume_obj, ObjectVoxelwise): + self.volume_obj.reset() + + self.ad_recon_vol = None + + # --- Projection Operators ---- + def projection_operator( + self, + vol, + z1, + x, + z3, + shift_x, + shift_y, + device, + ): + projection = ( + rot_ZXZ( + mags=vol.unsqueeze(0), # Add batch dimension + z1=z1, + x=-x, + z3=z3, + device=device, + mode="bilinear", + ) + .squeeze() + .sum(axis=0) + ) + + shifted_projection = differentiable_shift_2d( + image=projection, + shift_x=shift_x, + shift_y=shift_y, + sampling_rate=1.0, # Assuming 1 pixel = 1 physical unit + ) + + return shifted_projection From 370934aa833db611843794b943959d0c45fc5666 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 27 Jan 2026 22:14:34 -0800 Subject: [PATCH 051/335] DDP projection indices fixed; added hard constraints to the forward model of INR --- src/quantem/tomography/dataset_models.py | 1 - src/quantem/tomography/object_models.py | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index b18090f1..efd7e9d1 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -317,7 +317,6 @@ def get_coords( # target_values = batch["target_value"].to(self.device, non_blocking=True) phis = batch["phi"].to(self.device, non_blocking=True) projection_indices = batch["projection_idx"].to(self.device, non_blocking=True) - print(f"projection_indices: {projection_indices}") with torch.no_grad(): batch_ray_coords = self.create_batch_rays(pixel_i, pixel_j, N, num_samples_per_ray) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 6ab64067..a603c089 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -338,6 +338,19 @@ def apply_soft_constraints( return soft_loss + def apply_hard_constraints(self, pred: torch.Tensor) -> torch.Tensor: + """ + Apply hard constraints to the predicted values of the INR model. + """ + + if self.constraints.positivity: + pred = torch.clamp(pred, min=0.0, max=None) + if self.constraints.shrinkage: + pred = torch.max(pred - self.constraints.shrinkage, torch.zeros_like(pred)) + + + return pred + @property def params(self): return self.model.parameters() @@ -408,6 +421,7 @@ def forward(self, coords: torch.Tensor) -> torch.Tensor: all_densities = all_densities * valid_mask + all_densities = self.apply_hard_constraints(all_densities) return all_densities # Pretrain Loop @@ -529,6 +543,7 @@ def create_volume( self.device, non_blocking=True ) batch_outputs = model(batch_coords) + batch_outputs = self.apply_hard_constraints(batch_outputs) if batch_outputs.dim() > 1: batch_outputs = batch_outputs.squeeze(-1) outputs_list.append(batch_outputs.cpu()) @@ -566,6 +581,7 @@ def create_volume( pred_full = outputs.reshape(N, N, N).float() if return_vol: + return pred_full.detach().cpu() else: self._obj = pred_full.detach().cpu() From b80cd68c4e3b6e7de11d31911624c5fb8ce058ac Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Wed, 28 Jan 2026 00:00:27 -0800 Subject: [PATCH 052/335] NVIDIA Profiling testing added in the reconstruction loop in tomography.py --- src/quantem/tomography/tomography.py | 214 ++++++++++++++++----------- 1 file changed, 130 insertions(+), 84 deletions(-) diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index b0dfc92f..33abc011 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -1,7 +1,9 @@ +from contextlib import contextmanager from typing import List, Literal, Optional, Self, Tuple import numpy as np import torch +import torch.distributed as dist from tqdm.auto import tqdm from quantem.core.ml.ddp import DDPMixin @@ -13,7 +15,6 @@ from quantem.tomography.tomography_opt import TomographyOpt from quantem.tomography.utils import torch_phase_cross_correlation from quantem.tomography_old.utils import gaussian_filter_2d_stack, gaussian_kernel_1d -import torch.distributed as dist class Tomography(TomographyOpt, TomographyBase, DDPMixin): @@ -51,6 +52,7 @@ def reconstruct( constraints: dict = {}, # TODO: What to pass into the constraints? loss_func: Tuple[str, Optional[float]] = ("smooth_l1", 0.07), num_samples_per_ray: int | List[Tuple[int, int]] = None, + profiling_mode: bool = False, ): """ This function should be able to handle both AD and INR-based tomography reconstruction methods. @@ -64,16 +66,33 @@ def reconstruct( # f"Should never happen! obj_model and dset must be on the same device, got {self.obj_model.device} and {self.dset.device}" # ) + if profiling_mode: + if self.global_rank == 0: + print("Profiling mode enabled.") + import torch.cuda.nvtx as nvtx + + @contextmanager + def nvtx_range(enabled: bool, name: str): + if enabled: + nvtx.range_push(name) + try: + yield + finally: + if enabled: + nvtx.range_pop() + if reset: raise NotImplementedError("Reset is not implemented yet.") if optimizer_params is not None: - self.optimizer_params = optimizer_params - self.set_optimizers() + with nvtx_range(profiling_mode, "Setting Optimizer Params"): + self.optimizer_params = optimizer_params + self.set_optimizers() if scheduler_params is not None: - self.scheduler_params = scheduler_params - self.set_schedulers(scheduler_params) + with nvtx_range(profiling_mode, "Setting Scheduler Params"): + self.scheduler_params = scheduler_params + self.set_schedulers(scheduler_params) if constraints is not None: self.obj_model.constraints = constraints @@ -84,9 +103,10 @@ def reconstruct( # Setting up DDP if not hasattr(self, "dataloader"): - self.dataloader, self.sampler = self.setup_dataloader( - self.dset, batch_size, num_workers=num_workers - ) + with nvtx_range(profiling_mode, "Setting Dataloader"): + self.dataloader, self.sampler = self.setup_dataloader( + self.dset, batch_size, num_workers=num_workers + ) self.obj_model.model.train() @@ -101,103 +121,129 @@ def reconstruct( print("num_samples_per_ray schedule provided.") print(f"N: {N}, num_samples_per_ray: {num_samples_per_ray}") - - for a0 in range(num_iter): - consistency_loss = 0.0 - total_loss = 0.0 - epoch_soft_constraint_loss = 0.0 - # self._reset_iter_constraints() + with nvtx_range(profiling_mode, f"Epoch {a0}"): + consistency_loss = 0.0 + total_loss = 0.0 + epoch_soft_constraint_loss = 0.0 + # self._reset_iter_constraints() - if self.sampler is not None: - self.sampler.set_epoch(a0) + if self.sampler is not None: + self.sampler.set_epoch(a0) - if isinstance(num_samples_per_ray, list): - curr_num_samples_per_ray = num_samples_per_ray[a0][1] - else: - curr_num_samples_per_ray = num_samples_per_ray + if isinstance(num_samples_per_ray, list): + curr_num_samples_per_ray = num_samples_per_ray[a0][1] + else: + curr_num_samples_per_ray = num_samples_per_ray - if self.global_rank == 0: - print(f"curr_num_samples_per_ray: {curr_num_samples_per_ray}") - for batch_idx, batch in enumerate(self.dataloader): - self.zero_grad_all() - with torch.autocast( - device_type=self.device.type, - dtype=torch.bfloat16, - enabled=True, - ): - all_coords = self.dset.get_coords(batch, N, curr_num_samples_per_ray) - - all_densities = self.obj_model.forward(all_coords) - - integrated_densities = self.dset.integrate_rays( - all_densities, curr_num_samples_per_ray, len(batch["target_value"]) - ) + if self.global_rank == 0: + print(f"curr_num_samples_per_ray: {curr_num_samples_per_ray}") + for batch_idx, batch in enumerate(self.dataloader): + with nvtx_range(profiling_mode, f"batch_{batch_idx}"): + self.zero_grad_all() + with torch.autocast( + device_type=self.device.type, + dtype=torch.bfloat16, + enabled=True, + ): + with nvtx_range(profiling_mode, "Getting Coords"): + all_coords = self.dset.get_coords( + batch, N, curr_num_samples_per_ray + ) + + with nvtx_range(profiling_mode, "Forwarding"): + all_densities = self.obj_model.forward(all_coords) + + with nvtx_range(profiling_mode, "Integrating"): + integrated_densities = self.dset.integrate_rays( + all_densities, + curr_num_samples_per_ray, + len(batch["target_value"]), + ) + + pred = integrated_densities.float() + + with nvtx_range(profiling_mode, "Getting Target"): + target = ( + batch["target_value"].to(self.device, non_blocking=True).float() + ) - pred = integrated_densities.float() - target = batch["target_value"].to(self.device, non_blocking=True).float() + with nvtx_range(profiling_mode, "Calculating Loss"): + batch_consistency_loss = torch.nn.functional.mse_loss(pred, target) - batch_consistency_loss = torch.nn.functional.mse_loss(pred, target) - soft_constraints_loss = self.obj_model.apply_soft_constraints(all_coords) - epoch_soft_constraint_loss += soft_constraints_loss.item() - batch_loss = batch_consistency_loss.float() + soft_constraints_loss - batch_loss.backward() + with nvtx_range(profiling_mode, "Applying Soft Constraints"): + soft_constraints_loss = self.obj_model.apply_soft_constraints( + all_coords + ) - # Clip gradients - torch.nn.utils.clip_grad_norm_(self.obj_model.model.parameters(), max_norm=1.0) + epoch_soft_constraint_loss += soft_constraints_loss.item() - self.step_optimizers() - total_loss += batch_loss.item() - consistency_loss += batch_consistency_loss.item() + with nvtx_range(profiling_mode, "Calculating Batch Loss"): + batch_loss = batch_consistency_loss.float() + soft_constraints_loss - # TODO: Maybe reorganize the losses so that the order makes sense lol. + batch_loss.backward() - total_loss = total_loss / len(self.dataloader) - consistency_loss = consistency_loss / len(self.dataloader) - epoch_soft_constraint_loss = epoch_soft_constraint_loss / len(self.dataloader) + # Clip gradients + torch.nn.utils.clip_grad_norm_( + self.obj_model.model.parameters(), max_norm=1.0 + ) - metrics = torch.tensor( - [total_loss, consistency_loss, epoch_soft_constraint_loss], device=self.device - ) + self.step_optimizers() + total_loss += batch_loss.item() + consistency_loss += batch_consistency_loss.item() - if self.world_size > 1: - dist.all_reduce(metrics, dist.ReduceOp.AVG) + # TODO: Maybe reorganize the losses so that the order makes sense lol. - total_loss, consistency_loss, epoch_soft_constraint_loss = metrics.tolist() + total_loss = total_loss / len(self.dataloader) + consistency_loss = consistency_loss / len(self.dataloader) + epoch_soft_constraint_loss = epoch_soft_constraint_loss / len(self.dataloader) - if self.global_rank == 0: - print(f"Total Loss: {total_loss:.4f}, Consistency Loss: {consistency_loss:.4f}") - - self._epoch_losses.append(total_loss) - self._consistency_losses.append(consistency_loss) - self.obj_model._soft_constraint_losses.append(epoch_soft_constraint_loss) - - if self.logger is not None: - if self.logger.log_images_every > 0 and self.num_epochs % self.logger.log_images_every == 0: - print("Creating volume...") - pred_full = self.obj_model.create_volume(return_vol=True) - - if self.global_rank == 0: - print("Logging images...") - self.logger.log_iter_images( - pred_volume=pred_full, - dataset_model=self.dset, - iter=self.num_epochs, - ) + metrics = torch.tensor( + [total_loss, consistency_loss, epoch_soft_constraint_loss], device=self.device + ) + if self.world_size > 1: + dist.all_reduce(metrics, dist.ReduceOp.AVG) + + total_loss, consistency_loss, epoch_soft_constraint_loss = metrics.tolist() if self.global_rank == 0: - self.logger.log_iter( - object_model=self.obj_model, - iter=self.num_epochs, - consistency_loss=consistency_loss, - total_loss=total_loss, - num_samples_per_ray=curr_num_samples_per_ray, + print( + f"Total Loss: {total_loss:.4f}, Consistency Loss: {consistency_loss:.4f}" ) + self._epoch_losses.append(total_loss) + self._consistency_losses.append(consistency_loss) + self.obj_model._soft_constraint_losses.append(epoch_soft_constraint_loss) + + with nvtx_range(profiling_mode, "Logging"): + if self.logger is not None: + if ( + self.logger.log_images_every > 0 + and self.num_epochs % self.logger.log_images_every == 0 + ): + print("Creating volume...") + pred_full = self.obj_model.create_volume(return_vol=True) + + if self.global_rank == 0: + print("Logging images...") + self.logger.log_iter_images( + pred_volume=pred_full, + dataset_model=self.dset, + iter=self.num_epochs, + ) + + if self.global_rank == 0: + self.logger.log_iter( + object_model=self.obj_model, + iter=self.num_epochs, + consistency_loss=consistency_loss, + total_loss=total_loss, + num_samples_per_ray=curr_num_samples_per_ray, + ) - self.logger.flush() + self.logger.flush() class TomographyConventional(TomographyBase): From a61cce332faab6b24b34c11e303e4ee674e6973c Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Wed, 28 Jan 2026 22:50:02 -0800 Subject: [PATCH 053/335] Starting profiling of the reconstruction loop; need to move stuff over to conventional tomography file for conv recons --- src/quantem/tomography/dataset_models.py | 3 +- src/quantem/tomography/tomography.py | 42 +- src/quantem/tomography_old/tomography.py | 1766 +++++++++++----------- 3 files changed, 912 insertions(+), 899 deletions(-) diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index efd7e9d1..a506d128 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -340,6 +340,7 @@ def get_coords( return all_coords @staticmethod + @torch.compile(mode="reduce-overhead") def create_batch_rays( pixel_i: torch.Tensor, pixel_j: torch.Tensor, N: int, num_samples_per_ray: int ) -> torch.Tensor: @@ -357,7 +358,6 @@ def create_batch_rays( return rays @staticmethod - @torch.compile(mode="reduce-overhead") def transform_batch_rays( rays: torch.Tensor, z1: torch.Tensor, @@ -403,6 +403,7 @@ def transform_batch_rays( return transformed_rays @staticmethod + @torch.compile(mode="reduce-overhead") def integrate_rays( rays: torch.Tensor, num_samples_per_ray: int, target_values_len: int ) -> torch.Tensor: diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 33abc011..0c1b7a49 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -177,27 +177,39 @@ def nvtx_range(enabled: bool, name: str): all_coords ) - epoch_soft_constraint_loss += soft_constraints_loss.item() + with nvtx_range( + profiling_mode, "Adding soft constraint loss to epoch loss" + ): + epoch_soft_constraint_loss += soft_constraints_loss.detach() with nvtx_range(profiling_mode, "Calculating Batch Loss"): - batch_loss = batch_consistency_loss.float() + soft_constraints_loss - - batch_loss.backward() - - # Clip gradients - torch.nn.utils.clip_grad_norm_( - self.obj_model.model.parameters(), max_norm=1.0 - ) + batch_loss = ( + batch_consistency_loss.float() + soft_constraints_loss.detach() + ) - self.step_optimizers() - total_loss += batch_loss.item() - consistency_loss += batch_consistency_loss.item() + with nvtx_range(profiling_mode, "Backwarding"): + batch_loss.backward() + with nvtx_range(profiling_mode, "Clipping Gradients"): + # Clip gradients + torch.nn.utils.clip_grad_norm_( + self.obj_model.model.parameters(), max_norm=1.0 + ) + with nvtx_range(profiling_mode, "Stepping Optimizers"): + self.step_optimizers() + with nvtx_range(profiling_mode, "Adding batch loss to total loss"): + total_loss += batch_loss.detach() + with nvtx_range( + profiling_mode, "Adding batch consistency loss to consistency loss" + ): + consistency_loss += batch_consistency_loss.detach() # TODO: Maybe reorganize the losses so that the order makes sense lol. - total_loss = total_loss / len(self.dataloader) - consistency_loss = consistency_loss / len(self.dataloader) - epoch_soft_constraint_loss = epoch_soft_constraint_loss / len(self.dataloader) + total_loss = total_loss.item() / len(self.dataloader) + consistency_loss = consistency_loss.item() / len(self.dataloader) + epoch_soft_constraint_loss = epoch_soft_constraint_loss.item() / len( + self.dataloader + ) metrics = torch.tensor( [total_loss, consistency_loss, epoch_soft_constraint_loss], device=self.device diff --git a/src/quantem/tomography_old/tomography.py b/src/quantem/tomography_old/tomography.py index ec8c5db3..7be24397 100644 --- a/src/quantem/tomography_old/tomography.py +++ b/src/quantem/tomography_old/tomography.py @@ -1,883 +1,883 @@ -import matplotlib.pyplot as plt -import numpy as np -import torch -import torch.distributed as dist -from torch.nn import SmoothL1Loss -from torch.utils.tensorboard import SummaryWriter - -# from torch_radon.radon import ParallelBeam as Radon -from tqdm.auto import tqdm - -from quantem.core.ml.loss_functions import ( - CharbonnierLoss, - L1Loss, - LLMSELoss, - MSELogMSELoss, - MSELoss, -) - -# Temporary imports for TomographyNERF -from quantem.tomography.object_models import ObjectINN, ObjectVoxelwise -from quantem.tomography.tomography_base import TomographyBase -from quantem.tomography.tomography_conv import TomographyConv -from quantem.tomography.tomography_ml import TomographyML -from quantem.tomography.utils import differentiable_shift_2d, gaussian_kernel_1d, rot_ZXZ - - -# Temporary aux class for TomographyNERF -# TODO: Maybe put this in INN? -def get_num_samples_per_ray(N: int, epoch: int): - """Increase number of samples per ray at specific epochs.""" - # Exponential schedule - - epochs = np.linspace(0, 10, 5, dtype=int) - # schedule = np.linspace(20, N, 5, dtype=int) - schedule = np.array([20, 100, 150, 200, 200]) - # schedule = np.array([200, 200, 200, 200, 200]) - # schedule = np.array([20, 100, 250, 500, 500]) - # schedule = np.array([500, 500, 500, 500, 500]) - # schedule = np.array([300, 300, 300, 300, 300]) - # schedule = np.exp(schedule) - schedule_warmup = dict[int, int](zip(epochs, schedule)) - - for epoch_threshold, samples in schedule_warmup.items(): - if epoch >= epoch_threshold: - num_samples = samples - - return num_samples - - -class Tomography(TomographyConv, TomographyML, TomographyBase, TomographyDDP): - """ - Top level class for either using conventional or ML-based reconstruction methods - for tomography. - """ - - def __init__( - self, - dataset, - volume_obj, - device, - _token, - ): - super().__init__(dataset, volume_obj, device, _token) - - # TODO: More elegant way of doing this. - self.global_epochs = 0 - self.ddp_instantiated = False - self.pretraining_instantiated = False - - # --- Reconstruction Method --- - - def sirt_recon( - self, - num_iterations: int = 10, - inline_alignment: bool = False, - enforce_positivity: bool = True, - volume_shape: tuple = None, - reset: bool = True, - smoothing_sigma: float = None, - shrinkage: float = None, - filter_name: str = "hamming", - circle: bool = True, - plot_loss: bool = False, - ): - num_angles, num_rows, num_cols = self.dataset.tilt_series.shape - sirt_tilt_series = self.dataset.tilt_series.clone() - sirt_tilt_series = sirt_tilt_series.permute(2, 0, 1) - - hard_constraints = { - "positivity": enforce_positivity, - "shrinkage": shrinkage, - } - self.volume_obj.hard_constraints = hard_constraints - - if volume_shape is None: - volume_shape = (num_rows, num_rows, num_rows) - else: - D, H, W = volume_shape - - if reset: - self.volume_obj.reset() - self.loss = [] - - proj_forward = torch.zeros_like(self.dataset.tilt_series) - - pbar = tqdm(range(num_iterations), desc="SIRT Reconstruction") - - if smoothing_sigma is not None: - gaussian_kernel = gaussian_kernel_1d(smoothing_sigma).to(self.device) - else: - gaussian_kernel = None - - print( - "Devices", - sirt_tilt_series.device, - proj_forward.device, - self.dataset.tilt_angles.device, - ) - - for iter in pbar: - proj_forward, loss = self._sirt_run_epoch( - tilt_series=sirt_tilt_series, - proj_forward=proj_forward, - angles=self.dataset.tilt_angles, - inline_alignment=iter > 0 and inline_alignment, - filter_name=filter_name, - gaussian_kernel=gaussian_kernel, - circle=circle, - ) - - pbar.set_description(f"SIRT Reconstruction | Loss: {loss.item():.4f}") - - self.loss.append(loss.item()) - - self.sirt_recon_vol = self.volume_obj - - # Permutation due to sinogram ordering. - self.sirt_recon_vol.obj = self.sirt_recon_vol.obj.permute(1, 2, 0) - - if plot_loss: - self.plot_loss() - - # TODO: ML Recon which has NeRF and AD depending on the object type. - # TODO: Temporary - - def create_batch_projection_rays(self, pixel_i, pixel_j, N, num_samples_per_ray): - """Create projection rays for entire batch simultaneously.""" - batch_size = len(pixel_i) - - # Convert all pixels to normalized coordinates - x_coords = (pixel_j / (N - 1)) * 2 - 1 - y_coords = (pixel_i / (N - 1)) * 2 - 1 - # TODO: maybe pixel_j.device? - # Create z coordinates - z_coords = torch.linspace(-1, 1, num_samples_per_ray, device=self.device) - - # Create rays for all pixels: [batch_size, num_samples_per_ray, 3] - rays = torch.zeros(batch_size, num_samples_per_ray, 3, device=self.device) - - # Fill coordinates efficiently - rays[:, :, 0] = x_coords.unsqueeze(1) # x constant per ray - rays[:, :, 1] = y_coords.unsqueeze(1) # y constant per ray - rays[:, :, 2] = z_coords.unsqueeze(0) # z varies along ray - - return rays - - @torch.compile(mode="reduce-overhead") - def transform_batch_ray_coordinates(self, rays, z1, x, z3, shifts, N, sampling_rate): - # Step 1: Apply shifts - shift_x_norm = (shifts[:, 0:1] * sampling_rate * 2) / (N - 1) - shift_y_norm = (shifts[:, 1:2] * sampling_rate * 2) / (N - 1) - - rays_x = rays[:, :, 0] - shift_x_norm - rays_y = rays[:, :, 1] - shift_y_norm - rays_z = rays[:, :, 2] - - # Rotation 1: Z(-z3) - theta = torch.deg2rad(-z3).view(-1, 1) - cos_t = torch.cos(theta) - sin_t = torch.sin(theta) - - rays_x_rot1 = cos_t * rays_x - sin_t * rays_y - rays_y_rot1 = sin_t * rays_x + cos_t * rays_y - rays_z_rot1 = rays_z - - # Rotation 2: X(x) - theta = torch.deg2rad(x).view(-1, 1) - cos_t = torch.cos(theta) - sin_t = torch.sin(theta) - - rays_x_rot2 = rays_x_rot1 - rays_y_rot2 = cos_t * rays_y_rot1 - sin_t * rays_z_rot1 - rays_z_rot2 = sin_t * rays_y_rot1 + cos_t * rays_z_rot1 - - # Rotation 3: Z(-z1) - theta = torch.deg2rad(-z1).view(-1, 1) - cos_t = torch.cos(theta) - sin_t = torch.sin(theta) - - rays_x_final = cos_t * rays_x_rot2 - sin_t * rays_y_rot2 - rays_y_final = sin_t * rays_x_rot2 + cos_t * rays_y_rot2 - rays_z_final = rays_z_rot2 - - # Stack the final result - transformed_rays = torch.stack([rays_x_final, rays_y_final, rays_z_final], dim=2) - - return transformed_rays - - # TODO: Temp logger - def setup_logger(self, log_path): - if self.global_rank == 0: - self.temp_logger = SummaryWriter(log_dir=log_path) - else: - self.temp_logger = None - - def pretrain( - self, - # volume_dataset: PretrainVolumeDataset, - obj: ObjectINN, - batch_size: int, - soft_constraints: dict = None, - log_path: str = None, - optimizer_params: dict = None, - epochs: int = 100, - viz_freq: int = 1, - consistency_criterion: str = "mse", - ): - if not self.pretraining_instantiated: - self.setup_distributed() - # self.setup_pretraining_dataloader(volume_dataset, batch_size) - self.pretraining_instantiated = True - obj.model = self.build_model(obj._model) - self.obj = obj - - if soft_constraints is not None: - self.obj.soft_constraints = soft_constraints - - if not hasattr(self, "temp_logger"): - self.setup_logger(log_path=log_path) - - if optimizer_params is not None: - optimizer_params = self.scale_lr(optimizer_params) - self.optimizer_params = optimizer_params - self.set_optimizers() - device_type = self.device.type - consistency_loss_fn = None - if consistency_criterion[0].lower() == "mse": - consistency_loss_fn = MSELoss() - elif consistency_criterion[0].lower() == "l1": - consistency_loss_fn = L1Loss() - elif consistency_criterion[0].lower() == "mse_log": - consistency_loss_fn = MSELogMSELoss() - elif consistency_criterion[0].lower() == "llmse": - consistency_loss_fn = LLMSELoss() - elif consistency_criterion[0].lower() == "smooth_l1": - consistency_loss_fn = SmoothL1Loss(beta=consistency_criterion[1]) - elif consistency_criterion[0].lower() == "adaptive_smooth_l1": - # consistency_loss_fn = AdaptiveSmoothL1LossDDP( - # beta_init=consistency_criterion[1], ema_factor=0.99, eps=1e-8 - # ) - pass - elif consistency_criterion[0].lower() == "charbonnier": - consistency_loss_fn = CharbonnierLoss(epsilon=1e-12, reduction="mean") - else: - raise ValueError(f"Invalid consistency criterion: {consistency_criterion}") - - for epoch in range(epochs): - if self.pretraining_sampler is not None: - self.pretraining_sampler.set_epoch(epoch) - - self.obj.model.train() - - epoch_loss = 0.0 - epoch_consistency_loss = 0.0 - epoch_tv_loss = 0.0 - num_batches = 0 - - for batch_idx, batch in enumerate(self.pretraining_dataloader): - coords = batch["coords"].to(self.device, non_blocking=True) - target = batch["target"].to(self.device, non_blocking=True) - - for _, opt in self.optimizers.items(): - opt.zero_grad() - - with torch.autocast(device_type=device_type, dtype=torch.bfloat16, enabled=True): - outputs = self.obj.forward(coords) - consistency_loss = consistency_loss_fn(outputs, target) - tv_loss = self.obj.apply_soft_constraints(coords) - - loss = consistency_loss + tv_loss - - loss.backward() - - epoch_loss += loss.detach() - epoch_consistency_loss += consistency_loss.detach() - epoch_tv_loss += tv_loss.detach() - - torch.nn.utils.clip_grad_norm_(self.obj.model.parameters(), max_norm=1.0) - - for _, opt in self.optimizers.items(): - opt.step() - epoch_loss += loss.detach() - num_batches += 1 - - if epoch % viz_freq == 0 or epoch == epochs - 1: - avg_loss = epoch_loss / num_batches - with torch.no_grad(): - self.obj.create_volume( - world_size=self.world_size, - global_rank=self.global_rank, - # ray_size=volume_dataset.N, - ) - pred_full = self.obj.obj - loss_tensor = avg_loss.clone().detach() - avg_loss = loss_tensor.item() - avg_tv_loss = epoch_tv_loss / num_batches - avg_consistency_loss = epoch_consistency_loss / num_batches - - metrics = torch.tensor( - [avg_loss, avg_consistency_loss, avg_tv_loss], device=self.device - ) - if self.world_size > 1: - dist.all_reduce(metrics, op=dist.ReduceOp.AVG) - avg_loss, avg_consistency_loss, avg_tv_loss = metrics.tolist() - - if self.global_rank == 0: - self.temp_logger.add_scalar("Pretrain/total_loss", avg_loss, epoch) - self.temp_logger.add_scalar( - "Pretrain/consistency_loss", avg_consistency_loss, epoch - ) - self.temp_logger.add_scalar("Pretrain/tv_loss", avg_tv_loss, epoch) - - # current_lr = self.schedulers["model"].get_last_lr()[0] - # self.temp_logger.add_scalar("Pretrain/lr", current_lr, epoch) - - fig, ax = plt.subplots(ncols=4, figsize=(36, 12)) - ax[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap="turbo", vmin=0) - ax[0].set_title("Sum over Z-axis") - ax[1].matshow( - # pred_full[volume_dataset.N // 2].cpu().numpy(), cmap="turbo", vmin=0 - # ) - # ax[1].set_title(f"Slice at Z={volume_dataset.N // 2}") - ax[2].matshow(pred_full.sum(dim=1).cpu().numpy(), cmap="turbo", vmin=0) - ax[2].set_title("Sum over Y-axis") - ax[3].matshow(pred_full.sum(dim=2).cpu().numpy(), cmap="turbo", vmin=0) - ax[3].set_title("Sum over X-axis") - self.temp_logger.add_figure("Pretrain/viz", fig, epoch, close=True) - - plt.close(fig) - print( - f"Epoch [{epoch}/{epochs}] Pretrain Total Loss: {avg_loss:.6f}, Consistency Loss: {avg_consistency_loss:.6f}, TV Loss: {avg_tv_loss:.6f}" - ) - self.global_epochs += 1 - if self.global_rank == 0: - print(f"Pretraining Completed, saving model weights to {log_path}/model_weights.pth") - torch.save(self.obj.model.state_dict(), f"{log_path}/model_weights.pth") - - def recon( - self, - obj: ObjectINN, - batch_size: int, - num_workers: int = 0, - epochs=20, - use_amp=True, - viz_freq=1, - checkpoint_freq=5, - optimizer_params: dict = None, - scheduler_params: dict = None, - soft_constraints: dict = None, - vol_save_path: str = None, # TODO: TEMPORARY - log_path=None, - val_fraction: float = 0.0, - learn_shifts: bool = True, - # l1_loss: bool = False, - consistency_criterion: str = "mse", - model_weights_path: str = None, - force_cpu: bool = False, - ): - if not self.ddp_instantiated: - if not self.pretraining_instantiated: - self.setup_distributed() - if model_weights_path is not None: - print(f"Loading model weights from {model_weights_path}") - state_dict = torch.load(model_weights_path, map_location="cpu") - - # Handle DataParallel/DDP checkpoints - if any(k.startswith("module.") for k in state_dict.keys()): - new_state_dict = { - k.replace("module.", ""): v for k, v in state_dict.items() - } - else: - new_state_dict = state_dict - - obj.model.load_state_dict(new_state_dict) - print("Model weights loaded successfully") - obj.model = self.build_model(obj._model) - self.obj = obj - - self.setup_dataloader(self.dataset, batch_size, num_workers, val_fraction) - self.ddp_instantiated = True - - if soft_constraints is not None: - self.obj.soft_constraints = soft_constraints - - if not hasattr(self, "temp_logger"): - self.setup_logger(log_path=log_path) - - zero_tilt_idx = torch.argmin(torch.abs(self.dataset.tilt_angles)).item() - - if self.global_rank == 0: - print( - f"Using projection {zero_tilt_idx} (angle={self.dataset.tilt_angles[zero_tilt_idx]:.2f}°) as reference" - ) - print(f"Using consistency criterion: {consistency_criterion}") - - # Auxiliary params setup - self.dataset.setup_auxiliary_params(zero_tilt_idx, self.device, learn_shifts) - aux_params = self.dataset.auxiliary_params - # Scaling learning rates to account for distributed training - if optimizer_params is not None: - optimizer_params = self.scale_lr(optimizer_params) - self.optimizer_params = optimizer_params - self.set_optimizers() - - if scheduler_params is not None: - self.scheduler_params = scheduler_params - self.set_schedulers(self.scheduler_params, num_iter=epochs) - - aux_norm = torch.tensor(0.0, device=self.device) - model_norm = torch.tensor(0.0, device=self.device) - - for _, opt in self.optimizers.items(): - opt.zero_grad() - - N = max(self.dataset.dims) - - device_type = self.device.type - autocast_dtype = torch.bfloat16 if use_amp else None - - for epoch in range(epochs): - num_samples_per_ray = get_num_samples_per_ray(N=N, epoch=self.global_epochs) - - # num_samples_per_ray = get_num_samples_per_ray(epoch) - # Log the change if it happens - if self.global_rank == 0: - print(f"Epoch {epoch}: num_samples_per_ray = {num_samples_per_ray}") - - if self.global_rank == 0 and self.global_epochs > 0: - prev_samples = get_num_samples_per_ray(N=N, epoch=self.global_epochs - 1) - - if num_samples_per_ray != prev_samples: - print( - f"Epoch {epoch}: Changing num_samples_per_ray from {prev_samples} to {num_samples_per_ray}" - ) - - if self.sampler is not None: - self.sampler.set_epoch(epoch) - - epoch_loss = 0.0 - epoch_consistency_loss = 0.0 - epoch_tv_loss = 0.0 - epoch_z1_loss = 0.0 - - num_batches = 0 - consistency_loss_fn = None - if consistency_criterion[0].lower() == "mse": - consistency_loss_fn = MSELoss() - elif consistency_criterion[0].lower() == "l1": - consistency_loss_fn = L1Loss() - elif consistency_criterion[0].lower() == "mse_log": - consistency_loss_fn = MSELogMSELoss() - elif consistency_criterion[0].lower() == "llmse": - consistency_loss_fn = LLMSELoss() - elif consistency_criterion[0].lower() == "smooth_l1": - consistency_loss_fn = SmoothL1Loss(beta=consistency_criterion[1]) - elif consistency_criterion[0].lower() == "adaptive_smooth_l1": - # consistency_loss_fn = AdaptiveSmoothL1LossDDP( - # beta_init=consistency_criterion[1], ema_factor=0.99, eps=1e-8 - # ) - pass - elif consistency_criterion[0].lower() == "charbonnier": - consistency_loss_fn = CharbonnierLoss(epsilon=1e-12, reduction="mean") - else: - raise ValueError(f"Invalid consistency criterion: {consistency_criterion}") - - for batch_idx, batch in enumerate(self.dataloader): - projection_indices = batch["projection_idx"] - - pixel_i = batch["pixel_i"].float().to(self.device, non_blocking=True) - pixel_j = batch["pixel_j"].float().to(self.device, non_blocking=True) - target_values = batch["target_value"].to(self.device, non_blocking=True) - phis = batch["phi"].to(self.device, non_blocking=True) - projection_indices = batch["projection_idx"].to(self.device, non_blocking=True) - - shifts, z1_params, z3_params = aux_params(None) - batch_shifts = torch.index_select(shifts, 0, projection_indices) - batch_z1 = torch.index_select(z1_params, 0, projection_indices) - batch_z3 = torch.index_select(z3_params, 0, projection_indices) - - with torch.autocast( - device_type=device_type, dtype=autocast_dtype, enabled=use_amp - ): - with torch.no_grad(): - batch_ray_coords = self.create_batch_projection_rays( - pixel_i, pixel_j, N, num_samples_per_ray - ) - - # TODO: .forward passing z1, x, z3, shifts, N, sampling_rate - transformed_rays = self.transform_batch_ray_coordinates( - batch_ray_coords, - z1=batch_z1, - x=phis, - z3=batch_z3, - shifts=batch_shifts, - N=N, - sampling_rate=1.0, - ) - - all_coords = transformed_rays.view(-1, 3) - - all_densities = self.obj.forward(all_coords) - - tv_loss = self.obj.apply_soft_constraints(all_coords) - ray_densities = all_densities.view( - len(target_values), num_samples_per_ray - ) # Reshape rays and integarte - step_size = 2.0 / (num_samples_per_ray - 1) - - predicted_values = ray_densities.sum(dim=1) * step_size - - consistency_loss = consistency_loss_fn(predicted_values, target_values) - tv_loss_z1 = torch.tensor(0.0, device=self.device) - - loss = consistency_loss + tv_loss + tv_loss_z1 - - loss.backward() - - epoch_loss += loss.detach() - epoch_consistency_loss += consistency_loss.detach() - epoch_tv_loss += tv_loss.detach() - epoch_z1_loss += tv_loss_z1.detach() - num_batches += 1 - - for key, opt in self.optimizers.items(): - if key == "model": - model_norm = torch.nn.utils.clip_grad_norm_( - self.obj.model.parameters(), max_norm=1 - ) - elif key == "aux_params": - aux_norm = torch.nn.utils.clip_grad_norm_( - self.dataset.auxiliary_params.parameters(), max_norm=1 - ) - opt.step() - opt.zero_grad() - - for key, sched in self.schedulers.items(): - if isinstance(sched, torch.optim.lr_scheduler.ReduceLROnPlateau): - sched.step(epoch_loss) - else: - sched.step() - - if epoch % viz_freq == 0 or epoch == epochs - 1 or epoch % checkpoint_freq == 0: - with torch.no_grad(): - self.obj.create_volume( - world_size=self.world_size, - global_rank=self.global_rank, - ray_size=num_samples_per_ray, - ) - pred_full = self.obj.obj - avg_loss = epoch_loss.item() / num_batches - avg_consistency_loss = epoch_consistency_loss.item() / num_batches - avg_tv_loss = epoch_tv_loss.item() / num_batches - avg_z1_loss = epoch_z1_loss.item() / num_batches - shifts, z1, z3 = self.dataset.auxiliary_params.forward() - shifts = shifts.detach().cpu() - z1 = z1.detach().cpu() - z3 = z3.detach().cpu() - metrics = torch.tensor( - [avg_loss, avg_consistency_loss, avg_tv_loss, avg_z1_loss], - device=self.device, - ) - if self.world_size > 1: - dist.all_reduce(metrics, op=dist.ReduceOp.AVG) - avg_loss, avg_consistency_loss, avg_tv_loss, avg_z1_loss = metrics.tolist() - - if hasattr(self, "val_dataloader") and self.val_dataloader is not None: - val_loss = self.validate( - aux_params, num_samples_per_ray, device_type, autocast_dtype, use_amp, N - ) - - if self.global_rank == 0 and (epoch % viz_freq == 0 or epoch == epochs - 1): - with torch.no_grad(): - current_lr = self.schedulers["model"].get_last_lr()[0] - - # Log metrics - self.temp_logger.add_scalar( - f"train/consistency_loss_{consistency_criterion[0] if isinstance(consistency_criterion, tuple) else consistency_criterion}", - avg_consistency_loss, - self.global_epochs, - ) - if hasattr(self, "val_dataloader") and self.val_dataloader is not None: - self.temp_logger.add_scalar( - f"val/consistency_loss_{consistency_criterion[0] if isinstance(consistency_criterion, tuple) else consistency_criterion}", - val_loss, - self.global_epochs, - ) - self.temp_logger.add_scalar("train/z1_loss", avg_z1_loss, self.global_epochs) - # if tv_weight > 0: - self.temp_logger.add_scalar("train/tv_loss", avg_tv_loss, self.global_epochs) - self.temp_logger.add_scalar("train/total_loss", avg_loss, self.global_epochs) - self.temp_logger.add_scalar( - "train/model_grad_norm", model_norm.item(), self.global_epochs - ) - self.temp_logger.add_scalar( - "train/aux_grad_norm", aux_norm.item(), self.global_epochs - ) - self.temp_logger.add_scalar("train/lr", current_lr, self.global_epochs) - self.temp_logger.add_scalar( - "train/num_samples_per_ray", num_samples_per_ray, self.global_epochs - ) - if consistency_criterion[0].lower() == "adaptive_smooth_l1": - self.temp_logger.add_scalar( - "train/beta_2", consistency_loss_fn.beta2.item(), self.global_epochs - ) - fig, axes = plt.subplots(1, 5, figsize=(36, 12)) - axes[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap="turbo", vmin=0) - axes[0].set_title("Sum over Z-axis") - - axes[1].matshow(pred_full[N // 2].cpu().numpy(), cmap="turbo", vmin=0) - axes[1].set_title(f"Slice at Z={N // 2}") - - slice_start = max(0, N // 2 - 5) - slice_end = min(N, N // 2 + 6) - thick_slice = pred_full[slice_start:slice_end].sum(dim=0).cpu().numpy() - axes[2].matshow(thick_slice, cmap="turbo", vmin=0) - axes[2].set_title(f"Thick slice sum (Z={slice_start}:{slice_end - 1})") - - axes[3].matshow(pred_full.sum(dim=1).cpu().numpy(), cmap="turbo", vmin=0) - axes[3].set_title("Sum over Y-axis") - - axes[4].matshow(pred_full.sum(dim=2).cpu().numpy(), cmap="turbo", vmin=0) - axes[4].set_title("Sum over X-axis") - - self.temp_logger.add_figure("train/viz", fig, self.global_epochs, close=True) - - fig, axes = plt.subplots(ncols=3, figsize=(10, 5)) - axes[0].plot(shifts[:, 0].cpu().numpy(), label="Shifts X") - axes[0].plot(shifts[:, 1].cpu().numpy(), label="Shifts Y") - axes[0].legend() - axes[1].plot(z1.cpu().numpy(), label="Z1") - axes[2].plot(z3.cpu().numpy(), label="Z3") - self.temp_logger.add_figure( - "train/auxiliary_params", fig, self.global_epochs, close=True - ) - plt.close(fig) - - if epoch % checkpoint_freq == 0 or self.global_epochs == epochs - 1: - with torch.no_grad(): - if self.global_rank == 0: - save_path = f"{vol_save_path}/volume_epoch_{self.global_epochs:04d}.pt" - torch.save(pred_full.cpu(), save_path) - - self.global_epochs += 1 - - if self.global_rank == 0: - print("Training complete.") - - torch.save(self.dataset.auxiliary_params.forward(), f"{log_path}/auxiliary_params.pt") - # print("Successfully setup DDP and dataloader") - - def validate(self, aux_params, num_samples_per_ray, device_type, autocast_dtype, use_amp, N): - """Validate the model on the validation set.""" - self.obj.model.eval() - aux_params.eval() - - val_loss = 0.0 - num_batches = 0 - - with torch.no_grad(): - for batch in self.val_dataloader: - pixel_i = batch["pixel_i"].float().to(self.device, non_blocking=True) - pixel_j = batch["pixel_j"].float().to(self.device, non_blocking=True) - target_values = batch["target_value"].to(self.device, non_blocking=True) - phis = batch["phi"].to(self.device, non_blocking=True) - projection_indices = batch["projection_idx"].to(self.device, non_blocking=True) - - shifts, z1_params, z3_params = aux_params(None) - batch_shifts = torch.index_select(shifts, 0, projection_indices) - batch_z1 = torch.index_select(z1_params, 0, projection_indices) - batch_z3 = torch.index_select(z3_params, 0, projection_indices) - - with torch.autocast( - device_type=device_type, dtype=autocast_dtype, enabled=use_amp - ): - with torch.no_grad(): - batch_ray_coords = self.create_batch_projection_rays( - pixel_i, pixel_j, N, num_samples_per_ray - ) - - transformed_rays = self.transform_batch_ray_coordinates( - batch_ray_coords, - z1=batch_z1, - x=phis, - z3=batch_z3, - shifts=batch_shifts, - N=N, - sampling_rate=1.0, - ) - - all_coords = transformed_rays.view(-1, 3) - - all_densities = self.obj.forward(all_coords) - - ray_densities = all_densities.view( - len(target_values), num_samples_per_ray - ) # Reshape rays and integarte - step_size = 2.0 / (num_samples_per_ray - 1) - - predicted_values = ray_densities.sum(dim=1) * step_size - - mse_loss = torch.nn.functional.mse_loss(predicted_values, target_values) - - loss = mse_loss - - val_loss += loss.detach() - num_batches += 1 - - avg_val_loss = val_loss / num_batches if num_batches > 0 else 0.0 - - if self.world_size > 1: - val_loss_tensor = avg_val_loss.detach().clone() - dist.all_reduce(val_loss_tensor, op=dist.ReduceOp.AVG) - avg_val_loss = val_loss_tensor.item() - - self.obj.model.train() - aux_params.train() - - return avg_val_loss - - def ad_recon( - self, - optimizer_params: dict, - num_iter: int = 0, - reset: bool = False, - scheduler_params: dict | None = None, - hard_constraints: dict | None = None, - soft_constraints: dict | None = None, - # store_iterations: bool | None = None, - # store_iterations_every: int | None = None, - # autograd: bool = True, - ): - if reset: - self.reset_recon() - - self.hard_constraints = hard_constraints - self.soft_constraints = soft_constraints - - # Make sure everything is in the correct device, might be redundant/cleaner way to do this - self.dataset.to(self.device) - self.volume_obj.to(self.device) - - # Making optimizable parameters into leaf tensors. - self.dataset.shifts = self.dataset.shifts.detach().to(self.device).requires_grad_(True) - self.dataset.z1_angles = ( - self.dataset.z1_angles.detach().to(self.device).requires_grad_(True) - ) - self.dataset.z3_angles = ( - self.dataset.z3_angles.detach().to(self.device).requires_grad_(True) - ) - - if optimizer_params is not None: - self.optimizer_params = optimizer_params - self.set_optimizers() - - if scheduler_params is not None: - self.scheduler_params = scheduler_params - self.set_schedulers(self.scheduler_params, num_iter=num_iter) - - if hard_constraints is not None: - self.volume_obj.hard_constraints = hard_constraints - if soft_constraints is not None: - self.volume_obj.soft_constraints = soft_constraints - - pbar = tqdm(range(num_iter), desc="AD Reconstruction") - - for a0 in pbar: - total_loss = 0.0 - tilt_series_loss = 0.0 - - pred_volume = self.volume_obj.forward() - - for i in range(len(self.dataset.tilt_series)): - forward_projection = self.projection_operator( - vol=pred_volume, - z1=self.dataset.z1_angles[i], - x=self.dataset.tilt_angles[i], - z3=self.dataset.z3_angles[i], - shift_x=self.dataset.shifts[i, 0], - shift_y=self.dataset.shifts[i, 1], - device=self.device, - ) - - tilt_series_loss += torch.nn.functional.mse_loss( - forward_projection, self.dataset.tilt_series[i] - ) - tilt_series_loss /= len(self.dataset.tilt_series) - - total_loss = tilt_series_loss + self.volume_obj.soft_loss - self.loss.append(total_loss.item()) - - total_loss.backward() - - for opt in self.optimizers.values(): - opt.step() - opt.zero_grad() - - if self.schedulers is not None: - for sch in self.schedulers.values(): - if isinstance(sch, torch.optim.lr_scheduler.ReduceLROnPlateau): - sch.step(total_loss) - elif sch is not None: - sch.step() - - pbar.set_description(f"AD Reconstruction | Loss: {total_loss:.4f}") - - if self.logger is not None: - self.logger.log_scalar("loss/total", total_loss.item(), a0) - self.logger.log_scalar("loss/tilt_series", tilt_series_loss.item(), a0) - self.logger.log_scalar( - "loss/soft constraints", self.volume_obj.soft_loss.item(), a0 - ) - - if a0 % self.logger.log_images_every == 0: - self.logger.projection_images( - volume_obj=self.volume_obj, - epoch=a0, - ) - self.logger.tilt_angles_figure(dataset=self.dataset, step=a0) - - self.logger.flush() - - self.ad_recon_vol = self.volume_obj.forward() - - return self - - def reset_recon(self) -> None: - if isinstance(self.volume_obj, ObjectVoxelwise): - self.volume_obj.reset() - - self.ad_recon_vol = None - - # --- Projection Operators ---- - def projection_operator( - self, - vol, - z1, - x, - z3, - shift_x, - shift_y, - device, - ): - projection = ( - rot_ZXZ( - mags=vol.unsqueeze(0), # Add batch dimension - z1=z1, - x=-x, - z3=z3, - device=device, - mode="bilinear", - ) - .squeeze() - .sum(axis=0) - ) - - shifted_projection = differentiable_shift_2d( - image=projection, - shift_x=shift_x, - shift_y=shift_y, - sampling_rate=1.0, # Assuming 1 pixel = 1 physical unit - ) - - return shifted_projection +# import matplotlib.pyplot as plt +# import numpy as np +# import torch +# import torch.distributed as dist +# from torch.nn import SmoothL1Loss +# from torch.utils.tensorboard import SummaryWriter + +# # from torch_radon.radon import ParallelBeam as Radon +# from tqdm.auto import tqdm + +# from quantem.core.ml.loss_functions import ( +# CharbonnierLoss, +# L1Loss, +# LLMSELoss, +# MSELogMSELoss, +# MSELoss, +# ) + +# # Temporary imports for TomographyNERF +# from quantem.tomography.object_models import ObjectINN, ObjectVoxelwise +# from quantem.tomography.tomography_base import TomographyBase +# from quantem.tomography.tomography_conv import TomographyConv +# from quantem.tomography.tomography_ml import TomographyML +# from quantem.tomography.utils import differentiable_shift_2d, gaussian_kernel_1d, rot_ZXZ + + +# # Temporary aux class for TomographyNERF +# # TODO: Maybe put this in INN? +# def get_num_samples_per_ray(N: int, epoch: int): +# """Increase number of samples per ray at specific epochs.""" +# # Exponential schedule + +# epochs = np.linspace(0, 10, 5, dtype=int) +# # schedule = np.linspace(20, N, 5, dtype=int) +# schedule = np.array([20, 100, 150, 200, 200]) +# # schedule = np.array([200, 200, 200, 200, 200]) +# # schedule = np.array([20, 100, 250, 500, 500]) +# # schedule = np.array([500, 500, 500, 500, 500]) +# # schedule = np.array([300, 300, 300, 300, 300]) +# # schedule = np.exp(schedule) +# schedule_warmup = dict[int, int](zip(epochs, schedule)) + +# for epoch_threshold, samples in schedule_warmup.items(): +# if epoch >= epoch_threshold: +# num_samples = samples + +# return num_samples + + +# class Tomography(TomographyConv, TomographyML, TomographyBase, TomographyDDP): +# """ +# Top level class for either using conventional or ML-based reconstruction methods +# for tomography. +# """ + +# def __init__( +# self, +# dataset, +# volume_obj, +# device, +# _token, +# ): +# super().__init__(dataset, volume_obj, device, _token) + +# # TODO: More elegant way of doing this. +# self.global_epochs = 0 +# self.ddp_instantiated = False +# self.pretraining_instantiated = False + +# # --- Reconstruction Method --- + +# def sirt_recon( +# self, +# num_iterations: int = 10, +# inline_alignment: bool = False, +# enforce_positivity: bool = True, +# volume_shape: tuple = None, +# reset: bool = True, +# smoothing_sigma: float = None, +# shrinkage: float = None, +# filter_name: str = "hamming", +# circle: bool = True, +# plot_loss: bool = False, +# ): +# num_angles, num_rows, num_cols = self.dataset.tilt_series.shape +# sirt_tilt_series = self.dataset.tilt_series.clone() +# sirt_tilt_series = sirt_tilt_series.permute(2, 0, 1) + +# hard_constraints = { +# "positivity": enforce_positivity, +# "shrinkage": shrinkage, +# } +# self.volume_obj.hard_constraints = hard_constraints + +# if volume_shape is None: +# volume_shape = (num_rows, num_rows, num_rows) +# else: +# D, H, W = volume_shape + +# if reset: +# self.volume_obj.reset() +# self.loss = [] + +# proj_forward = torch.zeros_like(self.dataset.tilt_series) + +# pbar = tqdm(range(num_iterations), desc="SIRT Reconstruction") + +# if smoothing_sigma is not None: +# gaussian_kernel = gaussian_kernel_1d(smoothing_sigma).to(self.device) +# else: +# gaussian_kernel = None + +# print( +# "Devices", +# sirt_tilt_series.device, +# proj_forward.device, +# self.dataset.tilt_angles.device, +# ) + +# for iter in pbar: +# proj_forward, loss = self._sirt_run_epoch( +# tilt_series=sirt_tilt_series, +# proj_forward=proj_forward, +# angles=self.dataset.tilt_angles, +# inline_alignment=iter > 0 and inline_alignment, +# filter_name=filter_name, +# gaussian_kernel=gaussian_kernel, +# circle=circle, +# ) + +# pbar.set_description(f"SIRT Reconstruction | Loss: {loss.item():.4f}") + +# self.loss.append(loss.item()) + +# self.sirt_recon_vol = self.volume_obj + +# # Permutation due to sinogram ordering. +# self.sirt_recon_vol.obj = self.sirt_recon_vol.obj.permute(1, 2, 0) + +# if plot_loss: +# self.plot_loss() + +# # TODO: ML Recon which has NeRF and AD depending on the object type. +# # TODO: Temporary + +# def create_batch_projection_rays(self, pixel_i, pixel_j, N, num_samples_per_ray): +# """Create projection rays for entire batch simultaneously.""" +# batch_size = len(pixel_i) + +# # Convert all pixels to normalized coordinates +# x_coords = (pixel_j / (N - 1)) * 2 - 1 +# y_coords = (pixel_i / (N - 1)) * 2 - 1 +# # TODO: maybe pixel_j.device? +# # Create z coordinates +# z_coords = torch.linspace(-1, 1, num_samples_per_ray, device=self.device) + +# # Create rays for all pixels: [batch_size, num_samples_per_ray, 3] +# rays = torch.zeros(batch_size, num_samples_per_ray, 3, device=self.device) + +# # Fill coordinates efficiently +# rays[:, :, 0] = x_coords.unsqueeze(1) # x constant per ray +# rays[:, :, 1] = y_coords.unsqueeze(1) # y constant per ray +# rays[:, :, 2] = z_coords.unsqueeze(0) # z varies along ray + +# return rays + +# @torch.compile(mode="reduce-overhead") +# def transform_batch_ray_coordinates(self, rays, z1, x, z3, shifts, N, sampling_rate): +# # Step 1: Apply shifts +# shift_x_norm = (shifts[:, 0:1] * sampling_rate * 2) / (N - 1) +# shift_y_norm = (shifts[:, 1:2] * sampling_rate * 2) / (N - 1) + +# rays_x = rays[:, :, 0] - shift_x_norm +# rays_y = rays[:, :, 1] - shift_y_norm +# rays_z = rays[:, :, 2] + +# # Rotation 1: Z(-z3) +# theta = torch.deg2rad(-z3).view(-1, 1) +# cos_t = torch.cos(theta) +# sin_t = torch.sin(theta) + +# rays_x_rot1 = cos_t * rays_x - sin_t * rays_y +# rays_y_rot1 = sin_t * rays_x + cos_t * rays_y +# rays_z_rot1 = rays_z + +# # Rotation 2: X(x) +# theta = torch.deg2rad(x).view(-1, 1) +# cos_t = torch.cos(theta) +# sin_t = torch.sin(theta) + +# rays_x_rot2 = rays_x_rot1 +# rays_y_rot2 = cos_t * rays_y_rot1 - sin_t * rays_z_rot1 +# rays_z_rot2 = sin_t * rays_y_rot1 + cos_t * rays_z_rot1 + +# # Rotation 3: Z(-z1) +# theta = torch.deg2rad(-z1).view(-1, 1) +# cos_t = torch.cos(theta) +# sin_t = torch.sin(theta) + +# rays_x_final = cos_t * rays_x_rot2 - sin_t * rays_y_rot2 +# rays_y_final = sin_t * rays_x_rot2 + cos_t * rays_y_rot2 +# rays_z_final = rays_z_rot2 + +# # Stack the final result +# transformed_rays = torch.stack([rays_x_final, rays_y_final, rays_z_final], dim=2) + +# return transformed_rays + +# # TODO: Temp logger +# def setup_logger(self, log_path): +# if self.global_rank == 0: +# self.temp_logger = SummaryWriter(log_dir=log_path) +# else: +# self.temp_logger = None + +# def pretrain( +# self, +# # volume_dataset: PretrainVolumeDataset, +# obj: ObjectINN, +# batch_size: int, +# soft_constraints: dict = None, +# log_path: str = None, +# optimizer_params: dict = None, +# epochs: int = 100, +# viz_freq: int = 1, +# consistency_criterion: str = "mse", +# ): +# if not self.pretraining_instantiated: +# self.setup_distributed() +# # self.setup_pretraining_dataloader(volume_dataset, batch_size) +# self.pretraining_instantiated = True +# obj.model = self.build_model(obj._model) +# self.obj = obj + +# if soft_constraints is not None: +# self.obj.soft_constraints = soft_constraints + +# if not hasattr(self, "temp_logger"): +# self.setup_logger(log_path=log_path) + +# if optimizer_params is not None: +# optimizer_params = self.scale_lr(optimizer_params) +# self.optimizer_params = optimizer_params +# self.set_optimizers() +# device_type = self.device.type +# consistency_loss_fn = None +# if consistency_criterion[0].lower() == "mse": +# consistency_loss_fn = MSELoss() +# elif consistency_criterion[0].lower() == "l1": +# consistency_loss_fn = L1Loss() +# elif consistency_criterion[0].lower() == "mse_log": +# consistency_loss_fn = MSELogMSELoss() +# elif consistency_criterion[0].lower() == "llmse": +# consistency_loss_fn = LLMSELoss() +# elif consistency_criterion[0].lower() == "smooth_l1": +# consistency_loss_fn = SmoothL1Loss(beta=consistency_criterion[1]) +# elif consistency_criterion[0].lower() == "adaptive_smooth_l1": +# # consistency_loss_fn = AdaptiveSmoothL1LossDDP( +# # beta_init=consistency_criterion[1], ema_factor=0.99, eps=1e-8 +# # ) +# pass +# elif consistency_criterion[0].lower() == "charbonnier": +# consistency_loss_fn = CharbonnierLoss(epsilon=1e-12, reduction="mean") +# else: +# raise ValueError(f"Invalid consistency criterion: {consistency_criterion}") + +# for epoch in range(epochs): +# if self.pretraining_sampler is not None: +# self.pretraining_sampler.set_epoch(epoch) + +# self.obj.model.train() + +# epoch_loss = 0.0 +# epoch_consistency_loss = 0.0 +# epoch_tv_loss = 0.0 +# num_batches = 0 + +# for batch_idx, batch in enumerate(self.pretraining_dataloader): +# coords = batch["coords"].to(self.device, non_blocking=True) +# target = batch["target"].to(self.device, non_blocking=True) + +# for _, opt in self.optimizers.items(): +# opt.zero_grad() + +# with torch.autocast(device_type=device_type, dtype=torch.bfloat16, enabled=True): +# outputs = self.obj.forward(coords) +# consistency_loss = consistency_loss_fn(outputs, target) +# tv_loss = self.obj.apply_soft_constraints(coords) + +# loss = consistency_loss + tv_loss + +# loss.backward() + +# epoch_loss += loss.detach() +# epoch_consistency_loss += consistency_loss.detach() +# epoch_tv_loss += tv_loss.detach() + +# torch.nn.utils.clip_grad_norm_(self.obj.model.parameters(), max_norm=1.0) + +# for _, opt in self.optimizers.items(): +# opt.step() +# epoch_loss += loss.detach() +# num_batches += 1 + +# if epoch % viz_freq == 0 or epoch == epochs - 1: +# avg_loss = epoch_loss / num_batches +# with torch.no_grad(): +# self.obj.create_volume( +# world_size=self.world_size, +# global_rank=self.global_rank, +# # ray_size=volume_dataset.N, +# ) +# pred_full = self.obj.obj +# loss_tensor = avg_loss.clone().detach() +# avg_loss = loss_tensor.item() +# avg_tv_loss = epoch_tv_loss / num_batches +# avg_consistency_loss = epoch_consistency_loss / num_batches + +# metrics = torch.tensor( +# [avg_loss, avg_consistency_loss, avg_tv_loss], device=self.device +# ) +# if self.world_size > 1: +# dist.all_reduce(metrics, op=dist.ReduceOp.AVG) +# avg_loss, avg_consistency_loss, avg_tv_loss = metrics.tolist() + +# if self.global_rank == 0: +# self.temp_logger.add_scalar("Pretrain/total_loss", avg_loss, epoch) +# self.temp_logger.add_scalar( +# "Pretrain/consistency_loss", avg_consistency_loss, epoch +# ) +# self.temp_logger.add_scalar("Pretrain/tv_loss", avg_tv_loss, epoch) + +# # current_lr = self.schedulers["model"].get_last_lr()[0] +# # self.temp_logger.add_scalar("Pretrain/lr", current_lr, epoch) + +# fig, ax = plt.subplots(ncols=4, figsize=(36, 12)) +# ax[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap="turbo", vmin=0) +# ax[0].set_title("Sum over Z-axis") +# ax[1].matshow( +# # pred_full[volume_dataset.N // 2].cpu().numpy(), cmap="turbo", vmin=0 +# # ) +# # ax[1].set_title(f"Slice at Z={volume_dataset.N // 2}") +# ax[2].matshow(pred_full.sum(dim=1).cpu().numpy(), cmap="turbo", vmin=0) +# ax[2].set_title("Sum over Y-axis") +# ax[3].matshow(pred_full.sum(dim=2).cpu().numpy(), cmap="turbo", vmin=0) +# ax[3].set_title("Sum over X-axis") +# self.temp_logger.add_figure("Pretrain/viz", fig, epoch, close=True) + +# plt.close(fig) +# print( +# f"Epoch [{epoch}/{epochs}] Pretrain Total Loss: {avg_loss:.6f}, Consistency Loss: {avg_consistency_loss:.6f}, TV Loss: {avg_tv_loss:.6f}" +# ) +# self.global_epochs += 1 +# if self.global_rank == 0: +# print(f"Pretraining Completed, saving model weights to {log_path}/model_weights.pth") +# torch.save(self.obj.model.state_dict(), f"{log_path}/model_weights.pth") + +# def recon( +# self, +# obj: ObjectINN, +# batch_size: int, +# num_workers: int = 0, +# epochs=20, +# use_amp=True, +# viz_freq=1, +# checkpoint_freq=5, +# optimizer_params: dict = None, +# scheduler_params: dict = None, +# soft_constraints: dict = None, +# vol_save_path: str = None, # TODO: TEMPORARY +# log_path=None, +# val_fraction: float = 0.0, +# learn_shifts: bool = True, +# # l1_loss: bool = False, +# consistency_criterion: str = "mse", +# model_weights_path: str = None, +# force_cpu: bool = False, +# ): +# if not self.ddp_instantiated: +# if not self.pretraining_instantiated: +# self.setup_distributed() +# if model_weights_path is not None: +# print(f"Loading model weights from {model_weights_path}") +# state_dict = torch.load(model_weights_path, map_location="cpu") + +# # Handle DataParallel/DDP checkpoints +# if any(k.startswith("module.") for k in state_dict.keys()): +# new_state_dict = { +# k.replace("module.", ""): v for k, v in state_dict.items() +# } +# else: +# new_state_dict = state_dict + +# obj.model.load_state_dict(new_state_dict) +# print("Model weights loaded successfully") +# obj.model = self.build_model(obj._model) +# self.obj = obj + +# self.setup_dataloader(self.dataset, batch_size, num_workers, val_fraction) +# self.ddp_instantiated = True + +# if soft_constraints is not None: +# self.obj.soft_constraints = soft_constraints + +# if not hasattr(self, "temp_logger"): +# self.setup_logger(log_path=log_path) + +# zero_tilt_idx = torch.argmin(torch.abs(self.dataset.tilt_angles)).item() + +# if self.global_rank == 0: +# print( +# f"Using projection {zero_tilt_idx} (angle={self.dataset.tilt_angles[zero_tilt_idx]:.2f}°) as reference" +# ) +# print(f"Using consistency criterion: {consistency_criterion}") + +# # Auxiliary params setup +# self.dataset.setup_auxiliary_params(zero_tilt_idx, self.device, learn_shifts) +# aux_params = self.dataset.auxiliary_params +# # Scaling learning rates to account for distributed training +# if optimizer_params is not None: +# optimizer_params = self.scale_lr(optimizer_params) +# self.optimizer_params = optimizer_params +# self.set_optimizers() + +# if scheduler_params is not None: +# self.scheduler_params = scheduler_params +# self.set_schedulers(self.scheduler_params, num_iter=epochs) + +# aux_norm = torch.tensor(0.0, device=self.device) +# model_norm = torch.tensor(0.0, device=self.device) + +# for _, opt in self.optimizers.items(): +# opt.zero_grad() + +# N = max(self.dataset.dims) + +# device_type = self.device.type +# autocast_dtype = torch.bfloat16 if use_amp else None + +# for epoch in range(epochs): +# num_samples_per_ray = get_num_samples_per_ray(N=N, epoch=self.global_epochs) + +# # num_samples_per_ray = get_num_samples_per_ray(epoch) +# # Log the change if it happens +# if self.global_rank == 0: +# print(f"Epoch {epoch}: num_samples_per_ray = {num_samples_per_ray}") + +# if self.global_rank == 0 and self.global_epochs > 0: +# prev_samples = get_num_samples_per_ray(N=N, epoch=self.global_epochs - 1) + +# if num_samples_per_ray != prev_samples: +# print( +# f"Epoch {epoch}: Changing num_samples_per_ray from {prev_samples} to {num_samples_per_ray}" +# ) + +# if self.sampler is not None: +# self.sampler.set_epoch(epoch) + +# epoch_loss = 0.0 +# epoch_consistency_loss = 0.0 +# epoch_tv_loss = 0.0 +# epoch_z1_loss = 0.0 + +# num_batches = 0 +# consistency_loss_fn = None +# if consistency_criterion[0].lower() == "mse": +# consistency_loss_fn = MSELoss() +# elif consistency_criterion[0].lower() == "l1": +# consistency_loss_fn = L1Loss() +# elif consistency_criterion[0].lower() == "mse_log": +# consistency_loss_fn = MSELogMSELoss() +# elif consistency_criterion[0].lower() == "llmse": +# consistency_loss_fn = LLMSELoss() +# elif consistency_criterion[0].lower() == "smooth_l1": +# consistency_loss_fn = SmoothL1Loss(beta=consistency_criterion[1]) +# elif consistency_criterion[0].lower() == "adaptive_smooth_l1": +# # consistency_loss_fn = AdaptiveSmoothL1LossDDP( +# # beta_init=consistency_criterion[1], ema_factor=0.99, eps=1e-8 +# # ) +# pass +# elif consistency_criterion[0].lower() == "charbonnier": +# consistency_loss_fn = CharbonnierLoss(epsilon=1e-12, reduction="mean") +# else: +# raise ValueError(f"Invalid consistency criterion: {consistency_criterion}") + +# for batch_idx, batch in enumerate(self.dataloader): +# projection_indices = batch["projection_idx"] + +# pixel_i = batch["pixel_i"].float().to(self.device, non_blocking=True) +# pixel_j = batch["pixel_j"].float().to(self.device, non_blocking=True) +# target_values = batch["target_value"].to(self.device, non_blocking=True) +# phis = batch["phi"].to(self.device, non_blocking=True) +# projection_indices = batch["projection_idx"].to(self.device, non_blocking=True) + +# shifts, z1_params, z3_params = aux_params(None) +# batch_shifts = torch.index_select(shifts, 0, projection_indices) +# batch_z1 = torch.index_select(z1_params, 0, projection_indices) +# batch_z3 = torch.index_select(z3_params, 0, projection_indices) + +# with torch.autocast( +# device_type=device_type, dtype=autocast_dtype, enabled=use_amp +# ): +# with torch.no_grad(): +# batch_ray_coords = self.create_batch_projection_rays( +# pixel_i, pixel_j, N, num_samples_per_ray +# ) + +# # TODO: .forward passing z1, x, z3, shifts, N, sampling_rate +# transformed_rays = self.transform_batch_ray_coordinates( +# batch_ray_coords, +# z1=batch_z1, +# x=phis, +# z3=batch_z3, +# shifts=batch_shifts, +# N=N, +# sampling_rate=1.0, +# ) + +# all_coords = transformed_rays.view(-1, 3) + +# all_densities = self.obj.forward(all_coords) + +# tv_loss = self.obj.apply_soft_constraints(all_coords) +# ray_densities = all_densities.view( +# len(target_values), num_samples_per_ray +# ) # Reshape rays and integarte +# step_size = 2.0 / (num_samples_per_ray - 1) + +# predicted_values = ray_densities.sum(dim=1) * step_size + +# consistency_loss = consistency_loss_fn(predicted_values, target_values) +# tv_loss_z1 = torch.tensor(0.0, device=self.device) + +# loss = consistency_loss + tv_loss + tv_loss_z1 + +# loss.backward() + +# epoch_loss += loss.detach() +# epoch_consistency_loss += consistency_loss.detach() +# epoch_tv_loss += tv_loss.detach() +# epoch_z1_loss += tv_loss_z1.detach() +# num_batches += 1 + +# for key, opt in self.optimizers.items(): +# if key == "model": +# model_norm = torch.nn.utils.clip_grad_norm_( +# self.obj.model.parameters(), max_norm=1 +# ) +# elif key == "aux_params": +# aux_norm = torch.nn.utils.clip_grad_norm_( +# self.dataset.auxiliary_params.parameters(), max_norm=1 +# ) +# opt.step() +# opt.zero_grad() + +# for key, sched in self.schedulers.items(): +# if isinstance(sched, torch.optim.lr_scheduler.ReduceLROnPlateau): +# sched.step(epoch_loss) +# else: +# sched.step() + +# if epoch % viz_freq == 0 or epoch == epochs - 1 or epoch % checkpoint_freq == 0: +# with torch.no_grad(): +# self.obj.create_volume( +# world_size=self.world_size, +# global_rank=self.global_rank, +# ray_size=num_samples_per_ray, +# ) +# pred_full = self.obj.obj +# avg_loss = epoch_loss.item() / num_batches +# avg_consistency_loss = epoch_consistency_loss.item() / num_batches +# avg_tv_loss = epoch_tv_loss.item() / num_batches +# avg_z1_loss = epoch_z1_loss.item() / num_batches +# shifts, z1, z3 = self.dataset.auxiliary_params.forward() +# shifts = shifts.detach().cpu() +# z1 = z1.detach().cpu() +# z3 = z3.detach().cpu() +# metrics = torch.tensor( +# [avg_loss, avg_consistency_loss, avg_tv_loss, avg_z1_loss], +# device=self.device, +# ) +# if self.world_size > 1: +# dist.all_reduce(metrics, op=dist.ReduceOp.AVG) +# avg_loss, avg_consistency_loss, avg_tv_loss, avg_z1_loss = metrics.tolist() + +# if hasattr(self, "val_dataloader") and self.val_dataloader is not None: +# val_loss = self.validate( +# aux_params, num_samples_per_ray, device_type, autocast_dtype, use_amp, N +# ) + +# if self.global_rank == 0 and (epoch % viz_freq == 0 or epoch == epochs - 1): +# with torch.no_grad(): +# current_lr = self.schedulers["model"].get_last_lr()[0] + +# # Log metrics +# self.temp_logger.add_scalar( +# f"train/consistency_loss_{consistency_criterion[0] if isinstance(consistency_criterion, tuple) else consistency_criterion}", +# avg_consistency_loss, +# self.global_epochs, +# ) +# if hasattr(self, "val_dataloader") and self.val_dataloader is not None: +# self.temp_logger.add_scalar( +# f"val/consistency_loss_{consistency_criterion[0] if isinstance(consistency_criterion, tuple) else consistency_criterion}", +# val_loss, +# self.global_epochs, +# ) +# self.temp_logger.add_scalar("train/z1_loss", avg_z1_loss, self.global_epochs) +# # if tv_weight > 0: +# self.temp_logger.add_scalar("train/tv_loss", avg_tv_loss, self.global_epochs) +# self.temp_logger.add_scalar("train/total_loss", avg_loss, self.global_epochs) +# self.temp_logger.add_scalar( +# "train/model_grad_norm", model_norm.item(), self.global_epochs +# ) +# self.temp_logger.add_scalar( +# "train/aux_grad_norm", aux_norm.item(), self.global_epochs +# ) +# self.temp_logger.add_scalar("train/lr", current_lr, self.global_epochs) +# self.temp_logger.add_scalar( +# "train/num_samples_per_ray", num_samples_per_ray, self.global_epochs +# ) +# if consistency_criterion[0].lower() == "adaptive_smooth_l1": +# self.temp_logger.add_scalar( +# "train/beta_2", consistency_loss_fn.beta2.item(), self.global_epochs +# ) +# fig, axes = plt.subplots(1, 5, figsize=(36, 12)) +# axes[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap="turbo", vmin=0) +# axes[0].set_title("Sum over Z-axis") + +# axes[1].matshow(pred_full[N // 2].cpu().numpy(), cmap="turbo", vmin=0) +# axes[1].set_title(f"Slice at Z={N // 2}") + +# slice_start = max(0, N // 2 - 5) +# slice_end = min(N, N // 2 + 6) +# thick_slice = pred_full[slice_start:slice_end].sum(dim=0).cpu().numpy() +# axes[2].matshow(thick_slice, cmap="turbo", vmin=0) +# axes[2].set_title(f"Thick slice sum (Z={slice_start}:{slice_end - 1})") + +# axes[3].matshow(pred_full.sum(dim=1).cpu().numpy(), cmap="turbo", vmin=0) +# axes[3].set_title("Sum over Y-axis") + +# axes[4].matshow(pred_full.sum(dim=2).cpu().numpy(), cmap="turbo", vmin=0) +# axes[4].set_title("Sum over X-axis") + +# self.temp_logger.add_figure("train/viz", fig, self.global_epochs, close=True) + +# fig, axes = plt.subplots(ncols=3, figsize=(10, 5)) +# axes[0].plot(shifts[:, 0].cpu().numpy(), label="Shifts X") +# axes[0].plot(shifts[:, 1].cpu().numpy(), label="Shifts Y") +# axes[0].legend() +# axes[1].plot(z1.cpu().numpy(), label="Z1") +# axes[2].plot(z3.cpu().numpy(), label="Z3") +# self.temp_logger.add_figure( +# "train/auxiliary_params", fig, self.global_epochs, close=True +# ) +# plt.close(fig) + +# if epoch % checkpoint_freq == 0 or self.global_epochs == epochs - 1: +# with torch.no_grad(): +# if self.global_rank == 0: +# save_path = f"{vol_save_path}/volume_epoch_{self.global_epochs:04d}.pt" +# torch.save(pred_full.cpu(), save_path) + +# self.global_epochs += 1 + +# if self.global_rank == 0: +# print("Training complete.") + +# torch.save(self.dataset.auxiliary_params.forward(), f"{log_path}/auxiliary_params.pt") +# # print("Successfully setup DDP and dataloader") + +# def validate(self, aux_params, num_samples_per_ray, device_type, autocast_dtype, use_amp, N): +# """Validate the model on the validation set.""" +# self.obj.model.eval() +# aux_params.eval() + +# val_loss = 0.0 +# num_batches = 0 + +# with torch.no_grad(): +# for batch in self.val_dataloader: +# pixel_i = batch["pixel_i"].float().to(self.device, non_blocking=True) +# pixel_j = batch["pixel_j"].float().to(self.device, non_blocking=True) +# target_values = batch["target_value"].to(self.device, non_blocking=True) +# phis = batch["phi"].to(self.device, non_blocking=True) +# projection_indices = batch["projection_idx"].to(self.device, non_blocking=True) + +# shifts, z1_params, z3_params = aux_params(None) +# batch_shifts = torch.index_select(shifts, 0, projection_indices) +# batch_z1 = torch.index_select(z1_params, 0, projection_indices) +# batch_z3 = torch.index_select(z3_params, 0, projection_indices) + +# with torch.autocast( +# device_type=device_type, dtype=autocast_dtype, enabled=use_amp +# ): +# with torch.no_grad(): +# batch_ray_coords = self.create_batch_projection_rays( +# pixel_i, pixel_j, N, num_samples_per_ray +# ) + +# transformed_rays = self.transform_batch_ray_coordinates( +# batch_ray_coords, +# z1=batch_z1, +# x=phis, +# z3=batch_z3, +# shifts=batch_shifts, +# N=N, +# sampling_rate=1.0, +# ) + +# all_coords = transformed_rays.view(-1, 3) + +# all_densities = self.obj.forward(all_coords) + +# ray_densities = all_densities.view( +# len(target_values), num_samples_per_ray +# ) # Reshape rays and integarte +# step_size = 2.0 / (num_samples_per_ray - 1) + +# predicted_values = ray_densities.sum(dim=1) * step_size + +# mse_loss = torch.nn.functional.mse_loss(predicted_values, target_values) + +# loss = mse_loss + +# val_loss += loss.detach() +# num_batches += 1 + +# avg_val_loss = val_loss / num_batches if num_batches > 0 else 0.0 + +# if self.world_size > 1: +# val_loss_tensor = avg_val_loss.detach().clone() +# dist.all_reduce(val_loss_tensor, op=dist.ReduceOp.AVG) +# avg_val_loss = val_loss_tensor.item() + +# self.obj.model.train() +# aux_params.train() + +# return avg_val_loss + +# def ad_recon( +# self, +# optimizer_params: dict, +# num_iter: int = 0, +# reset: bool = False, +# scheduler_params: dict | None = None, +# hard_constraints: dict | None = None, +# soft_constraints: dict | None = None, +# # store_iterations: bool | None = None, +# # store_iterations_every: int | None = None, +# # autograd: bool = True, +# ): +# if reset: +# self.reset_recon() + +# self.hard_constraints = hard_constraints +# self.soft_constraints = soft_constraints + +# # Make sure everything is in the correct device, might be redundant/cleaner way to do this +# self.dataset.to(self.device) +# self.volume_obj.to(self.device) + +# # Making optimizable parameters into leaf tensors. +# self.dataset.shifts = self.dataset.shifts.detach().to(self.device).requires_grad_(True) +# self.dataset.z1_angles = ( +# self.dataset.z1_angles.detach().to(self.device).requires_grad_(True) +# ) +# self.dataset.z3_angles = ( +# self.dataset.z3_angles.detach().to(self.device).requires_grad_(True) +# ) + +# if optimizer_params is not None: +# self.optimizer_params = optimizer_params +# self.set_optimizers() + +# if scheduler_params is not None: +# self.scheduler_params = scheduler_params +# self.set_schedulers(self.scheduler_params, num_iter=num_iter) + +# if hard_constraints is not None: +# self.volume_obj.hard_constraints = hard_constraints +# if soft_constraints is not None: +# self.volume_obj.soft_constraints = soft_constraints + +# pbar = tqdm(range(num_iter), desc="AD Reconstruction") + +# for a0 in pbar: +# total_loss = 0.0 +# tilt_series_loss = 0.0 + +# pred_volume = self.volume_obj.forward() + +# for i in range(len(self.dataset.tilt_series)): +# forward_projection = self.projection_operator( +# vol=pred_volume, +# z1=self.dataset.z1_angles[i], +# x=self.dataset.tilt_angles[i], +# z3=self.dataset.z3_angles[i], +# shift_x=self.dataset.shifts[i, 0], +# shift_y=self.dataset.shifts[i, 1], +# device=self.device, +# ) + +# tilt_series_loss += torch.nn.functional.mse_loss( +# forward_projection, self.dataset.tilt_series[i] +# ) +# tilt_series_loss /= len(self.dataset.tilt_series) + +# total_loss = tilt_series_loss + self.volume_obj.soft_loss +# self.loss.append(total_loss.item()) + +# total_loss.backward() + +# for opt in self.optimizers.values(): +# opt.step() +# opt.zero_grad() + +# if self.schedulers is not None: +# for sch in self.schedulers.values(): +# if isinstance(sch, torch.optim.lr_scheduler.ReduceLROnPlateau): +# sch.step(total_loss) +# elif sch is not None: +# sch.step() + +# pbar.set_description(f"AD Reconstruction | Loss: {total_loss:.4f}") + +# if self.logger is not None: +# self.logger.log_scalar("loss/total", total_loss.item(), a0) +# self.logger.log_scalar("loss/tilt_series", tilt_series_loss.item(), a0) +# self.logger.log_scalar( +# "loss/soft constraints", self.volume_obj.soft_loss.item(), a0 +# ) + +# if a0 % self.logger.log_images_every == 0: +# self.logger.projection_images( +# volume_obj=self.volume_obj, +# epoch=a0, +# ) +# self.logger.tilt_angles_figure(dataset=self.dataset, step=a0) + +# self.logger.flush() + +# self.ad_recon_vol = self.volume_obj.forward() + +# return self + +# def reset_recon(self) -> None: +# if isinstance(self.volume_obj, ObjectVoxelwise): +# self.volume_obj.reset() + +# self.ad_recon_vol = None + +# # --- Projection Operators ---- +# def projection_operator( +# self, +# vol, +# z1, +# x, +# z3, +# shift_x, +# shift_y, +# device, +# ): +# projection = ( +# rot_ZXZ( +# mags=vol.unsqueeze(0), # Add batch dimension +# z1=z1, +# x=-x, +# z3=z3, +# device=device, +# mode="bilinear", +# ) +# .squeeze() +# .sum(axis=0) +# ) + +# shifted_projection = differentiable_shift_2d( +# image=projection, +# shift_x=shift_x, +# shift_y=shift_y, +# sampling_rate=1.0, # Assuming 1 pixel = 1 physical unit +# ) + +# return shifted_projection From 8f6cb02aa0dee2e24e1f235249f486ad4e8a849a Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Thu, 29 Jan 2026 23:03:03 -0800 Subject: [PATCH 054/335] Val + train test split implemented - cuBLAS error after adding this need to track down; scheduler stepping fixed prior implementations was not stepping schedulers; end result volume saving, figure out protocol for how to save model weights, volume, etc...; added nvtx wrapper in core/ml; starting implementation of TomographyLite --- src/quantem/core/ml/ddp.py | 46 ++++++-- src/quantem/core/ml/optimizer_mixin.py | 5 +- src/quantem/core/ml/profiling.py | 14 +++ src/quantem/tomography/logger_tomography.py | 9 +- src/quantem/tomography/object_models.py | 12 ++- src/quantem/tomography/tomography.py | 112 +++++++++++++++----- src/quantem/tomography/tomography_base.py | 1 + src/quantem/tomography/tomography_lite.py | 0 src/quantem/tomography/tomography_opt.py | 37 ++++--- 9 files changed, 182 insertions(+), 54 deletions(-) create mode 100644 src/quantem/core/ml/profiling.py create mode 100644 src/quantem/tomography/tomography_lite.py diff --git a/src/quantem/core/ml/ddp.py b/src/quantem/core/ml/ddp.py index b6ec536f..5cc28188 100644 --- a/src/quantem/core/ml/ddp.py +++ b/src/quantem/core/ml/ddp.py @@ -3,7 +3,7 @@ import torch import torch.distributed as dist import torch.nn as nn -from torch.utils.data import DataLoader, Dataset, DistributedSampler +from torch.utils.data import DataLoader, Dataset, DistributedSampler, random_split # Rename DDPMixin @@ -60,29 +60,42 @@ def setup_dataloader( dataset: Dataset, batch_size: int, num_workers: int = 0, - # val_fraction: float = 0.0, + val_fraction: float = 0.0, ): pin_mem = self.device.type == "cuda" persist = num_workers > 0 - # TODO: Implement validation dataloader + if val_fraction > 0.0: + train_dataset, val_dataset = random_split(dataset, [1 - val_fraction, val_fraction]) + else: + train_dataset = dataset + val_dataset = None if self.world_size > 1: shuffle = True train_sampler = DistributedSampler( - dataset, + train_dataset, num_replicas=self.world_size, rank=self.global_rank, shuffle=shuffle, ) + + if val_dataset: + val_sampler = DistributedSampler( + val_dataset, + num_replicas=self.world_size, + rank=self.global_rank, + shuffle=False, + ) shuffle = False else: train_sampler = None + val_sampler = None shuffle = True train_dataloader = DataLoader( - dataset, + train_dataset, batch_size=batch_size, num_workers=num_workers, sampler=train_sampler, @@ -92,14 +105,33 @@ def setup_dataloader( persistent_workers=persist, ) + if val_dataset: + val_dataloader = DataLoader( + val_dataset, + batch_size=batch_size * 4, + num_workers=num_workers, + sampler=val_sampler, + shuffle=False, + pin_memory=pin_mem, + drop_last=False, + persistent_workers=persist, + ) + val_dataloader = val_dataloader + else: + val_dataloader = None + if self.global_rank == 0: print("Dataloader setup complete:") - print(f" Total samples: {len(dataset)}") + print(f" Total train samples: {len(train_dataset)}") print(f" Local batch size: {batch_size}") print(f" Global batch size: {batch_size * self.world_size}") print(f" Train batches per GPU per epoch: {len(train_dataloader)}") - return train_dataloader, train_sampler + if val_dataset: + print(f" Total val samples: {len(val_dataset)}") + print(f" Val batches per GPU per epoch: {len(val_dataloader)}") + + return train_dataloader, train_sampler, val_dataloader, val_sampler def build_model( self, diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index 7c39e39d..92db06f1 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -61,10 +61,11 @@ def scheduler_params(self, params: dict): "exp", "gamma", "linear", + "cosine_annealing", "none", ]: raise ValueError( - f"Unknown scheduler type: {params['type']}, expected one of ['cyclic', 'plateau', 'exp', 'gamma', 'none']" + f"Unknown scheduler type: {params['type']}, expected one of ['cyclic', 'plateau', 'exp', 'gamma', 'linear', 'cosine_annealing', 'none']" ) self._scheduler_params = params.copy() else: @@ -183,7 +184,7 @@ def set_scheduler( self._scheduler = torch.optim.lr_scheduler.CosineAnnealingLR( optimizer, T_max=params.get("T_max", num_iter), - eta_min=params.get("eta_min", base_LR), + eta_min=params.get("eta_min", 1e-7), ) else: raise ValueError(f"Unknown scheduler type: {sched_type}") diff --git a/src/quantem/core/ml/profiling.py b/src/quantem/core/ml/profiling.py new file mode 100644 index 00000000..aec6e5ed --- /dev/null +++ b/src/quantem/core/ml/profiling.py @@ -0,0 +1,14 @@ +from contextlib import contextmanager + +import torch.cuda.nvtx as nvtx + + +@contextmanager +def nvtx_range(enabled: bool, name: str): + if enabled: + nvtx.range_push(name) + try: + yield + finally: + if enabled: + nvtx.range_pop() diff --git a/src/quantem/tomography/logger_tomography.py b/src/quantem/tomography/logger_tomography.py index 16e6d9f3..4d850e18 100644 --- a/src/quantem/tomography/logger_tomography.py +++ b/src/quantem/tomography/logger_tomography.py @@ -31,12 +31,18 @@ def log_iter( iter: int, consistency_loss: float, total_loss: float, - num_samples_per_ray: int | None = None, + learning_rates: dict[str, float], + num_samples_per_ray: int, + val_loss: float | None = None, ): self.log_scalar("loss/consistency", consistency_loss, iter) self.log_scalar("loss/total", total_loss, iter) self.log_scalar("loss/soft", object_model._soft_constraint_losses[-1], iter) self.log_scalar("num_samples_per_ray", num_samples_per_ray, iter) + for param_name, lr_value in learning_rates.items(): + self.log_scalar(f"learning_rate/{param_name}", float(lr_value), iter) + if val_loss is not None: + self.log_scalar("loss/val", val_loss, iter) def log_iter_images( self, @@ -45,7 +51,6 @@ def log_iter_images( iter: int, logger_cmap: str = "turbo", ): - with torch.no_grad(): z1_vals = dataset_model.z1_params.detach().cpu().numpy() z3_vals = dataset_model.z3_params.detach().cpu().numpy() diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index a603c089..aba0bc78 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -311,13 +311,17 @@ def model(self) -> "nn.Module": # """ # raise RuntimeError("\n\n\nsetting model, this shouldn't be reachable???\n\n\n") + @property + def obj(self) -> torch.Tensor: + return self._obj + def apply_soft_constraints( self, coords: torch.Tensor, ) -> torch.Tensor: soft_loss = torch.tensor(0.0, device=coords.device) if self.constraints.tv_vol > 0: - num_tv_samples = min(10000, coords.shape[0]) + num_tv_samples = min(1000, coords.shape[0]) tv_indices = torch.randperm(coords.shape[0], device=coords.device)[:num_tv_samples] tv_coords = coords[tv_indices].detach().requires_grad_(True) @@ -348,7 +352,6 @@ def apply_hard_constraints(self, pred: torch.Tensor) -> torch.Tensor: if self.constraints.shrinkage: pred = torch.max(pred - self.constraints.shrinkage, torch.zeros_like(pred)) - return pred @property @@ -581,10 +584,9 @@ def create_volume( pred_full = outputs.reshape(N, N, N).float() if return_vol: - return pred_full.detach().cpu() - else: - self._obj = pred_full.detach().cpu() + + self._obj = pred_full.detach().cpu() def get_tv_loss( self, diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 0c1b7a49..d2e3eb3c 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -1,4 +1,3 @@ -from contextlib import contextmanager from typing import List, Literal, Optional, Self, Tuple import numpy as np @@ -7,6 +6,7 @@ from tqdm.auto import tqdm from quantem.core.ml.ddp import DDPMixin +from quantem.core.ml.profiling import nvtx_range from quantem.tomography.dataset_models import DatasetModelType from quantem.tomography.logger_tomography import LoggerTomography from quantem.tomography.object_models import ObjectModelType @@ -41,8 +41,6 @@ def from_models( def reconstruct( self, - # obj_model: ObjectModelType, - # dset: DatasetModelType, num_iter: int = 10, batch_size: int = 1024, num_workers: int = 32, @@ -53,6 +51,7 @@ def reconstruct( loss_func: Tuple[str, Optional[float]] = ("smooth_l1", 0.07), num_samples_per_ray: int | List[Tuple[int, int]] = None, profiling_mode: bool = False, + val_fraction: float = 0.0, ): """ This function should be able to handle both AD and INR-based tomography reconstruction methods. @@ -65,51 +64,45 @@ def reconstruct( # raise ValueError( # f"Should never happen! obj_model and dset must be on the same device, got {self.obj_model.device} and {self.dset.device}" # ) - if profiling_mode: if self.global_rank == 0: print("Profiling mode enabled.") - import torch.cuda.nvtx as nvtx - - @contextmanager - def nvtx_range(enabled: bool, name: str): - if enabled: - nvtx.range_push(name) - try: - yield - finally: - if enabled: - nvtx.range_pop() if reset: raise NotImplementedError("Reset is not implemented yet.") + new_scheduler = reset + if optimizer_params is not None: with nvtx_range(profiling_mode, "Setting Optimizer Params"): self.optimizer_params = optimizer_params self.set_optimizers() + new_scheduler = True if scheduler_params is not None: with nvtx_range(profiling_mode, "Setting Scheduler Params"): self.scheduler_params = scheduler_params - self.set_schedulers(scheduler_params) + new_scheduler = True if constraints is not None: - self.obj_model.constraints = constraints + with nvtx_range(profiling_mode, "Setting Constraints"): + self.obj_model.constraints = constraints - new_scheduler = reset if new_scheduler: - raise NotImplementedError("New schedulers are not implemented yet.") + with nvtx_range(profiling_mode, "Setting Schedulers"): + self.set_schedulers(scheduler_params, num_iter=num_iter) # Setting up DDP if not hasattr(self, "dataloader"): with nvtx_range(profiling_mode, "Setting Dataloader"): - self.dataloader, self.sampler = self.setup_dataloader( - self.dset, batch_size, num_workers=num_workers + self.dataloader, self.sampler, self.val_dataloader, self.val_sampler = ( + self.setup_dataloader( + self.dset, + batch_size, + num_workers=num_workers, + val_fraction=val_fraction, + ) ) - - self.obj_model.model.train() - N = max(self.obj_model.shape) if num_samples_per_ray is None: @@ -121,12 +114,13 @@ def nvtx_range(enabled: bool, name: str): print("num_samples_per_ray schedule provided.") print(f"N: {N}, num_samples_per_ray: {num_samples_per_ray}") - for a0 in range(num_iter): with nvtx_range(profiling_mode, f"Epoch {a0}"): consistency_loss = 0.0 total_loss = 0.0 epoch_soft_constraint_loss = 0.0 + self.obj_model.model.train() + self.dset.train() # self._reset_iter_constraints() if self.sampler is not None: @@ -151,7 +145,6 @@ def nvtx_range(enabled: bool, name: str): all_coords = self.dset.get_coords( batch, N, curr_num_samples_per_ray ) - with nvtx_range(profiling_mode, "Forwarding"): all_densities = self.obj_model.forward(all_coords) @@ -203,6 +196,8 @@ def nvtx_range(enabled: bool, name: str): ): consistency_loss += batch_consistency_loss.detach() + with nvtx_range(profiling_mode, "Stepping Schedulers"): + self.step_schedulers(loss=total_loss) # TODO: Maybe reorganize the losses so that the order makes sense lol. total_loss = total_loss.item() / len(self.dataloader) @@ -211,6 +206,53 @@ def nvtx_range(enabled: bool, name: str): self.dataloader ) + if self.val_dataloader is not None: + print("Validating...") + self.obj_model.model.eval() + self.dset.eval() + with torch.no_grad(): + val_loss = 0.0 + + for batch in self.val_dataloader: + with torch.autocast( + device_type=self.device.type, + dtype=torch.bfloat16, + enabled=True, + ): + with nvtx_range(profiling_mode, "Getting Coords"): + all_coords = self.dset.get_coords( + batch, N, curr_num_samples_per_ray + ) + + with nvtx_range(profiling_mode, "Forwarding"): + all_densities = self.obj_model.forward(all_coords) + + with nvtx_range(profiling_mode, "Integrating"): + integrated_densities = self.dset.integrate_rays( + all_densities, + curr_num_samples_per_ray, + len(batch["target_value"]), + ) + + with nvtx_range(profiling_mode, "Getting Target"): + target = ( + batch["target_value"] + .to(self.device, non_blocking=True) + .float() + ) + + with nvtx_range(profiling_mode, "Calculating Loss"): + batch_val_loss = torch.nn.functional.mse_loss( + integrated_densities, target + ) + + with nvtx_range(profiling_mode, "Adding batch loss to total loss"): + val_loss += ( + batch_val_loss.detach() + soft_constraints_loss.detach() + ) + + avg_val_loss = val_loss.item() / len(self.val_dataloader) + metrics = torch.tensor( [total_loss, consistency_loss, epoch_soft_constraint_loss], device=self.device ) @@ -225,9 +267,14 @@ def nvtx_range(enabled: bool, name: str): f"Total Loss: {total_loss:.4f}, Consistency Loss: {consistency_loss:.4f}" ) + if self.val_dataloader: + print(f"Validation loss: {avg_val_loss:4f}") + self._epoch_losses.append(total_loss) self._consistency_losses.append(consistency_loss) self.obj_model._soft_constraint_losses.append(epoch_soft_constraint_loss) + if self.val_dataloader is not None: + self._val_losses.append(avg_val_loss) with nvtx_range(profiling_mode, "Logging"): if self.logger is not None: @@ -252,11 +299,24 @@ def nvtx_range(enabled: bool, name: str): iter=self.num_epochs, consistency_loss=consistency_loss, total_loss=total_loss, + learning_rates=self.get_current_lrs(), num_samples_per_ray=curr_num_samples_per_ray, + val_loss=avg_val_loss if self.val_dataloader is not None else None, ) self.logger.flush() + # --- Helper Functions --- + + def save_volume(self, path: str = "recon_volume.npz"): + # TODO: Temporary, need to talk to Arthur what the correct way of saving results is. + if self.global_rank == 0: + print(f"Saving volume to {path}") + + self.obj_model.create_volume() + + np.savez(path, volume=self.obj_model.obj.detach().cpu().numpy()) + class TomographyConventional(TomographyBase): """ diff --git a/src/quantem/tomography/tomography_base.py b/src/quantem/tomography/tomography_base.py index 2f8d6fc7..6b517f03 100644 --- a/src/quantem/tomography/tomography_base.py +++ b/src/quantem/tomography/tomography_base.py @@ -45,6 +45,7 @@ def __init__( # Loss self._epoch_losses: list[float] = [] self._consistency_losses: list[float] = [] + self._val_losses: list[float] = [] # DDP Initialization # print("Checking if obj_model is a ObjectPixelated: ", not isinstance(obj_model, ObjectPixelated)) if not isinstance(obj_model, ObjectPixelated): diff --git a/src/quantem/tomography/tomography_lite.py b/src/quantem/tomography/tomography_lite.py new file mode 100644 index 00000000..e69de29b diff --git a/src/quantem/tomography/tomography_opt.py b/src/quantem/tomography/tomography_opt.py index 2a148433..c7bc6b38 100644 --- a/src/quantem/tomography/tomography_opt.py +++ b/src/quantem/tomography/tomography_opt.py @@ -37,11 +37,8 @@ def optimizer_params(self) -> dict[str, dict]: @optimizer_params.setter def optimizer_params(self, d: dict): """Set the optimizer parameters.""" - self._optimizer_params = d.copy() if d else {} - - for key in self.OPTIMIZABLE_VALS: - if key not in d: - d[key] = {"type": "none"} + if isinstance(d, (tuple, list)): + d = {k: {} for k in d} for k, v in d.items(): if "type" not in v.keys(): @@ -71,6 +68,20 @@ def set_optimizers(self): else: raise ValueError(f"Unknown optimization key: {key}") + def get_current_lrs(self) -> dict[str, float]: + if self.obj_model.has_optimizer(): + obj_lr = self.obj_model.get_current_lr() + else: + obj_lr = 0.0 + if self.dset.has_optimizer(): + pose_lr = self.dset.get_current_lr() + else: + pose_lr = 0.0 + return { + "object": obj_lr, + "pose": pose_lr, + } + def remove_optimizer(self, key: str): if key == "object": self.obj_model.remove_optimizer() @@ -106,10 +117,14 @@ def scheduler_params(self, d: dict): @property def schedulers(self) -> dict[str, torch.optim.lr_scheduler._LRScheduler]: - return { - "object": self.obj_model.scheduler, - "pose": self.dset.scheduler, - } + schedulers = {} + + if self.obj_model.scheduler is not None: + schedulers["object"] = self.obj_model.scheduler + if self.dset.scheduler is not None: + schedulers["pose"] = self.dset.scheduler + + return schedulers def set_schedulers(self, params: dict[str, dict], num_iter: int | None = None): for key, scheduler_params in params.items(): @@ -142,7 +157,5 @@ def step_schedulers(self, loss: float | None = None): for key in self.scheduler_params.keys(): if key == "object" and self.obj_model.scheduler is not None: self.obj_model.step_scheduler(loss) - elif key == "pose" and self.dset.scheduler is not None: + elif self.dset.scheduler is not None and key == "pose": self.dset.step_scheduler(loss) - else: - raise ValueError(f"Unknown optimization key: {key}") From 791a644de042f357526f3985c69464c107c22906 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Fri, 30 Jan 2026 00:28:22 -0800 Subject: [PATCH 055/335] Small updates --- src/quantem/tomography/object_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index aba0bc78..c5315848 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -321,7 +321,7 @@ def apply_soft_constraints( ) -> torch.Tensor: soft_loss = torch.tensor(0.0, device=coords.device) if self.constraints.tv_vol > 0: - num_tv_samples = min(1000, coords.shape[0]) + num_tv_samples = min(10000, coords.shape[0]) tv_indices = torch.randperm(coords.shape[0], device=coords.device)[:num_tv_samples] tv_coords = coords[tv_indices].detach().requires_grad_(True) From 4b7f1086ded434e1503fdd50bbd6fc764fca6bcd Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Fri, 30 Jan 2026 17:28:29 -0800 Subject: [PATCH 056/335] Implemented a working TomographyLite, need to test AutoSerialize, and move conventional algorithm .forward calls. --- src/quantem/core/ml/ddp.py | 1 - src/quantem/tomography/tomography.py | 7 +- src/quantem/tomography/tomography_conv.py | 0 src/quantem/tomography/tomography_lite.py | 121 +++ src/quantem/tomography/tomography_ml.py | 0 src/quantem/tomography_old/__init__.py | 0 src/quantem/tomography_old/models.py | 96 -- src/quantem/tomography_old/object_models.py | 723 -------------- .../tomography_old/preprocess/__init__.py | 0 .../tomography_old/preprocess/drift.py | 0 src/quantem/tomography_old/tomography.py | 883 ------------------ src/quantem/tomography_old/tomography_base.py | 514 ---------- src/quantem/tomography_old/tomography_conv.py | 235 ----- .../tomography_old/tomography_dataset.py | 468 ---------- src/quantem/tomography_old/tomography_ddp.py | 321 ------- .../tomography_old/tomography_logger.py | 68 -- src/quantem/tomography_old/tomography_ml.py | 338 ------- src/quantem/tomography_old/tomography_nerf.py | 636 ------------- src/quantem/tomography_old/utils.py | 300 ------ 19 files changed, 126 insertions(+), 4585 deletions(-) delete mode 100644 src/quantem/tomography/tomography_conv.py delete mode 100644 src/quantem/tomography/tomography_ml.py delete mode 100644 src/quantem/tomography_old/__init__.py delete mode 100644 src/quantem/tomography_old/models.py delete mode 100644 src/quantem/tomography_old/object_models.py delete mode 100644 src/quantem/tomography_old/preprocess/__init__.py delete mode 100644 src/quantem/tomography_old/preprocess/drift.py delete mode 100644 src/quantem/tomography_old/tomography.py delete mode 100644 src/quantem/tomography_old/tomography_base.py delete mode 100644 src/quantem/tomography_old/tomography_conv.py delete mode 100644 src/quantem/tomography_old/tomography_dataset.py delete mode 100644 src/quantem/tomography_old/tomography_ddp.py delete mode 100644 src/quantem/tomography_old/tomography_logger.py delete mode 100644 src/quantem/tomography_old/tomography_ml.py delete mode 100644 src/quantem/tomography_old/tomography_nerf.py delete mode 100644 src/quantem/tomography_old/utils.py diff --git a/src/quantem/core/ml/ddp.py b/src/quantem/core/ml/ddp.py index 5cc28188..df43c2e5 100644 --- a/src/quantem/core/ml/ddp.py +++ b/src/quantem/core/ml/ddp.py @@ -6,7 +6,6 @@ from torch.utils.data import DataLoader, Dataset, DistributedSampler, random_split -# Rename DDPMixin class DDPMixin: """ Class for setting up all distributed training. diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index d2e3eb3c..aaf48d17 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -13,8 +13,11 @@ from quantem.tomography.radon.radon import iradon_torch, radon_torch from quantem.tomography.tomography_base import TomographyBase from quantem.tomography.tomography_opt import TomographyOpt -from quantem.tomography.utils import torch_phase_cross_correlation -from quantem.tomography_old.utils import gaussian_filter_2d_stack, gaussian_kernel_1d +from quantem.tomography.utils import ( + gaussian_filter_2d_stack, + gaussian_kernel_1d, + torch_phase_cross_correlation, +) class Tomography(TomographyOpt, TomographyBase, DDPMixin): diff --git a/src/quantem/tomography/tomography_conv.py b/src/quantem/tomography/tomography_conv.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/quantem/tomography/tomography_lite.py b/src/quantem/tomography/tomography_lite.py index e69de29b..b78ea4ff 100644 --- a/src/quantem/tomography/tomography_lite.py +++ b/src/quantem/tomography/tomography_lite.py @@ -0,0 +1,121 @@ +import os +from typing import Literal, Self + +import numpy as np + +from quantem.core.ml.inr import HSiren +from quantem.tomography.dataset_models import DatasetModelType +from quantem.tomography.logger_tomography import LoggerTomography +from quantem.tomography.object_models import ObjectINR +from quantem.tomography.tomography import Tomography + + +class TomographyLiteINR(Tomography): + """ + A lite version of the Tomography class. + """ + + @classmethod + def from_dataset( + cls, + dset: DatasetModelType, + device: str = "cuda", + log_dir: os.PathLike | str | None = None, + log_images_every: int = 10, + rng: np.random.Generator | int | None = None, + ) -> Self: + dset_model = dset + + # Define the object model + model = HSiren(alpha=1, winner_initialization=True) + obj_model = ObjectINR.from_model( + model=model, + shape=( + max(dset_model.tilt_stack.shape), + max(dset_model.tilt_stack.shape), + max(dset_model.tilt_stack.shape), + ), + device=device, + rng=rng, + ) + + # TODO: Implement pretrain + + if log_dir is not None: + logger = LoggerTomography( + log_dir=log_dir, + run_prefix="tomography_lite_inr", + run_suffix="", + log_images_every=log_images_every, + ) + else: + logger = None + + tomography = cls.from_models( + dset=dset_model, + obj_model=obj_model, + device=device, + rng=rng, + logger=logger, + ) + + return tomography + + def reconstruct( + self, + num_iter: int = 10, + reset: bool = False, + obj_lr: float = 1e-4, + pose_lr: float = 1e-2, + batch_size: int = 1024, + num_workers: int = 32, + learn_pose: bool = True, + warmup_routine: bool = True, + scheduler_type: Literal[ + "exp", "cyclic", "plateau", "cosine_annealing", "linear", "full_warmup" + ] = "none", + scheduler_factor: float = 0.5, + new_optimizers: bool = False, + constraints: dict = {}, + ): + if self.num_epochs == 0: + opt_params = { + "object": { + "type": "adam", + "lr": obj_lr, + }, + } + + scheduler_params = { + "object": { + "type": scheduler_type, + "factor": scheduler_factor, + }, + } + + if learn_pose: + opt_params["pose"] = { + "type": "adam", + "lr": pose_lr, + } + scheduler_params["pose"] = { + "type": scheduler_type, + "factor": scheduler_factor, + } + + else: + opt_params = None + scheduler_params = None + + constraints = constraints + num_samples_per_ray = int(max(self.dset.tilt_stack.shape)) + return super().reconstruct( + num_iter=num_iter, + batch_size=batch_size, + num_workers=num_workers, + reset=reset, + num_samples_per_ray=num_samples_per_ray, + optimizer_params=opt_params, + scheduler_params=scheduler_params, + constraints=constraints, + ) diff --git a/src/quantem/tomography/tomography_ml.py b/src/quantem/tomography/tomography_ml.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/quantem/tomography_old/__init__.py b/src/quantem/tomography_old/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/quantem/tomography_old/models.py b/src/quantem/tomography_old/models.py deleted file mode 100644 index a972400c..00000000 --- a/src/quantem/tomography_old/models.py +++ /dev/null @@ -1,96 +0,0 @@ -import numpy as np -import torch -from torch import nn - - -class SineLayer(nn.Module): - def __init__( - self, - in_features, - out_features, - bias=True, - is_first=False, - omega_0=30, - hsiren=False, - alpha=1.0, - ): - super().__init__() - self.omega_0 = omega_0 - self.is_first = is_first - self.hsiren = hsiren - self.in_features = in_features - self.alpha = alpha - self.linear = nn.Linear(in_features, out_features, bias=bias) - self.init_weights() - - def init_weights(self): - with torch.no_grad(): - if self.is_first: - # Scale the first layer initialization by alpha - self.linear.weight.uniform_( - -self.alpha / self.in_features, self.alpha / self.in_features - ) - else: - # Scale the hidden layer initialization by alpha - self.linear.weight.uniform_( - -self.alpha * np.sqrt(6 / self.in_features) / self.omega_0, - self.alpha * np.sqrt(6 / self.in_features) / self.omega_0, - ) - - def forward(self, input): - if self.is_first and self.hsiren: - out = torch.sin(self.omega_0 * torch.sinh(2 * self.linear(input))) - else: - out = torch.sin(self.omega_0 * self.linear(input)) - return out - - -class HSiren(nn.Module): - def __init__( - self, - in_features=2, - out_features=3, - hidden_layers=3, - hidden_features=256, - first_omega_0=30, - hidden_omega_0=30, - alpha=1.0, - ): - super().__init__() - self.net_list = [] - self.net_list.append( - SineLayer( - in_features, - hidden_features, - is_first=True, - omega_0=first_omega_0, - hsiren=True, - alpha=alpha, - ) - ) - - for i in range(hidden_layers): - self.net_list.append( - SineLayer( - hidden_features, - hidden_features, - is_first=False, - omega_0=hidden_omega_0, - alpha=alpha, - ) - ) - - final_linear = nn.Linear(hidden_features, out_features) - with torch.no_grad(): - # Final layer keeps original initialization (no alpha scaling) - final_linear.weight.uniform_( - -np.sqrt(6 / hidden_features) / hidden_omega_0, - np.sqrt(6 / hidden_features) / hidden_omega_0, - ) - self.net_list.append(final_linear) - self.net_list.append(nn.Softplus()) - self.net = nn.Sequential(*self.net_list) - - def forward(self, coords): - output = self.net(coords) - return output diff --git a/src/quantem/tomography_old/object_models.py b/src/quantem/tomography_old/object_models.py deleted file mode 100644 index 57192e27..00000000 --- a/src/quantem/tomography_old/object_models.py +++ /dev/null @@ -1,723 +0,0 @@ -from abc import abstractmethod -from copy import deepcopy -from typing import Any, Callable - -import numpy as np -import torch -import torch.distributed as dist -import torch.nn as nn -from tqdm.auto import tqdm - -from quantem.core.io.serialize import AutoSerialize -from quantem.core.ml.blocks import reset_weights -from quantem.core.utils.validators import validate_gt, validate_tensor -from quantem.tomography.utils import get_TV_loss - - -class ObjectBase(AutoSerialize): - """ - Base class for all ObjectModels to inherit from. - """ - - def __init__( - self, - volume_shape: tuple[int, int, int], - device: str, - offset_obj: float = 1e-5, - ): - self._shape = volume_shape - - self._obj = torch.zeros(self._shape, device=device, dtype=torch.float32) + offset_obj - self._offset_obj = offset_obj - self._device = device - self._hard_constraints = {} - self._soft_constraints = {} # One big dicitonary - - @property - def shape(self) -> tuple[int, int, int]: - return self._shape - - @shape.setter - def shape(self, shape: tuple[int, int, int]): - self._shape = shape - - @property - def offset_obj(self) -> float: - return self._offset_obj - - @offset_obj.setter - def offset_obj(self, offset_obj: float): - self._offset_obj = offset_obj - - @property - def obj(self) -> torch.Tensor: - pass - - @obj.setter - def obj(self, obj: torch.Tensor): - self._obj = obj - - @property - def device(self) -> str: - return self._device - - @device.setter - def device(self, device: str): - self._device = device - - @abstractmethod - def forward( - self, z1: torch.Tensor, z3: torch.Tensor, shift_x: torch.Tensor, shift_y: torch.Tensor - ): - pass - - @abstractmethod - def obj(self): - pass - - @abstractmethod - def reset(self): - pass - - @abstractmethod - def to(self, device: str): - pass - - @abstractmethod - def name(self) -> str: - pass - - @abstractmethod - def params(self) -> torch.Tensor: - pass - - -class ObjectConstraints(ObjectBase): - DEFAULT_HARD_CONSTRAINTS = { - "fourier_filter": False, - "positivity": False, - "shrinkage": False, - "circular_mask": False, - } - - DEFAULT_SOFT_CONSTRAINTS = { - "tv_vol": 0, - } - - @property - def hard_constraints(self) -> dict[str, Any]: - return self._hard_constraints - - @hard_constraints.setter - def hard_constraints(self, hard_constraints: dict[str, Any]): - gkeys = self.DEFAULT_HARD_CONSTRAINTS.keys() - for key, value in hard_constraints.items(): - if key not in gkeys: # This might be redundant since add_constraint is checking. - raise KeyError(f"Invalid object constraint key '{key}', allowed keys are {gkeys}") - self._hard_constraints[key] = value - - @property - def soft_constraints(self) -> dict[str, Any]: - return self._soft_constraints - - @soft_constraints.setter - def soft_constraints(self, soft_constraints: dict[str, Any]): - gkeys = self.DEFAULT_SOFT_CONSTRAINTS.keys() - for key, value in soft_constraints.items(): - if key not in gkeys: - raise KeyError(f"Invalid object constraint key '{key}', allowed keys are {gkeys}") - self._soft_constraints[key] = value - - def add_hard_constraint(self, constraint: str, value: Any): - """Add constraints to the object model.""" - gkeys = self.DEFAULT_HARD_CONSTRAINTS.keys() - if constraint not in gkeys: - raise KeyError( - f"Invalid object constraint key '{constraint}', allowed keys are {gkeys}" - ) - self._hard_constraints[constraint] = value - - def add_soft_constraint(self, constraint: str, value: Any): - """Add constraints to the object model.""" - gkeys = self.DEFAULT_SOFT_CONSTRAINTS.keys() - if constraint not in gkeys: - raise KeyError( - f"Invalid object constraint key '{constraint}', allowed keys are {gkeys}" - ) - self._soft_constraints[constraint] = value - - def apply_hard_constraints( - self, - obj: torch.Tensor, - ) -> torch.Tensor: - """ - Apply constraints to the object model. - """ - obj2 = obj.clone() - if self.hard_constraints["positivity"]: - obj2 = torch.clamp(obj, min=0.0, max=None) - if self.hard_constraints["shrinkage"]: - obj2 = torch.max(obj2 - self.hard_constraints["shrinkage"], torch.zeros_like(obj2)) - - return obj2 - - def apply_soft_constraints( - self, - obj: torch.Tensor, - ) -> torch.Tensor: - """ - 'Applies' soft constraints to the object model. This will return additional loss terms. - """ - soft_loss = torch.tensor(0.0, device=obj.device, dtype=obj.dtype, requires_grad=True) - if self.soft_constraints["tv_vol"] > 0: - tv_loss = get_TV_loss( - obj.unsqueeze(0).unsqueeze(0), factor=self.soft_constraints["tv_vol"] - ) - - soft_loss += tv_loss - - return soft_loss - - -class ObjectVoxelwise(ObjectConstraints): - """ - Object model for voxelwise objects. - """ - - def __init__( - self, - volume_shape: tuple[int, int, int], - device: str, - initial_volume: torch.Tensor | None = None, - ): - super().__init__( - volume_shape=volume_shape, - device=device, - ) - self.hard_constraints = self.DEFAULT_HARD_CONSTRAINTS.copy() - self.soft_constraints = self.DEFAULT_SOFT_CONSTRAINTS.copy() - - if initial_volume is not None: - self._initial_obj = initial_volume - else: - self.initial_obj = ( - torch.zeros(self._shape, device=self._device, dtype=torch.float32) - + self.offset_obj - ) - - @property - def obj(self): - return self.apply_hard_constraints(self._obj) - - @obj.setter - def obj(self, obj: torch.Tensor): - self._obj = obj - - @property - def initial_obj(self): - return self._initial_obj - - @initial_obj.setter - def initial_obj(self, initial_obj: torch.Tensor): - if not isinstance(initial_obj, torch.Tensor): - raise ValueError("initial_obj must be a torch.Tensor") - - self._initial_obj = initial_obj - - def forward(self): - return self.obj - - def reset(self): - self._obj = ( - torch.zeros(self._shape, device=self._device, dtype=torch.float32) + self.offset_obj - ) - - def to(self, device: str): - self._device = device - self._obj = self._obj.to(self._device) - - @property - def name(self) -> str: - return "ObjectVoxelwise" - - @property - def params(self) -> torch.Tensor: - return self._obj - - @property - def soft_loss(self) -> torch.Tensor: - return self.apply_soft_constraints(self._obj) - - -class ObjectDIP(ObjectConstraints): - """ - Object model for DIP objects. - """ - - def __init__( - self, - model: torch.nn.Module, - volume_shape: tuple[int, int, int], - model_input: torch.Tensor - | None = None, # Determines output size, model input pretraining target - input_noise_std: float = 0.0, - device: str = "cpu", - ): - super().__init__( - volume_shape=volume_shape, - device=device, - ) - self.hard_constraints = self.DEFAULT_HARD_CONSTRAINTS.copy() - self.soft_constraints = self.DEFAULT_SOFT_CONSTRAINTS.copy() - - if model_input is None: - self.model_input = torch.randn(1, 1, volume_shape[0], volume_shape[1], volume_shape[2]) - else: - self.model_input = model_input.clone().detach() - - self.pretrain_target = model_input.clone().detach() - - self._model = model - self._optimizer = None - self._scheduler = None - self._pretrain_losses = [] - self._pretrain_lrs = [] - self._model_input_noise_std = input_noise_std - - @property - def name(self) -> str: - return "ObjectDIP" - - @property - def model(self) -> torch.nn.Module: - return self._model - - @model.setter - def model(self, model: torch.nn.Module): - if not isinstance(model, torch.nn.Module): - raise TypeError(f"Model must be a torch.nn.Module, got {type(model)}") - self._model = model.to(self._device) - self.set_pretrained_weights(self._model) - - @property - def pretrained_weights(self) -> dict[str, torch.Tensor]: - return self._pretrained_weights - - def set_pretrained_weights(self, model: torch.nn.Module): - if not isinstance(model, torch.nn.Module): - raise TypeError(f"Pretrained model must be a torch.nn.Module, got {type(model)}") - self._pretrained_weights = deepcopy(model.state_dict()) - - @property - def model_input(self) -> torch.Tensor: - return self._model_input - - @model_input.setter - def model_input(self, input_tensor: torch.Tensor): - inp = validate_tensor( - input_tensor, - name="model_input", - dtype=torch.float32, - ndim=5, - expand_dims=True, - ) - self._model_input = inp.to(self._device) - - @property - def pretrain_target(self) -> torch.Tensor: - return self._pretrain_target - - @pretrain_target.setter - def pretrain_target(self, target: torch.Tensor): - if target.ndim == 5: - target = target.squeeze(0).squeeze(0) - - target = validate_tensor( - target, - name="pretrain_target", - ndim=3, - dtype=torch.float32, - expand_dims=True, - ) - if target.shape[-3:] != self.model_input.shape[-3:]: - raise ValueError( - f"Pretrain target shape {target.shape} does not match model input shape {self.model_input.shape}" - ) - self._pretrain_target = target.to(self._device) - - @property - def _model_input_noise_std(self) -> float: - """standard deviation of the gaussian noise added to the model input each forward call""" - return self._input_noise_std - - @_model_input_noise_std.setter - def _model_input_noise_std(self, std: float): - validate_gt(std, 0.0, "input_noise_std", geq=True) - self._input_noise_std = std - - @property - def optimizer(self) -> torch.optim.Optimizer: - """get the optimizer for the DIP model""" - if self._optimizer is None: - raise ValueError("Optimizer is not set. Use set_optimizer() to set it.") - return self._optimizer - - def set_optimizer(self, opt_params: dict): - opt_type = opt_params.pop("type") - if isinstance(opt_type, torch.optim.Optimizer): - self._optimizer = opt_type - elif isinstance(opt_type, type): - self._optimizer = opt_type(self.model.parameters(), **opt_params) - elif opt_type == "adam": - self._optimizer = torch.optim.Adam(self.model.parameters(), **opt_params) - elif opt_type == "adamw": - self._optimizer = torch.optim.AdamW(self.model.parameters(), **opt_params) - elif opt_type == "sgd": - self._optimizer = torch.optim.SGD(self.model.parameters(), **opt_params) - else: - raise NotImplementedError(f"Unknown optimizer type: {opt_params['type']}") - - @property - def scheduler( - self, - ) -> ( - torch.optim.lr_scheduler._LRScheduler - | torch.optim.lr_scheduler.CyclicLR - | torch.optim.lr_scheduler.ReduceLROnPlateau - | torch.optim.lr_scheduler.ExponentialLR - | None - ): - return self._scheduler - - def set_scheduler(self, params: dict, num_iter: int | None = None) -> None: - sched_type: str = params["type"].lower() - optimizer = self.optimizer - base_LR = optimizer.param_groups[0]["lr"] - if sched_type == "none": - scheduler = None - elif sched_type == "cyclic": - scheduler = torch.optim.lr_scheduler.CyclicLR( - optimizer, - base_lr=params.get("base_lr", base_LR / 4), - max_lr=params.get("max_lr", base_LR * 4), - step_size_up=params.get("step_size_up", 100), - mode=params.get("mode", "triangular2"), - cycle_momentum=params.get("momentum", False), - ) - elif sched_type.startswith(("plat", "reducelronplat")): - scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau( - optimizer, - mode="min", - factor=params.get("factor", 0.5), - patience=params.get("patience", 10), - threshold=params.get("threshold", 1e-3), - min_lr=params.get("min_lr", base_LR / 20), - cooldown=params.get("cooldown", 20), - ) - elif sched_type in ["exp", "gamma", "exponential"]: - if "gamma" in params.keys(): - gamma = params["gamma"] - elif num_iter is not None: - fac = params.get("factor", 0.01) - gamma = fac ** (1.0 / num_iter) - else: - gamma = 0.999 - scheduler = torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma=gamma) - else: - raise ValueError(f"Unknown scheduler type: {sched_type}") - self._scheduler = scheduler - - @property - def pretrain_losses(self) -> np.ndarray: - return np.array(self._pretrain_losses) - - @property - def pretrain_lrs(self) -> np.ndarray: - return np.array(self._pretrain_lrs) - - @property - def obj(self): - obj = self.model(self._model_input)[0] - return self.apply_hard_constraints(obj) - - def forward(self): - return self.model(self._model_input) - - def to(self, device: str): - self.device = device - self._model = self._model.to(self.device) - self._model_input = self._model_input.to(self.device) - self._pretrain_target = self._pretrain_target.to(self.device) - - @property - def params(self): - return self._model.parameters() - - def reset(self): - self.model.load_state_dict(self.pretrained_weights.copy()) - - def pretrain( - self, - model_input: torch.Tensor, - pretrain_target: torch.Tensor, - reset: bool = True, - num_epochs: int = 100, - optimizer_params: dict | None = None, - scheduler_params: dict | None = None, - loss_fn: Callable | str = "l2", - apply_constraints: bool = False, - show: bool = True, - ): - model_input.to(self.device) - pretrain_target.to(self.device) - - if optimizer_params is not None: - self.set_optimizer(optimizer_params) - - if scheduler_params is not None: - self.set_scheduler(scheduler_params, num_epochs) - - if reset: - self._model.apply(reset_weights) - self._pretrain_losses = [] - self._pretrain_lrs = [] - - if model_input is not None: - self.model_input = model_input - - if pretrain_target.shape[-3:] != self.model_input.shape[-3:]: - raise ValueError( - f"Pretrain target shape {pretrain_target.shape} does not match model input shape {self.model_input.shape}" - ) - self.pretrain_target = pretrain_target.clone().detach().to(self.device) - - loss_fn = torch.nn.functional.mse_loss - - self._pretrain( - num_epochs=num_epochs, - loss_fn=loss_fn, - apply_constraints=apply_constraints, - show=show, - ) - self.set_pretrained_weights(self.model) - - def _pretrain( - self, - num_epochs: int, - loss_fn: Callable, - apply_constraints: bool = False, - show: bool = False, - ): - if not hasattr(self, "pretrain_target"): - raise ValueError("Pretrain target is not set. Use pretrain_target to set it.") - - self.model.train() - optimizer = self.optimizer - sch = self.scheduler - pbar = tqdm(range(num_epochs)) - output = self.obj - - for a0 in pbar: - if apply_constraints: - output = self.obj - else: - output = self.model(self.model_input).squeeze(0).squeeze(0) - - loss = loss_fn(output, self.pretrain_target) - loss.backward() - optimizer.step() - optimizer.zero_grad() - - if sch is not None: - if isinstance(sch, torch.optim.lr_scheduler.ReduceLROnPlateau): - sch.step(loss.item()) - else: - sch.step() - - self._pretrain_losses.append(loss.item()) - self._pretrain_lrs.append(optimizer.param_groups[0]["lr"]) - pbar.set_description(f"Epoch {a0 + 1}/{num_epochs}, Loss: {loss.item():.4f}, ") - - -# INR stuff - - -class ObjectINN(ObjectConstraints): - """ - Object model for INN objects. - - - VolumeDataset (?) dependent on the object - """ - - def __init__( - self, - model: nn.Module, - volume_shape=tuple[int, int, int], - device: str = "cuda", - ): - super().__init__( - volume_shape=volume_shape, - device=device, - offset_obj=0, - ) - - self._model = model - - # --- Properties --- - @property - def obj(self): - return self._obj - - def create_volume( - self, - world_size: int, - global_rank: int, - ray_size: int, - ): - N = max(self._shape) - with torch.no_grad(): - coords_1d = torch.linspace(-1, 1, N) - x, y, z = torch.meshgrid(coords_1d, coords_1d, coords_1d, indexing="ij") - inputs = torch.stack([x, y, z], dim=-1).reshape(-1, 3) - - # Underlying model if using DDP - model = self.model.module if hasattr(self.model, "module") else self.model - - # Batch size for inference - # 5x larger batches no gradients needed - - inference_batch_size = 5 * N * ray_size - - # Distribute work across GPUs - total_samples = N**3 - samples_per_gpu = total_samples // world_size - remainder = total_samples % world_size - - # Handle uneven distribution - if global_rank < remainder: - start_idx = global_rank * (samples_per_gpu + 1) - end_idx = start_idx + samples_per_gpu + 1 - else: - start_idx = global_rank * samples_per_gpu + remainder - end_idx = start_idx + samples_per_gpu - - inputs_subset = inputs[start_idx:end_idx] - num_samples = inputs_subset.shape[0] - outputs_list = [] - - for batch_start in range(0, num_samples, inference_batch_size): - batch_end = min(batch_start + inference_batch_size, num_samples) - batch_coords = inputs_subset[batch_start:batch_end].to( - self.device, non_blocking=True - ) - - batch_outputs = model(batch_coords) - if batch_outputs.dim() > 1: - batch_outputs = batch_outputs.squeeze(-1) - - outputs_list.append(batch_outputs.cpu()) - - outputs = torch.cat(outputs_list, dim=0) - - if world_size > 1: - # Gather from all ranks - # handle potentially different sizes - output_size = torch.tensor(outputs.shape[0], device=self.device, dtype=torch.long) - all_sizes = [ - torch.zeros(1, device=self.device, dtype=torch.long) for _ in range(world_size) - ] - dist.all_gather(all_sizes, output_size) - - # Create gather list with correct sizes - max_size = max(size.item() for size in all_sizes) - - # Pad if necessary for gathering - if outputs.shape[0] < max_size: - padding = torch.zeros( - max_size - outputs.shape[0], device=outputs.device, dtype=outputs.dtype - ) - outputs_padded = torch.cat([outputs, padding], dim=0).to(self.device) - else: - outputs_padded = outputs.to(self.device) - - gathered_outputs = [ - torch.empty(max_size, device=self.device, dtype=outputs.dtype) - for _ in range(world_size) - ] - dist.all_gather(gathered_outputs, outputs_padded.contiguous()) - - # Trim padding and concatenate - trimmed_outputs = [] - for rank, size in enumerate(all_sizes): - trimmed_outputs.append(gathered_outputs[rank][: size.item()]) - - pred_full = torch.cat(trimmed_outputs, dim=0).reshape(N, N, N).float() - else: - pred_full = outputs.reshape(N, N, N).float() - - self._obj = pred_full.detach().cpu() - - @property - def model(self): - return self._model - - @model.setter - def model(self, model): - self._model = model - - def apply_soft_constraints( - self, - coords: torch.Tensor, - ): - soft_loss = torch.tensor(0.0, device=coords.device) - if self.soft_constraints["tv_vol"] > 0: - num_tv_samples = min(10000, coords.shape[0]) - tv_indices = torch.randperm(coords.shape[0], device=coords.device)[:num_tv_samples] - - # Rerun forward for gradient tracking - tv_coords = coords[tv_indices].detach().requires_grad_(True) - - tv_densities_recomputed = self.model(tv_coords) - if tv_densities_recomputed.dim() > 1: - tv_densities_recomputed = tv_densities_recomputed.squeeze(-1) - - # Compute gradients - grad_outputs = torch.autograd.grad( - outputs=tv_densities_recomputed, - inputs=tv_coords, - grad_outputs=torch.ones_like(tv_densities_recomputed), - create_graph=True, - )[0] - - grad_norm = torch.norm(grad_outputs, dim=1) - soft_loss += self.soft_constraints["tv_vol"] * grad_norm.mean() - - return soft_loss - - def forward( - self, - all_coords: torch.Tensor, - ): - all_densities = self.model(all_coords) - - if all_densities.dim() > 1: - all_densities = all_densities.squeeze(-1) - - valid_mask = ( - (all_coords[:, 0] >= -1) - & (all_coords[:, 0] <= 1) - & (all_coords[:, 1] >= -1) - & (all_coords[:, 1] <= 1) - & (all_coords[:, 2] >= -1) - & (all_coords[:, 2] <= 1) - ).float() - - all_densities = all_densities * valid_mask - - return all_densities - - -ObjectModelType = ObjectVoxelwise | ObjectINN # | ObjectDIP | ObjectImplicit (ObjectFFN?) - - -# Pretrain Volume Dataset diff --git a/src/quantem/tomography_old/preprocess/__init__.py b/src/quantem/tomography_old/preprocess/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/quantem/tomography_old/preprocess/drift.py b/src/quantem/tomography_old/preprocess/drift.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/quantem/tomography_old/tomography.py b/src/quantem/tomography_old/tomography.py deleted file mode 100644 index 7be24397..00000000 --- a/src/quantem/tomography_old/tomography.py +++ /dev/null @@ -1,883 +0,0 @@ -# import matplotlib.pyplot as plt -# import numpy as np -# import torch -# import torch.distributed as dist -# from torch.nn import SmoothL1Loss -# from torch.utils.tensorboard import SummaryWriter - -# # from torch_radon.radon import ParallelBeam as Radon -# from tqdm.auto import tqdm - -# from quantem.core.ml.loss_functions import ( -# CharbonnierLoss, -# L1Loss, -# LLMSELoss, -# MSELogMSELoss, -# MSELoss, -# ) - -# # Temporary imports for TomographyNERF -# from quantem.tomography.object_models import ObjectINN, ObjectVoxelwise -# from quantem.tomography.tomography_base import TomographyBase -# from quantem.tomography.tomography_conv import TomographyConv -# from quantem.tomography.tomography_ml import TomographyML -# from quantem.tomography.utils import differentiable_shift_2d, gaussian_kernel_1d, rot_ZXZ - - -# # Temporary aux class for TomographyNERF -# # TODO: Maybe put this in INN? -# def get_num_samples_per_ray(N: int, epoch: int): -# """Increase number of samples per ray at specific epochs.""" -# # Exponential schedule - -# epochs = np.linspace(0, 10, 5, dtype=int) -# # schedule = np.linspace(20, N, 5, dtype=int) -# schedule = np.array([20, 100, 150, 200, 200]) -# # schedule = np.array([200, 200, 200, 200, 200]) -# # schedule = np.array([20, 100, 250, 500, 500]) -# # schedule = np.array([500, 500, 500, 500, 500]) -# # schedule = np.array([300, 300, 300, 300, 300]) -# # schedule = np.exp(schedule) -# schedule_warmup = dict[int, int](zip(epochs, schedule)) - -# for epoch_threshold, samples in schedule_warmup.items(): -# if epoch >= epoch_threshold: -# num_samples = samples - -# return num_samples - - -# class Tomography(TomographyConv, TomographyML, TomographyBase, TomographyDDP): -# """ -# Top level class for either using conventional or ML-based reconstruction methods -# for tomography. -# """ - -# def __init__( -# self, -# dataset, -# volume_obj, -# device, -# _token, -# ): -# super().__init__(dataset, volume_obj, device, _token) - -# # TODO: More elegant way of doing this. -# self.global_epochs = 0 -# self.ddp_instantiated = False -# self.pretraining_instantiated = False - -# # --- Reconstruction Method --- - -# def sirt_recon( -# self, -# num_iterations: int = 10, -# inline_alignment: bool = False, -# enforce_positivity: bool = True, -# volume_shape: tuple = None, -# reset: bool = True, -# smoothing_sigma: float = None, -# shrinkage: float = None, -# filter_name: str = "hamming", -# circle: bool = True, -# plot_loss: bool = False, -# ): -# num_angles, num_rows, num_cols = self.dataset.tilt_series.shape -# sirt_tilt_series = self.dataset.tilt_series.clone() -# sirt_tilt_series = sirt_tilt_series.permute(2, 0, 1) - -# hard_constraints = { -# "positivity": enforce_positivity, -# "shrinkage": shrinkage, -# } -# self.volume_obj.hard_constraints = hard_constraints - -# if volume_shape is None: -# volume_shape = (num_rows, num_rows, num_rows) -# else: -# D, H, W = volume_shape - -# if reset: -# self.volume_obj.reset() -# self.loss = [] - -# proj_forward = torch.zeros_like(self.dataset.tilt_series) - -# pbar = tqdm(range(num_iterations), desc="SIRT Reconstruction") - -# if smoothing_sigma is not None: -# gaussian_kernel = gaussian_kernel_1d(smoothing_sigma).to(self.device) -# else: -# gaussian_kernel = None - -# print( -# "Devices", -# sirt_tilt_series.device, -# proj_forward.device, -# self.dataset.tilt_angles.device, -# ) - -# for iter in pbar: -# proj_forward, loss = self._sirt_run_epoch( -# tilt_series=sirt_tilt_series, -# proj_forward=proj_forward, -# angles=self.dataset.tilt_angles, -# inline_alignment=iter > 0 and inline_alignment, -# filter_name=filter_name, -# gaussian_kernel=gaussian_kernel, -# circle=circle, -# ) - -# pbar.set_description(f"SIRT Reconstruction | Loss: {loss.item():.4f}") - -# self.loss.append(loss.item()) - -# self.sirt_recon_vol = self.volume_obj - -# # Permutation due to sinogram ordering. -# self.sirt_recon_vol.obj = self.sirt_recon_vol.obj.permute(1, 2, 0) - -# if plot_loss: -# self.plot_loss() - -# # TODO: ML Recon which has NeRF and AD depending on the object type. -# # TODO: Temporary - -# def create_batch_projection_rays(self, pixel_i, pixel_j, N, num_samples_per_ray): -# """Create projection rays for entire batch simultaneously.""" -# batch_size = len(pixel_i) - -# # Convert all pixels to normalized coordinates -# x_coords = (pixel_j / (N - 1)) * 2 - 1 -# y_coords = (pixel_i / (N - 1)) * 2 - 1 -# # TODO: maybe pixel_j.device? -# # Create z coordinates -# z_coords = torch.linspace(-1, 1, num_samples_per_ray, device=self.device) - -# # Create rays for all pixels: [batch_size, num_samples_per_ray, 3] -# rays = torch.zeros(batch_size, num_samples_per_ray, 3, device=self.device) - -# # Fill coordinates efficiently -# rays[:, :, 0] = x_coords.unsqueeze(1) # x constant per ray -# rays[:, :, 1] = y_coords.unsqueeze(1) # y constant per ray -# rays[:, :, 2] = z_coords.unsqueeze(0) # z varies along ray - -# return rays - -# @torch.compile(mode="reduce-overhead") -# def transform_batch_ray_coordinates(self, rays, z1, x, z3, shifts, N, sampling_rate): -# # Step 1: Apply shifts -# shift_x_norm = (shifts[:, 0:1] * sampling_rate * 2) / (N - 1) -# shift_y_norm = (shifts[:, 1:2] * sampling_rate * 2) / (N - 1) - -# rays_x = rays[:, :, 0] - shift_x_norm -# rays_y = rays[:, :, 1] - shift_y_norm -# rays_z = rays[:, :, 2] - -# # Rotation 1: Z(-z3) -# theta = torch.deg2rad(-z3).view(-1, 1) -# cos_t = torch.cos(theta) -# sin_t = torch.sin(theta) - -# rays_x_rot1 = cos_t * rays_x - sin_t * rays_y -# rays_y_rot1 = sin_t * rays_x + cos_t * rays_y -# rays_z_rot1 = rays_z - -# # Rotation 2: X(x) -# theta = torch.deg2rad(x).view(-1, 1) -# cos_t = torch.cos(theta) -# sin_t = torch.sin(theta) - -# rays_x_rot2 = rays_x_rot1 -# rays_y_rot2 = cos_t * rays_y_rot1 - sin_t * rays_z_rot1 -# rays_z_rot2 = sin_t * rays_y_rot1 + cos_t * rays_z_rot1 - -# # Rotation 3: Z(-z1) -# theta = torch.deg2rad(-z1).view(-1, 1) -# cos_t = torch.cos(theta) -# sin_t = torch.sin(theta) - -# rays_x_final = cos_t * rays_x_rot2 - sin_t * rays_y_rot2 -# rays_y_final = sin_t * rays_x_rot2 + cos_t * rays_y_rot2 -# rays_z_final = rays_z_rot2 - -# # Stack the final result -# transformed_rays = torch.stack([rays_x_final, rays_y_final, rays_z_final], dim=2) - -# return transformed_rays - -# # TODO: Temp logger -# def setup_logger(self, log_path): -# if self.global_rank == 0: -# self.temp_logger = SummaryWriter(log_dir=log_path) -# else: -# self.temp_logger = None - -# def pretrain( -# self, -# # volume_dataset: PretrainVolumeDataset, -# obj: ObjectINN, -# batch_size: int, -# soft_constraints: dict = None, -# log_path: str = None, -# optimizer_params: dict = None, -# epochs: int = 100, -# viz_freq: int = 1, -# consistency_criterion: str = "mse", -# ): -# if not self.pretraining_instantiated: -# self.setup_distributed() -# # self.setup_pretraining_dataloader(volume_dataset, batch_size) -# self.pretraining_instantiated = True -# obj.model = self.build_model(obj._model) -# self.obj = obj - -# if soft_constraints is not None: -# self.obj.soft_constraints = soft_constraints - -# if not hasattr(self, "temp_logger"): -# self.setup_logger(log_path=log_path) - -# if optimizer_params is not None: -# optimizer_params = self.scale_lr(optimizer_params) -# self.optimizer_params = optimizer_params -# self.set_optimizers() -# device_type = self.device.type -# consistency_loss_fn = None -# if consistency_criterion[0].lower() == "mse": -# consistency_loss_fn = MSELoss() -# elif consistency_criterion[0].lower() == "l1": -# consistency_loss_fn = L1Loss() -# elif consistency_criterion[0].lower() == "mse_log": -# consistency_loss_fn = MSELogMSELoss() -# elif consistency_criterion[0].lower() == "llmse": -# consistency_loss_fn = LLMSELoss() -# elif consistency_criterion[0].lower() == "smooth_l1": -# consistency_loss_fn = SmoothL1Loss(beta=consistency_criterion[1]) -# elif consistency_criterion[0].lower() == "adaptive_smooth_l1": -# # consistency_loss_fn = AdaptiveSmoothL1LossDDP( -# # beta_init=consistency_criterion[1], ema_factor=0.99, eps=1e-8 -# # ) -# pass -# elif consistency_criterion[0].lower() == "charbonnier": -# consistency_loss_fn = CharbonnierLoss(epsilon=1e-12, reduction="mean") -# else: -# raise ValueError(f"Invalid consistency criterion: {consistency_criterion}") - -# for epoch in range(epochs): -# if self.pretraining_sampler is not None: -# self.pretraining_sampler.set_epoch(epoch) - -# self.obj.model.train() - -# epoch_loss = 0.0 -# epoch_consistency_loss = 0.0 -# epoch_tv_loss = 0.0 -# num_batches = 0 - -# for batch_idx, batch in enumerate(self.pretraining_dataloader): -# coords = batch["coords"].to(self.device, non_blocking=True) -# target = batch["target"].to(self.device, non_blocking=True) - -# for _, opt in self.optimizers.items(): -# opt.zero_grad() - -# with torch.autocast(device_type=device_type, dtype=torch.bfloat16, enabled=True): -# outputs = self.obj.forward(coords) -# consistency_loss = consistency_loss_fn(outputs, target) -# tv_loss = self.obj.apply_soft_constraints(coords) - -# loss = consistency_loss + tv_loss - -# loss.backward() - -# epoch_loss += loss.detach() -# epoch_consistency_loss += consistency_loss.detach() -# epoch_tv_loss += tv_loss.detach() - -# torch.nn.utils.clip_grad_norm_(self.obj.model.parameters(), max_norm=1.0) - -# for _, opt in self.optimizers.items(): -# opt.step() -# epoch_loss += loss.detach() -# num_batches += 1 - -# if epoch % viz_freq == 0 or epoch == epochs - 1: -# avg_loss = epoch_loss / num_batches -# with torch.no_grad(): -# self.obj.create_volume( -# world_size=self.world_size, -# global_rank=self.global_rank, -# # ray_size=volume_dataset.N, -# ) -# pred_full = self.obj.obj -# loss_tensor = avg_loss.clone().detach() -# avg_loss = loss_tensor.item() -# avg_tv_loss = epoch_tv_loss / num_batches -# avg_consistency_loss = epoch_consistency_loss / num_batches - -# metrics = torch.tensor( -# [avg_loss, avg_consistency_loss, avg_tv_loss], device=self.device -# ) -# if self.world_size > 1: -# dist.all_reduce(metrics, op=dist.ReduceOp.AVG) -# avg_loss, avg_consistency_loss, avg_tv_loss = metrics.tolist() - -# if self.global_rank == 0: -# self.temp_logger.add_scalar("Pretrain/total_loss", avg_loss, epoch) -# self.temp_logger.add_scalar( -# "Pretrain/consistency_loss", avg_consistency_loss, epoch -# ) -# self.temp_logger.add_scalar("Pretrain/tv_loss", avg_tv_loss, epoch) - -# # current_lr = self.schedulers["model"].get_last_lr()[0] -# # self.temp_logger.add_scalar("Pretrain/lr", current_lr, epoch) - -# fig, ax = plt.subplots(ncols=4, figsize=(36, 12)) -# ax[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap="turbo", vmin=0) -# ax[0].set_title("Sum over Z-axis") -# ax[1].matshow( -# # pred_full[volume_dataset.N // 2].cpu().numpy(), cmap="turbo", vmin=0 -# # ) -# # ax[1].set_title(f"Slice at Z={volume_dataset.N // 2}") -# ax[2].matshow(pred_full.sum(dim=1).cpu().numpy(), cmap="turbo", vmin=0) -# ax[2].set_title("Sum over Y-axis") -# ax[3].matshow(pred_full.sum(dim=2).cpu().numpy(), cmap="turbo", vmin=0) -# ax[3].set_title("Sum over X-axis") -# self.temp_logger.add_figure("Pretrain/viz", fig, epoch, close=True) - -# plt.close(fig) -# print( -# f"Epoch [{epoch}/{epochs}] Pretrain Total Loss: {avg_loss:.6f}, Consistency Loss: {avg_consistency_loss:.6f}, TV Loss: {avg_tv_loss:.6f}" -# ) -# self.global_epochs += 1 -# if self.global_rank == 0: -# print(f"Pretraining Completed, saving model weights to {log_path}/model_weights.pth") -# torch.save(self.obj.model.state_dict(), f"{log_path}/model_weights.pth") - -# def recon( -# self, -# obj: ObjectINN, -# batch_size: int, -# num_workers: int = 0, -# epochs=20, -# use_amp=True, -# viz_freq=1, -# checkpoint_freq=5, -# optimizer_params: dict = None, -# scheduler_params: dict = None, -# soft_constraints: dict = None, -# vol_save_path: str = None, # TODO: TEMPORARY -# log_path=None, -# val_fraction: float = 0.0, -# learn_shifts: bool = True, -# # l1_loss: bool = False, -# consistency_criterion: str = "mse", -# model_weights_path: str = None, -# force_cpu: bool = False, -# ): -# if not self.ddp_instantiated: -# if not self.pretraining_instantiated: -# self.setup_distributed() -# if model_weights_path is not None: -# print(f"Loading model weights from {model_weights_path}") -# state_dict = torch.load(model_weights_path, map_location="cpu") - -# # Handle DataParallel/DDP checkpoints -# if any(k.startswith("module.") for k in state_dict.keys()): -# new_state_dict = { -# k.replace("module.", ""): v for k, v in state_dict.items() -# } -# else: -# new_state_dict = state_dict - -# obj.model.load_state_dict(new_state_dict) -# print("Model weights loaded successfully") -# obj.model = self.build_model(obj._model) -# self.obj = obj - -# self.setup_dataloader(self.dataset, batch_size, num_workers, val_fraction) -# self.ddp_instantiated = True - -# if soft_constraints is not None: -# self.obj.soft_constraints = soft_constraints - -# if not hasattr(self, "temp_logger"): -# self.setup_logger(log_path=log_path) - -# zero_tilt_idx = torch.argmin(torch.abs(self.dataset.tilt_angles)).item() - -# if self.global_rank == 0: -# print( -# f"Using projection {zero_tilt_idx} (angle={self.dataset.tilt_angles[zero_tilt_idx]:.2f}°) as reference" -# ) -# print(f"Using consistency criterion: {consistency_criterion}") - -# # Auxiliary params setup -# self.dataset.setup_auxiliary_params(zero_tilt_idx, self.device, learn_shifts) -# aux_params = self.dataset.auxiliary_params -# # Scaling learning rates to account for distributed training -# if optimizer_params is not None: -# optimizer_params = self.scale_lr(optimizer_params) -# self.optimizer_params = optimizer_params -# self.set_optimizers() - -# if scheduler_params is not None: -# self.scheduler_params = scheduler_params -# self.set_schedulers(self.scheduler_params, num_iter=epochs) - -# aux_norm = torch.tensor(0.0, device=self.device) -# model_norm = torch.tensor(0.0, device=self.device) - -# for _, opt in self.optimizers.items(): -# opt.zero_grad() - -# N = max(self.dataset.dims) - -# device_type = self.device.type -# autocast_dtype = torch.bfloat16 if use_amp else None - -# for epoch in range(epochs): -# num_samples_per_ray = get_num_samples_per_ray(N=N, epoch=self.global_epochs) - -# # num_samples_per_ray = get_num_samples_per_ray(epoch) -# # Log the change if it happens -# if self.global_rank == 0: -# print(f"Epoch {epoch}: num_samples_per_ray = {num_samples_per_ray}") - -# if self.global_rank == 0 and self.global_epochs > 0: -# prev_samples = get_num_samples_per_ray(N=N, epoch=self.global_epochs - 1) - -# if num_samples_per_ray != prev_samples: -# print( -# f"Epoch {epoch}: Changing num_samples_per_ray from {prev_samples} to {num_samples_per_ray}" -# ) - -# if self.sampler is not None: -# self.sampler.set_epoch(epoch) - -# epoch_loss = 0.0 -# epoch_consistency_loss = 0.0 -# epoch_tv_loss = 0.0 -# epoch_z1_loss = 0.0 - -# num_batches = 0 -# consistency_loss_fn = None -# if consistency_criterion[0].lower() == "mse": -# consistency_loss_fn = MSELoss() -# elif consistency_criterion[0].lower() == "l1": -# consistency_loss_fn = L1Loss() -# elif consistency_criterion[0].lower() == "mse_log": -# consistency_loss_fn = MSELogMSELoss() -# elif consistency_criterion[0].lower() == "llmse": -# consistency_loss_fn = LLMSELoss() -# elif consistency_criterion[0].lower() == "smooth_l1": -# consistency_loss_fn = SmoothL1Loss(beta=consistency_criterion[1]) -# elif consistency_criterion[0].lower() == "adaptive_smooth_l1": -# # consistency_loss_fn = AdaptiveSmoothL1LossDDP( -# # beta_init=consistency_criterion[1], ema_factor=0.99, eps=1e-8 -# # ) -# pass -# elif consistency_criterion[0].lower() == "charbonnier": -# consistency_loss_fn = CharbonnierLoss(epsilon=1e-12, reduction="mean") -# else: -# raise ValueError(f"Invalid consistency criterion: {consistency_criterion}") - -# for batch_idx, batch in enumerate(self.dataloader): -# projection_indices = batch["projection_idx"] - -# pixel_i = batch["pixel_i"].float().to(self.device, non_blocking=True) -# pixel_j = batch["pixel_j"].float().to(self.device, non_blocking=True) -# target_values = batch["target_value"].to(self.device, non_blocking=True) -# phis = batch["phi"].to(self.device, non_blocking=True) -# projection_indices = batch["projection_idx"].to(self.device, non_blocking=True) - -# shifts, z1_params, z3_params = aux_params(None) -# batch_shifts = torch.index_select(shifts, 0, projection_indices) -# batch_z1 = torch.index_select(z1_params, 0, projection_indices) -# batch_z3 = torch.index_select(z3_params, 0, projection_indices) - -# with torch.autocast( -# device_type=device_type, dtype=autocast_dtype, enabled=use_amp -# ): -# with torch.no_grad(): -# batch_ray_coords = self.create_batch_projection_rays( -# pixel_i, pixel_j, N, num_samples_per_ray -# ) - -# # TODO: .forward passing z1, x, z3, shifts, N, sampling_rate -# transformed_rays = self.transform_batch_ray_coordinates( -# batch_ray_coords, -# z1=batch_z1, -# x=phis, -# z3=batch_z3, -# shifts=batch_shifts, -# N=N, -# sampling_rate=1.0, -# ) - -# all_coords = transformed_rays.view(-1, 3) - -# all_densities = self.obj.forward(all_coords) - -# tv_loss = self.obj.apply_soft_constraints(all_coords) -# ray_densities = all_densities.view( -# len(target_values), num_samples_per_ray -# ) # Reshape rays and integarte -# step_size = 2.0 / (num_samples_per_ray - 1) - -# predicted_values = ray_densities.sum(dim=1) * step_size - -# consistency_loss = consistency_loss_fn(predicted_values, target_values) -# tv_loss_z1 = torch.tensor(0.0, device=self.device) - -# loss = consistency_loss + tv_loss + tv_loss_z1 - -# loss.backward() - -# epoch_loss += loss.detach() -# epoch_consistency_loss += consistency_loss.detach() -# epoch_tv_loss += tv_loss.detach() -# epoch_z1_loss += tv_loss_z1.detach() -# num_batches += 1 - -# for key, opt in self.optimizers.items(): -# if key == "model": -# model_norm = torch.nn.utils.clip_grad_norm_( -# self.obj.model.parameters(), max_norm=1 -# ) -# elif key == "aux_params": -# aux_norm = torch.nn.utils.clip_grad_norm_( -# self.dataset.auxiliary_params.parameters(), max_norm=1 -# ) -# opt.step() -# opt.zero_grad() - -# for key, sched in self.schedulers.items(): -# if isinstance(sched, torch.optim.lr_scheduler.ReduceLROnPlateau): -# sched.step(epoch_loss) -# else: -# sched.step() - -# if epoch % viz_freq == 0 or epoch == epochs - 1 or epoch % checkpoint_freq == 0: -# with torch.no_grad(): -# self.obj.create_volume( -# world_size=self.world_size, -# global_rank=self.global_rank, -# ray_size=num_samples_per_ray, -# ) -# pred_full = self.obj.obj -# avg_loss = epoch_loss.item() / num_batches -# avg_consistency_loss = epoch_consistency_loss.item() / num_batches -# avg_tv_loss = epoch_tv_loss.item() / num_batches -# avg_z1_loss = epoch_z1_loss.item() / num_batches -# shifts, z1, z3 = self.dataset.auxiliary_params.forward() -# shifts = shifts.detach().cpu() -# z1 = z1.detach().cpu() -# z3 = z3.detach().cpu() -# metrics = torch.tensor( -# [avg_loss, avg_consistency_loss, avg_tv_loss, avg_z1_loss], -# device=self.device, -# ) -# if self.world_size > 1: -# dist.all_reduce(metrics, op=dist.ReduceOp.AVG) -# avg_loss, avg_consistency_loss, avg_tv_loss, avg_z1_loss = metrics.tolist() - -# if hasattr(self, "val_dataloader") and self.val_dataloader is not None: -# val_loss = self.validate( -# aux_params, num_samples_per_ray, device_type, autocast_dtype, use_amp, N -# ) - -# if self.global_rank == 0 and (epoch % viz_freq == 0 or epoch == epochs - 1): -# with torch.no_grad(): -# current_lr = self.schedulers["model"].get_last_lr()[0] - -# # Log metrics -# self.temp_logger.add_scalar( -# f"train/consistency_loss_{consistency_criterion[0] if isinstance(consistency_criterion, tuple) else consistency_criterion}", -# avg_consistency_loss, -# self.global_epochs, -# ) -# if hasattr(self, "val_dataloader") and self.val_dataloader is not None: -# self.temp_logger.add_scalar( -# f"val/consistency_loss_{consistency_criterion[0] if isinstance(consistency_criterion, tuple) else consistency_criterion}", -# val_loss, -# self.global_epochs, -# ) -# self.temp_logger.add_scalar("train/z1_loss", avg_z1_loss, self.global_epochs) -# # if tv_weight > 0: -# self.temp_logger.add_scalar("train/tv_loss", avg_tv_loss, self.global_epochs) -# self.temp_logger.add_scalar("train/total_loss", avg_loss, self.global_epochs) -# self.temp_logger.add_scalar( -# "train/model_grad_norm", model_norm.item(), self.global_epochs -# ) -# self.temp_logger.add_scalar( -# "train/aux_grad_norm", aux_norm.item(), self.global_epochs -# ) -# self.temp_logger.add_scalar("train/lr", current_lr, self.global_epochs) -# self.temp_logger.add_scalar( -# "train/num_samples_per_ray", num_samples_per_ray, self.global_epochs -# ) -# if consistency_criterion[0].lower() == "adaptive_smooth_l1": -# self.temp_logger.add_scalar( -# "train/beta_2", consistency_loss_fn.beta2.item(), self.global_epochs -# ) -# fig, axes = plt.subplots(1, 5, figsize=(36, 12)) -# axes[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap="turbo", vmin=0) -# axes[0].set_title("Sum over Z-axis") - -# axes[1].matshow(pred_full[N // 2].cpu().numpy(), cmap="turbo", vmin=0) -# axes[1].set_title(f"Slice at Z={N // 2}") - -# slice_start = max(0, N // 2 - 5) -# slice_end = min(N, N // 2 + 6) -# thick_slice = pred_full[slice_start:slice_end].sum(dim=0).cpu().numpy() -# axes[2].matshow(thick_slice, cmap="turbo", vmin=0) -# axes[2].set_title(f"Thick slice sum (Z={slice_start}:{slice_end - 1})") - -# axes[3].matshow(pred_full.sum(dim=1).cpu().numpy(), cmap="turbo", vmin=0) -# axes[3].set_title("Sum over Y-axis") - -# axes[4].matshow(pred_full.sum(dim=2).cpu().numpy(), cmap="turbo", vmin=0) -# axes[4].set_title("Sum over X-axis") - -# self.temp_logger.add_figure("train/viz", fig, self.global_epochs, close=True) - -# fig, axes = plt.subplots(ncols=3, figsize=(10, 5)) -# axes[0].plot(shifts[:, 0].cpu().numpy(), label="Shifts X") -# axes[0].plot(shifts[:, 1].cpu().numpy(), label="Shifts Y") -# axes[0].legend() -# axes[1].plot(z1.cpu().numpy(), label="Z1") -# axes[2].plot(z3.cpu().numpy(), label="Z3") -# self.temp_logger.add_figure( -# "train/auxiliary_params", fig, self.global_epochs, close=True -# ) -# plt.close(fig) - -# if epoch % checkpoint_freq == 0 or self.global_epochs == epochs - 1: -# with torch.no_grad(): -# if self.global_rank == 0: -# save_path = f"{vol_save_path}/volume_epoch_{self.global_epochs:04d}.pt" -# torch.save(pred_full.cpu(), save_path) - -# self.global_epochs += 1 - -# if self.global_rank == 0: -# print("Training complete.") - -# torch.save(self.dataset.auxiliary_params.forward(), f"{log_path}/auxiliary_params.pt") -# # print("Successfully setup DDP and dataloader") - -# def validate(self, aux_params, num_samples_per_ray, device_type, autocast_dtype, use_amp, N): -# """Validate the model on the validation set.""" -# self.obj.model.eval() -# aux_params.eval() - -# val_loss = 0.0 -# num_batches = 0 - -# with torch.no_grad(): -# for batch in self.val_dataloader: -# pixel_i = batch["pixel_i"].float().to(self.device, non_blocking=True) -# pixel_j = batch["pixel_j"].float().to(self.device, non_blocking=True) -# target_values = batch["target_value"].to(self.device, non_blocking=True) -# phis = batch["phi"].to(self.device, non_blocking=True) -# projection_indices = batch["projection_idx"].to(self.device, non_blocking=True) - -# shifts, z1_params, z3_params = aux_params(None) -# batch_shifts = torch.index_select(shifts, 0, projection_indices) -# batch_z1 = torch.index_select(z1_params, 0, projection_indices) -# batch_z3 = torch.index_select(z3_params, 0, projection_indices) - -# with torch.autocast( -# device_type=device_type, dtype=autocast_dtype, enabled=use_amp -# ): -# with torch.no_grad(): -# batch_ray_coords = self.create_batch_projection_rays( -# pixel_i, pixel_j, N, num_samples_per_ray -# ) - -# transformed_rays = self.transform_batch_ray_coordinates( -# batch_ray_coords, -# z1=batch_z1, -# x=phis, -# z3=batch_z3, -# shifts=batch_shifts, -# N=N, -# sampling_rate=1.0, -# ) - -# all_coords = transformed_rays.view(-1, 3) - -# all_densities = self.obj.forward(all_coords) - -# ray_densities = all_densities.view( -# len(target_values), num_samples_per_ray -# ) # Reshape rays and integarte -# step_size = 2.0 / (num_samples_per_ray - 1) - -# predicted_values = ray_densities.sum(dim=1) * step_size - -# mse_loss = torch.nn.functional.mse_loss(predicted_values, target_values) - -# loss = mse_loss - -# val_loss += loss.detach() -# num_batches += 1 - -# avg_val_loss = val_loss / num_batches if num_batches > 0 else 0.0 - -# if self.world_size > 1: -# val_loss_tensor = avg_val_loss.detach().clone() -# dist.all_reduce(val_loss_tensor, op=dist.ReduceOp.AVG) -# avg_val_loss = val_loss_tensor.item() - -# self.obj.model.train() -# aux_params.train() - -# return avg_val_loss - -# def ad_recon( -# self, -# optimizer_params: dict, -# num_iter: int = 0, -# reset: bool = False, -# scheduler_params: dict | None = None, -# hard_constraints: dict | None = None, -# soft_constraints: dict | None = None, -# # store_iterations: bool | None = None, -# # store_iterations_every: int | None = None, -# # autograd: bool = True, -# ): -# if reset: -# self.reset_recon() - -# self.hard_constraints = hard_constraints -# self.soft_constraints = soft_constraints - -# # Make sure everything is in the correct device, might be redundant/cleaner way to do this -# self.dataset.to(self.device) -# self.volume_obj.to(self.device) - -# # Making optimizable parameters into leaf tensors. -# self.dataset.shifts = self.dataset.shifts.detach().to(self.device).requires_grad_(True) -# self.dataset.z1_angles = ( -# self.dataset.z1_angles.detach().to(self.device).requires_grad_(True) -# ) -# self.dataset.z3_angles = ( -# self.dataset.z3_angles.detach().to(self.device).requires_grad_(True) -# ) - -# if optimizer_params is not None: -# self.optimizer_params = optimizer_params -# self.set_optimizers() - -# if scheduler_params is not None: -# self.scheduler_params = scheduler_params -# self.set_schedulers(self.scheduler_params, num_iter=num_iter) - -# if hard_constraints is not None: -# self.volume_obj.hard_constraints = hard_constraints -# if soft_constraints is not None: -# self.volume_obj.soft_constraints = soft_constraints - -# pbar = tqdm(range(num_iter), desc="AD Reconstruction") - -# for a0 in pbar: -# total_loss = 0.0 -# tilt_series_loss = 0.0 - -# pred_volume = self.volume_obj.forward() - -# for i in range(len(self.dataset.tilt_series)): -# forward_projection = self.projection_operator( -# vol=pred_volume, -# z1=self.dataset.z1_angles[i], -# x=self.dataset.tilt_angles[i], -# z3=self.dataset.z3_angles[i], -# shift_x=self.dataset.shifts[i, 0], -# shift_y=self.dataset.shifts[i, 1], -# device=self.device, -# ) - -# tilt_series_loss += torch.nn.functional.mse_loss( -# forward_projection, self.dataset.tilt_series[i] -# ) -# tilt_series_loss /= len(self.dataset.tilt_series) - -# total_loss = tilt_series_loss + self.volume_obj.soft_loss -# self.loss.append(total_loss.item()) - -# total_loss.backward() - -# for opt in self.optimizers.values(): -# opt.step() -# opt.zero_grad() - -# if self.schedulers is not None: -# for sch in self.schedulers.values(): -# if isinstance(sch, torch.optim.lr_scheduler.ReduceLROnPlateau): -# sch.step(total_loss) -# elif sch is not None: -# sch.step() - -# pbar.set_description(f"AD Reconstruction | Loss: {total_loss:.4f}") - -# if self.logger is not None: -# self.logger.log_scalar("loss/total", total_loss.item(), a0) -# self.logger.log_scalar("loss/tilt_series", tilt_series_loss.item(), a0) -# self.logger.log_scalar( -# "loss/soft constraints", self.volume_obj.soft_loss.item(), a0 -# ) - -# if a0 % self.logger.log_images_every == 0: -# self.logger.projection_images( -# volume_obj=self.volume_obj, -# epoch=a0, -# ) -# self.logger.tilt_angles_figure(dataset=self.dataset, step=a0) - -# self.logger.flush() - -# self.ad_recon_vol = self.volume_obj.forward() - -# return self - -# def reset_recon(self) -> None: -# if isinstance(self.volume_obj, ObjectVoxelwise): -# self.volume_obj.reset() - -# self.ad_recon_vol = None - -# # --- Projection Operators ---- -# def projection_operator( -# self, -# vol, -# z1, -# x, -# z3, -# shift_x, -# shift_y, -# device, -# ): -# projection = ( -# rot_ZXZ( -# mags=vol.unsqueeze(0), # Add batch dimension -# z1=z1, -# x=-x, -# z3=z3, -# device=device, -# mode="bilinear", -# ) -# .squeeze() -# .sum(axis=0) -# ) - -# shifted_projection = differentiable_shift_2d( -# image=projection, -# shift_x=shift_x, -# shift_y=shift_y, -# sampling_rate=1.0, # Assuming 1 pixel = 1 physical unit -# ) - -# return shifted_projection diff --git a/src/quantem/tomography_old/tomography_base.py b/src/quantem/tomography_old/tomography_base.py deleted file mode 100644 index d3b94427..00000000 --- a/src/quantem/tomography_old/tomography_base.py +++ /dev/null @@ -1,514 +0,0 @@ -from typing import Tuple - -import matplotlib.pyplot as plt -import numpy as np -import torch -from numpy.typing import NDArray -from torch._tensor import Tensor -from tqdm.auto import tqdm - -from quantem.core.datastructures.dataset3d import Dataset3d -from quantem.core.io.serialize import AutoSerialize -from quantem.core.visualization.visualization import show_2d -from quantem.imaging.drift import cross_correlation_shift -from quantem.tomography.object_models import ObjectModelType, ObjectVoxelwise -from quantem.tomography.tomography_dataset import TomographyDataset -from quantem.tomography.tomography_logger import LoggerTomography - - -class TomographyBase(AutoSerialize): - _token = object() - - DEFAULT_HARD_CONSTRAINTS = { - "positivity": False, - "shrinkage": False, - "circular_mask": False, - "fourier_filter": False, - } - - DEFAULT_SOFT_CONSTRAINTS = { - "tv_vol": 0.0, - } - - def __init__( - self, - dataset: TomographyDataset, - volume_obj: ObjectModelType, # ObjectDIP? - device: str = "cuda", - # ABF/HAADF property - logger: LoggerTomography | None = None, - _token: object | None = None, - ): - """Initialize a Tomography object. - - Parameters - ---------- - array : NDArray | Any - The underlying 3D array data - name : str - A descriptive name for the dataset - """ - - # if _token is not self._token: - # raise RuntimeError( - # "This class is not meant to be instantiated directly. Use the from_data method." - # ) - - self._device = device - self._dataset = dataset - self._volume_obj = volume_obj - self._loss = [] - self._mode = [] - - self._hard_constraints = self.DEFAULT_HARD_CONSTRAINTS.copy() - self._soft_constraints = self.DEFAULT_SOFT_CONSTRAINTS.copy() - - self._logger = None - - @classmethod - def from_data( - cls, - tilt_series: Dataset3d | NDArray | Tensor, - tilt_angles: NDArray | Tensor, - z1_angles: NDArray | Tensor | None = None, - z3_angles: NDArray | Tensor | None = None, - shifts: NDArray | Tensor | None = None, - volume_obj: NDArray | Dataset3d | ObjectModelType | None = None, - device: str = "cpu", - clamp: bool = True, - ): - device = device.lower() - - dataset = TomographyDataset.from_data( - tilt_series=tilt_series, - tilt_angles=tilt_angles, - z1_angles=z1_angles, - z3_angles=z3_angles, - shifts=shifts, - clamp=clamp, - ) - - dataset.to(device) - - if volume_obj is None: - max_shape = max(dataset.tilt_series.shape) - volume_obj = ObjectVoxelwise( - volume_shape=(max_shape, max_shape, max_shape), - device=device, - ) - elif isinstance(volume_obj, Dataset3d): - volume = torch.from_numpy(volume_obj.array) - volume_obj = ObjectVoxelwise( - volume_shape=volume_obj.shape, device=device, initial_volume=volume - ) - volume_obj.obj = volume - elif isinstance(volume_obj, np.ndarray): - volume = torch.from_numpy(volume_obj) - volume_obj = ObjectVoxelwise( - volume_shape=volume_obj.shape, device=device, initial_volume=volume - ) - volume_obj.obj = volume - else: - raise ValueError("volume_obj must be a Dataset3d, NDArray ObjectModelType") - - return cls( - dataset=dataset, - volume_obj=volume_obj, - device=device, - _token=cls._token, - ) - - # --- Properties --- - @property - def dataset(self) -> TomographyDataset: - """Tomography dataset.""" - - return self._dataset - - @dataset.setter - def dataset( - self, - tilt_series: Dataset3d | NDArray | TomographyDataset, - tilt_angles: NDArray | Tensor, - z1_angles: NDArray | Tensor | None = None, - z3_angles: NDArray | Tensor | None = None, - shifts: NDArray | Tensor | None = None, - # name: str | None = None, - # origin: NDArray | tuple | list | float | int | None = None, - # sampling: NDArray | tuple | list | float | int | None = None, - # units: list[str] | tuple | list | None = None, - # signal_units: str = "arb. units", - ): - """Set the tilt series dataset.""" - - if not isinstance(tilt_series, TomographyDataset): - dataset = TomographyDataset.from_array( - array=tilt_series, - tilt_angles=tilt_angles, - z1_angles=z1_angles, - z3_angles=z3_angles, - shifts=shifts, - ) - - self._dataset = dataset - - @property - def volume_obj(self) -> Dataset3d | ObjectModelType | None: - """Reconstruction volume dataset.""" - - return self._volume_obj - - @volume_obj.setter - # TODO: add support for ObjectModelType - def volume_obj(self, volume_obj: Dataset3d | NDArray): - """Set the reconstruction volume dataset.""" - if isinstance(volume_obj, ObjectModelType): - self._volume_obj = volume_obj - elif not isinstance(volume_obj, Dataset3d): - volume_obj = Dataset3d.from_array( - array=volume_obj, - # name=self._tilt_series.name, - # origin=self._tilt_series.origin, - # sampling=self._tilt_series.sampling, - # units=self._tilt_series.units, - # signal_units=self._tilt_series.signal_units, - ) - elif isinstance(volume_obj, Dataset3d): - self._volume_obj = volume_obj - else: - raise ValueError("volume_obj must be a Dataset3d or ObjectModelType") - - @property - def device(self) -> str: - """Computation device.""" - - return self._device - - @device.setter - def device(self, device: str): - """Set the computation device.""" - - # if "cuda" not in device or "gpu" not in device: - # raise NotImplementedError("Tomography not currently supported on CPU.") - - self._device = device - - @property - def loss(self) -> list: - """List of loss values during reconstruction.""" - - return self._loss - - @loss.setter - def loss(self, loss: list): - """Set the loss values during reconstruction.""" - - if not isinstance(loss, list): - raise TypeError("Loss must be a list.") - - self._loss = loss - - @property - def mode(self) -> list: - """List of modes used during reconstruction.""" - - return self._mode - - @property - def epochs(self) -> int: - """Number of epochs used during reconstruction.""" - return len(self.loss) - - @property - def logger(self) -> LoggerTomography: - return self._logger - - @logger.setter - def logger(self, logger: LoggerTomography): - if not isinstance(logger, LoggerTomography): - raise TypeError("Logger must be a LoggerTomography") - - self._logger = logger - - # --- Constraints --- - - @property - def hard_constraints(self) -> dict: - """Hard constraints for the reconstruction.""" - return self._hard_constraints - - @hard_constraints.setter - def hard_constraints(self, hard_constraints: dict): - """Set the hard constraints for the reconstruction.""" - - gkeys = self.DEFAULT_HARD_CONSTRAINTS.keys() - for key, value in hard_constraints.items(): - if key not in gkeys: - raise KeyError(f"Invalid object constraint key '{key}', allowed keys are {gkeys}") - self._hard_constraints[key] = value - - self._hard_constraints = hard_constraints - - @property - def soft_constraints(self) -> dict: - """Soft constraints for the reconstruction.""" - return self._soft_constraints - - @soft_constraints.setter - def soft_constraints(self, soft_constraints: dict): - """Set the soft constraints for the reconstruction.""" - - gkeys = self.DEFAULT_SOFT_CONSTRAINTS.keys() - for key, value in soft_constraints.items(): - if key not in gkeys: - raise KeyError(f"Invalid object constraint key '{key}', allowed keys are {gkeys}") - self._soft_constraints[key] = value - - self._soft_constraints = soft_constraints - - # --- RESET --- - - def reset_recon(self) -> None: - self.volume_obj.reset() - self.dataset.reset() - self.loss = [] - self.hard_constraints = self.DEFAULT_HARD_CONSTRAINTS.copy() - self.soft_constraints = self.DEFAULT_SOFT_CONSTRAINTS.copy() - - self._optimizers = {} - self._schedulers = {} - - # --- Preprocessing --- - - """ - TODO - 1. Implement tilt series cross-correlation alignment - 2. Background subtraction (for ABF) - 3. COM Alignment - 4. Masking - 5. Drift Correction - """ - - def cross_corr_alignment( - self, - upsample_factor: int = 1, - overwrite: bool = False, - ): - # TODO: This needs to be able to work with torch tensors. - - placeholder_tilt_series = self.dataset.tilt_series.clone().detach().cpu().numpy() - - aligned_tilt_series = np.zeros_like(placeholder_tilt_series) - aligned_tilt_series[0] = placeholder_tilt_series[0] - shifts = [] - num_imgs = placeholder_tilt_series.shape[1] - - pbar = tqdm(range(num_imgs - 1), desc="Cross-correlation alignment") - - for i in pbar: - shift, aligned_img = cross_correlation_shift( - placeholder_tilt_series[i], - placeholder_tilt_series[i + 1], - upsample_factor=upsample_factor, - return_shifted_image=True, - ) - - aligned_tilt_series[i + 1] = aligned_img - shifts.append(shift) - - if overwrite: - # TODO: Check this overwrite idea, maybe also need to save the relative shifts? - self.dataset.tilt_series = np.array(aligned_tilt_series) - - return np.array(aligned_tilt_series), np.array(shifts) - - # --- Postprocessing --- - - """ - TODO - 1. Apply circular mask - """ - - def circular_mask(self, shape, radius, center=None, dtype=torch.float32, device="cpu"): - """Generate a 2D circular mask of given shape and radius.""" - H, W = shape - - if center is None: - center = (H // 2, W // 2) - y = torch.arange(H, dtype=dtype, device=device).view(-1, 1) - x = torch.arange(W, dtype=dtype, device=device).view(1, -1) - dist_sq = (x - center[1]) ** 2 + (y - center[0]) ** 2 - return (dist_sq <= radius**2).to(dtype) - - def recon_vol_circular_mask(self, radii): - """ - Apply 2D circular masks along all three axes of a 3D volume. - - Args: - volume (torch.Tensor): 3D tensor of shape (H, W, D) - radii (tuple): (r0, r1, r2) for axes 0, 1, 2 - Returns: - masked_volume: tensor with all masks applied - """ - H, W, D = self.volume_obj.array.shape - device = self.device - dtype = torch.float32 - volume_obj = torch.tensor( - self.volume_obj.array, - device=self.device, - dtype=dtype, - ) - # Masks for each axis - mask0 = self.circular_mask((W, D), radii[0], dtype=dtype, device=device).unsqueeze( - 0 - ) # shape (1, W, D) - mask1 = self.circular_mask((H, D), radii[1], dtype=dtype, device=device).unsqueeze( - 1 - ) # shape (H, 1, D) - mask2 = self.circular_mask((H, W), radii[2], dtype=dtype, device=device).unsqueeze( - 2 - ) # shape (H, W, 1) - - # Broadcast and multiply all masks together - total_mask = mask0 * mask1 * mask2 # shape (H, W, D) - - volume_obj = volume_obj * total_mask - volume_obj = volume_obj.detach().cpu().numpy() - self.volume_obj = Dataset3d.from_array( - array=volume_obj, - # name=self.volume_obj.name, - # origin=self.volume_obj.origin, - # sampling=self.volume_obj.sampling, - # units=self.volume_obj.units, - # signal_units=self.volume_obj.signal_units, - ) - - # --- Visualizations --- - - def plot_projections( - self, - cmap: str = "turbo", - fft: bool = False, - norm: str = "log_auto", - figax: tuple[plt.Figure, plt.Axes] | None = None, - fft_vmax: Tuple[float, float] = (0, 40), - vmin: float = 0, - **kwargs, - ): - """ - Plots the projections of the volume object. - Note that the volume object is in the order of (z, y, x). - Parameters - ---------- - cmap : str - The colormap to use for the projections. - fft : bool - """ - - volume_obj_np = self.volume_obj.obj.detach().cpu().numpy() - - if figax is None: - fig, ax = plt.subplots(ncols=3, figsize=(20, 8)) - else: - fig, ax = figax - - show_2d( - volume_obj_np.sum(axis=0), - figax=(fig, ax[0]), - cmap=cmap, - title="Y-X Projection", - vmin=vmin, - ) - show_2d( - volume_obj_np.sum(axis=1), - figax=(fig, ax[1]), - cmap=cmap, - title="Z-X Projection", - vmin=vmin, - ) - show_2d( - volume_obj_np.sum(axis=2), - figax=(fig, ax[2]), - cmap=cmap, - title="Z-Y Projection", - vmin=vmin, - ) - - if fft: - fig, ax = plt.subplots(ncols=3, figsize=(25, 8)) - print(fft_vmax) - show_2d( - np.abs(np.fft.fftshift(np.fft.fftn(volume_obj_np.sum(axis=0)))), - figax=(fig, ax[0]), - cmap=cmap, - title="Y-X Projection FFT", - # norm=norm, - vmin=fft_vmax[0], - vmax=fft_vmax[1], - ) - - show_2d( - np.abs(np.fft.fftshift(np.fft.fftn(volume_obj_np.sum(axis=1)))), - figax=(fig, ax[1]), - cmap=cmap, - title="Z-X Projection FFT", - vmin=fft_vmax[0], - vmax=fft_vmax[1], - # norm=norm, - ) - show_2d( - np.abs(np.fft.fftshift(np.fft.fftn(volume_obj_np.sum(axis=2)))), - figax=(fig, ax[2]), - cmap=cmap, - title="Z-Y Projection FFT", - vmin=fft_vmax[0], - vmax=fft_vmax[1], - # norm=norm, - ) - - def plot_slice( - self, - cmap="turbo", - slice_index: int = 0, - vmin: float = 0, - figax: tuple[plt.Figure, plt.Axes] | None = None, - ): - if figax is None: - fig, ax = plt.subplots(figsize=(15, 8), ncols=3) - else: - fig, ax = figax - - show_2d( - self.volume_obj.obj[slice_index, :, :], - figax=(fig, ax[0]), - cmap=cmap, - vmin=vmin, - ) - show_2d( - self.volume_obj.obj[:, slice_index, :], - figax=(fig, ax[1]), - cmap=cmap, - vmin=vmin, - ) - - show_2d( - self.volume_obj.obj[:, :, slice_index], - figax=(fig, ax[2]), - cmap=cmap, - vmin=vmin, - ) - - def plot_loss( - self, - figsize: tuple = (8, 4), - figax: tuple[plt.Figure, plt.Axes] | None = None, - ): - if figax is None: - fig, ax = plt.subplots(figsize=figsize) - else: - fig, ax = figax - - ax.semilogy( - self.loss, - label="Loss", - ) diff --git a/src/quantem/tomography_old/tomography_conv.py b/src/quantem/tomography_old/tomography_conv.py deleted file mode 100644 index 1056da17..00000000 --- a/src/quantem/tomography_old/tomography_conv.py +++ /dev/null @@ -1,235 +0,0 @@ -import numpy as np -import torch - -# from torch_radon.radon import ParallelBeam as Radon -from quantem.tomography.radon.radon import iradon_torch, radon_torch -from quantem.tomography.tomography_base import TomographyBase -from quantem.tomography.utils import gaussian_filter_2d_stack, torch_phase_cross_correlation - - -class TomographyConv(TomographyBase): - """ - Class for handling conventional reconstruction methods of tomography data. - """ - - # --- Reconstruction Methods --- - - def _sirt_run_epoch( - self, - tilt_series: torch.Tensor, - proj_forward: torch.Tensor, - angles: torch.Tensor, - inline_alignment: bool, - filter_name: str, - circle: bool, - gaussian_kernel: torch.Tensor | None, - ): - loss = 0 - - if inline_alignment: - for ind in range(len(self.dataset.tilt_angles)): - im_proj = proj_forward[ind] - im_meas = tilt_series[ind] - - shift = torch_phase_cross_correlation(im_proj, im_meas) - if torch.linalg.norm(shift) <= 32: - shifted = torch.fft.ifft2( - torch.fft.fft2(im_meas) - * torch.exp( - -2j - * np.pi - * ( - shift[0] - * torch.fft.fftfreq( - im_meas.shape[0], device=im_meas.device - ).unsqueeze(1) - + shift[1] - * torch.fft.fftfreq(im_meas.shape[1], device=im_meas.device) - ) - ) - ).real - - proj_forward[ind] = shifted - - # Forward projection - - sinogram_est = radon_torch(self.volume_obj.obj, theta=angles, device=self.device) - # proj_forward = sinogram_est.permute(1, 2, 0) - # error = (tilt_series - proj_forward).permute(2, 0, 1) - proj_forward = sinogram_est - error = tilt_series - proj_forward - - correction = iradon_torch( - error, theta=angles, device=self.device, filter_name=filter_name, circle=circle - ) - - normalization = iradon_torch( - torch.ones_like(error), - theta=angles, - device=self.device, - filter_name=None, - circle=circle, - ) - - normalization[normalization == 0] = 1e-6 - - correction /= normalization - - self.volume_obj._obj += correction - - loss = torch.mean(torch.abs(error)) - - # for z in tqdm(range(self.volume_obj.obj.shape[0]), desc="SIRT Reconstruction"): - # slice_estimate = self.volume_obj.obj[z] - # sinogram_est = radon_torch(slice_estimate, theta=angles, device=self.device) - - # sinogram_true = tilt_series[:, :, z] - - # error = sinogram_true - sinogram_est - - # correction = iradon_torch( - # error, theta=angles, device=self.device, filter_name=filter_name, circle=circle - # ) - - # # I'm pretty sure this implementation of normalization is wrong - # normalization = iradon_torch( - # torch.ones_like(error), - # theta=angles, - # device=self.device, - # filter_name=None, - # circle=circle, - # ) - # normalization[normalization == 0] = 1e-6 - - # correction /= normalization - - # self.volume_obj._obj[z] += correction - - # proj_forward[:, :, z] = sinogram_est - - # loss += torch.mean(torch.abs(error)) - - # loss /= self.volume_obj._obj.shape[0] - - if gaussian_kernel is not None: - self.volume_obj.obj = gaussian_filter_2d_stack(self.volume_obj.obj, gaussian_kernel) - - return proj_forward, loss - - # Deprecated torch_radon implementations - # def _sirt_run_epoch( - # self, - # radon: Radon, - # stack_recon: torch.Tensor, - # stack_torch: torch.Tensor, - # proj_forward: torch.Tensor, - # step_size: float = 0.25, - # gaussian_kernel: torch.Tensor = None, - # inline_alignment=True, - # enforce_positivity=True, - # shrinkage: float = None, - # ): - # loss = 0 - - # if inline_alignment: - # for ind in range(len(self.tilt_series.tilt_angles)): - # im_proj = proj_forward[:, ind, :] - # im_meas = stack_torch[:, ind, :] - - # shift = torch_phase_cross_correlation(im_proj, im_meas) - # if torch.linalg.norm(shift) <= 32: - # shifted = torch.fft.ifft2( - # torch.fft.fft2(im_meas) - # * torch.exp( - # -2j - # * np.pi - # * ( - # shift[0] - # * torch.fft.fftfreq( - # im_meas.shape[0], device=im_meas.device - # ).unsqueeze(1) - # + shift[1] - # * torch.fft.fftfreq(im_meas.shape[1], device=im_meas.device) - # ) - # ) - # ).real - - # stack_torch[:, ind, :] = shifted - - # proj_forward = radon.forward(stack_recon) - - # proj_diff = stack_torch - proj_forward - - # loss = torch.mean(torch.abs(proj_diff)) - - # recon_slice_update = radon.backward( - # radon.filter_sinogram( - # proj_diff, - # ) - # ) - - # stack_recon += step_size * recon_slice_update - # if enforce_positivity: - # stack_recon = torch.clamp(stack_recon, min=0) - - # if gaussian_kernel is not None: - # stack_recon = gaussian_filter_2d_stack( - # stack_recon, - # gaussian_kernel, - # ) - - # if shrinkage is not None: - # stack_recon = torch.max( - # stack_recon - shrinkage, - # torch.zeros_like(stack_recon), - # ) - - # return stack_recon, loss - - # def _sirt_serial_run_epoch( - # self, - # radon: Radon, - # stack_recon: torch.Tensor, - # stack_torch: torch.Tensor, - # proj_forward: torch.Tensor, - # step_size: float = 0.25, - # gaussian_kernel: torch.Tensor = None, - # inline_alignment=True, - # enforce_positivity=True, - # ): - # recon_slice_update = torch.zeros_like(stack_recon).to(self.device) - - # loss = 0 - - # for i in range(stack_recon.shape[0]): - # proj_forward[i] = radon.forward(stack_recon[i]) - - # proj_diff = stack_torch - proj_forward - - # loss = torch.mean(torch.abs(proj_diff)) - - # for i in range(stack_recon.shape[0]): - # recon_slice_update[i] = radon.backward( - # radon.filter_sinogram( - # proj_diff[i], - # ) - # ) - - # stack_recon += step_size * recon_slice_update - - # if enforce_positivity: - # stack_recon = torch.clamp(stack_recon, min=0) - - # return stack_recon, loss - - # --- Properties --- - # @property - # def reconstruction_method(self) -> str: - # """Get the reconstruction method.""" - # return self._reconstruction_method - # @reconstruction_method.setter - # def reconstruction_method(self, value: str): - # """Set the reconstruction method.""" - # if value not in ["SIRT", "FBP"]: - # raise ValueError("Invalid reconstruction method. Choose 'SIRT' or 'FBP'.") - # self._reconstruction_method = value diff --git a/src/quantem/tomography_old/tomography_dataset.py b/src/quantem/tomography_old/tomography_dataset.py deleted file mode 100644 index c66f1d9d..00000000 --- a/src/quantem/tomography_old/tomography_dataset.py +++ /dev/null @@ -1,468 +0,0 @@ -from pathlib import Path - -import numpy as np -import torch -from numpy.typing import NDArray -from torch._tensor import Tensor -from torch.utils.data import Dataset - -from quantem.core.datastructures.dataset3d import Dataset3d -from quantem.core.io.serialize import AutoSerialize -from quantem.core.utils.validators import ( - validate_array, - validate_tensor, -) - - -class AuxiliaryParams(torch.nn.Module): - def __init__(self, num_tilts, device, zero_tilt_idx=None, learn_shifts: bool = True): - super().__init__() - - self.learn_shifts = learn_shifts - - if zero_tilt_idx is None: - # If not provided, assume first projection is reference - zero_tilt_idx = 0 - - self.zero_tilt_idx = zero_tilt_idx - self.num_tilts = num_tilts - - # Shifts: only parameterize non-reference tilts - num_param_tilts = num_tilts - 1 - self.shifts_param = torch.nn.Parameter( - torch.zeros(num_param_tilts, 2, device=device), requires_grad=learn_shifts - ) - - # Fixed zero shifts for reference - self.shifts_ref = torch.zeros(1, 2, device=device) - - # Z1 and Z3: parameterize all tilts EXCEPT the reference - self.z1_param = torch.nn.Parameter(torch.zeros(num_param_tilts, device=device)) - self.z3_param = torch.nn.Parameter(torch.zeros(num_param_tilts, device=device)) - - # Fixed zeros for reference tilt - self.z1_ref = torch.zeros(1, device=device) - self.z3_ref = torch.zeros(1, device=device) - - def forward(self, dummy_input=None): - # Reconstruct full arrays with zeros at reference position - before_shifts = self.shifts_param[: self.zero_tilt_idx] - after_shifts = self.shifts_param[self.zero_tilt_idx :] - shifts = torch.cat([before_shifts, self.shifts_ref, after_shifts], dim=0) - - before_z1 = self.z1_param[: self.zero_tilt_idx] - after_z1 = self.z1_param[self.zero_tilt_idx :] - z1 = torch.cat([before_z1, self.z1_ref, after_z1], dim=0) - - before_z3 = self.z3_param[: self.zero_tilt_idx] - after_z3 = self.z3_param[self.zero_tilt_idx :] - z3 = torch.cat([before_z3, self.z3_ref, after_z3], dim=0) - - return shifts, z1, z3 - - -class TomographyDataset(AutoSerialize, Dataset): - _token = object() - - """ - A tomography dataset which contains the tilt series, and also instatiates the - z1, z3, and shifts of the tilt series. - - Idea for this dataset is so that we can avoid moving things around as a torch tensor, - since the SIRT reconstruction algorthim, and AD reconstruction we have are all torch based. - """ - - def __init__( - self, - tilt_series: Tensor, - tilt_angles: Tensor, - z1_angles: Tensor, - z3_angles: Tensor, - shifts: Tensor, - clamp: bool, - ): - self._tilt_series = tilt_series - # Enforce Positivity - - self._tilt_angles = tilt_angles - self._z1_angles = z1_angles - self._z3_angles = z3_angles - self._shifts = shifts - - self._initial_tilt_angles = tilt_angles.clone() - self._initial_z1_angles = z1_angles.clone() - self._initial_z3_angles = z3_angles.clone() - self._initial_shifts = shifts.clone() - - # Move everything to CPU - self.to("cpu") - - # Number of indices (?) - - # Enforce normalization of tilt series - try: - tilt_percentile = torch.quantile(self._tilt_series, 0.95) - except (AttributeError, RuntimeError): - tilt_percentile = np.quantile(self._tilt_series, 0.95) - - self._tilt_series = self._tilt_series / tilt_percentile - if clamp: - self._tilt_series = torch.clamp(self._tilt_series, min=0) - - # --- Class Methods --- - @classmethod - def from_data( - cls, - tilt_series: Dataset3d | NDArray | Tensor, - tilt_angles: NDArray | Tensor, - z1_angles: NDArray | Tensor | None = None, - z3_angles: NDArray | Tensor | None = None, - shifts: NDArray | Tensor | None = None, - clamp: bool = True, - ): - """ - tilt_series: (N, H, W) - tilt_angles: (N,) - In units of degrees, the alpha tilt angle. - z1_angles: (N,) - In units of degrees, beta tilt angle. - z3_angles: (N,) - In units of degrees, negative beta tilt angle. - shifts: (N, 2) - - - The convention we use for projecting down is ZXZ Euler Angles. - - In theory, Z1 and Z3 should be the same value, except Z3 would be the - negative value of Z1. However, in some cases this is not the case and - there could be some merit in also optimizing both angles. However, the - downside is the rotation becomes less interpretable. - - The tilt angle can also be optimized. - """ - validated_tilt_series = torch.tensor(validate_array(tilt_series, "tilt_series")) - validated_tilt_angles = torch.tensor(validate_array(tilt_angles, "tilt_angles")) - - if z1_angles is not None: - validated_z1_angles = torch.tensor(validate_array(z1_angles, "z1_angles")) - else: - validated_z1_angles = torch.zeros(len(validated_tilt_angles)) - - if z3_angles is not None: - validated_z3_angles = torch.tensor(validate_array(z3_angles, "z3_angles")) - else: - validated_z3_angles = torch.zeros(len(validated_tilt_angles)) - - if shifts is not None: - validated_shifts = torch.tensor(validate_array(shifts, "shifts")) - else: - validated_shifts = torch.zeros(len(validated_tilt_angles), 2) - - return cls( - tilt_series=validated_tilt_series, - tilt_angles=validated_tilt_angles, - z1_angles=validated_z1_angles, - z3_angles=validated_z3_angles, - shifts=validated_shifts, - clamp=clamp, - # name=name, - # origin=origin, - # sampling=sampling, - # units=units, - # signal_units=signal_units, - ) - - def to(self, device: str): - self._tilt_series = self._tilt_series.to(device) - self._tilt_angles = self._tilt_angles.to(device) - self._z1_angles = self._z1_angles.to(device) - self._z3_angles = self._z3_angles.to(device) - self._shifts = self._shifts.to(device) - - # --- Properties --- - - @property - def tilt_series(self) -> Tensor: - return self._tilt_series - - @property - def tilt_angles(self) -> Tensor: - return self._tilt_angles - - @property - def z1_angles(self) -> Tensor: - return self._z1_angles - - @property - def z3_angles(self) -> Tensor: - return self._z3_angles - - @property - def shifts(self) -> Tensor: - return self._shifts - - @property - def initial_tilt_angles(self) -> Tensor: - return self._initial_tilt_angles - - @property - def initial_z1_angles(self) -> Tensor: - return self._initial_z1_angles - - @property - def initial_z3_angles(self) -> Tensor: - return self._initial_z3_angles - - @property - def initial_shifts(self) -> Tensor: - return self._initial_shifts - - @property - def num_projections(self) -> int: - return self._tilt_series.shape[0] - - @property - def num_pixels(self) -> int: - return self._tilt_series.shape[0] * self._tilt_series.shape[1] * self._tilt_series.shape[2] - - @property - def dims(self) -> tuple[int, int, int]: - return self._tilt_series.shape[0], self._tilt_series.shape[1], self._tilt_series.shape[2] - - def __len__(self) -> int: - return self.num_pixels - - def __getitem__(self, idx): - actual_idx = idx - - projection_idx = actual_idx // (self.dims[1] * self.dims[2]) - remaining = actual_idx % (self.dims[1] * self.dims[2]) - - # TODO: What if non-square tilt images? - if self.dims[1] != self.dims[2]: - raise NotImplementedError("Non-square tilt images are not supported yet.") - - # TODO: row, column - pixel_i = remaining // self.dims[1] - pixel_j = remaining % self.dims[1] - - target_value = self._tilt_series[projection_idx, pixel_i, pixel_j] - phi = self._tilt_angles[projection_idx] - - return { - "projection_idx": projection_idx, - "pixel_i": pixel_i, - "pixel_j": pixel_j, - "target_value": target_value, - "phi": phi, - } - - # --- Setters --- - - @tilt_series.setter - def tilt_series(self, tilt_series: NDArray | Tensor | Dataset3d) -> None: - if isinstance(tilt_series, Dataset3d): - validated_tilt_series = torch.tensor(tilt_series.array) - elif isinstance(tilt_series, Tensor): - validated_tilt_series = validate_tensor(tilt_series, "tilt_series") - else: - validated_tilt_series = torch.tensor(validate_array(tilt_series, "tilt_series")) - - self._tilt_series = validated_tilt_series - - @tilt_angles.setter - def tilt_angles(self, tilt_angles: NDArray | Tensor) -> None: - if tilt_angles.shape[0] != self.tilt_series.shape[0]: - raise ValueError("Tilt angles must match the number of projections.") - - validated_tilt_angles = torch.tensor(validate_array(tilt_angles, "tilt_angles")) - self._tilt_angles = validated_tilt_angles - - @z1_angles.setter - def z1_angles(self, z1_angles: NDArray | Tensor) -> None: - if z1_angles.shape[0] != self.tilt_series.shape[0]: - raise ValueError("Z1 angles must match the number of projections.") - - if isinstance(z1_angles, Tensor): - validated_z1_angles = validate_tensor(z1_angles, "z1_angles") - else: - validated_z1_angles = torch.tensor(validate_array(z1_angles, "z1_angles")) - self._z1_angles = validated_z1_angles - - @z3_angles.setter - def z3_angles(self, z3_angles: NDArray | Tensor) -> None: - if z3_angles.shape[0] != self.tilt_series.shape[0]: - raise ValueError("Z3 angles must match the number of projections.") - - if isinstance(z3_angles, Tensor): - validated_z3_angles = validate_tensor(z3_angles, "z3_angles") - else: - validated_z3_angles = torch.tensor(validate_array(z3_angles, "z3_angles")) - self._z3_angles = validated_z3_angles - - @shifts.setter - def shifts(self, shifts: NDArray | Tensor) -> None: - if shifts.shape[0] != self.tilt_series.shape[0]: - raise ValueError("Shifts must match the number of projections.") - - if isinstance(shifts, Tensor): - validated_shifts = validate_tensor(shifts, "shifts") - else: - validated_shifts = torch.tensor(validate_array(shifts, "shifts")) - - self._shifts = validated_shifts - - @initial_tilt_angles.setter - def initial_tilt_angles(self, tilt_angles: NDArray | Tensor) -> None: - self._initial_tilt_angles = tilt_angles - - @initial_z1_angles.setter - def initial_z1_angles(self, z1_angles: NDArray | Tensor) -> None: - self._initial_z1_angles = z1_angles - - @initial_z3_angles.setter - def initial_z3_angles(self, z3_angles: NDArray | Tensor) -> None: - self._initial_z3_angles = z3_angles - - @initial_shifts.setter - def initial_shifts(self, shifts: NDArray | Tensor) -> None: - self._initial_shifts = shifts - - # TODO: Temp auxiliary params - - def setup_auxiliary_params( - self, zero_tilt_idx: int = None, device: str = "cpu", learn_shifts: bool = True - ) -> None: - if not hasattr(self, "_auxiliary_params"): - self._auxiliary_params = AuxiliaryParams( - num_tilts=len(self.tilt_angles), - device=device, - zero_tilt_idx=zero_tilt_idx, - learn_shifts=learn_shifts, - ) - - else: - print("Auxiliary params already set") - - @property - def auxiliary_params(self) -> AuxiliaryParams: - return self._auxiliary_params - - # --- RESET --- - - def reset(self) -> None: - self._tilt_angles = self._initial_tilt_angles.clone() - self._z1_angles = self._initial_z1_angles.clone() - self._z3_angles = self._initial_z3_angles.clone() - self._shifts = self._initial_shifts.clone() - - -# TODO: Temporary for use when only doing validation -class TomographyRayDataset(Dataset): - """ - Dataset for ray-based tomography training. - Each sample contains: target pixel value, pixel coordinates, phi angle, and auxiliary info. - """ - - def __init__(self, tilt_series, phis, N, val_ratio=0.0, mode="train", seed=42): - """ - Args: - tilt_series: [M, N, N] tensor of projections - phis: [M] tensor of tilt angles - N: Grid size - """ - self.tilt_series = tilt_series.cpu() - - self.tilt_series = torch.clamp(self.tilt_series, min=0.0) - - # tilt_percentile = torch.quantile(self.tilt_series, .95) - tilt_percentile = np.quantile(self.tilt_series.numpy(), 0.95) - - self.tilt_series = self.tilt_series / tilt_percentile - - self.phis = phis.cpu() - self.N = N - self.M = tilt_series.shape[0] - self.total_pixels = self.M * self.N * self.N - - # Create train/val split - torch.manual_seed(seed) - all_indices = torch.randperm(self.total_pixels) - - num_val = int(self.total_pixels * val_ratio) - if mode == "val": - self.indices = all_indices[:num_val] - else: # train - self.indices = all_indices[num_val:] - - self.mode = mode - - def __len__(self): - return len(self.indices) - - def __getitem__(self, idx): - actual_idx = self.indices[idx].item() - - projection_idx = actual_idx // (self.N * self.N) - remaining = actual_idx % (self.N * self.N) - pixel_i = remaining // self.N - pixel_j = remaining % self.N - - target_value = self.tilt_series[projection_idx, pixel_i, pixel_j] - phi = self.phis[projection_idx] - - return { - "projection_idx": projection_idx, - "pixel_i": pixel_i, - "pixel_j": pixel_j, - "target_value": target_value, - "phi": phi, - } - - -# Pretrain Volume Dataset -class PretrainVolumeDataset(Dataset): - def __init__( - self, - volume_path: Path | str, - N: int, - ): - self._N = N - if str(volume_path).endswith(".npy"): - data = torch.from_numpy(np.load(volume_path)).float() - elif str(volume_path).endswith(".pt"): - data = torch.load(volume_path).float() - else: - raise ValueError(f"Unsupported file format: {volume_path}") - - # Normalize data - total_elements = data.numel() - if total_elements > 1e6: - sample_size = min(int(1e6), total_elements) - flat_data = data.flatten() - indices = torch.randperm(total_elements)[:sample_size] - sampled_data = flat_data[indices] - data_quantile = torch.quantile(sampled_data, 0.95) - else: - data_quantile = torch.quantile(data, 0.95) - - data = data / data_quantile - data = torch.permute(data, (2, 1, 0)) - # data = torch.flip(data, dims=(2,)) - - self.volume = data.cpu() - self.N = N - self.total_samples = N**3 - - coords_1d = torch.linspace(-1, 1, N) - x, y, z = torch.meshgrid(coords_1d, coords_1d, coords_1d, indexing="ij") - self.coords = torch.stack([x, y, z], dim=-1).reshape(-1, 3).cpu() - self.targets = self.volume.reshape(-1).cpu() - - def __len__(self): - return self.total_samples - - def __getitem__(self, idx): - return {"coords": self.coords[idx], "target": self.targets[idx]} - - @property - def N(self): - return self._N - - @N.setter - def N(self, N: int): - self._N = N diff --git a/src/quantem/tomography_old/tomography_ddp.py b/src/quantem/tomography_old/tomography_ddp.py deleted file mode 100644 index cfb59117..00000000 --- a/src/quantem/tomography_old/tomography_ddp.py +++ /dev/null @@ -1,321 +0,0 @@ -import os - -import numpy as np -import torch -import torch.distributed as dist -import torch.nn as nn -from torch.utils.data import DataLoader, DistributedSampler - -from quantem.tomography.tomography_dataset import ( - PretrainVolumeDataset, - TomographyDataset, - TomographyRayDataset, -) - - -class TomographyDDP: - """ - Initializing DDP stuff for tomo class. - """ - - def __init__( - self, - ): - self.setup_distributed() - - def setup_distributed(self): - # Check if in distributed env - if "RANK" in os.environ: - # Distributed training - if not dist.is_initialized(): - dist.init_process_group( - backend="nccl" if torch.cuda.is_available() else "gloo", init_method="env://" - ) - - self.world_size = dist.get_world_size() - self.global_rank = dist.get_rank() - self.local_rank = int(os.environ["LOCAL_RANK"]) - - torch.cuda.set_device(self.local_rank) - self.device = torch.device("cuda", self.local_rank) - - else: - # Single GPU/CPU training - self.world_size = 1 - self.global_rank = 0 - self.local_rank = 0 - - if torch.cuda.is_available(): - self.device = torch.device("cuda") - torch.cuda.set_device(0) - print("Single GPU training") - else: - self.device = torch.device("cpu") - print("CPU training") - - # Optional performance optimizations (only for CUDA) - if self.device.type == "cuda": - torch.backends.cudnn.benchmark = True - torch.backends.cuda.matmul.allow_tf32 = True - torch.backends.cudnn.allow_tf32 = True - - def build_model( - self, - model: nn.Module, - ): - # TODO: Generalized model --> Should be instantiated in the object? Where does `HSIREN` get instantiated? - model = model.to(self.device) - - if self.world_size > 1: - model = torch.nn.parallel.DistributedDataParallel( - model, - device_ids=[self.local_rank], - output_device=self.local_rank, - find_unused_parameters=False, - broadcast_buffers=True, - bucket_cap_mb=100, - gradient_as_bucket_view=True, - ) - - if self.global_rank == 0: - print("Model wrapped with DDP") - - if self.world_size > 1: - if self.global_rank == 0: - print("Model built, distributed, and compiled successfully") - - else: - print("Model built, compiled successfully") - - return model - - # Setup Tomo DataLoader - def setup_dataloader( - self, - tomo_dataset: TomographyDataset, - batch_size: int, - num_workers: int = 0, - val_fraction: float = 0.0, - ): - pin_mem = self.device.type == "cuda" - persist = num_workers > 0 - - # Split dataset if validation fraction > 0 - if val_fraction > 0.0: - # TODO: Temporary for when only doing validation, current TomographyDataset doesn't work correctly - train_dataset = TomographyRayDataset( - tomo_dataset.tilt_series.detach().clone(), - tomo_dataset.tilt_angles.detach().clone(), - 500, # TODO: TEMPORARY - val_ratio=val_fraction, - mode="train", - seed=42, - ) - val_dataset = TomographyRayDataset( - tomo_dataset.tilt_series.detach().clone(), - tomo_dataset.tilt_angles.detach().clone(), - 500, # TODO: TEMPORARY - val_ratio=val_fraction, - mode="val", - seed=42, - ) - else: - train_dataset, val_dataset = tomo_dataset, None - - # Samplers for distributed training - if self.world_size > 1: - train_sampler = DistributedSampler( - train_dataset, - num_replicas=self.world_size, - rank=self.global_rank, - shuffle=True, - ) - if val_dataset: - val_sampler = DistributedSampler( - val_dataset, - num_replicas=self.world_size, - rank=self.global_rank, - shuffle=False, - ) - shuffle = False - else: - train_sampler = None - val_sampler = None - shuffle = True - - # Main dataloader - self.dataloader = DataLoader( - train_dataset, - batch_size=batch_size, - num_workers=num_workers, - sampler=train_sampler, - shuffle=shuffle, - pin_memory=pin_mem, - drop_last=True, - persistent_workers=persist, - ) - - # Validation dataloader if applicable - if val_dataset: - self.val_dataloader = DataLoader( - val_dataset, - batch_size=batch_size * 4, - num_workers=num_workers, - sampler=val_sampler, - shuffle=False, - pin_memory=pin_mem, - drop_last=False, - persistent_workers=persist, - ) - self.val_sampler = val_sampler - - self.sampler = train_sampler - - if self.global_rank == 0: - print("Dataloader setup complete:") - print(f" Total projections: {len(tomo_dataset.tilt_angles)}") - if val_fraction > 0.0: - print(f" Total projections (val): {len(val_dataset)}") - print(f" Grid size: {tomo_dataset.dims[1]}{tomo_dataset.dims[2]}") - print(f" Total pixels: {tomo_dataset.num_pixels:,}") - if val_fraction > 0.0: - print(f" Total pixels (val): {len(val_dataset):,}") - print(f" Total pixels (train): {len(train_dataset):,}") - print(f" Local batch size (train): {batch_size}") - print(f" Global batch size: {batch_size * self.world_size}") - print(f" Train batches per GPU per epoch: {len(self.dataloader)}") - - # Setup pretraining dataloader - - def setup_pretraining_dataloader( - self, - volume_dataset: PretrainVolumeDataset, - batch_size: int, - ): - # TODO: temp - - num_workers = 0 - if self.world_size > 1: - sampler = DistributedSampler( - volume_dataset, - num_replicas=self.world_size, - rank=self.global_rank, - shuffle=True, - drop_last=True, - ) - shuffle = False - else: - sampler = None - shuffle = True - - self.pretraining_dataloader = DataLoader( - volume_dataset, - batch_size=batch_size, - sampler=sampler, - shuffle=shuffle, - pin_memory=self.device.type == "cuda", - drop_last=True, - persistent_workers=num_workers > 0, - ) - - self.pretraining_sampler = sampler - - if self.global_rank == 0: - print("Pretraining dataloader setup complete:") - print(f" Total samples: {len(volume_dataset)}") - print(f" Grid size: {volume_dataset.N**3}") - print(f" Local batch size: {batch_size}") - print(f" Global batch size: {batch_size * self.world_size}") - print(f" Pretraining batches per GPU per epoch: {len(self.pretraining_dataloader)}") - - def get_scaled_lr(self, base_lr, scaling_rule="sqrt"): - if scaling_rule == "sqrt": - return base_lr * np.sqrt(self.world_size) - elif scaling_rule == "linear": - return base_lr * self.world_size - else: - raise ValueError(f"Invalid scaling rule: {scaling_rule}") - - def scale_lr( - self, - optimizer_params: dict, - ): - new_optimizer_params = {} - for key, value in optimizer_params.items(): - if "original_lr" in value: - new_optimizer_params[key] = { - "type": value["type"], - "lr": self.get_scaled_lr(value["original_lr"]), - "original_lr": value["original_lr"], - } - else: - new_optimizer_params[key] = { - "type": value["type"], - "lr": self.get_scaled_lr(value["lr"]), - "original_lr": value["lr"], - } - - return new_optimizer_params - - # TODO: Temporary Adaptive L1 Smooth Loss - - -class AdaptiveSmoothL1Loss(nn.Module): - def __init__(self, beta_init=None, ema_factor=0.99, eps=1e-8): - """ - Adaptive smooth L1 loss with EMA-based beta adaptation. - - Args: - beta_init (float): optional initial β value; if None, starts as 1.0 - ema_factor (float): smoothing factor for EMA (1 - 1/N_b in Eq. 38) - eps (float): small constant for numerical stability - """ - super().__init__() - self.register_buffer("beta2", torch.tensor(beta_init**2 if beta_init else 1.0)) - self.ema_factor = ema_factor - self.eps = eps - - def forward(self, pred, target): - diff = pred - target - abs_diff = diff.abs() - - # compute current batch MSE (Eq. 38) - mse_batch = torch.mean(diff**2) - - # update β² adaptively using Eq. (39) - with torch.no_grad(): - self.beta2 = self.ema_factor * self.beta2 + (1 - self.ema_factor) * torch.min( - self.beta2, mse_batch - ) - - beta = torch.sqrt(self.beta2 + self.eps) - - # Smooth L1 (Eq. 36) - loss = torch.where( - abs_diff < beta, 0.5 * (diff**2) / (beta + self.eps), abs_diff - 0.5 * beta - ) - return loss.mean() - - -class AdaptiveSmoothL1LossDDP(AdaptiveSmoothL1Loss): - def forward(self, pred, target): - diff = pred - target - abs_diff = diff.abs() - mse_batch = torch.mean(diff**2) - - # Synchronize β across all GPUs - if dist.is_initialized(): - mse_batch_all = mse_batch.clone() - dist.all_reduce(mse_batch_all, op=dist.ReduceOp.AVG) - mse_batch = mse_batch_all / dist.get_world_size() - - with torch.no_grad(): - self.beta2 = self.ema_factor * self.beta2 + (1 - self.ema_factor) * torch.min( - self.beta2, mse_batch - ) - - beta = torch.sqrt(self.beta2 + self.eps) - loss = torch.where( - abs_diff < beta, 0.5 * (diff**2) / (beta + self.eps), abs_diff - 0.5 * beta - ) - return loss.mean() diff --git a/src/quantem/tomography_old/tomography_logger.py b/src/quantem/tomography_old/tomography_logger.py deleted file mode 100644 index 8156837b..00000000 --- a/src/quantem/tomography_old/tomography_logger.py +++ /dev/null @@ -1,68 +0,0 @@ -import matplotlib.pyplot as plt - -from quantem.core.ml.logger import LoggerBase -from quantem.tomography.object_models import ObjectModelType -from quantem.tomography.tomography_dataset import TomographyDataset - - -class LoggerTomography(LoggerBase): - def __init__(self, log_dir: str, run_prefix: str, run_suffix: str = None): - super().__init__(log_dir, run_prefix, run_suffix) - - # --- Tomography focused logging methods --- - - def tilt_angles_figure(self, dataset: TomographyDataset, step: int): - figs = [] - for angle_array, title in zip( - [dataset.z1_angles, dataset.tilt_angles, dataset.z3_angles], - ["Z1 Angles", "Tilt/ X Angles", "Z3 Angles"], - ): - fig, ax = plt.subplots(figsize=(5, 5)) - ax.plot(angle_array.detach().cpu().numpy()) - ax.set_title(title) - ax.set_xlabel("Index") - ax.set_ylabel("Angle") - figs.append(fig) - plt.close(fig) - - self.log_figure( - tag="tilt_angles/z1", - fig=figs[0], - step=step, - ) - self.log_figure( - tag="tilt_angles/x", - fig=figs[1], - step=step, - ) - self.log_figure( - tag="tilt_angles/z3", - fig=figs[2], - step=step, - ) - - def projection_images( - self, volume_obj: ObjectModelType, epoch: int, logger_cmap: str = "turbo" - ): - sum_0 = volume_obj.obj.sum(axis=0) - sum_1 = volume_obj.obj.sum(axis=1) - sum_2 = volume_obj.obj.sum(axis=2) - - self.log_image( - tag="projections/Y-X Projection", - image=sum_0, - step=epoch, - cmap=logger_cmap, - ) - self.log_image( - tag="projections/Z-X Projection", - image=sum_1, - step=epoch, - cmap=logger_cmap, - ) - self.log_image( - tag="projections/Z-Y Projection", - image=sum_2, - step=epoch, - cmap=logger_cmap, - ) diff --git a/src/quantem/tomography_old/tomography_ml.py b/src/quantem/tomography_old/tomography_ml.py deleted file mode 100644 index 5b130e7f..00000000 --- a/src/quantem/tomography_old/tomography_ml.py +++ /dev/null @@ -1,338 +0,0 @@ -from typing import Any, Generator, Iterator, Sequence - -import torch - -from quantem.tomography.tomography_base import TomographyBase - - -class TomographyML(TomographyBase): - """ - Class for handling conventional reconstruction methods of tomography data. - """ - - OPTIMIZABLE_VALS = ["volume", "z1", "x", "z3", "shifts", "model", "aux_params"] - DEFAULT_LRS = { - "volume": 1e-2, - "z1": 1e-1, - "x": 1e-1, - "z3": 1e-1, - "shifts": 1e-1, - "tv_weight_vol": 0, - "tv_weight_z1": 0, - "tv_weight_x": 0, - "tv_weight_z3": 0, - } - DEFAULT_OPTIMIZER_TYPE = "adam" - - # --- Properties --- - - @property - def optimizer_params(self) -> dict[str, dict]: - """Returns the parameters used to set the optimizers.""" - return self._optimizer_params - - @optimizer_params.setter - def optimizer_params(self, d: dict) -> None: - """ - # Takes a dictionary {key: torch.optim.Adam(params=[blah], lr=[blah]), ...} - Takes a dictionary: - { - "key1": { - "type": "adam", - "lr": 0.001, - }, - "key2": { - ... - }, - ... - } - """ - # resets _optimizers as well - self._optimizers = {} - self._optimizer_params = {} - if isinstance(d, (tuple, list)): - d = {k: {} for k in d} - - for k, v in d.items(): - if k not in self.OPTIMIZABLE_VALS: - raise ValueError( - f"key to be optimized, {k}, not in allowed keys: {self.OPTIMIZABLE_VALS}" - ) - if "type" not in v.keys(): - v["type"] = self.DEFAULT_OPTIMIZER_TYPE - if "lr" not in v.keys(): - v["lr"] = self.DEFAULT_LRS[k] - self._optimizer_params[k] = v - - @property - def optimizers(self) -> dict[str, torch.optim.Adam | torch.optim.AdamW]: - return self._optimizers - - def set_optimizers(self): - """Reset all optimizers and set them according to the optimizer_params.""" - for key, _ in self._optimizer_params.items(): - if key == "volume": - self._add_optimizer(key, self.volume_obj.params, self._optimizer_params[key]) - elif key == "shifts": - self._add_optimizer(key, self.dataset.shifts, self._optimizer_params[key]) - elif key == "z1": - self._add_optimizer(key, self.dataset.z1_angles, self._optimizer_params[key]) - elif key == "x": - self._add_optimizer(key, self.dataset.tilt_angles, self._optimizer_params[key]) - elif key == "z3": - self._add_optimizer(key, self.dataset.z3_angles, self._optimizer_params[key]) - elif key == "model": - if self._optimizer_params[key]["original_lr"] is not None: - optimizer_params = self._optimizer_params[key].copy() - optimizer_params.pop("original_lr", None) - self._add_optimizer(key, self.obj.model.parameters(), optimizer_params) - elif key == "aux_params": - if self._optimizer_params[key]["original_lr"] is not None: - optimizer_params = self._optimizer_params[key].copy() - optimizer_params.pop("original_lr", None) - self._add_optimizer( - key, self.dataset.auxiliary_params.parameters(), optimizer_params - ) - else: - raise ValueError( - f"key to be optimized, {key}, not in allowed keys: {self.OPTIMIZABLE_VALS}" - ) - - def remove_optimizer(self, key: str) -> None: - self._optimizers.pop(key, None) - self._optimizer_params.pop(key, None) - return - - def _add_optimizer( - self, - key: str, - params: "torch.Tensor|Sequence[torch.Tensor]|Iterator[torch.Tensor]", - opt_params: dict, - ) -> None: - """Can be used to add an optimizer without resetting the other optimizers.""" - - if key not in self.OPTIMIZABLE_VALS: - raise ValueError( - f"key to be optimized, {key}, not in allowed keys: {self.OPTIMIZABLE_VALS}" - ) - if isinstance(params, torch.Tensor): - params = [params] - elif isinstance(params, Generator): - params = list(params) - [p.requires_grad_(True) for p in params] - self.optimizer_params[key] = opt_params - opt_params = opt_params.copy() - opt_type = opt_params.pop("type") - if isinstance(opt_type, type): - opt = opt_type(params, **opt_params) - elif opt_type == "adam": - opt = torch.optim.Adam(params, **opt_params) - elif opt_type == "adamw": - opt = torch.optim.AdamW(params, **opt_params) # TODO pass all other kwargs - else: - raise NotImplementedError(f"Unknown optimizer type: {opt_params['type']}") - # if key in self.optimizers.keys(): - # self.vprint(f"Key {key} is already in optimizers, overwriting.") - self._optimizers[key] = opt - - @property - def scheduler_params(self) -> dict[str, dict]: - """Returns the parameters used to set the schedulers.""" - return self._scheduler_params - - @scheduler_params.setter - def scheduler_params(self, d: dict) -> None: - """ - Takes a dictionary: - { - "key1": { - "type": "cyclic", - "base_lr": 0.001, - }, - "key2": { - ... - }, - ... - } - """ - self._schedulers = {} - for k, v in d.items(): - if not any(v): - continue - if k not in self.OPTIMIZABLE_VALS: - raise ValueError( - f"key to be optimized, {k}, not in allowed keys: {self.OPTIMIZABLE_VALS}" - ) - if v["type"] not in [ - "cyclic", - "plateau", - "exp", - "gamma", - "linear", - "cosine_annealing", - "none", - ]: - raise ValueError( - f"Unknown scheduler type: {v['type']}, expected one of ['cyclic', 'plateau', 'exp', 'gamma', 'none']" - ) - self._scheduler_params = d - - @property - def schedulers( - self, - ) -> dict[ - str, - ( - torch.optim.lr_scheduler.CyclicLR - | torch.optim.lr_scheduler.ReduceLROnPlateau - | torch.optim.lr_scheduler.ExponentialLR - | torch.optim.lr_scheduler.LinearLR - | torch.optim.lr_scheduler.CosineAnnealingLR - | None - ), - ]: - return self._schedulers - - def set_schedulers( - self, - params: dict[str, dict], - num_iter: int | None = None, - ): - """ - TODO allow for new schedulers to be passed in when adding new optimizers without - removing the old schedulers or overwrtiting them. Not entirely sure what usecases there - will be for this. - - Sets the schedulers for the optimizer from a dictionary. Expects a dictionary of the form: - { - "optimizable_key1": { - "type": "scheduler_type", - "scheduler_kwarg": scheduler_kwarg_value, - ... - }, - "optimizable_key2": { - "type": "scheduler_type", - "scheduler_kwarg": scheduler_kwarg_value, - ... - }, - ... - } - where the keys are the same as the keys in self.OPTIMIZABLE_VALS. - - The scheduler type can be one of the following: - - "cyclic" - - "plateau" or "reducelronplateau" - - "exponential" - - None - - The num_iter kwarg is only used for exponential schedulers and if a "factor" is given - as a scheduler_kwarg instead of gamma. In that case, the gamma is calculated from num_iter - and the factor. - - TODO could update this to allow passing key:optimizer directly, would likely need to - rewrite get_schedulers to check the tpye - """ - if not any(self.optimizers): - raise NameError("self.optimizers have not yet been set.") - self._schedulers = self._get_schedulers( - params=params, - optimizers=self.optimizers, - num_iter=num_iter, - ) - - def _get_schedulers( - self, - params: dict[str, dict], - optimizers: dict, - num_iter: int | None = None, - ) -> dict[ - str, - ( - torch.optim.lr_scheduler.CyclicLR - | torch.optim.lr_scheduler.ReduceLROnPlateau - | torch.optim.lr_scheduler.ExponentialLR - | None - ), - ]: - """ - return schedulers for a given set of optimizers. Kept seperate from schedulers.setter so - that it can be called for pre-training - """ - schedulers = {} - for opt_key, p in params.items(): - if not any(p): - continue - elif opt_key not in self.OPTIMIZABLE_VALS: - raise KeyError( - f"Scheduler got bad key {opt_key}, schedulers can only be attached to one of {self.OPTIMIZABLE_VALS}" - ) - elif opt_key not in optimizers.keys(): - raise KeyError(f"optimizers does not have an optimizer for: {opt_key}") - else: - schedulers[opt_key] = self._get_scheduler( - optimizer=optimizers[opt_key], params=p, num_iter=num_iter - ) - return schedulers - - def _get_scheduler( - self, - optimizer: torch.optim.Adam, - params: dict[str, Any] | torch.optim.lr_scheduler._LRScheduler, - num_iter: int | None = None, - ) -> ( - torch.optim.lr_scheduler._LRScheduler - | torch.optim.lr_scheduler.CyclicLR - | torch.optim.lr_scheduler.ReduceLROnPlateau - | torch.optim.lr_scheduler.ExponentialLR - | None - ): - if isinstance(params, torch.optim.lr_scheduler._LRScheduler): - return params - - sched_type: str = params["type"].lower() - base_LR = optimizer.param_groups[0]["lr"] - if sched_type == "none": - scheduler = None - elif sched_type == "linear": - scheduler = torch.optim.lr_scheduler.LinearLR( - optimizer, - start_factor=params.get("start_factor", 0.1), - total_iters=params.get("total_iters", num_iter), - ) - elif sched_type == "cosine_annealing": - scheduler = torch.optim.lr_scheduler.CosineAnnealingLR( - optimizer, - T_max=params.get("T_max", num_iter), - eta_min=params.get("eta_min", base_LR / 100), - ) - elif sched_type == "cyclic": - scheduler = torch.optim.lr_scheduler.CyclicLR( - optimizer, - base_lr=params.get("base_lr", base_LR / 4), - max_lr=params.get("max_lr", base_LR * 4), - step_size_up=params.get("step_size_up", 100), - mode=params.get("mode", "triangular2"), - cycle_momentum=params.get("momentum", False), - ) - elif sched_type.startswith(("plat", "reducelronplat")): - scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau( - optimizer, - mode="min", - factor=params.get("factor", 0.5), - patience=params.get("patience", 10), - threshold=params.get("threshold", 1e-3), - min_lr=params.get("min_lr", base_LR / 20), - cooldown=params.get("cooldown", 50), - ) - elif sched_type in ["exp", "gamma", "exponential"]: - if "gamma" in params.keys(): - gamma = params["gamma"] - elif num_iter is not None: - fac = params.get("factor", 0.01) - gamma = fac ** (1.0 / num_iter) - else: - gamma = 0.999 - scheduler = torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma=gamma) - else: - raise ValueError(f"Unknown scheduler type: {sched_type}") - return scheduler diff --git a/src/quantem/tomography_old/tomography_nerf.py b/src/quantem/tomography_old/tomography_nerf.py deleted file mode 100644 index 2aa27dc8..00000000 --- a/src/quantem/tomography_old/tomography_nerf.py +++ /dev/null @@ -1,636 +0,0 @@ -import os -from pathlib import Path - -import matplotlib.pyplot as plt -import numpy as np -import torch -import torch.distributed as dist -from torch.utils.data import DataLoader, DistributedSampler -from torch.utils.tensorboard import SummaryWriter - -from quantem.tomography.models import HSiren -from quantem.tomography.tomography_dataset import TomographyDataset - - -def get_num_samples_per_ray(epoch): - """Increase number of samples per ray at specific epochs.""" - schedule = { - 0: 25, - 2: 50, - 4: 100, - 6: 200, - } - - num_samples = 64 - for epoch_threshold, samples in sorted(schedule.items()): - if epoch >= epoch_threshold: - num_samples = samples - - return num_samples - - -class AuxiliaryParams(torch.nn.Module): - def __init__(self, num_tilts, device, zero_tilt_idx=None): - super().__init__() - - if zero_tilt_idx is None: - # If not provided, assume first projection is reference - zero_tilt_idx = 0 - - self.zero_tilt_idx = zero_tilt_idx - self.num_tilts = num_tilts - - # Shifts: only parameterize non-reference tilts - num_param_tilts = num_tilts - 1 - self.shifts_param = torch.nn.Parameter(torch.zeros(num_param_tilts, 2, device=device)) - - # Fixed zero shifts for reference - self.shifts_ref = torch.zeros(1, 2, device=device) - - # Z1 and Z3: parameterize all tilts EXCEPT the reference - self.z1_param = torch.nn.Parameter(torch.zeros(num_param_tilts, device=device)) - # self.z3_param = torch.nn.Parameter(torch.zeros(num_param_tilts, device=device)) - - # Fixed zeros for reference tilt - self.z1_ref = torch.zeros(1, device=device) - # self.z3_ref = torch.zeros(1, device=device) - - def forward(self, dummy_input=None): - # Reconstruct full arrays with zeros at reference position - before_shifts = self.shifts_param[: self.zero_tilt_idx] - after_shifts = self.shifts_param[self.zero_tilt_idx :] - shifts = torch.cat([before_shifts, self.shifts_ref, after_shifts], dim=0) - - before_z1 = self.z1_param[: self.zero_tilt_idx] - after_z1 = self.z1_param[self.zero_tilt_idx :] - z1 = torch.cat([before_z1, self.z1_ref, after_z1], dim=0) - - # before_z3 = self.z3_param[:self.zero_tilt_idx] - # after_z3 = self.z3_param[self.zero_tilt_idx:] - # z3 = torch.cat([before_z3, self.z3_ref, after_z3], dim=0) - - return shifts, z1, -z1 - - -class TomographyNerf: - def __init__( - self, - tomo_dataset: TomographyDataset, - batch_size: int, - num_workers: int = 0, - ): - self.tomo_dataset = tomo_dataset - self.setup_distributed() - self.setup_dataloader( - batch_size=batch_size, - num_workers=num_workers, - ) - self.build_model() - - # --- Setup ---- - - def setup_distributed(self): - """Setup distributed training if available, otherwise use single GPU/CPU.""" - - # Check if we're in a distributed environment - if "RANK" in os.environ: - # Distributed training - if not dist.is_initialized(): - dist.init_process_group( - backend="nccl" if torch.cuda.is_available() else "gloo", init_method="env://" - ) - - self.world_size = dist.get_world_size() - self.global_rank = dist.get_rank() - self.local_rank = int(os.environ["LOCAL_RANK"]) - - torch.cuda.set_device(self.local_rank) - self.device = torch.device("cuda", self.local_rank) - - else: - # Single GPU/CPU training - self.world_size = 1 - self.global_rank = 0 - self.local_rank = 0 - - if torch.cuda.is_available(): - self.device = torch.device("cuda:0") - torch.cuda.set_device(0) - print("Single GPU training") - else: - self.device = torch.device("cpu") - print("CPU training") - - # Optional performance optimizations (only for CUDA) - if self.device.type == "cuda": - torch.backends.cudnn.benchmark = True - torch.backends.cuda.matmul.allow_tf32 = True - torch.backends.cudnn.allow_tf32 = True - - def build_model(self): - self.model = HSiren( - in_features=3, - out_features=1, - hidden_layers=4, - hidden_features=512, - first_omega_0=30, - alpha=1.0, - ).to(self.device) - - if self.world_size > 1: - self.model = torch.nn.parallel.DistributedDataParallel( - self.model, - device_ids=[self.local_rank], - output_device=self.local_rank, - find_unused_parameters=False, - broadcast_buffers=True, - bucket_cap_mb=100, - gradient_as_bucket_view=True, - ) - - if self.global_rank == 0: - print("Model wrapped with DDP") - - if self.world_size > 1: - if self.global_rank == 0: - print("Model built, distributed, and compiled successfully") - - else: - print("Model built, compiled successfully") - - def setup_dataloader( - self, - batch_size: int, - num_workers: int = 0, - ): - if self.world_size > 1: - sampler = DistributedSampler( - self.tomo_dataset, - num_replicas=self.world_size, - rank=self.global_rank, - shuffle=True, - ) - shuffle = False - else: - sampler = None - shuffle = True - self.dataloader = DataLoader( - self.tomo_dataset, - batch_size=batch_size, - num_workers=num_workers, - sampler=sampler, - shuffle=shuffle, - pin_memory=True if self.device.type == "cuda" else False, - drop_last=True, - persistent_workers=False if num_workers == 0 else True, - ) - - self.sampler = sampler - - if self.global_rank == 0: - print("Dataloader setup complete:") - print(f" Total projections: {len(self.tomo_dataset.tilt_angles)}") - print(f" Grid size: {self.tomo_dataset.dims[1]}{self.tomo_dataset.dims[2]}") - print(f" Total pixels: {self.tomo_dataset.num_pixels:,}") - - print(f" Local batch size (train): {batch_size}") - print(f" Global batch size: {batch_size * self.world_size}") - print(f" Train batches per GPU per epoch: {len(self.dataloader)}") - - # --- Creating Volume --- - def create_volume(self): - N = max(self.tomo_dataset.dims) - - with torch.no_grad(): - coords_1d = torch.linspace(-1, 1, N) - x, y, z = torch.meshgrid(coords_1d, coords_1d, coords_1d, indexing="ij") - inputs = torch.stack([x, y, z], dim=-1).reshape(-1, 3) - - model = self.model.module if hasattr(self.model, "module") else self.model - - samples_per_gpu = N**3 // self.world_size - start_idx = self.global_rank * samples_per_gpu - end_idx = start_idx + samples_per_gpu - - inputs_subset = inputs[start_idx:end_idx].to(self.device) - - # TODO: torch.nn.functional.softplus here - # outputs = torch.nn.functional.softplus(model(inputs_subset)) - outputs = model(inputs_subset) - - if outputs.dim() > 1: - outputs = outputs.squeeze(-1) - - if self.world_size > 1: - gathered_outputs = [torch.empty_like(outputs) for _ in range(self.world_size)] - dist.all_gather(gathered_outputs, outputs.contiguous()) - - pred_full = torch.cat(gathered_outputs, dim=0).reshape(N, N, N).float() - else: - pred_full = outputs.reshape(N, N, N).float() - - return pred_full - - # --- Scaling LR --- - def get_scaled_lr(self, base_lr, scaling_rule="sqrt"): - """Scale learning rate based on world size.""" - if self.world_size == 1: - return base_lr - - if scaling_rule == "linear": - # Linear scaling: lr = base_lr * world_size - return base_lr * self.world_size - elif scaling_rule == "sqrt": - # Square root scaling: lr = base_lr * sqrt(world_size) - return base_lr * np.sqrt(self.world_size) - else: - raise ValueError(f"Invalid scaling rule: {scaling_rule}") - - # --- Creating Optimizer --- - def create_optimizer( - self, - params, - lr, - fused=True, - ): - return torch.optim.Adam( - params, - lr=lr, - fused=fused, - ) - - # Batch Projection Rays --- - - def create_batch_projection_rays(self, pixel_i, pixel_j, N, num_samples_per_ray): - """Create projection rays for entire batch simultaneously.""" - batch_size = len(pixel_i) - - # Convert all pixels to normalized coordinates - x_coords = (pixel_j / (N - 1)) * 2 - 1 - y_coords = (pixel_i / (N - 1)) * 2 - 1 - - # Create z coordinates - z_coords = torch.linspace(-1, 1, num_samples_per_ray, device=self.device) - - # Create rays for all pixels: [batch_size, num_samples_per_ray, 3] - rays = torch.zeros(batch_size, num_samples_per_ray, 3, device=self.device) - - # Fill coordinates efficiently - rays[:, :, 0] = x_coords.unsqueeze(1) # x constant per ray - rays[:, :, 1] = y_coords.unsqueeze(1) # y constant per ray - rays[:, :, 2] = z_coords.unsqueeze(0) # z varies along ray - - return rays - - @torch.compile(mode="reduce-overhead") - def transform_batch_ray_coordinates(self, rays, z1, x, z3, shifts, N, sampling_rate): - # Step 1: Apply shifts - shift_x_norm = (shifts[:, 0:1] * sampling_rate * 2) / (N - 1) - shift_y_norm = (shifts[:, 1:2] * sampling_rate * 2) / (N - 1) - - rays_x = rays[:, :, 0] - shift_x_norm - rays_y = rays[:, :, 1] - shift_y_norm - rays_z = rays[:, :, 2] - - # Rotation 1: Z(-z3) - theta = torch.deg2rad(-z3).view(-1, 1) - cos_t = torch.cos(theta) - sin_t = torch.sin(theta) - - rays_x_rot1 = cos_t * rays_x - sin_t * rays_y - rays_y_rot1 = sin_t * rays_x + cos_t * rays_y - rays_z_rot1 = rays_z - - # Rotation 2: X(x) - theta = torch.deg2rad(x).view(-1, 1) - cos_t = torch.cos(theta) - sin_t = torch.sin(theta) - - rays_x_rot2 = rays_x_rot1 - rays_y_rot2 = cos_t * rays_y_rot1 - sin_t * rays_z_rot1 - rays_z_rot2 = sin_t * rays_y_rot1 + cos_t * rays_z_rot1 - - # Rotation 3: Z(-z1) - theta = torch.deg2rad(-z1).view(-1, 1) - cos_t = torch.cos(theta) - sin_t = torch.sin(theta) - - rays_x_final = cos_t * rays_x_rot2 - sin_t * rays_y_rot2 - rays_y_final = sin_t * rays_x_rot2 + cos_t * rays_y_rot2 - rays_z_final = rays_z_rot2 - - # Stack the final result - transformed_rays = torch.stack([rays_x_final, rays_y_final, rays_z_final], dim=2) - - return transformed_rays - - # --- Training --- - def train( - self, - train_lr: float = 1e-5, - epochs: int = 20, - warmup_epochs: int = 10, - use_amp: bool = True, - tv_weight: float = 0.0, - viz_freq: int = 1, - checkpoint_freq: int = 5, - ): - log_path = Path("runs/test_1") - if not log_path.exists(): - log_path.mkdir(parents=True, exist_ok=True) - self.writer = SummaryWriter(log_dir=log_path) - - # Find index closest to zero degrees - zero_tilt_idx = torch.argmin(torch.abs(self.tomo_dataset.tilt_angles)).item() - - if self.global_rank == 0: - print( - f"Using projection {zero_tilt_idx} (angle={self.tomo_dataset.tilt_angles[zero_tilt_idx]:.2f}°) as reference" - ) - - aux_params = AuxiliaryParams( - num_tilts=len(self.tomo_dataset.tilt_angles), - device=self.device, - zero_tilt_idx=zero_tilt_idx, - ) - - if self.world_size > 1: - aux_params = torch.nn.parallel.DistributedDataParallel( - aux_params, - device_ids=[self.local_rank], - output_device=self.local_rank, - find_unused_parameters=False, - broadcast_buffers=True, - ) - - if self.global_rank == 0: - print("Auxiliary parameters wrapped with DDP") - - scaled_train_lr = self.get_scaled_lr(train_lr, scaling_rule="sqrt") - scaled_aux_lr = self.get_scaled_lr(train_lr, scaling_rule="sqrt") - - optimizer = self.create_optimizer( - self.model.parameters(), - scaled_train_lr, - fused=True, - ) - - aux_optimizer = self.create_optimizer( - aux_params.parameters(), - scaled_aux_lr, - fused=True, - ) - - aux_norm = torch.tensor(0.0, device=self.device) - model_norm = torch.tensor(0.0, device=self.device) - - optimizer.zero_grad() - aux_optimizer.zero_grad() - - warmup_scheduler_model = torch.optim.lr_scheduler.LinearLR( - optimizer, - start_factor=0.001, - total_iters=warmup_epochs, - ) - cosine_scheduler_model = torch.optim.lr_scheduler.CosineAnnealingLR( - optimizer, - T_max=epochs - warmup_epochs, - eta_min=scaled_train_lr / 100, - ) - - scheduler_model = torch.optim.lr_scheduler.SequentialLR( - optimizer, - schedulers=[warmup_scheduler_model, cosine_scheduler_model], - milestones=[warmup_epochs], - ) - - scheduler_aux = torch.optim.lr_scheduler.CosineAnnealingLR( - aux_optimizer, - T_max=epochs, - eta_min=scaled_aux_lr / 100, - ) - - N = max(self.tomo_dataset.dims) - - device_type = self.device.type - autocast_dtype = torch.bfloat16 if use_amp else None - - for epoch in range(epochs): - num_samples_per_ray = get_num_samples_per_ray(epoch) - - # Log the change if it happens - if epoch > 0: - prev_samples = get_num_samples_per_ray(epoch - 1) - - if num_samples_per_ray != prev_samples and self.global_rank == 0: - print( - f"Epoch {epoch}: Changing num_samples_per_ray from {prev_samples} to {num_samples_per_ray}" - ) - - if self.sampler is not None: - self.sampler.set_epoch(epoch) - - epoch_loss = 0.0 - epoch_mse_loss = 0.0 - epoch_tv_loss = 0.0 - epoch_z1_loss = 0.0 - - num_batches = 0 - - for batch_idx, batch in enumerate(self.dataloader): - projection_indices = batch["projection_idx"] - - pixel_i = batch["pixel_i"].float().to(self.device, non_blocking=True) - pixel_j = batch["pixel_j"].float().to(self.device, non_blocking=True) - target_values = batch["target_value"].to(self.device, non_blocking=True) - phis = batch["phi"].to(self.device, non_blocking=True) - projection_indices = batch["projection_idx"].to(self.device, non_blocking=True) - - shifts, z1_params, z3_params = aux_params(None) - batch_shifts = torch.index_select(shifts, 0, projection_indices) - batch_z1 = torch.index_select(z1_params, 0, projection_indices) - batch_z3 = torch.index_select(z3_params, 0, projection_indices) - - with torch.autocast( - device_type=device_type, dtype=autocast_dtype, enabled=use_amp - ): - with torch.no_grad(): - batch_ray_coords = self.create_batch_projection_rays( - pixel_i, pixel_j, N, num_samples_per_ray - ) - - transformed_rays = self.transform_batch_ray_coordinates( - batch_ray_coords, - z1=batch_z1, - x=phis, - z3=batch_z3, - shifts=batch_shifts, - N=N, - sampling_rate=1.0, - ) - - all_coords = transformed_rays.view(-1, 3) - # TODO: torch.nn.functional.softplus here - # all_densities = torch.nn.functional.softplus(self.model(all_coords)) - all_densities = self.model(all_coords) - - if all_densities.dim() > 1: - all_densities = all_densities.squeeze(-1) # [N, 1] → [N] - - # Create mask for valid coordinates (within [-1, 1]^3) - valid_mask = ( - (all_coords[:, 0] >= -1) - & (all_coords[:, 0] <= 1) - & (all_coords[:, 1] >= -1) - & (all_coords[:, 1] <= 1) - & (all_coords[:, 2] >= -1) - & (all_coords[:, 2] <= 1) - ).float() - - all_densities = all_densities * valid_mask - - if tv_weight > 0: - num_tv_samples = min(10000, all_coords.shape[0]) - tv_indices = torch.randperm(all_coords.shape[0], device=all_coords.device)[ - :num_tv_samples - ] - - # Rerun forward for gradient tracking - tv_coords = all_coords[tv_indices].detach().requires_grad_(True) - - # TODO: torch.nn.functional.softplus here - # tv_densities_recomputed = torch.nn.functional.softplus(self.model(tv_coords)) # Get rid of this - tv_densities_recomputed = self.model(tv_coords) - if tv_densities_recomputed.dim() > 1: - tv_densities_recomputed = tv_densities_recomputed.squeeze(-1) - - # Compute gradients - grad_outputs = torch.autograd.grad( - outputs=tv_densities_recomputed, - inputs=tv_coords, - grad_outputs=torch.ones_like(tv_densities_recomputed), - create_graph=True, - )[0] - - grad_norm = torch.norm(grad_outputs, dim=1) - tv_loss = tv_weight * grad_norm.mean() - else: - tv_loss = torch.tensor(0.0, device=self.device) - - ray_densities = all_densities.view( - len(target_values), num_samples_per_ray - ) # Reshape rays and integarte - step_size = 2.0 / (num_samples_per_ray - 1) - - predicted_values = ray_densities.sum(dim=1) * step_size - - mse_loss = torch.nn.functional.mse_loss(predicted_values, target_values) - - tv_loss_z1 = torch.tensor(0.0, device=self.device) - # tv_loss_z1 = tv_loss_1d(z1_params, factor = 1e-3) + tv_loss_1d(z3_params, factor = 1e-3) - # tv_loss_z1 = 1e-5*(torch.sum(z1_params**2) + torch.sum(z3_params**2)) - - # Combine losses - loss = mse_loss + tv_loss_z1 + tv_loss - - loss.backward() - - epoch_loss += loss.detach() - epoch_mse_loss += mse_loss.detach() - epoch_tv_loss += tv_loss.detach() - epoch_z1_loss += tv_loss_z1.detach() - - num_batches += 1 - - if epoch >= warmup_epochs: - model_norm = torch.nn.utils.clip_grad_norm_( - self.model.parameters(), max_norm=1 - ) - optimizer.step() - optimizer.zero_grad() - - aux_norm = torch.nn.utils.clip_grad_norm_(aux_params.parameters(), max_norm=1) - aux_optimizer.step() - aux_optimizer.zero_grad() - else: - model_norm = torch.nn.utils.clip_grad_norm_( - self.model.parameters(), max_norm=1 - ) - optimizer.step() - optimizer.zero_grad() - - aux_optimizer.zero_grad() - - scheduler_model.step() - - if epoch >= warmup_epochs: - scheduler_aux.step() - - if epoch % viz_freq == 0 or epoch == epochs - 1 or epoch % checkpoint_freq == 0: - with torch.no_grad(): - pred_full = self.create_volume().cpu() - avg_loss = epoch_loss.item() / num_batches - avg_mse_loss = epoch_mse_loss.item() / num_batches - avg_tv_loss = epoch_tv_loss.item() / num_batches - avg_z1_loss = epoch_z1_loss.item() / num_batches - - metrics = torch.tensor( - [avg_loss, avg_mse_loss, avg_tv_loss, avg_z1_loss], device=self.device - ) - if self.world_size > 1: - torch.distributed.all_reduce(metrics, op=torch.distributed.ReduceOp.AVG) - avg_loss, avg_mse_loss, avg_tv_loss, avg_z1_loss = metrics.tolist() - - # Logging and visualization - if self.global_rank == 0 and (epoch % viz_freq == 0 or epoch == epochs - 1): - with torch.no_grad(): - current_lr = scheduler_model.get_last_lr()[0] - - # Log metrics - self.writer.add_scalar("train/mse_loss", avg_mse_loss, epoch) - - self.writer.add_scalar("train/z1_loss", avg_z1_loss, epoch) - if tv_weight > 0: - self.writer.add_scalar("train/tv_loss", avg_tv_loss, epoch) - self.writer.add_scalar("train/total_loss", avg_loss, epoch) - self.writer.add_scalar("train/model_grad_norm", model_norm.item(), epoch) - self.writer.add_scalar("train/aux_grad_norm", aux_norm.item(), epoch) - self.writer.add_scalar("train/lr", current_lr, epoch) - self.writer.add_scalar("train/num_samples_per_ray", num_samples_per_ray, epoch) - - fig, axes = plt.subplots(1, 3, figsize=(36, 12)) - axes[0].matshow(pred_full.sum(dim=0).cpu().numpy(), cmap="turbo", vmin=0) - axes[0].set_title("Sum over Z-axis") - - axes[1].matshow(pred_full[N // 2].cpu().numpy(), cmap="turbo", vmin=0) - axes[1].set_title(f"Slice at Z={N // 2}") - - slice_start = max(0, N // 2 - 5) - slice_end = min(N, N // 2 + 6) - thick_slice = pred_full[slice_start:slice_end].sum(dim=0).cpu().numpy() - axes[2].matshow(thick_slice, cmap="turbo", vmin=0) - axes[2].set_title(f"Thick slice sum (Z={slice_start}:{slice_end - 1})") - - self.writer.add_figure("train/viz", fig, epoch, close=True) - plt.close(fig) - - if epoch % checkpoint_freq == 0 or epoch == epochs - 1: - self.save_checkpoint(epoch, aux_params, log_path) - - if self.global_rank == 0: - print("Training complete.") - - def save_checkpoint(self, epoch, aux_params, log_path): - """Save model and auxiliary parameters checkpoint (rank 0 only).""" - if self.global_rank == 0: - model_to_save = self.model.module if hasattr(self.model, "module") else self.model - aux_params_to_save = aux_params.module if hasattr(aux_params, "module") else aux_params - - checkpoint = { - "model_state_dict": model_to_save.state_dict(), - "aux_params_state_dict": aux_params_to_save.state_dict(), - "epoch": epoch, - } - checkpoint_path = os.path.join(log_path, f"checkpoint_epoch_{epoch:04d}.pt") - torch.save(checkpoint, checkpoint_path) - print(f"Checkpoint saved at epoch {epoch}: {checkpoint_path}") diff --git a/src/quantem/tomography_old/utils.py b/src/quantem/tomography_old/utils.py deleted file mode 100644 index 82c1c9d5..00000000 --- a/src/quantem/tomography_old/utils.py +++ /dev/null @@ -1,300 +0,0 @@ -import numpy as np -import torch -import torch.nn.functional as F -from scipy.ndimage import center_of_mass, gaussian_filter, shift -from scipy.stats import norm -from tqdm.auto import tqdm - -from quantem.core.utils.imaging_utils import cross_correlation_shift - -# --- Projection Operator Utils --- - - -def rot_ZXZ(mags, z1, x, z3, device, mode="bilinear"): - if not isinstance(x, torch.Tensor) or not isinstance(z1, torch.Tensor): - z1 = torch.tensor(z1, dtype=torch.float32, device=device) - x = torch.tensor(x, dtype=torch.float32, device=device) - z3 = torch.tensor(z3, dtype=torch.float32, device=device) - curr_mags = mags - - curr_mags = differentiable_rotz_vectorized(curr_mags, z1, mode) - curr_mags = differentiable_rotx_vectorized(curr_mags, x, mode) - - curr_mags = differentiable_rotz_vectorized(curr_mags, z3, mode) - - return curr_mags - - -def differentiable_rotz_vectorized(mags, theta, mode="bilinear"): - _, dimz, dimy, dimx = mags.shape - - if theta.dim() == 0: - theta = theta.unsqueeze(0) - - theta_rad = torch.deg2rad(theta) - - cos_t, sin_t = torch.cos(theta_rad), torch.sin(theta_rad) - affine_matrix = torch.stack( - [cos_t, -sin_t, torch.zeros_like(theta), sin_t, cos_t, torch.zeros_like(theta)], dim=-1 - ).view(-1, 2, 3) - - mags = mags.permute(1, 0, 2, 3) - - def transform_slice(mag_slice): - grid = F.affine_grid(affine_matrix, mag_slice.unsqueeze(0).shape, align_corners=False) - return F.grid_sample(mag_slice.unsqueeze(0), grid, mode=mode, align_corners=False).squeeze( - 0 - ) - - rotated_mags = torch.vmap(transform_slice)(mags) - return rotated_mags.permute(1, 0, 2, 3) - - -def differentiable_rotx_vectorized(mags, theta, mode="bilinear"): - _, dimz, dimy, dimx = mags.shape - - if theta.dim() == 0: - theta = theta.unsqueeze(0) - - theta_rad = torch.deg2rad(theta) - - cos_t, sin_t = torch.cos(theta_rad), torch.sin(theta_rad) - affine_matrix = torch.stack( - [cos_t, -sin_t, torch.zeros_like(theta), sin_t, cos_t, torch.zeros_like(theta)], dim=-1 - ).view(-1, 2, 3) - - mags = mags.permute(3, 0, 1, 2) - - def transform_slice(mag_slice): - grid = F.affine_grid(affine_matrix, mag_slice.unsqueeze(0).shape, align_corners=False) - return F.grid_sample(mag_slice.unsqueeze(0), grid, mode=mode, align_corners=False).squeeze( - 0 - ) - - rotated_mags = torch.vmap(transform_slice)(mags) - return rotated_mags.permute(1, 2, 3, 0) - - -def differentiable_shift_2d(image, shift_x, shift_y, sampling_rate): - """ - Shifts a 2D image using grid_sample in a differentiable manner. - - Args: - image: Tensor of shape [H, W] - shift_x: Scalar tensor (dx) for shift in x-direction (in physical units) - shift_y: Scalar tensor (dy) for shift in y-direction (in physical units) - sampling_rate: Scalar value (physical units per pixel) to correctly normalize shifts - - Returns: - Shifted image of shape [H, W] - """ - H, W = image.shape - - # Convert physical shift to pixel shift - shift_x_pixel = shift_x - shift_y_pixel = shift_y - - # Normalize shift for grid_sample (assuming align_corners=True) - normalized_shift_x = shift_x_pixel * 2 / (W - 1) - normalized_shift_y = shift_y_pixel * 2 / (H - 1) - - # Create normalized grid - grid_y, grid_x = torch.meshgrid( - torch.linspace(-1, 1, H, device=image.device), - torch.linspace(-1, 1, W, device=image.device), - indexing="ij", - ) - - grid = torch.stack((grid_x, grid_y), dim=-1).unsqueeze(0) # [1, H, W, 2] - - # Apply shift (ensure it's differentiable) - grid[:, :, :, 0] -= normalized_shift_x - grid[:, :, :, 1] -= normalized_shift_y - - # Add batch and channel dimensions - image = image.unsqueeze(0).unsqueeze(0) # [1, 1, H, W] - - # Sample using grid_sample (fully differentiable) - shifted_image = F.grid_sample( - image, grid, mode="bicubic", padding_mode="zeros", align_corners=True - ) - - return shifted_image.squeeze(0).squeeze(0) # Back to [H, W] - - -# --- TV loss --- - - -def get_TV_loss(tensor, factor=1e-3): - tv_d = torch.pow(tensor[:, :, 1:, :, :] - tensor[:, :, :-1, :, :], 2).sum() - tv_h = torch.pow(tensor[:, :, :, 1:, :] - tensor[:, :, :, :-1, :], 2).sum() - tv_w = torch.pow(tensor[:, :, :, :, 1:] - tensor[:, :, :, :, :-1], 2).sum() - tv_loss = tv_d + tv_h + tv_w - - return tv_loss * factor / (torch.prod(torch.tensor(tensor.shape))) - - -# --- Gaussian filters --- - - -def gaussian_kernel_1d(sigma: float, num_sigmas: float = 3.0) -> torch.Tensor: - radius = np.ceil(num_sigmas * sigma) - support = torch.arange(-radius, radius + 1, dtype=torch.float) - kernel = torch.distributions.Normal(loc=0, scale=sigma).log_prob(support).exp_() - # Ensure kernel weights sum to 1, so that image brightness is not altered - return kernel.mul_(1 / kernel.sum()) - - -def gaussian_filter_2d( - img: torch.Tensor, sigma: float, kernel_1d: torch.Tensor -) -> torch.Tensor: # Add kernel_1d as an argument - # kernel_1d = gaussian_kernel_1d(sigma) # Create 1D Gaussian kernel - Moved outside function - padding = len(kernel_1d) // 2 # Ensure that image size does not change - img = img.unsqueeze(0).unsqueeze_(0) # Make copy, make 4D for ``conv2d()`` - # Convolve along columns and rows - img = torch.nn.functional.conv2d(img, weight=kernel_1d.view(1, 1, -1, 1), padding=(padding, 0)) - img = torch.nn.functional.conv2d(img, weight=kernel_1d.view(1, 1, 1, -1), padding=(0, padding)) - return img.squeeze_(0).squeeze_(0) # Make 2D again - - -def gaussian_filter_2d_stack(stack: torch.Tensor, kernel_1d: torch.Tensor) -> torch.Tensor: - """ - Apply 2D Gaussian blur to each slice stack[:, i, :] in a vectorized way. - - Args: - stack (torch.Tensor): Tensor of shape (H, N, W) where N is num_sinograms - kernel_1d (torch.Tensor): 1D Gaussian kernel - - Returns: - torch.Tensor: Blurred stack of same shape (H, N, W) - """ - H, N, W = stack.shape - padding = len(kernel_1d) // 2 - - # Reshape to (N, 1, H, W) for conv2d - stack_reshaped = stack.permute(1, 0, 2).unsqueeze(1) # (N, 1, H, W) - - # Apply separable conv2d: vertical then horizontal - out = torch.nn.functional.conv2d( - stack_reshaped, kernel_1d.view(1, 1, -1, 1), padding=(padding, 0) - ) - out = torch.nn.functional.conv2d(out, kernel_1d.view(1, 1, 1, -1), padding=(0, padding)) - - # Restore shape to (H, N, W) - return out.squeeze(1).permute(1, 0, 2) - - -# Circular mask - - -def torch_phase_cross_correlation(im1, im2): - f1 = torch.fft.fft2(im1) - f2 = torch.fft.fft2(im2) - cc = torch.fft.ifft2(f1 * torch.conj(f2)) - cc_abs = torch.abs(cc) - - max_idx = torch.argmax(cc_abs) - shifts = torch.tensor(np.unravel_index(max_idx.item(), im1.shape), device=im1.device).float() - - for i, dim in enumerate(im1.shape): - if shifts[i] > dim // 2: - shifts[i] -= dim - - # return shifts.flip(0) # (dx, dy) - return shifts - - -# --- Tilt Series Processing Utility Functions --- - - -def fourier_cropping(img, crop_size): - """ - Crop the img in Fourier space to the specified size. - """ - center = np.array(img.shape) // 2 - - fft_img = np.fft.fftshift(np.fft.fft2(img)) - - cropped_fft = fft_img[ - center[0] - crop_size[0] // 2 : center[0] + crop_size[0] // 2, - center[1] - crop_size[1] // 2 : center[1] + crop_size[1] // 2, - ] - cropped_img = np.fft.ifft2(np.fft.ifftshift(cropped_fft)).real - return cropped_img - - -def estimate_background( - img, - num_iterations=10, - cutoff=3, - smoothing_sigma=1.0, -): - """ - Estimate the background of the image using a Gaussian filter. - """ - if smoothing_sigma > 0: - img = gaussian_filter(img, sigma=smoothing_sigma) - pixel_vals = img.ravel() - - for i in range(num_iterations): - mu, std = norm.fit(pixel_vals) - - # Set cutoff threshold (e.g., 3 standard deviations) - lower = mu - cutoff * std - upper = mu + cutoff * std - - # Mask pixel values within ±3σ - pixel_vals = pixel_vals[(pixel_vals >= lower) & (pixel_vals <= upper)] - - return mu - - -def cross_correlation_align_stack(ref_img, stack, print_pred=False): - """ - Aligns a stack of images to a reference image using cross-correlation. - - This function assumes the stack does not contain the reference image itself. - - Stack shape should be (N, H, W) where N is the number of images. - """ - - new_images = [] - pred_shifts = [] - - prev_img = ref_img - for img in tqdm(stack): - shift_pred = cross_correlation_shift(prev_img, img) - if print_pred: - print(f"Shift prediction: {shift_pred}") - shifted_image = shift(img, shift=shift_pred, mode="constant", cval=0.0) - - pred_shifts.append(shift_pred) - new_images.append(shifted_image) - - prev_img = shifted_image - - return new_images, pred_shifts - - -def centering_com_alignment(image_stack): - """ - Aligns the image stack to the center of mass of the whole image_stack to the - image center. This is useful for aligning the tilt series to the invariant line. - """ - - aligned_stack = np.zeros_like(image_stack) - h, w = image_stack.shape[1:] - image_center = np.array([h // 2, w // 2]) - - com_reference = np.array(center_of_mass(image_stack.mean(axis=0))) - - for i, img in enumerate(image_stack): - com_img = np.array(center_of_mass(img)) - shift_vec = com_reference - com_img - aligned_stack[i] = shift(img, shift=shift_vec, mode="constant", cval=0.0) - - final_shift = image_center - com_reference - for i in range(aligned_stack.shape[0]): - aligned_stack[i] = shift(aligned_stack[i], shift=final_shift, mode="constant", cval=0.0) - - return aligned_stack From 49b2b2606f6a6279881cec240515e3ca03c4483d Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Sat, 31 Jan 2026 23:30:50 -0800 Subject: [PATCH 057/335] Added option to only learn parts of the pose i.e, shifts or tilt axis. Also included background_subtract again, did this get moved somewhere? --- src/quantem/core/utils/imaging_utils.py | 166 ++++++++++++++++++++++- src/quantem/tomography/dataset_models.py | 65 ++++++--- 2 files changed, 210 insertions(+), 21 deletions(-) diff --git a/src/quantem/core/utils/imaging_utils.py b/src/quantem/core/utils/imaging_utils.py index d3a65c79..362bfe7e 100644 --- a/src/quantem/core/utils/imaging_utils.py +++ b/src/quantem/core/utils/imaging_utils.py @@ -1,14 +1,22 @@ # Utilities for processing images import math -from typing import Optional, Tuple +from typing import Any, Optional, Tuple, TypeVar import numpy as np import torch + +# TODO: Temporary +from matplotlib import cm from numpy.typing import NDArray from scipy.ndimage import gaussian_filter +from scipy.special import comb from quantem.core.utils.utils import generate_batches +from quantem.core.visualization import show_2d + +ImageType = TypeVar("ImageType", NDArray[Any]) +BoolArray = NDArray[np.bool_] def dft_upsample( @@ -1048,3 +1056,159 @@ def unwrap_phase_2d_torch( raise ValueError( f'`method` must be one of {{"reliability-sorting", "poisson"}}, got {method!r}' ) + + +# Background subtraction + +# single TypeVar: works for both numpy and + + +def _as_array(x: ImageType) -> NDArray[Any]: + return ( + x.array + if isinstance( + x, + ) + else np.asarray(x) + ) + + +def _bernstein_basis_1d(n: int, t: NDArray[Any]) -> NDArray[Any]: + k = np.arange(n + 1, dtype=int) + return ( + comb(n, k)[None, :] * (t[:, None] ** k[None, :]) * ((1.0 - t)[:, None] ** (n - k)[None, :]) + ) + + +def _build_basis_matrix(im_shape: Tuple[int, int], order: Tuple[int, int]) -> NDArray[Any]: + H, W = im_shape + ou, ov = int(order[0]), int(order[1]) + u = np.linspace(0.0, 1.0, H) + v = np.linspace(0.0, 1.0, W) + Bu = _bernstein_basis_1d(ou, u) + Bv = _bernstein_basis_1d(ov, v) + basis_cube = np.einsum("ik,jl->ijkl", Bu, Bv) + return basis_cube.reshape(H * W, (ou + 1) * (ov + 1)) + + +def background_subtract( + image: ImageType, + mask: Optional[BoolArray] = None, + thresh_bg: Optional[float] = None, + order: Tuple[int, int] = (1, 1), + sigma: Optional[float] = None, + num_iter: int = 10, + plot_result: bool = True, + axsize: Tuple[int, int] = (3.1, 3), + cmap: str = "turbo", + return_background_and_mask: bool = False, + **show_kwargs, +) -> ImageType | Tuple[ImageType, NDArray[Any], BoolArray]: + """ + Background subtraction via bivariate Bernstein polynomial fitting. + + Returns + ------- + - If `return_background_and_mask=False`: ImageType (same as input) + - If `True`: (ImageType, numpy.ndarray, numpy.ndarray[bool]) + where background and mask are always NumPy. + """ + im = _as_array(image).astype(float, copy=True) + if im.ndim != 2: + raise ValueError("`image` must be 2D") + + mask_arr: BoolArray = ( + np.ones_like(im, dtype=bool) if mask is None else np.asarray(mask, dtype=bool) + ) + if mask_arr.shape != im.shape: + raise ValueError("`mask` must match `image` shape") + + order = (int(order[0]), int(order[1])) + A_full = _build_basis_matrix(im.shape, order) + H, W = im.shape + im_flat = im.ravel() + + im_bg = np.zeros_like(im) + thresh_val = np.median(im[mask_arr]) if thresh_bg is None else float(thresh_bg) + + resid = im - im_bg + if sigma and sigma > 0: + resid = gaussian_filter(resid, sigma=sigma, mode="nearest") + mask_bg: BoolArray = (resid < thresh_val) & mask_arr + + for _ in range(int(num_iter)): + idx = mask_bg.ravel() + if not np.any(idx): + idx = mask_arr.ravel() + coefs, *_ = np.linalg.lstsq(A_full[idx, :], im_flat[idx], rcond=None) + im_bg = (A_full @ coefs).reshape(H, W) + + resid = im - im_bg + if sigma and sigma > 0: + resid = gaussian_filter(resid, sigma=sigma, mode="nearest") + + thr = thresh_val if thresh_bg is None else float(thresh_bg) + mask_bg = (resid < thr) & mask_arr + + im_sub_np = im - im_bg + + if plot_result: + vals = im_sub_np[mask_arr] + vals = vals[np.isfinite(vals)] + if vals.size == 0: + vals = np.array([0.0]) + vmin_sub = float(np.min(vals)) + vmax_sub = float(np.max(vals)) + vrange = float(max(abs(vmin_sub), abs(vmax_sub))) or 1e-12 + + bg_disp = (im_bg - np.mean(im_bg)).copy() + bg_disp[~mask_bg] = np.nan + + cmap_base = cm.get_cmap(cmap).with_extremes(bad="black") + cmap_div = "RdBu_r" + + disp = [im - np.mean(im_bg), bg_disp, im_sub_np] + norm = [ + { + "interval_type": "manual", + "stretch_type": "linear", + "vmin": vmin_sub, + "vmax": vmax_sub, + }, + { + "interval_type": "manual", + "stretch_type": "linear", + "vmin": vmin_sub, + "vmax": vmax_sub, + }, + { + "interval_type": "centered", + "stretch_type": "linear", + "vcenter": 0.0, + "half_range": vrange, + }, + ] + + show_2d( + disp, + cmap=[cmap_base, cmap_base, cmap_div], + norm=norm, + cbar=[False, False, True], + title=["Input Image", "Background (fit region)", "Background Subtracted"], + axsize=axsize, + **show_kwargs, + ) + + # # preserve if needed + # if isinstance( + # image, + # ): + # meta = dict(origin=image.origin, sampling=image.sampling, units=image.units) + # name_base = getattr(image, "name", "image") + # im_sub: ImageType = .from_array(im_sub_np, name=f"{name_base} (bg-sub)", **meta) # type: ignore[assignment] + # else: + im_sub = im_sub_np # type: ignore[assignment] + + if return_background_and_mask: + return im_sub, im_bg, mask_bg + return im_sub diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index a506d128..ebba944a 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -43,7 +43,8 @@ def __init__( self, tilt_stack: Dataset3d | NDArray | torch.Tensor, tilt_angles: NDArray | torch.Tensor, - learn_pose: bool = True, + learn_shift: bool = True, + learn_tilt_axis: bool = True, _token: object | None = None, ): AutoSerialize.__init__(self) @@ -71,7 +72,8 @@ def __init__( self.tilt_stack = tilt_stack self.tilt_angles = tilt_angles - self.learn_pose = learn_pose + self.learn_shift = learn_shift + self.learn_tilt_axis = learn_tilt_axis # The reference tilt angle is the one with the smallest absolute tilt angle. # I.e, the pose will not be optimized for the reference tilt angle. @@ -94,9 +96,15 @@ def from_data( cls, tilt_stack: Dataset3d | NDArray | torch.Tensor, tilt_angles: NDArray | torch.Tensor, - learn_pose: bool = False, + learn_shift: bool = True, + learn_tilt_axis: bool = True, ): - return cls(tilt_stack=tilt_stack, tilt_angles=tilt_angles, learn_pose=learn_pose) + return cls( + tilt_stack=tilt_stack, + tilt_angles=tilt_angles, + learn_shift=learn_shift, + learn_tilt_axis=learn_tilt_axis, + ) # --- Optimization Parameters --- # @property @@ -133,12 +141,20 @@ def tilt_angles(self, tilt_angles: torch.Tensor): self._tilt_angles = tilt_angles @property - def learn_pose(self) -> bool: - return self._learn_pose + def learn_shift(self) -> bool: + return self._learn_shift + + @learn_shift.setter + def learn_shift(self, learn_shift: bool): + self._learn_shift = learn_shift + + @property + def learn_tilt_axis(self) -> bool: + return self._learn_tilt_axis - @learn_pose.setter - def learn_pose(self, learn_pose: bool): - self._learn_pose = learn_pose + @learn_tilt_axis.setter + def learn_tilt_axis(self, learn_tilt_axis: bool): + self._learn_tilt_axis = learn_tilt_axis @property def reference_tilt_idx(self) -> int: @@ -235,11 +251,16 @@ def __init__( self, tilt_stack: Dataset3d | NDArray | torch.Tensor, tilt_angles: NDArray | torch.Tensor, - learn_pose: bool = False, + learn_shift: bool = True, + learn_tilt_axis: bool = True, _token: object | None = None, ): super().__init__( - tilt_stack=tilt_stack, tilt_angles=tilt_angles, learn_pose=learn_pose, _token=_token + tilt_stack=tilt_stack, + tilt_angles=tilt_angles, + learn_shift=learn_shift, + learn_tilt_axis=learn_tilt_axis, + _token=_token, ) def forward( @@ -272,11 +293,12 @@ def __init__( self, tilt_stack: Dataset3d | NDArray | torch.Tensor, tilt_angles: NDArray | torch.Tensor, - learn_pose: bool = True, + learn_shift: bool = True, + learn_tilt_axis: bool = True, seed: int = 42, token: object | None = None, ): - super().__init__(tilt_stack, tilt_angles, learn_pose, token) + super().__init__(tilt_stack, tilt_angles, learn_shift, learn_tilt_axis, token) # --- Forward Pass w/ Params Method for OptimizerMixin --- def forward(self, dummy_input: Any = None): @@ -297,13 +319,16 @@ def forward(self, dummy_input: Any = None): second_half_z3 = self.z3_params[self.reference_tilt_idx :] z3 = torch.cat([first_half_z3, self._z3_ref, second_half_z3], dim=0) - # return DatasetValue( - # target=None, - # tilt_angle=None, - # pixel_loc=None, - # pose=(shifts, z1, z3), - # ) - return shifts, z1, z3 + if self.learn_shift and self.learn_tilt_axis: + return shifts, z1, z3 + elif self.learn_shift: + return shifts, torch.zeros_like(z1), torch.zeros_like(z3) + elif self.learn_tilt_axis: + return torch.zeros_like(shifts), z1, z3 + elif self.learn_shift and self.learn_tilt_axis: + return shifts, z1, z3 + else: + return torch.zeros_like(shifts), torch.zeros_like(z1), torch.zeros_like(z3) @property def params(self): From da6bbf9ebe270b94515fb73f6e62960f775416b6 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Sat, 31 Jan 2026 23:35:50 -0800 Subject: [PATCH 058/335] Fix for imaging_utils.py --- src/quantem/core/utils/imaging_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/quantem/core/utils/imaging_utils.py b/src/quantem/core/utils/imaging_utils.py index 362bfe7e..6c081ae4 100644 --- a/src/quantem/core/utils/imaging_utils.py +++ b/src/quantem/core/utils/imaging_utils.py @@ -1,7 +1,7 @@ # Utilities for processing images import math -from typing import Any, Optional, Tuple, TypeVar +from typing import Any, Optional, Tuple import numpy as np import torch @@ -15,7 +15,7 @@ from quantem.core.utils.utils import generate_batches from quantem.core.visualization import show_2d -ImageType = TypeVar("ImageType", NDArray[Any]) +ImageType = NDArray[Any] BoolArray = NDArray[np.bool_] From 0d6d7697d2f9ed5b430c1dc0dc6962cbe0741e07 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Sun, 1 Feb 2026 00:26:47 -0800 Subject: [PATCH 059/335] DDP fix validation sampler initialization. --- src/quantem/core/ml/ddp.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/quantem/core/ml/ddp.py b/src/quantem/core/ml/ddp.py index df43c2e5..01daa41f 100644 --- a/src/quantem/core/ml/ddp.py +++ b/src/quantem/core/ml/ddp.py @@ -86,7 +86,10 @@ def setup_dataloader( rank=self.global_rank, shuffle=False, ) + else: + val_sampler = None shuffle = False + else: train_sampler = None From 4005d7a2ba17a90ab5185b6a8ab1e265b334d777 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Sun, 1 Feb 2026 23:22:29 -0800 Subject: [PATCH 060/335] Save volume DDP fix --- src/quantem/core/ml/constraints.py | 8 ++++++-- src/quantem/tomography/tomography.py | 6 +++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/quantem/core/ml/constraints.py b/src/quantem/core/ml/constraints.py index 990c240e..5b19293f 100644 --- a/src/quantem/core/ml/constraints.py +++ b/src/quantem/core/ml/constraints.py @@ -33,12 +33,16 @@ def __str__(self) -> str: hard = "\n".join(f"{key}: {getattr(self, key)}" for key in self.hard_constraint_keys) soft = "\n".join(f"{key}: {getattr(self, key)}" for key in self.soft_constraint_keys) + # Fix: Move the replace operations outside the f-string or assign to variables + hard_indented = hard.replace('\n', '\n ') + soft_indented = soft.replace('\n', '\n ') + return ( "Constraints:\n" " Hard constraints:\n" - f" {hard.replace('\n', '\n ')}\n" + f" {hard_indented}\n" " Soft constraints:\n" - f" {soft.replace('\n', '\n ')}" + f" {soft_indented}" ) diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index aaf48d17..32b94807 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -315,11 +315,11 @@ def save_volume(self, path: str = "recon_volume.npz"): # TODO: Temporary, need to talk to Arthur what the correct way of saving results is. if self.global_rank == 0: print(f"Saving volume to {path}") - - self.obj_model.create_volume() - np.savez(path, volume=self.obj_model.obj.detach().cpu().numpy()) + if torch.distributed.is_initialized(): + print("Barrier") + torch.distributed.barrier() class TomographyConventional(TomographyBase): """ From 4d08112bf4f4662d9b59f2c6620273b49fa7f5f6 Mon Sep 17 00:00:00 2001 From: quantem-bot Date: Mon, 2 Feb 2026 09:59:10 +0000 Subject: [PATCH 061/335] chore: update lock file --- uv.lock | 855 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 446 insertions(+), 409 deletions(-) diff --git a/uv.lock b/uv.lock index 7ab7af26..d6170093 100644 --- a/uv.lock +++ b/uv.lock @@ -15,25 +15,25 @@ members = [ [[package]] name = "absl-py" -version = "2.3.1" +version = "2.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/10/2a/c93173ffa1b39c1d0395b7e842bbdc62e556ca9d8d3b5572926f3e4ca752/absl_py-2.3.1.tar.gz", hash = "sha256:a97820526f7fbfd2ec1bce83f3f25e3a14840dac0d8e02a0b71cd75db3f77fc9", size = 116588, upload-time = "2025-07-03T09:31:44.05Z" } +sdist = { url = "https://files.pythonhosted.org/packages/64/c7/8de93764ad66968d19329a7e0c147a2bb3c7054c554d4a119111b8f9440f/absl_py-2.4.0.tar.gz", hash = "sha256:8c6af82722b35cf71e0f4d1d47dcaebfff286e27110a99fc359349b247dfb5d4", size = 116543, upload-time = "2026-01-28T10:17:05.322Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/aa/ba0014cc4659328dc818a28827be78e6d97312ab0cb98105a770924dc11e/absl_py-2.3.1-py3-none-any.whl", hash = "sha256:eeecf07f0c2a93ace0772c92e596ace6d3d3996c042b2128459aaae2a76de11d", size = 135811, upload-time = "2025-07-03T09:31:42.253Z" }, + { url = "https://files.pythonhosted.org/packages/18/a6/907a406bb7d359e6a63f99c313846d9eec4f7e6f7437809e03aa00fa3074/absl_py-2.4.0-py3-none-any.whl", hash = "sha256:88476fd881ca8aab94ffa78b7b6c632a782ab3ba1cd19c9bd423abc4fb4cd28d", size = 135750, upload-time = "2026-01-28T10:17:04.19Z" }, ] [[package]] name = "alembic" -version = "1.18.1" +version = "1.18.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mako" }, { name = "sqlalchemy" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/49/cc/aca263693b2ece99fa99a09b6d092acb89973eb2bb575faef1777e04f8b4/alembic-1.18.1.tar.gz", hash = "sha256:83ac6b81359596816fb3b893099841a0862f2117b2963258e965d70dc62fb866", size = 2044319, upload-time = "2026-01-14T18:53:14.907Z" } +sdist = { url = "https://files.pythonhosted.org/packages/79/41/ab8f624929847b49f84955c594b165855efd829b0c271e1a8cac694138e5/alembic-1.18.3.tar.gz", hash = "sha256:1212aa3778626f2b0f0aa6dd4e99a5f99b94bd25a0c1ac0bba3be65e081e50b0", size = 2052564, upload-time = "2026-01-29T20:24:15.124Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/36/cd9cb6101e81e39076b2fbe303bfa3c85ca34e55142b0324fcbf22c5c6e2/alembic-1.18.1-py3-none-any.whl", hash = "sha256:f1c3b0920b87134e851c25f1f7f236d8a332c34b75416802d06971df5d1b7810", size = 260973, upload-time = "2026-01-14T18:53:17.533Z" }, + { url = "https://files.pythonhosted.org/packages/45/8e/d79281f323e7469b060f15bd229e48d7cdd219559e67e71c013720a88340/alembic-1.18.3-py3-none-any.whl", hash = "sha256:12a0359bfc068a4ecbb9b3b02cf77856033abfdb59e4a5aca08b7eacd7b74ddd", size = 262282, upload-time = "2026-01-29T20:24:17.488Z" }, ] [[package]] @@ -157,11 +157,11 @@ wheels = [ [[package]] name = "babel" -version = "2.17.0" +version = "2.18.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, ] [[package]] @@ -516,89 +516,89 @@ wheels = [ [[package]] name = "coverage" -version = "7.13.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/23/f9/e92df5e07f3fc8d4c7f9a0f146ef75446bf870351cd37b788cf5897f8079/coverage-7.13.1.tar.gz", hash = "sha256:b7593fe7eb5feaa3fbb461ac79aac9f9fc0387a5ca8080b0c6fe2ca27b091afd", size = 825862, upload-time = "2025-12-28T15:42:56.969Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/9b/77baf488516e9ced25fc215a6f75d803493fc3f6a1a1227ac35697910c2a/coverage-7.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a55d509a1dc5a5b708b5dad3b5334e07a16ad4c2185e27b40e4dba796ab7f88", size = 218755, upload-time = "2025-12-28T15:40:30.812Z" }, - { url = "https://files.pythonhosted.org/packages/d7/cd/7ab01154e6eb79ee2fab76bf4d89e94c6648116557307ee4ebbb85e5c1bf/coverage-7.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d010d080c4888371033baab27e47c9df7d6fb28d0b7b7adf85a4a49be9298b3", size = 219257, upload-time = "2025-12-28T15:40:32.333Z" }, - { url = "https://files.pythonhosted.org/packages/01/d5/b11ef7863ffbbdb509da0023fad1e9eda1c0eaea61a6d2ea5b17d4ac706e/coverage-7.13.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d938b4a840fb1523b9dfbbb454f652967f18e197569c32266d4d13f37244c3d9", size = 249657, upload-time = "2025-12-28T15:40:34.1Z" }, - { url = "https://files.pythonhosted.org/packages/f7/7c/347280982982383621d29b8c544cf497ae07ac41e44b1ca4903024131f55/coverage-7.13.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bf100a3288f9bb7f919b87eb84f87101e197535b9bd0e2c2b5b3179633324fee", size = 251581, upload-time = "2025-12-28T15:40:36.131Z" }, - { url = "https://files.pythonhosted.org/packages/82/f6/ebcfed11036ade4c0d75fa4453a6282bdd225bc073862766eec184a4c643/coverage-7.13.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef6688db9bf91ba111ae734ba6ef1a063304a881749726e0d3575f5c10a9facf", size = 253691, upload-time = "2025-12-28T15:40:37.626Z" }, - { url = "https://files.pythonhosted.org/packages/02/92/af8f5582787f5d1a8b130b2dcba785fa5e9a7a8e121a0bb2220a6fdbdb8a/coverage-7.13.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0b609fc9cdbd1f02e51f67f51e5aee60a841ef58a68d00d5ee2c0faf357481a3", size = 249799, upload-time = "2025-12-28T15:40:39.47Z" }, - { url = "https://files.pythonhosted.org/packages/24/aa/0e39a2a3b16eebf7f193863323edbff38b6daba711abaaf807d4290cf61a/coverage-7.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c43257717611ff5e9a1d79dce8e47566235ebda63328718d9b65dd640bc832ef", size = 251389, upload-time = "2025-12-28T15:40:40.954Z" }, - { url = "https://files.pythonhosted.org/packages/73/46/7f0c13111154dc5b978900c0ccee2e2ca239b910890e674a77f1363d483e/coverage-7.13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e09fbecc007f7b6afdfb3b07ce5bd9f8494b6856dd4f577d26c66c391b829851", size = 249450, upload-time = "2025-12-28T15:40:42.489Z" }, - { url = "https://files.pythonhosted.org/packages/ac/ca/e80da6769e8b669ec3695598c58eef7ad98b0e26e66333996aee6316db23/coverage-7.13.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a03a4f3a19a189919c7055098790285cc5c5b0b3976f8d227aea39dbf9f8bfdb", size = 249170, upload-time = "2025-12-28T15:40:44.279Z" }, - { url = "https://files.pythonhosted.org/packages/af/18/9e29baabdec1a8644157f572541079b4658199cfd372a578f84228e860de/coverage-7.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3820778ea1387c2b6a818caec01c63adc5b3750211af6447e8dcfb9b6f08dbba", size = 250081, upload-time = "2025-12-28T15:40:45.748Z" }, - { url = "https://files.pythonhosted.org/packages/00/f8/c3021625a71c3b2f516464d322e41636aea381018319050a8114105872ee/coverage-7.13.1-cp311-cp311-win32.whl", hash = "sha256:ff10896fa55167371960c5908150b434b71c876dfab97b69478f22c8b445ea19", size = 221281, upload-time = "2025-12-28T15:40:47.232Z" }, - { url = "https://files.pythonhosted.org/packages/27/56/c216625f453df6e0559ed666d246fcbaaa93f3aa99eaa5080cea1229aa3d/coverage-7.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:a998cc0aeeea4c6d5622a3754da5a493055d2d95186bad877b0a34ea6e6dbe0a", size = 222215, upload-time = "2025-12-28T15:40:49.19Z" }, - { url = "https://files.pythonhosted.org/packages/5c/9a/be342e76f6e531cae6406dc46af0d350586f24d9b67fdfa6daee02df71af/coverage-7.13.1-cp311-cp311-win_arm64.whl", hash = "sha256:fea07c1a39a22614acb762e3fbbb4011f65eedafcb2948feeef641ac78b4ee5c", size = 220886, upload-time = "2025-12-28T15:40:51.067Z" }, - { url = "https://files.pythonhosted.org/packages/ce/8a/87af46cccdfa78f53db747b09f5f9a21d5fc38d796834adac09b30a8ce74/coverage-7.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6f34591000f06e62085b1865c9bc5f7858df748834662a51edadfd2c3bfe0dd3", size = 218927, upload-time = "2025-12-28T15:40:52.814Z" }, - { url = "https://files.pythonhosted.org/packages/82/a8/6e22fdc67242a4a5a153f9438d05944553121c8f4ba70cb072af4c41362e/coverage-7.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b67e47c5595b9224599016e333f5ec25392597a89d5744658f837d204e16c63e", size = 219288, upload-time = "2025-12-28T15:40:54.262Z" }, - { url = "https://files.pythonhosted.org/packages/d0/0a/853a76e03b0f7c4375e2ca025df45c918beb367f3e20a0a8e91967f6e96c/coverage-7.13.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e7b8bd70c48ffb28461ebe092c2345536fb18bbbf19d287c8913699735f505c", size = 250786, upload-time = "2025-12-28T15:40:56.059Z" }, - { url = "https://files.pythonhosted.org/packages/ea/b4/694159c15c52b9f7ec7adf49d50e5f8ee71d3e9ef38adb4445d13dd56c20/coverage-7.13.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c223d078112e90dc0e5c4e35b98b9584164bea9fbbd221c0b21c5241f6d51b62", size = 253543, upload-time = "2025-12-28T15:40:57.585Z" }, - { url = "https://files.pythonhosted.org/packages/96/b2/7f1f0437a5c855f87e17cf5d0dc35920b6440ff2b58b1ba9788c059c26c8/coverage-7.13.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:794f7c05af0763b1bbd1b9e6eff0e52ad068be3b12cd96c87de037b01390c968", size = 254635, upload-time = "2025-12-28T15:40:59.443Z" }, - { url = "https://files.pythonhosted.org/packages/e9/d1/73c3fdb8d7d3bddd9473c9c6a2e0682f09fc3dfbcb9c3f36412a7368bcab/coverage-7.13.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0642eae483cc8c2902e4af7298bf886d605e80f26382124cddc3967c2a3df09e", size = 251202, upload-time = "2025-12-28T15:41:01.328Z" }, - { url = "https://files.pythonhosted.org/packages/66/3c/f0edf75dcc152f145d5598329e864bbbe04ab78660fe3e8e395f9fff010f/coverage-7.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5e772ed5fef25b3de9f2008fe67b92d46831bd2bc5bdc5dd6bfd06b83b316f", size = 252566, upload-time = "2025-12-28T15:41:03.319Z" }, - { url = "https://files.pythonhosted.org/packages/17/b3/e64206d3c5f7dcbceafd14941345a754d3dbc78a823a6ed526e23b9cdaab/coverage-7.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:45980ea19277dc0a579e432aef6a504fe098ef3a9032ead15e446eb0f1191aee", size = 250711, upload-time = "2025-12-28T15:41:06.411Z" }, - { url = "https://files.pythonhosted.org/packages/dc/ad/28a3eb970a8ef5b479ee7f0c484a19c34e277479a5b70269dc652b730733/coverage-7.13.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f18eca6028ffa62adbd185a8f1e1dd242f2e68164dba5c2b74a5204850b4cf", size = 250278, upload-time = "2025-12-28T15:41:08.285Z" }, - { url = "https://files.pythonhosted.org/packages/54/e3/c8f0f1a93133e3e1291ca76cbb63565bd4b5c5df63b141f539d747fff348/coverage-7.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8dca5590fec7a89ed6826fce625595279e586ead52e9e958d3237821fbc750c", size = 252154, upload-time = "2025-12-28T15:41:09.969Z" }, - { url = "https://files.pythonhosted.org/packages/d0/bf/9939c5d6859c380e405b19e736321f1c7d402728792f4c752ad1adcce005/coverage-7.13.1-cp312-cp312-win32.whl", hash = "sha256:ff86d4e85188bba72cfb876df3e11fa243439882c55957184af44a35bd5880b7", size = 221487, upload-time = "2025-12-28T15:41:11.468Z" }, - { url = "https://files.pythonhosted.org/packages/fa/dc/7282856a407c621c2aad74021680a01b23010bb8ebf427cf5eacda2e876f/coverage-7.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:16cc1da46c04fb0fb128b4dc430b78fa2aba8a6c0c9f8eb391fd5103409a6ac6", size = 222299, upload-time = "2025-12-28T15:41:13.386Z" }, - { url = "https://files.pythonhosted.org/packages/10/79/176a11203412c350b3e9578620013af35bcdb79b651eb976f4a4b32044fa/coverage-7.13.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d9bc218650022a768f3775dd7fdac1886437325d8d295d923ebcfef4892ad5c", size = 220941, upload-time = "2025-12-28T15:41:14.975Z" }, - { url = "https://files.pythonhosted.org/packages/a3/a4/e98e689347a1ff1a7f67932ab535cef82eb5e78f32a9e4132e114bbb3a0a/coverage-7.13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cb237bfd0ef4d5eb6a19e29f9e528ac67ac3be932ea6b44fb6cc09b9f3ecff78", size = 218951, upload-time = "2025-12-28T15:41:16.653Z" }, - { url = "https://files.pythonhosted.org/packages/32/33/7cbfe2bdc6e2f03d6b240d23dc45fdaf3fd270aaf2d640be77b7f16989ab/coverage-7.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1dcb645d7e34dcbcc96cd7c132b1fc55c39263ca62eb961c064eb3928997363b", size = 219325, upload-time = "2025-12-28T15:41:18.609Z" }, - { url = "https://files.pythonhosted.org/packages/59/f6/efdabdb4929487baeb7cb2a9f7dac457d9356f6ad1b255be283d58b16316/coverage-7.13.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3d42df8201e00384736f0df9be2ced39324c3907607d17d50d50116c989d84cd", size = 250309, upload-time = "2025-12-28T15:41:20.629Z" }, - { url = "https://files.pythonhosted.org/packages/12/da/91a52516e9d5aea87d32d1523f9cdcf7a35a3b298e6be05d6509ba3cfab2/coverage-7.13.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa3edde1aa8807de1d05934982416cb3ec46d1d4d91e280bcce7cca01c507992", size = 252907, upload-time = "2025-12-28T15:41:22.257Z" }, - { url = "https://files.pythonhosted.org/packages/75/38/f1ea837e3dc1231e086db1638947e00d264e7e8c41aa8ecacf6e1e0c05f4/coverage-7.13.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9edd0e01a343766add6817bc448408858ba6b489039eaaa2018474e4001651a4", size = 254148, upload-time = "2025-12-28T15:41:23.87Z" }, - { url = "https://files.pythonhosted.org/packages/7f/43/f4f16b881aaa34954ba446318dea6b9ed5405dd725dd8daac2358eda869a/coverage-7.13.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:985b7836931d033570b94c94713c6dba5f9d3ff26045f72c3e5dbc5fe3361e5a", size = 250515, upload-time = "2025-12-28T15:41:25.437Z" }, - { url = "https://files.pythonhosted.org/packages/84/34/8cba7f00078bd468ea914134e0144263194ce849ec3baad187ffb6203d1c/coverage-7.13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ffed1e4980889765c84a5d1a566159e363b71d6b6fbaf0bebc9d3c30bc016766", size = 252292, upload-time = "2025-12-28T15:41:28.459Z" }, - { url = "https://files.pythonhosted.org/packages/8c/a4/cffac66c7652d84ee4ac52d3ccb94c015687d3b513f9db04bfcac2ac800d/coverage-7.13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8842af7f175078456b8b17f1b73a0d16a65dcbdc653ecefeb00a56b3c8c298c4", size = 250242, upload-time = "2025-12-28T15:41:30.02Z" }, - { url = "https://files.pythonhosted.org/packages/f4/78/9a64d462263dde416f3c0067efade7b52b52796f489b1037a95b0dc389c9/coverage-7.13.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:ccd7a6fca48ca9c131d9b0a2972a581e28b13416fc313fb98b6d24a03ce9a398", size = 250068, upload-time = "2025-12-28T15:41:32.007Z" }, - { url = "https://files.pythonhosted.org/packages/69/c8/a8994f5fece06db7c4a97c8fc1973684e178599b42e66280dded0524ef00/coverage-7.13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0403f647055de2609be776965108447deb8e384fe4a553c119e3ff6bfbab4784", size = 251846, upload-time = "2025-12-28T15:41:33.946Z" }, - { url = "https://files.pythonhosted.org/packages/cc/f7/91fa73c4b80305c86598a2d4e54ba22df6bf7d0d97500944af7ef155d9f7/coverage-7.13.1-cp313-cp313-win32.whl", hash = "sha256:549d195116a1ba1e1ae2f5ca143f9777800f6636eab917d4f02b5310d6d73461", size = 221512, upload-time = "2025-12-28T15:41:35.519Z" }, - { url = "https://files.pythonhosted.org/packages/45/0b/0768b4231d5a044da8f75e097a8714ae1041246bb765d6b5563bab456735/coverage-7.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:5899d28b5276f536fcf840b18b61a9fce23cc3aec1d114c44c07fe94ebeaa500", size = 222321, upload-time = "2025-12-28T15:41:37.371Z" }, - { url = "https://files.pythonhosted.org/packages/9b/b8/bdcb7253b7e85157282450262008f1366aa04663f3e3e4c30436f596c3e2/coverage-7.13.1-cp313-cp313-win_arm64.whl", hash = "sha256:868a2fae76dfb06e87291bcbd4dcbcc778a8500510b618d50496e520bd94d9b9", size = 220949, upload-time = "2025-12-28T15:41:39.553Z" }, - { url = "https://files.pythonhosted.org/packages/70/52/f2be52cc445ff75ea8397948c96c1b4ee14f7f9086ea62fc929c5ae7b717/coverage-7.13.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:67170979de0dacac3f3097d02b0ad188d8edcea44ccc44aaa0550af49150c7dc", size = 219643, upload-time = "2025-12-28T15:41:41.567Z" }, - { url = "https://files.pythonhosted.org/packages/47/79/c85e378eaa239e2edec0c5523f71542c7793fe3340954eafb0bc3904d32d/coverage-7.13.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f80e2bb21bfab56ed7405c2d79d34b5dc0bc96c2c1d2a067b643a09fb756c43a", size = 219997, upload-time = "2025-12-28T15:41:43.418Z" }, - { url = "https://files.pythonhosted.org/packages/fe/9b/b1ade8bfb653c0bbce2d6d6e90cc6c254cbb99b7248531cc76253cb4da6d/coverage-7.13.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f83351e0f7dcdb14d7326c3d8d8c4e915fa685cbfdc6281f9470d97a04e9dfe4", size = 261296, upload-time = "2025-12-28T15:41:45.207Z" }, - { url = "https://files.pythonhosted.org/packages/1f/af/ebf91e3e1a2473d523e87e87fd8581e0aa08741b96265730e2d79ce78d8d/coverage-7.13.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb3f6562e89bad0110afbe64e485aac2462efdce6232cdec7862a095dc3412f6", size = 263363, upload-time = "2025-12-28T15:41:47.163Z" }, - { url = "https://files.pythonhosted.org/packages/c4/8b/fb2423526d446596624ac7fde12ea4262e66f86f5120114c3cfd0bb2befa/coverage-7.13.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77545b5dcda13b70f872c3b5974ac64c21d05e65b1590b441c8560115dc3a0d1", size = 265783, upload-time = "2025-12-28T15:41:49.03Z" }, - { url = "https://files.pythonhosted.org/packages/9b/26/ef2adb1e22674913b89f0fe7490ecadcef4a71fa96f5ced90c60ec358789/coverage-7.13.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4d240d260a1aed814790bbe1f10a5ff31ce6c21bc78f0da4a1e8268d6c80dbd", size = 260508, upload-time = "2025-12-28T15:41:51.035Z" }, - { url = "https://files.pythonhosted.org/packages/ce/7d/f0f59b3404caf662e7b5346247883887687c074ce67ba453ea08c612b1d5/coverage-7.13.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d2287ac9360dec3837bfdad969963a5d073a09a85d898bd86bea82aa8876ef3c", size = 263357, upload-time = "2025-12-28T15:41:52.631Z" }, - { url = "https://files.pythonhosted.org/packages/1a/b1/29896492b0b1a047604d35d6fa804f12818fa30cdad660763a5f3159e158/coverage-7.13.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0d2c11f3ea4db66b5cbded23b20185c35066892c67d80ec4be4bab257b9ad1e0", size = 260978, upload-time = "2025-12-28T15:41:54.589Z" }, - { url = "https://files.pythonhosted.org/packages/48/f2/971de1238a62e6f0a4128d37adadc8bb882ee96afbe03ff1570291754629/coverage-7.13.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:3fc6a169517ca0d7ca6846c3c5392ef2b9e38896f61d615cb75b9e7134d4ee1e", size = 259877, upload-time = "2025-12-28T15:41:56.263Z" }, - { url = "https://files.pythonhosted.org/packages/6a/fc/0474efcbb590ff8628830e9aaec5f1831594874360e3251f1fdec31d07a3/coverage-7.13.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d10a2ed46386e850bb3de503a54f9fe8192e5917fcbb143bfef653a9355e9a53", size = 262069, upload-time = "2025-12-28T15:41:58.093Z" }, - { url = "https://files.pythonhosted.org/packages/88/4f/3c159b7953db37a7b44c0eab8a95c37d1aa4257c47b4602c04022d5cb975/coverage-7.13.1-cp313-cp313t-win32.whl", hash = "sha256:75a6f4aa904301dab8022397a22c0039edc1f51e90b83dbd4464b8a38dc87842", size = 222184, upload-time = "2025-12-28T15:41:59.763Z" }, - { url = "https://files.pythonhosted.org/packages/58/a5/6b57d28f81417f9335774f20679d9d13b9a8fb90cd6160957aa3b54a2379/coverage-7.13.1-cp313-cp313t-win_amd64.whl", hash = "sha256:309ef5706e95e62578cda256b97f5e097916a2c26247c287bbe74794e7150df2", size = 223250, upload-time = "2025-12-28T15:42:01.52Z" }, - { url = "https://files.pythonhosted.org/packages/81/7c/160796f3b035acfbb58be80e02e484548595aa67e16a6345e7910ace0a38/coverage-7.13.1-cp313-cp313t-win_arm64.whl", hash = "sha256:92f980729e79b5d16d221038dbf2e8f9a9136afa072f9d5d6ed4cb984b126a09", size = 221521, upload-time = "2025-12-28T15:42:03.275Z" }, - { url = "https://files.pythonhosted.org/packages/aa/8e/ba0e597560c6563fc0adb902fda6526df5d4aa73bb10adf0574d03bd2206/coverage-7.13.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:97ab3647280d458a1f9adb85244e81587505a43c0c7cff851f5116cd2814b894", size = 218996, upload-time = "2025-12-28T15:42:04.978Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8e/764c6e116f4221dc7aa26c4061181ff92edb9c799adae6433d18eeba7a14/coverage-7.13.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8f572d989142e0908e6acf57ad1b9b86989ff057c006d13b76c146ec6a20216a", size = 219326, upload-time = "2025-12-28T15:42:06.691Z" }, - { url = "https://files.pythonhosted.org/packages/4f/a6/6130dc6d8da28cdcbb0f2bf8865aeca9b157622f7c0031e48c6cf9a0e591/coverage-7.13.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d72140ccf8a147e94274024ff6fd8fb7811354cf7ef88b1f0a988ebaa5bc774f", size = 250374, upload-time = "2025-12-28T15:42:08.786Z" }, - { url = "https://files.pythonhosted.org/packages/82/2b/783ded568f7cd6b677762f780ad338bf4b4750205860c17c25f7c708995e/coverage-7.13.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3c9f051b028810f5a87c88e5d6e9af3c0ff32ef62763bf15d29f740453ca909", size = 252882, upload-time = "2025-12-28T15:42:10.515Z" }, - { url = "https://files.pythonhosted.org/packages/cd/b2/9808766d082e6a4d59eb0cc881a57fc1600eb2c5882813eefff8254f71b5/coverage-7.13.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f398ba4df52d30b1763f62eed9de5620dcde96e6f491f4c62686736b155aa6e4", size = 254218, upload-time = "2025-12-28T15:42:12.208Z" }, - { url = "https://files.pythonhosted.org/packages/44/ea/52a985bb447c871cb4d2e376e401116520991b597c85afdde1ea9ef54f2c/coverage-7.13.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:132718176cc723026d201e347f800cd1a9e4b62ccd3f82476950834dad501c75", size = 250391, upload-time = "2025-12-28T15:42:14.21Z" }, - { url = "https://files.pythonhosted.org/packages/7f/1d/125b36cc12310718873cfc8209ecfbc1008f14f4f5fa0662aa608e579353/coverage-7.13.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e549d642426e3579b3f4b92d0431543b012dcb6e825c91619d4e93b7363c3f9", size = 252239, upload-time = "2025-12-28T15:42:16.292Z" }, - { url = "https://files.pythonhosted.org/packages/6a/16/10c1c164950cade470107f9f14bbac8485f8fb8515f515fca53d337e4a7f/coverage-7.13.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:90480b2134999301eea795b3a9dbf606c6fbab1b489150c501da84a959442465", size = 250196, upload-time = "2025-12-28T15:42:18.54Z" }, - { url = "https://files.pythonhosted.org/packages/2a/c6/cd860fac08780c6fd659732f6ced1b40b79c35977c1356344e44d72ba6c4/coverage-7.13.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e825dbb7f84dfa24663dd75835e7257f8882629fc11f03ecf77d84a75134b864", size = 250008, upload-time = "2025-12-28T15:42:20.365Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3a/a8c58d3d38f82a5711e1e0a67268362af48e1a03df27c03072ac30feefcf/coverage-7.13.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:623dcc6d7a7ba450bbdbeedbaa0c42b329bdae16491af2282f12a7e809be7eb9", size = 251671, upload-time = "2025-12-28T15:42:22.114Z" }, - { url = "https://files.pythonhosted.org/packages/f0/bc/fd4c1da651d037a1e3d53e8cb3f8182f4b53271ffa9a95a2e211bacc0349/coverage-7.13.1-cp314-cp314-win32.whl", hash = "sha256:6e73ebb44dca5f708dc871fe0b90cf4cff1a13f9956f747cc87b535a840386f5", size = 221777, upload-time = "2025-12-28T15:42:23.919Z" }, - { url = "https://files.pythonhosted.org/packages/4b/50/71acabdc8948464c17e90b5ffd92358579bd0910732c2a1c9537d7536aa6/coverage-7.13.1-cp314-cp314-win_amd64.whl", hash = "sha256:be753b225d159feb397bd0bf91ae86f689bad0da09d3b301478cd39b878ab31a", size = 222592, upload-time = "2025-12-28T15:42:25.619Z" }, - { url = "https://files.pythonhosted.org/packages/f7/c8/a6fb943081bb0cc926499c7907731a6dc9efc2cbdc76d738c0ab752f1a32/coverage-7.13.1-cp314-cp314-win_arm64.whl", hash = "sha256:228b90f613b25ba0019361e4ab81520b343b622fc657daf7e501c4ed6a2366c0", size = 221169, upload-time = "2025-12-28T15:42:27.629Z" }, - { url = "https://files.pythonhosted.org/packages/16/61/d5b7a0a0e0e40d62e59bc8c7aa1afbd86280d82728ba97f0673b746b78e2/coverage-7.13.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:60cfb538fe9ef86e5b2ab0ca8fc8d62524777f6c611dcaf76dc16fbe9b8e698a", size = 219730, upload-time = "2025-12-28T15:42:29.306Z" }, - { url = "https://files.pythonhosted.org/packages/a3/2c/8881326445fd071bb49514d1ce97d18a46a980712b51fee84f9ab42845b4/coverage-7.13.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:57dfc8048c72ba48a8c45e188d811e5efd7e49b387effc8fb17e97936dde5bf6", size = 220001, upload-time = "2025-12-28T15:42:31.319Z" }, - { url = "https://files.pythonhosted.org/packages/b5/d7/50de63af51dfa3a7f91cc37ad8fcc1e244b734232fbc8b9ab0f3c834a5cd/coverage-7.13.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3f2f725aa3e909b3c5fdb8192490bdd8e1495e85906af74fe6e34a2a77ba0673", size = 261370, upload-time = "2025-12-28T15:42:32.992Z" }, - { url = "https://files.pythonhosted.org/packages/e1/2c/d31722f0ec918fd7453b2758312729f645978d212b410cd0f7c2aed88a94/coverage-7.13.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ee68b21909686eeb21dfcba2c3b81fee70dcf38b140dcd5aa70680995fa3aa5", size = 263485, upload-time = "2025-12-28T15:42:34.759Z" }, - { url = "https://files.pythonhosted.org/packages/fa/7a/2c114fa5c5fc08ba0777e4aec4c97e0b4a1afcb69c75f1f54cff78b073ab/coverage-7.13.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724b1b270cb13ea2e6503476e34541a0b1f62280bc997eab443f87790202033d", size = 265890, upload-time = "2025-12-28T15:42:36.517Z" }, - { url = "https://files.pythonhosted.org/packages/65/d9/f0794aa1c74ceabc780fe17f6c338456bbc4e96bd950f2e969f48ac6fb20/coverage-7.13.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:916abf1ac5cf7eb16bc540a5bf75c71c43a676f5c52fcb9fe75a2bd75fb944e8", size = 260445, upload-time = "2025-12-28T15:42:38.646Z" }, - { url = "https://files.pythonhosted.org/packages/49/23/184b22a00d9bb97488863ced9454068c79e413cb23f472da6cbddc6cfc52/coverage-7.13.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:776483fd35b58d8afe3acbd9988d5de592ab6da2d2a865edfdbc9fdb43e7c486", size = 263357, upload-time = "2025-12-28T15:42:40.788Z" }, - { url = "https://files.pythonhosted.org/packages/7d/bd/58af54c0c9199ea4190284f389005779d7daf7bf3ce40dcd2d2b2f96da69/coverage-7.13.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b6f3b96617e9852703f5b633ea01315ca45c77e879584f283c44127f0f1ec564", size = 260959, upload-time = "2025-12-28T15:42:42.808Z" }, - { url = "https://files.pythonhosted.org/packages/4b/2a/6839294e8f78a4891bf1df79d69c536880ba2f970d0ff09e7513d6e352e9/coverage-7.13.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd63e7b74661fed317212fab774e2a648bc4bb09b35f25474f8e3325d2945cd7", size = 259792, upload-time = "2025-12-28T15:42:44.818Z" }, - { url = "https://files.pythonhosted.org/packages/ba/c3/528674d4623283310ad676c5af7414b9850ab6d55c2300e8aa4b945ec554/coverage-7.13.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:933082f161bbb3e9f90d00990dc956120f608cdbcaeea15c4d897f56ef4fe416", size = 262123, upload-time = "2025-12-28T15:42:47.108Z" }, - { url = "https://files.pythonhosted.org/packages/06/c5/8c0515692fb4c73ac379d8dc09b18eaf0214ecb76ea6e62467ba7a1556ff/coverage-7.13.1-cp314-cp314t-win32.whl", hash = "sha256:18be793c4c87de2965e1c0f060f03d9e5aff66cfeae8e1dbe6e5b88056ec153f", size = 222562, upload-time = "2025-12-28T15:42:49.144Z" }, - { url = "https://files.pythonhosted.org/packages/05/0e/c0a0c4678cb30dac735811db529b321d7e1c9120b79bd728d4f4d6b010e9/coverage-7.13.1-cp314-cp314t-win_amd64.whl", hash = "sha256:0e42e0ec0cd3e0d851cb3c91f770c9301f48647cb2877cb78f74bdaa07639a79", size = 223670, upload-time = "2025-12-28T15:42:51.218Z" }, - { url = "https://files.pythonhosted.org/packages/f5/5f/b177aa0011f354abf03a8f30a85032686d290fdeed4222b27d36b4372a50/coverage-7.13.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eaecf47ef10c72ece9a2a92118257da87e460e113b83cc0d2905cbbe931792b4", size = 221707, upload-time = "2025-12-28T15:42:53.034Z" }, - { url = "https://files.pythonhosted.org/packages/cc/48/d9f421cb8da5afaa1a64570d9989e00fb7955e6acddc5a12979f7666ef60/coverage-7.13.1-py3-none-any.whl", hash = "sha256:2016745cb3ba554469d02819d78958b571792bb68e31302610e898f80dd3a573", size = 210722, upload-time = "2025-12-28T15:42:54.901Z" }, +version = "7.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ad/49/349848445b0e53660e258acbcc9b0d014895b6739237920886672240f84b/coverage-7.13.2.tar.gz", hash = "sha256:044c6951ec37146b72a50cc81ef02217d27d4c3640efd2640311393cbbf143d3", size = 826523, upload-time = "2026-01-25T13:00:04.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/01/abca50583a8975bb6e1c59eff67ed8e48bb127c07dad5c28d9e96ccc09ec/coverage-7.13.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:060ebf6f2c51aff5ba38e1f43a2095e087389b1c69d559fde6049a4b0001320e", size = 218971, upload-time = "2026-01-25T12:57:36.953Z" }, + { url = "https://files.pythonhosted.org/packages/eb/0e/b6489f344d99cd1e5b4d5e1be52dfd3f8a3dc5112aa6c33948da8cabad4e/coverage-7.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1ea8ca9db5e7469cd364552985e15911548ea5b69c48a17291f0cac70484b2e", size = 219473, upload-time = "2026-01-25T12:57:38.934Z" }, + { url = "https://files.pythonhosted.org/packages/17/11/db2f414915a8e4ec53f60b17956c27f21fb68fcf20f8a455ce7c2ccec638/coverage-7.13.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b780090d15fd58f07cf2011943e25a5f0c1c894384b13a216b6c86c8a8a7c508", size = 249896, upload-time = "2026-01-25T12:57:40.365Z" }, + { url = "https://files.pythonhosted.org/packages/80/06/0823fe93913663c017e508e8810c998c8ebd3ec2a5a85d2c3754297bdede/coverage-7.13.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:88a800258d83acb803c38175b4495d293656d5fac48659c953c18e5f539a274b", size = 251810, upload-time = "2026-01-25T12:57:42.045Z" }, + { url = "https://files.pythonhosted.org/packages/61/dc/b151c3cc41b28cdf7f0166c5fa1271cbc305a8ec0124cce4b04f74791a18/coverage-7.13.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6326e18e9a553e674d948536a04a80d850a5eeefe2aae2e6d7cf05d54046c01b", size = 253920, upload-time = "2026-01-25T12:57:44.026Z" }, + { url = "https://files.pythonhosted.org/packages/2d/35/e83de0556e54a4729a2b94ea816f74ce08732e81945024adee46851c2264/coverage-7.13.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:59562de3f797979e1ff07c587e2ac36ba60ca59d16c211eceaa579c266c5022f", size = 250025, upload-time = "2026-01-25T12:57:45.624Z" }, + { url = "https://files.pythonhosted.org/packages/39/67/af2eb9c3926ce3ea0d58a0d2516fcbdacf7a9fc9559fe63076beaf3f2596/coverage-7.13.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:27ba1ed6f66b0e2d61bfa78874dffd4f8c3a12f8e2b5410e515ab345ba7bc9c3", size = 251612, upload-time = "2026-01-25T12:57:47.713Z" }, + { url = "https://files.pythonhosted.org/packages/26/62/5be2e25f3d6c711d23b71296f8b44c978d4c8b4e5b26871abfc164297502/coverage-7.13.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8be48da4d47cc68754ce643ea50b3234557cbefe47c2f120495e7bd0a2756f2b", size = 249670, upload-time = "2026-01-25T12:57:49.378Z" }, + { url = "https://files.pythonhosted.org/packages/b3/51/400d1b09a8344199f9b6a6fc1868005d766b7ea95e7882e494fa862ca69c/coverage-7.13.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2a47a4223d3361b91176aedd9d4e05844ca67d7188456227b6bf5e436630c9a1", size = 249395, upload-time = "2026-01-25T12:57:50.86Z" }, + { url = "https://files.pythonhosted.org/packages/e0/36/f02234bc6e5230e2f0a63fd125d0a2093c73ef20fdf681c7af62a140e4e7/coverage-7.13.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c6f141b468740197d6bd38f2b26ade124363228cc3f9858bd9924ab059e00059", size = 250298, upload-time = "2026-01-25T12:57:52.287Z" }, + { url = "https://files.pythonhosted.org/packages/b0/06/713110d3dd3151b93611c9cbfc65c15b4156b44f927fced49ac0b20b32a4/coverage-7.13.2-cp311-cp311-win32.whl", hash = "sha256:89567798404af067604246e01a49ef907d112edf2b75ef814b1364d5ce267031", size = 221485, upload-time = "2026-01-25T12:57:53.876Z" }, + { url = "https://files.pythonhosted.org/packages/16/0c/3ae6255fa1ebcb7dec19c9a59e85ef5f34566d1265c70af5b2fc981da834/coverage-7.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:21dd57941804ae2ac7e921771a5e21bbf9aabec317a041d164853ad0a96ce31e", size = 222421, upload-time = "2026-01-25T12:57:55.433Z" }, + { url = "https://files.pythonhosted.org/packages/b5/37/fabc3179af4d61d89ea47bd04333fec735cd5e8b59baad44fed9fc4170d7/coverage-7.13.2-cp311-cp311-win_arm64.whl", hash = "sha256:10758e0586c134a0bafa28f2d37dd2cdb5e4a90de25c0fc0c77dabbad46eca28", size = 221088, upload-time = "2026-01-25T12:57:57.41Z" }, + { url = "https://files.pythonhosted.org/packages/46/39/e92a35f7800222d3f7b2cbb7bbc3b65672ae8d501cb31801b2d2bd7acdf1/coverage-7.13.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f106b2af193f965d0d3234f3f83fc35278c7fb935dfbde56ae2da3dd2c03b84d", size = 219142, upload-time = "2026-01-25T12:58:00.448Z" }, + { url = "https://files.pythonhosted.org/packages/45/7a/8bf9e9309c4c996e65c52a7c5a112707ecdd9fbaf49e10b5a705a402bbb4/coverage-7.13.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78f45d21dc4d5d6bd29323f0320089ef7eae16e4bef712dff79d184fa7330af3", size = 219503, upload-time = "2026-01-25T12:58:02.451Z" }, + { url = "https://files.pythonhosted.org/packages/87/93/17661e06b7b37580923f3f12406ac91d78aeed293fb6da0b69cc7957582f/coverage-7.13.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fae91dfecd816444c74531a9c3d6ded17a504767e97aa674d44f638107265b99", size = 251006, upload-time = "2026-01-25T12:58:04.059Z" }, + { url = "https://files.pythonhosted.org/packages/12/f0/f9e59fb8c310171497f379e25db060abef9fa605e09d63157eebec102676/coverage-7.13.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:264657171406c114787b441484de620e03d8f7202f113d62fcd3d9688baa3e6f", size = 253750, upload-time = "2026-01-25T12:58:05.574Z" }, + { url = "https://files.pythonhosted.org/packages/e5/b1/1935e31add2232663cf7edd8269548b122a7d100047ff93475dbaaae673e/coverage-7.13.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae47d8dcd3ded0155afbb59c62bd8ab07ea0fd4902e1c40567439e6db9dcaf2f", size = 254862, upload-time = "2026-01-25T12:58:07.647Z" }, + { url = "https://files.pythonhosted.org/packages/af/59/b5e97071ec13df5f45da2b3391b6cdbec78ba20757bc92580a5b3d5fa53c/coverage-7.13.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8a0b33e9fd838220b007ce8f299114d406c1e8edb21336af4c97a26ecfd185aa", size = 251420, upload-time = "2026-01-25T12:58:09.309Z" }, + { url = "https://files.pythonhosted.org/packages/3f/75/9495932f87469d013dc515fb0ce1aac5fa97766f38f6b1a1deb1ee7b7f3a/coverage-7.13.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b3becbea7f3ce9a2d4d430f223ec15888e4deb31395840a79e916368d6004cce", size = 252786, upload-time = "2026-01-25T12:58:10.909Z" }, + { url = "https://files.pythonhosted.org/packages/6a/59/af550721f0eb62f46f7b8cb7e6f1860592189267b1c411a4e3a057caacee/coverage-7.13.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f819c727a6e6eeb8711e4ce63d78c620f69630a2e9d53bc95ca5379f57b6ba94", size = 250928, upload-time = "2026-01-25T12:58:12.449Z" }, + { url = "https://files.pythonhosted.org/packages/9b/b1/21b4445709aae500be4ab43bbcfb4e53dc0811c3396dcb11bf9f23fd0226/coverage-7.13.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:4f7b71757a3ab19f7ba286e04c181004c1d61be921795ee8ba6970fd0ec91da5", size = 250496, upload-time = "2026-01-25T12:58:14.047Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b1/0f5d89dfe0392990e4f3980adbde3eb34885bc1effb2dc369e0bf385e389/coverage-7.13.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b7fc50d2afd2e6b4f6f2f403b70103d280a8e0cb35320cbbe6debcda02a1030b", size = 252373, upload-time = "2026-01-25T12:58:15.976Z" }, + { url = "https://files.pythonhosted.org/packages/01/c9/0cf1a6a57a9968cc049a6b896693faa523c638a5314b1fc374eb2b2ac904/coverage-7.13.2-cp312-cp312-win32.whl", hash = "sha256:292250282cf9bcf206b543d7608bda17ca6fc151f4cbae949fc7e115112fbd41", size = 221696, upload-time = "2026-01-25T12:58:17.517Z" }, + { url = "https://files.pythonhosted.org/packages/4d/05/d7540bf983f09d32803911afed135524570f8c47bb394bf6206c1dc3a786/coverage-7.13.2-cp312-cp312-win_amd64.whl", hash = "sha256:eeea10169fac01549a7921d27a3e517194ae254b542102267bef7a93ed38c40e", size = 222504, upload-time = "2026-01-25T12:58:19.115Z" }, + { url = "https://files.pythonhosted.org/packages/15/8b/1a9f037a736ced0a12aacf6330cdaad5008081142a7070bc58b0f7930cbc/coverage-7.13.2-cp312-cp312-win_arm64.whl", hash = "sha256:2a5b567f0b635b592c917f96b9a9cb3dbd4c320d03f4bf94e9084e494f2e8894", size = 221120, upload-time = "2026-01-25T12:58:21.334Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f0/3d3eac7568ab6096ff23791a526b0048a1ff3f49d0e236b2af6fb6558e88/coverage-7.13.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ed75de7d1217cf3b99365d110975f83af0528c849ef5180a12fd91b5064df9d6", size = 219168, upload-time = "2026-01-25T12:58:23.376Z" }, + { url = "https://files.pythonhosted.org/packages/a3/a6/f8b5cfeddbab95fdef4dcd682d82e5dcff7a112ced57a959f89537ee9995/coverage-7.13.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:97e596de8fa9bada4d88fde64a3f4d37f1b6131e4faa32bad7808abc79887ddc", size = 219537, upload-time = "2026-01-25T12:58:24.932Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e6/8d8e6e0c516c838229d1e41cadcec91745f4b1031d4db17ce0043a0423b4/coverage-7.13.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:68c86173562ed4413345410c9480a8d64864ac5e54a5cda236748031e094229f", size = 250528, upload-time = "2026-01-25T12:58:26.567Z" }, + { url = "https://files.pythonhosted.org/packages/8e/78/befa6640f74092b86961f957f26504c8fba3d7da57cc2ab7407391870495/coverage-7.13.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7be4d613638d678b2b3773b8f687537b284d7074695a43fe2fbbfc0e31ceaed1", size = 253132, upload-time = "2026-01-25T12:58:28.251Z" }, + { url = "https://files.pythonhosted.org/packages/9d/10/1630db1edd8ce675124a2ee0f7becc603d2bb7b345c2387b4b95c6907094/coverage-7.13.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7f63ce526a96acd0e16c4af8b50b64334239550402fb1607ce6a584a6d62ce9", size = 254374, upload-time = "2026-01-25T12:58:30.294Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1d/0d9381647b1e8e6d310ac4140be9c428a0277330991e0c35bdd751e338a4/coverage-7.13.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:406821f37f864f968e29ac14c3fccae0fec9fdeba48327f0341decf4daf92d7c", size = 250762, upload-time = "2026-01-25T12:58:32.036Z" }, + { url = "https://files.pythonhosted.org/packages/43/e4/5636dfc9a7c871ee8776af83ee33b4c26bc508ad6cee1e89b6419a366582/coverage-7.13.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ee68e5a4e3e5443623406b905db447dceddffee0dceb39f4e0cd9ec2a35004b5", size = 252502, upload-time = "2026-01-25T12:58:33.961Z" }, + { url = "https://files.pythonhosted.org/packages/02/2a/7ff2884d79d420cbb2d12fed6fff727b6d0ef27253140d3cdbbd03187ee0/coverage-7.13.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2ee0e58cca0c17dd9c6c1cdde02bb705c7b3fbfa5f3b0b5afeda20d4ebff8ef4", size = 250463, upload-time = "2026-01-25T12:58:35.529Z" }, + { url = "https://files.pythonhosted.org/packages/91/c0/ba51087db645b6c7261570400fc62c89a16278763f36ba618dc8657a187b/coverage-7.13.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:6e5bbb5018bf76a56aabdb64246b5288d5ae1b7d0dd4d0534fe86df2c2992d1c", size = 250288, upload-time = "2026-01-25T12:58:37.226Z" }, + { url = "https://files.pythonhosted.org/packages/03/07/44e6f428551c4d9faf63ebcefe49b30e5c89d1be96f6a3abd86a52da9d15/coverage-7.13.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a55516c68ef3e08e134e818d5e308ffa6b1337cc8b092b69b24287bf07d38e31", size = 252063, upload-time = "2026-01-25T12:58:38.821Z" }, + { url = "https://files.pythonhosted.org/packages/c2/67/35b730ad7e1859dd57e834d1bc06080d22d2f87457d53f692fce3f24a5a9/coverage-7.13.2-cp313-cp313-win32.whl", hash = "sha256:5b20211c47a8abf4abc3319d8ce2464864fa9f30c5fcaf958a3eed92f4f1fef8", size = 221716, upload-time = "2026-01-25T12:58:40.484Z" }, + { url = "https://files.pythonhosted.org/packages/0d/82/e5fcf5a97c72f45fc14829237a6550bf49d0ab882ac90e04b12a69db76b4/coverage-7.13.2-cp313-cp313-win_amd64.whl", hash = "sha256:14f500232e521201cf031549fb1ebdfc0a40f401cf519157f76c397e586c3beb", size = 222522, upload-time = "2026-01-25T12:58:43.247Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f1/25d7b2f946d239dd2d6644ca2cc060d24f97551e2af13b6c24c722ae5f97/coverage-7.13.2-cp313-cp313-win_arm64.whl", hash = "sha256:9779310cb5a9778a60c899f075a8514c89fa6d10131445c2207fc893e0b14557", size = 221145, upload-time = "2026-01-25T12:58:45Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f7/080376c029c8f76fadfe43911d0daffa0cbdc9f9418a0eead70c56fb7f4b/coverage-7.13.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e64fa5a1e41ce5df6b547cbc3d3699381c9e2c2c369c67837e716ed0f549d48e", size = 219861, upload-time = "2026-01-25T12:58:46.586Z" }, + { url = "https://files.pythonhosted.org/packages/42/11/0b5e315af5ab35f4c4a70e64d3314e4eec25eefc6dec13be3a7d5ffe8ac5/coverage-7.13.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b01899e82a04085b6561eb233fd688474f57455e8ad35cd82286463ba06332b7", size = 220207, upload-time = "2026-01-25T12:58:48.277Z" }, + { url = "https://files.pythonhosted.org/packages/b2/0c/0874d0318fb1062117acbef06a09cf8b63f3060c22265adaad24b36306b7/coverage-7.13.2-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:838943bea48be0e2768b0cf7819544cdedc1bbb2f28427eabb6eb8c9eb2285d3", size = 261504, upload-time = "2026-01-25T12:58:49.904Z" }, + { url = "https://files.pythonhosted.org/packages/83/5e/1cd72c22ecb30751e43a72f40ba50fcef1b7e93e3ea823bd9feda8e51f9a/coverage-7.13.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:93d1d25ec2b27e90bcfef7012992d1f5121b51161b8bffcda756a816cf13c2c3", size = 263582, upload-time = "2026-01-25T12:58:51.582Z" }, + { url = "https://files.pythonhosted.org/packages/9b/da/8acf356707c7a42df4d0657020308e23e5a07397e81492640c186268497c/coverage-7.13.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93b57142f9621b0d12349c43fc7741fe578e4bc914c1e5a54142856cfc0bf421", size = 266008, upload-time = "2026-01-25T12:58:53.234Z" }, + { url = "https://files.pythonhosted.org/packages/41/41/ea1730af99960309423c6ea8d6a4f1fa5564b2d97bd1d29dda4b42611f04/coverage-7.13.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f06799ae1bdfff7ccb8665d75f8291c69110ba9585253de254688aa8a1ccc6c5", size = 260762, upload-time = "2026-01-25T12:58:55.372Z" }, + { url = "https://files.pythonhosted.org/packages/22/fa/02884d2080ba71db64fdc127b311db60e01fe6ba797d9c8363725e39f4d5/coverage-7.13.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f9405ab4f81d490811b1d91c7a20361135a2df4c170e7f0b747a794da5b7f23", size = 263571, upload-time = "2026-01-25T12:58:57.52Z" }, + { url = "https://files.pythonhosted.org/packages/d2/6b/4083aaaeba9b3112f55ac57c2ce7001dc4d8fa3fcc228a39f09cc84ede27/coverage-7.13.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f9ab1d5b86f8fbc97a5b3cd6280a3fd85fef3b028689d8a2c00918f0d82c728c", size = 261200, upload-time = "2026-01-25T12:58:59.255Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d2/aea92fa36d61955e8c416ede9cf9bf142aa196f3aea214bb67f85235a050/coverage-7.13.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:f674f59712d67e841525b99e5e2b595250e39b529c3bda14764e4f625a3fa01f", size = 260095, upload-time = "2026-01-25T12:59:01.066Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ae/04ffe96a80f107ea21b22b2367175c621da920063260a1c22f9452fd7866/coverage-7.13.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c6cadac7b8ace1ba9144feb1ae3cb787a6065ba6d23ffc59a934b16406c26573", size = 262284, upload-time = "2026-01-25T12:59:02.802Z" }, + { url = "https://files.pythonhosted.org/packages/1c/7a/6f354dcd7dfc41297791d6fb4e0d618acb55810bde2c1fd14b3939e05c2b/coverage-7.13.2-cp313-cp313t-win32.whl", hash = "sha256:14ae4146465f8e6e6253eba0cccd57423e598a4cb925958b240c805300918343", size = 222389, upload-time = "2026-01-25T12:59:04.563Z" }, + { url = "https://files.pythonhosted.org/packages/8d/d5/080ad292a4a3d3daf411574be0a1f56d6dee2c4fdf6b005342be9fac807f/coverage-7.13.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9074896edd705a05769e3de0eac0a8388484b503b68863dd06d5e473f874fd47", size = 223450, upload-time = "2026-01-25T12:59:06.677Z" }, + { url = "https://files.pythonhosted.org/packages/88/96/df576fbacc522e9fb8d1c4b7a7fc62eb734be56e2cba1d88d2eabe08ea3f/coverage-7.13.2-cp313-cp313t-win_arm64.whl", hash = "sha256:69e526e14f3f854eda573d3cf40cffd29a1a91c684743d904c33dbdcd0e0f3e7", size = 221707, upload-time = "2026-01-25T12:59:08.363Z" }, + { url = "https://files.pythonhosted.org/packages/55/53/1da9e51a0775634b04fcc11eb25c002fc58ee4f92ce2e8512f94ac5fc5bf/coverage-7.13.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:387a825f43d680e7310e6f325b2167dd093bc8ffd933b83e9aa0983cf6e0a2ef", size = 219213, upload-time = "2026-01-25T12:59:11.909Z" }, + { url = "https://files.pythonhosted.org/packages/46/35/b3caac3ebbd10230fea5a33012b27d19e999a17c9285c4228b4b2e35b7da/coverage-7.13.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f0d7fea9d8e5d778cd5a9e8fc38308ad688f02040e883cdc13311ef2748cb40f", size = 219549, upload-time = "2026-01-25T12:59:13.638Z" }, + { url = "https://files.pythonhosted.org/packages/76/9c/e1cf7def1bdc72c1907e60703983a588f9558434a2ff94615747bd73c192/coverage-7.13.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e080afb413be106c95c4ee96b4fffdc9e2fa56a8bbf90b5c0918e5c4449412f5", size = 250586, upload-time = "2026-01-25T12:59:15.808Z" }, + { url = "https://files.pythonhosted.org/packages/ba/49/f54ec02ed12be66c8d8897270505759e057b0c68564a65c429ccdd1f139e/coverage-7.13.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a7fc042ba3c7ce25b8a9f097eb0f32a5ce1ccdb639d9eec114e26def98e1f8a4", size = 253093, upload-time = "2026-01-25T12:59:17.491Z" }, + { url = "https://files.pythonhosted.org/packages/fb/5e/aaf86be3e181d907e23c0f61fccaeb38de8e6f6b47aed92bf57d8fc9c034/coverage-7.13.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d0ba505e021557f7f8173ee8cd6b926373d8653e5ff7581ae2efce1b11ef4c27", size = 254446, upload-time = "2026-01-25T12:59:19.752Z" }, + { url = "https://files.pythonhosted.org/packages/28/c8/a5fa01460e2d75b0c853b392080d6829d3ca8b5ab31e158fa0501bc7c708/coverage-7.13.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7de326f80e3451bd5cc7239ab46c73ddb658fe0b7649476bc7413572d36cd548", size = 250615, upload-time = "2026-01-25T12:59:21.928Z" }, + { url = "https://files.pythonhosted.org/packages/86/0b/6d56315a55f7062bb66410732c24879ccb2ec527ab6630246de5fe45a1df/coverage-7.13.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:abaea04f1e7e34841d4a7b343904a3f59481f62f9df39e2cd399d69a187a9660", size = 252452, upload-time = "2026-01-25T12:59:23.592Z" }, + { url = "https://files.pythonhosted.org/packages/30/19/9bc550363ebc6b0ea121977ee44d05ecd1e8bf79018b8444f1028701c563/coverage-7.13.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9f93959ee0c604bccd8e0697be21de0887b1f73efcc3aa73a3ec0fd13feace92", size = 250418, upload-time = "2026-01-25T12:59:25.392Z" }, + { url = "https://files.pythonhosted.org/packages/1f/53/580530a31ca2f0cc6f07a8f2ab5460785b02bb11bdf815d4c4d37a4c5169/coverage-7.13.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:13fe81ead04e34e105bf1b3c9f9cdf32ce31736ee5d90a8d2de02b9d3e1bcb82", size = 250231, upload-time = "2026-01-25T12:59:27.888Z" }, + { url = "https://files.pythonhosted.org/packages/e2/42/dd9093f919dc3088cb472893651884bd675e3df3d38a43f9053656dca9a2/coverage-7.13.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d6d16b0f71120e365741bca2cb473ca6fe38930bc5431c5e850ba949f708f892", size = 251888, upload-time = "2026-01-25T12:59:29.636Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a6/0af4053e6e819774626e133c3d6f70fae4d44884bfc4b126cb647baee8d3/coverage-7.13.2-cp314-cp314-win32.whl", hash = "sha256:9b2f4714bb7d99ba3790ee095b3b4ac94767e1347fe424278a0b10acb3ff04fe", size = 221968, upload-time = "2026-01-25T12:59:31.424Z" }, + { url = "https://files.pythonhosted.org/packages/c4/cc/5aff1e1f80d55862442855517bb8ad8ad3a68639441ff6287dde6a58558b/coverage-7.13.2-cp314-cp314-win_amd64.whl", hash = "sha256:e4121a90823a063d717a96e0a0529c727fb31ea889369a0ee3ec00ed99bf6859", size = 222783, upload-time = "2026-01-25T12:59:33.118Z" }, + { url = "https://files.pythonhosted.org/packages/de/20/09abafb24f84b3292cc658728803416c15b79f9ee5e68d25238a895b07d9/coverage-7.13.2-cp314-cp314-win_arm64.whl", hash = "sha256:6873f0271b4a15a33e7590f338d823f6f66f91ed147a03938d7ce26efd04eee6", size = 221348, upload-time = "2026-01-25T12:59:34.939Z" }, + { url = "https://files.pythonhosted.org/packages/b6/60/a3820c7232db63be060e4019017cd3426751c2699dab3c62819cdbcea387/coverage-7.13.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f61d349f5b7cd95c34017f1927ee379bfbe9884300d74e07cf630ccf7a610c1b", size = 219950, upload-time = "2026-01-25T12:59:36.624Z" }, + { url = "https://files.pythonhosted.org/packages/fd/37/e4ef5975fdeb86b1e56db9a82f41b032e3d93a840ebaf4064f39e770d5c5/coverage-7.13.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a43d34ce714f4ca674c0d90beb760eb05aad906f2c47580ccee9da8fe8bfb417", size = 220209, upload-time = "2026-01-25T12:59:38.339Z" }, + { url = "https://files.pythonhosted.org/packages/54/df/d40e091d00c51adca1e251d3b60a8b464112efa3004949e96a74d7c19a64/coverage-7.13.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bff1b04cb9d4900ce5c56c4942f047dc7efe57e2608cb7c3c8936e9970ccdbee", size = 261576, upload-time = "2026-01-25T12:59:40.446Z" }, + { url = "https://files.pythonhosted.org/packages/c5/44/5259c4bed54e3392e5c176121af9f71919d96dde853386e7730e705f3520/coverage-7.13.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6ae99e4560963ad8e163e819e5d77d413d331fd00566c1e0856aa252303552c1", size = 263704, upload-time = "2026-01-25T12:59:42.346Z" }, + { url = "https://files.pythonhosted.org/packages/16/bd/ae9f005827abcbe2c70157459ae86053971c9fa14617b63903abbdce26d9/coverage-7.13.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e79a8c7d461820257d9aa43716c4efc55366d7b292e46b5b37165be1d377405d", size = 266109, upload-time = "2026-01-25T12:59:44.073Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c0/8e279c1c0f5b1eaa3ad9b0fb7a5637fc0379ea7d85a781c0fe0bb3cfc2ab/coverage-7.13.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:060ee84f6a769d40c492711911a76811b4befb6fba50abb450371abb720f5bd6", size = 260686, upload-time = "2026-01-25T12:59:45.804Z" }, + { url = "https://files.pythonhosted.org/packages/b2/47/3a8112627e9d863e7cddd72894171c929e94491a597811725befdcd76bce/coverage-7.13.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bca209d001fd03ea2d978f8a4985093240a355c93078aee3f799852c23f561a", size = 263568, upload-time = "2026-01-25T12:59:47.929Z" }, + { url = "https://files.pythonhosted.org/packages/92/bc/7ea367d84afa3120afc3ce6de294fd2dcd33b51e2e7fbe4bbfd200f2cb8c/coverage-7.13.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:6b8092aa38d72f091db61ef83cb66076f18f02da3e1a75039a4f218629600e04", size = 261174, upload-time = "2026-01-25T12:59:49.717Z" }, + { url = "https://files.pythonhosted.org/packages/33/b7/f1092dcecb6637e31cc2db099581ee5c61a17647849bae6b8261a2b78430/coverage-7.13.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4a3158dc2dcce5200d91ec28cd315c999eebff355437d2765840555d765a6e5f", size = 260017, upload-time = "2026-01-25T12:59:51.463Z" }, + { url = "https://files.pythonhosted.org/packages/2b/cd/f3d07d4b95fbe1a2ef0958c15da614f7e4f557720132de34d2dc3aa7e911/coverage-7.13.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3973f353b2d70bd9796cc12f532a05945232ccae966456c8ed7034cb96bbfd6f", size = 262337, upload-time = "2026-01-25T12:59:53.407Z" }, + { url = "https://files.pythonhosted.org/packages/e0/db/b0d5b2873a07cb1e06a55d998697c0a5a540dcefbf353774c99eb3874513/coverage-7.13.2-cp314-cp314t-win32.whl", hash = "sha256:79f6506a678a59d4ded048dc72f1859ebede8ec2b9a2d509ebe161f01c2879d3", size = 222749, upload-time = "2026-01-25T12:59:56.316Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2f/838a5394c082ac57d85f57f6aba53093b30d9089781df72412126505716f/coverage-7.13.2-cp314-cp314t-win_amd64.whl", hash = "sha256:196bfeabdccc5a020a57d5a368c681e3a6ceb0447d153aeccc1ab4d70a5032ba", size = 223857, upload-time = "2026-01-25T12:59:58.201Z" }, + { url = "https://files.pythonhosted.org/packages/44/d4/b608243e76ead3a4298824b50922b89ef793e50069ce30316a65c1b4d7ef/coverage-7.13.2-cp314-cp314t-win_arm64.whl", hash = "sha256:69269ab58783e090bfbf5b916ab3d188126e22d6070bbfc93098fdd474ef937c", size = 221881, upload-time = "2026-01-25T13:00:00.449Z" }, + { url = "https://files.pythonhosted.org/packages/d2/db/d291e30fdf7ea617a335531e72294e0c723356d7fdde8fba00610a76bda9/coverage-7.13.2-py3-none-any.whl", hash = "sha256:40ce1ea1e25125556d8e76bd0b61500839a07944cc287ac21d5626f3e620cad5", size = 210943, upload-time = "2026-01-25T13:00:02.388Z" }, ] [package.optional-dependencies] @@ -606,6 +606,30 @@ toml = [ { name = "tomli", marker = "python_full_version <= '3.11'" }, ] +[[package]] +name = "cuda-bindings" +version = "12.9.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cuda-pathfinder" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/e7/b47792cc2d01c7e1d37c32402182524774dadd2d26339bd224e0e913832e/cuda_bindings-12.9.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c912a3d9e6b6651853eed8eed96d6800d69c08e94052c292fec3f282c5a817c9", size = 12210593, upload-time = "2025-10-21T14:51:36.574Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c1/dabe88f52c3e3760d861401bb994df08f672ec893b8f7592dc91626adcf3/cuda_bindings-12.9.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fda147a344e8eaeca0c6ff113d2851ffca8f7dfc0a6c932374ee5c47caa649c8", size = 12151019, upload-time = "2025-10-21T14:51:43.167Z" }, + { url = "https://files.pythonhosted.org/packages/63/56/e465c31dc9111be3441a9ba7df1941fe98f4aa6e71e8788a3fb4534ce24d/cuda_bindings-12.9.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:32bdc5a76906be4c61eb98f546a6786c5773a881f3b166486449b5d141e4a39f", size = 11906628, upload-time = "2025-10-21T14:51:49.905Z" }, + { url = "https://files.pythonhosted.org/packages/a3/84/1e6be415e37478070aeeee5884c2022713c1ecc735e6d82d744de0252eee/cuda_bindings-12.9.4-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56e0043c457a99ac473ddc926fe0dc4046694d99caef633e92601ab52cbe17eb", size = 11925991, upload-time = "2025-10-21T14:51:56.535Z" }, + { url = "https://files.pythonhosted.org/packages/d1/af/6dfd8f2ed90b1d4719bc053ff8940e494640fe4212dc3dd72f383e4992da/cuda_bindings-12.9.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8b72ee72a9cc1b531db31eebaaee5c69a8ec3500e32c6933f2d3b15297b53686", size = 11922703, upload-time = "2025-10-21T14:52:03.585Z" }, + { url = "https://files.pythonhosted.org/packages/6c/19/90ac264acc00f6df8a49378eedec9fd2db3061bf9263bf9f39fd3d8377c3/cuda_bindings-12.9.4-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d80bffc357df9988dca279734bc9674c3934a654cab10cadeed27ce17d8635ee", size = 11924658, upload-time = "2025-10-21T14:52:10.411Z" }, +] + +[[package]] +name = "cuda-pathfinder" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/02/4dbe7568a42e46582248942f54dc64ad094769532adbe21e525e4edf7bc4/cuda_pathfinder-1.3.3-py3-none-any.whl", hash = "sha256:9984b664e404f7c134954a771be8775dfd6180ea1e1aef4a5a37d4be05d9bbb1", size = 27154, upload-time = "2025-12-04T22:35:08.996Z" }, +] + [[package]] name = "cycler" version = "0.12.1" @@ -617,7 +641,7 @@ wheels = [ [[package]] name = "dask" -version = "2026.1.1" +version = "2026.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -629,9 +653,9 @@ dependencies = [ { name = "pyyaml" }, { name = "toolz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1e/46/61ecde57bac647ca7eb6ffef8dcd90af6c1c649020874cd7fd8738003d62/dask-2026.1.1.tar.gz", hash = "sha256:12b1dbb0d6e92f287feb4076871600b2fba3a843d35ff214776ada5e9e7a1529", size = 10994732, upload-time = "2026-01-16T12:35:30.258Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/52/b0f9172b22778def907db1ff173249e4eb41f054b46a9c83b1528aaf811f/dask-2026.1.2.tar.gz", hash = "sha256:1136683de2750d98ea792670f7434e6c1cfce90cab2cc2f2495a9e60fd25a4fc", size = 10997838, upload-time = "2026-01-30T21:04:20.54Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/4b/9cc373120658a2516aa5f6dcdde631c95d714b876d29ad8f8e009d793f3f/dask-2026.1.1-py3-none-any.whl", hash = "sha256:146b0ef2918eb581e06139183a88801b4a8c52d7c37758a91f8c3b75c54b0e15", size = 1481492, upload-time = "2026-01-16T12:35:22.602Z" }, + { url = "https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl", hash = "sha256:46a0cf3b8d87f78a3d2e6b145aea4418a6d6d606fe6a16c79bd8ca2bb862bc91", size = 1482084, upload-time = "2026-01-30T21:04:18.363Z" }, ] [package.optional-dependencies] @@ -641,27 +665,27 @@ array = [ [[package]] name = "debugpy" -version = "1.8.19" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/73/75/9e12d4d42349b817cd545b89247696c67917aab907012ae5b64bbfea3199/debugpy-1.8.19.tar.gz", hash = "sha256:eea7e5987445ab0b5ed258093722d5ecb8bb72217c5c9b1e21f64efe23ddebdb", size = 1644590, upload-time = "2025-12-15T21:53:28.044Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/80/e2/48531a609b5a2aa94c6b6853afdfec8da05630ab9aaa96f1349e772119e9/debugpy-1.8.19-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:c5dcfa21de1f735a4f7ced4556339a109aa0f618d366ede9da0a3600f2516d8b", size = 2207620, upload-time = "2025-12-15T21:53:37.1Z" }, - { url = "https://files.pythonhosted.org/packages/1b/d4/97775c01d56071969f57d93928899e5616a4cfbbf4c8cc75390d3a51c4a4/debugpy-1.8.19-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:806d6800246244004625d5222d7765874ab2d22f3ba5f615416cf1342d61c488", size = 3170796, upload-time = "2025-12-15T21:53:38.513Z" }, - { url = "https://files.pythonhosted.org/packages/8d/7e/8c7681bdb05be9ec972bbb1245eb7c4c7b0679bb6a9e6408d808bc876d3d/debugpy-1.8.19-cp311-cp311-win32.whl", hash = "sha256:783a519e6dfb1f3cd773a9bda592f4887a65040cb0c7bd38dde410f4e53c40d4", size = 5164287, upload-time = "2025-12-15T21:53:40.857Z" }, - { url = "https://files.pythonhosted.org/packages/f2/a8/aaac7ff12ddf5d68a39e13a423a8490426f5f661384f5ad8d9062761bd8e/debugpy-1.8.19-cp311-cp311-win_amd64.whl", hash = "sha256:14035cbdbb1fe4b642babcdcb5935c2da3b1067ac211c5c5a8fdc0bb31adbcaa", size = 5188269, upload-time = "2025-12-15T21:53:42.359Z" }, - { url = "https://files.pythonhosted.org/packages/4a/15/d762e5263d9e25b763b78be72dc084c7a32113a0bac119e2f7acae7700ed/debugpy-1.8.19-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:bccb1540a49cde77edc7ce7d9d075c1dbeb2414751bc0048c7a11e1b597a4c2e", size = 2549995, upload-time = "2025-12-15T21:53:43.773Z" }, - { url = "https://files.pythonhosted.org/packages/a7/88/f7d25c68b18873b7c53d7c156ca7a7ffd8e77073aa0eac170a9b679cf786/debugpy-1.8.19-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:e9c68d9a382ec754dc05ed1d1b4ed5bd824b9f7c1a8cd1083adb84b3c93501de", size = 4309891, upload-time = "2025-12-15T21:53:45.26Z" }, - { url = "https://files.pythonhosted.org/packages/c5/4f/a65e973aba3865794da65f71971dca01ae66666132c7b2647182d5be0c5f/debugpy-1.8.19-cp312-cp312-win32.whl", hash = "sha256:6599cab8a783d1496ae9984c52cb13b7c4a3bd06a8e6c33446832a5d97ce0bee", size = 5286355, upload-time = "2025-12-15T21:53:46.763Z" }, - { url = "https://files.pythonhosted.org/packages/d8/3a/d3d8b48fec96e3d824e404bf428276fb8419dfa766f78f10b08da1cb2986/debugpy-1.8.19-cp312-cp312-win_amd64.whl", hash = "sha256:66e3d2fd8f2035a8f111eb127fa508469dfa40928a89b460b41fd988684dc83d", size = 5328239, upload-time = "2025-12-15T21:53:48.868Z" }, - { url = "https://files.pythonhosted.org/packages/71/3d/388035a31a59c26f1ecc8d86af607d0c42e20ef80074147cd07b180c4349/debugpy-1.8.19-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:91e35db2672a0abaf325f4868fcac9c1674a0d9ad9bb8a8c849c03a5ebba3e6d", size = 2538859, upload-time = "2025-12-15T21:53:50.478Z" }, - { url = "https://files.pythonhosted.org/packages/4a/19/c93a0772d0962294f083dbdb113af1a7427bb632d36e5314297068f55db7/debugpy-1.8.19-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:85016a73ab84dea1c1f1dcd88ec692993bcbe4532d1b49ecb5f3c688ae50c606", size = 4292575, upload-time = "2025-12-15T21:53:51.821Z" }, - { url = "https://files.pythonhosted.org/packages/5c/56/09e48ab796b0a77e3d7dc250f95251832b8bf6838c9632f6100c98bdf426/debugpy-1.8.19-cp313-cp313-win32.whl", hash = "sha256:b605f17e89ba0ecee994391194285fada89cee111cfcd29d6f2ee11cbdc40976", size = 5286209, upload-time = "2025-12-15T21:53:53.602Z" }, - { url = "https://files.pythonhosted.org/packages/fb/4e/931480b9552c7d0feebe40c73725dd7703dcc578ba9efc14fe0e6d31cfd1/debugpy-1.8.19-cp313-cp313-win_amd64.whl", hash = "sha256:c30639998a9f9cd9699b4b621942c0179a6527f083c72351f95c6ab1728d5b73", size = 5328206, upload-time = "2025-12-15T21:53:55.433Z" }, - { url = "https://files.pythonhosted.org/packages/f6/b9/cbec520c3a00508327476c7fce26fbafef98f412707e511eb9d19a2ef467/debugpy-1.8.19-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:1e8c4d1bd230067bf1bbcdbd6032e5a57068638eb28b9153d008ecde288152af", size = 2537372, upload-time = "2025-12-15T21:53:57.318Z" }, - { url = "https://files.pythonhosted.org/packages/88/5e/cf4e4dc712a141e10d58405c58c8268554aec3c35c09cdcda7535ff13f76/debugpy-1.8.19-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:d40c016c1f538dbf1762936e3aeb43a89b965069d9f60f9e39d35d9d25e6b809", size = 4268729, upload-time = "2025-12-15T21:53:58.712Z" }, - { url = "https://files.pythonhosted.org/packages/82/a3/c91a087ab21f1047db328c1d3eb5d1ff0e52de9e74f9f6f6fa14cdd93d58/debugpy-1.8.19-cp314-cp314-win32.whl", hash = "sha256:0601708223fe1cd0e27c6cce67a899d92c7d68e73690211e6788a4b0e1903f5b", size = 5286388, upload-time = "2025-12-15T21:54:00.687Z" }, - { url = "https://files.pythonhosted.org/packages/17/b8/bfdc30b6e94f1eff09f2dc9cc1f9cd1c6cde3d996bcbd36ce2d9a4956e99/debugpy-1.8.19-cp314-cp314-win_amd64.whl", hash = "sha256:8e19a725f5d486f20e53a1dde2ab8bb2c9607c40c00a42ab646def962b41125f", size = 5327741, upload-time = "2025-12-15T21:54:02.148Z" }, - { url = "https://files.pythonhosted.org/packages/25/3e/e27078370414ef35fafad2c06d182110073daaeb5d3bf734b0b1eeefe452/debugpy-1.8.19-py2.py3-none-any.whl", hash = "sha256:360ffd231a780abbc414ba0f005dad409e71c78637efe8f2bd75837132a41d38", size = 5292321, upload-time = "2025-12-15T21:54:16.024Z" }, +version = "1.8.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/b7/cd8080344452e4874aae67c40d8940e2b4d47b01601a8fd9f44786c757c7/debugpy-1.8.20.tar.gz", hash = "sha256:55bc8701714969f1ab89a6d5f2f3d40c36f91b2cbe2f65d98bf8196f6a6a2c33", size = 1645207, upload-time = "2026-01-29T23:03:28.199Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/56/c3baf5cbe4dd77427fd9aef99fcdade259ad128feeb8a786c246adb838e5/debugpy-1.8.20-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:eada6042ad88fa1571b74bd5402ee8b86eded7a8f7b827849761700aff171f1b", size = 2208318, upload-time = "2026-01-29T23:03:36.481Z" }, + { url = "https://files.pythonhosted.org/packages/9a/7d/4fa79a57a8e69fe0d9763e98d1110320f9ecd7f1f362572e3aafd7417c9d/debugpy-1.8.20-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:7de0b7dfeedc504421032afba845ae2a7bcc32ddfb07dae2c3ca5442f821c344", size = 3171493, upload-time = "2026-01-29T23:03:37.775Z" }, + { url = "https://files.pythonhosted.org/packages/7d/f2/1e8f8affe51e12a26f3a8a8a4277d6e60aa89d0a66512f63b1e799d424a4/debugpy-1.8.20-cp311-cp311-win32.whl", hash = "sha256:773e839380cf459caf73cc533ea45ec2737a5cc184cf1b3b796cd4fd98504fec", size = 5209240, upload-time = "2026-01-29T23:03:39.109Z" }, + { url = "https://files.pythonhosted.org/packages/d5/92/1cb532e88560cbee973396254b21bece8c5d7c2ece958a67afa08c9f10dc/debugpy-1.8.20-cp311-cp311-win_amd64.whl", hash = "sha256:1f7650546e0eded1902d0f6af28f787fa1f1dbdbc97ddabaf1cd963a405930cb", size = 5233481, upload-time = "2026-01-29T23:03:40.659Z" }, + { url = "https://files.pythonhosted.org/packages/14/57/7f34f4736bfb6e00f2e4c96351b07805d83c9a7b33d28580ae01374430f7/debugpy-1.8.20-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:4ae3135e2089905a916909ef31922b2d733d756f66d87345b3e5e52b7a55f13d", size = 2550686, upload-time = "2026-01-29T23:03:42.023Z" }, + { url = "https://files.pythonhosted.org/packages/ab/78/b193a3975ca34458f6f0e24aaf5c3e3da72f5401f6054c0dfd004b41726f/debugpy-1.8.20-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:88f47850a4284b88bd2bfee1f26132147d5d504e4e86c22485dfa44b97e19b4b", size = 4310588, upload-time = "2026-01-29T23:03:43.314Z" }, + { url = "https://files.pythonhosted.org/packages/c1/55/f14deb95eaf4f30f07ef4b90a8590fc05d9e04df85ee379712f6fb6736d7/debugpy-1.8.20-cp312-cp312-win32.whl", hash = "sha256:4057ac68f892064e5f98209ab582abfee3b543fb55d2e87610ddc133a954d390", size = 5331372, upload-time = "2026-01-29T23:03:45.526Z" }, + { url = "https://files.pythonhosted.org/packages/a1/39/2bef246368bd42f9bd7cba99844542b74b84dacbdbea0833e610f384fee8/debugpy-1.8.20-cp312-cp312-win_amd64.whl", hash = "sha256:a1a8f851e7cf171330679ef6997e9c579ef6dd33c9098458bd9986a0f4ca52e3", size = 5372835, upload-time = "2026-01-29T23:03:47.245Z" }, + { url = "https://files.pythonhosted.org/packages/15/e2/fc500524cc6f104a9d049abc85a0a8b3f0d14c0a39b9c140511c61e5b40b/debugpy-1.8.20-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:5dff4bb27027821fdfcc9e8f87309a28988231165147c31730128b1c983e282a", size = 2539560, upload-time = "2026-01-29T23:03:48.738Z" }, + { url = "https://files.pythonhosted.org/packages/90/83/fb33dcea789ed6018f8da20c5a9bc9d82adc65c0c990faed43f7c955da46/debugpy-1.8.20-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:84562982dd7cf5ebebfdea667ca20a064e096099997b175fe204e86817f64eaf", size = 4293272, upload-time = "2026-01-29T23:03:50.169Z" }, + { url = "https://files.pythonhosted.org/packages/a6/25/b1e4a01bfb824d79a6af24b99ef291e24189080c93576dfd9b1a2815cd0f/debugpy-1.8.20-cp313-cp313-win32.whl", hash = "sha256:da11dea6447b2cadbf8ce2bec59ecea87cc18d2c574980f643f2d2dfe4862393", size = 5331208, upload-time = "2026-01-29T23:03:51.547Z" }, + { url = "https://files.pythonhosted.org/packages/13/f7/a0b368ce54ffff9e9028c098bd2d28cfc5b54f9f6c186929083d4c60ba58/debugpy-1.8.20-cp313-cp313-win_amd64.whl", hash = "sha256:eb506e45943cab2efb7c6eafdd65b842f3ae779f020c82221f55aca9de135ed7", size = 5372930, upload-time = "2026-01-29T23:03:53.585Z" }, + { url = "https://files.pythonhosted.org/packages/33/2e/f6cb9a8a13f5058f0a20fe09711a7b726232cd5a78c6a7c05b2ec726cff9/debugpy-1.8.20-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:9c74df62fc064cd5e5eaca1353a3ef5a5d50da5eb8058fcef63106f7bebe6173", size = 2538066, upload-time = "2026-01-29T23:03:54.999Z" }, + { url = "https://files.pythonhosted.org/packages/c5/56/6ddca50b53624e1ca3ce1d1e49ff22db46c47ea5fb4c0cc5c9b90a616364/debugpy-1.8.20-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:077a7447589ee9bc1ff0cdf443566d0ecf540ac8aa7333b775ebcb8ce9f4ecad", size = 4269425, upload-time = "2026-01-29T23:03:56.518Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d9/d64199c14a0d4c476df46c82470a3ce45c8d183a6796cfb5e66533b3663c/debugpy-1.8.20-cp314-cp314-win32.whl", hash = "sha256:352036a99dd35053b37b7803f748efc456076f929c6a895556932eaf2d23b07f", size = 5331407, upload-time = "2026-01-29T23:03:58.481Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d9/1f07395b54413432624d61524dfd98c1a7c7827d2abfdb8829ac92638205/debugpy-1.8.20-cp314-cp314-win_amd64.whl", hash = "sha256:a98eec61135465b062846112e5ecf2eebb855305acc1dfbae43b72903b8ab5be", size = 5372521, upload-time = "2026-01-29T23:03:59.864Z" }, + { url = "https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl", hash = "sha256:5be9bed9ae3be00665a06acaa48f8329d2b9632f15fd09f6a9a8c8d9907e54d7", size = 5337658, upload-time = "2026-01-29T23:04:17.404Z" }, ] [[package]] @@ -862,44 +886,49 @@ wheels = [ [[package]] name = "greenlet" -version = "3.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/e5/40dbda2736893e3e53d25838e0f19a2b417dfc122b9989c91918db30b5d3/greenlet-3.3.0.tar.gz", hash = "sha256:a82bb225a4e9e4d653dd2fb7b8b2d36e4fb25bc0165422a11e48b88e9e6f78fb", size = 190651, upload-time = "2025-12-04T14:49:44.05Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/cb/48e964c452ca2b92175a9b2dca037a553036cb053ba69e284650ce755f13/greenlet-3.3.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e29f3018580e8412d6aaf5641bb7745d38c85228dacf51a73bd4e26ddf2a6a8e", size = 274908, upload-time = "2025-12-04T14:23:26.435Z" }, - { url = "https://files.pythonhosted.org/packages/28/da/38d7bff4d0277b594ec557f479d65272a893f1f2a716cad91efeb8680953/greenlet-3.3.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a687205fb22794e838f947e2194c0566d3812966b41c78709554aa883183fb62", size = 577113, upload-time = "2025-12-04T14:50:05.493Z" }, - { url = "https://files.pythonhosted.org/packages/3c/f2/89c5eb0faddc3ff014f1c04467d67dee0d1d334ab81fadbf3744847f8a8a/greenlet-3.3.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4243050a88ba61842186cb9e63c7dfa677ec146160b0efd73b855a3d9c7fcf32", size = 590338, upload-time = "2025-12-04T14:57:41.136Z" }, - { url = "https://files.pythonhosted.org/packages/dc/a6/e959a127b630a58e23529972dbc868c107f9d583b5a9f878fb858c46bc1a/greenlet-3.3.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cb3a8ec3db4a3b0eb8a3c25436c2d49e3505821802074969db017b87bc6a948", size = 590206, upload-time = "2025-12-04T14:26:01.254Z" }, - { url = "https://files.pythonhosted.org/packages/48/60/29035719feb91798693023608447283b266b12efc576ed013dd9442364bb/greenlet-3.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2de5a0b09eab81fc6a382791b995b1ccf2b172a9fec934747a7a23d2ff291794", size = 1550668, upload-time = "2025-12-04T15:04:22.439Z" }, - { url = "https://files.pythonhosted.org/packages/0a/5f/783a23754b691bfa86bd72c3033aa107490deac9b2ef190837b860996c9f/greenlet-3.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4449a736606bd30f27f8e1ff4678ee193bc47f6ca810d705981cfffd6ce0d8c5", size = 1615483, upload-time = "2025-12-04T14:27:28.083Z" }, - { url = "https://files.pythonhosted.org/packages/1d/d5/c339b3b4bc8198b7caa4f2bd9fd685ac9f29795816d8db112da3d04175bb/greenlet-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:7652ee180d16d447a683c04e4c5f6441bae7ba7b17ffd9f6b3aff4605e9e6f71", size = 301164, upload-time = "2025-12-04T14:42:51.577Z" }, - { url = "https://files.pythonhosted.org/packages/f8/0a/a3871375c7b9727edaeeea994bfff7c63ff7804c9829c19309ba2e058807/greenlet-3.3.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:b01548f6e0b9e9784a2c99c5651e5dc89ffcbe870bc5fb2e5ef864e9cc6b5dcb", size = 276379, upload-time = "2025-12-04T14:23:30.498Z" }, - { url = "https://files.pythonhosted.org/packages/43/ab/7ebfe34dce8b87be0d11dae91acbf76f7b8246bf9d6b319c741f99fa59c6/greenlet-3.3.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:349345b770dc88f81506c6861d22a6ccd422207829d2c854ae2af8025af303e3", size = 597294, upload-time = "2025-12-04T14:50:06.847Z" }, - { url = "https://files.pythonhosted.org/packages/a4/39/f1c8da50024feecd0793dbd5e08f526809b8ab5609224a2da40aad3a7641/greenlet-3.3.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e8e18ed6995e9e2c0b4ed264d2cf89260ab3ac7e13555b8032b25a74c6d18655", size = 607742, upload-time = "2025-12-04T14:57:42.349Z" }, - { url = "https://files.pythonhosted.org/packages/75/b0/6bde0b1011a60782108c01de5913c588cf51a839174538d266de15e4bf4d/greenlet-3.3.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:047ab3df20ede6a57c35c14bf5200fcf04039d50f908270d3f9a7a82064f543b", size = 609885, upload-time = "2025-12-04T14:26:02.368Z" }, - { url = "https://files.pythonhosted.org/packages/49/0e/49b46ac39f931f59f987b7cd9f34bfec8ef81d2a1e6e00682f55be5de9f4/greenlet-3.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2d9ad37fc657b1102ec880e637cccf20191581f75c64087a549e66c57e1ceb53", size = 1567424, upload-time = "2025-12-04T15:04:23.757Z" }, - { url = "https://files.pythonhosted.org/packages/05/f5/49a9ac2dff7f10091935def9165c90236d8f175afb27cbed38fb1d61ab6b/greenlet-3.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83cd0e36932e0e7f36a64b732a6f60c2fc2df28c351bae79fbaf4f8092fe7614", size = 1636017, upload-time = "2025-12-04T14:27:29.688Z" }, - { url = "https://files.pythonhosted.org/packages/6c/79/3912a94cf27ec503e51ba493692d6db1e3cd8ac7ac52b0b47c8e33d7f4f9/greenlet-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7a34b13d43a6b78abf828a6d0e87d3385680eaf830cd60d20d52f249faabf39", size = 301964, upload-time = "2025-12-04T14:36:58.316Z" }, - { url = "https://files.pythonhosted.org/packages/02/2f/28592176381b9ab2cafa12829ba7b472d177f3acc35d8fbcf3673d966fff/greenlet-3.3.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a1e41a81c7e2825822f4e068c48cb2196002362619e2d70b148f20a831c00739", size = 275140, upload-time = "2025-12-04T14:23:01.282Z" }, - { url = "https://files.pythonhosted.org/packages/2c/80/fbe937bf81e9fca98c981fe499e59a3f45df2a04da0baa5c2be0dca0d329/greenlet-3.3.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f515a47d02da4d30caaa85b69474cec77b7929b2e936ff7fb853d42f4bf8808", size = 599219, upload-time = "2025-12-04T14:50:08.309Z" }, - { url = "https://files.pythonhosted.org/packages/c2/ff/7c985128f0514271b8268476af89aee6866df5eec04ac17dcfbc676213df/greenlet-3.3.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d2d9fd66bfadf230b385fdc90426fcd6eb64db54b40c495b72ac0feb5766c54", size = 610211, upload-time = "2025-12-04T14:57:43.968Z" }, - { url = "https://files.pythonhosted.org/packages/fd/8e/424b8c6e78bd9837d14ff7df01a9829fc883ba2ab4ea787d4f848435f23f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:087ea5e004437321508a8d6f20efc4cfec5e3c30118e1417ea96ed1d93950527", size = 612833, upload-time = "2025-12-04T14:26:03.669Z" }, - { url = "https://files.pythonhosted.org/packages/b5/ba/56699ff9b7c76ca12f1cdc27a886d0f81f2189c3455ff9f65246780f713d/greenlet-3.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ab97cf74045343f6c60a39913fa59710e4bd26a536ce7ab2397adf8b27e67c39", size = 1567256, upload-time = "2025-12-04T15:04:25.276Z" }, - { url = "https://files.pythonhosted.org/packages/1e/37/f31136132967982d698c71a281a8901daf1a8fbab935dce7c0cf15f942cc/greenlet-3.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5375d2e23184629112ca1ea89a53389dddbffcf417dad40125713d88eb5f96e8", size = 1636483, upload-time = "2025-12-04T14:27:30.804Z" }, - { url = "https://files.pythonhosted.org/packages/7e/71/ba21c3fb8c5dce83b8c01f458a42e99ffdb1963aeec08fff5a18588d8fd7/greenlet-3.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:9ee1942ea19550094033c35d25d20726e4f1c40d59545815e1128ac58d416d38", size = 301833, upload-time = "2025-12-04T14:32:23.929Z" }, - { url = "https://files.pythonhosted.org/packages/d7/7c/f0a6d0ede2c7bf092d00bc83ad5bafb7e6ec9b4aab2fbdfa6f134dc73327/greenlet-3.3.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:60c2ef0f578afb3c8d92ea07ad327f9a062547137afe91f38408f08aacab667f", size = 275671, upload-time = "2025-12-04T14:23:05.267Z" }, - { url = "https://files.pythonhosted.org/packages/44/06/dac639ae1a50f5969d82d2e3dd9767d30d6dbdbab0e1a54010c8fe90263c/greenlet-3.3.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a5d554d0712ba1de0a6c94c640f7aeba3f85b3a6e1f2899c11c2c0428da9365", size = 646360, upload-time = "2025-12-04T14:50:10.026Z" }, - { url = "https://files.pythonhosted.org/packages/e0/94/0fb76fe6c5369fba9bf98529ada6f4c3a1adf19e406a47332245ef0eb357/greenlet-3.3.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3a898b1e9c5f7307ebbde4102908e6cbfcb9ea16284a3abe15cab996bee8b9b3", size = 658160, upload-time = "2025-12-04T14:57:45.41Z" }, - { url = "https://files.pythonhosted.org/packages/b8/14/bab308fc2c1b5228c3224ec2bf928ce2e4d21d8046c161e44a2012b5203e/greenlet-3.3.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5773edda4dc00e173820722711d043799d3adb4f01731f40619e07ea2750b955", size = 660166, upload-time = "2025-12-04T14:26:05.099Z" }, - { url = "https://files.pythonhosted.org/packages/4b/d2/91465d39164eaa0085177f61983d80ffe746c5a1860f009811d498e7259c/greenlet-3.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ac0549373982b36d5fd5d30beb8a7a33ee541ff98d2b502714a09f1169f31b55", size = 1615193, upload-time = "2025-12-04T15:04:27.041Z" }, - { url = "https://files.pythonhosted.org/packages/42/1b/83d110a37044b92423084d52d5d5a3b3a73cafb51b547e6d7366ff62eff1/greenlet-3.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d198d2d977460358c3b3a4dc844f875d1adb33817f0613f663a656f463764ccc", size = 1683653, upload-time = "2025-12-04T14:27:32.366Z" }, - { url = "https://files.pythonhosted.org/packages/7c/9a/9030e6f9aa8fd7808e9c31ba4c38f87c4f8ec324ee67431d181fe396d705/greenlet-3.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:73f51dd0e0bdb596fb0417e475fa3c5e32d4c83638296e560086b8d7da7c4170", size = 305387, upload-time = "2025-12-04T14:26:51.063Z" }, - { url = "https://files.pythonhosted.org/packages/a0/66/bd6317bc5932accf351fc19f177ffba53712a202f9df10587da8df257c7e/greenlet-3.3.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:d6ed6f85fae6cdfdb9ce04c9bf7a08d666cfcfb914e7d006f44f840b46741931", size = 282638, upload-time = "2025-12-04T14:25:20.941Z" }, - { url = "https://files.pythonhosted.org/packages/30/cf/cc81cb030b40e738d6e69502ccbd0dd1bced0588e958f9e757945de24404/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9125050fcf24554e69c4cacb086b87b3b55dc395a8b3ebe6487b045b2614388", size = 651145, upload-time = "2025-12-04T14:50:11.039Z" }, - { url = "https://files.pythonhosted.org/packages/9c/ea/1020037b5ecfe95ca7df8d8549959baceb8186031da83d5ecceff8b08cd2/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:87e63ccfa13c0a0f6234ed0add552af24cc67dd886731f2261e46e241608bee3", size = 654236, upload-time = "2025-12-04T14:57:47.007Z" }, - { url = "https://files.pythonhosted.org/packages/57/b9/f8025d71a6085c441a7eaff0fd928bbb275a6633773667023d19179fe815/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c6e9b9c1527a78520357de498b0e709fb9e2f49c3a513afd5a249007261911b", size = 653783, upload-time = "2025-12-04T14:26:06.225Z" }, - { url = "https://files.pythonhosted.org/packages/f6/c7/876a8c7a7485d5d6b5c6821201d542ef28be645aa024cfe1145b35c120c1/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:286d093f95ec98fdd92fcb955003b8a3d054b4e2cab3e2707a5039e7b50520fd", size = 1614857, upload-time = "2025-12-04T15:04:28.484Z" }, - { url = "https://files.pythonhosted.org/packages/4f/dc/041be1dff9f23dac5f48a43323cd0789cb798342011c19a248d9c9335536/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c10513330af5b8ae16f023e8ddbfb486ab355d04467c4679c5cfe4659975dd9", size = 1676034, upload-time = "2025-12-04T14:27:33.531Z" }, +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/99/1cd3411c56a410994669062bd73dd58270c00cc074cac15f385a1fd91f8a/greenlet-3.3.1.tar.gz", hash = "sha256:41848f3230b58c08bb43dee542e74a2a2e34d3c59dc3076cec9151aeeedcae98", size = 184690, upload-time = "2026-01-23T15:31:02.076Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/e8/2e1462c8fdbe0f210feb5ac7ad2d9029af8be3bf45bd9fa39765f821642f/greenlet-3.3.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:5fd23b9bc6d37b563211c6abbb1b3cab27db385a4449af5c32e932f93017080c", size = 274974, upload-time = "2026-01-23T15:31:02.891Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a8/530a401419a6b302af59f67aaf0b9ba1015855ea7e56c036b5928793c5bd/greenlet-3.3.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09f51496a0bfbaa9d74d36a52d2580d1ef5ed4fdfcff0a73730abfbbbe1403dd", size = 577175, upload-time = "2026-01-23T16:00:56.213Z" }, + { url = "https://files.pythonhosted.org/packages/8e/89/7e812bb9c05e1aaef9b597ac1d0962b9021d2c6269354966451e885c4e6b/greenlet-3.3.1-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb0feb07fe6e6a74615ee62a880007d976cf739b6669cce95daa7373d4fc69c5", size = 590401, upload-time = "2026-01-23T16:05:26.365Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ae/8d472e1f5ac5efe55c563f3eabb38c98a44b832602e12910750a7c025802/greenlet-3.3.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:39eda9ba259cc9801da05351eaa8576e9aa83eb9411e8f0c299e05d712a210f2", size = 590272, upload-time = "2026-01-23T15:32:49.411Z" }, + { url = "https://files.pythonhosted.org/packages/a8/51/0fde34bebfcadc833550717eade64e35ec8738e6b097d5d248274a01258b/greenlet-3.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e2e7e882f83149f0a71ac822ebf156d902e7a5d22c9045e3e0d1daf59cee2cc9", size = 1550729, upload-time = "2026-01-23T16:04:20.867Z" }, + { url = "https://files.pythonhosted.org/packages/16/c9/2fb47bee83b25b119d5a35d580807bb8b92480a54b68fef009a02945629f/greenlet-3.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:80aa4d79eb5564f2e0a6144fcc744b5a37c56c4a92d60920720e99210d88db0f", size = 1615552, upload-time = "2026-01-23T15:33:45.743Z" }, + { url = "https://files.pythonhosted.org/packages/1f/54/dcf9f737b96606f82f8dd05becfb8d238db0633dd7397d542a296fe9cad3/greenlet-3.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:32e4ca9777c5addcbf42ff3915d99030d8e00173a56f80001fb3875998fe410b", size = 226462, upload-time = "2026-01-23T15:36:50.422Z" }, + { url = "https://files.pythonhosted.org/packages/91/37/61e1015cf944ddd2337447d8e97fb423ac9bc21f9963fb5f206b53d65649/greenlet-3.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:da19609432f353fed186cc1b85e9440db93d489f198b4bdf42ae19cc9d9ac9b4", size = 225715, upload-time = "2026-01-23T15:33:17.298Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c8/9d76a66421d1ae24340dfae7e79c313957f6e3195c144d2c73333b5bfe34/greenlet-3.3.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:7e806ca53acf6d15a888405880766ec84721aa4181261cd11a457dfe9a7a4975", size = 276443, upload-time = "2026-01-23T15:30:10.066Z" }, + { url = "https://files.pythonhosted.org/packages/81/99/401ff34bb3c032d1f10477d199724f5e5f6fbfb59816ad1455c79c1eb8e7/greenlet-3.3.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d842c94b9155f1c9b3058036c24ffb8ff78b428414a19792b2380be9cecf4f36", size = 597359, upload-time = "2026-01-23T16:00:57.394Z" }, + { url = "https://files.pythonhosted.org/packages/2b/bc/4dcc0871ed557792d304f50be0f7487a14e017952ec689effe2180a6ff35/greenlet-3.3.1-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:20fedaadd422fa02695f82093f9a98bad3dab5fcda793c658b945fcde2ab27ba", size = 607805, upload-time = "2026-01-23T16:05:28.068Z" }, + { url = "https://files.pythonhosted.org/packages/cf/05/821587cf19e2ce1f2b24945d890b164401e5085f9d09cbd969b0c193cd20/greenlet-3.3.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14194f5f4305800ff329cbf02c5fcc88f01886cadd29941b807668a45f0d2336", size = 609947, upload-time = "2026-01-23T15:32:51.004Z" }, + { url = "https://files.pythonhosted.org/packages/a4/52/ee8c46ed9f8babaa93a19e577f26e3d28a519feac6350ed6f25f1afee7e9/greenlet-3.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7b2fe4150a0cf59f847a67db8c155ac36aed89080a6a639e9f16df5d6c6096f1", size = 1567487, upload-time = "2026-01-23T16:04:22.125Z" }, + { url = "https://files.pythonhosted.org/packages/8f/7c/456a74f07029597626f3a6db71b273a3632aecb9afafeeca452cfa633197/greenlet-3.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:49f4ad195d45f4a66a0eb9c1ba4832bb380570d361912fa3554746830d332149", size = 1636087, upload-time = "2026-01-23T15:33:47.486Z" }, + { url = "https://files.pythonhosted.org/packages/34/2f/5e0e41f33c69655300a5e54aeb637cf8ff57f1786a3aba374eacc0228c1d/greenlet-3.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:cc98b9c4e4870fa983436afa999d4eb16b12872fab7071423d5262fa7120d57a", size = 227156, upload-time = "2026-01-23T15:34:34.808Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ab/717c58343cf02c5265b531384b248787e04d8160b8afe53d9eec053d7b44/greenlet-3.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:bfb2d1763d777de5ee495c85309460f6fd8146e50ec9d0ae0183dbf6f0a829d1", size = 226403, upload-time = "2026-01-23T15:31:39.372Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ab/d26750f2b7242c2b90ea2ad71de70cfcd73a948a49513188a0fc0d6fc15a/greenlet-3.3.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:7ab327905cabb0622adca5971e488064e35115430cec2c35a50fd36e72a315b3", size = 275205, upload-time = "2026-01-23T15:30:24.556Z" }, + { url = "https://files.pythonhosted.org/packages/10/d3/be7d19e8fad7c5a78eeefb2d896a08cd4643e1e90c605c4be3b46264998f/greenlet-3.3.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65be2f026ca6a176f88fb935ee23c18333ccea97048076aef4db1ef5bc0713ac", size = 599284, upload-time = "2026-01-23T16:00:58.584Z" }, + { url = "https://files.pythonhosted.org/packages/ae/21/fe703aaa056fdb0f17e5afd4b5c80195bbdab701208918938bd15b00d39b/greenlet-3.3.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7a3ae05b3d225b4155bda56b072ceb09d05e974bc74be6c3fc15463cf69f33fd", size = 610274, upload-time = "2026-01-23T16:05:29.312Z" }, + { url = "https://files.pythonhosted.org/packages/cb/86/5c6ab23bb3c28c21ed6bebad006515cfe08b04613eb105ca0041fecca852/greenlet-3.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6423481193bbbe871313de5fd06a082f2649e7ce6e08015d2a76c1e9186ca5b3", size = 612904, upload-time = "2026-01-23T15:32:52.317Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f3/7949994264e22639e40718c2daf6f6df5169bf48fb038c008a489ec53a50/greenlet-3.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:33a956fe78bbbda82bfc95e128d61129b32d66bcf0a20a1f0c08aa4839ffa951", size = 1567316, upload-time = "2026-01-23T16:04:23.316Z" }, + { url = "https://files.pythonhosted.org/packages/8d/6e/d73c94d13b6465e9f7cd6231c68abde838bb22408596c05d9059830b7872/greenlet-3.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b065d3284be43728dd280f6f9a13990b56470b81be20375a207cdc814a983f2", size = 1636549, upload-time = "2026-01-23T15:33:48.643Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b3/c9c23a6478b3bcc91f979ce4ca50879e4d0b2bd7b9a53d8ecded719b92e2/greenlet-3.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:27289986f4e5b0edec7b5a91063c109f0276abb09a7e9bdab08437525977c946", size = 227042, upload-time = "2026-01-23T15:33:58.216Z" }, + { url = "https://files.pythonhosted.org/packages/90/e7/824beda656097edee36ab15809fd063447b200cc03a7f6a24c34d520bc88/greenlet-3.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:2f080e028001c5273e0b42690eaf359aeef9cb1389da0f171ea51a5dc3c7608d", size = 226294, upload-time = "2026-01-23T15:30:52.73Z" }, + { url = "https://files.pythonhosted.org/packages/ae/fb/011c7c717213182caf78084a9bea51c8590b0afda98001f69d9f853a495b/greenlet-3.3.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:bd59acd8529b372775cd0fcbc5f420ae20681c5b045ce25bd453ed8455ab99b5", size = 275737, upload-time = "2026-01-23T15:32:16.889Z" }, + { url = "https://files.pythonhosted.org/packages/41/2e/a3a417d620363fdbb08a48b1dd582956a46a61bf8fd27ee8164f9dfe87c2/greenlet-3.3.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b31c05dd84ef6871dd47120386aed35323c944d86c3d91a17c4b8d23df62f15b", size = 646422, upload-time = "2026-01-23T16:01:00.354Z" }, + { url = "https://files.pythonhosted.org/packages/b4/09/c6c4a0db47defafd2d6bab8ddfe47ad19963b4e30f5bed84d75328059f8c/greenlet-3.3.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:02925a0bfffc41e542c70aa14c7eda3593e4d7e274bfcccca1827e6c0875902e", size = 658219, upload-time = "2026-01-23T16:05:30.956Z" }, + { url = "https://files.pythonhosted.org/packages/80/38/9d42d60dffb04b45f03dbab9430898352dba277758640751dc5cc316c521/greenlet-3.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34a729e2e4e4ffe9ae2408d5ecaf12f944853f40ad724929b7585bca808a9d6f", size = 660237, upload-time = "2026-01-23T15:32:53.967Z" }, + { url = "https://files.pythonhosted.org/packages/96/61/373c30b7197f9e756e4c81ae90a8d55dc3598c17673f91f4d31c3c689c3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aec9ab04e82918e623415947921dea15851b152b822661cce3f8e4393c3df683", size = 1615261, upload-time = "2026-01-23T16:04:25.066Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d3/ca534310343f5945316f9451e953dcd89b36fe7a19de652a1dc5a0eeef3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:71c767cf281a80d02b6c1bdc41c9468e1f5a494fb11bc8688c360524e273d7b1", size = 1683719, upload-time = "2026-01-23T15:33:50.61Z" }, + { url = "https://files.pythonhosted.org/packages/52/cb/c21a3fd5d2c9c8b622e7bede6d6d00e00551a5ee474ea6d831b5f567a8b4/greenlet-3.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:96aff77af063b607f2489473484e39a0bbae730f2ea90c9e5606c9b73c44174a", size = 228125, upload-time = "2026-01-23T15:32:45.265Z" }, + { url = "https://files.pythonhosted.org/packages/6a/8e/8a2db6d11491837af1de64b8aff23707c6e85241be13c60ed399a72e2ef8/greenlet-3.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:b066e8b50e28b503f604fa538adc764a638b38cf8e81e025011d26e8a627fa79", size = 227519, upload-time = "2026-01-23T15:31:47.284Z" }, + { url = "https://files.pythonhosted.org/packages/28/24/cbbec49bacdcc9ec652a81d3efef7b59f326697e7edf6ed775a5e08e54c2/greenlet-3.3.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:3e63252943c921b90abb035ebe9de832c436401d9c45f262d80e2d06cc659242", size = 282706, upload-time = "2026-01-23T15:33:05.525Z" }, + { url = "https://files.pythonhosted.org/packages/86/2e/4f2b9323c144c4fe8842a4e0d92121465485c3c2c5b9e9b30a52e80f523f/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76e39058e68eb125de10c92524573924e827927df5d3891fbc97bd55764a8774", size = 651209, upload-time = "2026-01-23T16:01:01.517Z" }, + { url = "https://files.pythonhosted.org/packages/d9/87/50ca60e515f5bb55a2fbc5f0c9b5b156de7d2fc51a0a69abc9d23914a237/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9f9d5e7a9310b7a2f416dd13d2e3fd8b42d803968ea580b7c0f322ccb389b97", size = 654300, upload-time = "2026-01-23T16:05:32.199Z" }, + { url = "https://files.pythonhosted.org/packages/1d/94/74310866dfa2b73dd08659a3d18762f83985ad3281901ba0ee9a815194fb/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92497c78adf3ac703b57f1e3813c2d874f27f71a178f9ea5887855da413cd6d2", size = 653842, upload-time = "2026-01-23T15:32:55.671Z" }, + { url = "https://files.pythonhosted.org/packages/97/43/8bf0ffa3d498eeee4c58c212a3905dd6146c01c8dc0b0a046481ca29b18c/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ed6b402bc74d6557a705e197d47f9063733091ed6357b3de33619d8a8d93ac53", size = 1614917, upload-time = "2026-01-23T16:04:26.276Z" }, + { url = "https://files.pythonhosted.org/packages/89/90/a3be7a5f378fc6e84abe4dcfb2ba32b07786861172e502388b4c90000d1b/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:59913f1e5ada20fde795ba906916aea25d442abcc0593fba7e26c92b7ad76249", size = 1676092, upload-time = "2026-01-23T15:33:52.176Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2b/98c7f93e6db9977aaee07eb1e51ca63bd5f779b900d362791d3252e60558/greenlet-3.3.1-cp314-cp314t-win_amd64.whl", hash = "sha256:301860987846c24cb8964bdec0e31a96ad4a2a801b41b4ef40963c1b44f33451", size = 233181, upload-time = "2026-01-23T15:33:00.29Z" }, ] [[package]] @@ -1374,7 +1403,7 @@ wheels = [ [[package]] name = "jupyterlab" -version = "4.5.2" +version = "4.5.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "async-lru" }, @@ -1391,9 +1420,9 @@ dependencies = [ { name = "tornado" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/93/dc/2c8c4ff1aee27ac999ba04c373c5d0d7c6c181b391640d7b916b884d5985/jupyterlab-4.5.2.tar.gz", hash = "sha256:c80a6b9f6dace96a566d590c65ee2785f61e7cd4aac5b4d453dcc7d0d5e069b7", size = 23990371, upload-time = "2026-01-12T12:27:08.493Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3e/76/393eae3349f9a39bf21f8f5406e5244d36e2bfc932049b6070c271f92764/jupyterlab-4.5.3.tar.gz", hash = "sha256:4a159f71067cb38e4a82e86a42de8e7e926f384d7f2291964f282282096d27e8", size = 23939231, upload-time = "2026-01-23T15:04:25.768Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/78/7e455920f104ef2aa94a4c0d2b40e5b44334ee7057eae1aa1fb97b9631ad/jupyterlab-4.5.2-py3-none-any.whl", hash = "sha256:76466ebcfdb7a9bb7e2fbd6459c0e2c032ccf75be673634a84bee4b3e6b13ab6", size = 12385807, upload-time = "2026-01-12T12:27:03.923Z" }, + { url = "https://files.pythonhosted.org/packages/9e/9a/0bf9a7a45f0006d7ff4fdc4fc313de4255acab02bf4db1887c65f0472c01/jupyterlab-4.5.3-py3-none-any.whl", hash = "sha256:63c9f3a48de72ba00df766ad6eed416394f5bb883829f11eeff0872302520ba7", size = 12391761, upload-time = "2026-01-23T15:04:21.214Z" }, ] [[package]] @@ -1580,11 +1609,11 @@ wheels = [ [[package]] name = "markdown" -version = "3.10" +version = "3.10.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/7dd27d9d863b3376fcf23a5a13cb5d024aed1db46f963f1b5735ae43b3be/markdown-3.10.tar.gz", hash = "sha256:37062d4f2aa4b2b6b32aefb80faa300f82cc790cb949a35b8caede34f2b68c0e", size = 364931, upload-time = "2025-11-03T19:51:15.007Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/b1/af95bcae8549f1f3fd70faacb29075826a0d689a27f232e8cee315efa053/markdown-3.10.1.tar.gz", hash = "sha256:1c19c10bd5c14ac948c53d0d762a04e2fa35a6d58a6b7b1e6bfcbe6fefc0001a", size = 365402, upload-time = "2026-01-21T18:09:28.206Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c", size = 107678, upload-time = "2025-11-03T19:51:13.887Z" }, + { url = "https://files.pythonhosted.org/packages/59/1b/6ef961f543593969d25b2afe57a3564200280528caa9bd1082eecdd7b3bc/markdown-3.10.1-py3-none-any.whl", hash = "sha256:867d788939fe33e4b736426f5b9f651ad0c0ae0ecf89df0ca5d1176c70812fe3", size = 107684, upload-time = "2026-01-21T18:09:27.203Z" }, ] [[package]] @@ -1772,7 +1801,7 @@ wheels = [ [[package]] name = "nbconvert" -version = "7.16.6" +version = "7.17.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "beautifulsoup4" }, @@ -1790,9 +1819,9 @@ dependencies = [ { name = "pygments" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/59/f28e15fc47ffb73af68a8d9b47367a8630d76e97ae85ad18271b9db96fdf/nbconvert-7.16.6.tar.gz", hash = "sha256:576a7e37c6480da7b8465eefa66c17844243816ce1ccc372633c6b71c3c0f582", size = 857715, upload-time = "2025-01-28T09:29:14.724Z" } +sdist = { url = "https://files.pythonhosted.org/packages/38/47/81f886b699450d0569f7bc551df2b1673d18df7ff25cc0c21ca36ed8a5ff/nbconvert-7.17.0.tar.gz", hash = "sha256:1b2696f1b5be12309f6c7d707c24af604b87dfaf6d950794c7b07acab96dda78", size = 862855, upload-time = "2026-01-29T16:37:48.478Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl", hash = "sha256:1375a7b67e0c2883678c48e506dc320febb57685e5ee67faa51b18a90f3a712b", size = 258525, upload-time = "2025-01-28T09:29:12.551Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl", hash = "sha256:4f99a63b337b9a23504347afdab24a11faa7d86b405e5c8f9881cd313336d518", size = 261510, upload-time = "2026-01-29T16:37:46.322Z" }, ] [[package]] @@ -1883,81 +1912,81 @@ wheels = [ [[package]] name = "numpy" -version = "2.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/62/ae72ff66c0f1fd959925b4c11f8c2dea61f47f6acaea75a08512cdfe3fed/numpy-2.4.1.tar.gz", hash = "sha256:a1ceafc5042451a858231588a104093474c6a5c57dcc724841f5c888d237d690", size = 20721320, upload-time = "2026-01-10T06:44:59.619Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/34/2b1bc18424f3ad9af577f6ce23600319968a70575bd7db31ce66731bbef9/numpy-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0cce2a669e3c8ba02ee563c7835f92c153cf02edff1ae05e1823f1dde21b16a5", size = 16944563, upload-time = "2026-01-10T06:42:14.615Z" }, - { url = "https://files.pythonhosted.org/packages/2c/57/26e5f97d075aef3794045a6ca9eada6a4ed70eb9a40e7a4a93f9ac80d704/numpy-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:899d2c18024984814ac7e83f8f49d8e8180e2fbe1b2e252f2e7f1d06bea92425", size = 12645658, upload-time = "2026-01-10T06:42:17.298Z" }, - { url = "https://files.pythonhosted.org/packages/8e/ba/80fc0b1e3cb2fd5c6143f00f42eb67762aa043eaa05ca924ecc3222a7849/numpy-2.4.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:09aa8a87e45b55a1c2c205d42e2808849ece5c484b2aab11fecabec3841cafba", size = 5474132, upload-time = "2026-01-10T06:42:19.637Z" }, - { url = "https://files.pythonhosted.org/packages/40/ae/0a5b9a397f0e865ec171187c78d9b57e5588afc439a04ba9cab1ebb2c945/numpy-2.4.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:edee228f76ee2dab4579fad6f51f6a305de09d444280109e0f75df247ff21501", size = 6804159, upload-time = "2026-01-10T06:42:21.44Z" }, - { url = "https://files.pythonhosted.org/packages/86/9c/841c15e691c7085caa6fd162f063eff494099c8327aeccd509d1ab1e36ab/numpy-2.4.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a92f227dbcdc9e4c3e193add1a189a9909947d4f8504c576f4a732fd0b54240a", size = 14708058, upload-time = "2026-01-10T06:42:23.546Z" }, - { url = "https://files.pythonhosted.org/packages/5d/9d/7862db06743f489e6a502a3b93136d73aea27d97b2cf91504f70a27501d6/numpy-2.4.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:538bf4ec353709c765ff75ae616c34d3c3dca1a68312727e8f2676ea644f8509", size = 16651501, upload-time = "2026-01-10T06:42:25.909Z" }, - { url = "https://files.pythonhosted.org/packages/a6/9c/6fc34ebcbd4015c6e5f0c0ce38264010ce8a546cb6beacb457b84a75dfc8/numpy-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ac08c63cb7779b85e9d5318e6c3518b424bc1f364ac4cb2c6136f12e5ff2dccc", size = 16492627, upload-time = "2026-01-10T06:42:28.938Z" }, - { url = "https://files.pythonhosted.org/packages/aa/63/2494a8597502dacda439f61b3c0db4da59928150e62be0e99395c3ad23c5/numpy-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4f9c360ecef085e5841c539a9a12b883dff005fbd7ce46722f5e9cef52634d82", size = 18585052, upload-time = "2026-01-10T06:42:31.312Z" }, - { url = "https://files.pythonhosted.org/packages/6a/93/098e1162ae7522fc9b618d6272b77404c4656c72432ecee3abc029aa3de0/numpy-2.4.1-cp311-cp311-win32.whl", hash = "sha256:0f118ce6b972080ba0758c6087c3617b5ba243d806268623dc34216d69099ba0", size = 6236575, upload-time = "2026-01-10T06:42:33.872Z" }, - { url = "https://files.pythonhosted.org/packages/8c/de/f5e79650d23d9e12f38a7bc6b03ea0835b9575494f8ec94c11c6e773b1b1/numpy-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:18e14c4d09d55eef39a6ab5b08406e84bc6869c1e34eef45564804f90b7e0574", size = 12604479, upload-time = "2026-01-10T06:42:35.778Z" }, - { url = "https://files.pythonhosted.org/packages/dd/65/e1097a7047cff12ce3369bd003811516b20ba1078dbdec135e1cd7c16c56/numpy-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:6461de5113088b399d655d45c3897fa188766415d0f568f175ab071c8873bd73", size = 10578325, upload-time = "2026-01-10T06:42:38.518Z" }, - { url = "https://files.pythonhosted.org/packages/78/7f/ec53e32bf10c813604edf07a3682616bd931d026fcde7b6d13195dfb684a/numpy-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d3703409aac693fa82c0aee023a1ae06a6e9d065dba10f5e8e80f642f1e9d0a2", size = 16656888, upload-time = "2026-01-10T06:42:40.913Z" }, - { url = "https://files.pythonhosted.org/packages/b8/e0/1f9585d7dae8f14864e948fd7fa86c6cb72dee2676ca2748e63b1c5acfe0/numpy-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7211b95ca365519d3596a1d8688a95874cc94219d417504d9ecb2df99fa7bfa8", size = 12373956, upload-time = "2026-01-10T06:42:43.091Z" }, - { url = "https://files.pythonhosted.org/packages/8e/43/9762e88909ff2326f5e7536fa8cb3c49fb03a7d92705f23e6e7f553d9cb3/numpy-2.4.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5adf01965456a664fc727ed69cc71848f28d063217c63e1a0e200a118d5eec9a", size = 5202567, upload-time = "2026-01-10T06:42:45.107Z" }, - { url = "https://files.pythonhosted.org/packages/4b/ee/34b7930eb61e79feb4478800a4b95b46566969d837546aa7c034c742ef98/numpy-2.4.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:26f0bcd9c79a00e339565b303badc74d3ea2bd6d52191eeca5f95936cad107d0", size = 6549459, upload-time = "2026-01-10T06:42:48.152Z" }, - { url = "https://files.pythonhosted.org/packages/79/e3/5f115fae982565771be994867c89bcd8d7208dbfe9469185497d70de5ddf/numpy-2.4.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0093e85df2960d7e4049664b26afc58b03236e967fb942354deef3208857a04c", size = 14404859, upload-time = "2026-01-10T06:42:49.947Z" }, - { url = "https://files.pythonhosted.org/packages/d9/7d/9c8a781c88933725445a859cac5d01b5871588a15969ee6aeb618ba99eee/numpy-2.4.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ad270f438cbdd402c364980317fb6b117d9ec5e226fff5b4148dd9aa9fc6e02", size = 16371419, upload-time = "2026-01-10T06:42:52.409Z" }, - { url = "https://files.pythonhosted.org/packages/a6/d2/8aa084818554543f17cf4162c42f162acbd3bb42688aefdba6628a859f77/numpy-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:297c72b1b98100c2e8f873d5d35fb551fce7040ade83d67dd51d38c8d42a2162", size = 16182131, upload-time = "2026-01-10T06:42:54.694Z" }, - { url = "https://files.pythonhosted.org/packages/60/db/0425216684297c58a8df35f3284ef56ec4a043e6d283f8a59c53562caf1b/numpy-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cf6470d91d34bf669f61d515499859fa7a4c2f7c36434afb70e82df7217933f9", size = 18295342, upload-time = "2026-01-10T06:42:56.991Z" }, - { url = "https://files.pythonhosted.org/packages/31/4c/14cb9d86240bd8c386c881bafbe43f001284b7cce3bc01623ac9475da163/numpy-2.4.1-cp312-cp312-win32.whl", hash = "sha256:b6bcf39112e956594b3331316d90c90c90fb961e39696bda97b89462f5f3943f", size = 5959015, upload-time = "2026-01-10T06:42:59.631Z" }, - { url = "https://files.pythonhosted.org/packages/51/cf/52a703dbeb0c65807540d29699fef5fda073434ff61846a564d5c296420f/numpy-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:e1a27bb1b2dee45a2a53f5ca6ff2d1a7f135287883a1689e930d44d1ff296c87", size = 12310730, upload-time = "2026-01-10T06:43:01.627Z" }, - { url = "https://files.pythonhosted.org/packages/69/80/a828b2d0ade5e74a9fe0f4e0a17c30fdc26232ad2bc8c9f8b3197cf7cf18/numpy-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:0e6e8f9d9ecf95399982019c01223dc130542960a12edfa8edd1122dfa66a8a8", size = 10312166, upload-time = "2026-01-10T06:43:03.673Z" }, - { url = "https://files.pythonhosted.org/packages/04/68/732d4b7811c00775f3bd522a21e8dd5a23f77eb11acdeb663e4a4ebf0ef4/numpy-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d797454e37570cfd61143b73b8debd623c3c0952959adb817dd310a483d58a1b", size = 16652495, upload-time = "2026-01-10T06:43:06.283Z" }, - { url = "https://files.pythonhosted.org/packages/20/ca/857722353421a27f1465652b2c66813eeeccea9d76d5f7b74b99f298e60e/numpy-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82c55962006156aeef1629b953fd359064aa47e4d82cfc8e67f0918f7da3344f", size = 12368657, upload-time = "2026-01-10T06:43:09.094Z" }, - { url = "https://files.pythonhosted.org/packages/81/0d/2377c917513449cc6240031a79d30eb9a163d32a91e79e0da47c43f2c0c8/numpy-2.4.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:71abbea030f2cfc3092a0ff9f8c8fdefdc5e0bf7d9d9c99663538bb0ecdac0b9", size = 5197256, upload-time = "2026-01-10T06:43:13.634Z" }, - { url = "https://files.pythonhosted.org/packages/17/39/569452228de3f5de9064ac75137082c6214be1f5c532016549a7923ab4b5/numpy-2.4.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5b55aa56165b17aaf15520beb9cbd33c9039810e0d9643dd4379e44294c7303e", size = 6545212, upload-time = "2026-01-10T06:43:15.661Z" }, - { url = "https://files.pythonhosted.org/packages/8c/a4/77333f4d1e4dac4395385482557aeecf4826e6ff517e32ca48e1dafbe42a/numpy-2.4.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0faba4a331195bfa96f93dd9dfaa10b2c7aa8cda3a02b7fd635e588fe821bf5", size = 14402871, upload-time = "2026-01-10T06:43:17.324Z" }, - { url = "https://files.pythonhosted.org/packages/ba/87/d341e519956273b39d8d47969dd1eaa1af740615394fe67d06f1efa68773/numpy-2.4.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e3087f53e2b4428766b54932644d148613c5a595150533ae7f00dab2f319a8", size = 16359305, upload-time = "2026-01-10T06:43:19.376Z" }, - { url = "https://files.pythonhosted.org/packages/32/91/789132c6666288eaa20ae8066bb99eba1939362e8f1a534949a215246e97/numpy-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:49e792ec351315e16da54b543db06ca8a86985ab682602d90c60ef4ff4db2a9c", size = 16181909, upload-time = "2026-01-10T06:43:21.808Z" }, - { url = "https://files.pythonhosted.org/packages/cf/b8/090b8bd27b82a844bb22ff8fdf7935cb1980b48d6e439ae116f53cdc2143/numpy-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79e9e06c4c2379db47f3f6fc7a8652e7498251789bf8ff5bd43bf478ef314ca2", size = 18284380, upload-time = "2026-01-10T06:43:23.957Z" }, - { url = "https://files.pythonhosted.org/packages/67/78/722b62bd31842ff029412271556a1a27a98f45359dea78b1548a3a9996aa/numpy-2.4.1-cp313-cp313-win32.whl", hash = "sha256:3d1a100e48cb266090a031397863ff8a30050ceefd798f686ff92c67a486753d", size = 5957089, upload-time = "2026-01-10T06:43:27.535Z" }, - { url = "https://files.pythonhosted.org/packages/da/a6/cf32198b0b6e18d4fbfa9a21a992a7fca535b9bb2b0cdd217d4a3445b5ca/numpy-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:92a0e65272fd60bfa0d9278e0484c2f52fe03b97aedc02b357f33fe752c52ffb", size = 12307230, upload-time = "2026-01-10T06:43:29.298Z" }, - { url = "https://files.pythonhosted.org/packages/44/6c/534d692bfb7d0afe30611320c5fb713659dcb5104d7cc182aff2aea092f5/numpy-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:20d4649c773f66cc2fc36f663e091f57c3b7655f936a4c681b4250855d1da8f5", size = 10313125, upload-time = "2026-01-10T06:43:31.782Z" }, - { url = "https://files.pythonhosted.org/packages/da/a1/354583ac5c4caa566de6ddfbc42744409b515039e085fab6e0ff942e0df5/numpy-2.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f93bc6892fe7b0663e5ffa83b61aab510aacffd58c16e012bb9352d489d90cb7", size = 12496156, upload-time = "2026-01-10T06:43:34.237Z" }, - { url = "https://files.pythonhosted.org/packages/51/b0/42807c6e8cce58c00127b1dc24d365305189991f2a7917aa694a109c8d7d/numpy-2.4.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:178de8f87948163d98a4c9ab5bee4ce6519ca918926ec8df195af582de28544d", size = 5324663, upload-time = "2026-01-10T06:43:36.211Z" }, - { url = "https://files.pythonhosted.org/packages/fe/55/7a621694010d92375ed82f312b2f28017694ed784775269115323e37f5e2/numpy-2.4.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:98b35775e03ab7f868908b524fc0a84d38932d8daf7b7e1c3c3a1b6c7a2c9f15", size = 6645224, upload-time = "2026-01-10T06:43:37.884Z" }, - { url = "https://files.pythonhosted.org/packages/50/96/9fa8635ed9d7c847d87e30c834f7109fac5e88549d79ef3324ab5c20919f/numpy-2.4.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:941c2a93313d030f219f3a71fd3d91a728b82979a5e8034eb2e60d394a2b83f9", size = 14462352, upload-time = "2026-01-10T06:43:39.479Z" }, - { url = "https://files.pythonhosted.org/packages/03/d1/8cf62d8bb2062da4fb82dd5d49e47c923f9c0738032f054e0a75342faba7/numpy-2.4.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:529050522e983e00a6c1c6b67411083630de8b57f65e853d7b03d9281b8694d2", size = 16407279, upload-time = "2026-01-10T06:43:41.93Z" }, - { url = "https://files.pythonhosted.org/packages/86/1c/95c86e17c6b0b31ce6ef219da00f71113b220bcb14938c8d9a05cee0ff53/numpy-2.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2302dc0224c1cbc49bb94f7064f3f923a971bfae45c33870dcbff63a2a550505", size = 16248316, upload-time = "2026-01-10T06:43:44.121Z" }, - { url = "https://files.pythonhosted.org/packages/30/b4/e7f5ff8697274c9d0fa82398b6a372a27e5cef069b37df6355ccb1f1db1a/numpy-2.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9171a42fcad32dcf3fa86f0a4faa5e9f8facefdb276f54b8b390d90447cff4e2", size = 18329884, upload-time = "2026-01-10T06:43:46.613Z" }, - { url = "https://files.pythonhosted.org/packages/37/a4/b073f3e9d77f9aec8debe8ca7f9f6a09e888ad1ba7488f0c3b36a94c03ac/numpy-2.4.1-cp313-cp313t-win32.whl", hash = "sha256:382ad67d99ef49024f11d1ce5dcb5ad8432446e4246a4b014418ba3a1175a1f4", size = 6081138, upload-time = "2026-01-10T06:43:48.854Z" }, - { url = "https://files.pythonhosted.org/packages/16/16/af42337b53844e67752a092481ab869c0523bc95c4e5c98e4dac4e9581ac/numpy-2.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:62fea415f83ad8fdb6c20840578e5fbaf5ddd65e0ec6c3c47eda0f69da172510", size = 12447478, upload-time = "2026-01-10T06:43:50.476Z" }, - { url = "https://files.pythonhosted.org/packages/6c/f8/fa85b2eac68ec631d0b631abc448552cb17d39afd17ec53dcbcc3537681a/numpy-2.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a7870e8c5fc11aef57d6fea4b4085e537a3a60ad2cdd14322ed531fdca68d261", size = 10382981, upload-time = "2026-01-10T06:43:52.575Z" }, - { url = "https://files.pythonhosted.org/packages/1b/a7/ef08d25698e0e4b4efbad8d55251d20fe2a15f6d9aa7c9b30cd03c165e6f/numpy-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3869ea1ee1a1edc16c29bbe3a2f2a4e515cc3a44d43903ad41e0cacdbaf733dc", size = 16652046, upload-time = "2026-01-10T06:43:54.797Z" }, - { url = "https://files.pythonhosted.org/packages/8f/39/e378b3e3ca13477e5ac70293ec027c438d1927f18637e396fe90b1addd72/numpy-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e867df947d427cdd7a60e3e271729090b0f0df80f5f10ab7dd436f40811699c3", size = 12378858, upload-time = "2026-01-10T06:43:57.099Z" }, - { url = "https://files.pythonhosted.org/packages/c3/74/7ec6154f0006910ed1fdbb7591cf4432307033102b8a22041599935f8969/numpy-2.4.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:e3bd2cb07841166420d2fa7146c96ce00cb3410664cbc1a6be028e456c4ee220", size = 5207417, upload-time = "2026-01-10T06:43:59.037Z" }, - { url = "https://files.pythonhosted.org/packages/f7/b7/053ac11820d84e42f8feea5cb81cc4fcd1091499b45b1ed8c7415b1bf831/numpy-2.4.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:f0a90aba7d521e6954670550e561a4cb925713bd944445dbe9e729b71f6cabee", size = 6542643, upload-time = "2026-01-10T06:44:01.852Z" }, - { url = "https://files.pythonhosted.org/packages/c0/c4/2e7908915c0e32ca636b92e4e4a3bdec4cb1e7eb0f8aedf1ed3c68a0d8cd/numpy-2.4.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d558123217a83b2d1ba316b986e9248a1ed1971ad495963d555ccd75dcb1556", size = 14418963, upload-time = "2026-01-10T06:44:04.047Z" }, - { url = "https://files.pythonhosted.org/packages/eb/c0/3ed5083d94e7ffd7c404e54619c088e11f2e1939a9544f5397f4adb1b8ba/numpy-2.4.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f44de05659b67d20499cbc96d49f2650769afcb398b79b324bb6e297bfe3844", size = 16363811, upload-time = "2026-01-10T06:44:06.207Z" }, - { url = "https://files.pythonhosted.org/packages/0e/68/42b66f1852bf525050a67315a4fb94586ab7e9eaa541b1bef530fab0c5dd/numpy-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:69e7419c9012c4aaf695109564e3387f1259f001b4326dfa55907b098af082d3", size = 16197643, upload-time = "2026-01-10T06:44:08.33Z" }, - { url = "https://files.pythonhosted.org/packages/d2/40/e8714fc933d85f82c6bfc7b998a0649ad9769a32f3494ba86598aaf18a48/numpy-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2ffd257026eb1b34352e749d7cc1678b5eeec3e329ad8c9965a797e08ccba205", size = 18289601, upload-time = "2026-01-10T06:44:10.841Z" }, - { url = "https://files.pythonhosted.org/packages/80/9a/0d44b468cad50315127e884802351723daca7cf1c98d102929468c81d439/numpy-2.4.1-cp314-cp314-win32.whl", hash = "sha256:727c6c3275ddefa0dc078524a85e064c057b4f4e71ca5ca29a19163c607be745", size = 6005722, upload-time = "2026-01-10T06:44:13.332Z" }, - { url = "https://files.pythonhosted.org/packages/7e/bb/c6513edcce5a831810e2dddc0d3452ce84d208af92405a0c2e58fd8e7881/numpy-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:7d5d7999df434a038d75a748275cd6c0094b0ecdb0837342b332a82defc4dc4d", size = 12438590, upload-time = "2026-01-10T06:44:15.006Z" }, - { url = "https://files.pythonhosted.org/packages/e9/da/a598d5cb260780cf4d255102deba35c1d072dc028c4547832f45dd3323a8/numpy-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:ce9ce141a505053b3c7bce3216071f3bf5c182b8b28930f14cd24d43932cd2df", size = 10596180, upload-time = "2026-01-10T06:44:17.386Z" }, - { url = "https://files.pythonhosted.org/packages/de/bc/ea3f2c96fcb382311827231f911723aeff596364eb6e1b6d1d91128aa29b/numpy-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4e53170557d37ae404bf8d542ca5b7c629d6efa1117dac6a83e394142ea0a43f", size = 12498774, upload-time = "2026-01-10T06:44:19.467Z" }, - { url = "https://files.pythonhosted.org/packages/aa/ab/ef9d939fe4a812648c7a712610b2ca6140b0853c5efea361301006c02ae5/numpy-2.4.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:a73044b752f5d34d4232f25f18160a1cc418ea4507f5f11e299d8ac36875f8a0", size = 5327274, upload-time = "2026-01-10T06:44:23.189Z" }, - { url = "https://files.pythonhosted.org/packages/bd/31/d381368e2a95c3b08b8cf7faac6004849e960f4a042d920337f71cef0cae/numpy-2.4.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:fb1461c99de4d040666ca0444057b06541e5642f800b71c56e6ea92d6a853a0c", size = 6648306, upload-time = "2026-01-10T06:44:25.012Z" }, - { url = "https://files.pythonhosted.org/packages/c8/e5/0989b44ade47430be6323d05c23207636d67d7362a1796ccbccac6773dd2/numpy-2.4.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:423797bdab2eeefbe608d7c1ec7b2b4fd3c58d51460f1ee26c7500a1d9c9ee93", size = 14464653, upload-time = "2026-01-10T06:44:26.706Z" }, - { url = "https://files.pythonhosted.org/packages/10/a7/cfbe475c35371cae1358e61f20c5f075badc18c4797ab4354140e1d283cf/numpy-2.4.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:52b5f61bdb323b566b528899cc7db2ba5d1015bda7ea811a8bcf3c89c331fa42", size = 16405144, upload-time = "2026-01-10T06:44:29.378Z" }, - { url = "https://files.pythonhosted.org/packages/f8/a3/0c63fe66b534888fa5177cc7cef061541064dbe2b4b60dcc60ffaf0d2157/numpy-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42d7dd5fa36d16d52a84f821eb96031836fd405ee6955dd732f2023724d0aa01", size = 16247425, upload-time = "2026-01-10T06:44:31.721Z" }, - { url = "https://files.pythonhosted.org/packages/6b/2b/55d980cfa2c93bd40ff4c290bf824d792bd41d2fe3487b07707559071760/numpy-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7b6b5e28bbd47b7532698e5db2fe1db693d84b58c254e4389d99a27bb9b8f6b", size = 18330053, upload-time = "2026-01-10T06:44:34.617Z" }, - { url = "https://files.pythonhosted.org/packages/23/12/8b5fc6b9c487a09a7957188e0943c9ff08432c65e34567cabc1623b03a51/numpy-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:5de60946f14ebe15e713a6f22850c2372fa72f4ff9a432ab44aa90edcadaa65a", size = 6152482, upload-time = "2026-01-10T06:44:36.798Z" }, - { url = "https://files.pythonhosted.org/packages/00/a5/9f8ca5856b8940492fc24fbe13c1bc34d65ddf4079097cf9e53164d094e1/numpy-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:8f085da926c0d491ffff3096f91078cc97ea67e7e6b65e490bc8dcda65663be2", size = 12627117, upload-time = "2026-01-10T06:44:38.828Z" }, - { url = "https://files.pythonhosted.org/packages/ad/0d/eca3d962f9eef265f01a8e0d20085c6dd1f443cbffc11b6dede81fd82356/numpy-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:6436cffb4f2bf26c974344439439c95e152c9a527013f26b3577be6c2ca64295", size = 10667121, upload-time = "2026-01-10T06:44:41.644Z" }, - { url = "https://files.pythonhosted.org/packages/1e/48/d86f97919e79314a1cdee4c832178763e6e98e623e123d0bada19e92c15a/numpy-2.4.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8ad35f20be147a204e28b6a0575fbf3540c5e5f802634d4258d55b1ff5facce1", size = 16822202, upload-time = "2026-01-10T06:44:43.738Z" }, - { url = "https://files.pythonhosted.org/packages/51/e9/1e62a7f77e0f37dcfb0ad6a9744e65df00242b6ea37dfafb55debcbf5b55/numpy-2.4.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:8097529164c0f3e32bb89412a0905d9100bf434d9692d9fc275e18dcf53c9344", size = 12569985, upload-time = "2026-01-10T06:44:45.945Z" }, - { url = "https://files.pythonhosted.org/packages/c7/7e/914d54f0c801342306fdcdce3e994a56476f1b818c46c47fc21ae968088c/numpy-2.4.1-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:ea66d2b41ca4a1630aae5507ee0a71647d3124d1741980138aa8f28f44dac36e", size = 5398484, upload-time = "2026-01-10T06:44:48.012Z" }, - { url = "https://files.pythonhosted.org/packages/1c/d8/9570b68584e293a33474e7b5a77ca404f1dcc655e40050a600dee81d27fb/numpy-2.4.1-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:d3f8f0df9f4b8be57b3bf74a1d087fec68f927a2fab68231fdb442bf2c12e426", size = 6713216, upload-time = "2026-01-10T06:44:49.725Z" }, - { url = "https://files.pythonhosted.org/packages/33/9b/9dd6e2db8d49eb24f86acaaa5258e5f4c8ed38209a4ee9de2d1a0ca25045/numpy-2.4.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2023ef86243690c2791fd6353e5b4848eedaa88ca8a2d129f462049f6d484696", size = 14538937, upload-time = "2026-01-10T06:44:51.498Z" }, - { url = "https://files.pythonhosted.org/packages/53/87/d5bd995b0f798a37105b876350d346eea5838bd8f77ea3d7a48392f3812b/numpy-2.4.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8361ea4220d763e54cff2fbe7d8c93526b744f7cd9ddab47afeff7e14e8503be", size = 16479830, upload-time = "2026-01-10T06:44:53.931Z" }, - { url = "https://files.pythonhosted.org/packages/5b/c7/b801bf98514b6ae6475e941ac05c58e6411dd863ea92916bfd6d510b08c1/numpy-2.4.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:4f1b68ff47680c2925f8063402a693ede215f0257f02596b1318ecdfb1d79e33", size = 12492579, upload-time = "2026-01-10T06:44:57.094Z" }, +version = "2.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/44/71852273146957899753e69986246d6a176061ea183407e95418c2aa4d9a/numpy-2.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7e88598032542bd49af7c4747541422884219056c268823ef6e5e89851c8825", size = 16955478, upload-time = "2026-01-31T23:10:25.623Z" }, + { url = "https://files.pythonhosted.org/packages/74/41/5d17d4058bd0cd96bcbd4d9ff0fb2e21f52702aab9a72e4a594efa18692f/numpy-2.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7edc794af8b36ca37ef5fcb5e0d128c7e0595c7b96a2318d1badb6fcd8ee86b1", size = 14965467, upload-time = "2026-01-31T23:10:28.186Z" }, + { url = "https://files.pythonhosted.org/packages/49/48/fb1ce8136c19452ed15f033f8aee91d5defe515094e330ce368a0647846f/numpy-2.4.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6e9f61981ace1360e42737e2bae58b27bf28a1b27e781721047d84bd754d32e7", size = 5475172, upload-time = "2026-01-31T23:10:30.848Z" }, + { url = "https://files.pythonhosted.org/packages/40/a9/3feb49f17bbd1300dd2570432961f5c8a4ffeff1db6f02c7273bd020a4c9/numpy-2.4.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cb7bbb88aa74908950d979eeaa24dbdf1a865e3c7e45ff0121d8f70387b55f73", size = 6805145, upload-time = "2026-01-31T23:10:32.352Z" }, + { url = "https://files.pythonhosted.org/packages/3f/39/fdf35cbd6d6e2fcad42fcf85ac04a85a0d0fbfbf34b30721c98d602fd70a/numpy-2.4.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f069069931240b3fc703f1e23df63443dbd6390614c8c44a87d96cd0ec81eb1", size = 15966084, upload-time = "2026-01-31T23:10:34.502Z" }, + { url = "https://files.pythonhosted.org/packages/1b/46/6fa4ea94f1ddf969b2ee941290cca6f1bfac92b53c76ae5f44afe17ceb69/numpy-2.4.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c02ef4401a506fb60b411467ad501e1429a3487abca4664871d9ae0b46c8ba32", size = 16899477, upload-time = "2026-01-31T23:10:37.075Z" }, + { url = "https://files.pythonhosted.org/packages/09/a1/2a424e162b1a14a5bd860a464ab4e07513916a64ab1683fae262f735ccd2/numpy-2.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2653de5c24910e49c2b106499803124dde62a5a1fe0eedeaecf4309a5f639390", size = 17323429, upload-time = "2026-01-31T23:10:39.704Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a2/73014149ff250628df72c58204822ac01d768697913881aacf839ff78680/numpy-2.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1ae241bbfc6ae276f94a170b14785e561cb5e7f626b6688cf076af4110887413", size = 18635109, upload-time = "2026-01-31T23:10:41.924Z" }, + { url = "https://files.pythonhosted.org/packages/6c/0c/73e8be2f1accd56df74abc1c5e18527822067dced5ec0861b5bb882c2ce0/numpy-2.4.2-cp311-cp311-win32.whl", hash = "sha256:df1b10187212b198dd45fa943d8985a3c8cf854aed4923796e0e019e113a1bda", size = 6237915, upload-time = "2026-01-31T23:10:45.26Z" }, + { url = "https://files.pythonhosted.org/packages/76/ae/e0265e0163cf127c24c3969d29f1c4c64551a1e375d95a13d32eab25d364/numpy-2.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:b9c618d56a29c9cb1c4da979e9899be7578d2e0b3c24d52079c166324c9e8695", size = 12607972, upload-time = "2026-01-31T23:10:47.021Z" }, + { url = "https://files.pythonhosted.org/packages/29/a5/c43029af9b8014d6ea157f192652c50042e8911f4300f8f6ed3336bf437f/numpy-2.4.2-cp311-cp311-win_arm64.whl", hash = "sha256:47c5a6ed21d9452b10227e5e8a0e1c22979811cad7dcc19d8e3e2fb8fa03f1a3", size = 10485763, upload-time = "2026-01-31T23:10:50.087Z" }, + { url = "https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a", size = 16667963, upload-time = "2026-01-31T23:10:52.147Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1", size = 14693571, upload-time = "2026-01-31T23:10:54.789Z" }, + { url = "https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e", size = 5203469, upload-time = "2026-01-31T23:10:57.343Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27", size = 6550820, upload-time = "2026-01-31T23:10:59.429Z" }, + { url = "https://files.pythonhosted.org/packages/b1/91/b97fdfd12dc75b02c44e26c6638241cc004d4079a0321a69c62f51470c4c/numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548", size = 15663067, upload-time = "2026-01-31T23:11:01.291Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c6/a18e59f3f0b8071cc85cbc8d80cd02d68aa9710170b2553a117203d46936/numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f", size = 16619782, upload-time = "2026-01-31T23:11:03.669Z" }, + { url = "https://files.pythonhosted.org/packages/b7/83/9751502164601a79e18847309f5ceec0b1446d7b6aa12305759b72cf98b2/numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460", size = 17013128, upload-time = "2026-01-31T23:11:05.913Z" }, + { url = "https://files.pythonhosted.org/packages/61/c4/c4066322256ec740acc1c8923a10047818691d2f8aec254798f3dd90f5f2/numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba", size = 18345324, upload-time = "2026-01-31T23:11:08.248Z" }, + { url = "https://files.pythonhosted.org/packages/ab/af/6157aa6da728fa4525a755bfad486ae7e3f76d4c1864138003eb84328497/numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f", size = 5960282, upload-time = "2026-01-31T23:11:10.497Z" }, + { url = "https://files.pythonhosted.org/packages/92/0f/7ceaaeaacb40567071e94dbf2c9480c0ae453d5bb4f52bea3892c39dc83c/numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85", size = 12314210, upload-time = "2026-01-31T23:11:12.176Z" }, + { url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z" }, + { url = "https://files.pythonhosted.org/packages/a1/22/815b9fe25d1d7ae7d492152adbc7226d3eff731dffc38fe970589fcaaa38/numpy-2.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c", size = 16663696, upload-time = "2026-01-31T23:11:17.516Z" }, + { url = "https://files.pythonhosted.org/packages/09/f0/817d03a03f93ba9c6c8993de509277d84e69f9453601915e4a69554102a1/numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979", size = 14688322, upload-time = "2026-01-31T23:11:19.883Z" }, + { url = "https://files.pythonhosted.org/packages/da/b4/f805ab79293c728b9a99438775ce51885fd4f31b76178767cfc718701a39/numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98", size = 5198157, upload-time = "2026-01-31T23:11:22.375Z" }, + { url = "https://files.pythonhosted.org/packages/74/09/826e4289844eccdcd64aac27d13b0fd3f32039915dd5b9ba01baae1f436c/numpy-2.4.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef", size = 6546330, upload-time = "2026-01-31T23:11:23.958Z" }, + { url = "https://files.pythonhosted.org/packages/19/fb/cbfdbfa3057a10aea5422c558ac57538e6acc87ec1669e666d32ac198da7/numpy-2.4.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7", size = 15660968, upload-time = "2026-01-31T23:11:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/04/dc/46066ce18d01645541f0186877377b9371b8fa8017fa8262002b4ef22612/numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499", size = 16607311, upload-time = "2026-01-31T23:11:28.117Z" }, + { url = "https://files.pythonhosted.org/packages/14/d9/4b5adfc39a43fa6bf918c6d544bc60c05236cc2f6339847fc5b35e6cb5b0/numpy-2.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb", size = 17012850, upload-time = "2026-01-31T23:11:30.888Z" }, + { url = "https://files.pythonhosted.org/packages/b7/20/adb6e6adde6d0130046e6fdfb7675cc62bc2f6b7b02239a09eb58435753d/numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7", size = 18334210, upload-time = "2026-01-31T23:11:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/78/0e/0a73b3dff26803a8c02baa76398015ea2a5434d9b8265a7898a6028c1591/numpy-2.4.2-cp313-cp313-win32.whl", hash = "sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110", size = 5958199, upload-time = "2026-01-31T23:11:35.385Z" }, + { url = "https://files.pythonhosted.org/packages/43/bc/6352f343522fcb2c04dbaf94cb30cca6fd32c1a750c06ad6231b4293708c/numpy-2.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622", size = 12310848, upload-time = "2026-01-31T23:11:38.001Z" }, + { url = "https://files.pythonhosted.org/packages/6e/8d/6da186483e308da5da1cc6918ce913dcfe14ffde98e710bfeff2a6158d4e/numpy-2.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71", size = 10221082, upload-time = "2026-01-31T23:11:40.392Z" }, + { url = "https://files.pythonhosted.org/packages/25/a1/9510aa43555b44781968935c7548a8926274f815de42ad3997e9e83680dd/numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262", size = 14815866, upload-time = "2026-01-31T23:11:42.495Z" }, + { url = "https://files.pythonhosted.org/packages/36/30/6bbb5e76631a5ae46e7923dd16ca9d3f1c93cfa8d4ed79a129814a9d8db3/numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913", size = 5325631, upload-time = "2026-01-31T23:11:44.7Z" }, + { url = "https://files.pythonhosted.org/packages/46/00/3a490938800c1923b567b3a15cd17896e68052e2145d8662aaf3e1ffc58f/numpy-2.4.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab", size = 6646254, upload-time = "2026-01-31T23:11:46.341Z" }, + { url = "https://files.pythonhosted.org/packages/d3/e9/fac0890149898a9b609caa5af7455a948b544746e4b8fe7c212c8edd71f8/numpy-2.4.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82", size = 15720138, upload-time = "2026-01-31T23:11:48.082Z" }, + { url = "https://files.pythonhosted.org/packages/ea/5c/08887c54e68e1e28df53709f1893ce92932cc6f01f7c3d4dc952f61ffd4e/numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f", size = 16655398, upload-time = "2026-01-31T23:11:50.293Z" }, + { url = "https://files.pythonhosted.org/packages/4d/89/253db0fa0e66e9129c745e4ef25631dc37d5f1314dad2b53e907b8538e6d/numpy-2.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554", size = 17079064, upload-time = "2026-01-31T23:11:52.927Z" }, + { url = "https://files.pythonhosted.org/packages/2a/d5/cbade46ce97c59c6c3da525e8d95b7abe8a42974a1dc5c1d489c10433e88/numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257", size = 18379680, upload-time = "2026-01-31T23:11:55.22Z" }, + { url = "https://files.pythonhosted.org/packages/40/62/48f99ae172a4b63d981babe683685030e8a3df4f246c893ea5c6ef99f018/numpy-2.4.2-cp313-cp313t-win32.whl", hash = "sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657", size = 6082433, upload-time = "2026-01-31T23:11:58.096Z" }, + { url = "https://files.pythonhosted.org/packages/07/38/e054a61cfe48ad9f1ed0d188e78b7e26859d0b60ef21cd9de4897cdb5326/numpy-2.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b", size = 12451181, upload-time = "2026-01-31T23:11:59.782Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a4/a05c3a6418575e185dd84d0b9680b6bb2e2dc3e4202f036b7b4e22d6e9dc/numpy-2.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1", size = 10290756, upload-time = "2026-01-31T23:12:02.438Z" }, + { url = "https://files.pythonhosted.org/packages/18/88/b7df6050bf18fdcfb7046286c6535cabbdd2064a3440fca3f069d319c16e/numpy-2.4.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:444be170853f1f9d528428eceb55f12918e4fda5d8805480f36a002f1415e09b", size = 16663092, upload-time = "2026-01-31T23:12:04.521Z" }, + { url = "https://files.pythonhosted.org/packages/25/7a/1fee4329abc705a469a4afe6e69b1ef7e915117747886327104a8493a955/numpy-2.4.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d1240d50adff70c2a88217698ca844723068533f3f5c5fa6ee2e3220e3bdb000", size = 14698770, upload-time = "2026-01-31T23:12:06.96Z" }, + { url = "https://files.pythonhosted.org/packages/fb/0b/f9e49ba6c923678ad5bc38181c08ac5e53b7a5754dbca8e581aa1a56b1ff/numpy-2.4.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:7cdde6de52fb6664b00b056341265441192d1291c130e99183ec0d4b110ff8b1", size = 5208562, upload-time = "2026-01-31T23:12:09.632Z" }, + { url = "https://files.pythonhosted.org/packages/7d/12/d7de8f6f53f9bb76997e5e4c069eda2051e3fe134e9181671c4391677bb2/numpy-2.4.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:cda077c2e5b780200b6b3e09d0b42205a3d1c68f30c6dceb90401c13bff8fe74", size = 6543710, upload-time = "2026-01-31T23:12:11.969Z" }, + { url = "https://files.pythonhosted.org/packages/09/63/c66418c2e0268a31a4cf8a8b512685748200f8e8e8ec6c507ce14e773529/numpy-2.4.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d30291931c915b2ab5717c2974bb95ee891a1cf22ebc16a8006bd59cd210d40a", size = 15677205, upload-time = "2026-01-31T23:12:14.33Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6c/7f237821c9642fb2a04d2f1e88b4295677144ca93285fd76eff3bcba858d/numpy-2.4.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bba37bc29d4d85761deed3954a1bc62be7cf462b9510b51d367b769a8c8df325", size = 16611738, upload-time = "2026-01-31T23:12:16.525Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a7/39c4cdda9f019b609b5c473899d87abff092fc908cfe4d1ecb2fcff453b0/numpy-2.4.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b2f0073ed0868db1dcd86e052d37279eef185b9c8db5bf61f30f46adac63c909", size = 17028888, upload-time = "2026-01-31T23:12:19.306Z" }, + { url = "https://files.pythonhosted.org/packages/da/b3/e84bb64bdfea967cc10950d71090ec2d84b49bc691df0025dddb7c26e8e3/numpy-2.4.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f54844851cdb630ceb623dcec4db3240d1ac13d4990532446761baede94996a", size = 18339556, upload-time = "2026-01-31T23:12:21.816Z" }, + { url = "https://files.pythonhosted.org/packages/88/f5/954a291bc1192a27081706862ac62bb5920fbecfbaa302f64682aa90beed/numpy-2.4.2-cp314-cp314-win32.whl", hash = "sha256:12e26134a0331d8dbd9351620f037ec470b7c75929cb8a1537f6bfe411152a1a", size = 6006899, upload-time = "2026-01-31T23:12:24.14Z" }, + { url = "https://files.pythonhosted.org/packages/05/cb/eff72a91b2efdd1bc98b3b8759f6a1654aa87612fc86e3d87d6fe4f948c4/numpy-2.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:068cdb2d0d644cdb45670810894f6a0600797a69c05f1ac478e8d31670b8ee75", size = 12443072, upload-time = "2026-01-31T23:12:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/37/75/62726948db36a56428fce4ba80a115716dc4fad6a3a4352487f8bb950966/numpy-2.4.2-cp314-cp314-win_arm64.whl", hash = "sha256:6ed0be1ee58eef41231a5c943d7d1375f093142702d5723ca2eb07db9b934b05", size = 10494886, upload-time = "2026-01-31T23:12:28.488Z" }, + { url = "https://files.pythonhosted.org/packages/36/2f/ee93744f1e0661dc267e4b21940870cabfae187c092e1433b77b09b50ac4/numpy-2.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:98f16a80e917003a12c0580f97b5f875853ebc33e2eaa4bccfc8201ac6869308", size = 14818567, upload-time = "2026-01-31T23:12:30.709Z" }, + { url = "https://files.pythonhosted.org/packages/a7/24/6535212add7d76ff938d8bdc654f53f88d35cddedf807a599e180dcb8e66/numpy-2.4.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:20abd069b9cda45874498b245c8015b18ace6de8546bf50dfa8cea1696ed06ef", size = 5328372, upload-time = "2026-01-31T23:12:32.962Z" }, + { url = "https://files.pythonhosted.org/packages/5e/9d/c48f0a035725f925634bf6b8994253b43f2047f6778a54147d7e213bc5a7/numpy-2.4.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e98c97502435b53741540a5717a6749ac2ada901056c7db951d33e11c885cc7d", size = 6649306, upload-time = "2026-01-31T23:12:34.797Z" }, + { url = "https://files.pythonhosted.org/packages/81/05/7c73a9574cd4a53a25907bad38b59ac83919c0ddc8234ec157f344d57d9a/numpy-2.4.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da6cad4e82cb893db4b69105c604d805e0c3ce11501a55b5e9f9083b47d2ffe8", size = 15722394, upload-time = "2026-01-31T23:12:36.565Z" }, + { url = "https://files.pythonhosted.org/packages/35/fa/4de10089f21fc7d18442c4a767ab156b25c2a6eaf187c0db6d9ecdaeb43f/numpy-2.4.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4424677ce4b47fe73c8b5556d876571f7c6945d264201180db2dc34f676ab5", size = 16653343, upload-time = "2026-01-31T23:12:39.188Z" }, + { url = "https://files.pythonhosted.org/packages/b8/f9/d33e4ffc857f3763a57aa85650f2e82486832d7492280ac21ba9efda80da/numpy-2.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2b8f157c8a6f20eb657e240f8985cc135598b2b46985c5bccbde7616dc9c6b1e", size = 17078045, upload-time = "2026-01-31T23:12:42.041Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b8/54bdb43b6225badbea6389fa038c4ef868c44f5890f95dd530a218706da3/numpy-2.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5daf6f3914a733336dab21a05cdec343144600e964d2fcdabaac0c0269874b2a", size = 18380024, upload-time = "2026-01-31T23:12:44.331Z" }, + { url = "https://files.pythonhosted.org/packages/a5/55/6e1a61ded7af8df04016d81b5b02daa59f2ea9252ee0397cb9f631efe9e5/numpy-2.4.2-cp314-cp314t-win32.whl", hash = "sha256:8c50dd1fc8826f5b26a5ee4d77ca55d88a895f4e4819c7ecc2a9f5905047a443", size = 6153937, upload-time = "2026-01-31T23:12:47.229Z" }, + { url = "https://files.pythonhosted.org/packages/45/aa/fa6118d1ed6d776b0983f3ceac9b1a5558e80df9365b1c3aa6d42bf9eee4/numpy-2.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fcf92bee92742edd401ba41135185866f7026c502617f422eb432cfeca4fe236", size = 12631844, upload-time = "2026-01-31T23:12:48.997Z" }, + { url = "https://files.pythonhosted.org/packages/32/0a/2ec5deea6dcd158f254a7b372fb09cfba5719419c8d66343bab35237b3fb/numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181", size = 10565379, upload-time = "2026-01-31T23:12:51.345Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f8/50e14d36d915ef64d8f8bc4a087fc8264d82c785eda6711f80ab7e620335/numpy-2.4.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:89f7268c009bc492f506abd6f5265defa7cb3f7487dc21d357c3d290add45082", size = 16833179, upload-time = "2026-01-31T23:12:53.5Z" }, + { url = "https://files.pythonhosted.org/packages/17/17/809b5cad63812058a8189e91a1e2d55a5a18fd04611dbad244e8aeae465c/numpy-2.4.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6dee3bb76aa4009d5a912180bf5b2de012532998d094acee25d9cb8dee3e44a", size = 14889755, upload-time = "2026-01-31T23:12:55.933Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ea/181b9bcf7627fc8371720316c24db888dcb9829b1c0270abf3d288b2e29b/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:cd2bd2bbed13e213d6b55dc1d035a4f91748a7d3edc9480c13898b0353708920", size = 5399500, upload-time = "2026-01-31T23:12:58.671Z" }, + { url = "https://files.pythonhosted.org/packages/33/9f/413adf3fc955541ff5536b78fcf0754680b3c6d95103230252a2c9408d23/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:cf28c0c1d4c4bf00f509fa7eb02c58d7caf221b50b467bcb0d9bbf1584d5c821", size = 6714252, upload-time = "2026-01-31T23:13:00.518Z" }, + { url = "https://files.pythonhosted.org/packages/91/da/643aad274e29ccbdf42ecd94dafe524b81c87bcb56b83872d54827f10543/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e04ae107ac591763a47398bb45b568fc38f02dbc4aa44c063f67a131f99346cb", size = 15797142, upload-time = "2026-01-31T23:13:02.219Z" }, + { url = "https://files.pythonhosted.org/packages/66/27/965b8525e9cb5dc16481b30a1b3c21e50c7ebf6e9dbd48d0c4d0d5089c7e/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:602f65afdef699cda27ec0b9224ae5dc43e328f4c24c689deaf77133dbee74d0", size = 16727979, upload-time = "2026-01-31T23:13:04.62Z" }, + { url = "https://files.pythonhosted.org/packages/de/e5/b7d20451657664b07986c2f6e3be564433f5dcaf3482d68eaecd79afaf03/numpy-2.4.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be71bf1edb48ebbbf7f6337b5bfd2f895d1902f6335a5830b20141fc126ffba0", size = 12502577, upload-time = "2026-01-31T23:13:07.08Z" }, ] [[package]] @@ -2080,10 +2109,10 @@ wheels = [ [[package]] name = "nvidia-nvshmem-cu12" -version = "3.3.20" +version = "3.4.5" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/6c/99acb2f9eb85c29fc6f3a7ac4dccfd992e22666dd08a642b303311326a97/nvidia_nvshmem_cu12-3.3.20-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d00f26d3f9b2e3c3065be895e3059d6479ea5c638a3f38c9fec49b1b9dd7c1e5", size = 124657145, upload-time = "2025-08-04T20:25:19.995Z" }, + { url = "https://files.pythonhosted.org/packages/b5/09/6ea3ea725f82e1e76684f0708bbedd871fc96da89945adeba65c3835a64c/nvidia_nvshmem_cu12-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:042f2500f24c021db8a06c5eec2539027d57460e1c1a762055a6554f72c369bd", size = 139103095, upload-time = "2025-09-06T00:32:31.266Z" }, ] [[package]] @@ -2123,11 +2152,11 @@ wheels = [ [[package]] name = "packaging" -version = "25.0" +version = "26.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] [[package]] @@ -2332,45 +2361,45 @@ wheels = [ [[package]] name = "protobuf" -version = "6.33.4" +version = "6.33.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/b8/cda15d9d46d03d4aa3a67cb6bffe05173440ccf86a9541afaf7ac59a1b6b/protobuf-6.33.4.tar.gz", hash = "sha256:dc2e61bca3b10470c1912d166fe0af67bfc20eb55971dcef8dfa48ce14f0ed91", size = 444346, upload-time = "2026-01-12T18:33:40.109Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465, upload-time = "2026-01-29T21:51:33.494Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/be/24ef9f3095bacdf95b458543334d0c4908ccdaee5130420bf064492c325f/protobuf-6.33.4-cp310-abi3-win32.whl", hash = "sha256:918966612c8232fc6c24c78e1cd89784307f5814ad7506c308ee3cf86662850d", size = 425612, upload-time = "2026-01-12T18:33:29.656Z" }, - { url = "https://files.pythonhosted.org/packages/31/ad/e5693e1974a28869e7cd244302911955c1cebc0161eb32dfa2b25b6e96f0/protobuf-6.33.4-cp310-abi3-win_amd64.whl", hash = "sha256:8f11ffae31ec67fc2554c2ef891dcb561dae9a2a3ed941f9e134c2db06657dbc", size = 436962, upload-time = "2026-01-12T18:33:31.345Z" }, - { url = "https://files.pythonhosted.org/packages/66/15/6ee23553b6bfd82670207ead921f4d8ef14c107e5e11443b04caeb5ab5ec/protobuf-6.33.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2fe67f6c014c84f655ee06f6f66213f9254b3a8b6bda6cda0ccd4232c73c06f0", size = 427612, upload-time = "2026-01-12T18:33:32.646Z" }, - { url = "https://files.pythonhosted.org/packages/2b/48/d301907ce6d0db75f959ca74f44b475a9caa8fcba102d098d3c3dd0f2d3f/protobuf-6.33.4-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:757c978f82e74d75cba88eddec479df9b99a42b31193313b75e492c06a51764e", size = 324484, upload-time = "2026-01-12T18:33:33.789Z" }, - { url = "https://files.pythonhosted.org/packages/92/1c/e53078d3f7fe710572ab2dcffd993e1e3b438ae71cfc031b71bae44fcb2d/protobuf-6.33.4-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c7c64f259c618f0bef7bee042075e390debbf9682334be2b67408ec7c1c09ee6", size = 339256, upload-time = "2026-01-12T18:33:35.231Z" }, - { url = "https://files.pythonhosted.org/packages/e8/8e/971c0edd084914f7ee7c23aa70ba89e8903918adca179319ee94403701d5/protobuf-6.33.4-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:3df850c2f8db9934de4cf8f9152f8dc2558f49f298f37f90c517e8e5c84c30e9", size = 323311, upload-time = "2026-01-12T18:33:36.305Z" }, - { url = "https://files.pythonhosted.org/packages/75/b1/1dc83c2c661b4c62d56cc081706ee33a4fc2835bd90f965baa2663ef7676/protobuf-6.33.4-py3-none-any.whl", hash = "sha256:1fe3730068fcf2e595816a6c34fe66eeedd37d51d0400b72fabc848811fdc1bc", size = 170532, upload-time = "2026-01-12T18:33:39.199Z" }, + { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769, upload-time = "2026-01-29T21:51:21.751Z" }, + { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118, upload-time = "2026-01-29T21:51:24.022Z" }, + { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766, upload-time = "2026-01-29T21:51:25.413Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638, upload-time = "2026-01-29T21:51:26.423Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411, upload-time = "2026-01-29T21:51:27.446Z" }, + { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465, upload-time = "2026-01-29T21:51:28.925Z" }, + { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" }, ] [[package]] name = "psutil" -version = "7.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/73/cb/09e5184fb5fc0358d110fc3ca7f6b1d033800734d34cac10f4136cfac10e/psutil-7.2.1.tar.gz", hash = "sha256:f7583aec590485b43ca601dd9cea0dcd65bd7bb21d30ef4ddbf4ea6b5ed1bdd3", size = 490253, upload-time = "2025-12-29T08:26:00.169Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/77/8e/f0c242053a368c2aa89584ecd1b054a18683f13d6e5a318fc9ec36582c94/psutil-7.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ba9f33bb525b14c3ea563b2fd521a84d2fa214ec59e3e6a2858f78d0844dd60d", size = 129624, upload-time = "2025-12-29T08:26:04.255Z" }, - { url = "https://files.pythonhosted.org/packages/26/97/a58a4968f8990617decee234258a2b4fc7cd9e35668387646c1963e69f26/psutil-7.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:81442dac7abfc2f4f4385ea9e12ddf5a796721c0f6133260687fec5c3780fa49", size = 130132, upload-time = "2025-12-29T08:26:06.228Z" }, - { url = "https://files.pythonhosted.org/packages/db/6d/ed44901e830739af5f72a85fa7ec5ff1edea7f81bfbf4875e409007149bd/psutil-7.2.1-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ea46c0d060491051d39f0d2cff4f98d5c72b288289f57a21556cc7d504db37fc", size = 180612, upload-time = "2025-12-29T08:26:08.276Z" }, - { url = "https://files.pythonhosted.org/packages/c7/65/b628f8459bca4efbfae50d4bf3feaab803de9a160b9d5f3bd9295a33f0c2/psutil-7.2.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:35630d5af80d5d0d49cfc4d64c1c13838baf6717a13effb35869a5919b854cdf", size = 183201, upload-time = "2025-12-29T08:26:10.622Z" }, - { url = "https://files.pythonhosted.org/packages/fb/23/851cadc9764edcc18f0effe7d0bf69f727d4cf2442deb4a9f78d4e4f30f2/psutil-7.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:923f8653416604e356073e6e0bccbe7c09990acef442def2f5640dd0faa9689f", size = 139081, upload-time = "2025-12-29T08:26:12.483Z" }, - { url = "https://files.pythonhosted.org/packages/59/82/d63e8494ec5758029f31c6cb06d7d161175d8281e91d011a4a441c8a43b5/psutil-7.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cfbe6b40ca48019a51827f20d830887b3107a74a79b01ceb8cc8de4ccb17b672", size = 134767, upload-time = "2025-12-29T08:26:14.528Z" }, - { url = "https://files.pythonhosted.org/packages/05/c2/5fb764bd61e40e1fe756a44bd4c21827228394c17414ade348e28f83cd79/psutil-7.2.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:494c513ccc53225ae23eec7fe6e1482f1b8a44674241b54561f755a898650679", size = 129716, upload-time = "2025-12-29T08:26:16.017Z" }, - { url = "https://files.pythonhosted.org/packages/c9/d2/935039c20e06f615d9ca6ca0ab756cf8408a19d298ffaa08666bc18dc805/psutil-7.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3fce5f92c22b00cdefd1645aa58ab4877a01679e901555067b1bd77039aa589f", size = 130133, upload-time = "2025-12-29T08:26:18.009Z" }, - { url = "https://files.pythonhosted.org/packages/77/69/19f1eb0e01d24c2b3eacbc2f78d3b5add8a89bf0bb69465bc8d563cc33de/psutil-7.2.1-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93f3f7b0bb07711b49626e7940d6fe52aa9940ad86e8f7e74842e73189712129", size = 181518, upload-time = "2025-12-29T08:26:20.241Z" }, - { url = "https://files.pythonhosted.org/packages/e1/6d/7e18b1b4fa13ad370787626c95887b027656ad4829c156bb6569d02f3262/psutil-7.2.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d34d2ca888208eea2b5c68186841336a7f5e0b990edec929be909353a202768a", size = 184348, upload-time = "2025-12-29T08:26:22.215Z" }, - { url = "https://files.pythonhosted.org/packages/98/60/1672114392dd879586d60dd97896325df47d9a130ac7401318005aab28ec/psutil-7.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2ceae842a78d1603753561132d5ad1b2f8a7979cb0c283f5b52fb4e6e14b1a79", size = 140400, upload-time = "2025-12-29T08:26:23.993Z" }, - { url = "https://files.pythonhosted.org/packages/fb/7b/d0e9d4513c46e46897b46bcfc410d51fc65735837ea57a25170f298326e6/psutil-7.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:08a2f175e48a898c8eb8eace45ce01777f4785bc744c90aa2cc7f2fa5462a266", size = 135430, upload-time = "2025-12-29T08:26:25.999Z" }, - { url = "https://files.pythonhosted.org/packages/c5/cf/5180eb8c8bdf6a503c6919f1da28328bd1e6b3b1b5b9d5b01ae64f019616/psutil-7.2.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b2e953fcfaedcfbc952b44744f22d16575d3aa78eb4f51ae74165b4e96e55f42", size = 128137, upload-time = "2025-12-29T08:26:27.759Z" }, - { url = "https://files.pythonhosted.org/packages/c5/2c/78e4a789306a92ade5000da4f5de3255202c534acdadc3aac7b5458fadef/psutil-7.2.1-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:05cc68dbb8c174828624062e73078e7e35406f4ca2d0866c272c2410d8ef06d1", size = 128947, upload-time = "2025-12-29T08:26:29.548Z" }, - { url = "https://files.pythonhosted.org/packages/29/f8/40e01c350ad9a2b3cb4e6adbcc8a83b17ee50dd5792102b6142385937db5/psutil-7.2.1-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e38404ca2bb30ed7267a46c02f06ff842e92da3bb8c5bfdadbd35a5722314d8", size = 154694, upload-time = "2025-12-29T08:26:32.147Z" }, - { url = "https://files.pythonhosted.org/packages/06/e4/b751cdf839c011a9714a783f120e6a86b7494eb70044d7d81a25a5cd295f/psutil-7.2.1-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab2b98c9fc19f13f59628d94df5cc4cc4844bc572467d113a8b517d634e362c6", size = 156136, upload-time = "2025-12-29T08:26:34.079Z" }, - { url = "https://files.pythonhosted.org/packages/44/ad/bbf6595a8134ee1e94a4487af3f132cef7fce43aef4a93b49912a48c3af7/psutil-7.2.1-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f78baafb38436d5a128f837fab2d92c276dfb48af01a240b861ae02b2413ada8", size = 148108, upload-time = "2025-12-29T08:26:36.225Z" }, - { url = "https://files.pythonhosted.org/packages/1c/15/dd6fd869753ce82ff64dcbc18356093471a5a5adf4f77ed1f805d473d859/psutil-7.2.1-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:99a4cd17a5fdd1f3d014396502daa70b5ec21bf4ffe38393e152f8e449757d67", size = 147402, upload-time = "2025-12-29T08:26:39.21Z" }, - { url = "https://files.pythonhosted.org/packages/34/68/d9317542e3f2b180c4306e3f45d3c922d7e86d8ce39f941bb9e2e9d8599e/psutil-7.2.1-cp37-abi3-win_amd64.whl", hash = "sha256:b1b0671619343aa71c20ff9767eced0483e4fc9e1f489d50923738caf6a03c17", size = 136938, upload-time = "2025-12-29T08:26:41.036Z" }, - { url = "https://files.pythonhosted.org/packages/3e/73/2ce007f4198c80fcf2cb24c169884f833fe93fbc03d55d302627b094ee91/psutil-7.2.1-cp37-abi3-win_arm64.whl", hash = "sha256:0d67c1822c355aa6f7314d92018fb4268a76668a536f133599b91edd48759442", size = 133836, upload-time = "2025-12-29T08:26:43.086Z" }, +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, ] [[package]] @@ -2422,11 +2451,11 @@ wheels = [ [[package]] name = "pycparser" -version = "2.23" +version = "3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] [[package]] @@ -2440,11 +2469,11 @@ wheels = [ [[package]] name = "pyparsing" -version = "3.3.1" +version = "3.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/33/c1/1d9de9aeaa1b89b0186e5fe23294ff6517fce1bc69149185577cd31016b2/pyparsing-3.3.1.tar.gz", hash = "sha256:47fad0f17ac1e2cad3de3b458570fbc9b03560aa029ed5e16ee5554da9a2251c", size = 1550512, upload-time = "2025-12-23T03:14:04.391Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/40/2614036cdd416452f5bf98ec037f38a1afb17f327cb8e6b652d4729e0af8/pyparsing-3.3.1-py3-none-any.whl", hash = "sha256:023b5e7e5520ad96642e2c6db4cb683d3970bd640cdf7115049a6e9c3682df82", size = 121793, upload-time = "2025-12-23T03:14:02.103Z" }, + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, ] [[package]] @@ -2963,28 +2992,28 @@ wheels = [ [[package]] name = "ruff" -version = "0.14.13" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/50/0a/1914efb7903174b381ee2ffeebb4253e729de57f114e63595114c8ca451f/ruff-0.14.13.tar.gz", hash = "sha256:83cd6c0763190784b99650a20fec7633c59f6ebe41c5cc9d45ee42749563ad47", size = 6059504, upload-time = "2026-01-15T20:15:16.918Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/ae/0deefbc65ca74b0ab1fd3917f94dc3b398233346a74b8bbb0a916a1a6bf6/ruff-0.14.13-py3-none-linux_armv6l.whl", hash = "sha256:76f62c62cd37c276cb03a275b198c7c15bd1d60c989f944db08a8c1c2dbec18b", size = 13062418, upload-time = "2026-01-15T20:14:50.779Z" }, - { url = "https://files.pythonhosted.org/packages/47/df/5916604faa530a97a3c154c62a81cb6b735c0cb05d1e26d5ad0f0c8ac48a/ruff-0.14.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:914a8023ece0528d5cc33f5a684f5f38199bbb566a04815c2c211d8f40b5d0ed", size = 13442344, upload-time = "2026-01-15T20:15:07.94Z" }, - { url = "https://files.pythonhosted.org/packages/4c/f3/e0e694dd69163c3a1671e102aa574a50357536f18a33375050334d5cd517/ruff-0.14.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d24899478c35ebfa730597a4a775d430ad0d5631b8647a3ab368c29b7e7bd063", size = 12354720, upload-time = "2026-01-15T20:15:09.854Z" }, - { url = "https://files.pythonhosted.org/packages/c3/e8/67f5fcbbaee25e8fc3b56cc33e9892eca7ffe09f773c8e5907757a7e3bdb/ruff-0.14.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9aaf3870f14d925bbaf18b8a2347ee0ae7d95a2e490e4d4aea6813ed15ebc80e", size = 12774493, upload-time = "2026-01-15T20:15:20.908Z" }, - { url = "https://files.pythonhosted.org/packages/6b/ce/d2e9cb510870b52a9565d885c0d7668cc050e30fa2c8ac3fb1fda15c083d/ruff-0.14.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac5b7f63dd3b27cc811850f5ffd8fff845b00ad70e60b043aabf8d6ecc304e09", size = 12815174, upload-time = "2026-01-15T20:15:05.74Z" }, - { url = "https://files.pythonhosted.org/packages/88/00/c38e5da58beebcf4fa32d0ddd993b63dfacefd02ab7922614231330845bf/ruff-0.14.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78d2b1097750d90ba82ce4ba676e85230a0ed694178ca5e61aa9b459970b3eb9", size = 13680909, upload-time = "2026-01-15T20:15:14.537Z" }, - { url = "https://files.pythonhosted.org/packages/61/61/cd37c9dd5bd0a3099ba79b2a5899ad417d8f3b04038810b0501a80814fd7/ruff-0.14.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7d0bf87705acbbcb8d4c24b2d77fbb73d40210a95c3903b443cd9e30824a5032", size = 15144215, upload-time = "2026-01-15T20:15:22.886Z" }, - { url = "https://files.pythonhosted.org/packages/56/8a/85502d7edbf98c2df7b8876f316c0157359165e16cdf98507c65c8d07d3d/ruff-0.14.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3eb5da8e2c9e9f13431032fdcbe7681de9ceda5835efee3269417c13f1fed5c", size = 14706067, upload-time = "2026-01-15T20:14:48.271Z" }, - { url = "https://files.pythonhosted.org/packages/7e/2f/de0df127feb2ee8c1e54354dc1179b4a23798f0866019528c938ba439aca/ruff-0.14.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:642442b42957093811cd8d2140dfadd19c7417030a7a68cf8d51fcdd5f217427", size = 14133916, upload-time = "2026-01-15T20:14:57.357Z" }, - { url = "https://files.pythonhosted.org/packages/0d/77/9b99686bb9fe07a757c82f6f95e555c7a47801a9305576a9c67e0a31d280/ruff-0.14.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4acdf009f32b46f6e8864af19cbf6841eaaed8638e65c8dac845aea0d703c841", size = 13859207, upload-time = "2026-01-15T20:14:55.111Z" }, - { url = "https://files.pythonhosted.org/packages/7d/46/2bdcb34a87a179a4d23022d818c1c236cb40e477faf0d7c9afb6813e5876/ruff-0.14.13-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:591a7f68860ea4e003917d19b5c4f5ac39ff558f162dc753a2c5de897fd5502c", size = 14043686, upload-time = "2026-01-15T20:14:52.841Z" }, - { url = "https://files.pythonhosted.org/packages/1a/a9/5c6a4f56a0512c691cf143371bcf60505ed0f0860f24a85da8bd123b2bf1/ruff-0.14.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:774c77e841cc6e046fc3e91623ce0903d1cd07e3a36b1a9fe79b81dab3de506b", size = 12663837, upload-time = "2026-01-15T20:15:18.921Z" }, - { url = "https://files.pythonhosted.org/packages/fe/bb/b920016ece7651fa7fcd335d9d199306665486694d4361547ccb19394c44/ruff-0.14.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:61f4e40077a1248436772bb6512db5fc4457fe4c49e7a94ea7c5088655dd21ae", size = 12805867, upload-time = "2026-01-15T20:14:59.272Z" }, - { url = "https://files.pythonhosted.org/packages/7d/b3/0bd909851e5696cd21e32a8fc25727e5f58f1934b3596975503e6e85415c/ruff-0.14.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6d02f1428357fae9e98ac7aa94b7e966fd24151088510d32cf6f902d6c09235e", size = 13208528, upload-time = "2026-01-15T20:15:03.732Z" }, - { url = "https://files.pythonhosted.org/packages/3b/3b/e2d94cb613f6bbd5155a75cbe072813756363eba46a3f2177a1fcd0cd670/ruff-0.14.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e399341472ce15237be0c0ae5fbceca4b04cd9bebab1a2b2c979e015455d8f0c", size = 13929242, upload-time = "2026-01-15T20:15:11.918Z" }, - { url = "https://files.pythonhosted.org/packages/6a/c5/abd840d4132fd51a12f594934af5eba1d5d27298a6f5b5d6c3be45301caf/ruff-0.14.13-py3-none-win32.whl", hash = "sha256:ef720f529aec113968b45dfdb838ac8934e519711da53a0456038a0efecbd680", size = 12919024, upload-time = "2026-01-15T20:14:43.647Z" }, - { url = "https://files.pythonhosted.org/packages/c2/55/6384b0b8ce731b6e2ade2b5449bf07c0e4c31e8a2e68ea65b3bafadcecc5/ruff-0.14.13-py3-none-win_amd64.whl", hash = "sha256:6070bd026e409734b9257e03e3ef18c6e1a216f0435c6751d7a8ec69cb59abef", size = 14097887, upload-time = "2026-01-15T20:15:01.48Z" }, - { url = "https://files.pythonhosted.org/packages/4d/e1/7348090988095e4e39560cfc2f7555b1b2a7357deba19167b600fdf5215d/ruff-0.14.13-py3-none-win_arm64.whl", hash = "sha256:7ab819e14f1ad9fe39f246cfcc435880ef7a9390d81a2b6ac7e01039083dd247", size = 13080224, upload-time = "2026-01-15T20:14:45.853Z" }, +version = "0.14.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/06/f71e3a86b2df0dfa2d2f72195941cd09b44f87711cb7fa5193732cb9a5fc/ruff-0.14.14.tar.gz", hash = "sha256:2d0f819c9a90205f3a867dbbd0be083bee9912e170fd7d9704cc8ae45824896b", size = 4515732, upload-time = "2026-01-22T22:30:17.527Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/89/20a12e97bc6b9f9f68343952da08a8099c57237aef953a56b82711d55edd/ruff-0.14.14-py3-none-linux_armv6l.whl", hash = "sha256:7cfe36b56e8489dee8fbc777c61959f60ec0f1f11817e8f2415f429552846aed", size = 10467650, upload-time = "2026-01-22T22:30:08.578Z" }, + { url = "https://files.pythonhosted.org/packages/a3/b1/c5de3fd2d5a831fcae21beda5e3589c0ba67eec8202e992388e4b17a6040/ruff-0.14.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6006a0082336e7920b9573ef8a7f52eec837add1265cc74e04ea8a4368cd704c", size = 10883245, upload-time = "2026-01-22T22:30:04.155Z" }, + { url = "https://files.pythonhosted.org/packages/b8/7c/3c1db59a10e7490f8f6f8559d1db8636cbb13dccebf18686f4e3c9d7c772/ruff-0.14.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:026c1d25996818f0bf498636686199d9bd0d9d6341c9c2c3b62e2a0198b758de", size = 10231273, upload-time = "2026-01-22T22:30:34.642Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6e/5e0e0d9674be0f8581d1f5e0f0a04761203affce3232c1a1189d0e3b4dad/ruff-0.14.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f666445819d31210b71e0a6d1c01e24447a20b85458eea25a25fe8142210ae0e", size = 10585753, upload-time = "2026-01-22T22:30:31.781Z" }, + { url = "https://files.pythonhosted.org/packages/23/09/754ab09f46ff1884d422dc26d59ba18b4e5d355be147721bb2518aa2a014/ruff-0.14.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c0f18b922c6d2ff9a5e6c3ee16259adc513ca775bcf82c67ebab7cbd9da5bc8", size = 10286052, upload-time = "2026-01-22T22:30:24.827Z" }, + { url = "https://files.pythonhosted.org/packages/c8/cc/e71f88dd2a12afb5f50733851729d6b571a7c3a35bfdb16c3035132675a0/ruff-0.14.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1629e67489c2dea43e8658c3dba659edbfd87361624b4040d1df04c9740ae906", size = 11043637, upload-time = "2026-01-22T22:30:13.239Z" }, + { url = "https://files.pythonhosted.org/packages/67/b2/397245026352494497dac935d7f00f1468c03a23a0c5db6ad8fc49ca3fb2/ruff-0.14.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:27493a2131ea0f899057d49d303e4292b2cae2bb57253c1ed1f256fbcd1da480", size = 12194761, upload-time = "2026-01-22T22:30:22.542Z" }, + { url = "https://files.pythonhosted.org/packages/5b/06/06ef271459f778323112c51b7587ce85230785cd64e91772034ddb88f200/ruff-0.14.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01ff589aab3f5b539e35db38425da31a57521efd1e4ad1ae08fc34dbe30bd7df", size = 12005701, upload-time = "2026-01-22T22:30:20.499Z" }, + { url = "https://files.pythonhosted.org/packages/41/d6/99364514541cf811ccc5ac44362f88df66373e9fec1b9d1c4cc830593fe7/ruff-0.14.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc12d74eef0f29f51775f5b755913eb523546b88e2d733e1d701fe65144e89b", size = 11282455, upload-time = "2026-01-22T22:29:59.679Z" }, + { url = "https://files.pythonhosted.org/packages/ca/71/37daa46f89475f8582b7762ecd2722492df26421714a33e72ccc9a84d7a5/ruff-0.14.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb8481604b7a9e75eff53772496201690ce2687067e038b3cc31aaf16aa0b974", size = 11215882, upload-time = "2026-01-22T22:29:57.032Z" }, + { url = "https://files.pythonhosted.org/packages/2c/10/a31f86169ec91c0705e618443ee74ede0bdd94da0a57b28e72db68b2dbac/ruff-0.14.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:14649acb1cf7b5d2d283ebd2f58d56b75836ed8c6f329664fa91cdea19e76e66", size = 11180549, upload-time = "2026-01-22T22:30:27.175Z" }, + { url = "https://files.pythonhosted.org/packages/fd/1e/c723f20536b5163adf79bdd10c5f093414293cdf567eed9bdb7b83940f3f/ruff-0.14.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8058d2145566510790eab4e2fad186002e288dec5e0d343a92fe7b0bc1b3e13", size = 10543416, upload-time = "2026-01-22T22:30:01.964Z" }, + { url = "https://files.pythonhosted.org/packages/3e/34/8a84cea7e42c2d94ba5bde1d7a4fae164d6318f13f933d92da6d7c2041ff/ruff-0.14.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e651e977a79e4c758eb807f0481d673a67ffe53cfa92209781dfa3a996cf8412", size = 10285491, upload-time = "2026-01-22T22:30:29.51Z" }, + { url = "https://files.pythonhosted.org/packages/55/ef/b7c5ea0be82518906c978e365e56a77f8de7678c8bb6651ccfbdc178c29f/ruff-0.14.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cc8b22da8d9d6fdd844a68ae937e2a0adf9b16514e9a97cc60355e2d4b219fc3", size = 10733525, upload-time = "2026-01-22T22:30:06.499Z" }, + { url = "https://files.pythonhosted.org/packages/6a/5b/aaf1dfbcc53a2811f6cc0a1759de24e4b03e02ba8762daabd9b6bd8c59e3/ruff-0.14.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:16bc890fb4cc9781bb05beb5ab4cd51be9e7cb376bf1dd3580512b24eb3fda2b", size = 11315626, upload-time = "2026-01-22T22:30:36.848Z" }, + { url = "https://files.pythonhosted.org/packages/2c/aa/9f89c719c467dfaf8ad799b9bae0df494513fb21d31a6059cb5870e57e74/ruff-0.14.14-py3-none-win32.whl", hash = "sha256:b530c191970b143375b6a68e6f743800b2b786bbcf03a7965b06c4bf04568167", size = 10502442, upload-time = "2026-01-22T22:30:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/87/44/90fa543014c45560cae1fffc63ea059fb3575ee6e1cb654562197e5d16fb/ruff-0.14.14-py3-none-win_amd64.whl", hash = "sha256:3dde1435e6b6fe5b66506c1dff67a421d0b7f6488d466f651c07f4cab3bf20fd", size = 11630486, upload-time = "2026-01-22T22:30:10.852Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6a/40fee331a52339926a92e17ae748827270b288a35ef4a15c9c8f2ec54715/ruff-0.14.14-py3-none-win_arm64.whl", hash = "sha256:56e6981a98b13a32236a72a8da421d7839221fa308b223b9283312312e5ac76c", size = 10920448, upload-time = "2026-01-22T22:30:15.417Z" }, ] [[package]] @@ -3135,11 +3164,11 @@ wheels = [ [[package]] name = "setuptools" -version = "80.9.0" +version = "80.10.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +sdist = { url = "https://files.pythonhosted.org/packages/76/95/faf61eb8363f26aa7e1d762267a8d602a1b26d4f3a1e758e92cb3cb8b054/setuptools-80.10.2.tar.gz", hash = "sha256:8b0e9d10c784bf7d262c4e5ec5d4ec94127ce206e8738f29a437945fbc219b70", size = 1200343, upload-time = "2026-01-25T22:38:17.252Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, + { url = "https://files.pythonhosted.org/packages/94/b8/f1f62a5e3c0ad2ff1d189590bfa4c46b4f3b6e49cef6f26c6ee4e575394d/setuptools-80.10.2-py3-none-any.whl", hash = "sha256:95b30ddfb717250edb492926c92b5221f7ef3fbcc2b07579bcd4a27da21d0173", size = 1064234, upload-time = "2026-01-25T22:38:15.216Z" }, ] [[package]] @@ -3153,53 +3182,60 @@ wheels = [ [[package]] name = "soupsieve" -version = "2.8.2" +version = "2.8.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/93/f2/21d6ca70c3cf35d01ae9e01be534bf6b6b103c157a728082a5028350c310/soupsieve-2.8.2.tar.gz", hash = "sha256:78a66b0fdee2ab40b7199dc3e747ee6c6e231899feeaae0b9b98a353afd48fd8", size = 118601, upload-time = "2026-01-18T16:21:31.09Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/9a/b4450ccce353e2430621b3bb571899ffe1033d5cd72c9e065110f95b1a63/soupsieve-2.8.2-py3-none-any.whl", hash = "sha256:0f4c2f6b5a5fb97a641cf69c0bd163670a0e45e6d6c01a2107f93a6a6f93c51a", size = 37016, upload-time = "2026-01-18T16:21:29.7Z" }, + { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, ] [[package]] name = "sqlalchemy" -version = "2.0.45" +version = "2.0.46" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/be/f9/5e4491e5ccf42f5d9cfc663741d261b3e6e1683ae7812114e7636409fcc6/sqlalchemy-2.0.45.tar.gz", hash = "sha256:1632a4bda8d2d25703fdad6363058d882541bdaaee0e5e3ddfa0cd3229efce88", size = 9869912, upload-time = "2025-12-09T21:05:16.737Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/1c/769552a9d840065137272ebe86ffbb0bc92b0f1e0a68ee5266a225f8cd7b/sqlalchemy-2.0.45-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e90a344c644a4fa871eb01809c32096487928bd2038bf10f3e4515cb688cc56", size = 2153860, upload-time = "2025-12-10T20:03:23.843Z" }, - { url = "https://files.pythonhosted.org/packages/f3/f8/9be54ff620e5b796ca7b44670ef58bc678095d51b0e89d6e3102ea468216/sqlalchemy-2.0.45-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8c8b41b97fba5f62349aa285654230296829672fc9939cd7f35aab246d1c08b", size = 3309379, upload-time = "2025-12-09T22:06:07.461Z" }, - { url = "https://files.pythonhosted.org/packages/f6/2b/60ce3ee7a5ae172bfcd419ce23259bb874d2cddd44f67c5df3760a1e22f9/sqlalchemy-2.0.45-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:12c694ed6468333a090d2f60950e4250b928f457e4962389553d6ba5fe9951ac", size = 3309948, upload-time = "2025-12-09T22:09:57.643Z" }, - { url = "https://files.pythonhosted.org/packages/a3/42/bac8d393f5db550e4e466d03d16daaafd2bad1f74e48c12673fb499a7fc1/sqlalchemy-2.0.45-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f7d27a1d977a1cfef38a0e2e1ca86f09c4212666ce34e6ae542f3ed0a33bc606", size = 3261239, upload-time = "2025-12-09T22:06:08.879Z" }, - { url = "https://files.pythonhosted.org/packages/6f/12/43dc70a0528c59842b04ea1c1ed176f072a9b383190eb015384dd102fb19/sqlalchemy-2.0.45-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d62e47f5d8a50099b17e2bfc1b0c7d7ecd8ba6b46b1507b58cc4f05eefc3bb1c", size = 3284065, upload-time = "2025-12-09T22:09:59.454Z" }, - { url = "https://files.pythonhosted.org/packages/cf/9c/563049cf761d9a2ec7bc489f7879e9d94e7b590496bea5bbee9ed7b4cc32/sqlalchemy-2.0.45-cp311-cp311-win32.whl", hash = "sha256:3c5f76216e7b85770d5bb5130ddd11ee89f4d52b11783674a662c7dd57018177", size = 2113480, upload-time = "2025-12-09T21:29:57.03Z" }, - { url = "https://files.pythonhosted.org/packages/bc/fa/09d0a11fe9f15c7fa5c7f0dd26be3d235b0c0cbf2f9544f43bc42efc8a24/sqlalchemy-2.0.45-cp311-cp311-win_amd64.whl", hash = "sha256:a15b98adb7f277316f2c276c090259129ee4afca783495e212048daf846654b2", size = 2138407, upload-time = "2025-12-09T21:29:58.556Z" }, - { url = "https://files.pythonhosted.org/packages/2d/c7/1900b56ce19bff1c26f39a4ce427faec7716c81ac792bfac8b6a9f3dca93/sqlalchemy-2.0.45-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3ee2aac15169fb0d45822983631466d60b762085bc4535cd39e66bea362df5f", size = 3333760, upload-time = "2025-12-09T22:11:02.66Z" }, - { url = "https://files.pythonhosted.org/packages/0a/93/3be94d96bb442d0d9a60e55a6bb6e0958dd3457751c6f8502e56ef95fed0/sqlalchemy-2.0.45-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba547ac0b361ab4f1608afbc8432db669bd0819b3e12e29fb5fa9529a8bba81d", size = 3348268, upload-time = "2025-12-09T22:13:49.054Z" }, - { url = "https://files.pythonhosted.org/packages/48/4b/f88ded696e61513595e4a9778f9d3f2bf7332cce4eb0c7cedaabddd6687b/sqlalchemy-2.0.45-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:215f0528b914e5c75ef2559f69dca86878a3beeb0c1be7279d77f18e8d180ed4", size = 3278144, upload-time = "2025-12-09T22:11:04.14Z" }, - { url = "https://files.pythonhosted.org/packages/ed/6a/310ecb5657221f3e1bd5288ed83aa554923fb5da48d760a9f7622afeb065/sqlalchemy-2.0.45-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:107029bf4f43d076d4011f1afb74f7c3e2ea029ec82eb23d8527d5e909e97aa6", size = 3313907, upload-time = "2025-12-09T22:13:50.598Z" }, - { url = "https://files.pythonhosted.org/packages/5c/39/69c0b4051079addd57c84a5bfb34920d87456dd4c90cf7ee0df6efafc8ff/sqlalchemy-2.0.45-cp312-cp312-win32.whl", hash = "sha256:0c9f6ada57b58420a2c0277ff853abe40b9e9449f8d7d231763c6bc30f5c4953", size = 2112182, upload-time = "2025-12-09T21:39:30.824Z" }, - { url = "https://files.pythonhosted.org/packages/f7/4e/510db49dd89fc3a6e994bee51848c94c48c4a00dc905e8d0133c251f41a7/sqlalchemy-2.0.45-cp312-cp312-win_amd64.whl", hash = "sha256:8defe5737c6d2179c7997242d6473587c3beb52e557f5ef0187277009f73e5e1", size = 2139200, upload-time = "2025-12-09T21:39:32.321Z" }, - { url = "https://files.pythonhosted.org/packages/6a/c8/7cc5221b47a54edc72a0140a1efa56e0a2730eefa4058d7ed0b4c4357ff8/sqlalchemy-2.0.45-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe187fc31a54d7fd90352f34e8c008cf3ad5d064d08fedd3de2e8df83eb4a1cf", size = 3277082, upload-time = "2025-12-09T22:11:06.167Z" }, - { url = "https://files.pythonhosted.org/packages/0e/50/80a8d080ac7d3d321e5e5d420c9a522b0aa770ec7013ea91f9a8b7d36e4a/sqlalchemy-2.0.45-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:672c45cae53ba88e0dad74b9027dddd09ef6f441e927786b05bec75d949fbb2e", size = 3293131, upload-time = "2025-12-09T22:13:52.626Z" }, - { url = "https://files.pythonhosted.org/packages/da/4c/13dab31266fc9904f7609a5dc308a2432a066141d65b857760c3bef97e69/sqlalchemy-2.0.45-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:470daea2c1ce73910f08caf10575676a37159a6d16c4da33d0033546bddebc9b", size = 3225389, upload-time = "2025-12-09T22:11:08.093Z" }, - { url = "https://files.pythonhosted.org/packages/74/04/891b5c2e9f83589de202e7abaf24cd4e4fa59e1837d64d528829ad6cc107/sqlalchemy-2.0.45-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9c6378449e0940476577047150fd09e242529b761dc887c9808a9a937fe990c8", size = 3266054, upload-time = "2025-12-09T22:13:54.262Z" }, - { url = "https://files.pythonhosted.org/packages/f1/24/fc59e7f71b0948cdd4cff7a286210e86b0443ef1d18a23b0d83b87e4b1f7/sqlalchemy-2.0.45-cp313-cp313-win32.whl", hash = "sha256:4b6bec67ca45bc166c8729910bd2a87f1c0407ee955df110d78948f5b5827e8a", size = 2110299, upload-time = "2025-12-09T21:39:33.486Z" }, - { url = "https://files.pythonhosted.org/packages/c0/c5/d17113020b2d43073412aeca09b60d2009442420372123b8d49cc253f8b8/sqlalchemy-2.0.45-cp313-cp313-win_amd64.whl", hash = "sha256:afbf47dc4de31fa38fd491f3705cac5307d21d4bb828a4f020ee59af412744ee", size = 2136264, upload-time = "2025-12-09T21:39:36.801Z" }, - { url = "https://files.pythonhosted.org/packages/3d/8d/bb40a5d10e7a5f2195f235c0b2f2c79b0bf6e8f00c0c223130a4fbd2db09/sqlalchemy-2.0.45-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:83d7009f40ce619d483d26ac1b757dfe3167b39921379a8bd1b596cf02dab4a6", size = 3521998, upload-time = "2025-12-09T22:13:28.622Z" }, - { url = "https://files.pythonhosted.org/packages/75/a5/346128b0464886f036c039ea287b7332a410aa2d3fb0bb5d404cb8861635/sqlalchemy-2.0.45-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d8a2ca754e5415cde2b656c27900b19d50ba076aa05ce66e2207623d3fe41f5a", size = 3473434, upload-time = "2025-12-09T22:13:30.188Z" }, - { url = "https://files.pythonhosted.org/packages/cc/64/4e1913772646b060b025d3fc52ce91a58967fe58957df32b455de5a12b4f/sqlalchemy-2.0.45-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f46ec744e7f51275582e6a24326e10c49fbdd3fc99103e01376841213028774", size = 3272404, upload-time = "2025-12-09T22:11:09.662Z" }, - { url = "https://files.pythonhosted.org/packages/b3/27/caf606ee924282fe4747ee4fd454b335a72a6e018f97eab5ff7f28199e16/sqlalchemy-2.0.45-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:883c600c345123c033c2f6caca18def08f1f7f4c3ebeb591a63b6fceffc95cce", size = 3277057, upload-time = "2025-12-09T22:13:56.213Z" }, - { url = "https://files.pythonhosted.org/packages/85/d0/3d64218c9724e91f3d1574d12eb7ff8f19f937643815d8daf792046d88ab/sqlalchemy-2.0.45-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2c0b74aa79e2deade948fe8593654c8ef4228c44ba862bb7c9585c8e0db90f33", size = 3222279, upload-time = "2025-12-09T22:11:11.1Z" }, - { url = "https://files.pythonhosted.org/packages/24/10/dd7688a81c5bc7690c2a3764d55a238c524cd1a5a19487928844cb247695/sqlalchemy-2.0.45-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8a420169cef179d4c9064365f42d779f1e5895ad26ca0c8b4c0233920973db74", size = 3244508, upload-time = "2025-12-09T22:13:57.932Z" }, - { url = "https://files.pythonhosted.org/packages/aa/41/db75756ca49f777e029968d9c9fee338c7907c563267740c6d310a8e3f60/sqlalchemy-2.0.45-cp314-cp314-win32.whl", hash = "sha256:e50dcb81a5dfe4b7b4a4aa8f338116d127cb209559124f3694c70d6cd072b68f", size = 2113204, upload-time = "2025-12-09T21:39:38.365Z" }, - { url = "https://files.pythonhosted.org/packages/89/a2/0e1590e9adb292b1d576dbcf67ff7df8cf55e56e78d2c927686d01080f4b/sqlalchemy-2.0.45-cp314-cp314-win_amd64.whl", hash = "sha256:4748601c8ea959e37e03d13dcda4a44837afcd1b21338e637f7c935b8da06177", size = 2138785, upload-time = "2025-12-09T21:39:39.503Z" }, - { url = "https://files.pythonhosted.org/packages/42/39/f05f0ed54d451156bbed0e23eb0516bcad7cbb9f18b3bf219c786371b3f0/sqlalchemy-2.0.45-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd337d3526ec5298f67d6a30bbbe4ed7e5e68862f0bf6dd21d289f8d37b7d60b", size = 3522029, upload-time = "2025-12-09T22:13:32.09Z" }, - { url = "https://files.pythonhosted.org/packages/54/0f/d15398b98b65c2bce288d5ee3f7d0a81f77ab89d9456994d5c7cc8b2a9db/sqlalchemy-2.0.45-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9a62b446b7d86a3909abbcd1cd3cc550a832f99c2bc37c5b22e1925438b9367b", size = 3475142, upload-time = "2025-12-09T22:13:33.739Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e1/3ccb13c643399d22289c6a9786c1a91e3dcbb68bce4beb44926ac2c557bf/sqlalchemy-2.0.45-py3-none-any.whl", hash = "sha256:5225a288e4c8cc2308dbdd874edad6e7d0fd38eac1e9e5f23503425c8eee20d0", size = 1936672, upload-time = "2025-12-09T21:54:52.608Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/06/aa/9ce0f3e7a9829ead5c8ce549392f33a12c4555a6c0609bb27d882e9c7ddf/sqlalchemy-2.0.46.tar.gz", hash = "sha256:cf36851ee7219c170bb0793dbc3da3e80c582e04a5437bc601bfe8c85c9216d7", size = 9865393, upload-time = "2026-01-21T18:03:45.119Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/ac/b42ad16800d0885105b59380ad69aad0cce5a65276e269ce2729a2343b6a/sqlalchemy-2.0.46-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:261c4b1f101b4a411154f1da2b76497d73abbfc42740029205d4d01fa1052684", size = 2154851, upload-time = "2026-01-21T18:27:30.54Z" }, + { url = "https://files.pythonhosted.org/packages/a0/60/d8710068cb79f64d002ebed62a7263c00c8fd95f4ebd4b5be8f7ca93f2bc/sqlalchemy-2.0.46-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:181903fe8c1b9082995325f1b2e84ac078b1189e2819380c2303a5f90e114a62", size = 3311241, upload-time = "2026-01-21T18:32:33.45Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/20c71487c7219ab3aa7421c7c62d93824c97c1460f2e8bb72404b0192d13/sqlalchemy-2.0.46-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:590be24e20e2424a4c3c1b0835e9405fa3d0af5823a1a9fc02e5dff56471515f", size = 3310741, upload-time = "2026-01-21T18:44:57.887Z" }, + { url = "https://files.pythonhosted.org/packages/65/80/d26d00b3b249ae000eee4db206fcfc564bf6ca5030e4747adf451f4b5108/sqlalchemy-2.0.46-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7568fe771f974abadce52669ef3a03150ff03186d8eb82613bc8adc435a03f01", size = 3263116, upload-time = "2026-01-21T18:32:35.044Z" }, + { url = "https://files.pythonhosted.org/packages/da/ee/74dda7506640923821340541e8e45bd3edd8df78664f1f2e0aae8077192b/sqlalchemy-2.0.46-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf7e1e78af38047e08836d33502c7a278915698b7c2145d045f780201679999", size = 3285327, upload-time = "2026-01-21T18:44:59.254Z" }, + { url = "https://files.pythonhosted.org/packages/9f/25/6dcf8abafff1389a21c7185364de145107b7394ecdcb05233815b236330d/sqlalchemy-2.0.46-cp311-cp311-win32.whl", hash = "sha256:9d80ea2ac519c364a7286e8d765d6cd08648f5b21ca855a8017d9871f075542d", size = 2114564, upload-time = "2026-01-21T18:33:15.85Z" }, + { url = "https://files.pythonhosted.org/packages/93/5f/e081490f8523adc0088f777e4ebad3cac21e498ec8a3d4067074e21447a1/sqlalchemy-2.0.46-cp311-cp311-win_amd64.whl", hash = "sha256:585af6afe518732d9ccd3aea33af2edaae4a7aa881af5d8f6f4fe3a368699597", size = 2139233, upload-time = "2026-01-21T18:33:17.528Z" }, + { url = "https://files.pythonhosted.org/packages/b6/35/d16bfa235c8b7caba3730bba43e20b1e376d2224f407c178fbf59559f23e/sqlalchemy-2.0.46-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3a9a72b0da8387f15d5810f1facca8f879de9b85af8c645138cba61ea147968c", size = 2153405, upload-time = "2026-01-21T19:05:54.143Z" }, + { url = "https://files.pythonhosted.org/packages/06/6c/3192e24486749862f495ddc6584ed730c0c994a67550ec395d872a2ad650/sqlalchemy-2.0.46-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2347c3f0efc4de367ba00218e0ae5c4ba2306e47216ef80d6e31761ac97cb0b9", size = 3334702, upload-time = "2026-01-21T18:46:45.384Z" }, + { url = "https://files.pythonhosted.org/packages/ea/a2/b9f33c8d68a3747d972a0bb758c6b63691f8fb8a49014bc3379ba15d4274/sqlalchemy-2.0.46-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9094c8b3197db12aa6f05c51c05daaad0a92b8c9af5388569847b03b1007fb1b", size = 3347664, upload-time = "2026-01-21T18:40:09.979Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d2/3e59e2a91eaec9db7e8dc6b37b91489b5caeb054f670f32c95bcba98940f/sqlalchemy-2.0.46-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37fee2164cf21417478b6a906adc1a91d69ae9aba8f9533e67ce882f4bb1de53", size = 3277372, upload-time = "2026-01-21T18:46:47.168Z" }, + { url = "https://files.pythonhosted.org/packages/dd/dd/67bc2e368b524e2192c3927b423798deda72c003e73a1e94c21e74b20a85/sqlalchemy-2.0.46-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b1e14b2f6965a685c7128bd315e27387205429c2e339eeec55cb75ca4ab0ea2e", size = 3312425, upload-time = "2026-01-21T18:40:11.548Z" }, + { url = "https://files.pythonhosted.org/packages/43/82/0ecd68e172bfe62247e96cb47867c2d68752566811a4e8c9d8f6e7c38a65/sqlalchemy-2.0.46-cp312-cp312-win32.whl", hash = "sha256:412f26bb4ba942d52016edc8d12fb15d91d3cd46b0047ba46e424213ad407bcb", size = 2113155, upload-time = "2026-01-21T18:42:49.748Z" }, + { url = "https://files.pythonhosted.org/packages/bc/2a/2821a45742073fc0331dc132552b30de68ba9563230853437cac54b2b53e/sqlalchemy-2.0.46-cp312-cp312-win_amd64.whl", hash = "sha256:ea3cd46b6713a10216323cda3333514944e510aa691c945334713fca6b5279ff", size = 2140078, upload-time = "2026-01-21T18:42:51.197Z" }, + { url = "https://files.pythonhosted.org/packages/b3/4b/fa7838fe20bb752810feed60e45625a9a8b0102c0c09971e2d1d95362992/sqlalchemy-2.0.46-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:93a12da97cca70cea10d4b4fc602589c4511f96c1f8f6c11817620c021d21d00", size = 2150268, upload-time = "2026-01-21T19:05:56.621Z" }, + { url = "https://files.pythonhosted.org/packages/46/c1/b34dccd712e8ea846edf396e00973dda82d598cb93762e55e43e6835eba9/sqlalchemy-2.0.46-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af865c18752d416798dae13f83f38927c52f085c52e2f32b8ab0fef46fdd02c2", size = 3276511, upload-time = "2026-01-21T18:46:49.022Z" }, + { url = "https://files.pythonhosted.org/packages/96/48/a04d9c94753e5d5d096c628c82a98c4793b9c08ca0e7155c3eb7d7db9f24/sqlalchemy-2.0.46-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8d679b5f318423eacb61f933a9a0f75535bfca7056daeadbf6bd5bcee6183aee", size = 3292881, upload-time = "2026-01-21T18:40:13.089Z" }, + { url = "https://files.pythonhosted.org/packages/be/f4/06eda6e91476f90a7d8058f74311cb65a2fb68d988171aced81707189131/sqlalchemy-2.0.46-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64901e08c33462acc9ec3bad27fc7a5c2b6491665f2aa57564e57a4f5d7c52ad", size = 3224559, upload-time = "2026-01-21T18:46:50.974Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a2/d2af04095412ca6345ac22b33b89fe8d6f32a481e613ffcb2377d931d8d0/sqlalchemy-2.0.46-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e8ac45e8f4eaac0f9f8043ea0e224158855c6a4329fd4ee37c45c61e3beb518e", size = 3262728, upload-time = "2026-01-21T18:40:14.883Z" }, + { url = "https://files.pythonhosted.org/packages/31/48/1980c7caa5978a3b8225b4d230e69a2a6538a3562b8b31cea679b6933c83/sqlalchemy-2.0.46-cp313-cp313-win32.whl", hash = "sha256:8d3b44b3d0ab2f1319d71d9863d76eeb46766f8cf9e921ac293511804d39813f", size = 2111295, upload-time = "2026-01-21T18:42:52.366Z" }, + { url = "https://files.pythonhosted.org/packages/2d/54/f8d65bbde3d877617c4720f3c9f60e99bb7266df0d5d78b6e25e7c149f35/sqlalchemy-2.0.46-cp313-cp313-win_amd64.whl", hash = "sha256:77f8071d8fbcbb2dd11b7fd40dedd04e8ebe2eb80497916efedba844298065ef", size = 2137076, upload-time = "2026-01-21T18:42:53.924Z" }, + { url = "https://files.pythonhosted.org/packages/56/ba/9be4f97c7eb2b9d5544f2624adfc2853e796ed51d2bb8aec90bc94b7137e/sqlalchemy-2.0.46-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1e8cc6cc01da346dc92d9509a63033b9b1bda4fed7a7a7807ed385c7dccdc10", size = 3556533, upload-time = "2026-01-21T18:33:06.636Z" }, + { url = "https://files.pythonhosted.org/packages/20/a6/b1fc6634564dbb4415b7ed6419cdfeaadefd2c39cdab1e3aa07a5f2474c2/sqlalchemy-2.0.46-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:96c7cca1a4babaaf3bfff3e4e606e38578856917e52f0384635a95b226c87764", size = 3523208, upload-time = "2026-01-21T18:45:08.436Z" }, + { url = "https://files.pythonhosted.org/packages/a1/d8/41e0bdfc0f930ff236f86fccd12962d8fa03713f17ed57332d38af6a3782/sqlalchemy-2.0.46-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b2a9f9aee38039cf4755891a1e50e1effcc42ea6ba053743f452c372c3152b1b", size = 3464292, upload-time = "2026-01-21T18:33:08.208Z" }, + { url = "https://files.pythonhosted.org/packages/f0/8b/9dcbec62d95bea85f5ecad9b8d65b78cc30fb0ffceeb3597961f3712549b/sqlalchemy-2.0.46-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:db23b1bf8cfe1f7fda19018e7207b20cdb5168f83c437ff7e95d19e39289c447", size = 3473497, upload-time = "2026-01-21T18:45:10.552Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f8/5ecdfc73383ec496de038ed1614de9e740a82db9ad67e6e4514ebc0708a3/sqlalchemy-2.0.46-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:56bdd261bfd0895452006d5316cbf35739c53b9bb71a170a331fa0ea560b2ada", size = 2152079, upload-time = "2026-01-21T19:05:58.477Z" }, + { url = "https://files.pythonhosted.org/packages/e5/bf/eba3036be7663ce4d9c050bc3d63794dc29fbe01691f2bf5ccb64e048d20/sqlalchemy-2.0.46-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33e462154edb9493f6c3ad2125931e273bbd0be8ae53f3ecd1c161ea9a1dd366", size = 3272216, upload-time = "2026-01-21T18:46:52.634Z" }, + { url = "https://files.pythonhosted.org/packages/05/45/1256fb597bb83b58a01ddb600c59fe6fdf0e5afe333f0456ed75c0f8d7bd/sqlalchemy-2.0.46-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bcdce05f056622a632f1d44bb47dbdb677f58cad393612280406ce37530eb6d", size = 3277208, upload-time = "2026-01-21T18:40:16.38Z" }, + { url = "https://files.pythonhosted.org/packages/d9/a0/2053b39e4e63b5d7ceb3372cface0859a067c1ddbd575ea7e9985716f771/sqlalchemy-2.0.46-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e84b09a9b0f19accedcbeff5c2caf36e0dd537341a33aad8d680336152dc34e", size = 3221994, upload-time = "2026-01-21T18:46:54.622Z" }, + { url = "https://files.pythonhosted.org/packages/1e/87/97713497d9502553c68f105a1cb62786ba1ee91dea3852ae4067ed956a50/sqlalchemy-2.0.46-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4f52f7291a92381e9b4de9050b0a65ce5d6a763333406861e33906b8aa4906bf", size = 3243990, upload-time = "2026-01-21T18:40:18.253Z" }, + { url = "https://files.pythonhosted.org/packages/a8/87/5d1b23548f420ff823c236f8bea36b1a997250fd2f892e44a3838ca424f4/sqlalchemy-2.0.46-cp314-cp314-win32.whl", hash = "sha256:70ed2830b169a9960193f4d4322d22be5c0925357d82cbf485b3369893350908", size = 2114215, upload-time = "2026-01-21T18:42:55.232Z" }, + { url = "https://files.pythonhosted.org/packages/3a/20/555f39cbcf0c10cf452988b6a93c2a12495035f68b3dbd1a408531049d31/sqlalchemy-2.0.46-cp314-cp314-win_amd64.whl", hash = "sha256:3c32e993bc57be6d177f7d5d31edb93f30726d798ad86ff9066d75d9bf2e0b6b", size = 2139867, upload-time = "2026-01-21T18:42:56.474Z" }, + { url = "https://files.pythonhosted.org/packages/3e/f0/f96c8057c982d9d8a7a68f45d69c674bc6f78cad401099692fe16521640a/sqlalchemy-2.0.46-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4dafb537740eef640c4d6a7c254611dca2df87eaf6d14d6a5fca9d1f4c3fc0fa", size = 3561202, upload-time = "2026-01-21T18:33:10.337Z" }, + { url = "https://files.pythonhosted.org/packages/d7/53/3b37dda0a5b137f21ef608d8dfc77b08477bab0fe2ac9d3e0a66eaeab6fc/sqlalchemy-2.0.46-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42a1643dc5427b69aca967dae540a90b0fbf57eaf248f13a90ea5930e0966863", size = 3526296, upload-time = "2026-01-21T18:45:12.657Z" }, + { url = "https://files.pythonhosted.org/packages/33/75/f28622ba6dde79cd545055ea7bd4062dc934e0621f7b3be2891f8563f8de/sqlalchemy-2.0.46-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ff33c6e6ad006bbc0f34f5faf941cfc62c45841c64c0a058ac38c799f15b5ede", size = 3470008, upload-time = "2026-01-21T18:33:11.725Z" }, + { url = "https://files.pythonhosted.org/packages/a9/42/4afecbbc38d5e99b18acef446453c76eec6fbd03db0a457a12a056836e22/sqlalchemy-2.0.46-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:82ec52100ec1e6ec671563bbd02d7c7c8d0b9e71a0723c72f22ecf52d1755330", size = 3476137, upload-time = "2026-01-21T18:45:15.001Z" }, + { url = "https://files.pythonhosted.org/packages/fc/a1/9c4efa03300926601c19c18582531b45aededfb961ab3c3585f1e24f120b/sqlalchemy-2.0.46-py3-none-any.whl", hash = "sha256:f9c11766e7e7c0a2767dda5acb006a118640c9fc0a4104214b96269bfb78399e", size = 1937882, upload-time = "2026-01-21T18:22:10.456Z" }, ] [[package]] @@ -3274,14 +3310,14 @@ wheels = [ [[package]] name = "tifffile" -version = "2026.1.14" +version = "2026.1.28" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5d/19/a41ab0dc1b314da952d99957289944c3b8b76021399c72693e4c1fddc6c3/tifffile-2026.1.14.tar.gz", hash = "sha256:a423c583e1eecd9ca255642d47f463efa8d7f2365a0e110eb0167570493e0c8c", size = 373639, upload-time = "2026-01-14T22:40:43.551Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/32/38498d2a1a5d70f33f6c3909bbad48557c9a54b0e33a9307ff06b6d416ba/tifffile-2026.1.28.tar.gz", hash = "sha256:537ae6466a8bb555c336108bb1878d8319d52c9c738041d3349454dea6956e1c", size = 374675, upload-time = "2026-01-29T05:17:24.992Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/4d/3fd60d3a37b544cb59463add86e4dfbb485880225115341281906a7b140e/tifffile-2026.1.14-py3-none-any.whl", hash = "sha256:29cf4adb43562a4624fc959018ab1b44e0342015d3db4581b983fe40e05f5924", size = 232213, upload-time = "2026-01-14T22:40:41.553Z" }, + { url = "https://files.pythonhosted.org/packages/09/19/529b28ca338c5a88315e71e672badc85eef89460c248c4164f6ce058f8c7/tifffile-2026.1.28-py3-none-any.whl", hash = "sha256:45b08a19cf603dd99952eff54a61519626a1912e4e2a4d355f05938fe4a6e9fd", size = 233011, upload-time = "2026-01-29T05:17:23.078Z" }, ] [[package]] @@ -3361,9 +3397,10 @@ wheels = [ [[package]] name = "torch" -version = "2.9.1" +version = "2.10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "cuda-bindings", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "filelock" }, { name = "fsspec" }, { name = "jinja2" }, @@ -3389,30 +3426,30 @@ dependencies = [ { name = "typing-extensions" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/15/db/c064112ac0089af3d2f7a2b5bfbabf4aa407a78b74f87889e524b91c5402/torch-2.9.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:62b3fd888277946918cba4478cf849303da5359f0fb4e3bfb86b0533ba2eaf8d", size = 104220430, upload-time = "2025-11-12T15:20:31.705Z" }, - { url = "https://files.pythonhosted.org/packages/56/be/76eaa36c9cd032d3b01b001e2c5a05943df75f26211f68fae79e62f87734/torch-2.9.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d033ff0ac3f5400df862a51bdde9bad83561f3739ea0046e68f5401ebfa67c1b", size = 899821446, upload-time = "2025-11-12T15:20:15.544Z" }, - { url = "https://files.pythonhosted.org/packages/47/cc/7a2949e38dfe3244c4df21f0e1c27bce8aedd6c604a587dd44fc21017cb4/torch-2.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:0d06b30a9207b7c3516a9e0102114024755a07045f0c1d2f2a56b1819ac06bcb", size = 110973074, upload-time = "2025-11-12T15:21:39.958Z" }, - { url = "https://files.pythonhosted.org/packages/1e/ce/7d251155a783fb2c1bb6837b2b7023c622a2070a0a72726ca1df47e7ea34/torch-2.9.1-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:52347912d868653e1528b47cafaf79b285b98be3f4f35d5955389b1b95224475", size = 74463887, upload-time = "2025-11-12T15:20:36.611Z" }, - { url = "https://files.pythonhosted.org/packages/0f/27/07c645c7673e73e53ded71705045d6cb5bae94c4b021b03aa8d03eee90ab/torch-2.9.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:da5f6f4d7f4940a173e5572791af238cb0b9e21b1aab592bd8b26da4c99f1cd6", size = 104126592, upload-time = "2025-11-12T15:20:41.62Z" }, - { url = "https://files.pythonhosted.org/packages/19/17/e377a460603132b00760511299fceba4102bd95db1a0ee788da21298ccff/torch-2.9.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:27331cd902fb4322252657f3902adf1c4f6acad9dcad81d8df3ae14c7c4f07c4", size = 899742281, upload-time = "2025-11-12T15:22:17.602Z" }, - { url = "https://files.pythonhosted.org/packages/b1/1a/64f5769025db846a82567fa5b7d21dba4558a7234ee631712ee4771c436c/torch-2.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:81a285002d7b8cfd3fdf1b98aa8df138d41f1a8334fd9ea37511517cedf43083", size = 110940568, upload-time = "2025-11-12T15:21:18.689Z" }, - { url = "https://files.pythonhosted.org/packages/6e/ab/07739fd776618e5882661d04c43f5b5586323e2f6a2d7d84aac20d8f20bd/torch-2.9.1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:c0d25d1d8e531b8343bea0ed811d5d528958f1dcbd37e7245bc686273177ad7e", size = 74479191, upload-time = "2025-11-12T15:21:25.816Z" }, - { url = "https://files.pythonhosted.org/packages/20/60/8fc5e828d050bddfab469b3fe78e5ab9a7e53dda9c3bdc6a43d17ce99e63/torch-2.9.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c29455d2b910b98738131990394da3e50eea8291dfeb4b12de71ecf1fdeb21cb", size = 104135743, upload-time = "2025-11-12T15:21:34.936Z" }, - { url = "https://files.pythonhosted.org/packages/f2/b7/6d3f80e6918213babddb2a37b46dbb14c15b14c5f473e347869a51f40e1f/torch-2.9.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:524de44cd13931208ba2c4bde9ec7741fd4ae6bfd06409a604fc32f6520c2bc9", size = 899749493, upload-time = "2025-11-12T15:24:36.356Z" }, - { url = "https://files.pythonhosted.org/packages/a6/47/c7843d69d6de8938c1cbb1eba426b1d48ddf375f101473d3e31a5fc52b74/torch-2.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:545844cc16b3f91e08ce3b40e9c2d77012dd33a48d505aed34b7740ed627a1b2", size = 110944162, upload-time = "2025-11-12T15:21:53.151Z" }, - { url = "https://files.pythonhosted.org/packages/28/0e/2a37247957e72c12151b33a01e4df651d9d155dd74d8cfcbfad15a79b44a/torch-2.9.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5be4bf7496f1e3ffb1dd44b672adb1ac3f081f204c5ca81eba6442f5f634df8e", size = 74830751, upload-time = "2025-11-12T15:21:43.792Z" }, - { url = "https://files.pythonhosted.org/packages/4b/f7/7a18745edcd7b9ca2381aa03353647bca8aace91683c4975f19ac233809d/torch-2.9.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:30a3e170a84894f3652434b56d59a64a2c11366b0ed5776fab33c2439396bf9a", size = 104142929, upload-time = "2025-11-12T15:21:48.319Z" }, - { url = "https://files.pythonhosted.org/packages/f4/dd/f1c0d879f2863ef209e18823a988dc7a1bf40470750e3ebe927efdb9407f/torch-2.9.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:8301a7b431e51764629208d0edaa4f9e4c33e6df0f2f90b90e261d623df6a4e2", size = 899748978, upload-time = "2025-11-12T15:23:04.568Z" }, - { url = "https://files.pythonhosted.org/packages/1f/9f/6986b83a53b4d043e36f3f898b798ab51f7f20fdf1a9b01a2720f445043d/torch-2.9.1-cp313-cp313t-win_amd64.whl", hash = "sha256:2e1c42c0ae92bf803a4b2409fdfed85e30f9027a66887f5e7dcdbc014c7531db", size = 111176995, upload-time = "2025-11-12T15:22:01.618Z" }, - { url = "https://files.pythonhosted.org/packages/40/60/71c698b466dd01e65d0e9514b5405faae200c52a76901baf6906856f17e4/torch-2.9.1-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:2c14b3da5df416cf9cb5efab83aa3056f5b8cd8620b8fde81b4987ecab730587", size = 74480347, upload-time = "2025-11-12T15:21:57.648Z" }, - { url = "https://files.pythonhosted.org/packages/48/50/c4b5112546d0d13cc9eaa1c732b823d676a9f49ae8b6f97772f795874a03/torch-2.9.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1edee27a7c9897f4e0b7c14cfc2f3008c571921134522d5b9b5ec4ebbc69041a", size = 74433245, upload-time = "2025-11-12T15:22:39.027Z" }, - { url = "https://files.pythonhosted.org/packages/81/c9/2628f408f0518b3bae49c95f5af3728b6ab498c8624ab1e03a43dd53d650/torch-2.9.1-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:19d144d6b3e29921f1fc70503e9f2fc572cde6a5115c0c0de2f7ca8b1483e8b6", size = 104134804, upload-time = "2025-11-12T15:22:35.222Z" }, - { url = "https://files.pythonhosted.org/packages/28/fc/5bc91d6d831ae41bf6e9e6da6468f25330522e92347c9156eb3f1cb95956/torch-2.9.1-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:c432d04376f6d9767a9852ea0def7b47a7bbc8e7af3b16ac9cf9ce02b12851c9", size = 899747132, upload-time = "2025-11-12T15:23:36.068Z" }, - { url = "https://files.pythonhosted.org/packages/63/5d/e8d4e009e52b6b2cf1684bde2a6be157b96fb873732542fb2a9a99e85a83/torch-2.9.1-cp314-cp314-win_amd64.whl", hash = "sha256:d187566a2cdc726fc80138c3cdb260970fab1c27e99f85452721f7759bbd554d", size = 110934845, upload-time = "2025-11-12T15:22:48.367Z" }, - { url = "https://files.pythonhosted.org/packages/bd/b2/2d15a52516b2ea3f414643b8de68fa4cb220d3877ac8b1028c83dc8ca1c4/torch-2.9.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cb10896a1f7fedaddbccc2017ce6ca9ecaaf990f0973bdfcf405439750118d2c", size = 74823558, upload-time = "2025-11-12T15:22:43.392Z" }, - { url = "https://files.pythonhosted.org/packages/86/5c/5b2e5d84f5b9850cd1e71af07524d8cbb74cba19379800f1f9f7c997fc70/torch-2.9.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:0a2bd769944991c74acf0c4ef23603b9c777fdf7637f115605a4b2d8023110c7", size = 104145788, upload-time = "2025-11-12T15:23:52.109Z" }, - { url = "https://files.pythonhosted.org/packages/a9/8c/3da60787bcf70add986c4ad485993026ac0ca74f2fc21410bc4eb1bb7695/torch-2.9.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:07c8a9660bc9414c39cac530ac83b1fb1b679d7155824144a40a54f4a47bfa73", size = 899735500, upload-time = "2025-11-12T15:24:08.788Z" }, - { url = "https://files.pythonhosted.org/packages/db/2b/f7818f6ec88758dfd21da46b6cd46af9d1b3433e53ddbb19ad1e0da17f9b/torch-2.9.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c88d3299ddeb2b35dcc31753305612db485ab6f1823e37fb29451c8b2732b87e", size = 111163659, upload-time = "2025-11-12T15:23:20.009Z" }, + { url = "https://files.pythonhosted.org/packages/78/89/f5554b13ebd71e05c0b002f95148033e730d3f7067f67423026cc9c69410/torch-2.10.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:3282d9febd1e4e476630a099692b44fdc214ee9bf8ee5377732d9d9dfe5712e4", size = 145992610, upload-time = "2026-01-21T16:25:26.327Z" }, + { url = "https://files.pythonhosted.org/packages/ae/30/a3a2120621bf9c17779b169fc17e3dc29b230c29d0f8222f499f5e159aa8/torch-2.10.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a2f9edd8dbc99f62bc4dfb78af7bf89499bca3d753423ac1b4e06592e467b763", size = 915607863, upload-time = "2026-01-21T16:25:06.696Z" }, + { url = "https://files.pythonhosted.org/packages/6f/3d/c87b33c5f260a2a8ad68da7147e105f05868c281c63d65ed85aa4da98c66/torch-2.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:29b7009dba4b7a1c960260fc8ac85022c784250af43af9fb0ebafc9883782ebd", size = 113723116, upload-time = "2026-01-21T16:25:21.916Z" }, + { url = "https://files.pythonhosted.org/packages/61/d8/15b9d9d3a6b0c01b883787bd056acbe5cc321090d4b216d3ea89a8fcfdf3/torch-2.10.0-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:b7bd80f3477b830dd166c707c5b0b82a898e7b16f59a7d9d42778dd058272e8b", size = 79423461, upload-time = "2026-01-21T16:24:50.266Z" }, + { url = "https://files.pythonhosted.org/packages/cc/af/758e242e9102e9988969b5e621d41f36b8f258bb4a099109b7a4b4b50ea4/torch-2.10.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:5fd4117d89ffd47e3dcc71e71a22efac24828ad781c7e46aaaf56bf7f2796acf", size = 145996088, upload-time = "2026-01-21T16:24:44.171Z" }, + { url = "https://files.pythonhosted.org/packages/23/8e/3c74db5e53bff7ed9e34c8123e6a8bfef718b2450c35eefab85bb4a7e270/torch-2.10.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:787124e7db3b379d4f1ed54dd12ae7c741c16a4d29b49c0226a89bea50923ffb", size = 915711952, upload-time = "2026-01-21T16:23:53.503Z" }, + { url = "https://files.pythonhosted.org/packages/6e/01/624c4324ca01f66ae4c7cd1b74eb16fb52596dce66dbe51eff95ef9e7a4c/torch-2.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:2c66c61f44c5f903046cc696d088e21062644cbe541c7f1c4eaae88b2ad23547", size = 113757972, upload-time = "2026-01-21T16:24:39.516Z" }, + { url = "https://files.pythonhosted.org/packages/c9/5c/dee910b87c4d5c0fcb41b50839ae04df87c1cfc663cf1b5fca7ea565eeaa/torch-2.10.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:6d3707a61863d1c4d6ebba7be4ca320f42b869ee657e9b2c21c736bf17000294", size = 79498198, upload-time = "2026-01-21T16:24:34.704Z" }, + { url = "https://files.pythonhosted.org/packages/c9/6f/f2e91e34e3fcba2e3fc8d8f74e7d6c22e74e480bbd1db7bc8900fdf3e95c/torch-2.10.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:5c4d217b14741e40776dd7074d9006fd28b8a97ef5654db959d8635b2fe5f29b", size = 146004247, upload-time = "2026-01-21T16:24:29.335Z" }, + { url = "https://files.pythonhosted.org/packages/98/fb/5160261aeb5e1ee12ee95fe599d0541f7c976c3701d607d8fc29e623229f/torch-2.10.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6b71486353fce0f9714ca0c9ef1c850a2ae766b409808acd58e9678a3edb7738", size = 915716445, upload-time = "2026-01-21T16:22:45.353Z" }, + { url = "https://files.pythonhosted.org/packages/6a/16/502fb1b41e6d868e8deb5b0e3ae926bbb36dab8ceb0d1b769b266ad7b0c3/torch-2.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:c2ee399c644dc92ef7bc0d4f7e74b5360c37cdbe7c5ba11318dda49ffac2bc57", size = 113757050, upload-time = "2026-01-21T16:24:19.204Z" }, + { url = "https://files.pythonhosted.org/packages/1a/0b/39929b148f4824bc3ad6f9f72a29d4ad865bcf7ebfc2fa67584773e083d2/torch-2.10.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:3202429f58309b9fa96a614885eace4b7995729f44beb54d3e4a47773649d382", size = 79851305, upload-time = "2026-01-21T16:24:09.209Z" }, + { url = "https://files.pythonhosted.org/packages/d8/14/21fbce63bc452381ba5f74a2c0a959fdf5ad5803ccc0c654e752e0dbe91a/torch-2.10.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:aae1b29cd68e50a9397f5ee897b9c24742e9e306f88a807a27d617f07adb3bd8", size = 146005472, upload-time = "2026-01-21T16:22:29.022Z" }, + { url = "https://files.pythonhosted.org/packages/54/fd/b207d1c525cb570ef47f3e9f836b154685011fce11a2f444ba8a4084d042/torch-2.10.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:6021db85958db2f07ec94e1bc77212721ba4920c12a18dc552d2ae36a3eb163f", size = 915612644, upload-time = "2026-01-21T16:21:47.019Z" }, + { url = "https://files.pythonhosted.org/packages/36/53/0197f868c75f1050b199fe58f9bf3bf3aecac9b4e85cc9c964383d745403/torch-2.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff43db38af76fda183156153983c9a096fc4c78d0cd1e07b14a2314c7f01c2c8", size = 113997015, upload-time = "2026-01-21T16:23:00.767Z" }, + { url = "https://files.pythonhosted.org/packages/0e/13/e76b4d9c160e89fff48bf16b449ea324bda84745d2ab30294c37c2434c0d/torch-2.10.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:cdf2a523d699b70d613243211ecaac14fe9c5df8a0b0a9c02add60fb2a413e0f", size = 79498248, upload-time = "2026-01-21T16:23:09.315Z" }, + { url = "https://files.pythonhosted.org/packages/4f/93/716b5ac0155f1be70ed81bacc21269c3ece8dba0c249b9994094110bfc51/torch-2.10.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:bf0d9ff448b0218e0433aeb198805192346c4fd659c852370d5cc245f602a06a", size = 79464992, upload-time = "2026-01-21T16:23:05.162Z" }, + { url = "https://files.pythonhosted.org/packages/69/2b/51e663ff190c9d16d4a8271203b71bc73a16aa7619b9f271a69b9d4a936b/torch-2.10.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:233aed0659a2503b831d8a67e9da66a62c996204c0bba4f4c442ccc0c68a3f60", size = 146018567, upload-time = "2026-01-21T16:22:23.393Z" }, + { url = "https://files.pythonhosted.org/packages/5e/cd/4b95ef7f293b927c283db0b136c42be91c8ec6845c44de0238c8c23bdc80/torch-2.10.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:682497e16bdfa6efeec8cde66531bc8d1fbbbb4d8788ec6173c089ed3cc2bfe5", size = 915721646, upload-time = "2026-01-21T16:21:16.983Z" }, + { url = "https://files.pythonhosted.org/packages/56/97/078a007208f8056d88ae43198833469e61a0a355abc0b070edd2c085eb9a/torch-2.10.0-cp314-cp314-win_amd64.whl", hash = "sha256:6528f13d2a8593a1a412ea07a99812495bec07e9224c28b2a25c0a30c7da025c", size = 113752373, upload-time = "2026-01-21T16:22:13.471Z" }, + { url = "https://files.pythonhosted.org/packages/d8/94/71994e7d0d5238393df9732fdab607e37e2b56d26a746cb59fdb415f8966/torch-2.10.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:f5ab4ba32383061be0fb74bda772d470140a12c1c3b58a0cfbf3dae94d164c28", size = 79850324, upload-time = "2026-01-21T16:22:09.494Z" }, + { url = "https://files.pythonhosted.org/packages/e2/65/1a05346b418ea8ccd10360eef4b3e0ce688fba544e76edec26913a8d0ee0/torch-2.10.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:716b01a176c2a5659c98f6b01bf868244abdd896526f1c692712ab36dbaf9b63", size = 146006482, upload-time = "2026-01-21T16:22:18.42Z" }, + { url = "https://files.pythonhosted.org/packages/1d/b9/5f6f9d9e859fc3235f60578fa64f52c9c6e9b4327f0fe0defb6de5c0de31/torch-2.10.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:d8f5912ba938233f86361e891789595ff35ca4b4e2ac8fe3670895e5976731d6", size = 915613050, upload-time = "2026-01-21T16:20:49.035Z" }, + { url = "https://files.pythonhosted.org/packages/66/4d/35352043ee0eaffdeff154fad67cd4a31dbed7ff8e3be1cc4549717d6d51/torch-2.10.0-cp314-cp314t-win_amd64.whl", hash = "sha256:71283a373f0ee2c89e0f0d5f446039bdabe8dbc3c9ccf35f0f784908b0acd185", size = 113995816, upload-time = "2026-01-21T16:22:05.312Z" }, ] [[package]] @@ -3441,7 +3478,7 @@ wheels = [ [[package]] name = "torchvision" -version = "0.24.1" +version = "0.25.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, @@ -3449,30 +3486,30 @@ dependencies = [ { name = "torch" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/69/30f5f03752aa1a7c23931d2519b31e557f3f10af5089d787cddf3b903ecf/torchvision-0.24.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:056c525dc875f18fe8e9c27079ada166a7b2755cea5a2199b0bc7f1f8364e600", size = 1891436, upload-time = "2025-11-12T15:25:04.3Z" }, - { url = "https://files.pythonhosted.org/packages/0c/69/49aae86edb75fe16460b59a191fcc0f568c2378f780bb063850db0fe007a/torchvision-0.24.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:1e39619de698e2821d71976c92c8a9e50cdfd1e993507dfb340f2688bfdd8283", size = 2387757, upload-time = "2025-11-12T15:25:06.795Z" }, - { url = "https://files.pythonhosted.org/packages/11/c9/1dfc3db98797b326f1d0c3f3bb61c83b167a813fc7eab6fcd2edb8c7eb9d/torchvision-0.24.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a0f106663e60332aa4fcb1ca2159ef8c3f2ed266b0e6df88de261048a840e0df", size = 8047682, upload-time = "2025-11-12T15:25:21.125Z" }, - { url = "https://files.pythonhosted.org/packages/fa/bb/cfc6a6f6ccc84a534ed1fdf029ae5716dd6ff04e57ed9dc2dab38bf652d5/torchvision-0.24.1-cp311-cp311-win_amd64.whl", hash = "sha256:a9308cdd37d8a42e14a3e7fd9d271830c7fecb150dd929b642f3c1460514599a", size = 4037588, upload-time = "2025-11-12T15:25:14.402Z" }, - { url = "https://files.pythonhosted.org/packages/f0/af/18e2c6b9538a045f60718a0c5a058908ccb24f88fde8e6f0fc12d5ff7bd3/torchvision-0.24.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e48bf6a8ec95872eb45763f06499f87bd2fb246b9b96cb00aae260fda2f96193", size = 1891433, upload-time = "2025-11-12T15:25:03.232Z" }, - { url = "https://files.pythonhosted.org/packages/9d/43/600e5cfb0643d10d633124f5982d7abc2170dfd7ce985584ff16edab3e76/torchvision-0.24.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:7fb7590c737ebe3e1c077ad60c0e5e2e56bb26e7bccc3b9d04dbfc34fd09f050", size = 2386737, upload-time = "2025-11-12T15:25:08.288Z" }, - { url = "https://files.pythonhosted.org/packages/93/b1/db2941526ecddd84884132e2742a55c9311296a6a38627f9e2627f5ac889/torchvision-0.24.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:66a98471fc18cad9064123106d810a75f57f0838eee20edc56233fd8484b0cc7", size = 8049868, upload-time = "2025-11-12T15:25:13.058Z" }, - { url = "https://files.pythonhosted.org/packages/69/98/16e583f59f86cd59949f59d52bfa8fc286f86341a229a9d15cbe7a694f0c/torchvision-0.24.1-cp312-cp312-win_amd64.whl", hash = "sha256:4aa6cb806eb8541e92c9b313e96192c6b826e9eb0042720e2fa250d021079952", size = 4302006, upload-time = "2025-11-12T15:25:16.184Z" }, - { url = "https://files.pythonhosted.org/packages/e4/97/ab40550f482577f2788304c27220e8ba02c63313bd74cf2f8920526aac20/torchvision-0.24.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:8a6696db7fb71eadb2c6a48602106e136c785642e598eb1533e0b27744f2cce6", size = 1891435, upload-time = "2025-11-12T15:25:28.642Z" }, - { url = "https://files.pythonhosted.org/packages/30/65/ac0a3f9be6abdbe4e1d82c915d7e20de97e7fd0e9a277970508b015309f3/torchvision-0.24.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:db2125c46f9cb25dc740be831ce3ce99303cfe60439249a41b04fd9f373be671", size = 2338718, upload-time = "2025-11-12T15:25:26.19Z" }, - { url = "https://files.pythonhosted.org/packages/10/b5/5bba24ff9d325181508501ed7f0c3de8ed3dd2edca0784d48b144b6c5252/torchvision-0.24.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:f035f0cacd1f44a8ff6cb7ca3627d84c54d685055961d73a1a9fb9827a5414c8", size = 8049661, upload-time = "2025-11-12T15:25:22.558Z" }, - { url = "https://files.pythonhosted.org/packages/5c/ec/54a96ae9ab6a0dd66d4bba27771f892e36478a9c3489fa56e51c70abcc4d/torchvision-0.24.1-cp313-cp313-win_amd64.whl", hash = "sha256:16274823b93048e0a29d83415166a2e9e0bf4e1b432668357b657612a4802864", size = 4319808, upload-time = "2025-11-12T15:25:17.318Z" }, - { url = "https://files.pythonhosted.org/packages/d5/f3/a90a389a7e547f3eb8821b13f96ea7c0563cdefbbbb60a10e08dda9720ff/torchvision-0.24.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e3f96208b4bef54cd60e415545f5200346a65024e04f29a26cd0006dbf9e8e66", size = 2005342, upload-time = "2025-11-12T15:25:11.871Z" }, - { url = "https://files.pythonhosted.org/packages/a9/fe/ff27d2ed1b524078164bea1062f23d2618a5fc3208e247d6153c18c91a76/torchvision-0.24.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:f231f6a4f2aa6522713326d0d2563538fa72d613741ae364f9913027fa52ea35", size = 2341708, upload-time = "2025-11-12T15:25:25.08Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b9/d6c903495cbdfd2533b3ef6f7b5643ff589ea062f8feb5c206ee79b9d9e5/torchvision-0.24.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:1540a9e7f8cf55fe17554482f5a125a7e426347b71de07327d5de6bfd8d17caa", size = 8177239, upload-time = "2025-11-12T15:25:18.554Z" }, - { url = "https://files.pythonhosted.org/packages/4f/2b/ba02e4261369c3798310483028495cf507e6cb3f394f42e4796981ecf3a7/torchvision-0.24.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d83e16d70ea85d2f196d678bfb702c36be7a655b003abed84e465988b6128938", size = 4251604, upload-time = "2025-11-12T15:25:34.069Z" }, - { url = "https://files.pythonhosted.org/packages/42/84/577b2cef8f32094add5f52887867da4c2a3e6b4261538447e9b48eb25812/torchvision-0.24.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cccf4b4fec7fdfcd3431b9ea75d1588c0a8596d0333245dafebee0462abe3388", size = 2005319, upload-time = "2025-11-12T15:25:23.827Z" }, - { url = "https://files.pythonhosted.org/packages/5f/34/ecb786bffe0159a3b49941a61caaae089853132f3cd1e8f555e3621f7e6f/torchvision-0.24.1-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:1b495edd3a8f9911292424117544f0b4ab780452e998649425d1f4b2bed6695f", size = 2338844, upload-time = "2025-11-12T15:25:32.625Z" }, - { url = "https://files.pythonhosted.org/packages/51/99/a84623786a6969504c87f2dc3892200f586ee13503f519d282faab0bb4f0/torchvision-0.24.1-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:ab211e1807dc3e53acf8f6638df9a7444c80c0ad050466e8d652b3e83776987b", size = 8175144, upload-time = "2025-11-12T15:25:31.355Z" }, - { url = "https://files.pythonhosted.org/packages/6d/ba/8fae3525b233e109317ce6a9c1de922ab2881737b029a7e88021f81e068f/torchvision-0.24.1-cp314-cp314-win_amd64.whl", hash = "sha256:18f9cb60e64b37b551cd605a3d62c15730c086362b40682d23e24b616a697d41", size = 4234459, upload-time = "2025-11-12T15:25:19.859Z" }, - { url = "https://files.pythonhosted.org/packages/50/33/481602c1c72d0485d4b3a6b48c9534b71c2957c9d83bf860eb837bf5a620/torchvision-0.24.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ec9d7379c519428395e4ffda4dbb99ec56be64b0a75b95989e00f9ec7ae0b2d7", size = 2005336, upload-time = "2025-11-12T15:25:27.225Z" }, - { url = "https://files.pythonhosted.org/packages/d0/7f/372de60bf3dd8f5593bd0d03f4aecf0d1fd58f5bc6943618d9d913f5e6d5/torchvision-0.24.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:af9201184c2712d808bd4eb656899011afdfce1e83721c7cb08000034df353fe", size = 2341704, upload-time = "2025-11-12T15:25:29.857Z" }, - { url = "https://files.pythonhosted.org/packages/36/9b/0f3b9ff3d0225ee2324ec663de0e7fb3eb855615ca958ac1875f22f1f8e5/torchvision-0.24.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:9ef95d819fd6df81bc7cc97b8f21a15d2c0d3ac5dbfaab5cbc2d2ce57114b19e", size = 8177422, upload-time = "2025-11-12T15:25:37.357Z" }, - { url = "https://files.pythonhosted.org/packages/d6/ab/e2bcc7c2f13d882a58f8b30ff86f794210b075736587ea50f8c545834f8a/torchvision-0.24.1-cp314-cp314t-win_amd64.whl", hash = "sha256:480b271d6edff83ac2e8d69bbb4cf2073f93366516a50d48f140ccfceedb002e", size = 4335190, upload-time = "2025-11-12T15:25:35.745Z" }, + { url = "https://files.pythonhosted.org/packages/3e/be/c704bceaf11c4f6b19d64337a34a877fcdfe3bd68160a8c9ae9bea4a35a3/torchvision-0.25.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:db74a551946b75d19f9996c419a799ffdf6a223ecf17c656f90da011f1d75b20", size = 1874923, upload-time = "2026-01-21T16:27:46.574Z" }, + { url = "https://files.pythonhosted.org/packages/ae/e9/f143cd71232430de1f547ceab840f68c55e127d72558b1061a71d0b193cd/torchvision-0.25.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:f49964f96644dbac2506dffe1a0a7ec0f2bf8cf7a588c3319fed26e6329ffdf3", size = 2344808, upload-time = "2026-01-21T16:27:43.191Z" }, + { url = "https://files.pythonhosted.org/packages/43/ae/ad5d6165797de234c9658752acb4fce65b78a6a18d82efdf8367c940d8da/torchvision-0.25.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:153c0d2cbc34b7cf2da19d73450f24ba36d2b75ec9211b9962b5022fb9e4ecee", size = 8070752, upload-time = "2026-01-21T16:27:33.748Z" }, + { url = "https://files.pythonhosted.org/packages/23/19/55b28aecdc7f38df57b8eb55eb0b14a62b470ed8efeb22cdc74224df1d6a/torchvision-0.25.0-cp311-cp311-win_amd64.whl", hash = "sha256:ea580ffd6094cc01914ad32f8c8118174f18974629af905cea08cb6d5d48c7b7", size = 4038722, upload-time = "2026-01-21T16:27:41.355Z" }, + { url = "https://files.pythonhosted.org/packages/56/3a/6ea0d73f49a9bef38a1b3a92e8dd455cea58470985d25635beab93841748/torchvision-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c2abe430c90b1d5e552680037d68da4eb80a5852ebb1c811b2b89d299b10573b", size = 1874920, upload-time = "2026-01-21T16:27:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/51/f8/c0e1ef27c66e15406fece94930e7d6feee4cb6374bbc02d945a630d6426e/torchvision-0.25.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:b75deafa2dfea3e2c2a525559b04783515e3463f6e830cb71de0fb7ea36fe233", size = 2344556, upload-time = "2026-01-21T16:27:40.125Z" }, + { url = "https://files.pythonhosted.org/packages/68/2f/f24b039169db474e8688f649377de082a965fbf85daf4e46c44412f1d15a/torchvision-0.25.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f25aa9e380865b11ea6e9d99d84df86b9cc959f1a007cd966fc6f1ab2ed0e248", size = 8072351, upload-time = "2026-01-21T16:27:21.074Z" }, + { url = "https://files.pythonhosted.org/packages/ad/16/8f650c2e288977cf0f8f85184b90ee56ed170a4919347fc74ee99286ed6f/torchvision-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:f9c55ae8d673ab493325d1267cbd285bb94d56f99626c00ac4644de32a59ede3", size = 4303059, upload-time = "2026-01-21T16:27:11.08Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5b/1562a04a6a5a4cf8cf40016a0cdeda91ede75d6962cff7f809a85ae966a5/torchvision-0.25.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:24e11199e4d84ba9c5ee7825ebdf1cd37ce8deec225117f10243cae984ced3ec", size = 1874918, upload-time = "2026-01-21T16:27:39.02Z" }, + { url = "https://files.pythonhosted.org/packages/36/b1/3d6c42f62c272ce34fcce609bb8939bdf873dab5f1b798fd4e880255f129/torchvision-0.25.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:5f271136d2d2c0b7a24c5671795c6e4fd8da4e0ea98aeb1041f62bc04c4370ef", size = 2309106, upload-time = "2026-01-21T16:27:30.624Z" }, + { url = "https://files.pythonhosted.org/packages/c7/60/59bb9c8b67cce356daeed4cb96a717caa4f69c9822f72e223a0eae7a9bd9/torchvision-0.25.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:855c0dc6d37f462482da7531c6788518baedca1e0847f3df42a911713acdfe52", size = 8071522, upload-time = "2026-01-21T16:27:29.392Z" }, + { url = "https://files.pythonhosted.org/packages/32/a5/9a9b1de0720f884ea50dbf9acb22cbe5312e51d7b8c4ac6ba9b51efd9bba/torchvision-0.25.0-cp313-cp313-win_amd64.whl", hash = "sha256:cef0196be31be421f6f462d1e9da1101be7332d91984caa6f8022e6c78a5877f", size = 4321911, upload-time = "2026-01-21T16:27:35.195Z" }, + { url = "https://files.pythonhosted.org/packages/52/99/dca81ed21ebaeff2b67cc9f815a20fdaa418b69f5f9ea4c6ed71721470db/torchvision-0.25.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a8f8061284395ce31bcd460f2169013382ccf411148ceb2ee38e718e9860f5a7", size = 1896209, upload-time = "2026-01-21T16:27:32.159Z" }, + { url = "https://files.pythonhosted.org/packages/28/cc/2103149761fdb4eaed58a53e8437b2d716d48f05174fab1d9fcf1e2a2244/torchvision-0.25.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:146d02c9876858420adf41f3189fe90e3d6a409cbfa65454c09f25fb33bf7266", size = 2310735, upload-time = "2026-01-21T16:27:22.327Z" }, + { url = "https://files.pythonhosted.org/packages/76/ad/f4c985ad52ddd3b22711c588501be1b330adaeaf6850317f66751711b78c/torchvision-0.25.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:c4d395cb2c4a2712f6eb93a34476cdf7aae74bb6ea2ea1917f858e96344b00aa", size = 8089557, upload-time = "2026-01-21T16:27:27.666Z" }, + { url = "https://files.pythonhosted.org/packages/63/cc/0ea68b5802e5e3c31f44b307e74947bad5a38cc655231d845534ed50ddb8/torchvision-0.25.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5e6b449e9fa7d642142c0e27c41e5a43b508d57ed8e79b7c0a0c28652da8678c", size = 4344260, upload-time = "2026-01-21T16:27:17.018Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1f/fa839532660e2602b7e704d65010787c5bb296258b44fa8b9c1cd6175e7d/torchvision-0.25.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:620a236288d594dcec7634c754484542dc0a5c1b0e0b83a34bda5e91e9b7c3a1", size = 1896193, upload-time = "2026-01-21T16:27:24.785Z" }, + { url = "https://files.pythonhosted.org/packages/80/ed/d51889da7ceaf5ff7a0574fb28f9b6b223df19667265395891f81b364ab3/torchvision-0.25.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:0b5e7f50002a8145a98c5694a018e738c50e2972608310c7e88e1bd4c058f6ce", size = 2309331, upload-time = "2026-01-21T16:27:19.97Z" }, + { url = "https://files.pythonhosted.org/packages/90/a5/f93fcffaddd8f12f9e812256830ec9c9ca65abbf1bc369379f9c364d1ff4/torchvision-0.25.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:632db02300e83793812eee4f61ae6a2686dab10b4cfd628b620dc47747aa9d03", size = 8088713, upload-time = "2026-01-21T16:27:15.281Z" }, + { url = "https://files.pythonhosted.org/packages/1f/eb/d0096eed5690d962853213f2ee00d91478dfcb586b62dbbb449fb8abc3a6/torchvision-0.25.0-cp314-cp314-win_amd64.whl", hash = "sha256:d1abd5ed030c708f5dbf4812ad5f6fbe9384b63c40d6bd79f8df41a4a759a917", size = 4325058, upload-time = "2026-01-21T16:27:26.165Z" }, + { url = "https://files.pythonhosted.org/packages/97/36/96374a4c7ab50dea9787ce987815614ccfe988a42e10ac1a2e3e5b60319a/torchvision-0.25.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ad9a8a5877782944d99186e4502a614770fe906626d76e9cd32446a0ac3075f2", size = 1896207, upload-time = "2026-01-21T16:27:23.383Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e2/7abb10a867db79b226b41da419b63b69c0bd5b82438c4a4ed50e084c552f/torchvision-0.25.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:40a122c3cf4d14b651f095e0f672b688dde78632783fc5cd3d4d5e4f6a828563", size = 2310741, upload-time = "2026-01-21T16:27:18.712Z" }, + { url = "https://files.pythonhosted.org/packages/08/e6/0927784e6ffc340b6676befde1c60260bd51641c9c574b9298d791a9cda4/torchvision-0.25.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:846890161b825b38aa85fc37fb3ba5eea74e7091ff28bab378287111483b6443", size = 8089772, upload-time = "2026-01-21T16:27:14.048Z" }, + { url = "https://files.pythonhosted.org/packages/b6/37/e7ca4ec820d434c0f23f824eb29f0676a0c3e7a118f1514f5b949c3356da/torchvision-0.25.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f07f01d27375ad89d72aa2b3f2180f07da95dd9d2e4c758e015c0acb2da72977", size = 4425879, upload-time = "2026-01-21T16:27:12.579Z" }, ] [[package]] @@ -3496,14 +3533,14 @@ wheels = [ [[package]] name = "tqdm" -version = "4.67.1" +version = "4.67.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +sdist = { url = "https://files.pythonhosted.org/packages/27/89/4b0001b2dab8df0a5ee2787dcbe771de75ded01f18f1f8d53dedeea2882b/tqdm-4.67.2.tar.gz", hash = "sha256:649aac53964b2cb8dec76a14b405a4c0d13612cb8933aae547dd144eacc99653", size = 169514, upload-time = "2026-01-30T23:12:06.555Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e2/31eac96de2915cf20ccaed0225035db149dfb9165a9ed28d4b252ef3f7f7/tqdm-4.67.2-py3-none-any.whl", hash = "sha256:9a12abcbbff58b6036b2167d9d3853042b9d436fe7330f06ae047867f2f8e0a7", size = 78354, upload-time = "2026-01-30T23:12:04.368Z" }, ] [[package]] @@ -3517,15 +3554,15 @@ wheels = [ [[package]] name = "triton" -version = "3.5.1" +version = "3.6.0" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/72/ec90c3519eaf168f22cb1757ad412f3a2add4782ad3a92861c9ad135d886/triton-3.5.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:61413522a48add32302353fdbaaf92daaaab06f6b5e3229940d21b5207f47579", size = 170425802, upload-time = "2025-11-11T17:40:53.209Z" }, - { url = "https://files.pythonhosted.org/packages/f2/50/9a8358d3ef58162c0a415d173cfb45b67de60176e1024f71fbc4d24c0b6d/triton-3.5.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d2c6b915a03888ab931a9fd3e55ba36785e1fe70cbea0b40c6ef93b20fc85232", size = 170470207, upload-time = "2025-11-11T17:41:00.253Z" }, - { url = "https://files.pythonhosted.org/packages/27/46/8c3bbb5b0a19313f50edcaa363b599e5a1a5ac9683ead82b9b80fe497c8d/triton-3.5.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3f4346b6ebbd4fad18773f5ba839114f4826037c9f2f34e0148894cd5dd3dba", size = 170470410, upload-time = "2025-11-11T17:41:06.319Z" }, - { url = "https://files.pythonhosted.org/packages/37/92/e97fcc6b2c27cdb87ce5ee063d77f8f26f19f06916aa680464c8104ef0f6/triton-3.5.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0b4d2c70127fca6a23e247f9348b8adde979d2e7a20391bfbabaac6aebc7e6a8", size = 170579924, upload-time = "2025-11-11T17:41:12.455Z" }, - { url = "https://files.pythonhosted.org/packages/a4/e6/c595c35e5c50c4bc56a7bac96493dad321e9e29b953b526bbbe20f9911d0/triton-3.5.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0637b1efb1db599a8e9dc960d53ab6e4637db7d4ab6630a0974705d77b14b60", size = 170480488, upload-time = "2025-11-11T17:41:18.222Z" }, - { url = "https://files.pythonhosted.org/packages/16/b5/b0d3d8b901b6a04ca38df5e24c27e53afb15b93624d7fd7d658c7cd9352a/triton-3.5.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bac7f7d959ad0f48c0e97d6643a1cc0fd5786fe61cb1f83b537c6b2d54776478", size = 170582192, upload-time = "2025-11-11T17:41:23.963Z" }, + { url = "https://files.pythonhosted.org/packages/e0/12/b05ba554d2c623bffa59922b94b0775673de251f468a9609bc9e45de95e9/triton-3.6.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8e323d608e3a9bfcc2d9efcc90ceefb764a82b99dea12a86d643c72539ad5d3", size = 188214640, upload-time = "2026-01-20T16:00:35.869Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a8/cdf8b3e4c98132f965f88c2313a4b493266832ad47fb52f23d14d4f86bb5/triton-3.6.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74caf5e34b66d9f3a429af689c1c7128daba1d8208df60e81106b115c00d6fca", size = 188266850, upload-time = "2026-01-20T16:00:43.041Z" }, + { url = "https://files.pythonhosted.org/packages/f9/0b/37d991d8c130ce81a8728ae3c25b6e60935838e9be1b58791f5997b24a54/triton-3.6.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10c7f76c6e72d2ef08df639e3d0d30729112f47a56b0c81672edc05ee5116ac9", size = 188289450, upload-time = "2026-01-20T16:00:49.136Z" }, + { url = "https://files.pythonhosted.org/packages/35/f8/9c66bfc55361ec6d0e4040a0337fb5924ceb23de4648b8a81ae9d33b2b38/triton-3.6.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d002e07d7180fd65e622134fbd980c9a3d4211fb85224b56a0a0efbd422ab72f", size = 188400296, upload-time = "2026-01-20T16:00:56.042Z" }, + { url = "https://files.pythonhosted.org/packages/df/3d/9e7eee57b37c80cec63322c0231bb6da3cfe535a91d7a4d64896fcb89357/triton-3.6.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a17a5d5985f0ac494ed8a8e54568f092f7057ef60e1b0fa09d3fd1512064e803", size = 188273063, upload-time = "2026-01-20T16:01:07.278Z" }, + { url = "https://files.pythonhosted.org/packages/f6/56/6113c23ff46c00aae423333eb58b3e60bdfe9179d542781955a5e1514cb3/triton-3.6.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46bd1c1af4b6704e554cad2eeb3b0a6513a980d470ccfa63189737340c7746a7", size = 188397994, upload-time = "2026-01-20T16:01:14.236Z" }, ] [[package]] @@ -3580,11 +3617,11 @@ wheels = [ [[package]] name = "wcwidth" -version = "0.2.14" +version = "0.5.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/62/a7c072fbfefb2980a00f99ca994279cb9ecf310cb2e6b2a4d2a28fe192b3/wcwidth-0.5.3.tar.gz", hash = "sha256:53123b7af053c74e9fe2e92ac810301f6139e64379031f7124574212fb3b4091", size = 157587, upload-time = "2026-01-31T03:52:10.92Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, + { url = "https://files.pythonhosted.org/packages/3c/c1/d73f12f8cdb1891334a2ccf7389eed244d3941e74d80dd220badb937f3fb/wcwidth-0.5.3-py3-none-any.whl", hash = "sha256:d584eff31cd4753e1e5ff6c12e1edfdb324c995713f75d26c29807bb84bf649e", size = 92981, upload-time = "2026-01-31T03:52:09.14Z" }, ] [[package]] From e1cd567dddf246159c259d7baf81566bcee7c00e Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 2 Feb 2026 15:00:23 -0800 Subject: [PATCH 062/335] Updates --- src/quantem/tomography/dataset_models.py | 2 +- src/quantem/tomography/object_models.py | 9 ++++-- src/quantem/tomography/tomography_lite.py | 34 +++++++++++++++++++++-- 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index ebba944a..247fa0d9 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -257,7 +257,7 @@ def __init__( ): super().__init__( tilt_stack=tilt_stack, - tilt_angles=tilt_angles, + tilt_angles=-tilt_angles, # TODO: Flip the tilt angles to be negative to match the convention of INR. learn_shift=learn_shift, learn_tilt_axis=learn_tilt_axis, _token=_token, diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index c5315848..6d9ca6e3 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -450,9 +450,12 @@ def pretrain( pretrain_dataset is not None ): # Need to make a check if there's already a pretrain dataset to not go through with the setup again. self.pretrain_dataset = pretrain_dataset - self.pretraining_dataloader, self.pretraining_sampler = self.setup_dataloader( - pretrain_dataset, batch_size, num_workers=num_workers - ) + ( + self.pretraining_dataloader, + self.pretraining_sampler, + self.pretraining_val_dataloader, + self.pretraining_val_sampler, + ) = self.setup_dataloader(pretrain_dataset, batch_size, num_workers=num_workers) if optimizer_params is not None: self.set_optimizer(optimizer_params) diff --git a/src/quantem/tomography/tomography_lite.py b/src/quantem/tomography/tomography_lite.py index b78ea4ff..786c333a 100644 --- a/src/quantem/tomography/tomography_lite.py +++ b/src/quantem/tomography/tomography_lite.py @@ -6,8 +6,8 @@ from quantem.core.ml.inr import HSiren from quantem.tomography.dataset_models import DatasetModelType from quantem.tomography.logger_tomography import LoggerTomography -from quantem.tomography.object_models import ObjectINR -from quantem.tomography.tomography import Tomography +from quantem.tomography.object_models import ObjectINR, ObjectPixelated +from quantem.tomography.tomography import Tomography, TomographyConventional class TomographyLiteINR(Tomography): @@ -119,3 +119,33 @@ def reconstruct( scheduler_params=scheduler_params, constraints=constraints, ) + + +class TomographyLiteConv(TomographyConventional): + @classmethod + def from_dataset( + cls, + dset: DatasetModelType, + device: str = "cuda", + rng: np.random.Generator | int | None = None, + ) -> Self: + dset_model = dset + + obj_model = ObjectPixelated( + shape=( + max(dset_model.tilt_stack.shape), + max(dset_model.tilt_stack.shape), + max(dset_model.tilt_stack.shape), + ), + device=device, + rng=rng, + ) + + tomography = cls.from_models( + dset=dset_model, + obj_model=obj_model, + device=device, + rng=rng, + ) + + return tomography From 075bb7ec6dad7d8ecc09cedc7311aa1ab364a8d5 Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Mon, 2 Feb 2026 15:34:38 -0800 Subject: [PATCH 063/335] updates to ptycho viz and inr --- src/quantem/core/ml/inr.py | 37 ++++++++++++++----- .../diffractive_imaging/ptychography_base.py | 2 + .../ptychography_visualizations.py | 11 ++++-- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/quantem/core/ml/inr.py b/src/quantem/core/ml/inr.py index 24ee45e4..276e5680 100644 --- a/src/quantem/core/ml/inr.py +++ b/src/quantem/core/ml/inr.py @@ -118,7 +118,10 @@ def reset_weights(self) -> None: self._build() def make_equispaced_grid( - self, bounds: tuple[tuple[float, float], ...], sampling: tuple[float, ...] + self, + bounds: tuple[tuple[float, float], ...], + sampling: tuple[float, ...] | None = None, + num_points: tuple[int, ...] | None = None, ) -> torch.Tensor: """Create an equispaced coordinate grid for the implicit neural representation. @@ -130,7 +133,10 @@ def make_equispaced_grid( sampling : tuple of float Sampling interval for each dimension (spacing_0, spacing_1, ...). Length must match in_features. - + num_points : tuple of int, optional + Number of points to sample in each dimension. If None, the number of points + is calculated from the sampling interval. If both sampling and num_points are provided, + num_points takes precedence. Returns ------- torch.Tensor @@ -153,15 +159,28 @@ def make_equispaced_grid( raise ValueError( f"Bounds length ({len(bounds)}) must match in_features ({self.in_features})" ) - if len(sampling) != self.in_features: - raise ValueError( - f"Sampling length ({len(sampling)}) must match in_features ({self.in_features})" + if sampling is not None and num_points is not None: + raise ValueError("Only one of sampling or num_points can be provided") + if sampling is not None: + if len(sampling) != self.in_features: + raise ValueError( + f"Sampling length ({len(sampling)}) must match in_features ({self.in_features})" + ) + num_points = tuple( + int((bound_max - bound_min) / sample) + 1 + for (bound_min, bound_max), sample in zip(bounds, sampling) ) - + elif num_points is not None: + if len(num_points) != self.in_features: + raise ValueError( + f"Num points length ({len(num_points)}) must match in_features ({self.in_features})" + ) + else: + raise ValueError("Either sampling or num_points must be provided") grids = [] - for (bound_min, bound_max), sample in zip(bounds, sampling): - num_points = int((bound_max - bound_min) / sample) + 1 - grids.append(torch.linspace(bound_min, bound_max, num_points)) + for i, (bound_min, bound_max) in enumerate(bounds): + n = num_points[i] + grids.append(torch.linspace(bound_min, bound_max, n)) coords = torch.meshgrid(*grids, indexing="ij") coords = torch.stack(coords, dim=-1).to(self.dtype) diff --git a/src/quantem/diffractive_imaging/ptychography_base.py b/src/quantem/diffractive_imaging/ptychography_base.py index f359dbee..665819d9 100644 --- a/src/quantem/diffractive_imaging/ptychography_base.py +++ b/src/quantem/diffractive_imaging/ptychography_base.py @@ -465,6 +465,8 @@ def get_snapshot_by_iter( "No snapshots available. Use store_snapshots=True during reconstruction." ) iteration = int(iteration) + if iteration < 0: + iteration = self.num_iters + iteration if closest: closest_snapshot = min(self.snapshots, key=lambda s: abs(s["iteration"] - iteration)) snp = closest_snapshot diff --git a/src/quantem/diffractive_imaging/ptychography_visualizations.py b/src/quantem/diffractive_imaging/ptychography_visualizations.py index 20d5ca09..d415dd4f 100644 --- a/src/quantem/diffractive_imaging/ptychography_visualizations.py +++ b/src/quantem/diffractive_imaging/ptychography_visualizations.py @@ -1,3 +1,4 @@ +import warnings from typing import Any, Literal import matplotlib.gridspec as gridspec @@ -128,8 +129,6 @@ def show_obj_fft( obj_iter = "Final" if obj is None: if snapshot_iter is not None: - if snapshot_iter < 0: - snapshot_iter = len(self.snapshots) + snapshot_iter snp = self.get_snapshot_by_iter(snapshot_iter, closest=True, cropped=True) obj_np = snp["obj"] obj_iter = snp["iteration"] @@ -379,8 +378,8 @@ def show_obj_slices( if obj.ndim == 2: obj = obj[None, ...] - t_parts = [f"0/{len(obj)} | 0 Å"] - for i in range(1, len(obj)): + t_parts = [] + for i in range(len(obj)): t_parts.append(f"{i + 1}/{len(obj)} | {self.slice_thicknesses[i - 1]:.1f} Å") if self.obj_type == "potential": @@ -403,6 +402,10 @@ def show_obj_slices( if interval_type == "quantile": norm = {"interval_type": "quantile"} # TODO -- make this work with interval_scaling + if interval_scaling == "all": + warnings.warn( + "interval_scaling='all' is not yet supported for quantile normalization" + ) elif interval_type in ["manual", "minmax", "abs"]: norm: dict[str, Any] = {"interval_type": "manual"} if interval_scaling == "all": From 1711c22fd97d1d298561c3a9940f1579e2a4b6fb Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Tue, 3 Feb 2026 14:19:30 -0800 Subject: [PATCH 064/335] Fix type hints for modify_in_place overloads in Dataset --- src/quantem/core/datastructures/dataset.py | 46 ++++++++++++++-------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/src/quantem/core/datastructures/dataset.py b/src/quantem/core/datastructures/dataset.py index 764e2343..f01e68f6 100644 --- a/src/quantem/core/datastructures/dataset.py +++ b/src/quantem/core/datastructures/dataset.py @@ -1,5 +1,5 @@ -import os import numbers +import os from pathlib import Path from typing import Any, Literal, Optional, Self, Union, overload @@ -52,7 +52,9 @@ def __init__( super().__init__() arr = ensure_valid_array(array) if not isinstance(arr, np.ndarray): - raise TypeError("Dataset requires a NumPy array (CuPy is not supported on this branch).") + raise TypeError( + "Dataset requires a NumPy array (CuPy is not supported on this branch)." + ) self._array = arr self.name = name self.origin = origin @@ -97,7 +99,9 @@ def from_array( """ validated_array = ensure_valid_array(array) if not isinstance(validated_array, np.ndarray): - raise TypeError("Dataset requires a NumPy array (CuPy is not supported on this branch).") + raise TypeError( + "Dataset requires a NumPy array (CuPy is not supported on this branch)." + ) _ndim = validated_array.ndim # Set defaults if None @@ -126,7 +130,9 @@ def array(self) -> NDArray: def array(self, value: NDArray) -> None: arr = ensure_valid_array(value, ndim=self.ndim) # want to allow changing dtype if not isinstance(arr, np.ndarray): - raise TypeError("Dataset requires a NumPy array (CuPy is not supported on this branch).") + raise TypeError( + "Dataset requires a NumPy array (CuPy is not supported on this branch)." + ) self._array = arr # self._array = ensure_valid_array(value, dtype=self.dtype, ndim=self.ndim) @@ -350,8 +356,9 @@ def min(self, axes: int | tuple[int, ...] | None = None) -> Any: @overload def pad( self, - pad_width: int | tuple[int, int] | tuple[tuple[int, int], ...] | None, - output_shape: tuple[int, ...] | None, + pad_width: int | tuple[int, int] | tuple[tuple[int, int], ...] | None = None, + output_shape: tuple[int, ...] | None = None, + *, modify_in_place: Literal[True], **kwargs: Any, ) -> None: ... @@ -363,7 +370,7 @@ def pad( output_shape: tuple[int, ...] | None = None, modify_in_place: Literal[False] = False, **kwargs: Any, - ) -> "Dataset": ... + ) -> Self: ... def pad( self, @@ -371,7 +378,7 @@ def pad( output_shape: tuple[int, ...] | None = None, modify_in_place: bool = False, **kwargs: Any, - ) -> "Dataset | None": + ) -> Self | None: """ Pads Dataset data array using numpy.pad. Metadata (origin, sampling) is not modified. @@ -426,7 +433,8 @@ def pad( def crop( self, crop_widths: tuple[tuple[int, int], ...], - axes: tuple | None, + axes: tuple | None = None, + *, modify_in_place: Literal[True], ) -> None: ... @@ -464,7 +472,7 @@ def crop( if len(crop_widths) != self.ndim: raise ValueError("crop_widths must match number of dimensions when axes is None.") axes = tuple(range(self.ndim)) - elif np.isscalar(axes): + elif isinstance(axes, int | float): axes = (int(axes),) crop_widths = (crop_widths[0],) # Take first crop_width for single axis else: @@ -496,7 +504,8 @@ def crop( def bin( self, bin_factors, - axes, + axes=None, + *, modify_in_place: Literal[True], reducer: str = "sum", ) -> None: ... @@ -545,7 +554,7 @@ def bin( if axes is None: axes = tuple(range(self.ndim)) - elif np.isscalar(axes): + elif isinstance(axes, int | float): axes = (int(axes),) else: axes = tuple(int(ax) for ax in axes) @@ -660,7 +669,7 @@ def fourier_resample( """ if axes is None: axes = tuple(range(self.ndim)) - elif np.isscalar(axes): + elif isinstance(axes, int | float): axes = (int(axes),) else: axes = tuple(int(a0) for a0 in axes) @@ -670,14 +679,17 @@ def fourier_resample( # Resolve out_shape & factors if factors is not None: - if np.isscalar(factors): + if isinstance(factors, int | float): factors = (float(factors),) * len(axes) else: factors = tuple(float(f) for f in factors) if len(factors) != len(axes): raise ValueError("factors length must match number of axes.") - out_shape = tuple(max(1, int(round(self.shape[a1] * f))) for a1, f in zip(axes, factors)) + out_shape = tuple( + max(1, int(round(self.shape[a1] * f))) for a1, f in zip(axes, factors) + ) else: + assert out_shape is not None # Guaranteed by check above if len(out_shape) != len(axes): raise ValueError("out_shape length must match number of axes.") out_shape = tuple(int(nl) for nl in out_shape) @@ -806,7 +818,9 @@ def __getitem__(self, index) -> Self: kept_axes = [i for i, idx in enumerate(index) if not isinstance(idx, (int, np.integer))] # Slice/reduce metadata accordingly - new_origin = np.asarray(self.origin)[kept_axes] if np.ndim(self.origin) > 0 else self.origin + new_origin = ( + np.asarray(self.origin)[kept_axes] if np.ndim(self.origin) > 0 else self.origin + ) new_sampling = ( np.asarray(self.sampling)[kept_axes] if np.ndim(self.sampling) > 0 else self.sampling ) From e9bf8da38ded0dd8378d3bd54a02411803c5a6a8 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 3 Feb 2026 23:45:37 -0800 Subject: [PATCH 065/335] Reinstantiating new dataloaders in the training loop; kinda messy, need to figure out a better way to do this. Also the current way of reinitializing the z1_params is jank, need a better way to reinitialize parameters --- src/quantem/tomography/dataset_models.py | 8 ++++++++ src/quantem/tomography/tomography.py | 15 +++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index 247fa0d9..4c45f44b 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -130,6 +130,10 @@ def tilt_stack(self) -> torch.Tensor: @tilt_stack.setter def tilt_stack(self, tilt_stack: torch.Tensor): + if type(tilt_stack) is not torch.Tensor: + print("Converting tilt stack to torch.Tensor") + tilt_stack = torch.from_numpy(tilt_stack) + self._tilt_stack = tilt_stack @property @@ -138,6 +142,10 @@ def tilt_angles(self) -> torch.Tensor: @tilt_angles.setter def tilt_angles(self, tilt_angles: torch.Tensor): + if type(tilt_angles) is not torch.Tensor: + print("Converting tilt angles to torch.Tensor") + tilt_angles = torch.from_numpy(tilt_angles) + self._tilt_angles = tilt_angles @property diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 32b94807..d3b0c065 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -49,12 +49,14 @@ def reconstruct( num_workers: int = 32, reset: bool = False, optimizer_params: dict | None = None, - scheduler_params: dict | None = None, + scheduler_params: dict | None = {}, constraints: dict = {}, # TODO: What to pass into the constraints? loss_func: Tuple[str, Optional[float]] = ("smooth_l1", 0.07), num_samples_per_ray: int | List[Tuple[int, int]] = None, profiling_mode: bool = False, val_fraction: float = 0.0, + # reset_dset: bool = False, + reset_dset: DatasetModelType | None = None, ): """ This function should be able to handle both AD and INR-based tomography reconstruction methods. @@ -96,8 +98,16 @@ def reconstruct( self.set_schedulers(scheduler_params, num_iter=num_iter) # Setting up DDP - if not hasattr(self, "dataloader"): + if not hasattr(self, "dataloader") or reset_dset is not None: with nvtx_range(profiling_mode, "Setting Dataloader"): + if reset_dset is not None: + print("Resetting Dataloader") + print("Putting in params from previous dataset.") + + self.dset = reset_dset + self.dset.to(self.device) + self.optimizer_params = optimizer_params + self.set_optimizers() self.dataloader, self.sampler, self.val_dataloader, self.val_sampler = ( self.setup_dataloader( self.dset, @@ -321,6 +331,7 @@ def save_volume(self, path: str = "recon_volume.npz"): print("Barrier") torch.distributed.barrier() + class TomographyConventional(TomographyBase): """ Class for handling all conventional tomography reconstruction methods. From aea1c1b57e03392d00a351a3677f00adb92c2125 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Wed, 4 Feb 2026 15:55:44 -0800 Subject: [PATCH 066/335] Fixed some device issues, one process is still hanging on cuda:0, need to track down --- src/quantem/core/ml/ddp.py | 13 +++++++++++-- src/quantem/tomography/dataset_models.py | 1 + src/quantem/tomography/object_models.py | 7 +++++-- src/quantem/tomography/tomography.py | 16 ++++++++++++++++ src/quantem/tomography/tomography_base.py | 6 ++++-- 5 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/quantem/core/ml/ddp.py b/src/quantem/core/ml/ddp.py index 01daa41f..114b06b9 100644 --- a/src/quantem/core/ml/ddp.py +++ b/src/quantem/core/ml/ddp.py @@ -89,7 +89,6 @@ def setup_dataloader( else: val_sampler = None shuffle = False - else: train_sampler = None @@ -145,7 +144,7 @@ def build_model( Returns the model. """ - + print("Building Model on device: ", self.device) model = model.to(self.device) if pretrained_weights is not None: model.load_state_dict(pretrained_weights.copy()) @@ -172,3 +171,13 @@ def build_model( print("Model built, compiled successfully") return model + + @property + def device(self) -> torch.device: + return self._device + + @device.setter + def device(self, device: torch.device): + if isinstance(device, str): + device = torch.device(device) + self._device = device diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index 4c45f44b..eba21ef3 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -500,6 +500,7 @@ def to(self, device: str): self._shifts_ref = self._shifts_ref.to(device) self.device = device + self.reconnect_optimizer_to_parameters() class TomographyINRPretrainDataset(Dataset): diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 6d9ca6e3..c70c9c3f 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -397,6 +397,8 @@ def shape(self, shape: tuple[int, int, int]): self._shape = shape # --- Helper Functions --- + def rebuild_model(self): + self._model = self.build_model(self._model) # Reset method that goes back to the pretrained weights. def reset(self): @@ -412,7 +414,6 @@ def get_optimization_parameters(self): def forward(self, coords: torch.Tensor) -> torch.Tensor: """forward pass for the INR model""" - all_densities = self.model(coords) if all_densities.dim() > 1: @@ -621,7 +622,9 @@ def get_tv_loss( def to(self, device: str): # self._model = self._model.to(device) - self._obj = self._obj.to(device) + self.device = device + self._obj = self._obj.to(self.device) + self.reconnect_optimizer_to_parameters() ObjectModelType = ObjectPixelated | ObjectINR diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index d3b0c065..ff8ab977 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -5,6 +5,7 @@ import torch.distributed as dist from tqdm.auto import tqdm +from quantem.core.io.serialize import load as autoserialize_load from quantem.core.ml.ddp import DDPMixin from quantem.core.ml.profiling import nvtx_range from quantem.tomography.dataset_models import DatasetModelType @@ -331,6 +332,21 @@ def save_volume(self, path: str = "recon_volume.npz"): print("Barrier") torch.distributed.barrier() + # Loading and Saving + @classmethod + def _recursive_load_from_path(cls, path: str): + return autoserialize_load(path) + + @classmethod + def from_file( + cls, + path: str, + device: str = "cpu", + ) -> Self: + tomography = cls._recursive_load_from_path(path) + tomography.to(device) + return tomography + class TomographyConventional(TomographyBase): """ diff --git a/src/quantem/tomography/tomography_base.py b/src/quantem/tomography/tomography_base.py index 6b517f03..7661c301 100644 --- a/src/quantem/tomography/tomography_base.py +++ b/src/quantem/tomography/tomography_base.py @@ -1,4 +1,5 @@ import numpy as np +import torch from numpy.typing import NDArray from quantem.core.io.serialize import AutoSerialize @@ -108,7 +109,7 @@ def logger(self, logger: LoggerTomography | None): @property def device(self) -> str: - return self._device + return torch.device(self._device) @device.setter def device(self, device: str): @@ -116,7 +117,7 @@ def device(self, device: str): # if not isinstance(device, str): # raise TypeError(f"device should be a str, got {type(device)}") self._device = device - self.to(device) + # self.to(device) @property def epoch_losses(self) -> NDArray: @@ -141,3 +142,4 @@ def num_epochs(self) -> int: def to(self, device: str): self.obj_model.to(device) self.dset.to(device) + self.device = device From 99411000cf5ad72a72bb234d4ac01e986f96f9ce Mon Sep 17 00:00:00 2001 From: Stephanie Ribet <55254539+smribet@users.noreply.github.com> Date: Thu, 5 Feb 2026 16:43:04 -0800 Subject: [PATCH 067/335] Update src/quantem/core/io/file_readers.py Co-authored-by: Carter Francis --- src/quantem/core/io/file_readers.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/quantem/core/io/file_readers.py b/src/quantem/core/io/file_readers.py index 7cb6fb5e..2db53e96 100644 --- a/src/quantem/core/io/file_readers.py +++ b/src/quantem/core/io/file_readers.py @@ -31,7 +31,18 @@ def read_4dstem( If None, automatically selects the first 4D dataset found. **kwargs: dict Additional keyword arguments to pass to the file reader. - +Other Parameters +---------------- +name : str | None, optional + A descriptive name for the dataset. If None, defaults to "4D-STEM dataset" +origin : NDArray | tuple | list | float | int | None, optional + The origin coordinates for each dimension. If None, defaults to zeros +sampling : NDArray | tuple | list | float | int | None, optional + The sampling rate/spacing for each dimension. If None, defaults to ones +units : list[str] | tuple | list | None, optional + Units for each dimension. If None, defaults to ["pixels"] * 4 +signal_units : str, optional + Units for the array values, by default "arb. units" Returns -------- Dataset4dstem From f23ecf7481167bff2b4b1a2ada96e82b2d53a898 Mon Sep 17 00:00:00 2001 From: Stephanie Ribet <55254539+smribet@users.noreply.github.com> Date: Thu, 5 Feb 2026 16:43:21 -0800 Subject: [PATCH 068/335] Update src/quantem/core/io/file_readers.py Co-authored-by: Carter Francis --- src/quantem/core/io/file_readers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quantem/core/io/file_readers.py b/src/quantem/core/io/file_readers.py index 2db53e96..b2621273 100644 --- a/src/quantem/core/io/file_readers.py +++ b/src/quantem/core/io/file_readers.py @@ -53,7 +53,7 @@ def read_4dstem( sampling_override = kwargs.pop("sampling", None) origin_override = kwargs.pop("origin", None) units_override = kwargs.pop("units", None) - +name_override = kwargs.pop("name", None) file_reader = importlib.import_module(f"rsciio.{file_type}").file_reader data_list = file_reader(file_path, **kwargs) From 5328a7f932db58526f5be148e538bc0ede6810f2 Mon Sep 17 00:00:00 2001 From: Stephanie Ribet <55254539+smribet@users.noreply.github.com> Date: Thu, 5 Feb 2026 16:43:40 -0800 Subject: [PATCH 069/335] Update src/quantem/core/io/file_readers.py Co-authored-by: Carter Francis --- src/quantem/core/io/file_readers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quantem/core/io/file_readers.py b/src/quantem/core/io/file_readers.py index b2621273..27182dd9 100644 --- a/src/quantem/core/io/file_readers.py +++ b/src/quantem/core/io/file_readers.py @@ -87,7 +87,7 @@ def read_4dstem( sampling = ( sampling_override if sampling_override is not None - else [ax["scale"] for ax in imported_axes] + else [ax.get("scale", 1) for ax in imported_axes] ) origin = ( origin_override if origin_override is not None else [ax["offset"] for ax in imported_axes] From cc19df78c87f4f5a1a43072cc8347103aaa90092 Mon Sep 17 00:00:00 2001 From: Stephanie Ribet <55254539+smribet@users.noreply.github.com> Date: Thu, 5 Feb 2026 16:43:51 -0800 Subject: [PATCH 070/335] Update src/quantem/core/io/file_readers.py Co-authored-by: Carter Francis --- src/quantem/core/io/file_readers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quantem/core/io/file_readers.py b/src/quantem/core/io/file_readers.py index 27182dd9..3402adcb 100644 --- a/src/quantem/core/io/file_readers.py +++ b/src/quantem/core/io/file_readers.py @@ -90,7 +90,7 @@ def read_4dstem( else [ax.get("scale", 1) for ax in imported_axes] ) origin = ( - origin_override if origin_override is not None else [ax["offset"] for ax in imported_axes] + origin_override if origin_override is not None else [ax.get("offset", 0) for ax in imported_axes] ) units = ( units_override From a101b7e20fd17ecdc40446ef075cc25f214736ee Mon Sep 17 00:00:00 2001 From: Stephanie Ribet <55254539+smribet@users.noreply.github.com> Date: Thu, 5 Feb 2026 16:43:58 -0800 Subject: [PATCH 071/335] Update src/quantem/core/io/file_readers.py Co-authored-by: Carter Francis --- src/quantem/core/io/file_readers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/quantem/core/io/file_readers.py b/src/quantem/core/io/file_readers.py index 3402adcb..5bfcf930 100644 --- a/src/quantem/core/io/file_readers.py +++ b/src/quantem/core/io/file_readers.py @@ -103,6 +103,7 @@ def read_4dstem( sampling=sampling, origin=origin, units=units, + name = name, ) return dataset From b41a3993501e49fb9ac5a56d11044df6d553f90a Mon Sep 17 00:00:00 2001 From: smribet Date: Thu, 5 Feb 2026 16:48:23 -0800 Subject: [PATCH 072/335] cleaning up --- src/quantem/core/io/file_readers.py | 35 ++++++++++++++++------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/quantem/core/io/file_readers.py b/src/quantem/core/io/file_readers.py index 5bfcf930..b575a6d3 100644 --- a/src/quantem/core/io/file_readers.py +++ b/src/quantem/core/io/file_readers.py @@ -31,18 +31,20 @@ def read_4dstem( If None, automatically selects the first 4D dataset found. **kwargs: dict Additional keyword arguments to pass to the file reader. -Other Parameters ----------------- -name : str | None, optional - A descriptive name for the dataset. If None, defaults to "4D-STEM dataset" -origin : NDArray | tuple | list | float | int | None, optional - The origin coordinates for each dimension. If None, defaults to zeros -sampling : NDArray | tuple | list | float | int | None, optional - The sampling rate/spacing for each dimension. If None, defaults to ones -units : list[str] | tuple | list | None, optional - Units for each dimension. If None, defaults to ["pixels"] * 4 -signal_units : str, optional - Units for the array values, by default "arb. units" + + Other Parameters + ---------------- + name : str | None, optional + A descriptive name for the dataset. If None, defaults to "4D-STEM dataset" + origin : NDArray | tuple | list | float | int | None, optional + The origin coordinates for each dimension. If None, defaults to zeros + sampling : NDArray | tuple | list | float | int | None, optional + The sampling rate/spacing for each dimension. If None, defaults to ones + units : list[str] | tuple | list | None, optional + Units for each dimension. If None, defaults to ["pixels"] * 4 + signal_units : str, optional + Units for the array values, by default "arb. units" + Returns -------- Dataset4dstem @@ -53,7 +55,8 @@ def read_4dstem( sampling_override = kwargs.pop("sampling", None) origin_override = kwargs.pop("origin", None) units_override = kwargs.pop("units", None) -name_override = kwargs.pop("name", None) + name_override = kwargs.pop("name", None) + file_reader = importlib.import_module(f"rsciio.{file_type}").file_reader data_list = file_reader(file_path, **kwargs) @@ -90,7 +93,9 @@ def read_4dstem( else [ax.get("scale", 1) for ax in imported_axes] ) origin = ( - origin_override if origin_override is not None else [ax.get("offset", 0) for ax in imported_axes] + origin_override + if origin_override is not None + else [ax.get("offset", 0) for ax in imported_axes] ) units = ( units_override @@ -103,7 +108,7 @@ def read_4dstem( sampling=sampling, origin=origin, units=units, - name = name, + name=name_override, ) return dataset From 377ef6fae98b6f3c3ba92a71d5846a29658c458f Mon Sep 17 00:00:00 2001 From: quantem-bot Date: Mon, 9 Feb 2026 10:08:46 +0000 Subject: [PATCH 073/335] chore: update lock file --- uv.lock | 356 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 182 insertions(+), 174 deletions(-) diff --git a/uv.lock b/uv.lock index d6170093..7a43d17a 100644 --- a/uv.lock +++ b/uv.lock @@ -516,89 +516,89 @@ wheels = [ [[package]] name = "coverage" -version = "7.13.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ad/49/349848445b0e53660e258acbcc9b0d014895b6739237920886672240f84b/coverage-7.13.2.tar.gz", hash = "sha256:044c6951ec37146b72a50cc81ef02217d27d4c3640efd2640311393cbbf143d3", size = 826523, upload-time = "2026-01-25T13:00:04.889Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/01/abca50583a8975bb6e1c59eff67ed8e48bb127c07dad5c28d9e96ccc09ec/coverage-7.13.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:060ebf6f2c51aff5ba38e1f43a2095e087389b1c69d559fde6049a4b0001320e", size = 218971, upload-time = "2026-01-25T12:57:36.953Z" }, - { url = "https://files.pythonhosted.org/packages/eb/0e/b6489f344d99cd1e5b4d5e1be52dfd3f8a3dc5112aa6c33948da8cabad4e/coverage-7.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1ea8ca9db5e7469cd364552985e15911548ea5b69c48a17291f0cac70484b2e", size = 219473, upload-time = "2026-01-25T12:57:38.934Z" }, - { url = "https://files.pythonhosted.org/packages/17/11/db2f414915a8e4ec53f60b17956c27f21fb68fcf20f8a455ce7c2ccec638/coverage-7.13.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b780090d15fd58f07cf2011943e25a5f0c1c894384b13a216b6c86c8a8a7c508", size = 249896, upload-time = "2026-01-25T12:57:40.365Z" }, - { url = "https://files.pythonhosted.org/packages/80/06/0823fe93913663c017e508e8810c998c8ebd3ec2a5a85d2c3754297bdede/coverage-7.13.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:88a800258d83acb803c38175b4495d293656d5fac48659c953c18e5f539a274b", size = 251810, upload-time = "2026-01-25T12:57:42.045Z" }, - { url = "https://files.pythonhosted.org/packages/61/dc/b151c3cc41b28cdf7f0166c5fa1271cbc305a8ec0124cce4b04f74791a18/coverage-7.13.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6326e18e9a553e674d948536a04a80d850a5eeefe2aae2e6d7cf05d54046c01b", size = 253920, upload-time = "2026-01-25T12:57:44.026Z" }, - { url = "https://files.pythonhosted.org/packages/2d/35/e83de0556e54a4729a2b94ea816f74ce08732e81945024adee46851c2264/coverage-7.13.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:59562de3f797979e1ff07c587e2ac36ba60ca59d16c211eceaa579c266c5022f", size = 250025, upload-time = "2026-01-25T12:57:45.624Z" }, - { url = "https://files.pythonhosted.org/packages/39/67/af2eb9c3926ce3ea0d58a0d2516fcbdacf7a9fc9559fe63076beaf3f2596/coverage-7.13.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:27ba1ed6f66b0e2d61bfa78874dffd4f8c3a12f8e2b5410e515ab345ba7bc9c3", size = 251612, upload-time = "2026-01-25T12:57:47.713Z" }, - { url = "https://files.pythonhosted.org/packages/26/62/5be2e25f3d6c711d23b71296f8b44c978d4c8b4e5b26871abfc164297502/coverage-7.13.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8be48da4d47cc68754ce643ea50b3234557cbefe47c2f120495e7bd0a2756f2b", size = 249670, upload-time = "2026-01-25T12:57:49.378Z" }, - { url = "https://files.pythonhosted.org/packages/b3/51/400d1b09a8344199f9b6a6fc1868005d766b7ea95e7882e494fa862ca69c/coverage-7.13.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2a47a4223d3361b91176aedd9d4e05844ca67d7188456227b6bf5e436630c9a1", size = 249395, upload-time = "2026-01-25T12:57:50.86Z" }, - { url = "https://files.pythonhosted.org/packages/e0/36/f02234bc6e5230e2f0a63fd125d0a2093c73ef20fdf681c7af62a140e4e7/coverage-7.13.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c6f141b468740197d6bd38f2b26ade124363228cc3f9858bd9924ab059e00059", size = 250298, upload-time = "2026-01-25T12:57:52.287Z" }, - { url = "https://files.pythonhosted.org/packages/b0/06/713110d3dd3151b93611c9cbfc65c15b4156b44f927fced49ac0b20b32a4/coverage-7.13.2-cp311-cp311-win32.whl", hash = "sha256:89567798404af067604246e01a49ef907d112edf2b75ef814b1364d5ce267031", size = 221485, upload-time = "2026-01-25T12:57:53.876Z" }, - { url = "https://files.pythonhosted.org/packages/16/0c/3ae6255fa1ebcb7dec19c9a59e85ef5f34566d1265c70af5b2fc981da834/coverage-7.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:21dd57941804ae2ac7e921771a5e21bbf9aabec317a041d164853ad0a96ce31e", size = 222421, upload-time = "2026-01-25T12:57:55.433Z" }, - { url = "https://files.pythonhosted.org/packages/b5/37/fabc3179af4d61d89ea47bd04333fec735cd5e8b59baad44fed9fc4170d7/coverage-7.13.2-cp311-cp311-win_arm64.whl", hash = "sha256:10758e0586c134a0bafa28f2d37dd2cdb5e4a90de25c0fc0c77dabbad46eca28", size = 221088, upload-time = "2026-01-25T12:57:57.41Z" }, - { url = "https://files.pythonhosted.org/packages/46/39/e92a35f7800222d3f7b2cbb7bbc3b65672ae8d501cb31801b2d2bd7acdf1/coverage-7.13.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f106b2af193f965d0d3234f3f83fc35278c7fb935dfbde56ae2da3dd2c03b84d", size = 219142, upload-time = "2026-01-25T12:58:00.448Z" }, - { url = "https://files.pythonhosted.org/packages/45/7a/8bf9e9309c4c996e65c52a7c5a112707ecdd9fbaf49e10b5a705a402bbb4/coverage-7.13.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78f45d21dc4d5d6bd29323f0320089ef7eae16e4bef712dff79d184fa7330af3", size = 219503, upload-time = "2026-01-25T12:58:02.451Z" }, - { url = "https://files.pythonhosted.org/packages/87/93/17661e06b7b37580923f3f12406ac91d78aeed293fb6da0b69cc7957582f/coverage-7.13.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fae91dfecd816444c74531a9c3d6ded17a504767e97aa674d44f638107265b99", size = 251006, upload-time = "2026-01-25T12:58:04.059Z" }, - { url = "https://files.pythonhosted.org/packages/12/f0/f9e59fb8c310171497f379e25db060abef9fa605e09d63157eebec102676/coverage-7.13.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:264657171406c114787b441484de620e03d8f7202f113d62fcd3d9688baa3e6f", size = 253750, upload-time = "2026-01-25T12:58:05.574Z" }, - { url = "https://files.pythonhosted.org/packages/e5/b1/1935e31add2232663cf7edd8269548b122a7d100047ff93475dbaaae673e/coverage-7.13.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae47d8dcd3ded0155afbb59c62bd8ab07ea0fd4902e1c40567439e6db9dcaf2f", size = 254862, upload-time = "2026-01-25T12:58:07.647Z" }, - { url = "https://files.pythonhosted.org/packages/af/59/b5e97071ec13df5f45da2b3391b6cdbec78ba20757bc92580a5b3d5fa53c/coverage-7.13.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8a0b33e9fd838220b007ce8f299114d406c1e8edb21336af4c97a26ecfd185aa", size = 251420, upload-time = "2026-01-25T12:58:09.309Z" }, - { url = "https://files.pythonhosted.org/packages/3f/75/9495932f87469d013dc515fb0ce1aac5fa97766f38f6b1a1deb1ee7b7f3a/coverage-7.13.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b3becbea7f3ce9a2d4d430f223ec15888e4deb31395840a79e916368d6004cce", size = 252786, upload-time = "2026-01-25T12:58:10.909Z" }, - { url = "https://files.pythonhosted.org/packages/6a/59/af550721f0eb62f46f7b8cb7e6f1860592189267b1c411a4e3a057caacee/coverage-7.13.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f819c727a6e6eeb8711e4ce63d78c620f69630a2e9d53bc95ca5379f57b6ba94", size = 250928, upload-time = "2026-01-25T12:58:12.449Z" }, - { url = "https://files.pythonhosted.org/packages/9b/b1/21b4445709aae500be4ab43bbcfb4e53dc0811c3396dcb11bf9f23fd0226/coverage-7.13.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:4f7b71757a3ab19f7ba286e04c181004c1d61be921795ee8ba6970fd0ec91da5", size = 250496, upload-time = "2026-01-25T12:58:14.047Z" }, - { url = "https://files.pythonhosted.org/packages/ba/b1/0f5d89dfe0392990e4f3980adbde3eb34885bc1effb2dc369e0bf385e389/coverage-7.13.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b7fc50d2afd2e6b4f6f2f403b70103d280a8e0cb35320cbbe6debcda02a1030b", size = 252373, upload-time = "2026-01-25T12:58:15.976Z" }, - { url = "https://files.pythonhosted.org/packages/01/c9/0cf1a6a57a9968cc049a6b896693faa523c638a5314b1fc374eb2b2ac904/coverage-7.13.2-cp312-cp312-win32.whl", hash = "sha256:292250282cf9bcf206b543d7608bda17ca6fc151f4cbae949fc7e115112fbd41", size = 221696, upload-time = "2026-01-25T12:58:17.517Z" }, - { url = "https://files.pythonhosted.org/packages/4d/05/d7540bf983f09d32803911afed135524570f8c47bb394bf6206c1dc3a786/coverage-7.13.2-cp312-cp312-win_amd64.whl", hash = "sha256:eeea10169fac01549a7921d27a3e517194ae254b542102267bef7a93ed38c40e", size = 222504, upload-time = "2026-01-25T12:58:19.115Z" }, - { url = "https://files.pythonhosted.org/packages/15/8b/1a9f037a736ced0a12aacf6330cdaad5008081142a7070bc58b0f7930cbc/coverage-7.13.2-cp312-cp312-win_arm64.whl", hash = "sha256:2a5b567f0b635b592c917f96b9a9cb3dbd4c320d03f4bf94e9084e494f2e8894", size = 221120, upload-time = "2026-01-25T12:58:21.334Z" }, - { url = "https://files.pythonhosted.org/packages/a7/f0/3d3eac7568ab6096ff23791a526b0048a1ff3f49d0e236b2af6fb6558e88/coverage-7.13.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ed75de7d1217cf3b99365d110975f83af0528c849ef5180a12fd91b5064df9d6", size = 219168, upload-time = "2026-01-25T12:58:23.376Z" }, - { url = "https://files.pythonhosted.org/packages/a3/a6/f8b5cfeddbab95fdef4dcd682d82e5dcff7a112ced57a959f89537ee9995/coverage-7.13.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:97e596de8fa9bada4d88fde64a3f4d37f1b6131e4faa32bad7808abc79887ddc", size = 219537, upload-time = "2026-01-25T12:58:24.932Z" }, - { url = "https://files.pythonhosted.org/packages/7b/e6/8d8e6e0c516c838229d1e41cadcec91745f4b1031d4db17ce0043a0423b4/coverage-7.13.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:68c86173562ed4413345410c9480a8d64864ac5e54a5cda236748031e094229f", size = 250528, upload-time = "2026-01-25T12:58:26.567Z" }, - { url = "https://files.pythonhosted.org/packages/8e/78/befa6640f74092b86961f957f26504c8fba3d7da57cc2ab7407391870495/coverage-7.13.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7be4d613638d678b2b3773b8f687537b284d7074695a43fe2fbbfc0e31ceaed1", size = 253132, upload-time = "2026-01-25T12:58:28.251Z" }, - { url = "https://files.pythonhosted.org/packages/9d/10/1630db1edd8ce675124a2ee0f7becc603d2bb7b345c2387b4b95c6907094/coverage-7.13.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7f63ce526a96acd0e16c4af8b50b64334239550402fb1607ce6a584a6d62ce9", size = 254374, upload-time = "2026-01-25T12:58:30.294Z" }, - { url = "https://files.pythonhosted.org/packages/ed/1d/0d9381647b1e8e6d310ac4140be9c428a0277330991e0c35bdd751e338a4/coverage-7.13.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:406821f37f864f968e29ac14c3fccae0fec9fdeba48327f0341decf4daf92d7c", size = 250762, upload-time = "2026-01-25T12:58:32.036Z" }, - { url = "https://files.pythonhosted.org/packages/43/e4/5636dfc9a7c871ee8776af83ee33b4c26bc508ad6cee1e89b6419a366582/coverage-7.13.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ee68e5a4e3e5443623406b905db447dceddffee0dceb39f4e0cd9ec2a35004b5", size = 252502, upload-time = "2026-01-25T12:58:33.961Z" }, - { url = "https://files.pythonhosted.org/packages/02/2a/7ff2884d79d420cbb2d12fed6fff727b6d0ef27253140d3cdbbd03187ee0/coverage-7.13.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2ee0e58cca0c17dd9c6c1cdde02bb705c7b3fbfa5f3b0b5afeda20d4ebff8ef4", size = 250463, upload-time = "2026-01-25T12:58:35.529Z" }, - { url = "https://files.pythonhosted.org/packages/91/c0/ba51087db645b6c7261570400fc62c89a16278763f36ba618dc8657a187b/coverage-7.13.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:6e5bbb5018bf76a56aabdb64246b5288d5ae1b7d0dd4d0534fe86df2c2992d1c", size = 250288, upload-time = "2026-01-25T12:58:37.226Z" }, - { url = "https://files.pythonhosted.org/packages/03/07/44e6f428551c4d9faf63ebcefe49b30e5c89d1be96f6a3abd86a52da9d15/coverage-7.13.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a55516c68ef3e08e134e818d5e308ffa6b1337cc8b092b69b24287bf07d38e31", size = 252063, upload-time = "2026-01-25T12:58:38.821Z" }, - { url = "https://files.pythonhosted.org/packages/c2/67/35b730ad7e1859dd57e834d1bc06080d22d2f87457d53f692fce3f24a5a9/coverage-7.13.2-cp313-cp313-win32.whl", hash = "sha256:5b20211c47a8abf4abc3319d8ce2464864fa9f30c5fcaf958a3eed92f4f1fef8", size = 221716, upload-time = "2026-01-25T12:58:40.484Z" }, - { url = "https://files.pythonhosted.org/packages/0d/82/e5fcf5a97c72f45fc14829237a6550bf49d0ab882ac90e04b12a69db76b4/coverage-7.13.2-cp313-cp313-win_amd64.whl", hash = "sha256:14f500232e521201cf031549fb1ebdfc0a40f401cf519157f76c397e586c3beb", size = 222522, upload-time = "2026-01-25T12:58:43.247Z" }, - { url = "https://files.pythonhosted.org/packages/b1/f1/25d7b2f946d239dd2d6644ca2cc060d24f97551e2af13b6c24c722ae5f97/coverage-7.13.2-cp313-cp313-win_arm64.whl", hash = "sha256:9779310cb5a9778a60c899f075a8514c89fa6d10131445c2207fc893e0b14557", size = 221145, upload-time = "2026-01-25T12:58:45Z" }, - { url = "https://files.pythonhosted.org/packages/9e/f7/080376c029c8f76fadfe43911d0daffa0cbdc9f9418a0eead70c56fb7f4b/coverage-7.13.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e64fa5a1e41ce5df6b547cbc3d3699381c9e2c2c369c67837e716ed0f549d48e", size = 219861, upload-time = "2026-01-25T12:58:46.586Z" }, - { url = "https://files.pythonhosted.org/packages/42/11/0b5e315af5ab35f4c4a70e64d3314e4eec25eefc6dec13be3a7d5ffe8ac5/coverage-7.13.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b01899e82a04085b6561eb233fd688474f57455e8ad35cd82286463ba06332b7", size = 220207, upload-time = "2026-01-25T12:58:48.277Z" }, - { url = "https://files.pythonhosted.org/packages/b2/0c/0874d0318fb1062117acbef06a09cf8b63f3060c22265adaad24b36306b7/coverage-7.13.2-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:838943bea48be0e2768b0cf7819544cdedc1bbb2f28427eabb6eb8c9eb2285d3", size = 261504, upload-time = "2026-01-25T12:58:49.904Z" }, - { url = "https://files.pythonhosted.org/packages/83/5e/1cd72c22ecb30751e43a72f40ba50fcef1b7e93e3ea823bd9feda8e51f9a/coverage-7.13.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:93d1d25ec2b27e90bcfef7012992d1f5121b51161b8bffcda756a816cf13c2c3", size = 263582, upload-time = "2026-01-25T12:58:51.582Z" }, - { url = "https://files.pythonhosted.org/packages/9b/da/8acf356707c7a42df4d0657020308e23e5a07397e81492640c186268497c/coverage-7.13.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93b57142f9621b0d12349c43fc7741fe578e4bc914c1e5a54142856cfc0bf421", size = 266008, upload-time = "2026-01-25T12:58:53.234Z" }, - { url = "https://files.pythonhosted.org/packages/41/41/ea1730af99960309423c6ea8d6a4f1fa5564b2d97bd1d29dda4b42611f04/coverage-7.13.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f06799ae1bdfff7ccb8665d75f8291c69110ba9585253de254688aa8a1ccc6c5", size = 260762, upload-time = "2026-01-25T12:58:55.372Z" }, - { url = "https://files.pythonhosted.org/packages/22/fa/02884d2080ba71db64fdc127b311db60e01fe6ba797d9c8363725e39f4d5/coverage-7.13.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f9405ab4f81d490811b1d91c7a20361135a2df4c170e7f0b747a794da5b7f23", size = 263571, upload-time = "2026-01-25T12:58:57.52Z" }, - { url = "https://files.pythonhosted.org/packages/d2/6b/4083aaaeba9b3112f55ac57c2ce7001dc4d8fa3fcc228a39f09cc84ede27/coverage-7.13.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f9ab1d5b86f8fbc97a5b3cd6280a3fd85fef3b028689d8a2c00918f0d82c728c", size = 261200, upload-time = "2026-01-25T12:58:59.255Z" }, - { url = "https://files.pythonhosted.org/packages/e9/d2/aea92fa36d61955e8c416ede9cf9bf142aa196f3aea214bb67f85235a050/coverage-7.13.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:f674f59712d67e841525b99e5e2b595250e39b529c3bda14764e4f625a3fa01f", size = 260095, upload-time = "2026-01-25T12:59:01.066Z" }, - { url = "https://files.pythonhosted.org/packages/0d/ae/04ffe96a80f107ea21b22b2367175c621da920063260a1c22f9452fd7866/coverage-7.13.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c6cadac7b8ace1ba9144feb1ae3cb787a6065ba6d23ffc59a934b16406c26573", size = 262284, upload-time = "2026-01-25T12:59:02.802Z" }, - { url = "https://files.pythonhosted.org/packages/1c/7a/6f354dcd7dfc41297791d6fb4e0d618acb55810bde2c1fd14b3939e05c2b/coverage-7.13.2-cp313-cp313t-win32.whl", hash = "sha256:14ae4146465f8e6e6253eba0cccd57423e598a4cb925958b240c805300918343", size = 222389, upload-time = "2026-01-25T12:59:04.563Z" }, - { url = "https://files.pythonhosted.org/packages/8d/d5/080ad292a4a3d3daf411574be0a1f56d6dee2c4fdf6b005342be9fac807f/coverage-7.13.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9074896edd705a05769e3de0eac0a8388484b503b68863dd06d5e473f874fd47", size = 223450, upload-time = "2026-01-25T12:59:06.677Z" }, - { url = "https://files.pythonhosted.org/packages/88/96/df576fbacc522e9fb8d1c4b7a7fc62eb734be56e2cba1d88d2eabe08ea3f/coverage-7.13.2-cp313-cp313t-win_arm64.whl", hash = "sha256:69e526e14f3f854eda573d3cf40cffd29a1a91c684743d904c33dbdcd0e0f3e7", size = 221707, upload-time = "2026-01-25T12:59:08.363Z" }, - { url = "https://files.pythonhosted.org/packages/55/53/1da9e51a0775634b04fcc11eb25c002fc58ee4f92ce2e8512f94ac5fc5bf/coverage-7.13.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:387a825f43d680e7310e6f325b2167dd093bc8ffd933b83e9aa0983cf6e0a2ef", size = 219213, upload-time = "2026-01-25T12:59:11.909Z" }, - { url = "https://files.pythonhosted.org/packages/46/35/b3caac3ebbd10230fea5a33012b27d19e999a17c9285c4228b4b2e35b7da/coverage-7.13.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f0d7fea9d8e5d778cd5a9e8fc38308ad688f02040e883cdc13311ef2748cb40f", size = 219549, upload-time = "2026-01-25T12:59:13.638Z" }, - { url = "https://files.pythonhosted.org/packages/76/9c/e1cf7def1bdc72c1907e60703983a588f9558434a2ff94615747bd73c192/coverage-7.13.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e080afb413be106c95c4ee96b4fffdc9e2fa56a8bbf90b5c0918e5c4449412f5", size = 250586, upload-time = "2026-01-25T12:59:15.808Z" }, - { url = "https://files.pythonhosted.org/packages/ba/49/f54ec02ed12be66c8d8897270505759e057b0c68564a65c429ccdd1f139e/coverage-7.13.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a7fc042ba3c7ce25b8a9f097eb0f32a5ce1ccdb639d9eec114e26def98e1f8a4", size = 253093, upload-time = "2026-01-25T12:59:17.491Z" }, - { url = "https://files.pythonhosted.org/packages/fb/5e/aaf86be3e181d907e23c0f61fccaeb38de8e6f6b47aed92bf57d8fc9c034/coverage-7.13.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d0ba505e021557f7f8173ee8cd6b926373d8653e5ff7581ae2efce1b11ef4c27", size = 254446, upload-time = "2026-01-25T12:59:19.752Z" }, - { url = "https://files.pythonhosted.org/packages/28/c8/a5fa01460e2d75b0c853b392080d6829d3ca8b5ab31e158fa0501bc7c708/coverage-7.13.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7de326f80e3451bd5cc7239ab46c73ddb658fe0b7649476bc7413572d36cd548", size = 250615, upload-time = "2026-01-25T12:59:21.928Z" }, - { url = "https://files.pythonhosted.org/packages/86/0b/6d56315a55f7062bb66410732c24879ccb2ec527ab6630246de5fe45a1df/coverage-7.13.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:abaea04f1e7e34841d4a7b343904a3f59481f62f9df39e2cd399d69a187a9660", size = 252452, upload-time = "2026-01-25T12:59:23.592Z" }, - { url = "https://files.pythonhosted.org/packages/30/19/9bc550363ebc6b0ea121977ee44d05ecd1e8bf79018b8444f1028701c563/coverage-7.13.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9f93959ee0c604bccd8e0697be21de0887b1f73efcc3aa73a3ec0fd13feace92", size = 250418, upload-time = "2026-01-25T12:59:25.392Z" }, - { url = "https://files.pythonhosted.org/packages/1f/53/580530a31ca2f0cc6f07a8f2ab5460785b02bb11bdf815d4c4d37a4c5169/coverage-7.13.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:13fe81ead04e34e105bf1b3c9f9cdf32ce31736ee5d90a8d2de02b9d3e1bcb82", size = 250231, upload-time = "2026-01-25T12:59:27.888Z" }, - { url = "https://files.pythonhosted.org/packages/e2/42/dd9093f919dc3088cb472893651884bd675e3df3d38a43f9053656dca9a2/coverage-7.13.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d6d16b0f71120e365741bca2cb473ca6fe38930bc5431c5e850ba949f708f892", size = 251888, upload-time = "2026-01-25T12:59:29.636Z" }, - { url = "https://files.pythonhosted.org/packages/fa/a6/0af4053e6e819774626e133c3d6f70fae4d44884bfc4b126cb647baee8d3/coverage-7.13.2-cp314-cp314-win32.whl", hash = "sha256:9b2f4714bb7d99ba3790ee095b3b4ac94767e1347fe424278a0b10acb3ff04fe", size = 221968, upload-time = "2026-01-25T12:59:31.424Z" }, - { url = "https://files.pythonhosted.org/packages/c4/cc/5aff1e1f80d55862442855517bb8ad8ad3a68639441ff6287dde6a58558b/coverage-7.13.2-cp314-cp314-win_amd64.whl", hash = "sha256:e4121a90823a063d717a96e0a0529c727fb31ea889369a0ee3ec00ed99bf6859", size = 222783, upload-time = "2026-01-25T12:59:33.118Z" }, - { url = "https://files.pythonhosted.org/packages/de/20/09abafb24f84b3292cc658728803416c15b79f9ee5e68d25238a895b07d9/coverage-7.13.2-cp314-cp314-win_arm64.whl", hash = "sha256:6873f0271b4a15a33e7590f338d823f6f66f91ed147a03938d7ce26efd04eee6", size = 221348, upload-time = "2026-01-25T12:59:34.939Z" }, - { url = "https://files.pythonhosted.org/packages/b6/60/a3820c7232db63be060e4019017cd3426751c2699dab3c62819cdbcea387/coverage-7.13.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f61d349f5b7cd95c34017f1927ee379bfbe9884300d74e07cf630ccf7a610c1b", size = 219950, upload-time = "2026-01-25T12:59:36.624Z" }, - { url = "https://files.pythonhosted.org/packages/fd/37/e4ef5975fdeb86b1e56db9a82f41b032e3d93a840ebaf4064f39e770d5c5/coverage-7.13.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a43d34ce714f4ca674c0d90beb760eb05aad906f2c47580ccee9da8fe8bfb417", size = 220209, upload-time = "2026-01-25T12:59:38.339Z" }, - { url = "https://files.pythonhosted.org/packages/54/df/d40e091d00c51adca1e251d3b60a8b464112efa3004949e96a74d7c19a64/coverage-7.13.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bff1b04cb9d4900ce5c56c4942f047dc7efe57e2608cb7c3c8936e9970ccdbee", size = 261576, upload-time = "2026-01-25T12:59:40.446Z" }, - { url = "https://files.pythonhosted.org/packages/c5/44/5259c4bed54e3392e5c176121af9f71919d96dde853386e7730e705f3520/coverage-7.13.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6ae99e4560963ad8e163e819e5d77d413d331fd00566c1e0856aa252303552c1", size = 263704, upload-time = "2026-01-25T12:59:42.346Z" }, - { url = "https://files.pythonhosted.org/packages/16/bd/ae9f005827abcbe2c70157459ae86053971c9fa14617b63903abbdce26d9/coverage-7.13.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e79a8c7d461820257d9aa43716c4efc55366d7b292e46b5b37165be1d377405d", size = 266109, upload-time = "2026-01-25T12:59:44.073Z" }, - { url = "https://files.pythonhosted.org/packages/a2/c0/8e279c1c0f5b1eaa3ad9b0fb7a5637fc0379ea7d85a781c0fe0bb3cfc2ab/coverage-7.13.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:060ee84f6a769d40c492711911a76811b4befb6fba50abb450371abb720f5bd6", size = 260686, upload-time = "2026-01-25T12:59:45.804Z" }, - { url = "https://files.pythonhosted.org/packages/b2/47/3a8112627e9d863e7cddd72894171c929e94491a597811725befdcd76bce/coverage-7.13.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bca209d001fd03ea2d978f8a4985093240a355c93078aee3f799852c23f561a", size = 263568, upload-time = "2026-01-25T12:59:47.929Z" }, - { url = "https://files.pythonhosted.org/packages/92/bc/7ea367d84afa3120afc3ce6de294fd2dcd33b51e2e7fbe4bbfd200f2cb8c/coverage-7.13.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:6b8092aa38d72f091db61ef83cb66076f18f02da3e1a75039a4f218629600e04", size = 261174, upload-time = "2026-01-25T12:59:49.717Z" }, - { url = "https://files.pythonhosted.org/packages/33/b7/f1092dcecb6637e31cc2db099581ee5c61a17647849bae6b8261a2b78430/coverage-7.13.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4a3158dc2dcce5200d91ec28cd315c999eebff355437d2765840555d765a6e5f", size = 260017, upload-time = "2026-01-25T12:59:51.463Z" }, - { url = "https://files.pythonhosted.org/packages/2b/cd/f3d07d4b95fbe1a2ef0958c15da614f7e4f557720132de34d2dc3aa7e911/coverage-7.13.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3973f353b2d70bd9796cc12f532a05945232ccae966456c8ed7034cb96bbfd6f", size = 262337, upload-time = "2026-01-25T12:59:53.407Z" }, - { url = "https://files.pythonhosted.org/packages/e0/db/b0d5b2873a07cb1e06a55d998697c0a5a540dcefbf353774c99eb3874513/coverage-7.13.2-cp314-cp314t-win32.whl", hash = "sha256:79f6506a678a59d4ded048dc72f1859ebede8ec2b9a2d509ebe161f01c2879d3", size = 222749, upload-time = "2026-01-25T12:59:56.316Z" }, - { url = "https://files.pythonhosted.org/packages/e5/2f/838a5394c082ac57d85f57f6aba53093b30d9089781df72412126505716f/coverage-7.13.2-cp314-cp314t-win_amd64.whl", hash = "sha256:196bfeabdccc5a020a57d5a368c681e3a6ceb0447d153aeccc1ab4d70a5032ba", size = 223857, upload-time = "2026-01-25T12:59:58.201Z" }, - { url = "https://files.pythonhosted.org/packages/44/d4/b608243e76ead3a4298824b50922b89ef793e50069ce30316a65c1b4d7ef/coverage-7.13.2-cp314-cp314t-win_arm64.whl", hash = "sha256:69269ab58783e090bfbf5b916ab3d188126e22d6070bbfc93098fdd474ef937c", size = 221881, upload-time = "2026-01-25T13:00:00.449Z" }, - { url = "https://files.pythonhosted.org/packages/d2/db/d291e30fdf7ea617a335531e72294e0c723356d7fdde8fba00610a76bda9/coverage-7.13.2-py3-none-any.whl", hash = "sha256:40ce1ea1e25125556d8e76bd0b61500839a07944cc287ac21d5626f3e620cad5", size = 210943, upload-time = "2026-01-25T13:00:02.388Z" }, +version = "7.13.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/43/3e4ac666cc35f231fa70c94e9f38459299de1a152813f9d2f60fc5f3ecaf/coverage-7.13.3.tar.gz", hash = "sha256:f7f6182d3dfb8802c1747eacbfe611b669455b69b7c037484bb1efbbb56711ac", size = 826832, upload-time = "2026-02-03T14:02:30.944Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/09/1ac74e37cf45f17eb41e11a21854f7f92a4c2d6c6098ef4a1becb0c6d8d3/coverage-7.13.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5907605ee20e126eeee2abe14aae137043c2c8af2fa9b38d2ab3b7a6b8137f73", size = 219276, upload-time = "2026-02-03T14:00:00.296Z" }, + { url = "https://files.pythonhosted.org/packages/2e/cb/71908b08b21beb2c437d0d5870c4ec129c570ca1b386a8427fcdb11cf89c/coverage-7.13.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a88705500988c8acad8b8fd86c2a933d3aa96bec1ddc4bc5cb256360db7bbd00", size = 219776, upload-time = "2026-02-03T14:00:02.414Z" }, + { url = "https://files.pythonhosted.org/packages/09/85/c4f3dd69232887666a2c0394d4be21c60ea934d404db068e6c96aa59cd87/coverage-7.13.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bbb5aa9016c4c29e3432e087aa29ebee3f8fda089cfbfb4e6d64bd292dcd1c2", size = 250196, upload-time = "2026-02-03T14:00:04.197Z" }, + { url = "https://files.pythonhosted.org/packages/9c/cc/560ad6f12010344d0778e268df5ba9aa990aacccc310d478bf82bf3d302c/coverage-7.13.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0c2be202a83dde768937a61cdc5d06bf9fb204048ca199d93479488e6247656c", size = 252111, upload-time = "2026-02-03T14:00:05.639Z" }, + { url = "https://files.pythonhosted.org/packages/f0/66/3193985fb2c58e91f94cfbe9e21a6fdf941e9301fe2be9e92c072e9c8f8c/coverage-7.13.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f45e32ef383ce56e0ca099b2e02fcdf7950be4b1b56afaab27b4ad790befe5b", size = 254217, upload-time = "2026-02-03T14:00:07.738Z" }, + { url = "https://files.pythonhosted.org/packages/c5/78/f0f91556bf1faa416792e537c523c5ef9db9b1d32a50572c102b3d7c45b3/coverage-7.13.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6ed2e787249b922a93cd95c671cc9f4c9797a106e81b455c83a9ddb9d34590c0", size = 250318, upload-time = "2026-02-03T14:00:09.224Z" }, + { url = "https://files.pythonhosted.org/packages/6f/aa/fc654e45e837d137b2c1f3a2cc09b4aea1e8b015acd2f774fa0f3d2ddeba/coverage-7.13.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:05dd25b21afffe545e808265897c35f32d3e4437663923e0d256d9ab5031fb14", size = 251909, upload-time = "2026-02-03T14:00:10.712Z" }, + { url = "https://files.pythonhosted.org/packages/73/4d/ab53063992add8a9ca0463c9d92cce5994a29e17affd1c2daa091b922a93/coverage-7.13.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:46d29926349b5c4f1ea4fca95e8c892835515f3600995a383fa9a923b5739ea4", size = 249971, upload-time = "2026-02-03T14:00:12.402Z" }, + { url = "https://files.pythonhosted.org/packages/29/25/83694b81e46fcff9899694a1b6f57573429cdd82b57932f09a698f03eea5/coverage-7.13.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:fae6a21537519c2af00245e834e5bf2884699cc7c1055738fd0f9dc37a3644ad", size = 249692, upload-time = "2026-02-03T14:00:13.868Z" }, + { url = "https://files.pythonhosted.org/packages/d4/ef/d68fc304301f4cb4bf6aefa0045310520789ca38dabdfba9dbecd3f37919/coverage-7.13.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c672d4e2f0575a4ca2bf2aa0c5ced5188220ab806c1bb6d7179f70a11a017222", size = 250597, upload-time = "2026-02-03T14:00:15.461Z" }, + { url = "https://files.pythonhosted.org/packages/8d/85/240ad396f914df361d0f71e912ddcedb48130c71b88dc4193fe3c0306f00/coverage-7.13.3-cp311-cp311-win32.whl", hash = "sha256:fcda51c918c7a13ad93b5f89a58d56e3a072c9e0ba5c231b0ed81404bf2648fb", size = 221773, upload-time = "2026-02-03T14:00:17.462Z" }, + { url = "https://files.pythonhosted.org/packages/2f/71/165b3a6d3d052704a9ab52d11ea64ef3426745de517dda44d872716213a7/coverage-7.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:d1a049b5c51b3b679928dd35e47c4a2235e0b6128b479a7596d0ef5b42fa6301", size = 222711, upload-time = "2026-02-03T14:00:19.449Z" }, + { url = "https://files.pythonhosted.org/packages/51/d0/0ddc9c5934cdd52639c5df1f1eb0fdab51bb52348f3a8d1c7db9c600d93a/coverage-7.13.3-cp311-cp311-win_arm64.whl", hash = "sha256:79f2670c7e772f4917895c3d89aad59e01f3dbe68a4ed2d0373b431fad1dcfba", size = 221377, upload-time = "2026-02-03T14:00:20.968Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/330f8e83b143f6668778ed61d17ece9dc48459e9e74669177de02f45fec5/coverage-7.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ed48b4170caa2c4420e0cd27dc977caaffc7eecc317355751df8373dddcef595", size = 219441, upload-time = "2026-02-03T14:00:22.585Z" }, + { url = "https://files.pythonhosted.org/packages/08/e7/29db05693562c2e65bdf6910c0af2fd6f9325b8f43caf7a258413f369e30/coverage-7.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8f2adf4bcffbbec41f366f2e6dffb9d24e8172d16e91da5799c9b7ed6b5716e6", size = 219801, upload-time = "2026-02-03T14:00:24.186Z" }, + { url = "https://files.pythonhosted.org/packages/90/ae/7f8a78249b02b0818db46220795f8ac8312ea4abd1d37d79ea81db5cae81/coverage-7.13.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:01119735c690786b6966a1e9f098da4cd7ca9174c4cfe076d04e653105488395", size = 251306, upload-time = "2026-02-03T14:00:25.798Z" }, + { url = "https://files.pythonhosted.org/packages/62/71/a18a53d1808e09b2e9ebd6b47dad5e92daf4c38b0686b4c4d1b2f3e42b7f/coverage-7.13.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8bb09e83c603f152d855f666d70a71765ca8e67332e5829e62cb9466c176af23", size = 254051, upload-time = "2026-02-03T14:00:27.474Z" }, + { url = "https://files.pythonhosted.org/packages/4a/0a/eb30f6455d04c5a3396d0696cad2df0269ae7444bb322f86ffe3376f7bf9/coverage-7.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b607a40cba795cfac6d130220d25962931ce101f2f478a29822b19755377fb34", size = 255160, upload-time = "2026-02-03T14:00:29.024Z" }, + { url = "https://files.pythonhosted.org/packages/7b/7e/a45baac86274ce3ed842dbb84f14560c673ad30535f397d89164ec56c5df/coverage-7.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:44f14a62f5da2e9aedf9080e01d2cda61df39197d48e323538ec037336d68da8", size = 251709, upload-time = "2026-02-03T14:00:30.641Z" }, + { url = "https://files.pythonhosted.org/packages/c0/df/dd0dc12f30da11349993f3e218901fdf82f45ee44773596050c8f5a1fb25/coverage-7.13.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:debf29e0b157769843dff0981cc76f79e0ed04e36bb773c6cac5f6029054bd8a", size = 253083, upload-time = "2026-02-03T14:00:32.14Z" }, + { url = "https://files.pythonhosted.org/packages/ab/32/fc764c8389a8ce95cb90eb97af4c32f392ab0ac23ec57cadeefb887188d3/coverage-7.13.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:824bb95cd71604031ae9a48edb91fd6effde669522f960375668ed21b36e3ec4", size = 251227, upload-time = "2026-02-03T14:00:34.721Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ca/d025e9da8f06f24c34d2da9873957cfc5f7e0d67802c3e34d0caa8452130/coverage-7.13.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8f1010029a5b52dc427c8e2a8dbddb2303ddd180b806687d1acd1bb1d06649e7", size = 250794, upload-time = "2026-02-03T14:00:36.278Z" }, + { url = "https://files.pythonhosted.org/packages/45/c7/76bf35d5d488ec8f68682eb8e7671acc50a6d2d1c1182de1d2b6d4ffad3b/coverage-7.13.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cd5dee4fd7659d8306ffa79eeaaafd91fa30a302dac3af723b9b469e549247e0", size = 252671, upload-time = "2026-02-03T14:00:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/bf/10/1921f1a03a7c209e1cb374f81a6b9b68b03cdb3ecc3433c189bc90e2a3d5/coverage-7.13.3-cp312-cp312-win32.whl", hash = "sha256:f7f153d0184d45f3873b3ad3ad22694fd73aadcb8cdbc4337ab4b41ea6b4dff1", size = 221986, upload-time = "2026-02-03T14:00:40.442Z" }, + { url = "https://files.pythonhosted.org/packages/3c/7c/f5d93297f8e125a80c15545edc754d93e0ed8ba255b65e609b185296af01/coverage-7.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:03a6e5e1e50819d6d7436f5bc40c92ded7e484e400716886ac921e35c133149d", size = 222793, upload-time = "2026-02-03T14:00:42.106Z" }, + { url = "https://files.pythonhosted.org/packages/43/59/c86b84170015b4555ebabca8649bdf9f4a1f737a73168088385ed0f947c4/coverage-7.13.3-cp312-cp312-win_arm64.whl", hash = "sha256:51c4c42c0e7d09a822b08b6cf79b3c4db8333fffde7450da946719ba0d45730f", size = 221410, upload-time = "2026-02-03T14:00:43.726Z" }, + { url = "https://files.pythonhosted.org/packages/81/f3/4c333da7b373e8c8bfb62517e8174a01dcc373d7a9083698e3b39d50d59c/coverage-7.13.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:853c3d3c79ff0db65797aad79dee6be020efd218ac4510f15a205f1e8d13ce25", size = 219468, upload-time = "2026-02-03T14:00:45.829Z" }, + { url = "https://files.pythonhosted.org/packages/d6/31/0714337b7d23630c8de2f4d56acf43c65f8728a45ed529b34410683f7217/coverage-7.13.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f75695e157c83d374f88dcc646a60cb94173304a9258b2e74ba5a66b7614a51a", size = 219839, upload-time = "2026-02-03T14:00:47.407Z" }, + { url = "https://files.pythonhosted.org/packages/12/99/bd6f2a2738144c98945666f90cae446ed870cecf0421c767475fcf42cdbe/coverage-7.13.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2d098709621d0819039f3f1e471ee554f55a0b2ac0d816883c765b14129b5627", size = 250828, upload-time = "2026-02-03T14:00:49.029Z" }, + { url = "https://files.pythonhosted.org/packages/6f/99/97b600225fbf631e6f5bfd3ad5bcaf87fbb9e34ff87492e5a572ff01bbe2/coverage-7.13.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16d23d6579cf80a474ad160ca14d8b319abaa6db62759d6eef53b2fc979b58c8", size = 253432, upload-time = "2026-02-03T14:00:50.655Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5c/abe2b3490bda26bd4f5e3e799be0bdf00bd81edebedc2c9da8d3ef288fa8/coverage-7.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00d34b29a59d2076e6f318b30a00a69bf63687e30cd882984ed444e753990cc1", size = 254672, upload-time = "2026-02-03T14:00:52.757Z" }, + { url = "https://files.pythonhosted.org/packages/31/ba/5d1957c76b40daff53971fe0adb84d9c2162b614280031d1d0653dd010c1/coverage-7.13.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ab6d72bffac9deb6e6cb0f61042e748de3f9f8e98afb0375a8e64b0b6e11746b", size = 251050, upload-time = "2026-02-03T14:00:54.332Z" }, + { url = "https://files.pythonhosted.org/packages/69/dc/dffdf3bfe9d32090f047d3c3085378558cb4eb6778cda7de414ad74581ed/coverage-7.13.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e129328ad1258e49cae0123a3b5fcb93d6c2fa90d540f0b4c7cdcdc019aaa3dc", size = 252801, upload-time = "2026-02-03T14:00:56.121Z" }, + { url = "https://files.pythonhosted.org/packages/87/51/cdf6198b0f2746e04511a30dc9185d7b8cdd895276c07bdb538e37f1cd50/coverage-7.13.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2213a8d88ed35459bda71597599d4eec7c2ebad201c88f0bfc2c26fd9b0dd2ea", size = 250763, upload-time = "2026-02-03T14:00:58.719Z" }, + { url = "https://files.pythonhosted.org/packages/d7/1a/596b7d62218c1d69f2475b69cc6b211e33c83c902f38ee6ae9766dd422da/coverage-7.13.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:00dd3f02de6d5f5c9c3d95e3e036c3c2e2a669f8bf2d3ceb92505c4ce7838f67", size = 250587, upload-time = "2026-02-03T14:01:01.197Z" }, + { url = "https://files.pythonhosted.org/packages/f7/46/52330d5841ff660f22c130b75f5e1dd3e352c8e7baef5e5fef6b14e3e991/coverage-7.13.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f9bada7bc660d20b23d7d312ebe29e927b655cf414dadcdb6335a2075695bd86", size = 252358, upload-time = "2026-02-03T14:01:02.824Z" }, + { url = "https://files.pythonhosted.org/packages/36/8a/e69a5be51923097ba7d5cff9724466e74fe486e9232020ba97c809a8b42b/coverage-7.13.3-cp313-cp313-win32.whl", hash = "sha256:75b3c0300f3fa15809bd62d9ca8b170eb21fcf0100eb4b4154d6dc8b3a5bbd43", size = 222007, upload-time = "2026-02-03T14:01:04.876Z" }, + { url = "https://files.pythonhosted.org/packages/0a/09/a5a069bcee0d613bdd48ee7637fa73bc09e7ed4342b26890f2df97cc9682/coverage-7.13.3-cp313-cp313-win_amd64.whl", hash = "sha256:a2f7589c6132c44c53f6e705e1a6677e2b7821378c22f7703b2cf5388d0d4587", size = 222812, upload-time = "2026-02-03T14:01:07.296Z" }, + { url = "https://files.pythonhosted.org/packages/3d/4f/d62ad7dfe32f9e3d4a10c178bb6f98b10b083d6e0530ca202b399371f6c1/coverage-7.13.3-cp313-cp313-win_arm64.whl", hash = "sha256:123ceaf2b9d8c614f01110f908a341e05b1b305d6b2ada98763b9a5a59756051", size = 221433, upload-time = "2026-02-03T14:01:09.156Z" }, + { url = "https://files.pythonhosted.org/packages/04/b2/4876c46d723d80b9c5b695f1a11bf5f7c3dabf540ec00d6edc076ff025e6/coverage-7.13.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:cc7fd0f726795420f3678ac82ff882c7fc33770bd0074463b5aef7293285ace9", size = 220162, upload-time = "2026-02-03T14:01:11.409Z" }, + { url = "https://files.pythonhosted.org/packages/fc/04/9942b64a0e0bdda2c109f56bda42b2a59d9d3df4c94b85a323c1cae9fc77/coverage-7.13.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d358dc408edc28730aed5477a69338e444e62fba0b7e9e4a131c505fadad691e", size = 220510, upload-time = "2026-02-03T14:01:13.038Z" }, + { url = "https://files.pythonhosted.org/packages/5a/82/5cfe1e81eae525b74669f9795f37eb3edd4679b873d79d1e6c1c14ee6c1c/coverage-7.13.3-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5d67b9ed6f7b5527b209b24b3df9f2e5bf0198c1bbf99c6971b0e2dcb7e2a107", size = 261801, upload-time = "2026-02-03T14:01:14.674Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ec/a553d7f742fd2cd12e36a16a7b4b3582d5934b496ef2b5ea8abeb10903d4/coverage-7.13.3-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:59224bfb2e9b37c1335ae35d00daa3a5b4e0b1a20f530be208fff1ecfa436f43", size = 263882, upload-time = "2026-02-03T14:01:16.343Z" }, + { url = "https://files.pythonhosted.org/packages/e1/58/8f54a2a93e3d675635bc406de1c9ac8d551312142ff52c9d71b5e533ad45/coverage-7.13.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9306b5299e31e31e0d3b908c66bcb6e7e3ddca143dea0266e9ce6c667346d3", size = 266306, upload-time = "2026-02-03T14:01:18.02Z" }, + { url = "https://files.pythonhosted.org/packages/1a/be/e593399fd6ea1f00aee79ebd7cc401021f218d34e96682a92e1bae092ff6/coverage-7.13.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:343aaeb5f8bb7bcd38620fd7bc56e6ee8207847d8c6103a1e7b72322d381ba4a", size = 261051, upload-time = "2026-02-03T14:01:19.757Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e5/e9e0f6138b21bcdebccac36fbfde9cf15eb1bbcea9f5b1f35cd1f465fb91/coverage-7.13.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b2182129f4c101272ff5f2f18038d7b698db1bf8e7aa9e615cb48440899ad32e", size = 263868, upload-time = "2026-02-03T14:01:21.487Z" }, + { url = "https://files.pythonhosted.org/packages/9a/bf/de72cfebb69756f2d4a2dde35efcc33c47d85cd3ebdf844b3914aac2ef28/coverage-7.13.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:94d2ac94bd0cc57c5626f52f8c2fffed1444b5ae8c9fc68320306cc2b255e155", size = 261498, upload-time = "2026-02-03T14:01:23.097Z" }, + { url = "https://files.pythonhosted.org/packages/f2/91/4a2d313a70fc2e98ca53afd1c8ce67a89b1944cd996589a5b1fe7fbb3e5c/coverage-7.13.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:65436cde5ecabe26fb2f0bf598962f0a054d3f23ad529361326ac002c61a2a1e", size = 260394, upload-time = "2026-02-03T14:01:24.949Z" }, + { url = "https://files.pythonhosted.org/packages/40/83/25113af7cf6941e779eb7ed8de2a677865b859a07ccee9146d4cc06a03e3/coverage-7.13.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:db83b77f97129813dbd463a67e5335adc6a6a91db652cc085d60c2d512746f96", size = 262579, upload-time = "2026-02-03T14:01:26.703Z" }, + { url = "https://files.pythonhosted.org/packages/1e/19/a5f2b96262977e82fb9aabbe19b4d83561f5d063f18dde3e72f34ffc3b2f/coverage-7.13.3-cp313-cp313t-win32.whl", hash = "sha256:dfb428e41377e6b9ba1b0a32df6db5409cb089a0ed1d0a672dc4953ec110d84f", size = 222679, upload-time = "2026-02-03T14:01:28.553Z" }, + { url = "https://files.pythonhosted.org/packages/81/82/ef1747b88c87a5c7d7edc3704799ebd650189a9158e680a063308b6125ef/coverage-7.13.3-cp313-cp313t-win_amd64.whl", hash = "sha256:5badd7e596e6b0c89aa8ec6d37f4473e4357f982ce57f9a2942b0221cd9cf60c", size = 223740, upload-time = "2026-02-03T14:01:30.776Z" }, + { url = "https://files.pythonhosted.org/packages/1c/4c/a67c7bb5b560241c22736a9cb2f14c5034149ffae18630323fde787339e4/coverage-7.13.3-cp313-cp313t-win_arm64.whl", hash = "sha256:989aa158c0eb19d83c76c26f4ba00dbb272485c56e452010a3450bdbc9daafd9", size = 221996, upload-time = "2026-02-03T14:01:32.495Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b3/677bb43427fed9298905106f39c6520ac75f746f81b8f01104526a8026e4/coverage-7.13.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c6f6169bbdbdb85aab8ac0392d776948907267fcc91deeacf6f9d55f7a83ae3b", size = 219513, upload-time = "2026-02-03T14:01:34.29Z" }, + { url = "https://files.pythonhosted.org/packages/42/53/290046e3bbf8986cdb7366a42dab3440b9983711eaff044a51b11006c67b/coverage-7.13.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2f5e731627a3d5ef11a2a35aa0c6f7c435867c7ccbc391268eb4f2ca5dbdcc10", size = 219850, upload-time = "2026-02-03T14:01:35.984Z" }, + { url = "https://files.pythonhosted.org/packages/ea/2b/ab41f10345ba2e49d5e299be8663be2b7db33e77ac1b85cd0af985ea6406/coverage-7.13.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9db3a3285d91c0b70fab9f39f0a4aa37d375873677efe4e71e58d8321e8c5d39", size = 250886, upload-time = "2026-02-03T14:01:38.287Z" }, + { url = "https://files.pythonhosted.org/packages/72/2d/b3f6913ee5a1d5cdd04106f257e5fac5d048992ffc2d9995d07b0f17739f/coverage-7.13.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:06e49c5897cb12e3f7ecdc111d44e97c4f6d0557b81a7a0204ed70a8b038f86f", size = 253393, upload-time = "2026-02-03T14:01:40.118Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f6/b1f48810ffc6accf49a35b9943636560768f0812330f7456aa87dc39aff5/coverage-7.13.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb25061a66802df9fc13a9ba1967d25faa4dae0418db469264fd9860a921dde4", size = 254740, upload-time = "2026-02-03T14:01:42.413Z" }, + { url = "https://files.pythonhosted.org/packages/57/d0/e59c54f9be0b61808f6bc4c8c4346bd79f02dd6bbc3f476ef26124661f20/coverage-7.13.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:99fee45adbb1caeb914da16f70e557fb7ff6ddc9e4b14de665bd41af631367ef", size = 250905, upload-time = "2026-02-03T14:01:44.163Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f7/5291bcdf498bafbee3796bb32ef6966e9915aebd4d0954123c8eae921c32/coverage-7.13.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:318002f1fd819bdc1651c619268aa5bc853c35fa5cc6d1e8c96bd9cd6c828b75", size = 252753, upload-time = "2026-02-03T14:01:45.974Z" }, + { url = "https://files.pythonhosted.org/packages/a0/a9/1dcafa918c281554dae6e10ece88c1add82db685be123e1b05c2056ff3fb/coverage-7.13.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:71295f2d1d170b9977dc386d46a7a1b7cbb30e5405492529b4c930113a33f895", size = 250716, upload-time = "2026-02-03T14:01:48.844Z" }, + { url = "https://files.pythonhosted.org/packages/44/bb/4ea4eabcce8c4f6235df6e059fbc5db49107b24c4bdffc44aee81aeca5a8/coverage-7.13.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5b1ad2e0dc672625c44bc4fe34514602a9fd8b10d52ddc414dc585f74453516c", size = 250530, upload-time = "2026-02-03T14:01:50.793Z" }, + { url = "https://files.pythonhosted.org/packages/6d/31/4a6c9e6a71367e6f923b27b528448c37f4e959b7e4029330523014691007/coverage-7.13.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b2beb64c145593a50d90db5c7178f55daeae129123b0d265bdb3cbec83e5194a", size = 252186, upload-time = "2026-02-03T14:01:52.607Z" }, + { url = "https://files.pythonhosted.org/packages/27/92/e1451ef6390a4f655dc42da35d9971212f7abbbcad0bdb7af4407897eb76/coverage-7.13.3-cp314-cp314-win32.whl", hash = "sha256:3d1aed4f4e837a832df2f3b4f68a690eede0de4560a2dbc214ea0bc55aabcdb4", size = 222253, upload-time = "2026-02-03T14:01:55.071Z" }, + { url = "https://files.pythonhosted.org/packages/8a/98/78885a861a88de020c32a2693487c37d15a9873372953f0c3c159d575a43/coverage-7.13.3-cp314-cp314-win_amd64.whl", hash = "sha256:9f9efbbaf79f935d5fbe3ad814825cbce4f6cdb3054384cb49f0c0f496125fa0", size = 223069, upload-time = "2026-02-03T14:01:56.95Z" }, + { url = "https://files.pythonhosted.org/packages/eb/fb/3784753a48da58a5337972abf7ca58b1fb0f1bda21bc7b4fae992fd28e47/coverage-7.13.3-cp314-cp314-win_arm64.whl", hash = "sha256:31b6e889c53d4e6687ca63706148049494aace140cffece1c4dc6acadb70a7b3", size = 221633, upload-time = "2026-02-03T14:01:58.758Z" }, + { url = "https://files.pythonhosted.org/packages/40/f9/75b732d9674d32cdbffe801ed5f770786dd1c97eecedef2125b0d25102dc/coverage-7.13.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c5e9787cec750793a19a28df7edd85ac4e49d3fb91721afcdc3b86f6c08d9aa8", size = 220243, upload-time = "2026-02-03T14:02:01.109Z" }, + { url = "https://files.pythonhosted.org/packages/cf/7e/2868ec95de5a65703e6f0c87407ea822d1feb3619600fbc3c1c4fa986090/coverage-7.13.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e5b86db331c682fd0e4be7098e6acee5e8a293f824d41487c667a93705d415ca", size = 220515, upload-time = "2026-02-03T14:02:02.862Z" }, + { url = "https://files.pythonhosted.org/packages/7d/eb/9f0d349652fced20bcaea0f67fc5777bd097c92369f267975732f3dc5f45/coverage-7.13.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:edc7754932682d52cf6e7a71806e529ecd5ce660e630e8bd1d37109a2e5f63ba", size = 261874, upload-time = "2026-02-03T14:02:04.727Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a5/6619bc4a6c7b139b16818149a3e74ab2e21599ff9a7b6811b6afde99f8ec/coverage-7.13.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3a16d6398666510a6886f67f43d9537bfd0e13aca299688a19daa84f543122f", size = 264004, upload-time = "2026-02-03T14:02:06.634Z" }, + { url = "https://files.pythonhosted.org/packages/29/b7/90aa3fc645a50c6f07881fca4fd0ba21e3bfb6ce3a7078424ea3a35c74c9/coverage-7.13.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:303d38b19626c1981e1bb067a9928236d88eb0e4479b18a74812f05a82071508", size = 266408, upload-time = "2026-02-03T14:02:09.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/55/08bb2a1e4dcbae384e638f0effef486ba5987b06700e481691891427d879/coverage-7.13.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:284e06eadfe15ddfee2f4ee56631f164ef897a7d7d5a15bca5f0bb88889fc5ba", size = 260977, upload-time = "2026-02-03T14:02:11.755Z" }, + { url = "https://files.pythonhosted.org/packages/9b/76/8bd4ae055a42d8fb5dd2230e5cf36ff2e05f85f2427e91b11a27fea52ed7/coverage-7.13.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d401f0864a1d3198422816878e4e84ca89ec1c1bf166ecc0ae01380a39b888cd", size = 263868, upload-time = "2026-02-03T14:02:13.565Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f9/ba000560f11e9e32ec03df5aa8477242c2d95b379c99ac9a7b2e7fbacb1a/coverage-7.13.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3f379b02c18a64de78c4ccdddf1c81c2c5ae1956c72dacb9133d7dd7809794ab", size = 261474, upload-time = "2026-02-03T14:02:16.069Z" }, + { url = "https://files.pythonhosted.org/packages/90/4b/4de4de8f9ca7af4733bfcf4baa440121b7dbb3856daf8428ce91481ff63b/coverage-7.13.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:7a482f2da9086971efb12daca1d6547007ede3674ea06e16d7663414445c683e", size = 260317, upload-time = "2026-02-03T14:02:17.996Z" }, + { url = "https://files.pythonhosted.org/packages/05/71/5cd8436e2c21410ff70be81f738c0dddea91bcc3189b1517d26e0102ccb3/coverage-7.13.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:562136b0d401992118d9b49fbee5454e16f95f85b120a4226a04d816e33fe024", size = 262635, upload-time = "2026-02-03T14:02:20.405Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f8/2834bb45bdd70b55a33ec354b8b5f6062fc90e5bb787e14385903a979503/coverage-7.13.3-cp314-cp314t-win32.whl", hash = "sha256:ca46e5c3be3b195098dd88711890b8011a9fa4feca942292bb84714ce5eab5d3", size = 223035, upload-time = "2026-02-03T14:02:22.323Z" }, + { url = "https://files.pythonhosted.org/packages/26/75/f8290f0073c00d9ae14056d2b84ab92dff21d5370e464cb6cb06f52bf580/coverage-7.13.3-cp314-cp314t-win_amd64.whl", hash = "sha256:06d316dbb3d9fd44cca05b2dbcfbef22948493d63a1f28e828d43e6cc505fed8", size = 224142, upload-time = "2026-02-03T14:02:24.143Z" }, + { url = "https://files.pythonhosted.org/packages/03/01/43ac78dfea8946c4a9161bbc034b5549115cb2b56781a4b574927f0d141a/coverage-7.13.3-cp314-cp314t-win_arm64.whl", hash = "sha256:299d66e9218193f9dc6e4880629ed7c4cd23486005166247c283fb98531656c3", size = 222166, upload-time = "2026-02-03T14:02:26.005Z" }, + { url = "https://files.pythonhosted.org/packages/7d/fb/70af542d2d938c778c9373ce253aa4116dbe7c0a5672f78b2b2ae0e1b94b/coverage-7.13.3-py3-none-any.whl", hash = "sha256:90a8af9dba6429b2573199622d72e0ebf024d6276f16abce394ad4d181bb0910", size = 211237, upload-time = "2026-02-03T14:02:27.986Z" }, ] [package.optional-dependencies] @@ -847,11 +847,11 @@ wheels = [ [[package]] name = "fsspec" -version = "2026.1.0" +version = "2026.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d5/7d/5df2650c57d47c57232af5ef4b4fdbff182070421e405e0d62c6cdbfaa87/fsspec-2026.1.0.tar.gz", hash = "sha256:e987cb0496a0d81bba3a9d1cee62922fb395e7d4c3b575e57f547953334fe07b", size = 310496, upload-time = "2026-01-09T15:21:35.562Z" } +sdist = { url = "https://files.pythonhosted.org/packages/51/7c/f60c259dcbf4f0c47cc4ddb8f7720d2dcdc8888c8e5ad84c73ea4531cc5b/fsspec-2026.2.0.tar.gz", hash = "sha256:6544e34b16869f5aacd5b90bdf1a71acb37792ea3ddf6125ee69a22a53fb8bff", size = 313441, upload-time = "2026-02-05T21:50:53.743Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/c9/97cc5aae1648dcb851958a3ddf73ccd7dbe5650d95203ecb4d7720b4cdbf/fsspec-2026.1.0-py3-none-any.whl", hash = "sha256:cb76aa913c2285a3b49bdd5fc55b1d7c708d7208126b60f2eb8194fe1b4cbdcc", size = 201838, upload-time = "2026-01-09T15:21:34.041Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl", hash = "sha256:98de475b5cb3bd66bedd5c4679e87b4fdfe1a3bf4d707b151b3c07e58c9a2437", size = 202505, upload-time = "2026-02-05T21:50:51.819Z" }, ] [[package]] @@ -933,53 +933,53 @@ wheels = [ [[package]] name = "grpcio" -version = "1.76.0" +version = "1.78.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b6/e0/318c1ce3ae5a17894d5791e87aea147587c9e702f24122cc7a5c8bbaeeb1/grpcio-1.76.0.tar.gz", hash = "sha256:7be78388d6da1a25c0d5ec506523db58b18be22d9c37d8d3a32c08be4987bd73", size = 12785182, upload-time = "2025-10-21T16:23:12.106Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/00/8163a1beeb6971f66b4bbe6ac9457b97948beba8dd2fc8e1281dce7f79ec/grpcio-1.76.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:2e1743fbd7f5fa713a1b0a8ac8ebabf0ec980b5d8809ec358d488e273b9cf02a", size = 5843567, upload-time = "2025-10-21T16:20:52.829Z" }, - { url = "https://files.pythonhosted.org/packages/10/c1/934202f5cf335e6d852530ce14ddb0fef21be612ba9ecbbcbd4d748ca32d/grpcio-1.76.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:a8c2cf1209497cf659a667d7dea88985e834c24b7c3b605e6254cbb5076d985c", size = 11848017, upload-time = "2025-10-21T16:20:56.705Z" }, - { url = "https://files.pythonhosted.org/packages/11/0b/8dec16b1863d74af6eb3543928600ec2195af49ca58b16334972f6775663/grpcio-1.76.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:08caea849a9d3c71a542827d6df9d5a69067b0a1efbea8a855633ff5d9571465", size = 6412027, upload-time = "2025-10-21T16:20:59.3Z" }, - { url = "https://files.pythonhosted.org/packages/d7/64/7b9e6e7ab910bea9d46f2c090380bab274a0b91fb0a2fe9b0cd399fffa12/grpcio-1.76.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f0e34c2079d47ae9f6188211db9e777c619a21d4faba6977774e8fa43b085e48", size = 7075913, upload-time = "2025-10-21T16:21:01.645Z" }, - { url = "https://files.pythonhosted.org/packages/68/86/093c46e9546073cefa789bd76d44c5cb2abc824ca62af0c18be590ff13ba/grpcio-1.76.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8843114c0cfce61b40ad48df65abcfc00d4dba82eae8718fab5352390848c5da", size = 6615417, upload-time = "2025-10-21T16:21:03.844Z" }, - { url = "https://files.pythonhosted.org/packages/f7/b6/5709a3a68500a9c03da6fb71740dcdd5ef245e39266461a03f31a57036d8/grpcio-1.76.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8eddfb4d203a237da6f3cc8a540dad0517d274b5a1e9e636fd8d2c79b5c1d397", size = 7199683, upload-time = "2025-10-21T16:21:06.195Z" }, - { url = "https://files.pythonhosted.org/packages/91/d3/4b1f2bf16ed52ce0b508161df3a2d186e4935379a159a834cb4a7d687429/grpcio-1.76.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:32483fe2aab2c3794101c2a159070584e5db11d0aa091b2c0ea9c4fc43d0d749", size = 8163109, upload-time = "2025-10-21T16:21:08.498Z" }, - { url = "https://files.pythonhosted.org/packages/5c/61/d9043f95f5f4cf085ac5dd6137b469d41befb04bd80280952ffa2a4c3f12/grpcio-1.76.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dcfe41187da8992c5f40aa8c5ec086fa3672834d2be57a32384c08d5a05b4c00", size = 7626676, upload-time = "2025-10-21T16:21:10.693Z" }, - { url = "https://files.pythonhosted.org/packages/36/95/fd9a5152ca02d8881e4dd419cdd790e11805979f499a2e5b96488b85cf27/grpcio-1.76.0-cp311-cp311-win32.whl", hash = "sha256:2107b0c024d1b35f4083f11245c0e23846ae64d02f40b2b226684840260ed054", size = 3997688, upload-time = "2025-10-21T16:21:12.746Z" }, - { url = "https://files.pythonhosted.org/packages/60/9c/5c359c8d4c9176cfa3c61ecd4efe5affe1f38d9bae81e81ac7186b4c9cc8/grpcio-1.76.0-cp311-cp311-win_amd64.whl", hash = "sha256:522175aba7af9113c48ec10cc471b9b9bd4f6ceb36aeb4544a8e2c80ed9d252d", size = 4709315, upload-time = "2025-10-21T16:21:15.26Z" }, - { url = "https://files.pythonhosted.org/packages/bf/05/8e29121994b8d959ffa0afd28996d452f291b48cfc0875619de0bde2c50c/grpcio-1.76.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:81fd9652b37b36f16138611c7e884eb82e0cec137c40d3ef7c3f9b3ed00f6ed8", size = 5799718, upload-time = "2025-10-21T16:21:17.939Z" }, - { url = "https://files.pythonhosted.org/packages/d9/75/11d0e66b3cdf998c996489581bdad8900db79ebd83513e45c19548f1cba4/grpcio-1.76.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:04bbe1bfe3a68bbfd4e52402ab7d4eb59d72d02647ae2042204326cf4bbad280", size = 11825627, upload-time = "2025-10-21T16:21:20.466Z" }, - { url = "https://files.pythonhosted.org/packages/28/50/2f0aa0498bc188048f5d9504dcc5c2c24f2eb1a9337cd0fa09a61a2e75f0/grpcio-1.76.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d388087771c837cdb6515539f43b9d4bf0b0f23593a24054ac16f7a960be16f4", size = 6359167, upload-time = "2025-10-21T16:21:23.122Z" }, - { url = "https://files.pythonhosted.org/packages/66/e5/bbf0bb97d29ede1d59d6588af40018cfc345b17ce979b7b45424628dc8bb/grpcio-1.76.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9f8f757bebaaea112c00dba718fc0d3260052ce714e25804a03f93f5d1c6cc11", size = 7044267, upload-time = "2025-10-21T16:21:25.995Z" }, - { url = "https://files.pythonhosted.org/packages/f5/86/f6ec2164f743d9609691115ae8ece098c76b894ebe4f7c94a655c6b03e98/grpcio-1.76.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:980a846182ce88c4f2f7e2c22c56aefd515daeb36149d1c897f83cf57999e0b6", size = 6573963, upload-time = "2025-10-21T16:21:28.631Z" }, - { url = "https://files.pythonhosted.org/packages/60/bc/8d9d0d8505feccfdf38a766d262c71e73639c165b311c9457208b56d92ae/grpcio-1.76.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f92f88e6c033db65a5ae3d97905c8fea9c725b63e28d5a75cb73b49bda5024d8", size = 7164484, upload-time = "2025-10-21T16:21:30.837Z" }, - { url = "https://files.pythonhosted.org/packages/67/e6/5d6c2fc10b95edf6df9b8f19cf10a34263b7fd48493936fffd5085521292/grpcio-1.76.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4baf3cbe2f0be3289eb68ac8ae771156971848bb8aaff60bad42005539431980", size = 8127777, upload-time = "2025-10-21T16:21:33.577Z" }, - { url = "https://files.pythonhosted.org/packages/3f/c8/dce8ff21c86abe025efe304d9e31fdb0deaaa3b502b6a78141080f206da0/grpcio-1.76.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:615ba64c208aaceb5ec83bfdce7728b80bfeb8be97562944836a7a0a9647d882", size = 7594014, upload-time = "2025-10-21T16:21:41.882Z" }, - { url = "https://files.pythonhosted.org/packages/e0/42/ad28191ebf983a5d0ecef90bab66baa5a6b18f2bfdef9d0a63b1973d9f75/grpcio-1.76.0-cp312-cp312-win32.whl", hash = "sha256:45d59a649a82df5718fd9527ce775fd66d1af35e6d31abdcdc906a49c6822958", size = 3984750, upload-time = "2025-10-21T16:21:44.006Z" }, - { url = "https://files.pythonhosted.org/packages/9e/00/7bd478cbb851c04a48baccaa49b75abaa8e4122f7d86da797500cccdd771/grpcio-1.76.0-cp312-cp312-win_amd64.whl", hash = "sha256:c088e7a90b6017307f423efbb9d1ba97a22aa2170876223f9709e9d1de0b5347", size = 4704003, upload-time = "2025-10-21T16:21:46.244Z" }, - { url = "https://files.pythonhosted.org/packages/fc/ed/71467ab770effc9e8cef5f2e7388beb2be26ed642d567697bb103a790c72/grpcio-1.76.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:26ef06c73eb53267c2b319f43e6634c7556ea37672029241a056629af27c10e2", size = 5807716, upload-time = "2025-10-21T16:21:48.475Z" }, - { url = "https://files.pythonhosted.org/packages/2c/85/c6ed56f9817fab03fa8a111ca91469941fb514e3e3ce6d793cb8f1e1347b/grpcio-1.76.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:45e0111e73f43f735d70786557dc38141185072d7ff8dc1829d6a77ac1471468", size = 11821522, upload-time = "2025-10-21T16:21:51.142Z" }, - { url = "https://files.pythonhosted.org/packages/ac/31/2b8a235ab40c39cbc141ef647f8a6eb7b0028f023015a4842933bc0d6831/grpcio-1.76.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:83d57312a58dcfe2a3a0f9d1389b299438909a02db60e2f2ea2ae2d8034909d3", size = 6362558, upload-time = "2025-10-21T16:21:54.213Z" }, - { url = "https://files.pythonhosted.org/packages/bd/64/9784eab483358e08847498ee56faf8ff6ea8e0a4592568d9f68edc97e9e9/grpcio-1.76.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3e2a27c89eb9ac3d81ec8835e12414d73536c6e620355d65102503064a4ed6eb", size = 7049990, upload-time = "2025-10-21T16:21:56.476Z" }, - { url = "https://files.pythonhosted.org/packages/2b/94/8c12319a6369434e7a184b987e8e9f3b49a114c489b8315f029e24de4837/grpcio-1.76.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61f69297cba3950a524f61c7c8ee12e55c486cb5f7db47ff9dcee33da6f0d3ae", size = 6575387, upload-time = "2025-10-21T16:21:59.051Z" }, - { url = "https://files.pythonhosted.org/packages/15/0f/f12c32b03f731f4a6242f771f63039df182c8b8e2cf8075b245b409259d4/grpcio-1.76.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a15c17af8839b6801d554263c546c69c4d7718ad4321e3166175b37eaacca77", size = 7166668, upload-time = "2025-10-21T16:22:02.049Z" }, - { url = "https://files.pythonhosted.org/packages/ff/2d/3ec9ce0c2b1d92dd59d1c3264aaec9f0f7c817d6e8ac683b97198a36ed5a/grpcio-1.76.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:25a18e9810fbc7e7f03ec2516addc116a957f8cbb8cbc95ccc80faa072743d03", size = 8124928, upload-time = "2025-10-21T16:22:04.984Z" }, - { url = "https://files.pythonhosted.org/packages/1a/74/fd3317be5672f4856bcdd1a9e7b5e17554692d3db9a3b273879dc02d657d/grpcio-1.76.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:931091142fd8cc14edccc0845a79248bc155425eee9a98b2db2ea4f00a235a42", size = 7589983, upload-time = "2025-10-21T16:22:07.881Z" }, - { url = "https://files.pythonhosted.org/packages/45/bb/ca038cf420f405971f19821c8c15bcbc875505f6ffadafe9ffd77871dc4c/grpcio-1.76.0-cp313-cp313-win32.whl", hash = "sha256:5e8571632780e08526f118f74170ad8d50fb0a48c23a746bef2a6ebade3abd6f", size = 3984727, upload-time = "2025-10-21T16:22:10.032Z" }, - { url = "https://files.pythonhosted.org/packages/41/80/84087dc56437ced7cdd4b13d7875e7439a52a261e3ab4e06488ba6173b0a/grpcio-1.76.0-cp313-cp313-win_amd64.whl", hash = "sha256:f9f7bd5faab55f47231ad8dba7787866b69f5e93bc306e3915606779bbfb4ba8", size = 4702799, upload-time = "2025-10-21T16:22:12.709Z" }, - { url = "https://files.pythonhosted.org/packages/b4/46/39adac80de49d678e6e073b70204091e76631e03e94928b9ea4ecf0f6e0e/grpcio-1.76.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:ff8a59ea85a1f2191a0ffcc61298c571bc566332f82e5f5be1b83c9d8e668a62", size = 5808417, upload-time = "2025-10-21T16:22:15.02Z" }, - { url = "https://files.pythonhosted.org/packages/9c/f5/a4531f7fb8b4e2a60b94e39d5d924469b7a6988176b3422487be61fe2998/grpcio-1.76.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:06c3d6b076e7b593905d04fdba6a0525711b3466f43b3400266f04ff735de0cd", size = 11828219, upload-time = "2025-10-21T16:22:17.954Z" }, - { url = "https://files.pythonhosted.org/packages/4b/1c/de55d868ed7a8bd6acc6b1d6ddc4aa36d07a9f31d33c912c804adb1b971b/grpcio-1.76.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd5ef5932f6475c436c4a55e4336ebbe47bd3272be04964a03d316bbf4afbcbc", size = 6367826, upload-time = "2025-10-21T16:22:20.721Z" }, - { url = "https://files.pythonhosted.org/packages/59/64/99e44c02b5adb0ad13ab3adc89cb33cb54bfa90c74770f2607eea629b86f/grpcio-1.76.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b331680e46239e090f5b3cead313cc772f6caa7d0fc8de349337563125361a4a", size = 7049550, upload-time = "2025-10-21T16:22:23.637Z" }, - { url = "https://files.pythonhosted.org/packages/43/28/40a5be3f9a86949b83e7d6a2ad6011d993cbe9b6bd27bea881f61c7788b6/grpcio-1.76.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2229ae655ec4e8999599469559e97630185fdd53ae1e8997d147b7c9b2b72cba", size = 6575564, upload-time = "2025-10-21T16:22:26.016Z" }, - { url = "https://files.pythonhosted.org/packages/4b/a9/1be18e6055b64467440208a8559afac243c66a8b904213af6f392dc2212f/grpcio-1.76.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:490fa6d203992c47c7b9e4a9d39003a0c2bcc1c9aa3c058730884bbbb0ee9f09", size = 7176236, upload-time = "2025-10-21T16:22:28.362Z" }, - { url = "https://files.pythonhosted.org/packages/0f/55/dba05d3fcc151ce6e81327541d2cc8394f442f6b350fead67401661bf041/grpcio-1.76.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:479496325ce554792dba6548fae3df31a72cef7bad71ca2e12b0e58f9b336bfc", size = 8125795, upload-time = "2025-10-21T16:22:31.075Z" }, - { url = "https://files.pythonhosted.org/packages/4a/45/122df922d05655f63930cf42c9e3f72ba20aadb26c100ee105cad4ce4257/grpcio-1.76.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1c9b93f79f48b03ada57ea24725d83a30284a012ec27eab2cf7e50a550cbbbcc", size = 7592214, upload-time = "2025-10-21T16:22:33.831Z" }, - { url = "https://files.pythonhosted.org/packages/4a/6e/0b899b7f6b66e5af39e377055fb4a6675c9ee28431df5708139df2e93233/grpcio-1.76.0-cp314-cp314-win32.whl", hash = "sha256:747fa73efa9b8b1488a95d0ba1039c8e2dca0f741612d80415b1e1c560febf4e", size = 4062961, upload-time = "2025-10-21T16:22:36.468Z" }, - { url = "https://files.pythonhosted.org/packages/19/41/0b430b01a2eb38ee887f88c1f07644a1df8e289353b78e82b37ef988fb64/grpcio-1.76.0-cp314-cp314-win_amd64.whl", hash = "sha256:922fa70ba549fce362d2e2871ab542082d66e2aaf0c19480ea453905b01f384e", size = 4834462, upload-time = "2025-10-21T16:22:39.772Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/06/8a/3d098f35c143a89520e568e6539cc098fcd294495910e359889ce8741c84/grpcio-1.78.0.tar.gz", hash = "sha256:7382b95189546f375c174f53a5fa873cef91c4b8005faa05cc5b3beea9c4f1c5", size = 12852416, upload-time = "2026-02-06T09:57:18.093Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/c7/d0b780a29b0837bf4ca9580904dfb275c1fc321ded7897d620af7047ec57/grpcio-1.78.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:2777b783f6c13b92bd7b716667452c329eefd646bfb3f2e9dabea2e05dbd34f6", size = 5951525, upload-time = "2026-02-06T09:55:01.989Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b1/96920bf2ee61df85a9503cb6f733fe711c0ff321a5a697d791b075673281/grpcio-1.78.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:9dca934f24c732750389ce49d638069c3892ad065df86cb465b3fa3012b70c9e", size = 11830418, upload-time = "2026-02-06T09:55:04.462Z" }, + { url = "https://files.pythonhosted.org/packages/83/0c/7c1528f098aeb75a97de2bae18c530f56959fb7ad6c882db45d9884d6edc/grpcio-1.78.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:459ab414b35f4496138d0ecd735fed26f1318af5e52cb1efbc82a09f0d5aa911", size = 6524477, upload-time = "2026-02-06T09:55:07.111Z" }, + { url = "https://files.pythonhosted.org/packages/8d/52/e7c1f3688f949058e19a011c4e0dec973da3d0ae5e033909677f967ae1f4/grpcio-1.78.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:082653eecbdf290e6e3e2c276ab2c54b9e7c299e07f4221872380312d8cf395e", size = 7198266, upload-time = "2026-02-06T09:55:10.016Z" }, + { url = "https://files.pythonhosted.org/packages/e5/61/8ac32517c1e856677282c34f2e7812d6c328fa02b8f4067ab80e77fdc9c9/grpcio-1.78.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85f93781028ec63f383f6bc90db785a016319c561cc11151fbb7b34e0d012303", size = 6730552, upload-time = "2026-02-06T09:55:12.207Z" }, + { url = "https://files.pythonhosted.org/packages/bd/98/b8ee0158199250220734f620b12e4a345955ac7329cfd908d0bf0fda77f0/grpcio-1.78.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f12857d24d98441af6a1d5c87442d624411db486f7ba12550b07788f74b67b04", size = 7304296, upload-time = "2026-02-06T09:55:15.044Z" }, + { url = "https://files.pythonhosted.org/packages/bd/0f/7b72762e0d8840b58032a56fdbd02b78fc645b9fa993d71abf04edbc54f4/grpcio-1.78.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5397fff416b79e4b284959642a4e95ac4b0f1ece82c9993658e0e477d40551ec", size = 8288298, upload-time = "2026-02-06T09:55:17.276Z" }, + { url = "https://files.pythonhosted.org/packages/24/ae/ae4ce56bc5bb5caa3a486d60f5f6083ac3469228faa734362487176c15c5/grpcio-1.78.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fbe6e89c7ffb48518384068321621b2a69cab509f58e40e4399fdd378fa6d074", size = 7730953, upload-time = "2026-02-06T09:55:19.545Z" }, + { url = "https://files.pythonhosted.org/packages/b5/6e/8052e3a28eb6a820c372b2eb4b5e32d195c661e137d3eca94d534a4cfd8a/grpcio-1.78.0-cp311-cp311-win32.whl", hash = "sha256:6092beabe1966a3229f599d7088b38dfc8ffa1608b5b5cdda31e591e6500f856", size = 4076503, upload-time = "2026-02-06T09:55:21.521Z" }, + { url = "https://files.pythonhosted.org/packages/08/62/f22c98c5265dfad327251fa2f840b591b1df5f5e15d88b19c18c86965b27/grpcio-1.78.0-cp311-cp311-win_amd64.whl", hash = "sha256:1afa62af6e23f88629f2b29ec9e52ec7c65a7176c1e0a83292b93c76ca882558", size = 4799767, upload-time = "2026-02-06T09:55:24.107Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f4/7384ed0178203d6074446b3c4f46c90a22ddf7ae0b3aee521627f54cfc2a/grpcio-1.78.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:f9ab915a267fc47c7e88c387a3a28325b58c898e23d4995f765728f4e3dedb97", size = 5913985, upload-time = "2026-02-06T09:55:26.832Z" }, + { url = "https://files.pythonhosted.org/packages/81/ed/be1caa25f06594463f685b3790b320f18aea49b33166f4141bfdc2bfb236/grpcio-1.78.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3f8904a8165ab21e07e58bf3e30a73f4dffc7a1e0dbc32d51c61b5360d26f43e", size = 11811853, upload-time = "2026-02-06T09:55:29.224Z" }, + { url = "https://files.pythonhosted.org/packages/24/a7/f06d151afc4e64b7e3cc3e872d331d011c279aaab02831e40a81c691fb65/grpcio-1.78.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:859b13906ce098c0b493af92142ad051bf64c7870fa58a123911c88606714996", size = 6475766, upload-time = "2026-02-06T09:55:31.825Z" }, + { url = "https://files.pythonhosted.org/packages/8a/a8/4482922da832ec0082d0f2cc3a10976d84a7424707f25780b82814aafc0a/grpcio-1.78.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b2342d87af32790f934a79c3112641e7b27d63c261b8b4395350dad43eff1dc7", size = 7170027, upload-time = "2026-02-06T09:55:34.7Z" }, + { url = "https://files.pythonhosted.org/packages/54/bf/f4a3b9693e35d25b24b0b39fa46d7d8a3c439e0a3036c3451764678fec20/grpcio-1.78.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12a771591ae40bc65ba67048fa52ef4f0e6db8279e595fd349f9dfddeef571f9", size = 6690766, upload-time = "2026-02-06T09:55:36.902Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b9/521875265cc99fe5ad4c5a17010018085cae2810a928bf15ebe7d8bcd9cc/grpcio-1.78.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:185dea0d5260cbb2d224c507bf2a5444d5abbb1fa3594c1ed7e4c709d5eb8383", size = 7266161, upload-time = "2026-02-06T09:55:39.824Z" }, + { url = "https://files.pythonhosted.org/packages/05/86/296a82844fd40a4ad4a95f100b55044b4f817dece732bf686aea1a284147/grpcio-1.78.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51b13f9aed9d59ee389ad666b8c2214cc87b5de258fa712f9ab05f922e3896c6", size = 8253303, upload-time = "2026-02-06T09:55:42.353Z" }, + { url = "https://files.pythonhosted.org/packages/f3/e4/ea3c0caf5468537f27ad5aab92b681ed7cc0ef5f8c9196d3fd42c8c2286b/grpcio-1.78.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd5f135b1bd58ab088930b3c613455796dfa0393626a6972663ccdda5b4ac6ce", size = 7698222, upload-time = "2026-02-06T09:55:44.629Z" }, + { url = "https://files.pythonhosted.org/packages/d7/47/7f05f81e4bb6b831e93271fb12fd52ba7b319b5402cbc101d588f435df00/grpcio-1.78.0-cp312-cp312-win32.whl", hash = "sha256:94309f498bcc07e5a7d16089ab984d42ad96af1d94b5a4eb966a266d9fcabf68", size = 4066123, upload-time = "2026-02-06T09:55:47.644Z" }, + { url = "https://files.pythonhosted.org/packages/ad/e7/d6914822c88aa2974dbbd10903d801a28a19ce9cd8bad7e694cbbcf61528/grpcio-1.78.0-cp312-cp312-win_amd64.whl", hash = "sha256:9566fe4ababbb2610c39190791e5b829869351d14369603702e890ef3ad2d06e", size = 4797657, upload-time = "2026-02-06T09:55:49.86Z" }, + { url = "https://files.pythonhosted.org/packages/05/a9/8f75894993895f361ed8636cd9237f4ab39ef87fd30db17467235ed1c045/grpcio-1.78.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:ce3a90455492bf8bfa38e56fbbe1dbd4f872a3d8eeaf7337dc3b1c8aa28c271b", size = 5920143, upload-time = "2026-02-06T09:55:52.035Z" }, + { url = "https://files.pythonhosted.org/packages/55/06/0b78408e938ac424100100fd081189451b472236e8a3a1f6500390dc4954/grpcio-1.78.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:2bf5e2e163b356978b23652c4818ce4759d40f4712ee9ec5a83c4be6f8c23a3a", size = 11803926, upload-time = "2026-02-06T09:55:55.494Z" }, + { url = "https://files.pythonhosted.org/packages/88/93/b59fe7832ff6ae3c78b813ea43dac60e295fa03606d14d89d2e0ec29f4f3/grpcio-1.78.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8f2ac84905d12918e4e55a16da17939eb63e433dc11b677267c35568aa63fc84", size = 6478628, upload-time = "2026-02-06T09:55:58.533Z" }, + { url = "https://files.pythonhosted.org/packages/ed/df/e67e3734527f9926b7d9c0dde6cd998d1d26850c3ed8eeec81297967ac67/grpcio-1.78.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b58f37edab4a3881bc6c9bca52670610e0c9ca14e2ea3cf9debf185b870457fb", size = 7173574, upload-time = "2026-02-06T09:56:01.786Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/cc03fffb07bfba982a9ec097b164e8835546980aec25ecfa5f9c1a47e022/grpcio-1.78.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:735e38e176a88ce41840c21bb49098ab66177c64c82426e24e0082500cc68af5", size = 6692639, upload-time = "2026-02-06T09:56:04.529Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9a/289c32e301b85bdb67d7ec68b752155e674ee3ba2173a1858f118e399ef3/grpcio-1.78.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2045397e63a7a0ee7957c25f7dbb36ddc110e0cfb418403d110c0a7a68a844e9", size = 7268838, upload-time = "2026-02-06T09:56:08.397Z" }, + { url = "https://files.pythonhosted.org/packages/0e/79/1be93f32add280461fa4773880196572563e9c8510861ac2da0ea0f892b6/grpcio-1.78.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a9f136fbafe7ccf4ac7e8e0c28b31066e810be52d6e344ef954a3a70234e1702", size = 8251878, upload-time = "2026-02-06T09:56:10.914Z" }, + { url = "https://files.pythonhosted.org/packages/65/65/793f8e95296ab92e4164593674ae6291b204bb5f67f9d4a711489cd30ffa/grpcio-1.78.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:748b6138585379c737adc08aeffd21222abbda1a86a0dca2a39682feb9196c20", size = 7695412, upload-time = "2026-02-06T09:56:13.593Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9f/1e233fe697ecc82845942c2822ed06bb522e70d6771c28d5528e4c50f6a4/grpcio-1.78.0-cp313-cp313-win32.whl", hash = "sha256:271c73e6e5676afe4fc52907686670c7cea22ab2310b76a59b678403ed40d670", size = 4064899, upload-time = "2026-02-06T09:56:15.601Z" }, + { url = "https://files.pythonhosted.org/packages/4d/27/d86b89e36de8a951501fb06a0f38df19853210f341d0b28f83f4aa0ffa08/grpcio-1.78.0-cp313-cp313-win_amd64.whl", hash = "sha256:f2d4e43ee362adfc05994ed479334d5a451ab7bc3f3fee1b796b8ca66895acb4", size = 4797393, upload-time = "2026-02-06T09:56:17.882Z" }, + { url = "https://files.pythonhosted.org/packages/29/f2/b56e43e3c968bfe822fa6ce5bca10d5c723aa40875b48791ce1029bb78c7/grpcio-1.78.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:e87cbc002b6f440482b3519e36e1313eb5443e9e9e73d6a52d43bd2004fcfd8e", size = 5920591, upload-time = "2026-02-06T09:56:20.758Z" }, + { url = "https://files.pythonhosted.org/packages/5d/81/1f3b65bd30c334167bfa8b0d23300a44e2725ce39bba5b76a2460d85f745/grpcio-1.78.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:c41bc64626db62e72afec66b0c8a0da76491510015417c127bfc53b2fe6d7f7f", size = 11813685, upload-time = "2026-02-06T09:56:24.315Z" }, + { url = "https://files.pythonhosted.org/packages/0e/1c/bbe2f8216a5bd3036119c544d63c2e592bdf4a8ec6e4a1867592f4586b26/grpcio-1.78.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8dfffba826efcf366b1e3ccc37e67afe676f290e13a3b48d31a46739f80a8724", size = 6487803, upload-time = "2026-02-06T09:56:27.367Z" }, + { url = "https://files.pythonhosted.org/packages/16/5c/a6b2419723ea7ddce6308259a55e8e7593d88464ce8db9f4aa857aba96fa/grpcio-1.78.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:74be1268d1439eaaf552c698cdb11cd594f0c49295ae6bb72c34ee31abbe611b", size = 7173206, upload-time = "2026-02-06T09:56:29.876Z" }, + { url = "https://files.pythonhosted.org/packages/df/1e/b8801345629a415ea7e26c83d75eb5dbe91b07ffe5210cc517348a8d4218/grpcio-1.78.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be63c88b32e6c0f1429f1398ca5c09bc64b0d80950c8bb7807d7d7fb36fb84c7", size = 6693826, upload-time = "2026-02-06T09:56:32.305Z" }, + { url = "https://files.pythonhosted.org/packages/34/84/0de28eac0377742679a510784f049738a80424b17287739fc47d63c2439e/grpcio-1.78.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3c586ac70e855c721bda8f548d38c3ca66ac791dc49b66a8281a1f99db85e452", size = 7277897, upload-time = "2026-02-06T09:56:34.915Z" }, + { url = "https://files.pythonhosted.org/packages/ca/9c/ad8685cfe20559a9edb66f735afdcb2b7d3de69b13666fdfc542e1916ebd/grpcio-1.78.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:35eb275bf1751d2ffbd8f57cdbc46058e857cf3971041521b78b7db94bdaf127", size = 8252404, upload-time = "2026-02-06T09:56:37.553Z" }, + { url = "https://files.pythonhosted.org/packages/3c/05/33a7a4985586f27e1de4803887c417ec7ced145ebd069bc38a9607059e2b/grpcio-1.78.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:207db540302c884b8848036b80db352a832b99dfdf41db1eb554c2c2c7800f65", size = 7696837, upload-time = "2026-02-06T09:56:40.173Z" }, + { url = "https://files.pythonhosted.org/packages/73/77/7382241caf88729b106e49e7d18e3116216c778e6a7e833826eb96de22f7/grpcio-1.78.0-cp314-cp314-win32.whl", hash = "sha256:57bab6deef2f4f1ca76cc04565df38dc5713ae6c17de690721bdf30cb1e0545c", size = 4142439, upload-time = "2026-02-06T09:56:43.258Z" }, + { url = "https://files.pythonhosted.org/packages/48/b2/b096ccce418882fbfda4f7496f9357aaa9a5af1896a9a7f60d9f2b275a06/grpcio-1.78.0-cp314-cp314-win_amd64.whl", hash = "sha256:dce09d6116df20a96acfdbf85e4866258c3758180e8c49845d6ba8248b6d0bbb", size = 4929852, upload-time = "2026-02-06T09:56:45.885Z" }, ] [[package]] @@ -1132,7 +1132,7 @@ wheels = [ [[package]] name = "ipykernel" -version = "7.1.0" +version = "7.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "appnope", marker = "sys_platform == 'darwin'" }, @@ -1149,14 +1149,14 @@ dependencies = [ { name = "tornado" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/a4/4948be6eb88628505b83a1f2f40d90254cab66abf2043b3c40fa07dfce0f/ipykernel-7.1.0.tar.gz", hash = "sha256:58a3fc88533d5930c3546dc7eac66c6d288acde4f801e2001e65edc5dc9cf0db", size = 174579, upload-time = "2025-10-27T09:46:39.471Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/8d/b68b728e2d06b9e0051019640a40a9eb7a88fcd82c2e1b5ce70bef5ff044/ipykernel-7.2.0.tar.gz", hash = "sha256:18ed160b6dee2cbb16e5f3575858bc19d8f1fe6046a9a680c708494ce31d909e", size = 176046, upload-time = "2026-02-06T16:43:27.403Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/17/20c2552266728ceba271967b87919664ecc0e33efca29c3efc6baf88c5f9/ipykernel-7.1.0-py3-none-any.whl", hash = "sha256:763b5ec6c5b7776f6a8d7ce09b267693b4e5ce75cb50ae696aaefb3c85e1ea4c", size = 117968, upload-time = "2025-10-27T09:46:37.805Z" }, + { url = "https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl", hash = "sha256:3bbd4420d2b3cc105cbdf3756bfc04500b1e52f090a90716851f3916c62e1661", size = 118788, upload-time = "2026-02-06T16:43:25.149Z" }, ] [[package]] name = "ipython" -version = "9.9.0" +version = "9.10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -1171,9 +1171,9 @@ dependencies = [ { name = "traitlets" }, { name = "typing-extensions", marker = "python_full_version < '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/46/dd/fb08d22ec0c27e73c8bc8f71810709870d51cadaf27b7ddd3f011236c100/ipython-9.9.0.tar.gz", hash = "sha256:48fbed1b2de5e2c7177eefa144aba7fcb82dac514f09b57e2ac9da34ddb54220", size = 4425043, upload-time = "2026-01-05T12:36:46.233Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/60/2111715ea11f39b1535bed6024b7dec7918b71e5e5d30855a5b503056b50/ipython-9.10.0.tar.gz", hash = "sha256:cd9e656be97618a0676d058134cd44e6dc7012c0e5cb36a9ce96a8c904adaf77", size = 4426526, upload-time = "2026-02-02T10:00:33.594Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/86/92/162cfaee4ccf370465c5af1ce36a9eacec1becb552f2033bb3584e6f640a/ipython-9.9.0-py3-none-any.whl", hash = "sha256:b457fe9165df2b84e8ec909a97abcf2ed88f565970efba16b1f7229c283d252b", size = 621431, upload-time = "2026-01-05T12:36:44.669Z" }, + { url = "https://files.pythonhosted.org/packages/3d/aa/898dec789a05731cd5a9f50605b7b44a72bd198fd0d4528e11fc610177cc/ipython-9.10.0-py3-none-any.whl", hash = "sha256:c6ab68cc23bba8c7e18e9b932797014cc61ea7fd6f19de180ab9ba73e65ee58d", size = 622774, upload-time = "2026-02-02T10:00:31.503Z" }, ] [[package]] @@ -2547,16 +2547,22 @@ wheels = [ [[package]] name = "pywinpty" -version = "3.0.2" +version = "3.0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/bb/a7cc2967c5c4eceb6cc49cfe39447d4bfc56e6c865e7c2249b6eb978935f/pywinpty-3.0.2.tar.gz", hash = "sha256:1505cc4cb248af42cb6285a65c9c2086ee9e7e574078ee60933d5d7fa86fb004", size = 30669, upload-time = "2025-10-03T21:16:29.205Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/54/37c7370ba91f579235049dc26cd2c5e657d2a943e01820844ffc81f32176/pywinpty-3.0.3.tar.gz", hash = "sha256:523441dc34d231fb361b4b00f8c99d3f16de02f5005fd544a0183112bcc22412", size = 31309, upload-time = "2026-02-04T21:51:09.524Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/a1/409c1651c9f874d598c10f51ff586c416625601df4bca315d08baec4c3e3/pywinpty-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:327790d70e4c841ebd9d0f295a780177149aeb405bca44c7115a3de5c2054b23", size = 2050304, upload-time = "2025-10-03T21:19:29.466Z" }, - { url = "https://files.pythonhosted.org/packages/02/4e/1098484e042c9485f56f16eb2b69b43b874bd526044ee401512234cf9e04/pywinpty-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:99fdd9b455f0ad6419aba6731a7a0d2f88ced83c3c94a80ff9533d95fa8d8a9e", size = 2050391, upload-time = "2025-10-03T21:19:01.642Z" }, - { url = "https://files.pythonhosted.org/packages/fc/19/b757fe28008236a4a713e813283721b8a40aa60cd7d3f83549f2e25a3155/pywinpty-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:18f78b81e4cfee6aabe7ea8688441d30247b73e52cd9657138015c5f4ee13a51", size = 2050057, upload-time = "2025-10-03T21:19:26.732Z" }, - { url = "https://files.pythonhosted.org/packages/cb/44/cbae12ecf6f4fa4129c36871fd09c6bef4f98d5f625ecefb5e2449765508/pywinpty-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:663383ecfab7fc382cc97ea5c4f7f0bb32c2f889259855df6ea34e5df42d305b", size = 2049874, upload-time = "2025-10-03T21:18:53.923Z" }, - { url = "https://files.pythonhosted.org/packages/ca/15/f12c6055e2d7a617d4d5820e8ac4ceaff849da4cb124640ef5116a230771/pywinpty-3.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:28297cecc37bee9f24d8889e47231972d6e9e84f7b668909de54f36ca785029a", size = 2050386, upload-time = "2025-10-03T21:18:50.477Z" }, - { url = "https://files.pythonhosted.org/packages/de/24/c6907c5bb06043df98ad6a0a0ff5db2e0affcecbc3b15c42404393a3f72a/pywinpty-3.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:34b55ae9a1b671fe3eae071d86618110538e8eaad18fcb1531c0830b91a82767", size = 2049834, upload-time = "2025-10-03T21:19:25.688Z" }, + { url = "https://files.pythonhosted.org/packages/79/c3/3e75075c7f71735f22b66fab0481f2c98e3a4d58cba55cb50ba29114bcf6/pywinpty-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:dff25a9a6435f527d7c65608a7e62783fc12076e7d44487a4911ee91be5a8ac8", size = 2114430, upload-time = "2026-02-04T21:54:19.485Z" }, + { url = "https://files.pythonhosted.org/packages/8d/1e/8a54166a8c5e4f5cb516514bdf4090be4d51a71e8d9f6d98c0aa00fe45d4/pywinpty-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:fbc1e230e5b193eef4431cba3f39996a288f9958f9c9f092c8a961d930ee8f68", size = 236191, upload-time = "2026-02-04T21:50:36.239Z" }, + { url = "https://files.pythonhosted.org/packages/7c/d4/aeb5e1784d2c5bff6e189138a9ca91a090117459cea0c30378e1f2db3d54/pywinpty-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:c9081df0e49ffa86d15db4a6ba61530630e48707f987df42c9d3313537e81fc0", size = 2113098, upload-time = "2026-02-04T21:54:37.711Z" }, + { url = "https://files.pythonhosted.org/packages/b9/53/7278223c493ccfe4883239cf06c823c56460a8010e0fc778eef67858dc14/pywinpty-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:15e79d870e18b678fb8a5a6105fd38496b55697c66e6fc0378236026bc4d59e9", size = 234901, upload-time = "2026-02-04T21:53:31.35Z" }, + { url = "https://files.pythonhosted.org/packages/e5/cb/58d6ed3fd429c96a90ef01ac9a617af10a6d41469219c25e7dc162abbb71/pywinpty-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9c91dbb026050c77bdcef964e63a4f10f01a639113c4d3658332614544c467ab", size = 2112686, upload-time = "2026-02-04T21:52:03.035Z" }, + { url = "https://files.pythonhosted.org/packages/fd/50/724ed5c38c504d4e58a88a072776a1e880d970789deaeb2b9f7bd9a5141a/pywinpty-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:fe1f7911805127c94cf51f89ab14096c6f91ffdcacf993d2da6082b2142a2523", size = 234591, upload-time = "2026-02-04T21:52:29.821Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ad/90a110538696b12b39fd8758a06d70ded899308198ad2305ac68e361126e/pywinpty-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:3f07a6cf1c1d470d284e614733c3d0f726d2c85e78508ea10a403140c3c0c18a", size = 2112360, upload-time = "2026-02-04T21:55:33.397Z" }, + { url = "https://files.pythonhosted.org/packages/44/0f/7ffa221757a220402bc79fda44044c3f2cc57338d878ab7d622add6f4581/pywinpty-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:15c7c0b6f8e9d87aabbaff76468dabf6e6121332c40fc1d83548d02a9d6a3759", size = 233107, upload-time = "2026-02-04T21:51:45.455Z" }, + { url = "https://files.pythonhosted.org/packages/28/88/2ff917caff61e55f38bcdb27de06ee30597881b2cae44fbba7627be015c4/pywinpty-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:d4b6b7b0fe0cdcd02e956bd57cfe9f4e5a06514eecf3b5ae174da4f951b58be9", size = 2113282, upload-time = "2026-02-04T21:52:08.188Z" }, + { url = "https://files.pythonhosted.org/packages/63/32/40a775343ace542cc43ece3f1d1fce454021521ecac41c4c4573081c2336/pywinpty-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:34789d685fc0d547ce0c8a65e5a70e56f77d732fa6e03c8f74fefb8cbb252019", size = 234207, upload-time = "2026-02-04T21:51:58.687Z" }, + { url = "https://files.pythonhosted.org/packages/8d/54/5d5e52f4cb75028104ca6faf36c10f9692389b1986d34471663b4ebebd6d/pywinpty-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:0c37e224a47a971d1a6e08649a1714dac4f63c11920780977829ed5c8cadead1", size = 2112910, upload-time = "2026-02-04T21:52:30.976Z" }, + { url = "https://files.pythonhosted.org/packages/0a/44/dcd184824e21d4620b06c7db9fbb15c3ad0a0f1fa2e6de79969fb82647ec/pywinpty-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:c4e9c3dff7d86ba81937438d5819f19f385a39d8f592d4e8af67148ceb4f6ab5", size = 233425, upload-time = "2026-02-04T21:51:56.754Z" }, ] [[package]] @@ -2992,28 +2998,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.14.14" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2e/06/f71e3a86b2df0dfa2d2f72195941cd09b44f87711cb7fa5193732cb9a5fc/ruff-0.14.14.tar.gz", hash = "sha256:2d0f819c9a90205f3a867dbbd0be083bee9912e170fd7d9704cc8ae45824896b", size = 4515732, upload-time = "2026-01-22T22:30:17.527Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/89/20a12e97bc6b9f9f68343952da08a8099c57237aef953a56b82711d55edd/ruff-0.14.14-py3-none-linux_armv6l.whl", hash = "sha256:7cfe36b56e8489dee8fbc777c61959f60ec0f1f11817e8f2415f429552846aed", size = 10467650, upload-time = "2026-01-22T22:30:08.578Z" }, - { url = "https://files.pythonhosted.org/packages/a3/b1/c5de3fd2d5a831fcae21beda5e3589c0ba67eec8202e992388e4b17a6040/ruff-0.14.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6006a0082336e7920b9573ef8a7f52eec837add1265cc74e04ea8a4368cd704c", size = 10883245, upload-time = "2026-01-22T22:30:04.155Z" }, - { url = "https://files.pythonhosted.org/packages/b8/7c/3c1db59a10e7490f8f6f8559d1db8636cbb13dccebf18686f4e3c9d7c772/ruff-0.14.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:026c1d25996818f0bf498636686199d9bd0d9d6341c9c2c3b62e2a0198b758de", size = 10231273, upload-time = "2026-01-22T22:30:34.642Z" }, - { url = "https://files.pythonhosted.org/packages/a1/6e/5e0e0d9674be0f8581d1f5e0f0a04761203affce3232c1a1189d0e3b4dad/ruff-0.14.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f666445819d31210b71e0a6d1c01e24447a20b85458eea25a25fe8142210ae0e", size = 10585753, upload-time = "2026-01-22T22:30:31.781Z" }, - { url = "https://files.pythonhosted.org/packages/23/09/754ab09f46ff1884d422dc26d59ba18b4e5d355be147721bb2518aa2a014/ruff-0.14.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c0f18b922c6d2ff9a5e6c3ee16259adc513ca775bcf82c67ebab7cbd9da5bc8", size = 10286052, upload-time = "2026-01-22T22:30:24.827Z" }, - { url = "https://files.pythonhosted.org/packages/c8/cc/e71f88dd2a12afb5f50733851729d6b571a7c3a35bfdb16c3035132675a0/ruff-0.14.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1629e67489c2dea43e8658c3dba659edbfd87361624b4040d1df04c9740ae906", size = 11043637, upload-time = "2026-01-22T22:30:13.239Z" }, - { url = "https://files.pythonhosted.org/packages/67/b2/397245026352494497dac935d7f00f1468c03a23a0c5db6ad8fc49ca3fb2/ruff-0.14.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:27493a2131ea0f899057d49d303e4292b2cae2bb57253c1ed1f256fbcd1da480", size = 12194761, upload-time = "2026-01-22T22:30:22.542Z" }, - { url = "https://files.pythonhosted.org/packages/5b/06/06ef271459f778323112c51b7587ce85230785cd64e91772034ddb88f200/ruff-0.14.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01ff589aab3f5b539e35db38425da31a57521efd1e4ad1ae08fc34dbe30bd7df", size = 12005701, upload-time = "2026-01-22T22:30:20.499Z" }, - { url = "https://files.pythonhosted.org/packages/41/d6/99364514541cf811ccc5ac44362f88df66373e9fec1b9d1c4cc830593fe7/ruff-0.14.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc12d74eef0f29f51775f5b755913eb523546b88e2d733e1d701fe65144e89b", size = 11282455, upload-time = "2026-01-22T22:29:59.679Z" }, - { url = "https://files.pythonhosted.org/packages/ca/71/37daa46f89475f8582b7762ecd2722492df26421714a33e72ccc9a84d7a5/ruff-0.14.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb8481604b7a9e75eff53772496201690ce2687067e038b3cc31aaf16aa0b974", size = 11215882, upload-time = "2026-01-22T22:29:57.032Z" }, - { url = "https://files.pythonhosted.org/packages/2c/10/a31f86169ec91c0705e618443ee74ede0bdd94da0a57b28e72db68b2dbac/ruff-0.14.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:14649acb1cf7b5d2d283ebd2f58d56b75836ed8c6f329664fa91cdea19e76e66", size = 11180549, upload-time = "2026-01-22T22:30:27.175Z" }, - { url = "https://files.pythonhosted.org/packages/fd/1e/c723f20536b5163adf79bdd10c5f093414293cdf567eed9bdb7b83940f3f/ruff-0.14.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8058d2145566510790eab4e2fad186002e288dec5e0d343a92fe7b0bc1b3e13", size = 10543416, upload-time = "2026-01-22T22:30:01.964Z" }, - { url = "https://files.pythonhosted.org/packages/3e/34/8a84cea7e42c2d94ba5bde1d7a4fae164d6318f13f933d92da6d7c2041ff/ruff-0.14.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e651e977a79e4c758eb807f0481d673a67ffe53cfa92209781dfa3a996cf8412", size = 10285491, upload-time = "2026-01-22T22:30:29.51Z" }, - { url = "https://files.pythonhosted.org/packages/55/ef/b7c5ea0be82518906c978e365e56a77f8de7678c8bb6651ccfbdc178c29f/ruff-0.14.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cc8b22da8d9d6fdd844a68ae937e2a0adf9b16514e9a97cc60355e2d4b219fc3", size = 10733525, upload-time = "2026-01-22T22:30:06.499Z" }, - { url = "https://files.pythonhosted.org/packages/6a/5b/aaf1dfbcc53a2811f6cc0a1759de24e4b03e02ba8762daabd9b6bd8c59e3/ruff-0.14.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:16bc890fb4cc9781bb05beb5ab4cd51be9e7cb376bf1dd3580512b24eb3fda2b", size = 11315626, upload-time = "2026-01-22T22:30:36.848Z" }, - { url = "https://files.pythonhosted.org/packages/2c/aa/9f89c719c467dfaf8ad799b9bae0df494513fb21d31a6059cb5870e57e74/ruff-0.14.14-py3-none-win32.whl", hash = "sha256:b530c191970b143375b6a68e6f743800b2b786bbcf03a7965b06c4bf04568167", size = 10502442, upload-time = "2026-01-22T22:30:38.93Z" }, - { url = "https://files.pythonhosted.org/packages/87/44/90fa543014c45560cae1fffc63ea059fb3575ee6e1cb654562197e5d16fb/ruff-0.14.14-py3-none-win_amd64.whl", hash = "sha256:3dde1435e6b6fe5b66506c1dff67a421d0b7f6488d466f651c07f4cab3bf20fd", size = 11630486, upload-time = "2026-01-22T22:30:10.852Z" }, - { url = "https://files.pythonhosted.org/packages/9e/6a/40fee331a52339926a92e17ae748827270b288a35ef4a15c9c8f2ec54715/ruff-0.14.14-py3-none-win_arm64.whl", hash = "sha256:56e6981a98b13a32236a72a8da421d7839221fa308b223b9283312312e5ac76c", size = 10920448, upload-time = "2026-01-22T22:30:15.417Z" }, +version = "0.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c8/39/5cee96809fbca590abea6b46c6d1c586b49663d1d2830a751cc8fc42c666/ruff-0.15.0.tar.gz", hash = "sha256:6bdea47cdbea30d40f8f8d7d69c0854ba7c15420ec75a26f463290949d7f7e9a", size = 4524893, upload-time = "2026-02-03T17:53:35.357Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/88/3fd1b0aa4b6330d6aaa63a285bc96c9f71970351579152d231ed90914586/ruff-0.15.0-py3-none-linux_armv6l.whl", hash = "sha256:aac4ebaa612a82b23d45964586f24ae9bc23ca101919f5590bdb368d74ad5455", size = 10354332, upload-time = "2026-02-03T17:52:54.892Z" }, + { url = "https://files.pythonhosted.org/packages/72/f6/62e173fbb7eb75cc29fe2576a1e20f0a46f671a2587b5f604bfb0eaf5f6f/ruff-0.15.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dcd4be7cc75cfbbca24a98d04d0b9b36a270d0833241f776b788d59f4142b14d", size = 10767189, upload-time = "2026-02-03T17:53:19.778Z" }, + { url = "https://files.pythonhosted.org/packages/99/e4/968ae17b676d1d2ff101d56dc69cf333e3a4c985e1ec23803df84fc7bf9e/ruff-0.15.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d747e3319b2bce179c7c1eaad3d884dc0a199b5f4d5187620530adf9105268ce", size = 10075384, upload-time = "2026-02-03T17:53:29.241Z" }, + { url = "https://files.pythonhosted.org/packages/a2/bf/9843c6044ab9e20af879c751487e61333ca79a2c8c3058b15722386b8cae/ruff-0.15.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:650bd9c56ae03102c51a5e4b554d74d825ff3abe4db22b90fd32d816c2e90621", size = 10481363, upload-time = "2026-02-03T17:52:43.332Z" }, + { url = "https://files.pythonhosted.org/packages/55/d9/4ada5ccf4cd1f532db1c8d44b6f664f2208d3d93acbeec18f82315e15193/ruff-0.15.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6664b7eac559e3048223a2da77769c2f92b43a6dfd4720cef42654299a599c9", size = 10187736, upload-time = "2026-02-03T17:53:00.522Z" }, + { url = "https://files.pythonhosted.org/packages/86/e2/f25eaecd446af7bb132af0a1d5b135a62971a41f5366ff41d06d25e77a91/ruff-0.15.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f811f97b0f092b35320d1556f3353bf238763420ade5d9e62ebd2b73f2ff179", size = 10968415, upload-time = "2026-02-03T17:53:15.705Z" }, + { url = "https://files.pythonhosted.org/packages/e7/dc/f06a8558d06333bf79b497d29a50c3a673d9251214e0d7ec78f90b30aa79/ruff-0.15.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:761ec0a66680fab6454236635a39abaf14198818c8cdf691e036f4bc0f406b2d", size = 11809643, upload-time = "2026-02-03T17:53:23.031Z" }, + { url = "https://files.pythonhosted.org/packages/dd/45/0ece8db2c474ad7df13af3a6d50f76e22a09d078af63078f005057ca59eb/ruff-0.15.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:940f11c2604d317e797b289f4f9f3fa5555ffe4fb574b55ed006c3d9b6f0eb78", size = 11234787, upload-time = "2026-02-03T17:52:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/8a/d9/0e3a81467a120fd265658d127db648e4d3acfe3e4f6f5d4ea79fac47e587/ruff-0.15.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcbca3d40558789126da91d7ef9a7c87772ee107033db7191edefa34e2c7f1b4", size = 11112797, upload-time = "2026-02-03T17:52:49.274Z" }, + { url = "https://files.pythonhosted.org/packages/b2/cb/8c0b3b0c692683f8ff31351dfb6241047fa873a4481a76df4335a8bff716/ruff-0.15.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9a121a96db1d75fa3eb39c4539e607f628920dd72ff1f7c5ee4f1b768ac62d6e", size = 11033133, upload-time = "2026-02-03T17:53:33.105Z" }, + { url = "https://files.pythonhosted.org/packages/f8/5e/23b87370cf0f9081a8c89a753e69a4e8778805b8802ccfe175cc410e50b9/ruff-0.15.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5298d518e493061f2eabd4abd067c7e4fb89e2f63291c94332e35631c07c3662", size = 10442646, upload-time = "2026-02-03T17:53:06.278Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9a/3c94de5ce642830167e6d00b5c75aacd73e6347b4c7fc6828699b150a5ee/ruff-0.15.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:afb6e603d6375ff0d6b0cee563fa21ab570fd15e65c852cb24922cef25050cf1", size = 10195750, upload-time = "2026-02-03T17:53:26.084Z" }, + { url = "https://files.pythonhosted.org/packages/30/15/e396325080d600b436acc970848d69df9c13977942fb62bb8722d729bee8/ruff-0.15.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:77e515f6b15f828b94dc17d2b4ace334c9ddb7d9468c54b2f9ed2b9c1593ef16", size = 10676120, upload-time = "2026-02-03T17:53:09.363Z" }, + { url = "https://files.pythonhosted.org/packages/8d/c9/229a23d52a2983de1ad0fb0ee37d36e0257e6f28bfd6b498ee2c76361874/ruff-0.15.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6f6e80850a01eb13b3e42ee0ebdf6e4497151b48c35051aab51c101266d187a3", size = 11201636, upload-time = "2026-02-03T17:52:57.281Z" }, + { url = "https://files.pythonhosted.org/packages/6f/b0/69adf22f4e24f3677208adb715c578266842e6e6a3cc77483f48dd999ede/ruff-0.15.0-py3-none-win32.whl", hash = "sha256:238a717ef803e501b6d51e0bdd0d2c6e8513fe9eec14002445134d3907cd46c3", size = 10465945, upload-time = "2026-02-03T17:53:12.591Z" }, + { url = "https://files.pythonhosted.org/packages/51/ad/f813b6e2c97e9b4598be25e94a9147b9af7e60523b0cb5d94d307c15229d/ruff-0.15.0-py3-none-win_amd64.whl", hash = "sha256:dd5e4d3301dc01de614da3cdffc33d4b1b96fb89e45721f1598e5532ccf78b18", size = 11564657, upload-time = "2026-02-03T17:52:51.893Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b0/2d823f6e77ebe560f4e397d078487e8d52c1516b331e3521bc75db4272ca/ruff-0.15.0-py3-none-win_arm64.whl", hash = "sha256:c480d632cc0ca3f0727acac8b7d053542d9e114a462a145d0b00e7cd658c515a", size = 10865753, upload-time = "2026-02-03T17:53:03.014Z" }, ] [[package]] @@ -3164,11 +3169,11 @@ wheels = [ [[package]] name = "setuptools" -version = "80.10.2" +version = "82.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/76/95/faf61eb8363f26aa7e1d762267a8d602a1b26d4f3a1e758e92cb3cb8b054/setuptools-80.10.2.tar.gz", hash = "sha256:8b0e9d10c784bf7d262c4e5ec5d4ec94127ce206e8738f29a437945fbc219b70", size = 1200343, upload-time = "2026-01-25T22:38:17.252Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/f3/748f4d6f65d1756b9ae577f329c951cda23fb900e4de9f70900ced962085/setuptools-82.0.0.tar.gz", hash = "sha256:22e0a2d69474c6ae4feb01951cb69d515ed23728cf96d05513d36e42b62b37cb", size = 1144893, upload-time = "2026-02-08T15:08:40.206Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/b8/f1f62a5e3c0ad2ff1d189590bfa4c46b4f3b6e49cef6f26c6ee4e575394d/setuptools-80.10.2-py3-none-any.whl", hash = "sha256:95b30ddfb717250edb492926c92b5221f7ef3fbcc2b07579bcd4a27da21d0173", size = 1064234, upload-time = "2026-01-25T22:38:15.216Z" }, + { url = "https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl", hash = "sha256:70b18734b607bd1da571d097d236cfcfacaf01de45717d59e6e04b96877532e0", size = 1003468, upload-time = "2026-02-08T15:08:38.723Z" }, ] [[package]] @@ -3426,6 +3431,9 @@ dependencies = [ { name = "typing-extensions" }, ] wheels = [ + { url = "https://files.pythonhosted.org/packages/25/d8/9e6b8e7df981a1e3ea3907fd5a74673e791da483e8c307f0b6ff012626d0/torch-2.10.0-1-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:f699f31a236a677b3118bc0a3ef3d89c0c29b5ec0b20f4c4bf0b110378487464", size = 79423460, upload-time = "2026-02-06T17:37:39.657Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/0b295dd8d199ef71e6f176f576473d645d41357b7b8aa978cc6b042575df/torch-2.10.0-1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:6abb224c2b6e9e27b592a1c0015c33a504b00a0e0938f1499f7f514e9b7bfb5c", size = 79498197, upload-time = "2026-02-06T17:37:27.627Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1b/af5fccb50c341bd69dc016769503cb0857c1423fbe9343410dfeb65240f2/torch-2.10.0-1-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:7350f6652dfd761f11f9ecb590bfe95b573e2961f7a242eccb3c8e78348d26fe", size = 79498248, upload-time = "2026-02-06T17:37:31.982Z" }, { url = "https://files.pythonhosted.org/packages/78/89/f5554b13ebd71e05c0b002f95148033e730d3f7067f67423026cc9c69410/torch-2.10.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:3282d9febd1e4e476630a099692b44fdc214ee9bf8ee5377732d9d9dfe5712e4", size = 145992610, upload-time = "2026-01-21T16:25:26.327Z" }, { url = "https://files.pythonhosted.org/packages/ae/30/a3a2120621bf9c17779b169fc17e3dc29b230c29d0f8222f499f5e159aa8/torch-2.10.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a2f9edd8dbc99f62bc4dfb78af7bf89499bca3d753423ac1b4e06592e467b763", size = 915607863, upload-time = "2026-01-21T16:25:06.696Z" }, { url = "https://files.pythonhosted.org/packages/6f/3d/c87b33c5f260a2a8ad68da7147e105f05868c281c63d65ed85aa4da98c66/torch-2.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:29b7009dba4b7a1c960260fc8ac85022c784250af43af9fb0ebafc9883782ebd", size = 113723116, upload-time = "2026-01-21T16:25:21.916Z" }, @@ -3533,14 +3541,14 @@ wheels = [ [[package]] name = "tqdm" -version = "4.67.2" +version = "4.67.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/27/89/4b0001b2dab8df0a5ee2787dcbe771de75ded01f18f1f8d53dedeea2882b/tqdm-4.67.2.tar.gz", hash = "sha256:649aac53964b2cb8dec76a14b405a4c0d13612cb8933aae547dd144eacc99653", size = 169514, upload-time = "2026-01-30T23:12:06.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/e2/31eac96de2915cf20ccaed0225035db149dfb9165a9ed28d4b252ef3f7f7/tqdm-4.67.2-py3-none-any.whl", hash = "sha256:9a12abcbbff58b6036b2167d9d3853042b9d436fe7330f06ae047867f2f8e0a7", size = 78354, upload-time = "2026-01-30T23:12:04.368Z" }, + { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, ] [[package]] @@ -3617,11 +3625,11 @@ wheels = [ [[package]] name = "wcwidth" -version = "0.5.3" +version = "0.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c2/62/a7c072fbfefb2980a00f99ca994279cb9ecf310cb2e6b2a4d2a28fe192b3/wcwidth-0.5.3.tar.gz", hash = "sha256:53123b7af053c74e9fe2e92ac810301f6139e64379031f7124574212fb3b4091", size = 157587, upload-time = "2026-01-31T03:52:10.92Z" } +sdist = { url = "https://files.pythonhosted.org/packages/35/a2/8e3becb46433538a38726c948d3399905a4c7cabd0df578ede5dc51f0ec2/wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159", size = 159684, upload-time = "2026-02-06T19:19:40.919Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/c1/d73f12f8cdb1891334a2ccf7389eed244d3941e74d80dd220badb937f3fb/wcwidth-0.5.3-py3-none-any.whl", hash = "sha256:d584eff31cd4753e1e5ff6c12e1edfdb324c995713f75d26c29807bb84bf649e", size = 92981, upload-time = "2026-01-31T03:52:09.14Z" }, + { url = "https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189, upload-time = "2026-02-06T19:19:39.646Z" }, ] [[package]] From 9fb34b8e8c257dc3abc3d7c128c71a705d88b675 Mon Sep 17 00:00:00 2001 From: Georgios Varnavides Date: Sat, 14 Feb 2026 15:35:49 +0100 Subject: [PATCH 074/335] move tagging and release to deploy action --- .github/workflows/check-pr-main-version.yml | 8 ------ .github/workflows/deploy.yml | 29 +++++++++++++++++++++ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/.github/workflows/check-pr-main-version.yml b/.github/workflows/check-pr-main-version.yml index 809e3158..d867ade3 100644 --- a/.github/workflows/check-pr-main-version.yml +++ b/.github/workflows/check-pr-main-version.yml @@ -84,10 +84,6 @@ jobs: echo "New version is greater — creating release branch" git checkout -b release/$PR_VERSION git push origin release/$PR_VERSION - - echo "Tagging release v$PR_VERSION" - git tag -a v$PR_VERSION -m "Release v$PR_VERSION" - git push origin v$PR_VERSION fi elif [[ "$RESULT" -eq 0 ]]; then @@ -108,10 +104,6 @@ jobs: git checkout -b release/$NEXT_VERSION git push origin release/$NEXT_VERSION - echo "Tagging release v$NEXT_VERSION" - git tag -a v$NEXT_VERSION -m "Release v$NEXT_VERSION" - git push origin v$NEXT_VERSION - PR_BODY=$(printf "This PR was automatically created because the submitted version \`%s\` matched the current release on \`main\`.\n\nIt bumps the patch version to \`%s\` and starts a new release process." "$PR_VERSION" "$NEXT_VERSION") gh pr create \ --repo "$REPO" \ diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index f4eab293..fd58e7c6 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -5,6 +5,9 @@ on: branches: - main +permissions: + contents: write + jobs: uv-deploy: name: deploy @@ -14,15 +17,41 @@ jobs: steps: - uses: actions/checkout@v6 + with: + fetch-depth: 0 - name: Install uv uses: astral-sh/setup-uv@v7 + with: + enable-cache: true - name: Verify lock file is up to date run: uv lock --check + - name: Read version + id: version + run: | + VERSION=$(uv version --short) + echo "version=$VERSION" >> $GITHUB_OUTPUT + - name: Build the project run: uv build + - name: Create Git tag + run: | + git config user.name "github-actions" + git config user.email "github-actions@github.com" + git tag v${{ steps.version.outputs.version }} + git push origin v${{ steps.version.outputs.version }} + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: v${{ steps.version.outputs.version }} + name: v${{ steps.version.outputs.version }} + generate_release_notes: true + files: | + dist/* + - name: Publish to PyPi run: uv publish From 82fde9a0700f6789542872a5e9cbc125b2d98a5b Mon Sep 17 00:00:00 2001 From: quantem-bot Date: Mon, 16 Feb 2026 10:01:33 +0000 Subject: [PATCH 075/335] chore: update lock file --- uv.lock | 438 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 225 insertions(+), 213 deletions(-) diff --git a/uv.lock b/uv.lock index 7a43d17a..9288f616 100644 --- a/uv.lock +++ b/uv.lock @@ -24,16 +24,16 @@ wheels = [ [[package]] name = "alembic" -version = "1.18.3" +version = "1.18.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mako" }, { name = "sqlalchemy" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/79/41/ab8f624929847b49f84955c594b165855efd829b0c271e1a8cac694138e5/alembic-1.18.3.tar.gz", hash = "sha256:1212aa3778626f2b0f0aa6dd4e99a5f99b94bd25a0c1ac0bba3be65e081e50b0", size = 2052564, upload-time = "2026-01-29T20:24:15.124Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/13/8b084e0f2efb0275a1d534838844926f798bd766566b1375174e2448cd31/alembic-1.18.4.tar.gz", hash = "sha256:cb6e1fd84b6174ab8dbb2329f86d631ba9559dd78df550b57804d607672cedbc", size = 2056725, upload-time = "2026-02-10T16:00:47.195Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/45/8e/d79281f323e7469b060f15bd229e48d7cdd219559e67e71c013720a88340/alembic-1.18.3-py3-none-any.whl", hash = "sha256:12a0359bfc068a4ecbb9b3b02cf77856033abfdb59e4a5aca08b7eacd7b74ddd", size = 262282, upload-time = "2026-01-29T20:24:17.488Z" }, + { url = "https://files.pythonhosted.org/packages/d2/29/6533c317b74f707ea28f8d633734dbda2119bbadfc61b2f3640ba835d0f7/alembic-1.18.4-py3-none-any.whl", hash = "sha256:a5ed4adcf6d8a4cb575f3d759f071b03cd6e5c7618eb796cb52497be25bfe19a", size = 263893, upload-time = "2026-02-10T16:00:49.997Z" }, ] [[package]] @@ -516,89 +516,101 @@ wheels = [ [[package]] name = "coverage" -version = "7.13.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/11/43/3e4ac666cc35f231fa70c94e9f38459299de1a152813f9d2f60fc5f3ecaf/coverage-7.13.3.tar.gz", hash = "sha256:f7f6182d3dfb8802c1747eacbfe611b669455b69b7c037484bb1efbbb56711ac", size = 826832, upload-time = "2026-02-03T14:02:30.944Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/09/1ac74e37cf45f17eb41e11a21854f7f92a4c2d6c6098ef4a1becb0c6d8d3/coverage-7.13.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5907605ee20e126eeee2abe14aae137043c2c8af2fa9b38d2ab3b7a6b8137f73", size = 219276, upload-time = "2026-02-03T14:00:00.296Z" }, - { url = "https://files.pythonhosted.org/packages/2e/cb/71908b08b21beb2c437d0d5870c4ec129c570ca1b386a8427fcdb11cf89c/coverage-7.13.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a88705500988c8acad8b8fd86c2a933d3aa96bec1ddc4bc5cb256360db7bbd00", size = 219776, upload-time = "2026-02-03T14:00:02.414Z" }, - { url = "https://files.pythonhosted.org/packages/09/85/c4f3dd69232887666a2c0394d4be21c60ea934d404db068e6c96aa59cd87/coverage-7.13.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bbb5aa9016c4c29e3432e087aa29ebee3f8fda089cfbfb4e6d64bd292dcd1c2", size = 250196, upload-time = "2026-02-03T14:00:04.197Z" }, - { url = "https://files.pythonhosted.org/packages/9c/cc/560ad6f12010344d0778e268df5ba9aa990aacccc310d478bf82bf3d302c/coverage-7.13.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0c2be202a83dde768937a61cdc5d06bf9fb204048ca199d93479488e6247656c", size = 252111, upload-time = "2026-02-03T14:00:05.639Z" }, - { url = "https://files.pythonhosted.org/packages/f0/66/3193985fb2c58e91f94cfbe9e21a6fdf941e9301fe2be9e92c072e9c8f8c/coverage-7.13.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f45e32ef383ce56e0ca099b2e02fcdf7950be4b1b56afaab27b4ad790befe5b", size = 254217, upload-time = "2026-02-03T14:00:07.738Z" }, - { url = "https://files.pythonhosted.org/packages/c5/78/f0f91556bf1faa416792e537c523c5ef9db9b1d32a50572c102b3d7c45b3/coverage-7.13.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6ed2e787249b922a93cd95c671cc9f4c9797a106e81b455c83a9ddb9d34590c0", size = 250318, upload-time = "2026-02-03T14:00:09.224Z" }, - { url = "https://files.pythonhosted.org/packages/6f/aa/fc654e45e837d137b2c1f3a2cc09b4aea1e8b015acd2f774fa0f3d2ddeba/coverage-7.13.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:05dd25b21afffe545e808265897c35f32d3e4437663923e0d256d9ab5031fb14", size = 251909, upload-time = "2026-02-03T14:00:10.712Z" }, - { url = "https://files.pythonhosted.org/packages/73/4d/ab53063992add8a9ca0463c9d92cce5994a29e17affd1c2daa091b922a93/coverage-7.13.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:46d29926349b5c4f1ea4fca95e8c892835515f3600995a383fa9a923b5739ea4", size = 249971, upload-time = "2026-02-03T14:00:12.402Z" }, - { url = "https://files.pythonhosted.org/packages/29/25/83694b81e46fcff9899694a1b6f57573429cdd82b57932f09a698f03eea5/coverage-7.13.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:fae6a21537519c2af00245e834e5bf2884699cc7c1055738fd0f9dc37a3644ad", size = 249692, upload-time = "2026-02-03T14:00:13.868Z" }, - { url = "https://files.pythonhosted.org/packages/d4/ef/d68fc304301f4cb4bf6aefa0045310520789ca38dabdfba9dbecd3f37919/coverage-7.13.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c672d4e2f0575a4ca2bf2aa0c5ced5188220ab806c1bb6d7179f70a11a017222", size = 250597, upload-time = "2026-02-03T14:00:15.461Z" }, - { url = "https://files.pythonhosted.org/packages/8d/85/240ad396f914df361d0f71e912ddcedb48130c71b88dc4193fe3c0306f00/coverage-7.13.3-cp311-cp311-win32.whl", hash = "sha256:fcda51c918c7a13ad93b5f89a58d56e3a072c9e0ba5c231b0ed81404bf2648fb", size = 221773, upload-time = "2026-02-03T14:00:17.462Z" }, - { url = "https://files.pythonhosted.org/packages/2f/71/165b3a6d3d052704a9ab52d11ea64ef3426745de517dda44d872716213a7/coverage-7.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:d1a049b5c51b3b679928dd35e47c4a2235e0b6128b479a7596d0ef5b42fa6301", size = 222711, upload-time = "2026-02-03T14:00:19.449Z" }, - { url = "https://files.pythonhosted.org/packages/51/d0/0ddc9c5934cdd52639c5df1f1eb0fdab51bb52348f3a8d1c7db9c600d93a/coverage-7.13.3-cp311-cp311-win_arm64.whl", hash = "sha256:79f2670c7e772f4917895c3d89aad59e01f3dbe68a4ed2d0373b431fad1dcfba", size = 221377, upload-time = "2026-02-03T14:00:20.968Z" }, - { url = "https://files.pythonhosted.org/packages/94/44/330f8e83b143f6668778ed61d17ece9dc48459e9e74669177de02f45fec5/coverage-7.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ed48b4170caa2c4420e0cd27dc977caaffc7eecc317355751df8373dddcef595", size = 219441, upload-time = "2026-02-03T14:00:22.585Z" }, - { url = "https://files.pythonhosted.org/packages/08/e7/29db05693562c2e65bdf6910c0af2fd6f9325b8f43caf7a258413f369e30/coverage-7.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8f2adf4bcffbbec41f366f2e6dffb9d24e8172d16e91da5799c9b7ed6b5716e6", size = 219801, upload-time = "2026-02-03T14:00:24.186Z" }, - { url = "https://files.pythonhosted.org/packages/90/ae/7f8a78249b02b0818db46220795f8ac8312ea4abd1d37d79ea81db5cae81/coverage-7.13.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:01119735c690786b6966a1e9f098da4cd7ca9174c4cfe076d04e653105488395", size = 251306, upload-time = "2026-02-03T14:00:25.798Z" }, - { url = "https://files.pythonhosted.org/packages/62/71/a18a53d1808e09b2e9ebd6b47dad5e92daf4c38b0686b4c4d1b2f3e42b7f/coverage-7.13.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8bb09e83c603f152d855f666d70a71765ca8e67332e5829e62cb9466c176af23", size = 254051, upload-time = "2026-02-03T14:00:27.474Z" }, - { url = "https://files.pythonhosted.org/packages/4a/0a/eb30f6455d04c5a3396d0696cad2df0269ae7444bb322f86ffe3376f7bf9/coverage-7.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b607a40cba795cfac6d130220d25962931ce101f2f478a29822b19755377fb34", size = 255160, upload-time = "2026-02-03T14:00:29.024Z" }, - { url = "https://files.pythonhosted.org/packages/7b/7e/a45baac86274ce3ed842dbb84f14560c673ad30535f397d89164ec56c5df/coverage-7.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:44f14a62f5da2e9aedf9080e01d2cda61df39197d48e323538ec037336d68da8", size = 251709, upload-time = "2026-02-03T14:00:30.641Z" }, - { url = "https://files.pythonhosted.org/packages/c0/df/dd0dc12f30da11349993f3e218901fdf82f45ee44773596050c8f5a1fb25/coverage-7.13.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:debf29e0b157769843dff0981cc76f79e0ed04e36bb773c6cac5f6029054bd8a", size = 253083, upload-time = "2026-02-03T14:00:32.14Z" }, - { url = "https://files.pythonhosted.org/packages/ab/32/fc764c8389a8ce95cb90eb97af4c32f392ab0ac23ec57cadeefb887188d3/coverage-7.13.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:824bb95cd71604031ae9a48edb91fd6effde669522f960375668ed21b36e3ec4", size = 251227, upload-time = "2026-02-03T14:00:34.721Z" }, - { url = "https://files.pythonhosted.org/packages/dd/ca/d025e9da8f06f24c34d2da9873957cfc5f7e0d67802c3e34d0caa8452130/coverage-7.13.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8f1010029a5b52dc427c8e2a8dbddb2303ddd180b806687d1acd1bb1d06649e7", size = 250794, upload-time = "2026-02-03T14:00:36.278Z" }, - { url = "https://files.pythonhosted.org/packages/45/c7/76bf35d5d488ec8f68682eb8e7671acc50a6d2d1c1182de1d2b6d4ffad3b/coverage-7.13.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cd5dee4fd7659d8306ffa79eeaaafd91fa30a302dac3af723b9b469e549247e0", size = 252671, upload-time = "2026-02-03T14:00:38.368Z" }, - { url = "https://files.pythonhosted.org/packages/bf/10/1921f1a03a7c209e1cb374f81a6b9b68b03cdb3ecc3433c189bc90e2a3d5/coverage-7.13.3-cp312-cp312-win32.whl", hash = "sha256:f7f153d0184d45f3873b3ad3ad22694fd73aadcb8cdbc4337ab4b41ea6b4dff1", size = 221986, upload-time = "2026-02-03T14:00:40.442Z" }, - { url = "https://files.pythonhosted.org/packages/3c/7c/f5d93297f8e125a80c15545edc754d93e0ed8ba255b65e609b185296af01/coverage-7.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:03a6e5e1e50819d6d7436f5bc40c92ded7e484e400716886ac921e35c133149d", size = 222793, upload-time = "2026-02-03T14:00:42.106Z" }, - { url = "https://files.pythonhosted.org/packages/43/59/c86b84170015b4555ebabca8649bdf9f4a1f737a73168088385ed0f947c4/coverage-7.13.3-cp312-cp312-win_arm64.whl", hash = "sha256:51c4c42c0e7d09a822b08b6cf79b3c4db8333fffde7450da946719ba0d45730f", size = 221410, upload-time = "2026-02-03T14:00:43.726Z" }, - { url = "https://files.pythonhosted.org/packages/81/f3/4c333da7b373e8c8bfb62517e8174a01dcc373d7a9083698e3b39d50d59c/coverage-7.13.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:853c3d3c79ff0db65797aad79dee6be020efd218ac4510f15a205f1e8d13ce25", size = 219468, upload-time = "2026-02-03T14:00:45.829Z" }, - { url = "https://files.pythonhosted.org/packages/d6/31/0714337b7d23630c8de2f4d56acf43c65f8728a45ed529b34410683f7217/coverage-7.13.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f75695e157c83d374f88dcc646a60cb94173304a9258b2e74ba5a66b7614a51a", size = 219839, upload-time = "2026-02-03T14:00:47.407Z" }, - { url = "https://files.pythonhosted.org/packages/12/99/bd6f2a2738144c98945666f90cae446ed870cecf0421c767475fcf42cdbe/coverage-7.13.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2d098709621d0819039f3f1e471ee554f55a0b2ac0d816883c765b14129b5627", size = 250828, upload-time = "2026-02-03T14:00:49.029Z" }, - { url = "https://files.pythonhosted.org/packages/6f/99/97b600225fbf631e6f5bfd3ad5bcaf87fbb9e34ff87492e5a572ff01bbe2/coverage-7.13.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16d23d6579cf80a474ad160ca14d8b319abaa6db62759d6eef53b2fc979b58c8", size = 253432, upload-time = "2026-02-03T14:00:50.655Z" }, - { url = "https://files.pythonhosted.org/packages/5f/5c/abe2b3490bda26bd4f5e3e799be0bdf00bd81edebedc2c9da8d3ef288fa8/coverage-7.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00d34b29a59d2076e6f318b30a00a69bf63687e30cd882984ed444e753990cc1", size = 254672, upload-time = "2026-02-03T14:00:52.757Z" }, - { url = "https://files.pythonhosted.org/packages/31/ba/5d1957c76b40daff53971fe0adb84d9c2162b614280031d1d0653dd010c1/coverage-7.13.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ab6d72bffac9deb6e6cb0f61042e748de3f9f8e98afb0375a8e64b0b6e11746b", size = 251050, upload-time = "2026-02-03T14:00:54.332Z" }, - { url = "https://files.pythonhosted.org/packages/69/dc/dffdf3bfe9d32090f047d3c3085378558cb4eb6778cda7de414ad74581ed/coverage-7.13.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e129328ad1258e49cae0123a3b5fcb93d6c2fa90d540f0b4c7cdcdc019aaa3dc", size = 252801, upload-time = "2026-02-03T14:00:56.121Z" }, - { url = "https://files.pythonhosted.org/packages/87/51/cdf6198b0f2746e04511a30dc9185d7b8cdd895276c07bdb538e37f1cd50/coverage-7.13.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2213a8d88ed35459bda71597599d4eec7c2ebad201c88f0bfc2c26fd9b0dd2ea", size = 250763, upload-time = "2026-02-03T14:00:58.719Z" }, - { url = "https://files.pythonhosted.org/packages/d7/1a/596b7d62218c1d69f2475b69cc6b211e33c83c902f38ee6ae9766dd422da/coverage-7.13.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:00dd3f02de6d5f5c9c3d95e3e036c3c2e2a669f8bf2d3ceb92505c4ce7838f67", size = 250587, upload-time = "2026-02-03T14:01:01.197Z" }, - { url = "https://files.pythonhosted.org/packages/f7/46/52330d5841ff660f22c130b75f5e1dd3e352c8e7baef5e5fef6b14e3e991/coverage-7.13.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f9bada7bc660d20b23d7d312ebe29e927b655cf414dadcdb6335a2075695bd86", size = 252358, upload-time = "2026-02-03T14:01:02.824Z" }, - { url = "https://files.pythonhosted.org/packages/36/8a/e69a5be51923097ba7d5cff9724466e74fe486e9232020ba97c809a8b42b/coverage-7.13.3-cp313-cp313-win32.whl", hash = "sha256:75b3c0300f3fa15809bd62d9ca8b170eb21fcf0100eb4b4154d6dc8b3a5bbd43", size = 222007, upload-time = "2026-02-03T14:01:04.876Z" }, - { url = "https://files.pythonhosted.org/packages/0a/09/a5a069bcee0d613bdd48ee7637fa73bc09e7ed4342b26890f2df97cc9682/coverage-7.13.3-cp313-cp313-win_amd64.whl", hash = "sha256:a2f7589c6132c44c53f6e705e1a6677e2b7821378c22f7703b2cf5388d0d4587", size = 222812, upload-time = "2026-02-03T14:01:07.296Z" }, - { url = "https://files.pythonhosted.org/packages/3d/4f/d62ad7dfe32f9e3d4a10c178bb6f98b10b083d6e0530ca202b399371f6c1/coverage-7.13.3-cp313-cp313-win_arm64.whl", hash = "sha256:123ceaf2b9d8c614f01110f908a341e05b1b305d6b2ada98763b9a5a59756051", size = 221433, upload-time = "2026-02-03T14:01:09.156Z" }, - { url = "https://files.pythonhosted.org/packages/04/b2/4876c46d723d80b9c5b695f1a11bf5f7c3dabf540ec00d6edc076ff025e6/coverage-7.13.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:cc7fd0f726795420f3678ac82ff882c7fc33770bd0074463b5aef7293285ace9", size = 220162, upload-time = "2026-02-03T14:01:11.409Z" }, - { url = "https://files.pythonhosted.org/packages/fc/04/9942b64a0e0bdda2c109f56bda42b2a59d9d3df4c94b85a323c1cae9fc77/coverage-7.13.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d358dc408edc28730aed5477a69338e444e62fba0b7e9e4a131c505fadad691e", size = 220510, upload-time = "2026-02-03T14:01:13.038Z" }, - { url = "https://files.pythonhosted.org/packages/5a/82/5cfe1e81eae525b74669f9795f37eb3edd4679b873d79d1e6c1c14ee6c1c/coverage-7.13.3-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5d67b9ed6f7b5527b209b24b3df9f2e5bf0198c1bbf99c6971b0e2dcb7e2a107", size = 261801, upload-time = "2026-02-03T14:01:14.674Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ec/a553d7f742fd2cd12e36a16a7b4b3582d5934b496ef2b5ea8abeb10903d4/coverage-7.13.3-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:59224bfb2e9b37c1335ae35d00daa3a5b4e0b1a20f530be208fff1ecfa436f43", size = 263882, upload-time = "2026-02-03T14:01:16.343Z" }, - { url = "https://files.pythonhosted.org/packages/e1/58/8f54a2a93e3d675635bc406de1c9ac8d551312142ff52c9d71b5e533ad45/coverage-7.13.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9306b5299e31e31e0d3b908c66bcb6e7e3ddca143dea0266e9ce6c667346d3", size = 266306, upload-time = "2026-02-03T14:01:18.02Z" }, - { url = "https://files.pythonhosted.org/packages/1a/be/e593399fd6ea1f00aee79ebd7cc401021f218d34e96682a92e1bae092ff6/coverage-7.13.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:343aaeb5f8bb7bcd38620fd7bc56e6ee8207847d8c6103a1e7b72322d381ba4a", size = 261051, upload-time = "2026-02-03T14:01:19.757Z" }, - { url = "https://files.pythonhosted.org/packages/5c/e5/e9e0f6138b21bcdebccac36fbfde9cf15eb1bbcea9f5b1f35cd1f465fb91/coverage-7.13.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b2182129f4c101272ff5f2f18038d7b698db1bf8e7aa9e615cb48440899ad32e", size = 263868, upload-time = "2026-02-03T14:01:21.487Z" }, - { url = "https://files.pythonhosted.org/packages/9a/bf/de72cfebb69756f2d4a2dde35efcc33c47d85cd3ebdf844b3914aac2ef28/coverage-7.13.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:94d2ac94bd0cc57c5626f52f8c2fffed1444b5ae8c9fc68320306cc2b255e155", size = 261498, upload-time = "2026-02-03T14:01:23.097Z" }, - { url = "https://files.pythonhosted.org/packages/f2/91/4a2d313a70fc2e98ca53afd1c8ce67a89b1944cd996589a5b1fe7fbb3e5c/coverage-7.13.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:65436cde5ecabe26fb2f0bf598962f0a054d3f23ad529361326ac002c61a2a1e", size = 260394, upload-time = "2026-02-03T14:01:24.949Z" }, - { url = "https://files.pythonhosted.org/packages/40/83/25113af7cf6941e779eb7ed8de2a677865b859a07ccee9146d4cc06a03e3/coverage-7.13.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:db83b77f97129813dbd463a67e5335adc6a6a91db652cc085d60c2d512746f96", size = 262579, upload-time = "2026-02-03T14:01:26.703Z" }, - { url = "https://files.pythonhosted.org/packages/1e/19/a5f2b96262977e82fb9aabbe19b4d83561f5d063f18dde3e72f34ffc3b2f/coverage-7.13.3-cp313-cp313t-win32.whl", hash = "sha256:dfb428e41377e6b9ba1b0a32df6db5409cb089a0ed1d0a672dc4953ec110d84f", size = 222679, upload-time = "2026-02-03T14:01:28.553Z" }, - { url = "https://files.pythonhosted.org/packages/81/82/ef1747b88c87a5c7d7edc3704799ebd650189a9158e680a063308b6125ef/coverage-7.13.3-cp313-cp313t-win_amd64.whl", hash = "sha256:5badd7e596e6b0c89aa8ec6d37f4473e4357f982ce57f9a2942b0221cd9cf60c", size = 223740, upload-time = "2026-02-03T14:01:30.776Z" }, - { url = "https://files.pythonhosted.org/packages/1c/4c/a67c7bb5b560241c22736a9cb2f14c5034149ffae18630323fde787339e4/coverage-7.13.3-cp313-cp313t-win_arm64.whl", hash = "sha256:989aa158c0eb19d83c76c26f4ba00dbb272485c56e452010a3450bdbc9daafd9", size = 221996, upload-time = "2026-02-03T14:01:32.495Z" }, - { url = "https://files.pythonhosted.org/packages/5e/b3/677bb43427fed9298905106f39c6520ac75f746f81b8f01104526a8026e4/coverage-7.13.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c6f6169bbdbdb85aab8ac0392d776948907267fcc91deeacf6f9d55f7a83ae3b", size = 219513, upload-time = "2026-02-03T14:01:34.29Z" }, - { url = "https://files.pythonhosted.org/packages/42/53/290046e3bbf8986cdb7366a42dab3440b9983711eaff044a51b11006c67b/coverage-7.13.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2f5e731627a3d5ef11a2a35aa0c6f7c435867c7ccbc391268eb4f2ca5dbdcc10", size = 219850, upload-time = "2026-02-03T14:01:35.984Z" }, - { url = "https://files.pythonhosted.org/packages/ea/2b/ab41f10345ba2e49d5e299be8663be2b7db33e77ac1b85cd0af985ea6406/coverage-7.13.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9db3a3285d91c0b70fab9f39f0a4aa37d375873677efe4e71e58d8321e8c5d39", size = 250886, upload-time = "2026-02-03T14:01:38.287Z" }, - { url = "https://files.pythonhosted.org/packages/72/2d/b3f6913ee5a1d5cdd04106f257e5fac5d048992ffc2d9995d07b0f17739f/coverage-7.13.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:06e49c5897cb12e3f7ecdc111d44e97c4f6d0557b81a7a0204ed70a8b038f86f", size = 253393, upload-time = "2026-02-03T14:01:40.118Z" }, - { url = "https://files.pythonhosted.org/packages/f0/f6/b1f48810ffc6accf49a35b9943636560768f0812330f7456aa87dc39aff5/coverage-7.13.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb25061a66802df9fc13a9ba1967d25faa4dae0418db469264fd9860a921dde4", size = 254740, upload-time = "2026-02-03T14:01:42.413Z" }, - { url = "https://files.pythonhosted.org/packages/57/d0/e59c54f9be0b61808f6bc4c8c4346bd79f02dd6bbc3f476ef26124661f20/coverage-7.13.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:99fee45adbb1caeb914da16f70e557fb7ff6ddc9e4b14de665bd41af631367ef", size = 250905, upload-time = "2026-02-03T14:01:44.163Z" }, - { url = "https://files.pythonhosted.org/packages/d5/f7/5291bcdf498bafbee3796bb32ef6966e9915aebd4d0954123c8eae921c32/coverage-7.13.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:318002f1fd819bdc1651c619268aa5bc853c35fa5cc6d1e8c96bd9cd6c828b75", size = 252753, upload-time = "2026-02-03T14:01:45.974Z" }, - { url = "https://files.pythonhosted.org/packages/a0/a9/1dcafa918c281554dae6e10ece88c1add82db685be123e1b05c2056ff3fb/coverage-7.13.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:71295f2d1d170b9977dc386d46a7a1b7cbb30e5405492529b4c930113a33f895", size = 250716, upload-time = "2026-02-03T14:01:48.844Z" }, - { url = "https://files.pythonhosted.org/packages/44/bb/4ea4eabcce8c4f6235df6e059fbc5db49107b24c4bdffc44aee81aeca5a8/coverage-7.13.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5b1ad2e0dc672625c44bc4fe34514602a9fd8b10d52ddc414dc585f74453516c", size = 250530, upload-time = "2026-02-03T14:01:50.793Z" }, - { url = "https://files.pythonhosted.org/packages/6d/31/4a6c9e6a71367e6f923b27b528448c37f4e959b7e4029330523014691007/coverage-7.13.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b2beb64c145593a50d90db5c7178f55daeae129123b0d265bdb3cbec83e5194a", size = 252186, upload-time = "2026-02-03T14:01:52.607Z" }, - { url = "https://files.pythonhosted.org/packages/27/92/e1451ef6390a4f655dc42da35d9971212f7abbbcad0bdb7af4407897eb76/coverage-7.13.3-cp314-cp314-win32.whl", hash = "sha256:3d1aed4f4e837a832df2f3b4f68a690eede0de4560a2dbc214ea0bc55aabcdb4", size = 222253, upload-time = "2026-02-03T14:01:55.071Z" }, - { url = "https://files.pythonhosted.org/packages/8a/98/78885a861a88de020c32a2693487c37d15a9873372953f0c3c159d575a43/coverage-7.13.3-cp314-cp314-win_amd64.whl", hash = "sha256:9f9efbbaf79f935d5fbe3ad814825cbce4f6cdb3054384cb49f0c0f496125fa0", size = 223069, upload-time = "2026-02-03T14:01:56.95Z" }, - { url = "https://files.pythonhosted.org/packages/eb/fb/3784753a48da58a5337972abf7ca58b1fb0f1bda21bc7b4fae992fd28e47/coverage-7.13.3-cp314-cp314-win_arm64.whl", hash = "sha256:31b6e889c53d4e6687ca63706148049494aace140cffece1c4dc6acadb70a7b3", size = 221633, upload-time = "2026-02-03T14:01:58.758Z" }, - { url = "https://files.pythonhosted.org/packages/40/f9/75b732d9674d32cdbffe801ed5f770786dd1c97eecedef2125b0d25102dc/coverage-7.13.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c5e9787cec750793a19a28df7edd85ac4e49d3fb91721afcdc3b86f6c08d9aa8", size = 220243, upload-time = "2026-02-03T14:02:01.109Z" }, - { url = "https://files.pythonhosted.org/packages/cf/7e/2868ec95de5a65703e6f0c87407ea822d1feb3619600fbc3c1c4fa986090/coverage-7.13.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e5b86db331c682fd0e4be7098e6acee5e8a293f824d41487c667a93705d415ca", size = 220515, upload-time = "2026-02-03T14:02:02.862Z" }, - { url = "https://files.pythonhosted.org/packages/7d/eb/9f0d349652fced20bcaea0f67fc5777bd097c92369f267975732f3dc5f45/coverage-7.13.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:edc7754932682d52cf6e7a71806e529ecd5ce660e630e8bd1d37109a2e5f63ba", size = 261874, upload-time = "2026-02-03T14:02:04.727Z" }, - { url = "https://files.pythonhosted.org/packages/ee/a5/6619bc4a6c7b139b16818149a3e74ab2e21599ff9a7b6811b6afde99f8ec/coverage-7.13.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3a16d6398666510a6886f67f43d9537bfd0e13aca299688a19daa84f543122f", size = 264004, upload-time = "2026-02-03T14:02:06.634Z" }, - { url = "https://files.pythonhosted.org/packages/29/b7/90aa3fc645a50c6f07881fca4fd0ba21e3bfb6ce3a7078424ea3a35c74c9/coverage-7.13.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:303d38b19626c1981e1bb067a9928236d88eb0e4479b18a74812f05a82071508", size = 266408, upload-time = "2026-02-03T14:02:09.037Z" }, - { url = "https://files.pythonhosted.org/packages/62/55/08bb2a1e4dcbae384e638f0effef486ba5987b06700e481691891427d879/coverage-7.13.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:284e06eadfe15ddfee2f4ee56631f164ef897a7d7d5a15bca5f0bb88889fc5ba", size = 260977, upload-time = "2026-02-03T14:02:11.755Z" }, - { url = "https://files.pythonhosted.org/packages/9b/76/8bd4ae055a42d8fb5dd2230e5cf36ff2e05f85f2427e91b11a27fea52ed7/coverage-7.13.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d401f0864a1d3198422816878e4e84ca89ec1c1bf166ecc0ae01380a39b888cd", size = 263868, upload-time = "2026-02-03T14:02:13.565Z" }, - { url = "https://files.pythonhosted.org/packages/e3/f9/ba000560f11e9e32ec03df5aa8477242c2d95b379c99ac9a7b2e7fbacb1a/coverage-7.13.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3f379b02c18a64de78c4ccdddf1c81c2c5ae1956c72dacb9133d7dd7809794ab", size = 261474, upload-time = "2026-02-03T14:02:16.069Z" }, - { url = "https://files.pythonhosted.org/packages/90/4b/4de4de8f9ca7af4733bfcf4baa440121b7dbb3856daf8428ce91481ff63b/coverage-7.13.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:7a482f2da9086971efb12daca1d6547007ede3674ea06e16d7663414445c683e", size = 260317, upload-time = "2026-02-03T14:02:17.996Z" }, - { url = "https://files.pythonhosted.org/packages/05/71/5cd8436e2c21410ff70be81f738c0dddea91bcc3189b1517d26e0102ccb3/coverage-7.13.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:562136b0d401992118d9b49fbee5454e16f95f85b120a4226a04d816e33fe024", size = 262635, upload-time = "2026-02-03T14:02:20.405Z" }, - { url = "https://files.pythonhosted.org/packages/e7/f8/2834bb45bdd70b55a33ec354b8b5f6062fc90e5bb787e14385903a979503/coverage-7.13.3-cp314-cp314t-win32.whl", hash = "sha256:ca46e5c3be3b195098dd88711890b8011a9fa4feca942292bb84714ce5eab5d3", size = 223035, upload-time = "2026-02-03T14:02:22.323Z" }, - { url = "https://files.pythonhosted.org/packages/26/75/f8290f0073c00d9ae14056d2b84ab92dff21d5370e464cb6cb06f52bf580/coverage-7.13.3-cp314-cp314t-win_amd64.whl", hash = "sha256:06d316dbb3d9fd44cca05b2dbcfbef22948493d63a1f28e828d43e6cc505fed8", size = 224142, upload-time = "2026-02-03T14:02:24.143Z" }, - { url = "https://files.pythonhosted.org/packages/03/01/43ac78dfea8946c4a9161bbc034b5549115cb2b56781a4b574927f0d141a/coverage-7.13.3-cp314-cp314t-win_arm64.whl", hash = "sha256:299d66e9218193f9dc6e4880629ed7c4cd23486005166247c283fb98531656c3", size = 222166, upload-time = "2026-02-03T14:02:26.005Z" }, - { url = "https://files.pythonhosted.org/packages/7d/fb/70af542d2d938c778c9373ce253aa4116dbe7c0a5672f78b2b2ae0e1b94b/coverage-7.13.3-py3-none-any.whl", hash = "sha256:90a8af9dba6429b2573199622d72e0ebf024d6276f16abce394ad4d181bb0910", size = 211237, upload-time = "2026-02-03T14:02:27.986Z" }, +version = "7.13.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/ad/b59e5b451cf7172b8d1043dc0fa718f23aab379bc1521ee13d4bd9bfa960/coverage-7.13.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d490ba50c3f35dd7c17953c68f3270e7ccd1c6642e2d2afe2d8e720b98f5a053", size = 219278, upload-time = "2026-02-09T12:56:31.673Z" }, + { url = "https://files.pythonhosted.org/packages/f1/17/0cb7ca3de72e5f4ef2ec2fa0089beafbcaaaead1844e8b8a63d35173d77d/coverage-7.13.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:19bc3c88078789f8ef36acb014d7241961dbf883fd2533d18cb1e7a5b4e28b11", size = 219783, upload-time = "2026-02-09T12:56:33.104Z" }, + { url = "https://files.pythonhosted.org/packages/ab/63/325d8e5b11e0eaf6d0f6a44fad444ae58820929a9b0de943fa377fe73e85/coverage-7.13.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3998e5a32e62fdf410c0dbd3115df86297995d6e3429af80b8798aad894ca7aa", size = 250200, upload-time = "2026-02-09T12:56:34.474Z" }, + { url = "https://files.pythonhosted.org/packages/76/53/c16972708cbb79f2942922571a687c52bd109a7bd51175aeb7558dff2236/coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7", size = 252114, upload-time = "2026-02-09T12:56:35.749Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c2/7ab36d8b8cc412bec9ea2d07c83c48930eb4ba649634ba00cb7e4e0f9017/coverage-7.13.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3aa4e7b9e416774b21797365b358a6e827ffadaaca81b69ee02946852449f00", size = 254220, upload-time = "2026-02-09T12:56:37.796Z" }, + { url = "https://files.pythonhosted.org/packages/d6/4d/cf52c9a3322c89a0e6febdfbc83bb45c0ed3c64ad14081b9503adee702e7/coverage-7.13.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:71ca20079dd8f27fcf808817e281e90220475cd75115162218d0e27549f95fef", size = 256164, upload-time = "2026-02-09T12:56:39.016Z" }, + { url = "https://files.pythonhosted.org/packages/78/e9/eb1dd17bd6de8289df3580e967e78294f352a5df8a57ff4671ee5fc3dcd0/coverage-7.13.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e2f25215f1a359ab17320b47bcdaca3e6e6356652e8256f2441e4ef972052903", size = 250325, upload-time = "2026-02-09T12:56:40.668Z" }, + { url = "https://files.pythonhosted.org/packages/71/07/8c1542aa873728f72267c07278c5cc0ec91356daf974df21335ccdb46368/coverage-7.13.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d65b2d373032411e86960604dc4edac91fdfb5dca539461cf2cbe78327d1e64f", size = 251913, upload-time = "2026-02-09T12:56:41.97Z" }, + { url = "https://files.pythonhosted.org/packages/74/d7/c62e2c5e4483a748e27868e4c32ad3daa9bdddbba58e1bc7a15e252baa74/coverage-7.13.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94eb63f9b363180aff17de3e7c8760c3ba94664ea2695c52f10111244d16a299", size = 249974, upload-time = "2026-02-09T12:56:43.323Z" }, + { url = "https://files.pythonhosted.org/packages/98/9f/4c5c015a6e98ced54efd0f5cf8d31b88e5504ecb6857585fc0161bb1e600/coverage-7.13.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e856bf6616714c3a9fbc270ab54103f4e685ba236fa98c054e8f87f266c93505", size = 253741, upload-time = "2026-02-09T12:56:45.155Z" }, + { url = "https://files.pythonhosted.org/packages/bd/59/0f4eef89b9f0fcd9633b5d350016f54126ab49426a70ff4c4e87446cabdc/coverage-7.13.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:65dfcbe305c3dfe658492df2d85259e0d79ead4177f9ae724b6fb245198f55d6", size = 249695, upload-time = "2026-02-09T12:56:46.636Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2c/b7476f938deb07166f3eb281a385c262675d688ff4659ad56c6c6b8e2e70/coverage-7.13.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b507778ae8a4c915436ed5c2e05b4a6cecfa70f734e19c22a005152a11c7b6a9", size = 250599, upload-time = "2026-02-09T12:56:48.13Z" }, + { url = "https://files.pythonhosted.org/packages/b8/34/c3420709d9846ee3785b9f2831b4d94f276f38884032dca1457fa83f7476/coverage-7.13.4-cp311-cp311-win32.whl", hash = "sha256:784fc3cf8be001197b652d51d3fd259b1e2262888693a4636e18879f613a62a9", size = 221780, upload-time = "2026-02-09T12:56:50.479Z" }, + { url = "https://files.pythonhosted.org/packages/61/08/3d9c8613079d2b11c185b865de9a4c1a68850cfda2b357fae365cf609f29/coverage-7.13.4-cp311-cp311-win_amd64.whl", hash = "sha256:2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f", size = 222715, upload-time = "2026-02-09T12:56:51.815Z" }, + { url = "https://files.pythonhosted.org/packages/18/1a/54c3c80b2f056164cc0a6cdcb040733760c7c4be9d780fe655f356f433e4/coverage-7.13.4-cp311-cp311-win_arm64.whl", hash = "sha256:79e73a76b854d9c6088fe5d8b2ebe745f8681c55f7397c3c0a016192d681045f", size = 221385, upload-time = "2026-02-09T12:56:53.194Z" }, + { url = "https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459", size = 219449, upload-time = "2026-02-09T12:56:54.889Z" }, + { url = "https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3", size = 219810, upload-time = "2026-02-09T12:56:56.33Z" }, + { url = "https://files.pythonhosted.org/packages/78/72/2f372b726d433c9c35e56377cf1d513b4c16fe51841060d826b95caacec1/coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634", size = 251308, upload-time = "2026-02-09T12:56:57.858Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3", size = 254052, upload-time = "2026-02-09T12:56:59.754Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ac/45dc2e19a1939098d783c846e130b8f862fbb50d09e0af663988f2f21973/coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa", size = 255165, upload-time = "2026-02-09T12:57:01.287Z" }, + { url = "https://files.pythonhosted.org/packages/2d/4d/26d236ff35abc3b5e63540d3386e4c3b192168c1d96da5cb2f43c640970f/coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3", size = 257432, upload-time = "2026-02-09T12:57:02.637Z" }, + { url = "https://files.pythonhosted.org/packages/ec/55/14a966c757d1348b2e19caf699415a2a4c4f7feaa4bbc6326a51f5c7dd1b/coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a", size = 251716, upload-time = "2026-02-09T12:57:04.056Z" }, + { url = "https://files.pythonhosted.org/packages/77/33/50116647905837c66d28b2af1321b845d5f5d19be9655cb84d4a0ea806b4/coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7", size = 253089, upload-time = "2026-02-09T12:57:05.503Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b4/8efb11a46e3665d92635a56e4f2d4529de6d33f2cb38afd47d779d15fc99/coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc", size = 251232, upload-time = "2026-02-09T12:57:06.879Z" }, + { url = "https://files.pythonhosted.org/packages/51/24/8cd73dd399b812cc76bb0ac260e671c4163093441847ffe058ac9fda1e32/coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47", size = 255299, upload-time = "2026-02-09T12:57:08.245Z" }, + { url = "https://files.pythonhosted.org/packages/03/94/0a4b12f1d0e029ce1ccc1c800944a9984cbe7d678e470bb6d3c6bc38a0da/coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985", size = 250796, upload-time = "2026-02-09T12:57:10.142Z" }, + { url = "https://files.pythonhosted.org/packages/73/44/6002fbf88f6698ca034360ce474c406be6d5a985b3fdb3401128031eef6b/coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0", size = 252673, upload-time = "2026-02-09T12:57:12.197Z" }, + { url = "https://files.pythonhosted.org/packages/de/c6/a0279f7c00e786be75a749a5674e6fa267bcbd8209cd10c9a450c655dfa7/coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246", size = 221990, upload-time = "2026-02-09T12:57:14.085Z" }, + { url = "https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126", size = 222800, upload-time = "2026-02-09T12:57:15.944Z" }, + { url = "https://files.pythonhosted.org/packages/47/ac/92da44ad9a6f4e3a7debd178949d6f3769bedca33830ce9b1dcdab589a37/coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d", size = 221415, upload-time = "2026-02-09T12:57:17.497Z" }, + { url = "https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9", size = 219474, upload-time = "2026-02-09T12:57:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac", size = 219844, upload-time = "2026-02-09T12:57:20.66Z" }, + { url = "https://files.pythonhosted.org/packages/97/fd/7e859f8fab324cef6c4ad7cff156ca7c489fef9179d5749b0c8d321281c2/coverage-7.13.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea", size = 250832, upload-time = "2026-02-09T12:57:22.007Z" }, + { url = "https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b", size = 253434, upload-time = "2026-02-09T12:57:23.339Z" }, + { url = "https://files.pythonhosted.org/packages/5a/88/6728a7ad17428b18d836540630487231f5470fb82454871149502f5e5aa2/coverage-7.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525", size = 254676, upload-time = "2026-02-09T12:57:24.774Z" }, + { url = "https://files.pythonhosted.org/packages/7c/bc/21244b1b8cedf0dff0a2b53b208015fe798d5f2a8d5348dbfece04224fff/coverage-7.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242", size = 256807, upload-time = "2026-02-09T12:57:26.125Z" }, + { url = "https://files.pythonhosted.org/packages/97/a0/ddba7ed3251cff51006737a727d84e05b61517d1784a9988a846ba508877/coverage-7.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148", size = 251058, upload-time = "2026-02-09T12:57:27.614Z" }, + { url = "https://files.pythonhosted.org/packages/9b/55/e289addf7ff54d3a540526f33751951bf0878f3809b47f6dfb3def69c6f7/coverage-7.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a", size = 252805, upload-time = "2026-02-09T12:57:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/13/4e/cc276b1fa4a59be56d96f1dabddbdc30f4ba22e3b1cd42504c37b3313255/coverage-7.13.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23", size = 250766, upload-time = "2026-02-09T12:57:30.522Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/1093b8f93018f8b41a8cf29636c9292502f05e4a113d4d107d14a3acd044/coverage-7.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80", size = 254923, upload-time = "2026-02-09T12:57:31.946Z" }, + { url = "https://files.pythonhosted.org/packages/8b/55/ea2796da2d42257f37dbea1aab239ba9263b31bd91d5527cdd6db5efe174/coverage-7.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea", size = 250591, upload-time = "2026-02-09T12:57:33.842Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/7c4bb72aacf8af5020675aa633e59c1fbe296d22aed191b6a5b711eb2bc7/coverage-7.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a", size = 252364, upload-time = "2026-02-09T12:57:35.743Z" }, + { url = "https://files.pythonhosted.org/packages/5c/38/a8d2ec0146479c20bbaa7181b5b455a0c41101eed57f10dd19a78ab44c80/coverage-7.13.4-cp313-cp313-win32.whl", hash = "sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d", size = 222010, upload-time = "2026-02-09T12:57:37.25Z" }, + { url = "https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd", size = 222818, upload-time = "2026-02-09T12:57:38.734Z" }, + { url = "https://files.pythonhosted.org/packages/04/d1/934918a138c932c90d78301f45f677fb05c39a3112b96fd2c8e60503cdc7/coverage-7.13.4-cp313-cp313-win_arm64.whl", hash = "sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af", size = 221438, upload-time = "2026-02-09T12:57:40.223Z" }, + { url = "https://files.pythonhosted.org/packages/52/57/ee93ced533bcb3e6df961c0c6e42da2fc6addae53fb95b94a89b1e33ebd7/coverage-7.13.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d", size = 220165, upload-time = "2026-02-09T12:57:41.639Z" }, + { url = "https://files.pythonhosted.org/packages/c5/e0/969fc285a6fbdda49d91af278488d904dcd7651b2693872f0ff94e40e84a/coverage-7.13.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12", size = 220516, upload-time = "2026-02-09T12:57:44.215Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b8/9531944e16267e2735a30a9641ff49671f07e8138ecf1ca13db9fd2560c7/coverage-7.13.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b", size = 261804, upload-time = "2026-02-09T12:57:45.989Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f3/e63df6d500314a2a60390d1989240d5f27318a7a68fa30ad3806e2a9323e/coverage-7.13.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9", size = 263885, upload-time = "2026-02-09T12:57:47.42Z" }, + { url = "https://files.pythonhosted.org/packages/f3/67/7654810de580e14b37670b60a09c599fa348e48312db5b216d730857ffe6/coverage-7.13.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092", size = 266308, upload-time = "2026-02-09T12:57:49.345Z" }, + { url = "https://files.pythonhosted.org/packages/37/6f/39d41eca0eab3cc82115953ad41c4e77935286c930e8fad15eaed1389d83/coverage-7.13.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9", size = 267452, upload-time = "2026-02-09T12:57:50.811Z" }, + { url = "https://files.pythonhosted.org/packages/50/6d/39c0fbb8fc5cd4d2090811e553c2108cf5112e882f82505ee7495349a6bf/coverage-7.13.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26", size = 261057, upload-time = "2026-02-09T12:57:52.447Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a2/60010c669df5fa603bb5a97fb75407e191a846510da70ac657eb696b7fce/coverage-7.13.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2", size = 263875, upload-time = "2026-02-09T12:57:53.938Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d9/63b22a6bdbd17f1f96e9ed58604c2a6b0e72a9133e37d663bef185877cf6/coverage-7.13.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940", size = 261500, upload-time = "2026-02-09T12:57:56.012Z" }, + { url = "https://files.pythonhosted.org/packages/70/bf/69f86ba1ad85bc3ad240e4c0e57a2e620fbc0e1645a47b5c62f0e941ad7f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c", size = 265212, upload-time = "2026-02-09T12:57:57.5Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f2/5f65a278a8c2148731831574c73e42f57204243d33bedaaf18fa79c5958f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0", size = 260398, upload-time = "2026-02-09T12:57:59.027Z" }, + { url = "https://files.pythonhosted.org/packages/ef/80/6e8280a350ee9fea92f14b8357448a242dcaa243cb2c72ab0ca591f66c8c/coverage-7.13.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b", size = 262584, upload-time = "2026-02-09T12:58:01.129Z" }, + { url = "https://files.pythonhosted.org/packages/22/63/01ff182fc95f260b539590fb12c11ad3e21332c15f9799cb5e2386f71d9f/coverage-7.13.4-cp313-cp313t-win32.whl", hash = "sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9", size = 222688, upload-time = "2026-02-09T12:58:02.736Z" }, + { url = "https://files.pythonhosted.org/packages/a9/43/89de4ef5d3cd53b886afa114065f7e9d3707bdb3e5efae13535b46ae483d/coverage-7.13.4-cp313-cp313t-win_amd64.whl", hash = "sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd", size = 223746, upload-time = "2026-02-09T12:58:05.362Z" }, + { url = "https://files.pythonhosted.org/packages/35/39/7cf0aa9a10d470a5309b38b289b9bb07ddeac5d61af9b664fe9775a4cb3e/coverage-7.13.4-cp313-cp313t-win_arm64.whl", hash = "sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997", size = 222003, upload-time = "2026-02-09T12:58:06.952Z" }, + { url = "https://files.pythonhosted.org/packages/92/11/a9cf762bb83386467737d32187756a42094927150c3e107df4cb078e8590/coverage-7.13.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:300deaee342f90696ed186e3a00c71b5b3d27bffe9e827677954f4ee56969601", size = 219522, upload-time = "2026-02-09T12:58:08.623Z" }, + { url = "https://files.pythonhosted.org/packages/d3/28/56e6d892b7b052236d67c95f1936b6a7cf7c3e2634bf27610b8cbd7f9c60/coverage-7.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29e3220258d682b6226a9b0925bc563ed9a1ebcff3cad30f043eceea7eaf2689", size = 219855, upload-time = "2026-02-09T12:58:10.176Z" }, + { url = "https://files.pythonhosted.org/packages/e5/69/233459ee9eb0c0d10fcc2fe425a029b3fa5ce0f040c966ebce851d030c70/coverage-7.13.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:391ee8f19bef69210978363ca930f7328081c6a0152f1166c91f0b5fdd2a773c", size = 250887, upload-time = "2026-02-09T12:58:12.503Z" }, + { url = "https://files.pythonhosted.org/packages/06/90/2cdab0974b9b5bbc1623f7876b73603aecac11b8d95b85b5b86b32de5eab/coverage-7.13.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0dd7ab8278f0d58a0128ba2fca25824321f05d059c1441800e934ff2efa52129", size = 253396, upload-time = "2026-02-09T12:58:14.615Z" }, + { url = "https://files.pythonhosted.org/packages/ac/15/ea4da0f85bf7d7b27635039e649e99deb8173fe551096ea15017f7053537/coverage-7.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78cdf0d578b15148b009ccf18c686aa4f719d887e76e6b40c38ffb61d264a552", size = 254745, upload-time = "2026-02-09T12:58:16.162Z" }, + { url = "https://files.pythonhosted.org/packages/99/11/bb356e86920c655ca4d61daee4e2bbc7258f0a37de0be32d233b561134ff/coverage-7.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:48685fee12c2eb3b27c62f2658e7ea21e9c3239cba5a8a242801a0a3f6a8c62a", size = 257055, upload-time = "2026-02-09T12:58:17.892Z" }, + { url = "https://files.pythonhosted.org/packages/c9/0f/9ae1f8cb17029e09da06ca4e28c9e1d5c1c0a511c7074592e37e0836c915/coverage-7.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4e83efc079eb39480e6346a15a1bcb3e9b04759c5202d157e1dd4303cd619356", size = 250911, upload-time = "2026-02-09T12:58:19.495Z" }, + { url = "https://files.pythonhosted.org/packages/89/3a/adfb68558fa815cbc29747b553bc833d2150228f251b127f1ce97e48547c/coverage-7.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecae9737b72408d6a950f7e525f30aca12d4bd8dd95e37342e5beb3a2a8c4f71", size = 252754, upload-time = "2026-02-09T12:58:21.064Z" }, + { url = "https://files.pythonhosted.org/packages/32/b1/540d0c27c4e748bd3cd0bd001076ee416eda993c2bae47a73b7cc9357931/coverage-7.13.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ae4578f8528569d3cf303fef2ea569c7f4c4059a38c8667ccef15c6e1f118aa5", size = 250720, upload-time = "2026-02-09T12:58:22.622Z" }, + { url = "https://files.pythonhosted.org/packages/c7/95/383609462b3ffb1fe133014a7c84fc0dd01ed55ac6140fa1093b5af7ebb1/coverage-7.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6fdef321fdfbb30a197efa02d48fcd9981f0d8ad2ae8903ac318adc653f5df98", size = 254994, upload-time = "2026-02-09T12:58:24.548Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ba/1761138e86c81680bfc3c49579d66312865457f9fe405b033184e5793cb3/coverage-7.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b0f6ccf3dbe577170bebfce1318707d0e8c3650003cb4b3a9dd744575daa8b5", size = 250531, upload-time = "2026-02-09T12:58:26.271Z" }, + { url = "https://files.pythonhosted.org/packages/f8/8e/05900df797a9c11837ab59c4d6fe94094e029582aab75c3309a93e6fb4e3/coverage-7.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75fcd519f2a5765db3f0e391eb3b7d150cce1a771bf4c9f861aeab86c767a3c0", size = 252189, upload-time = "2026-02-09T12:58:27.807Z" }, + { url = "https://files.pythonhosted.org/packages/00/bd/29c9f2db9ea4ed2738b8a9508c35626eb205d51af4ab7bf56a21a2e49926/coverage-7.13.4-cp314-cp314-win32.whl", hash = "sha256:8e798c266c378da2bd819b0677df41ab46d78065fb2a399558f3f6cae78b2fbb", size = 222258, upload-time = "2026-02-09T12:58:29.441Z" }, + { url = "https://files.pythonhosted.org/packages/a7/4d/1f8e723f6829977410efeb88f73673d794075091c8c7c18848d273dc9d73/coverage-7.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505", size = 223073, upload-time = "2026-02-09T12:58:31.026Z" }, + { url = "https://files.pythonhosted.org/packages/51/5b/84100025be913b44e082ea32abcf1afbf4e872f5120b7a1cab1d331b1e13/coverage-7.13.4-cp314-cp314-win_arm64.whl", hash = "sha256:ad27098a189e5838900ce4c2a99f2fe42a0bf0c2093c17c69b45a71579e8d4a2", size = 221638, upload-time = "2026-02-09T12:58:32.599Z" }, + { url = "https://files.pythonhosted.org/packages/a7/e4/c884a405d6ead1370433dad1e3720216b4f9fd8ef5b64bfd984a2a60a11a/coverage-7.13.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:85480adfb35ffc32d40918aad81b89c69c9cc5661a9b8a81476d3e645321a056", size = 220246, upload-time = "2026-02-09T12:58:34.181Z" }, + { url = "https://files.pythonhosted.org/packages/81/5c/4d7ed8b23b233b0fffbc9dfec53c232be2e695468523242ea9fd30f97ad2/coverage-7.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:79be69cf7f3bf9b0deeeb062eab7ac7f36cd4cc4c4dd694bd28921ba4d8596cc", size = 220514, upload-time = "2026-02-09T12:58:35.704Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6f/3284d4203fd2f28edd73034968398cd2d4cb04ab192abc8cff007ea35679/coverage-7.13.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:caa421e2684e382c5d8973ac55e4f36bed6821a9bad5c953494de960c74595c9", size = 261877, upload-time = "2026-02-09T12:58:37.864Z" }, + { url = "https://files.pythonhosted.org/packages/09/aa/b672a647bbe1556a85337dc95bfd40d146e9965ead9cc2fe81bde1e5cbce/coverage-7.13.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14375934243ee05f56c45393fe2ce81fe5cc503c07cee2bdf1725fb8bef3ffaf", size = 264004, upload-time = "2026-02-09T12:58:39.492Z" }, + { url = "https://files.pythonhosted.org/packages/79/a1/aa384dbe9181f98bba87dd23dda436f0c6cf2e148aecbb4e50fc51c1a656/coverage-7.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25a41c3104d08edb094d9db0d905ca54d0cd41c928bb6be3c4c799a54753af55", size = 266408, upload-time = "2026-02-09T12:58:41.852Z" }, + { url = "https://files.pythonhosted.org/packages/53/5e/5150bf17b4019bc600799f376bb9606941e55bd5a775dc1e096b6ffea952/coverage-7.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f01afcff62bf9a08fb32b2c1d6e924236c0383c02c790732b6537269e466a72", size = 267544, upload-time = "2026-02-09T12:58:44.093Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ed/f1de5c675987a4a7a672250d2c5c9d73d289dbf13410f00ed7181d8017dd/coverage-7.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eb9078108fbf0bcdde37c3f4779303673c2fa1fe8f7956e68d447d0dd426d38a", size = 260980, upload-time = "2026-02-09T12:58:45.721Z" }, + { url = "https://files.pythonhosted.org/packages/b3/e3/fe758d01850aa172419a6743fe76ba8b92c29d181d4f676ffe2dae2ba631/coverage-7.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e086334e8537ddd17e5f16a344777c1ab8194986ec533711cbe6c41cde841b6", size = 263871, upload-time = "2026-02-09T12:58:47.334Z" }, + { url = "https://files.pythonhosted.org/packages/b6/76/b829869d464115e22499541def9796b25312b8cf235d3bb00b39f1675395/coverage-7.13.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:725d985c5ab621268b2edb8e50dfe57633dc69bda071abc470fed55a14935fd3", size = 261472, upload-time = "2026-02-09T12:58:48.995Z" }, + { url = "https://files.pythonhosted.org/packages/14/9e/caedb1679e73e2f6ad240173f55218488bfe043e38da577c4ec977489915/coverage-7.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3c06f0f1337c667b971ca2f975523347e63ec5e500b9aa5882d91931cd3ef750", size = 265210, upload-time = "2026-02-09T12:58:51.178Z" }, + { url = "https://files.pythonhosted.org/packages/3a/10/0dd02cb009b16ede425b49ec344aba13a6ae1dc39600840ea6abcb085ac4/coverage-7.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:590c0ed4bf8e85f745e6b805b2e1c457b2e33d5255dd9729743165253bc9ad39", size = 260319, upload-time = "2026-02-09T12:58:53.081Z" }, + { url = "https://files.pythonhosted.org/packages/92/8e/234d2c927af27c6d7a5ffad5bd2cf31634c46a477b4c7adfbfa66baf7ebb/coverage-7.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eb30bf180de3f632cd043322dad5751390e5385108b2807368997d1a92a509d0", size = 262638, upload-time = "2026-02-09T12:58:55.258Z" }, + { url = "https://files.pythonhosted.org/packages/2f/64/e5547c8ff6964e5965c35a480855911b61509cce544f4d442caa759a0702/coverage-7.13.4-cp314-cp314t-win32.whl", hash = "sha256:c4240e7eded42d131a2d2c4dec70374b781b043ddc79a9de4d55ca71f8e98aea", size = 223040, upload-time = "2026-02-09T12:58:56.936Z" }, + { url = "https://files.pythonhosted.org/packages/c7/96/38086d58a181aac86d503dfa9c47eb20715a79c3e3acbdf786e92e5c09a8/coverage-7.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4c7d3cc01e7350f2f0f6f7036caaf5673fb56b6998889ccfe9e1c1fe75a9c932", size = 224148, upload-time = "2026-02-09T12:58:58.645Z" }, + { url = "https://files.pythonhosted.org/packages/ce/72/8d10abd3740a0beb98c305e0c3faf454366221c0f37a8bcf8f60020bb65a/coverage-7.13.4-cp314-cp314t-win_arm64.whl", hash = "sha256:23e3f687cf945070d1c90f85db66d11e3025665d8dafa831301a0e0038f3db9b", size = 222172, upload-time = "2026-02-09T12:59:00.396Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" }, ] [package.optional-dependencies] @@ -624,10 +636,10 @@ wheels = [ [[package]] name = "cuda-pathfinder" -version = "1.3.3" +version = "1.3.4" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/02/4dbe7568a42e46582248942f54dc64ad094769532adbe21e525e4edf7bc4/cuda_pathfinder-1.3.3-py3-none-any.whl", hash = "sha256:9984b664e404f7c134954a771be8775dfd6180ea1e1aef4a5a37d4be05d9bbb1", size = 27154, upload-time = "2025-12-04T22:35:08.996Z" }, + { url = "https://files.pythonhosted.org/packages/b8/5e/db279a3bfbd18d59d0598922a3b3c1454908d0969e8372260afec9736376/cuda_pathfinder-1.3.4-py3-none-any.whl", hash = "sha256:fb983f6e0d43af27ef486e14d5989b5f904ef45cedf40538bfdcbffa6bb01fb2", size = 30878, upload-time = "2026-02-11T18:50:31.008Z" }, ] [[package]] @@ -756,11 +768,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.20.3" +version = "3.24.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/65/ce7f1b70157833bf3cb851b556a37d4547ceafc158aa9b34b36782f23696/filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1", size = 19485, upload-time = "2026-01-09T17:55:05.421Z" } +sdist = { url = "https://files.pythonhosted.org/packages/02/a8/dae62680be63cbb3ff87cfa2f51cf766269514ea5488479d42fec5aa6f3a/filelock-3.24.2.tar.gz", hash = "sha256:c22803117490f156e59fafce621f0550a7a853e2bbf4f87f112b11d469b6c81b", size = 37601, upload-time = "2026-02-16T02:50:45.614Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" }, + { url = "https://files.pythonhosted.org/packages/e7/04/a94ebfb4eaaa08db56725a40de2887e95de4e8641b9e902c311bfa00aa39/filelock-3.24.2-py3-none-any.whl", hash = "sha256:667d7dc0b7d1e1064dd5f8f8e80bdac157a6482e8d2e02cd16fd3b6b33bd6556", size = 24152, upload-time = "2026-02-16T02:50:44Z" }, ] [[package]] @@ -1403,7 +1415,7 @@ wheels = [ [[package]] name = "jupyterlab" -version = "4.5.3" +version = "4.5.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "async-lru" }, @@ -1420,9 +1432,9 @@ dependencies = [ { name = "tornado" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3e/76/393eae3349f9a39bf21f8f5406e5244d36e2bfc932049b6070c271f92764/jupyterlab-4.5.3.tar.gz", hash = "sha256:4a159f71067cb38e4a82e86a42de8e7e926f384d7f2291964f282282096d27e8", size = 23939231, upload-time = "2026-01-23T15:04:25.768Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/6b/21af7c0512bdf67e0c54c121779a1f2a97a164a7657e13fced79db8fa5a0/jupyterlab-4.5.4.tar.gz", hash = "sha256:c215f48d8e4582bd2920ad61cc6a40d8ebfef7e5a517ae56b8a9413c9789fdfb", size = 23943597, upload-time = "2026-02-11T00:26:55.308Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/9a/0bf9a7a45f0006d7ff4fdc4fc313de4255acab02bf4db1887c65f0472c01/jupyterlab-4.5.3-py3-none-any.whl", hash = "sha256:63c9f3a48de72ba00df766ad6eed416394f5bb883829f11eeff0872302520ba7", size = 12391761, upload-time = "2026-01-23T15:04:21.214Z" }, + { url = "https://files.pythonhosted.org/packages/f5/9f/a70972ece62ead2d81acc6223188f6d18a92f665ccce17796a0cdea4fcf5/jupyterlab-4.5.4-py3-none-any.whl", hash = "sha256:cc233f70539728534669fb0015331f2a3a87656207b3bb2d07916e9289192f12", size = 12391867, upload-time = "2026-02-11T00:26:51.23Z" }, ] [[package]] @@ -1609,11 +1621,11 @@ wheels = [ [[package]] name = "markdown" -version = "3.10.1" +version = "3.10.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b7/b1/af95bcae8549f1f3fd70faacb29075826a0d689a27f232e8cee315efa053/markdown-3.10.1.tar.gz", hash = "sha256:1c19c10bd5c14ac948c53d0d762a04e2fa35a6d58a6b7b1e6bfcbe6fefc0001a", size = 365402, upload-time = "2026-01-21T18:09:28.206Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/59/1b/6ef961f543593969d25b2afe57a3564200280528caa9bd1082eecdd7b3bc/markdown-3.10.1-py3-none-any.whl", hash = "sha256:867d788939fe33e4b736426f5b9f651ad0c0ae0ecf89df0ca5d1176c70812fe3", size = 107684, upload-time = "2026-01-21T18:09:27.203Z" }, + { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, ] [[package]] @@ -2170,11 +2182,11 @@ wheels = [ [[package]] name = "parso" -version = "0.8.5" +version = "0.8.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d4/de/53e0bcf53d13e005bd8c92e7855142494f41171b34c2536b86187474184d/parso-0.8.5.tar.gz", hash = "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a", size = 401205, upload-time = "2025-08-23T15:15:28.028Z" } +sdist = { url = "https://files.pythonhosted.org/packages/81/76/a1e769043c0c0c9fe391b702539d594731a4362334cdf4dc25d0c09761e7/parso-0.8.6.tar.gz", hash = "sha256:2b9a0332696df97d454fa67b81618fd69c35a7b90327cbe6ba5c92d2c68a7bfd", size = 401621, upload-time = "2026-02-09T15:45:24.425Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887", size = 106668, upload-time = "2025-08-23T15:15:25.663Z" }, + { url = "https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl", hash = "sha256:2c549f800b70a5c4952197248825584cb00f033b29c692671d3bf08bf380baff", size = 106894, upload-time = "2026-02-09T15:45:21.391Z" }, ] [[package]] @@ -2204,89 +2216,89 @@ wheels = [ [[package]] name = "pillow" -version = "12.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/02/d52c733a2452ef1ffcc123b68e6606d07276b0e358db70eabad7e40042b7/pillow-12.1.0.tar.gz", hash = "sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9", size = 46977283, upload-time = "2026-01-02T09:13:29.892Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/c4/bf8328039de6cc22182c3ef007a2abfbbdab153661c0a9aa78af8d706391/pillow-12.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:a83e0850cb8f5ac975291ebfc4170ba481f41a28065277f7f735c202cd8e0af3", size = 5304057, upload-time = "2026-01-02T09:10:46.627Z" }, - { url = "https://files.pythonhosted.org/packages/43/06/7264c0597e676104cc22ca73ee48f752767cd4b1fe084662620b17e10120/pillow-12.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b6e53e82ec2db0717eabb276aa56cf4e500c9a7cec2c2e189b55c24f65a3e8c0", size = 4657811, upload-time = "2026-01-02T09:10:49.548Z" }, - { url = "https://files.pythonhosted.org/packages/72/64/f9189e44474610daf83da31145fa56710b627b5c4c0b9c235e34058f6b31/pillow-12.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:40a8e3b9e8773876d6e30daed22f016509e3987bab61b3b7fe309d7019a87451", size = 6232243, upload-time = "2026-01-02T09:10:51.62Z" }, - { url = "https://files.pythonhosted.org/packages/ef/30/0df458009be6a4caca4ca2c52975e6275c387d4e5c95544e34138b41dc86/pillow-12.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:800429ac32c9b72909c671aaf17ecd13110f823ddb7db4dfef412a5587c2c24e", size = 8037872, upload-time = "2026-01-02T09:10:53.446Z" }, - { url = "https://files.pythonhosted.org/packages/e4/86/95845d4eda4f4f9557e25381d70876aa213560243ac1a6d619c46caaedd9/pillow-12.1.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b022eaaf709541b391ee069f0022ee5b36c709df71986e3f7be312e46f42c84", size = 6345398, upload-time = "2026-01-02T09:10:55.426Z" }, - { url = "https://files.pythonhosted.org/packages/5c/1f/8e66ab9be3aaf1435bc03edd1ebdf58ffcd17f7349c1d970cafe87af27d9/pillow-12.1.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f345e7bc9d7f368887c712aa5054558bad44d2a301ddf9248599f4161abc7c0", size = 7034667, upload-time = "2026-01-02T09:10:57.11Z" }, - { url = "https://files.pythonhosted.org/packages/f9/f6/683b83cb9b1db1fb52b87951b1c0b99bdcfceaa75febf11406c19f82cb5e/pillow-12.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d70347c8a5b7ccd803ec0c85c8709f036e6348f1e6a5bf048ecd9c64d3550b8b", size = 6458743, upload-time = "2026-01-02T09:10:59.331Z" }, - { url = "https://files.pythonhosted.org/packages/9a/7d/de833d63622538c1d58ce5395e7c6cb7e7dce80decdd8bde4a484e095d9f/pillow-12.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1fcc52d86ce7a34fd17cb04e87cfdb164648a3662a6f20565910a99653d66c18", size = 7159342, upload-time = "2026-01-02T09:11:01.82Z" }, - { url = "https://files.pythonhosted.org/packages/8c/40/50d86571c9e5868c42b81fe7da0c76ca26373f3b95a8dd675425f4a92ec1/pillow-12.1.0-cp311-cp311-win32.whl", hash = "sha256:3ffaa2f0659e2f740473bcf03c702c39a8d4b2b7ffc629052028764324842c64", size = 6328655, upload-time = "2026-01-02T09:11:04.556Z" }, - { url = "https://files.pythonhosted.org/packages/6c/af/b1d7e301c4cd26cd45d4af884d9ee9b6fab893b0ad2450d4746d74a6968c/pillow-12.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:806f3987ffe10e867bab0ddad45df1148a2b98221798457fa097ad85d6e8bc75", size = 7031469, upload-time = "2026-01-02T09:11:06.538Z" }, - { url = "https://files.pythonhosted.org/packages/48/36/d5716586d887fb2a810a4a61518a327a1e21c8b7134c89283af272efe84b/pillow-12.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:9f5fefaca968e700ad1a4a9de98bf0869a94e397fe3524c4c9450c1445252304", size = 2452515, upload-time = "2026-01-02T09:11:08.226Z" }, - { url = "https://files.pythonhosted.org/packages/20/31/dc53fe21a2f2996e1b7d92bf671cdb157079385183ef7c1ae08b485db510/pillow-12.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a332ac4ccb84b6dde65dbace8431f3af08874bf9770719d32a635c4ef411b18b", size = 5262642, upload-time = "2026-01-02T09:11:10.138Z" }, - { url = "https://files.pythonhosted.org/packages/ab/c1/10e45ac9cc79419cedf5121b42dcca5a50ad2b601fa080f58c22fb27626e/pillow-12.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:907bfa8a9cb790748a9aa4513e37c88c59660da3bcfffbd24a7d9e6abf224551", size = 4657464, upload-time = "2026-01-02T09:11:12.319Z" }, - { url = "https://files.pythonhosted.org/packages/ad/26/7b82c0ab7ef40ebede7a97c72d473bda5950f609f8e0c77b04af574a0ddb/pillow-12.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efdc140e7b63b8f739d09a99033aa430accce485ff78e6d311973a67b6bf3208", size = 6234878, upload-time = "2026-01-02T09:11:14.096Z" }, - { url = "https://files.pythonhosted.org/packages/76/25/27abc9792615b5e886ca9411ba6637b675f1b77af3104710ac7353fe5605/pillow-12.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bef9768cab184e7ae6e559c032e95ba8d07b3023c289f79a2bd36e8bf85605a5", size = 8044868, upload-time = "2026-01-02T09:11:15.903Z" }, - { url = "https://files.pythonhosted.org/packages/0a/ea/f200a4c36d836100e7bc738fc48cd963d3ba6372ebc8298a889e0cfc3359/pillow-12.1.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:742aea052cf5ab5034a53c3846165bc3ce88d7c38e954120db0ab867ca242661", size = 6349468, upload-time = "2026-01-02T09:11:17.631Z" }, - { url = "https://files.pythonhosted.org/packages/11/8f/48d0b77ab2200374c66d344459b8958c86693be99526450e7aee714e03e4/pillow-12.1.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6dfc2af5b082b635af6e08e0d1f9f1c4e04d17d4e2ca0ef96131e85eda6eb17", size = 7041518, upload-time = "2026-01-02T09:11:19.389Z" }, - { url = "https://files.pythonhosted.org/packages/1d/23/c281182eb986b5d31f0a76d2a2c8cd41722d6fb8ed07521e802f9bba52de/pillow-12.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:609e89d9f90b581c8d16358c9087df76024cf058fa693dd3e1e1620823f39670", size = 6462829, upload-time = "2026-01-02T09:11:21.28Z" }, - { url = "https://files.pythonhosted.org/packages/25/ef/7018273e0faac099d7b00982abdcc39142ae6f3bd9ceb06de09779c4a9d6/pillow-12.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43b4899cfd091a9693a1278c4982f3e50f7fb7cff5153b05174b4afc9593b616", size = 7166756, upload-time = "2026-01-02T09:11:23.559Z" }, - { url = "https://files.pythonhosted.org/packages/8f/c8/993d4b7ab2e341fe02ceef9576afcf5830cdec640be2ac5bee1820d693d4/pillow-12.1.0-cp312-cp312-win32.whl", hash = "sha256:aa0c9cc0b82b14766a99fbe6084409972266e82f459821cd26997a488a7261a7", size = 6328770, upload-time = "2026-01-02T09:11:25.661Z" }, - { url = "https://files.pythonhosted.org/packages/a7/87/90b358775a3f02765d87655237229ba64a997b87efa8ccaca7dd3e36e7a7/pillow-12.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:d70534cea9e7966169ad29a903b99fc507e932069a881d0965a1a84bb57f6c6d", size = 7033406, upload-time = "2026-01-02T09:11:27.474Z" }, - { url = "https://files.pythonhosted.org/packages/5d/cf/881b457eccacac9e5b2ddd97d5071fb6d668307c57cbf4e3b5278e06e536/pillow-12.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:65b80c1ee7e14a87d6a068dd3b0aea268ffcabfe0498d38661b00c5b4b22e74c", size = 2452612, upload-time = "2026-01-02T09:11:29.309Z" }, - { url = "https://files.pythonhosted.org/packages/dd/c7/2530a4aa28248623e9d7f27316b42e27c32ec410f695929696f2e0e4a778/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7b5dd7cbae20285cdb597b10eb5a2c13aa9de6cde9bb64a3c1317427b1db1ae1", size = 4062543, upload-time = "2026-01-02T09:11:31.566Z" }, - { url = "https://files.pythonhosted.org/packages/8f/1f/40b8eae823dc1519b87d53c30ed9ef085506b05281d313031755c1705f73/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:29a4cef9cb672363926f0470afc516dbf7305a14d8c54f7abbb5c199cd8f8179", size = 4138373, upload-time = "2026-01-02T09:11:33.367Z" }, - { url = "https://files.pythonhosted.org/packages/d4/77/6fa60634cf06e52139fd0e89e5bbf055e8166c691c42fb162818b7fda31d/pillow-12.1.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:681088909d7e8fa9e31b9799aaa59ba5234c58e5e4f1951b4c4d1082a2e980e0", size = 3601241, upload-time = "2026-01-02T09:11:35.011Z" }, - { url = "https://files.pythonhosted.org/packages/4f/bf/28ab865de622e14b747f0cd7877510848252d950e43002e224fb1c9ababf/pillow-12.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:983976c2ab753166dc66d36af6e8ec15bb511e4a25856e2227e5f7e00a160587", size = 5262410, upload-time = "2026-01-02T09:11:36.682Z" }, - { url = "https://files.pythonhosted.org/packages/1c/34/583420a1b55e715937a85bd48c5c0991598247a1fd2eb5423188e765ea02/pillow-12.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:db44d5c160a90df2d24a24760bbd37607d53da0b34fb546c4c232af7192298ac", size = 4657312, upload-time = "2026-01-02T09:11:38.535Z" }, - { url = "https://files.pythonhosted.org/packages/1d/fd/f5a0896839762885b3376ff04878f86ab2b097c2f9a9cdccf4eda8ba8dc0/pillow-12.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6b7a9d1db5dad90e2991645874f708e87d9a3c370c243c2d7684d28f7e133e6b", size = 6232605, upload-time = "2026-01-02T09:11:40.602Z" }, - { url = "https://files.pythonhosted.org/packages/98/aa/938a09d127ac1e70e6ed467bd03834350b33ef646b31edb7452d5de43792/pillow-12.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6258f3260986990ba2fa8a874f8b6e808cf5abb51a94015ca3dc3c68aa4f30ea", size = 8041617, upload-time = "2026-01-02T09:11:42.721Z" }, - { url = "https://files.pythonhosted.org/packages/17/e8/538b24cb426ac0186e03f80f78bc8dc7246c667f58b540bdd57c71c9f79d/pillow-12.1.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e115c15e3bc727b1ca3e641a909f77f8ca72a64fff150f666fcc85e57701c26c", size = 6346509, upload-time = "2026-01-02T09:11:44.955Z" }, - { url = "https://files.pythonhosted.org/packages/01/9a/632e58ec89a32738cabfd9ec418f0e9898a2b4719afc581f07c04a05e3c9/pillow-12.1.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6741e6f3074a35e47c77b23a4e4f2d90db3ed905cb1c5e6e0d49bff2045632bc", size = 7038117, upload-time = "2026-01-02T09:11:46.736Z" }, - { url = "https://files.pythonhosted.org/packages/c7/a2/d40308cf86eada842ca1f3ffa45d0ca0df7e4ab33c83f81e73f5eaed136d/pillow-12.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:935b9d1aed48fcfb3f838caac506f38e29621b44ccc4f8a64d575cb1b2a88644", size = 6460151, upload-time = "2026-01-02T09:11:48.625Z" }, - { url = "https://files.pythonhosted.org/packages/f1/88/f5b058ad6453a085c5266660a1417bdad590199da1b32fb4efcff9d33b05/pillow-12.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5fee4c04aad8932da9f8f710af2c1a15a83582cfb884152a9caa79d4efcdbf9c", size = 7164534, upload-time = "2026-01-02T09:11:50.445Z" }, - { url = "https://files.pythonhosted.org/packages/19/ce/c17334caea1db789163b5d855a5735e47995b0b5dc8745e9a3605d5f24c0/pillow-12.1.0-cp313-cp313-win32.whl", hash = "sha256:a786bf667724d84aa29b5db1c61b7bfdde380202aaca12c3461afd6b71743171", size = 6332551, upload-time = "2026-01-02T09:11:52.234Z" }, - { url = "https://files.pythonhosted.org/packages/e5/07/74a9d941fa45c90a0d9465098fe1ec85de3e2afbdc15cc4766622d516056/pillow-12.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:461f9dfdafa394c59cd6d818bdfdbab4028b83b02caadaff0ffd433faf4c9a7a", size = 7040087, upload-time = "2026-01-02T09:11:54.822Z" }, - { url = "https://files.pythonhosted.org/packages/88/09/c99950c075a0e9053d8e880595926302575bc742b1b47fe1bbcc8d388d50/pillow-12.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:9212d6b86917a2300669511ed094a9406888362e085f2431a7da985a6b124f45", size = 2452470, upload-time = "2026-01-02T09:11:56.522Z" }, - { url = "https://files.pythonhosted.org/packages/b5/ba/970b7d85ba01f348dee4d65412476321d40ee04dcb51cd3735b9dc94eb58/pillow-12.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:00162e9ca6d22b7c3ee8e61faa3c3253cd19b6a37f126cad04f2f88b306f557d", size = 5264816, upload-time = "2026-01-02T09:11:58.227Z" }, - { url = "https://files.pythonhosted.org/packages/10/60/650f2fb55fdba7a510d836202aa52f0baac633e50ab1cf18415d332188fb/pillow-12.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7d6daa89a00b58c37cb1747ec9fb7ac3bc5ffd5949f5888657dfddde6d1312e0", size = 4660472, upload-time = "2026-01-02T09:12:00.798Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c0/5273a99478956a099d533c4f46cbaa19fd69d606624f4334b85e50987a08/pillow-12.1.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2479c7f02f9d505682dc47df8c0ea1fc5e264c4d1629a5d63fe3e2334b89554", size = 6268974, upload-time = "2026-01-02T09:12:02.572Z" }, - { url = "https://files.pythonhosted.org/packages/b4/26/0bf714bc2e73d5267887d47931d53c4ceeceea6978148ed2ab2a4e6463c4/pillow-12.1.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f188d580bd870cda1e15183790d1cc2fa78f666e76077d103edf048eed9c356e", size = 8073070, upload-time = "2026-01-02T09:12:04.75Z" }, - { url = "https://files.pythonhosted.org/packages/43/cf/1ea826200de111a9d65724c54f927f3111dc5ae297f294b370a670c17786/pillow-12.1.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0fde7ec5538ab5095cc02df38ee99b0443ff0e1c847a045554cf5f9af1f4aa82", size = 6380176, upload-time = "2026-01-02T09:12:06.626Z" }, - { url = "https://files.pythonhosted.org/packages/03/e0/7938dd2b2013373fd85d96e0f38d62b7a5a262af21ac274250c7ca7847c9/pillow-12.1.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ed07dca4a8464bada6139ab38f5382f83e5f111698caf3191cb8dbf27d908b4", size = 7067061, upload-time = "2026-01-02T09:12:08.624Z" }, - { url = "https://files.pythonhosted.org/packages/86/ad/a2aa97d37272a929a98437a8c0ac37b3cf012f4f8721e1bd5154699b2518/pillow-12.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f45bd71d1fa5e5749587613037b172e0b3b23159d1c00ef2fc920da6f470e6f0", size = 6491824, upload-time = "2026-01-02T09:12:10.488Z" }, - { url = "https://files.pythonhosted.org/packages/a4/44/80e46611b288d51b115826f136fb3465653c28f491068a72d3da49b54cd4/pillow-12.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:277518bf4fe74aa91489e1b20577473b19ee70fb97c374aa50830b279f25841b", size = 7190911, upload-time = "2026-01-02T09:12:12.772Z" }, - { url = "https://files.pythonhosted.org/packages/86/77/eacc62356b4cf81abe99ff9dbc7402750044aed02cfd6a503f7c6fc11f3e/pillow-12.1.0-cp313-cp313t-win32.whl", hash = "sha256:7315f9137087c4e0ee73a761b163fc9aa3b19f5f606a7fc08d83fd3e4379af65", size = 6336445, upload-time = "2026-01-02T09:12:14.775Z" }, - { url = "https://files.pythonhosted.org/packages/e7/3c/57d81d0b74d218706dafccb87a87ea44262c43eef98eb3b164fd000e0491/pillow-12.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:0ddedfaa8b5f0b4ffbc2fa87b556dc59f6bb4ecb14a53b33f9189713ae8053c0", size = 7045354, upload-time = "2026-01-02T09:12:16.599Z" }, - { url = "https://files.pythonhosted.org/packages/ac/82/8b9b97bba2e3576a340f93b044a3a3a09841170ab4c1eb0d5c93469fd32f/pillow-12.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:80941e6d573197a0c28f394753de529bb436b1ca990ed6e765cf42426abc39f8", size = 2454547, upload-time = "2026-01-02T09:12:18.704Z" }, - { url = "https://files.pythonhosted.org/packages/8c/87/bdf971d8bbcf80a348cc3bacfcb239f5882100fe80534b0ce67a784181d8/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:5cb7bc1966d031aec37ddb9dcf15c2da5b2e9f7cc3ca7c54473a20a927e1eb91", size = 4062533, upload-time = "2026-01-02T09:12:20.791Z" }, - { url = "https://files.pythonhosted.org/packages/ff/4f/5eb37a681c68d605eb7034c004875c81f86ec9ef51f5be4a63eadd58859a/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:97e9993d5ed946aba26baf9c1e8cf18adbab584b99f452ee72f7ee8acb882796", size = 4138546, upload-time = "2026-01-02T09:12:23.664Z" }, - { url = "https://files.pythonhosted.org/packages/11/6d/19a95acb2edbace40dcd582d077b991646b7083c41b98da4ed7555b59733/pillow-12.1.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:414b9a78e14ffeb98128863314e62c3f24b8a86081066625700b7985b3f529bd", size = 3601163, upload-time = "2026-01-02T09:12:26.338Z" }, - { url = "https://files.pythonhosted.org/packages/fc/36/2b8138e51cb42e4cc39c3297713455548be855a50558c3ac2beebdc251dd/pillow-12.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e6bdb408f7c9dd2a5ff2b14a3b0bb6d4deb29fb9961e6eb3ae2031ae9a5cec13", size = 5266086, upload-time = "2026-01-02T09:12:28.782Z" }, - { url = "https://files.pythonhosted.org/packages/53/4b/649056e4d22e1caa90816bf99cef0884aed607ed38075bd75f091a607a38/pillow-12.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3413c2ae377550f5487991d444428f1a8ae92784aac79caa8b1e3b89b175f77e", size = 4657344, upload-time = "2026-01-02T09:12:31.117Z" }, - { url = "https://files.pythonhosted.org/packages/6c/6b/c5742cea0f1ade0cd61485dc3d81f05261fc2276f537fbdc00802de56779/pillow-12.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e5dcbe95016e88437ecf33544ba5db21ef1b8dd6e1b434a2cb2a3d605299e643", size = 6232114, upload-time = "2026-01-02T09:12:32.936Z" }, - { url = "https://files.pythonhosted.org/packages/bf/8f/9f521268ce22d63991601aafd3d48d5ff7280a246a1ef62d626d67b44064/pillow-12.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d0a7735df32ccbcc98b98a1ac785cc4b19b580be1bdf0aeb5c03223220ea09d5", size = 8042708, upload-time = "2026-01-02T09:12:34.78Z" }, - { url = "https://files.pythonhosted.org/packages/1a/eb/257f38542893f021502a1bbe0c2e883c90b5cff26cc33b1584a841a06d30/pillow-12.1.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c27407a2d1b96774cbc4a7594129cc027339fd800cd081e44497722ea1179de", size = 6347762, upload-time = "2026-01-02T09:12:36.748Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5a/8ba375025701c09b309e8d5163c5a4ce0102fa86bbf8800eb0d7ac87bc51/pillow-12.1.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15c794d74303828eaa957ff8070846d0efe8c630901a1c753fdc63850e19ecd9", size = 7039265, upload-time = "2026-01-02T09:12:39.082Z" }, - { url = "https://files.pythonhosted.org/packages/cf/dc/cf5e4cdb3db533f539e88a7bbf9f190c64ab8a08a9bc7a4ccf55067872e4/pillow-12.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c990547452ee2800d8506c4150280757f88532f3de2a58e3022e9b179107862a", size = 6462341, upload-time = "2026-01-02T09:12:40.946Z" }, - { url = "https://files.pythonhosted.org/packages/d0/47/0291a25ac9550677e22eda48510cfc4fa4b2ef0396448b7fbdc0a6946309/pillow-12.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b63e13dd27da389ed9475b3d28510f0f954bca0041e8e551b2a4eb1eab56a39a", size = 7165395, upload-time = "2026-01-02T09:12:42.706Z" }, - { url = "https://files.pythonhosted.org/packages/4f/4c/e005a59393ec4d9416be06e6b45820403bb946a778e39ecec62f5b2b991e/pillow-12.1.0-cp314-cp314-win32.whl", hash = "sha256:1a949604f73eb07a8adab38c4fe50791f9919344398bdc8ac6b307f755fc7030", size = 6431413, upload-time = "2026-01-02T09:12:44.944Z" }, - { url = "https://files.pythonhosted.org/packages/1c/af/f23697f587ac5f9095d67e31b81c95c0249cd461a9798a061ed6709b09b5/pillow-12.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:4f9f6a650743f0ddee5593ac9e954ba1bdbc5e150bc066586d4f26127853ab94", size = 7176779, upload-time = "2026-01-02T09:12:46.727Z" }, - { url = "https://files.pythonhosted.org/packages/b3/36/6a51abf8599232f3e9afbd16d52829376a68909fe14efe29084445db4b73/pillow-12.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:808b99604f7873c800c4840f55ff389936ef1948e4e87645eaf3fccbc8477ac4", size = 2543105, upload-time = "2026-01-02T09:12:49.243Z" }, - { url = "https://files.pythonhosted.org/packages/82/54/2e1dd20c8749ff225080d6ba465a0cab4387f5db0d1c5fb1439e2d99923f/pillow-12.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc11908616c8a283cf7d664f77411a5ed2a02009b0097ff8abbba5e79128ccf2", size = 5268571, upload-time = "2026-01-02T09:12:51.11Z" }, - { url = "https://files.pythonhosted.org/packages/57/61/571163a5ef86ec0cf30d265ac2a70ae6fc9e28413d1dc94fa37fae6bda89/pillow-12.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:896866d2d436563fa2a43a9d72f417874f16b5545955c54a64941e87c1376c61", size = 4660426, upload-time = "2026-01-02T09:12:52.865Z" }, - { url = "https://files.pythonhosted.org/packages/5e/e1/53ee5163f794aef1bf84243f755ee6897a92c708505350dd1923f4afec48/pillow-12.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8e178e3e99d3c0ea8fc64b88447f7cac8ccf058af422a6cedc690d0eadd98c51", size = 6269908, upload-time = "2026-01-02T09:12:54.884Z" }, - { url = "https://files.pythonhosted.org/packages/bc/0b/b4b4106ff0ee1afa1dc599fde6ab230417f800279745124f6c50bcffed8e/pillow-12.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:079af2fb0c599c2ec144ba2c02766d1b55498e373b3ac64687e43849fbbef5bc", size = 8074733, upload-time = "2026-01-02T09:12:56.802Z" }, - { url = "https://files.pythonhosted.org/packages/19/9f/80b411cbac4a732439e629a26ad3ef11907a8c7fc5377b7602f04f6fe4e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdec5e43377761c5dbca620efb69a77f6855c5a379e32ac5b158f54c84212b14", size = 6381431, upload-time = "2026-01-02T09:12:58.823Z" }, - { url = "https://files.pythonhosted.org/packages/8f/b7/d65c45db463b66ecb6abc17c6ba6917a911202a07662247e1355ce1789e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:565c986f4b45c020f5421a4cea13ef294dde9509a8577f29b2fc5edc7587fff8", size = 7068529, upload-time = "2026-01-02T09:13:00.885Z" }, - { url = "https://files.pythonhosted.org/packages/50/96/dfd4cd726b4a45ae6e3c669fc9e49deb2241312605d33aba50499e9d9bd1/pillow-12.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:43aca0a55ce1eefc0aefa6253661cb54571857b1a7b2964bd8a1e3ef4b729924", size = 6492981, upload-time = "2026-01-02T09:13:03.314Z" }, - { url = "https://files.pythonhosted.org/packages/4d/1c/b5dc52cf713ae46033359c5ca920444f18a6359ce1020dd3e9c553ea5bc6/pillow-12.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0deedf2ea233722476b3a81e8cdfbad786f7adbed5d848469fa59fe52396e4ef", size = 7191878, upload-time = "2026-01-02T09:13:05.276Z" }, - { url = "https://files.pythonhosted.org/packages/53/26/c4188248bd5edaf543864fe4834aebe9c9cb4968b6f573ce014cc42d0720/pillow-12.1.0-cp314-cp314t-win32.whl", hash = "sha256:b17fbdbe01c196e7e159aacb889e091f28e61020a8abeac07b68079b6e626988", size = 6438703, upload-time = "2026-01-02T09:13:07.491Z" }, - { url = "https://files.pythonhosted.org/packages/b8/0e/69ed296de8ea05cb03ee139cee600f424ca166e632567b2d66727f08c7ed/pillow-12.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27b9baecb428899db6c0de572d6d305cfaf38ca1596b5c0542a5182e3e74e8c6", size = 7182927, upload-time = "2026-01-02T09:13:09.841Z" }, - { url = "https://files.pythonhosted.org/packages/fc/f5/68334c015eed9b5cff77814258717dec591ded209ab5b6fb70e2ae873d1d/pillow-12.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f61333d817698bdcdd0f9d7793e365ac3d2a21c1f1eb02b32ad6aefb8d8ea831", size = 2545104, upload-time = "2026-01-02T09:13:12.068Z" }, - { url = "https://files.pythonhosted.org/packages/8b/bc/224b1d98cffd7164b14707c91aac83c07b047fbd8f58eba4066a3e53746a/pillow-12.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ca94b6aac0d7af2a10ba08c0f888b3d5114439b6b3ef39968378723622fed377", size = 5228605, upload-time = "2026-01-02T09:13:14.084Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ca/49ca7769c4550107de049ed85208240ba0f330b3f2e316f24534795702ce/pillow-12.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:351889afef0f485b84078ea40fe33727a0492b9af3904661b0abbafee0355b72", size = 4622245, upload-time = "2026-01-02T09:13:15.964Z" }, - { url = "https://files.pythonhosted.org/packages/73/48/fac807ce82e5955bcc2718642b94b1bd22a82a6d452aea31cbb678cddf12/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb0984b30e973f7e2884362b7d23d0a348c7143ee559f38ef3eaab640144204c", size = 5247593, upload-time = "2026-01-02T09:13:17.913Z" }, - { url = "https://files.pythonhosted.org/packages/d2/95/3e0742fe358c4664aed4fd05d5f5373dcdad0b27af52aa0972568541e3f4/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:84cabc7095dd535ca934d57e9ce2a72ffd216e435a84acb06b2277b1de2689bd", size = 6989008, upload-time = "2026-01-02T09:13:20.083Z" }, - { url = "https://files.pythonhosted.org/packages/5a/74/fe2ac378e4e202e56d50540d92e1ef4ff34ed687f3c60f6a121bcf99437e/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53d8b764726d3af1a138dd353116f774e3862ec7e3794e0c8781e30db0f35dfc", size = 5313824, upload-time = "2026-01-02T09:13:22.405Z" }, - { url = "https://files.pythonhosted.org/packages/f3/77/2a60dee1adee4e2655ac328dd05c02a955c1cd683b9f1b82ec3feb44727c/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5da841d81b1a05ef940a8567da92decaa15bc4d7dedb540a8c219ad83d91808a", size = 5963278, upload-time = "2026-01-02T09:13:24.706Z" }, - { url = "https://files.pythonhosted.org/packages/2d/71/64e9b1c7f04ae0027f788a248e6297d7fcc29571371fe7d45495a78172c0/pillow-12.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:75af0b4c229ac519b155028fa1be632d812a519abba9b46b20e50c6caa184f19", size = 7029809, upload-time = "2026-01-02T09:13:26.541Z" }, +version = "12.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/46/5da1ec4a5171ee7bf1a0efa064aba70ba3d6e0788ce3f5acd1375d23c8c0/pillow-12.1.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e879bb6cd5c73848ef3b2b48b8af9ff08c5b71ecda8048b7dd22d8a33f60be32", size = 5304084, upload-time = "2026-02-11T04:20:27.501Z" }, + { url = "https://files.pythonhosted.org/packages/78/93/a29e9bc02d1cf557a834da780ceccd54e02421627200696fcf805ebdc3fb/pillow-12.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:365b10bb9417dd4498c0e3b128018c4a624dc11c7b97d8cc54effe3b096f4c38", size = 4657866, upload-time = "2026-02-11T04:20:29.827Z" }, + { url = "https://files.pythonhosted.org/packages/13/84/583a4558d492a179d31e4aae32eadce94b9acf49c0337c4ce0b70e0a01f2/pillow-12.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d4ce8e329c93845720cd2014659ca67eac35f6433fd3050393d85f3ecef0dad5", size = 6232148, upload-time = "2026-02-11T04:20:31.329Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e2/53c43334bbbb2d3b938978532fbda8e62bb6e0b23a26ce8592f36bcc4987/pillow-12.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc354a04072b765eccf2204f588a7a532c9511e8b9c7f900e1b64e3e33487090", size = 8038007, upload-time = "2026-02-11T04:20:34.225Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a6/3d0e79c8a9d58150dd98e199d7c1c56861027f3829a3a60b3c2784190180/pillow-12.1.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e7976bf1910a8116b523b9f9f58bf410f3e8aa330cd9a2bb2953f9266ab49af", size = 6345418, upload-time = "2026-02-11T04:20:35.858Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c8/46dfeac5825e600579157eea177be43e2f7ff4a99da9d0d0a49533509ac5/pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:597bd9c8419bc7c6af5604e55847789b69123bbe25d65cc6ad3012b4f3c98d8b", size = 7034590, upload-time = "2026-02-11T04:20:37.91Z" }, + { url = "https://files.pythonhosted.org/packages/af/bf/e6f65d3db8a8bbfeaf9e13cc0417813f6319863a73de934f14b2229ada18/pillow-12.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2c1fc0f2ca5f96a3c8407e41cca26a16e46b21060fe6d5b099d2cb01412222f5", size = 6458655, upload-time = "2026-02-11T04:20:39.496Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c2/66091f3f34a25894ca129362e510b956ef26f8fb67a0e6417bc5744e56f1/pillow-12.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:578510d88c6229d735855e1f278aa305270438d36a05031dfaae5067cc8eb04d", size = 7159286, upload-time = "2026-02-11T04:20:41.139Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5a/24bc8eb526a22f957d0cec6243146744966d40857e3d8deb68f7902ca6c1/pillow-12.1.1-cp311-cp311-win32.whl", hash = "sha256:7311c0a0dcadb89b36b7025dfd8326ecfa36964e29913074d47382706e516a7c", size = 6328663, upload-time = "2026-02-11T04:20:43.184Z" }, + { url = "https://files.pythonhosted.org/packages/31/03/bef822e4f2d8f9d7448c133d0a18185d3cce3e70472774fffefe8b0ed562/pillow-12.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:fbfa2a7c10cc2623f412753cddf391c7f971c52ca40a3f65dc5039b2939e8563", size = 7031448, upload-time = "2026-02-11T04:20:44.696Z" }, + { url = "https://files.pythonhosted.org/packages/49/70/f76296f53610bd17b2e7d31728b8b7825e3ac3b5b3688b51f52eab7c0818/pillow-12.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:b81b5e3511211631b3f672a595e3221252c90af017e399056d0faabb9538aa80", size = 2453651, upload-time = "2026-02-11T04:20:46.243Z" }, + { url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803, upload-time = "2026-02-11T04:20:47.653Z" }, + { url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601, upload-time = "2026-02-11T04:20:49.328Z" }, + { url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995, upload-time = "2026-02-11T04:20:51.032Z" }, + { url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012, upload-time = "2026-02-11T04:20:52.882Z" }, + { url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638, upload-time = "2026-02-11T04:20:54.444Z" }, + { url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540, upload-time = "2026-02-11T04:20:55.97Z" }, + { url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613, upload-time = "2026-02-11T04:20:57.542Z" }, + { url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745, upload-time = "2026-02-11T04:20:59.196Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823, upload-time = "2026-02-11T04:21:01.385Z" }, + { url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367, upload-time = "2026-02-11T04:21:03.536Z" }, + { url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811, upload-time = "2026-02-11T04:21:05.116Z" }, + { url = "https://files.pythonhosted.org/packages/d5/11/6db24d4bd7685583caeae54b7009584e38da3c3d4488ed4cd25b439de486/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e", size = 4062689, upload-time = "2026-02-11T04:21:06.804Z" }, + { url = "https://files.pythonhosted.org/packages/33/c0/ce6d3b1fe190f0021203e0d9b5b99e57843e345f15f9ef22fcd43842fd21/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", size = 4138535, upload-time = "2026-02-11T04:21:08.452Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c6/d5eb6a4fb32a3f9c21a8c7613ec706534ea1cf9f4b3663e99f0d83f6fca8/pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6", size = 3601364, upload-time = "2026-02-11T04:21:10.194Z" }, + { url = "https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60", size = 5262561, upload-time = "2026-02-11T04:21:11.742Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2", size = 4657460, upload-time = "2026-02-11T04:21:13.786Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1b/f1a4ea9a895b5732152789326202a82464d5254759fbacae4deea3069334/pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850", size = 6232698, upload-time = "2026-02-11T04:21:15.949Z" }, + { url = "https://files.pythonhosted.org/packages/95/f4/86f51b8745070daf21fd2e5b1fe0eb35d4db9ca26e6d58366562fb56a743/pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289", size = 8041706, upload-time = "2026-02-11T04:21:17.723Z" }, + { url = "https://files.pythonhosted.org/packages/29/9b/d6ecd956bb1266dd1045e995cce9b8d77759e740953a1c9aad9502a0461e/pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e", size = 6346621, upload-time = "2026-02-11T04:21:19.547Z" }, + { url = "https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717", size = 7038069, upload-time = "2026-02-11T04:21:21.378Z" }, + { url = "https://files.pythonhosted.org/packages/94/0e/58cb1a6bc48f746bc4cb3adb8cabff73e2742c92b3bf7a220b7cf69b9177/pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a", size = 6460040, upload-time = "2026-02-11T04:21:23.148Z" }, + { url = "https://files.pythonhosted.org/packages/6c/57/9045cb3ff11eeb6c1adce3b2d60d7d299d7b273a2e6c8381a524abfdc474/pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029", size = 7164523, upload-time = "2026-02-11T04:21:25.01Z" }, + { url = "https://files.pythonhosted.org/packages/73/f2/9be9cb99f2175f0d4dbadd6616ce1bf068ee54a28277ea1bf1fbf729c250/pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b", size = 6332552, upload-time = "2026-02-11T04:21:27.238Z" }, + { url = "https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1", size = 7040108, upload-time = "2026-02-11T04:21:29.462Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7d/fc09634e2aabdd0feabaff4a32f4a7d97789223e7c2042fd805ea4b4d2c2/pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a", size = 2453712, upload-time = "2026-02-11T04:21:31.072Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/b9d62794fc8a0dd14c1943df68347badbd5511103e0d04c035ffe5cf2255/pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da", size = 5264880, upload-time = "2026-02-11T04:21:32.865Z" }, + { url = "https://files.pythonhosted.org/packages/26/9d/e03d857d1347fa5ed9247e123fcd2a97b6220e15e9cb73ca0a8d91702c6e/pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc", size = 4660616, upload-time = "2026-02-11T04:21:34.97Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ec/8a6d22afd02570d30954e043f09c32772bfe143ba9285e2fdb11284952cd/pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c", size = 6269008, upload-time = "2026-02-11T04:21:36.623Z" }, + { url = "https://files.pythonhosted.org/packages/3d/1d/6d875422c9f28a4a361f495a5f68d9de4a66941dc2c619103ca335fa6446/pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8", size = 8073226, upload-time = "2026-02-11T04:21:38.585Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cd/134b0b6ee5eda6dc09e25e24b40fdafe11a520bc725c1d0bbaa5e00bf95b/pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20", size = 6380136, upload-time = "2026-02-11T04:21:40.562Z" }, + { url = "https://files.pythonhosted.org/packages/7a/a9/7628f013f18f001c1b98d8fffe3452f306a70dc6aba7d931019e0492f45e/pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13", size = 7067129, upload-time = "2026-02-11T04:21:42.521Z" }, + { url = "https://files.pythonhosted.org/packages/1e/f8/66ab30a2193b277785601e82ee2d49f68ea575d9637e5e234faaa98efa4c/pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf", size = 6491807, upload-time = "2026-02-11T04:21:44.22Z" }, + { url = "https://files.pythonhosted.org/packages/da/0b/a877a6627dc8318fdb84e357c5e1a758c0941ab1ddffdafd231983788579/pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524", size = 7190954, upload-time = "2026-02-11T04:21:46.114Z" }, + { url = "https://files.pythonhosted.org/packages/83/43/6f732ff85743cf746b1361b91665d9f5155e1483817f693f8d57ea93147f/pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986", size = 6336441, upload-time = "2026-02-11T04:21:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/3b/44/e865ef3986611bb75bfabdf94a590016ea327833f434558801122979cd0e/pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c", size = 7045383, upload-time = "2026-02-11T04:21:50.015Z" }, + { url = "https://files.pythonhosted.org/packages/a8/c6/f4fb24268d0c6908b9f04143697ea18b0379490cb74ba9e8d41b898bd005/pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3", size = 2456104, upload-time = "2026-02-11T04:21:51.633Z" }, + { url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652, upload-time = "2026-02-11T04:21:53.19Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823, upload-time = "2026-02-11T04:22:03.088Z" }, + { url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143, upload-time = "2026-02-11T04:22:04.909Z" }, + { url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254, upload-time = "2026-02-11T04:22:07.656Z" }, + { url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499, upload-time = "2026-02-11T04:22:09.613Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137, upload-time = "2026-02-11T04:22:11.434Z" }, + { url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721, upload-time = "2026-02-11T04:22:13.321Z" }, + { url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798, upload-time = "2026-02-11T04:22:15.449Z" }, + { url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315, upload-time = "2026-02-11T04:22:17.24Z" }, + { url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360, upload-time = "2026-02-11T04:22:19.111Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438, upload-time = "2026-02-11T04:22:21.041Z" }, + { url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503, upload-time = "2026-02-11T04:22:22.833Z" }, + { url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748, upload-time = "2026-02-11T04:22:24.64Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314, upload-time = "2026-02-11T04:22:26.685Z" }, + { url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612, upload-time = "2026-02-11T04:22:29.884Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567, upload-time = "2026-02-11T04:22:31.799Z" }, + { url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951, upload-time = "2026-02-11T04:22:33.921Z" }, + { url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769, upload-time = "2026-02-11T04:22:35.877Z" }, + { url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358, upload-time = "2026-02-11T04:22:37.698Z" }, + { url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558, upload-time = "2026-02-11T04:22:39.597Z" }, + { url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028, upload-time = "2026-02-11T04:22:42.73Z" }, + { url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940, upload-time = "2026-02-11T04:22:44.543Z" }, + { url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736, upload-time = "2026-02-11T04:22:46.347Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894, upload-time = "2026-02-11T04:22:48.114Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446, upload-time = "2026-02-11T04:22:50.342Z" }, + { url = "https://files.pythonhosted.org/packages/56/11/5d43209aa4cb58e0cc80127956ff1796a68b928e6324bbf06ef4db34367b/pillow-12.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:600fd103672b925fe62ed08e0d874ea34d692474df6f4bf7ebe148b30f89f39f", size = 5228606, upload-time = "2026-02-11T04:22:52.106Z" }, + { url = "https://files.pythonhosted.org/packages/5f/d5/3b005b4e4fda6698b371fa6c21b097d4707585d7db99e98d9b0b87ac612a/pillow-12.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:665e1b916b043cef294bc54d47bf02d87e13f769bc4bc5fa225a24b3a6c5aca9", size = 4622321, upload-time = "2026-02-11T04:22:53.827Z" }, + { url = "https://files.pythonhosted.org/packages/df/36/ed3ea2d594356fd8037e5a01f6156c74bc8d92dbb0fa60746cc96cabb6e8/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:495c302af3aad1ca67420ddd5c7bd480c8867ad173528767d906428057a11f0e", size = 5247579, upload-time = "2026-02-11T04:22:56.094Z" }, + { url = "https://files.pythonhosted.org/packages/54/9a/9cc3e029683cf6d20ae5085da0dafc63148e3252c2f13328e553aaa13cfb/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8fd420ef0c52c88b5a035a0886f367748c72147b2b8f384c9d12656678dfdfa9", size = 6989094, upload-time = "2026-02-11T04:22:58.288Z" }, + { url = "https://files.pythonhosted.org/packages/00/98/fc53ab36da80b88df0967896b6c4b4cd948a0dc5aa40a754266aa3ae48b3/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f975aa7ef9684ce7e2c18a3aa8f8e2106ce1e46b94ab713d156b2898811651d3", size = 5313850, upload-time = "2026-02-11T04:23:00.554Z" }, + { url = "https://files.pythonhosted.org/packages/30/02/00fa585abfd9fe9d73e5f6e554dc36cc2b842898cbfc46d70353dae227f8/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8089c852a56c2966cf18835db62d9b34fef7ba74c726ad943928d494fa7f4735", size = 5963343, upload-time = "2026-02-11T04:23:02.934Z" }, + { url = "https://files.pythonhosted.org/packages/f2/26/c56ce33ca856e358d27fda9676c055395abddb82c35ac0f593877ed4562e/pillow-12.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:cb9bb857b2d057c6dfc72ac5f3b44836924ba15721882ef103cecb40d002d80e", size = 7029880, upload-time = "2026-02-11T04:23:04.783Z" }, ] [[package]] @@ -2306,11 +2318,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.5.1" +version = "4.9.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394, upload-time = "2026-02-16T03:56:10.574Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, + { url = "https://files.pythonhosted.org/packages/48/31/05e764397056194206169869b50cf2fee4dbbbc71b344705b9c0d878d4d8/platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd", size = 21168, upload-time = "2026-02-16T03:56:08.891Z" }, ] [[package]] @@ -2998,27 +3010,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c8/39/5cee96809fbca590abea6b46c6d1c586b49663d1d2830a751cc8fc42c666/ruff-0.15.0.tar.gz", hash = "sha256:6bdea47cdbea30d40f8f8d7d69c0854ba7c15420ec75a26f463290949d7f7e9a", size = 4524893, upload-time = "2026-02-03T17:53:35.357Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/88/3fd1b0aa4b6330d6aaa63a285bc96c9f71970351579152d231ed90914586/ruff-0.15.0-py3-none-linux_armv6l.whl", hash = "sha256:aac4ebaa612a82b23d45964586f24ae9bc23ca101919f5590bdb368d74ad5455", size = 10354332, upload-time = "2026-02-03T17:52:54.892Z" }, - { url = "https://files.pythonhosted.org/packages/72/f6/62e173fbb7eb75cc29fe2576a1e20f0a46f671a2587b5f604bfb0eaf5f6f/ruff-0.15.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dcd4be7cc75cfbbca24a98d04d0b9b36a270d0833241f776b788d59f4142b14d", size = 10767189, upload-time = "2026-02-03T17:53:19.778Z" }, - { url = "https://files.pythonhosted.org/packages/99/e4/968ae17b676d1d2ff101d56dc69cf333e3a4c985e1ec23803df84fc7bf9e/ruff-0.15.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d747e3319b2bce179c7c1eaad3d884dc0a199b5f4d5187620530adf9105268ce", size = 10075384, upload-time = "2026-02-03T17:53:29.241Z" }, - { url = "https://files.pythonhosted.org/packages/a2/bf/9843c6044ab9e20af879c751487e61333ca79a2c8c3058b15722386b8cae/ruff-0.15.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:650bd9c56ae03102c51a5e4b554d74d825ff3abe4db22b90fd32d816c2e90621", size = 10481363, upload-time = "2026-02-03T17:52:43.332Z" }, - { url = "https://files.pythonhosted.org/packages/55/d9/4ada5ccf4cd1f532db1c8d44b6f664f2208d3d93acbeec18f82315e15193/ruff-0.15.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6664b7eac559e3048223a2da77769c2f92b43a6dfd4720cef42654299a599c9", size = 10187736, upload-time = "2026-02-03T17:53:00.522Z" }, - { url = "https://files.pythonhosted.org/packages/86/e2/f25eaecd446af7bb132af0a1d5b135a62971a41f5366ff41d06d25e77a91/ruff-0.15.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f811f97b0f092b35320d1556f3353bf238763420ade5d9e62ebd2b73f2ff179", size = 10968415, upload-time = "2026-02-03T17:53:15.705Z" }, - { url = "https://files.pythonhosted.org/packages/e7/dc/f06a8558d06333bf79b497d29a50c3a673d9251214e0d7ec78f90b30aa79/ruff-0.15.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:761ec0a66680fab6454236635a39abaf14198818c8cdf691e036f4bc0f406b2d", size = 11809643, upload-time = "2026-02-03T17:53:23.031Z" }, - { url = "https://files.pythonhosted.org/packages/dd/45/0ece8db2c474ad7df13af3a6d50f76e22a09d078af63078f005057ca59eb/ruff-0.15.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:940f11c2604d317e797b289f4f9f3fa5555ffe4fb574b55ed006c3d9b6f0eb78", size = 11234787, upload-time = "2026-02-03T17:52:46.432Z" }, - { url = "https://files.pythonhosted.org/packages/8a/d9/0e3a81467a120fd265658d127db648e4d3acfe3e4f6f5d4ea79fac47e587/ruff-0.15.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcbca3d40558789126da91d7ef9a7c87772ee107033db7191edefa34e2c7f1b4", size = 11112797, upload-time = "2026-02-03T17:52:49.274Z" }, - { url = "https://files.pythonhosted.org/packages/b2/cb/8c0b3b0c692683f8ff31351dfb6241047fa873a4481a76df4335a8bff716/ruff-0.15.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9a121a96db1d75fa3eb39c4539e607f628920dd72ff1f7c5ee4f1b768ac62d6e", size = 11033133, upload-time = "2026-02-03T17:53:33.105Z" }, - { url = "https://files.pythonhosted.org/packages/f8/5e/23b87370cf0f9081a8c89a753e69a4e8778805b8802ccfe175cc410e50b9/ruff-0.15.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5298d518e493061f2eabd4abd067c7e4fb89e2f63291c94332e35631c07c3662", size = 10442646, upload-time = "2026-02-03T17:53:06.278Z" }, - { url = "https://files.pythonhosted.org/packages/e1/9a/3c94de5ce642830167e6d00b5c75aacd73e6347b4c7fc6828699b150a5ee/ruff-0.15.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:afb6e603d6375ff0d6b0cee563fa21ab570fd15e65c852cb24922cef25050cf1", size = 10195750, upload-time = "2026-02-03T17:53:26.084Z" }, - { url = "https://files.pythonhosted.org/packages/30/15/e396325080d600b436acc970848d69df9c13977942fb62bb8722d729bee8/ruff-0.15.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:77e515f6b15f828b94dc17d2b4ace334c9ddb7d9468c54b2f9ed2b9c1593ef16", size = 10676120, upload-time = "2026-02-03T17:53:09.363Z" }, - { url = "https://files.pythonhosted.org/packages/8d/c9/229a23d52a2983de1ad0fb0ee37d36e0257e6f28bfd6b498ee2c76361874/ruff-0.15.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6f6e80850a01eb13b3e42ee0ebdf6e4497151b48c35051aab51c101266d187a3", size = 11201636, upload-time = "2026-02-03T17:52:57.281Z" }, - { url = "https://files.pythonhosted.org/packages/6f/b0/69adf22f4e24f3677208adb715c578266842e6e6a3cc77483f48dd999ede/ruff-0.15.0-py3-none-win32.whl", hash = "sha256:238a717ef803e501b6d51e0bdd0d2c6e8513fe9eec14002445134d3907cd46c3", size = 10465945, upload-time = "2026-02-03T17:53:12.591Z" }, - { url = "https://files.pythonhosted.org/packages/51/ad/f813b6e2c97e9b4598be25e94a9147b9af7e60523b0cb5d94d307c15229d/ruff-0.15.0-py3-none-win_amd64.whl", hash = "sha256:dd5e4d3301dc01de614da3cdffc33d4b1b96fb89e45721f1598e5532ccf78b18", size = 11564657, upload-time = "2026-02-03T17:52:51.893Z" }, - { url = "https://files.pythonhosted.org/packages/f6/b0/2d823f6e77ebe560f4e397d078487e8d52c1516b331e3521bc75db4272ca/ruff-0.15.0-py3-none-win_arm64.whl", hash = "sha256:c480d632cc0ca3f0727acac8b7d053542d9e114a462a145d0b00e7cd658c515a", size = 10865753, upload-time = "2026-02-03T17:53:03.014Z" }, +version = "0.15.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/dc/4e6ac71b511b141cf626357a3946679abeba4cf67bc7cc5a17920f31e10d/ruff-0.15.1.tar.gz", hash = "sha256:c590fe13fb57c97141ae975c03a1aedb3d3156030cabd740d6ff0b0d601e203f", size = 4540855, upload-time = "2026-02-12T23:09:09.998Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/bf/e6e4324238c17f9d9120a9d60aa99a7daaa21204c07fcd84e2ef03bb5fd1/ruff-0.15.1-py3-none-linux_armv6l.whl", hash = "sha256:b101ed7cf4615bda6ffe65bdb59f964e9f4a0d3f85cbf0e54f0ab76d7b90228a", size = 10367819, upload-time = "2026-02-12T23:09:03.598Z" }, + { url = "https://files.pythonhosted.org/packages/b3/ea/c8f89d32e7912269d38c58f3649e453ac32c528f93bb7f4219258be2e7ed/ruff-0.15.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:939c995e9277e63ea632cc8d3fae17aa758526f49a9a850d2e7e758bfef46602", size = 10798618, upload-time = "2026-02-12T23:09:22.928Z" }, + { url = "https://files.pythonhosted.org/packages/5e/0f/1d0d88bc862624247d82c20c10d4c0f6bb2f346559d8af281674cf327f15/ruff-0.15.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1d83466455fdefe60b8d9c8df81d3c1bbb2115cede53549d3b522ce2bc703899", size = 10148518, upload-time = "2026-02-12T23:08:58.339Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c8/291c49cefaa4a9248e986256df2ade7add79388fe179e0691be06fae6f37/ruff-0.15.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9457e3c3291024866222b96108ab2d8265b477e5b1534c7ddb1810904858d16", size = 10518811, upload-time = "2026-02-12T23:09:31.865Z" }, + { url = "https://files.pythonhosted.org/packages/c3/1a/f5707440e5ae43ffa5365cac8bbb91e9665f4a883f560893829cf16a606b/ruff-0.15.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:92c92b003e9d4f7fbd33b1867bb15a1b785b1735069108dfc23821ba045b29bc", size = 10196169, upload-time = "2026-02-12T23:09:17.306Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ff/26ddc8c4da04c8fd3ee65a89c9fb99eaa5c30394269d424461467be2271f/ruff-0.15.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fe5c41ab43e3a06778844c586251eb5a510f67125427625f9eb2b9526535779", size = 10990491, upload-time = "2026-02-12T23:09:25.503Z" }, + { url = "https://files.pythonhosted.org/packages/fc/00/50920cb385b89413f7cdb4bb9bc8fc59c1b0f30028d8bccc294189a54955/ruff-0.15.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66a6dd6df4d80dc382c6484f8ce1bcceb55c32e9f27a8b94c32f6c7331bf14fb", size = 11843280, upload-time = "2026-02-12T23:09:19.88Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6d/2f5cad8380caf5632a15460c323ae326f1e1a2b5b90a6ee7519017a017ca/ruff-0.15.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a4a42cbb8af0bda9bcd7606b064d7c0bc311a88d141d02f78920be6acb5aa83", size = 11274336, upload-time = "2026-02-12T23:09:14.907Z" }, + { url = "https://files.pythonhosted.org/packages/a3/1d/5f56cae1d6c40b8a318513599b35ea4b075d7dc1cd1d04449578c29d1d75/ruff-0.15.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ab064052c31dddada35079901592dfba2e05f5b1e43af3954aafcbc1096a5b2", size = 11137288, upload-time = "2026-02-12T23:09:07.475Z" }, + { url = "https://files.pythonhosted.org/packages/cd/20/6f8d7d8f768c93b0382b33b9306b3b999918816da46537d5a61635514635/ruff-0.15.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:5631c940fe9fe91f817a4c2ea4e81f47bee3ca4aa646134a24374f3c19ad9454", size = 11070681, upload-time = "2026-02-12T23:08:55.43Z" }, + { url = "https://files.pythonhosted.org/packages/9a/67/d640ac76069f64cdea59dba02af2e00b1fa30e2103c7f8d049c0cff4cafd/ruff-0.15.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:68138a4ba184b4691ccdc39f7795c66b3c68160c586519e7e8444cf5a53e1b4c", size = 10486401, upload-time = "2026-02-12T23:09:27.927Z" }, + { url = "https://files.pythonhosted.org/packages/65/3d/e1429f64a3ff89297497916b88c32a5cc88eeca7e9c787072d0e7f1d3e1e/ruff-0.15.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:518f9af03bfc33c03bdb4cb63fabc935341bb7f54af500f92ac309ecfbba6330", size = 10197452, upload-time = "2026-02-12T23:09:12.147Z" }, + { url = "https://files.pythonhosted.org/packages/78/83/e2c3bade17dad63bf1e1c2ffaf11490603b760be149e1419b07049b36ef2/ruff-0.15.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:da79f4d6a826caaea95de0237a67e33b81e6ec2e25fc7e1993a4015dffca7c61", size = 10693900, upload-time = "2026-02-12T23:09:34.418Z" }, + { url = "https://files.pythonhosted.org/packages/a1/27/fdc0e11a813e6338e0706e8b39bb7a1d61ea5b36873b351acee7e524a72a/ruff-0.15.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3dd86dccb83cd7d4dcfac303ffc277e6048600dfc22e38158afa208e8bf94a1f", size = 11227302, upload-time = "2026-02-12T23:09:36.536Z" }, + { url = "https://files.pythonhosted.org/packages/f6/58/ac864a75067dcbd3b95be5ab4eb2b601d7fbc3d3d736a27e391a4f92a5c1/ruff-0.15.1-py3-none-win32.whl", hash = "sha256:660975d9cb49b5d5278b12b03bb9951d554543a90b74ed5d366b20e2c57c2098", size = 10462555, upload-time = "2026-02-12T23:09:29.899Z" }, + { url = "https://files.pythonhosted.org/packages/e0/5e/d4ccc8a27ecdb78116feac4935dfc39d1304536f4296168f91ed3ec00cd2/ruff-0.15.1-py3-none-win_amd64.whl", hash = "sha256:c820fef9dd5d4172a6570e5721704a96c6679b80cf7be41659ed439653f62336", size = 11599956, upload-time = "2026-02-12T23:09:01.157Z" }, + { url = "https://files.pythonhosted.org/packages/2a/07/5bda6a85b220c64c65686bc85bd0bbb23b29c62b3a9f9433fa55f17cda93/ruff-0.15.1-py3-none-win_arm64.whl", hash = "sha256:5ff7d5f0f88567850f45081fac8f4ec212be8d0b963e385c3f7d0d2eb4899416", size = 10874604, upload-time = "2026-02-12T23:09:05.515Z" }, ] [[package]] @@ -3315,14 +3327,14 @@ wheels = [ [[package]] name = "tifffile" -version = "2026.1.28" +version = "2026.2.15" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/94/32/38498d2a1a5d70f33f6c3909bbad48557c9a54b0e33a9307ff06b6d416ba/tifffile-2026.1.28.tar.gz", hash = "sha256:537ae6466a8bb555c336108bb1878d8319d52c9c738041d3349454dea6956e1c", size = 374675, upload-time = "2026-01-29T05:17:24.992Z" } +sdist = { url = "https://files.pythonhosted.org/packages/79/e6/0d04362a57f4c6d59a3392e3c2008d3ba3254e0a9684c683755689003950/tifffile-2026.2.15.tar.gz", hash = "sha256:28fe145c615fe3d33d40c2d4c9cc848f7631fd30af852583c4186069458895b2", size = 375461, upload-time = "2026-02-15T20:16:45.991Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/09/19/529b28ca338c5a88315e71e672badc85eef89460c248c4164f6ce058f8c7/tifffile-2026.1.28-py3-none-any.whl", hash = "sha256:45b08a19cf603dd99952eff54a61519626a1912e4e2a4d355f05938fe4a6e9fd", size = 233011, upload-time = "2026-01-29T05:17:23.078Z" }, + { url = "https://files.pythonhosted.org/packages/1e/ac/d0d24cbf1ca2a0220b2797dd9b4e528cda201259d3baa28b40aa53287d4e/tifffile-2026.2.15-py3-none-any.whl", hash = "sha256:d9b427d269a708c58400e8ce5a702b26b2502087537beb88b8e29ba7ba825a90", size = 233248, upload-time = "2026-02-15T20:16:44.256Z" }, ] [[package]] @@ -3431,9 +3443,9 @@ dependencies = [ { name = "typing-extensions" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/25/d8/9e6b8e7df981a1e3ea3907fd5a74673e791da483e8c307f0b6ff012626d0/torch-2.10.0-1-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:f699f31a236a677b3118bc0a3ef3d89c0c29b5ec0b20f4c4bf0b110378487464", size = 79423460, upload-time = "2026-02-06T17:37:39.657Z" }, - { url = "https://files.pythonhosted.org/packages/c9/2f/0b295dd8d199ef71e6f176f576473d645d41357b7b8aa978cc6b042575df/torch-2.10.0-1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:6abb224c2b6e9e27b592a1c0015c33a504b00a0e0938f1499f7f514e9b7bfb5c", size = 79498197, upload-time = "2026-02-06T17:37:27.627Z" }, - { url = "https://files.pythonhosted.org/packages/a4/1b/af5fccb50c341bd69dc016769503cb0857c1423fbe9343410dfeb65240f2/torch-2.10.0-1-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:7350f6652dfd761f11f9ecb590bfe95b573e2961f7a242eccb3c8e78348d26fe", size = 79498248, upload-time = "2026-02-06T17:37:31.982Z" }, + { url = "https://files.pythonhosted.org/packages/0f/8b/4b61d6e13f7108f36910df9ab4b58fd389cc2520d54d81b88660804aad99/torch-2.10.0-2-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:418997cb02d0a0f1497cf6a09f63166f9f5df9f3e16c8a716ab76a72127c714f", size = 79423467, upload-time = "2026-02-10T21:44:48.711Z" }, + { url = "https://files.pythonhosted.org/packages/d3/54/a2ba279afcca44bbd320d4e73675b282fcee3d81400ea1b53934efca6462/torch-2.10.0-2-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:13ec4add8c3faaed8d13e0574f5cd4a323c11655546f91fbe6afa77b57423574", size = 79498202, upload-time = "2026-02-10T21:44:52.603Z" }, + { url = "https://files.pythonhosted.org/packages/ec/23/2c9fe0c9c27f7f6cb865abcea8a4568f29f00acaeadfc6a37f6801f84cb4/torch-2.10.0-2-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:e521c9f030a3774ed770a9c011751fb47c4d12029a3d6522116e48431f2ff89e", size = 79498254, upload-time = "2026-02-10T21:44:44.095Z" }, { url = "https://files.pythonhosted.org/packages/78/89/f5554b13ebd71e05c0b002f95148033e730d3f7067f67423026cc9c69410/torch-2.10.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:3282d9febd1e4e476630a099692b44fdc214ee9bf8ee5377732d9d9dfe5712e4", size = 145992610, upload-time = "2026-01-21T16:25:26.327Z" }, { url = "https://files.pythonhosted.org/packages/ae/30/a3a2120621bf9c17779b169fc17e3dc29b230c29d0f8222f499f5e159aa8/torch-2.10.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a2f9edd8dbc99f62bc4dfb78af7bf89499bca3d753423ac1b4e06592e467b763", size = 915607863, upload-time = "2026-01-21T16:25:06.696Z" }, { url = "https://files.pythonhosted.org/packages/6f/3d/c87b33c5f260a2a8ad68da7147e105f05868c281c63d65ed85aa4da98c66/torch-2.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:29b7009dba4b7a1c960260fc8ac85022c784250af43af9fb0ebafc9883782ebd", size = 113723116, upload-time = "2026-01-21T16:25:21.916Z" }, From b6a43d3f0cb878f50453028492af2573c359c040 Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Mon, 2 Feb 2026 15:34:38 -0800 Subject: [PATCH 076/335] updates to ptycho viz and inr --- src/quantem/core/ml/inr.py | 37 ++++++++++++++----- .../diffractive_imaging/ptychography_base.py | 2 + .../ptychography_visualizations.py | 11 ++++-- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/quantem/core/ml/inr.py b/src/quantem/core/ml/inr.py index 24ee45e4..276e5680 100644 --- a/src/quantem/core/ml/inr.py +++ b/src/quantem/core/ml/inr.py @@ -118,7 +118,10 @@ def reset_weights(self) -> None: self._build() def make_equispaced_grid( - self, bounds: tuple[tuple[float, float], ...], sampling: tuple[float, ...] + self, + bounds: tuple[tuple[float, float], ...], + sampling: tuple[float, ...] | None = None, + num_points: tuple[int, ...] | None = None, ) -> torch.Tensor: """Create an equispaced coordinate grid for the implicit neural representation. @@ -130,7 +133,10 @@ def make_equispaced_grid( sampling : tuple of float Sampling interval for each dimension (spacing_0, spacing_1, ...). Length must match in_features. - + num_points : tuple of int, optional + Number of points to sample in each dimension. If None, the number of points + is calculated from the sampling interval. If both sampling and num_points are provided, + num_points takes precedence. Returns ------- torch.Tensor @@ -153,15 +159,28 @@ def make_equispaced_grid( raise ValueError( f"Bounds length ({len(bounds)}) must match in_features ({self.in_features})" ) - if len(sampling) != self.in_features: - raise ValueError( - f"Sampling length ({len(sampling)}) must match in_features ({self.in_features})" + if sampling is not None and num_points is not None: + raise ValueError("Only one of sampling or num_points can be provided") + if sampling is not None: + if len(sampling) != self.in_features: + raise ValueError( + f"Sampling length ({len(sampling)}) must match in_features ({self.in_features})" + ) + num_points = tuple( + int((bound_max - bound_min) / sample) + 1 + for (bound_min, bound_max), sample in zip(bounds, sampling) ) - + elif num_points is not None: + if len(num_points) != self.in_features: + raise ValueError( + f"Num points length ({len(num_points)}) must match in_features ({self.in_features})" + ) + else: + raise ValueError("Either sampling or num_points must be provided") grids = [] - for (bound_min, bound_max), sample in zip(bounds, sampling): - num_points = int((bound_max - bound_min) / sample) + 1 - grids.append(torch.linspace(bound_min, bound_max, num_points)) + for i, (bound_min, bound_max) in enumerate(bounds): + n = num_points[i] + grids.append(torch.linspace(bound_min, bound_max, n)) coords = torch.meshgrid(*grids, indexing="ij") coords = torch.stack(coords, dim=-1).to(self.dtype) diff --git a/src/quantem/diffractive_imaging/ptychography_base.py b/src/quantem/diffractive_imaging/ptychography_base.py index f359dbee..665819d9 100644 --- a/src/quantem/diffractive_imaging/ptychography_base.py +++ b/src/quantem/diffractive_imaging/ptychography_base.py @@ -465,6 +465,8 @@ def get_snapshot_by_iter( "No snapshots available. Use store_snapshots=True during reconstruction." ) iteration = int(iteration) + if iteration < 0: + iteration = self.num_iters + iteration if closest: closest_snapshot = min(self.snapshots, key=lambda s: abs(s["iteration"] - iteration)) snp = closest_snapshot diff --git a/src/quantem/diffractive_imaging/ptychography_visualizations.py b/src/quantem/diffractive_imaging/ptychography_visualizations.py index 20d5ca09..d415dd4f 100644 --- a/src/quantem/diffractive_imaging/ptychography_visualizations.py +++ b/src/quantem/diffractive_imaging/ptychography_visualizations.py @@ -1,3 +1,4 @@ +import warnings from typing import Any, Literal import matplotlib.gridspec as gridspec @@ -128,8 +129,6 @@ def show_obj_fft( obj_iter = "Final" if obj is None: if snapshot_iter is not None: - if snapshot_iter < 0: - snapshot_iter = len(self.snapshots) + snapshot_iter snp = self.get_snapshot_by_iter(snapshot_iter, closest=True, cropped=True) obj_np = snp["obj"] obj_iter = snp["iteration"] @@ -379,8 +378,8 @@ def show_obj_slices( if obj.ndim == 2: obj = obj[None, ...] - t_parts = [f"0/{len(obj)} | 0 Å"] - for i in range(1, len(obj)): + t_parts = [] + for i in range(len(obj)): t_parts.append(f"{i + 1}/{len(obj)} | {self.slice_thicknesses[i - 1]:.1f} Å") if self.obj_type == "potential": @@ -403,6 +402,10 @@ def show_obj_slices( if interval_type == "quantile": norm = {"interval_type": "quantile"} # TODO -- make this work with interval_scaling + if interval_scaling == "all": + warnings.warn( + "interval_scaling='all' is not yet supported for quantile normalization" + ) elif interval_type in ["manual", "minmax", "abs"]: norm: dict[str, Any] = {"interval_type": "manual"} if interval_scaling == "all": From e49e25730f1c5bbadaa09551076726c5d4087687 Mon Sep 17 00:00:00 2001 From: quantem-bot Date: Mon, 23 Feb 2026 10:02:29 +0000 Subject: [PATCH 077/335] chore: update lock file --- uv.lock | 399 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 200 insertions(+), 199 deletions(-) diff --git a/uv.lock b/uv.lock index 9288f616..2d2559a0 100644 --- a/uv.lock +++ b/uv.lock @@ -139,11 +139,11 @@ wheels = [ [[package]] name = "async-lru" -version = "2.1.0" +version = "2.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ef/c3/bbf34f15ea88dfb649ab2c40f9d75081784a50573a9ea431563cab64adb8/async_lru-2.1.0.tar.gz", hash = "sha256:9eeb2fecd3fe42cc8a787fc32ead53a3a7158cc43d039c3c55ab3e4e5b2a80ed", size = 12041, upload-time = "2026-01-17T22:52:18.931Z" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8a/ca724066c32a53fa75f59e0f21aa822fdaa8a0dffa112d223634e3caabf9/async_lru-2.2.0.tar.gz", hash = "sha256:80abae2a237dbc6c60861d621619af39f0d920aea306de34cb992c879e01370c", size = 14654, upload-time = "2026-02-20T19:11:43.848Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/e9/eb6a5db5ac505d5d45715388e92bced7a5bb556facc4d0865d192823f2d2/async_lru-2.1.0-py3-none-any.whl", hash = "sha256:fa12dcf99a42ac1280bc16c634bbaf06883809790f6304d85cdab3f666f33a7e", size = 6933, upload-time = "2026-01-17T22:52:17.389Z" }, + { url = "https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl", hash = "sha256:e2c1cf731eba202b59c5feedaef14ffd9d02ad0037fcda64938699f2c380eafe", size = 7890, upload-time = "2026-02-20T19:11:42.273Z" }, ] [[package]] @@ -768,11 +768,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.24.2" +version = "3.24.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/02/a8/dae62680be63cbb3ff87cfa2f51cf766269514ea5488479d42fec5aa6f3a/filelock-3.24.2.tar.gz", hash = "sha256:c22803117490f156e59fafce621f0550a7a853e2bbf4f87f112b11d469b6c81b", size = 37601, upload-time = "2026-02-16T02:50:45.614Z" } +sdist = { url = "https://files.pythonhosted.org/packages/73/92/a8e2479937ff39185d20dd6a851c1a63e55849e447a55e798cc2e1f49c65/filelock-3.24.3.tar.gz", hash = "sha256:011a5644dc937c22699943ebbfc46e969cdde3e171470a6e40b9533e5a72affa", size = 37935, upload-time = "2026-02-19T00:48:20.543Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/04/a94ebfb4eaaa08db56725a40de2887e95de4e8641b9e902c311bfa00aa39/filelock-3.24.2-py3-none-any.whl", hash = "sha256:667d7dc0b7d1e1064dd5f8f8e80bdac157a6482e8d2e02cd16fd3b6b33bd6556", size = 24152, upload-time = "2026-02-16T02:50:44Z" }, + { url = "https://files.pythonhosted.org/packages/9c/0f/5d0c71a1aefeb08efff26272149e07ab922b64f46c63363756224bd6872e/filelock-3.24.3-py3-none-any.whl", hash = "sha256:426e9a4660391f7f8a810d71b0555bce9008b0a1cc342ab1f6947d37639e002d", size = 24331, upload-time = "2026-02-19T00:48:18.465Z" }, ] [[package]] @@ -898,100 +898,100 @@ wheels = [ [[package]] name = "greenlet" -version = "3.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8a/99/1cd3411c56a410994669062bd73dd58270c00cc074cac15f385a1fd91f8a/greenlet-3.3.1.tar.gz", hash = "sha256:41848f3230b58c08bb43dee542e74a2a2e34d3c59dc3076cec9151aeeedcae98", size = 184690, upload-time = "2026-01-23T15:31:02.076Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/e8/2e1462c8fdbe0f210feb5ac7ad2d9029af8be3bf45bd9fa39765f821642f/greenlet-3.3.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:5fd23b9bc6d37b563211c6abbb1b3cab27db385a4449af5c32e932f93017080c", size = 274974, upload-time = "2026-01-23T15:31:02.891Z" }, - { url = "https://files.pythonhosted.org/packages/7e/a8/530a401419a6b302af59f67aaf0b9ba1015855ea7e56c036b5928793c5bd/greenlet-3.3.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09f51496a0bfbaa9d74d36a52d2580d1ef5ed4fdfcff0a73730abfbbbe1403dd", size = 577175, upload-time = "2026-01-23T16:00:56.213Z" }, - { url = "https://files.pythonhosted.org/packages/8e/89/7e812bb9c05e1aaef9b597ac1d0962b9021d2c6269354966451e885c4e6b/greenlet-3.3.1-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb0feb07fe6e6a74615ee62a880007d976cf739b6669cce95daa7373d4fc69c5", size = 590401, upload-time = "2026-01-23T16:05:26.365Z" }, - { url = "https://files.pythonhosted.org/packages/5c/ae/8d472e1f5ac5efe55c563f3eabb38c98a44b832602e12910750a7c025802/greenlet-3.3.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:39eda9ba259cc9801da05351eaa8576e9aa83eb9411e8f0c299e05d712a210f2", size = 590272, upload-time = "2026-01-23T15:32:49.411Z" }, - { url = "https://files.pythonhosted.org/packages/a8/51/0fde34bebfcadc833550717eade64e35ec8738e6b097d5d248274a01258b/greenlet-3.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e2e7e882f83149f0a71ac822ebf156d902e7a5d22c9045e3e0d1daf59cee2cc9", size = 1550729, upload-time = "2026-01-23T16:04:20.867Z" }, - { url = "https://files.pythonhosted.org/packages/16/c9/2fb47bee83b25b119d5a35d580807bb8b92480a54b68fef009a02945629f/greenlet-3.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:80aa4d79eb5564f2e0a6144fcc744b5a37c56c4a92d60920720e99210d88db0f", size = 1615552, upload-time = "2026-01-23T15:33:45.743Z" }, - { url = "https://files.pythonhosted.org/packages/1f/54/dcf9f737b96606f82f8dd05becfb8d238db0633dd7397d542a296fe9cad3/greenlet-3.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:32e4ca9777c5addcbf42ff3915d99030d8e00173a56f80001fb3875998fe410b", size = 226462, upload-time = "2026-01-23T15:36:50.422Z" }, - { url = "https://files.pythonhosted.org/packages/91/37/61e1015cf944ddd2337447d8e97fb423ac9bc21f9963fb5f206b53d65649/greenlet-3.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:da19609432f353fed186cc1b85e9440db93d489f198b4bdf42ae19cc9d9ac9b4", size = 225715, upload-time = "2026-01-23T15:33:17.298Z" }, - { url = "https://files.pythonhosted.org/packages/f9/c8/9d76a66421d1ae24340dfae7e79c313957f6e3195c144d2c73333b5bfe34/greenlet-3.3.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:7e806ca53acf6d15a888405880766ec84721aa4181261cd11a457dfe9a7a4975", size = 276443, upload-time = "2026-01-23T15:30:10.066Z" }, - { url = "https://files.pythonhosted.org/packages/81/99/401ff34bb3c032d1f10477d199724f5e5f6fbfb59816ad1455c79c1eb8e7/greenlet-3.3.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d842c94b9155f1c9b3058036c24ffb8ff78b428414a19792b2380be9cecf4f36", size = 597359, upload-time = "2026-01-23T16:00:57.394Z" }, - { url = "https://files.pythonhosted.org/packages/2b/bc/4dcc0871ed557792d304f50be0f7487a14e017952ec689effe2180a6ff35/greenlet-3.3.1-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:20fedaadd422fa02695f82093f9a98bad3dab5fcda793c658b945fcde2ab27ba", size = 607805, upload-time = "2026-01-23T16:05:28.068Z" }, - { url = "https://files.pythonhosted.org/packages/cf/05/821587cf19e2ce1f2b24945d890b164401e5085f9d09cbd969b0c193cd20/greenlet-3.3.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14194f5f4305800ff329cbf02c5fcc88f01886cadd29941b807668a45f0d2336", size = 609947, upload-time = "2026-01-23T15:32:51.004Z" }, - { url = "https://files.pythonhosted.org/packages/a4/52/ee8c46ed9f8babaa93a19e577f26e3d28a519feac6350ed6f25f1afee7e9/greenlet-3.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7b2fe4150a0cf59f847a67db8c155ac36aed89080a6a639e9f16df5d6c6096f1", size = 1567487, upload-time = "2026-01-23T16:04:22.125Z" }, - { url = "https://files.pythonhosted.org/packages/8f/7c/456a74f07029597626f3a6db71b273a3632aecb9afafeeca452cfa633197/greenlet-3.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:49f4ad195d45f4a66a0eb9c1ba4832bb380570d361912fa3554746830d332149", size = 1636087, upload-time = "2026-01-23T15:33:47.486Z" }, - { url = "https://files.pythonhosted.org/packages/34/2f/5e0e41f33c69655300a5e54aeb637cf8ff57f1786a3aba374eacc0228c1d/greenlet-3.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:cc98b9c4e4870fa983436afa999d4eb16b12872fab7071423d5262fa7120d57a", size = 227156, upload-time = "2026-01-23T15:34:34.808Z" }, - { url = "https://files.pythonhosted.org/packages/c8/ab/717c58343cf02c5265b531384b248787e04d8160b8afe53d9eec053d7b44/greenlet-3.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:bfb2d1763d777de5ee495c85309460f6fd8146e50ec9d0ae0183dbf6f0a829d1", size = 226403, upload-time = "2026-01-23T15:31:39.372Z" }, - { url = "https://files.pythonhosted.org/packages/ec/ab/d26750f2b7242c2b90ea2ad71de70cfcd73a948a49513188a0fc0d6fc15a/greenlet-3.3.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:7ab327905cabb0622adca5971e488064e35115430cec2c35a50fd36e72a315b3", size = 275205, upload-time = "2026-01-23T15:30:24.556Z" }, - { url = "https://files.pythonhosted.org/packages/10/d3/be7d19e8fad7c5a78eeefb2d896a08cd4643e1e90c605c4be3b46264998f/greenlet-3.3.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65be2f026ca6a176f88fb935ee23c18333ccea97048076aef4db1ef5bc0713ac", size = 599284, upload-time = "2026-01-23T16:00:58.584Z" }, - { url = "https://files.pythonhosted.org/packages/ae/21/fe703aaa056fdb0f17e5afd4b5c80195bbdab701208918938bd15b00d39b/greenlet-3.3.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7a3ae05b3d225b4155bda56b072ceb09d05e974bc74be6c3fc15463cf69f33fd", size = 610274, upload-time = "2026-01-23T16:05:29.312Z" }, - { url = "https://files.pythonhosted.org/packages/cb/86/5c6ab23bb3c28c21ed6bebad006515cfe08b04613eb105ca0041fecca852/greenlet-3.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6423481193bbbe871313de5fd06a082f2649e7ce6e08015d2a76c1e9186ca5b3", size = 612904, upload-time = "2026-01-23T15:32:52.317Z" }, - { url = "https://files.pythonhosted.org/packages/c2/f3/7949994264e22639e40718c2daf6f6df5169bf48fb038c008a489ec53a50/greenlet-3.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:33a956fe78bbbda82bfc95e128d61129b32d66bcf0a20a1f0c08aa4839ffa951", size = 1567316, upload-time = "2026-01-23T16:04:23.316Z" }, - { url = "https://files.pythonhosted.org/packages/8d/6e/d73c94d13b6465e9f7cd6231c68abde838bb22408596c05d9059830b7872/greenlet-3.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b065d3284be43728dd280f6f9a13990b56470b81be20375a207cdc814a983f2", size = 1636549, upload-time = "2026-01-23T15:33:48.643Z" }, - { url = "https://files.pythonhosted.org/packages/5e/b3/c9c23a6478b3bcc91f979ce4ca50879e4d0b2bd7b9a53d8ecded719b92e2/greenlet-3.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:27289986f4e5b0edec7b5a91063c109f0276abb09a7e9bdab08437525977c946", size = 227042, upload-time = "2026-01-23T15:33:58.216Z" }, - { url = "https://files.pythonhosted.org/packages/90/e7/824beda656097edee36ab15809fd063447b200cc03a7f6a24c34d520bc88/greenlet-3.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:2f080e028001c5273e0b42690eaf359aeef9cb1389da0f171ea51a5dc3c7608d", size = 226294, upload-time = "2026-01-23T15:30:52.73Z" }, - { url = "https://files.pythonhosted.org/packages/ae/fb/011c7c717213182caf78084a9bea51c8590b0afda98001f69d9f853a495b/greenlet-3.3.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:bd59acd8529b372775cd0fcbc5f420ae20681c5b045ce25bd453ed8455ab99b5", size = 275737, upload-time = "2026-01-23T15:32:16.889Z" }, - { url = "https://files.pythonhosted.org/packages/41/2e/a3a417d620363fdbb08a48b1dd582956a46a61bf8fd27ee8164f9dfe87c2/greenlet-3.3.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b31c05dd84ef6871dd47120386aed35323c944d86c3d91a17c4b8d23df62f15b", size = 646422, upload-time = "2026-01-23T16:01:00.354Z" }, - { url = "https://files.pythonhosted.org/packages/b4/09/c6c4a0db47defafd2d6bab8ddfe47ad19963b4e30f5bed84d75328059f8c/greenlet-3.3.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:02925a0bfffc41e542c70aa14c7eda3593e4d7e274bfcccca1827e6c0875902e", size = 658219, upload-time = "2026-01-23T16:05:30.956Z" }, - { url = "https://files.pythonhosted.org/packages/80/38/9d42d60dffb04b45f03dbab9430898352dba277758640751dc5cc316c521/greenlet-3.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34a729e2e4e4ffe9ae2408d5ecaf12f944853f40ad724929b7585bca808a9d6f", size = 660237, upload-time = "2026-01-23T15:32:53.967Z" }, - { url = "https://files.pythonhosted.org/packages/96/61/373c30b7197f9e756e4c81ae90a8d55dc3598c17673f91f4d31c3c689c3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aec9ab04e82918e623415947921dea15851b152b822661cce3f8e4393c3df683", size = 1615261, upload-time = "2026-01-23T16:04:25.066Z" }, - { url = "https://files.pythonhosted.org/packages/fd/d3/ca534310343f5945316f9451e953dcd89b36fe7a19de652a1dc5a0eeef3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:71c767cf281a80d02b6c1bdc41c9468e1f5a494fb11bc8688c360524e273d7b1", size = 1683719, upload-time = "2026-01-23T15:33:50.61Z" }, - { url = "https://files.pythonhosted.org/packages/52/cb/c21a3fd5d2c9c8b622e7bede6d6d00e00551a5ee474ea6d831b5f567a8b4/greenlet-3.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:96aff77af063b607f2489473484e39a0bbae730f2ea90c9e5606c9b73c44174a", size = 228125, upload-time = "2026-01-23T15:32:45.265Z" }, - { url = "https://files.pythonhosted.org/packages/6a/8e/8a2db6d11491837af1de64b8aff23707c6e85241be13c60ed399a72e2ef8/greenlet-3.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:b066e8b50e28b503f604fa538adc764a638b38cf8e81e025011d26e8a627fa79", size = 227519, upload-time = "2026-01-23T15:31:47.284Z" }, - { url = "https://files.pythonhosted.org/packages/28/24/cbbec49bacdcc9ec652a81d3efef7b59f326697e7edf6ed775a5e08e54c2/greenlet-3.3.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:3e63252943c921b90abb035ebe9de832c436401d9c45f262d80e2d06cc659242", size = 282706, upload-time = "2026-01-23T15:33:05.525Z" }, - { url = "https://files.pythonhosted.org/packages/86/2e/4f2b9323c144c4fe8842a4e0d92121465485c3c2c5b9e9b30a52e80f523f/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76e39058e68eb125de10c92524573924e827927df5d3891fbc97bd55764a8774", size = 651209, upload-time = "2026-01-23T16:01:01.517Z" }, - { url = "https://files.pythonhosted.org/packages/d9/87/50ca60e515f5bb55a2fbc5f0c9b5b156de7d2fc51a0a69abc9d23914a237/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9f9d5e7a9310b7a2f416dd13d2e3fd8b42d803968ea580b7c0f322ccb389b97", size = 654300, upload-time = "2026-01-23T16:05:32.199Z" }, - { url = "https://files.pythonhosted.org/packages/1d/94/74310866dfa2b73dd08659a3d18762f83985ad3281901ba0ee9a815194fb/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92497c78adf3ac703b57f1e3813c2d874f27f71a178f9ea5887855da413cd6d2", size = 653842, upload-time = "2026-01-23T15:32:55.671Z" }, - { url = "https://files.pythonhosted.org/packages/97/43/8bf0ffa3d498eeee4c58c212a3905dd6146c01c8dc0b0a046481ca29b18c/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ed6b402bc74d6557a705e197d47f9063733091ed6357b3de33619d8a8d93ac53", size = 1614917, upload-time = "2026-01-23T16:04:26.276Z" }, - { url = "https://files.pythonhosted.org/packages/89/90/a3be7a5f378fc6e84abe4dcfb2ba32b07786861172e502388b4c90000d1b/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:59913f1e5ada20fde795ba906916aea25d442abcc0593fba7e26c92b7ad76249", size = 1676092, upload-time = "2026-01-23T15:33:52.176Z" }, - { url = "https://files.pythonhosted.org/packages/e1/2b/98c7f93e6db9977aaee07eb1e51ca63bd5f779b900d362791d3252e60558/greenlet-3.3.1-cp314-cp314t-win_amd64.whl", hash = "sha256:301860987846c24cb8964bdec0e31a96ad4a2a801b41b4ef40963c1b44f33451", size = 233181, upload-time = "2026-01-23T15:33:00.29Z" }, +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/51/1664f6b78fc6ebbd98019a1fd730e83fa78f2db7058f72b1463d3612b8db/greenlet-3.3.2.tar.gz", hash = "sha256:2eaf067fc6d886931c7962e8c6bede15d2f01965560f3359b27c80bde2d151f2", size = 188267, upload-time = "2026-02-20T20:54:15.531Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/47/16400cb42d18d7a6bb46f0626852c1718612e35dcb0dffa16bbaffdf5dd2/greenlet-3.3.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:c56692189a7d1c7606cb794be0a8381470d95c57ce5be03fb3d0ef57c7853b86", size = 278890, upload-time = "2026-02-20T20:19:39.263Z" }, + { url = "https://files.pythonhosted.org/packages/a3/90/42762b77a5b6aa96cd8c0e80612663d39211e8ae8a6cd47c7f1249a66262/greenlet-3.3.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ebd458fa8285960f382841da585e02201b53a5ec2bac6b156fc623b5ce4499f", size = 581120, upload-time = "2026-02-20T20:47:30.161Z" }, + { url = "https://files.pythonhosted.org/packages/bf/6f/f3d64f4fa0a9c7b5c5b3c810ff1df614540d5aa7d519261b53fba55d4df9/greenlet-3.3.2-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a443358b33c4ec7b05b79a7c8b466f5d275025e750298be7340f8fc63dff2a55", size = 594363, upload-time = "2026-02-20T20:55:56.965Z" }, + { url = "https://files.pythonhosted.org/packages/72/83/3e06a52aca8128bdd4dcd67e932b809e76a96ab8c232a8b025b2850264c5/greenlet-3.3.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e2cd90d413acbf5e77ae41e5d3c9b3ac1d011a756d7284d7f3f2b806bbd6358", size = 594156, upload-time = "2026-02-20T20:20:59.955Z" }, + { url = "https://files.pythonhosted.org/packages/70/79/0de5e62b873e08fe3cef7dbe84e5c4bc0e8ed0c7ff131bccb8405cd107c8/greenlet-3.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:442b6057453c8cb29b4fb36a2ac689382fc71112273726e2423f7f17dc73bf99", size = 1554649, upload-time = "2026-02-20T20:49:32.293Z" }, + { url = "https://files.pythonhosted.org/packages/5a/00/32d30dee8389dc36d42170a9c66217757289e2afb0de59a3565260f38373/greenlet-3.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:45abe8eb6339518180d5a7fa47fa01945414d7cca5ecb745346fc6a87d2750be", size = 1619472, upload-time = "2026-02-20T20:21:07.966Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3a/efb2cf697fbccdf75b24e2c18025e7dfa54c4f31fab75c51d0fe79942cef/greenlet-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e692b2dae4cc7077cbb11b47d258533b48c8fde69a33d0d8a82e2fe8d8531d5", size = 230389, upload-time = "2026-02-20T20:17:18.772Z" }, + { url = "https://files.pythonhosted.org/packages/e1/a1/65bbc059a43a7e2143ec4fc1f9e3f673e04f9c7b371a494a101422ac4fd5/greenlet-3.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:02b0a8682aecd4d3c6c18edf52bc8e51eacdd75c8eac52a790a210b06aa295fd", size = 229645, upload-time = "2026-02-20T20:18:18.695Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ab/1608e5a7578e62113506740b88066bf09888322a311cff602105e619bd87/greenlet-3.3.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ac8d61d4343b799d1e526db579833d72f23759c71e07181c2d2944e429eb09cd", size = 280358, upload-time = "2026-02-20T20:17:43.971Z" }, + { url = "https://files.pythonhosted.org/packages/a5/23/0eae412a4ade4e6623ff7626e38998cb9b11e9ff1ebacaa021e4e108ec15/greenlet-3.3.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ceec72030dae6ac0c8ed7591b96b70410a8be370b6a477b1dbc072856ad02bd", size = 601217, upload-time = "2026-02-20T20:47:31.462Z" }, + { url = "https://files.pythonhosted.org/packages/f8/16/5b1678a9c07098ecb9ab2dd159fafaf12e963293e61ee8d10ecb55273e5e/greenlet-3.3.2-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2a5be83a45ce6188c045bcc44b0ee037d6a518978de9a5d97438548b953a1ac", size = 611792, upload-time = "2026-02-20T20:55:58.423Z" }, + { url = "https://files.pythonhosted.org/packages/50/1f/5155f55bd71cabd03765a4aac9ac446be129895271f73872c36ebd4b04b6/greenlet-3.3.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e99d1749147ac21dde49b99c9abffcbc1e2d55c67501465ef0930d6e78e070", size = 613875, upload-time = "2026-02-20T20:21:01.102Z" }, + { url = "https://files.pythonhosted.org/packages/fc/dd/845f249c3fcd69e32df80cdab059b4be8b766ef5830a3d0aa9d6cad55beb/greenlet-3.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c956a19350e2c37f2c48b336a3afb4bff120b36076d9d7fb68cb44e05d95b79", size = 1571467, upload-time = "2026-02-20T20:49:33.495Z" }, + { url = "https://files.pythonhosted.org/packages/2a/50/2649fe21fcc2b56659a452868e695634722a6655ba245d9f77f5656010bf/greenlet-3.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c6f8ba97d17a1e7d664151284cb3315fc5f8353e75221ed4324f84eb162b395", size = 1640001, upload-time = "2026-02-20T20:21:09.154Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/cc802e067d02af8b60b6771cea7d57e21ef5e6659912814babb42b864713/greenlet-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:34308836d8370bddadb41f5a7ce96879b72e2fdfb4e87729330c6ab52376409f", size = 231081, upload-time = "2026-02-20T20:17:28.121Z" }, + { url = "https://files.pythonhosted.org/packages/58/2e/fe7f36ff1982d6b10a60d5e0740c759259a7d6d2e1dc41da6d96de32fff6/greenlet-3.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:d3a62fa76a32b462a97198e4c9e99afb9ab375115e74e9a83ce180e7a496f643", size = 230331, upload-time = "2026-02-20T20:17:23.34Z" }, + { url = "https://files.pythonhosted.org/packages/ac/48/f8b875fa7dea7dd9b33245e37f065af59df6a25af2f9561efa8d822fde51/greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:aa6ac98bdfd716a749b84d4034486863fd81c3abde9aa3cf8eff9127981a4ae4", size = 279120, upload-time = "2026-02-20T20:19:01.9Z" }, + { url = "https://files.pythonhosted.org/packages/49/8d/9771d03e7a8b1ee456511961e1b97a6d77ae1dea4a34a5b98eee706689d3/greenlet-3.3.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab0c7e7901a00bc0a7284907273dc165b32e0d109a6713babd04471327ff7986", size = 603238, upload-time = "2026-02-20T20:47:32.873Z" }, + { url = "https://files.pythonhosted.org/packages/59/0e/4223c2bbb63cd5c97f28ffb2a8aee71bdfb30b323c35d409450f51b91e3e/greenlet-3.3.2-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d248d8c23c67d2291ffd47af766e2a3aa9fa1c6703155c099feb11f526c63a92", size = 614219, upload-time = "2026-02-20T20:55:59.817Z" }, + { url = "https://files.pythonhosted.org/packages/7a/34/259b28ea7a2a0c904b11cd36c79b8cef8019b26ee5dbe24e73b469dea347/greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6997d360a4e6a4e936c0f9625b1c20416b8a0ea18a8e19cabbefc712e7397ab", size = 616774, upload-time = "2026-02-20T20:21:02.454Z" }, + { url = "https://files.pythonhosted.org/packages/0a/03/996c2d1689d486a6e199cb0f1cf9e4aa940c500e01bdf201299d7d61fa69/greenlet-3.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64970c33a50551c7c50491671265d8954046cb6e8e2999aacdd60e439b70418a", size = 1571277, upload-time = "2026-02-20T20:49:34.795Z" }, + { url = "https://files.pythonhosted.org/packages/d9/c4/2570fc07f34a39f2caf0bf9f24b0a1a0a47bc2e8e465b2c2424821389dfc/greenlet-3.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1a9172f5bf6bd88e6ba5a84e0a68afeac9dc7b6b412b245dd64f52d83c81e55b", size = 1640455, upload-time = "2026-02-20T20:21:10.261Z" }, + { url = "https://files.pythonhosted.org/packages/91/39/5ef5aa23bc545aa0d31e1b9b55822b32c8da93ba657295840b6b34124009/greenlet-3.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:a7945dd0eab63ded0a48e4dcade82939783c172290a7903ebde9e184333ca124", size = 230961, upload-time = "2026-02-20T20:16:58.461Z" }, + { url = "https://files.pythonhosted.org/packages/62/6b/a89f8456dcb06becff288f563618e9f20deed8dd29beea14f9a168aef64b/greenlet-3.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:394ead29063ee3515b4e775216cb756b2e3b4a7e55ae8fd884f17fa579e6b327", size = 230221, upload-time = "2026-02-20T20:17:37.152Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ae/8bffcbd373b57a5992cd077cbe8858fff39110480a9d50697091faea6f39/greenlet-3.3.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8d1658d7291f9859beed69a776c10822a0a799bc4bfe1bd4272bb60e62507dab", size = 279650, upload-time = "2026-02-20T20:18:00.783Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c0/45f93f348fa49abf32ac8439938726c480bd96b2a3c6f4d949ec0124b69f/greenlet-3.3.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18cb1b7337bca281915b3c5d5ae19f4e76d35e1df80f4ad3c1a7be91fadf1082", size = 650295, upload-time = "2026-02-20T20:47:34.036Z" }, + { url = "https://files.pythonhosted.org/packages/b3/de/dd7589b3f2b8372069ab3e4763ea5329940fc7ad9dcd3e272a37516d7c9b/greenlet-3.3.2-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2e47408e8ce1c6f1ceea0dffcdf6ebb85cc09e55c7af407c99f1112016e45e9", size = 662163, upload-time = "2026-02-20T20:56:01.295Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d8/09bfa816572a4d83bccd6750df1926f79158b1c36c5f73786e26dbe4ee38/greenlet-3.3.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63d10328839d1973e5ba35e98cccbca71b232b14051fd957b6f8b6e8e80d0506", size = 664160, upload-time = "2026-02-20T20:21:04.015Z" }, + { url = "https://files.pythonhosted.org/packages/48/cf/56832f0c8255d27f6c35d41b5ec91168d74ec721d85f01a12131eec6b93c/greenlet-3.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e4ab3cfb02993c8cc248ea73d7dae6cec0253e9afa311c9b37e603ca9fad2ce", size = 1619181, upload-time = "2026-02-20T20:49:36.052Z" }, + { url = "https://files.pythonhosted.org/packages/0a/23/b90b60a4aabb4cec0796e55f25ffbfb579a907c3898cd2905c8918acaa16/greenlet-3.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94ad81f0fd3c0c0681a018a976e5c2bd2ca2d9d94895f23e7bb1af4e8af4e2d5", size = 1687713, upload-time = "2026-02-20T20:21:11.684Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ca/2101ca3d9223a1dc125140dbc063644dca76df6ff356531eb27bc267b446/greenlet-3.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:8c4dd0f3997cf2512f7601563cc90dfb8957c0cff1e3a1b23991d4ea1776c492", size = 232034, upload-time = "2026-02-20T20:20:08.186Z" }, + { url = "https://files.pythonhosted.org/packages/f6/4a/ecf894e962a59dea60f04877eea0fd5724618da89f1867b28ee8b91e811f/greenlet-3.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:cd6f9e2bbd46321ba3bbb4c8a15794d32960e3b0ae2cc4d49a1a53d314805d71", size = 231437, upload-time = "2026-02-20T20:18:59.722Z" }, + { url = "https://files.pythonhosted.org/packages/98/6d/8f2ef704e614bcf58ed43cfb8d87afa1c285e98194ab2cfad351bf04f81e/greenlet-3.3.2-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:e26e72bec7ab387ac80caa7496e0f908ff954f31065b0ffc1f8ecb1338b11b54", size = 286617, upload-time = "2026-02-20T20:19:29.856Z" }, + { url = "https://files.pythonhosted.org/packages/5e/0d/93894161d307c6ea237a43988f27eba0947b360b99ac5239ad3fe09f0b47/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b466dff7a4ffda6ca975979bab80bdadde979e29fc947ac3be4451428d8b0e4", size = 655189, upload-time = "2026-02-20T20:47:35.742Z" }, + { url = "https://files.pythonhosted.org/packages/f5/2c/d2d506ebd8abcb57386ec4f7ba20f4030cbe56eae541bc6fd6ef399c0b41/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b8bddc5b73c9720bea487b3bffdb1840fe4e3656fba3bd40aa1489e9f37877ff", size = 658225, upload-time = "2026-02-20T20:56:02.527Z" }, + { url = "https://files.pythonhosted.org/packages/8e/30/3a09155fbf728673a1dea713572d2d31159f824a37c22da82127056c44e4/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b26b0f4428b871a751968285a1ac9648944cea09807177ac639b030bddebcea4", size = 657907, upload-time = "2026-02-20T20:21:05.259Z" }, + { url = "https://files.pythonhosted.org/packages/f3/fd/d05a4b7acd0154ed758797f0a43b4c0962a843bedfe980115e842c5b2d08/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1fb39a11ee2e4d94be9a76671482be9398560955c9e568550de0224e41104727", size = 1618857, upload-time = "2026-02-20T20:49:37.309Z" }, + { url = "https://files.pythonhosted.org/packages/6f/e1/50ee92a5db521de8f35075b5eff060dd43d39ebd46c2181a2042f7070385/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:20154044d9085151bc309e7689d6f7ba10027f8f5a8c0676ad398b951913d89e", size = 1680010, upload-time = "2026-02-20T20:21:13.427Z" }, + { url = "https://files.pythonhosted.org/packages/29/4b/45d90626aef8e65336bed690106d1382f7a43665e2249017e9527df8823b/greenlet-3.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c04c5e06ec3e022cbfe2cd4a846e1d4e50087444f875ff6d2c2ad8445495cf1a", size = 237086, upload-time = "2026-02-20T20:20:45.786Z" }, ] [[package]] name = "grpcio" -version = "1.78.0" +version = "1.78.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/8a/3d098f35c143a89520e568e6539cc098fcd294495910e359889ce8741c84/grpcio-1.78.0.tar.gz", hash = "sha256:7382b95189546f375c174f53a5fa873cef91c4b8005faa05cc5b3beea9c4f1c5", size = 12852416, upload-time = "2026-02-06T09:57:18.093Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/86/c7/d0b780a29b0837bf4ca9580904dfb275c1fc321ded7897d620af7047ec57/grpcio-1.78.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:2777b783f6c13b92bd7b716667452c329eefd646bfb3f2e9dabea2e05dbd34f6", size = 5951525, upload-time = "2026-02-06T09:55:01.989Z" }, - { url = "https://files.pythonhosted.org/packages/c5/b1/96920bf2ee61df85a9503cb6f733fe711c0ff321a5a697d791b075673281/grpcio-1.78.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:9dca934f24c732750389ce49d638069c3892ad065df86cb465b3fa3012b70c9e", size = 11830418, upload-time = "2026-02-06T09:55:04.462Z" }, - { url = "https://files.pythonhosted.org/packages/83/0c/7c1528f098aeb75a97de2bae18c530f56959fb7ad6c882db45d9884d6edc/grpcio-1.78.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:459ab414b35f4496138d0ecd735fed26f1318af5e52cb1efbc82a09f0d5aa911", size = 6524477, upload-time = "2026-02-06T09:55:07.111Z" }, - { url = "https://files.pythonhosted.org/packages/8d/52/e7c1f3688f949058e19a011c4e0dec973da3d0ae5e033909677f967ae1f4/grpcio-1.78.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:082653eecbdf290e6e3e2c276ab2c54b9e7c299e07f4221872380312d8cf395e", size = 7198266, upload-time = "2026-02-06T09:55:10.016Z" }, - { url = "https://files.pythonhosted.org/packages/e5/61/8ac32517c1e856677282c34f2e7812d6c328fa02b8f4067ab80e77fdc9c9/grpcio-1.78.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85f93781028ec63f383f6bc90db785a016319c561cc11151fbb7b34e0d012303", size = 6730552, upload-time = "2026-02-06T09:55:12.207Z" }, - { url = "https://files.pythonhosted.org/packages/bd/98/b8ee0158199250220734f620b12e4a345955ac7329cfd908d0bf0fda77f0/grpcio-1.78.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f12857d24d98441af6a1d5c87442d624411db486f7ba12550b07788f74b67b04", size = 7304296, upload-time = "2026-02-06T09:55:15.044Z" }, - { url = "https://files.pythonhosted.org/packages/bd/0f/7b72762e0d8840b58032a56fdbd02b78fc645b9fa993d71abf04edbc54f4/grpcio-1.78.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5397fff416b79e4b284959642a4e95ac4b0f1ece82c9993658e0e477d40551ec", size = 8288298, upload-time = "2026-02-06T09:55:17.276Z" }, - { url = "https://files.pythonhosted.org/packages/24/ae/ae4ce56bc5bb5caa3a486d60f5f6083ac3469228faa734362487176c15c5/grpcio-1.78.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fbe6e89c7ffb48518384068321621b2a69cab509f58e40e4399fdd378fa6d074", size = 7730953, upload-time = "2026-02-06T09:55:19.545Z" }, - { url = "https://files.pythonhosted.org/packages/b5/6e/8052e3a28eb6a820c372b2eb4b5e32d195c661e137d3eca94d534a4cfd8a/grpcio-1.78.0-cp311-cp311-win32.whl", hash = "sha256:6092beabe1966a3229f599d7088b38dfc8ffa1608b5b5cdda31e591e6500f856", size = 4076503, upload-time = "2026-02-06T09:55:21.521Z" }, - { url = "https://files.pythonhosted.org/packages/08/62/f22c98c5265dfad327251fa2f840b591b1df5f5e15d88b19c18c86965b27/grpcio-1.78.0-cp311-cp311-win_amd64.whl", hash = "sha256:1afa62af6e23f88629f2b29ec9e52ec7c65a7176c1e0a83292b93c76ca882558", size = 4799767, upload-time = "2026-02-06T09:55:24.107Z" }, - { url = "https://files.pythonhosted.org/packages/4e/f4/7384ed0178203d6074446b3c4f46c90a22ddf7ae0b3aee521627f54cfc2a/grpcio-1.78.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:f9ab915a267fc47c7e88c387a3a28325b58c898e23d4995f765728f4e3dedb97", size = 5913985, upload-time = "2026-02-06T09:55:26.832Z" }, - { url = "https://files.pythonhosted.org/packages/81/ed/be1caa25f06594463f685b3790b320f18aea49b33166f4141bfdc2bfb236/grpcio-1.78.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3f8904a8165ab21e07e58bf3e30a73f4dffc7a1e0dbc32d51c61b5360d26f43e", size = 11811853, upload-time = "2026-02-06T09:55:29.224Z" }, - { url = "https://files.pythonhosted.org/packages/24/a7/f06d151afc4e64b7e3cc3e872d331d011c279aaab02831e40a81c691fb65/grpcio-1.78.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:859b13906ce098c0b493af92142ad051bf64c7870fa58a123911c88606714996", size = 6475766, upload-time = "2026-02-06T09:55:31.825Z" }, - { url = "https://files.pythonhosted.org/packages/8a/a8/4482922da832ec0082d0f2cc3a10976d84a7424707f25780b82814aafc0a/grpcio-1.78.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b2342d87af32790f934a79c3112641e7b27d63c261b8b4395350dad43eff1dc7", size = 7170027, upload-time = "2026-02-06T09:55:34.7Z" }, - { url = "https://files.pythonhosted.org/packages/54/bf/f4a3b9693e35d25b24b0b39fa46d7d8a3c439e0a3036c3451764678fec20/grpcio-1.78.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12a771591ae40bc65ba67048fa52ef4f0e6db8279e595fd349f9dfddeef571f9", size = 6690766, upload-time = "2026-02-06T09:55:36.902Z" }, - { url = "https://files.pythonhosted.org/packages/c7/b9/521875265cc99fe5ad4c5a17010018085cae2810a928bf15ebe7d8bcd9cc/grpcio-1.78.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:185dea0d5260cbb2d224c507bf2a5444d5abbb1fa3594c1ed7e4c709d5eb8383", size = 7266161, upload-time = "2026-02-06T09:55:39.824Z" }, - { url = "https://files.pythonhosted.org/packages/05/86/296a82844fd40a4ad4a95f100b55044b4f817dece732bf686aea1a284147/grpcio-1.78.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51b13f9aed9d59ee389ad666b8c2214cc87b5de258fa712f9ab05f922e3896c6", size = 8253303, upload-time = "2026-02-06T09:55:42.353Z" }, - { url = "https://files.pythonhosted.org/packages/f3/e4/ea3c0caf5468537f27ad5aab92b681ed7cc0ef5f8c9196d3fd42c8c2286b/grpcio-1.78.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd5f135b1bd58ab088930b3c613455796dfa0393626a6972663ccdda5b4ac6ce", size = 7698222, upload-time = "2026-02-06T09:55:44.629Z" }, - { url = "https://files.pythonhosted.org/packages/d7/47/7f05f81e4bb6b831e93271fb12fd52ba7b319b5402cbc101d588f435df00/grpcio-1.78.0-cp312-cp312-win32.whl", hash = "sha256:94309f498bcc07e5a7d16089ab984d42ad96af1d94b5a4eb966a266d9fcabf68", size = 4066123, upload-time = "2026-02-06T09:55:47.644Z" }, - { url = "https://files.pythonhosted.org/packages/ad/e7/d6914822c88aa2974dbbd10903d801a28a19ce9cd8bad7e694cbbcf61528/grpcio-1.78.0-cp312-cp312-win_amd64.whl", hash = "sha256:9566fe4ababbb2610c39190791e5b829869351d14369603702e890ef3ad2d06e", size = 4797657, upload-time = "2026-02-06T09:55:49.86Z" }, - { url = "https://files.pythonhosted.org/packages/05/a9/8f75894993895f361ed8636cd9237f4ab39ef87fd30db17467235ed1c045/grpcio-1.78.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:ce3a90455492bf8bfa38e56fbbe1dbd4f872a3d8eeaf7337dc3b1c8aa28c271b", size = 5920143, upload-time = "2026-02-06T09:55:52.035Z" }, - { url = "https://files.pythonhosted.org/packages/55/06/0b78408e938ac424100100fd081189451b472236e8a3a1f6500390dc4954/grpcio-1.78.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:2bf5e2e163b356978b23652c4818ce4759d40f4712ee9ec5a83c4be6f8c23a3a", size = 11803926, upload-time = "2026-02-06T09:55:55.494Z" }, - { url = "https://files.pythonhosted.org/packages/88/93/b59fe7832ff6ae3c78b813ea43dac60e295fa03606d14d89d2e0ec29f4f3/grpcio-1.78.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8f2ac84905d12918e4e55a16da17939eb63e433dc11b677267c35568aa63fc84", size = 6478628, upload-time = "2026-02-06T09:55:58.533Z" }, - { url = "https://files.pythonhosted.org/packages/ed/df/e67e3734527f9926b7d9c0dde6cd998d1d26850c3ed8eeec81297967ac67/grpcio-1.78.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b58f37edab4a3881bc6c9bca52670610e0c9ca14e2ea3cf9debf185b870457fb", size = 7173574, upload-time = "2026-02-06T09:56:01.786Z" }, - { url = "https://files.pythonhosted.org/packages/a6/62/cc03fffb07bfba982a9ec097b164e8835546980aec25ecfa5f9c1a47e022/grpcio-1.78.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:735e38e176a88ce41840c21bb49098ab66177c64c82426e24e0082500cc68af5", size = 6692639, upload-time = "2026-02-06T09:56:04.529Z" }, - { url = "https://files.pythonhosted.org/packages/bf/9a/289c32e301b85bdb67d7ec68b752155e674ee3ba2173a1858f118e399ef3/grpcio-1.78.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2045397e63a7a0ee7957c25f7dbb36ddc110e0cfb418403d110c0a7a68a844e9", size = 7268838, upload-time = "2026-02-06T09:56:08.397Z" }, - { url = "https://files.pythonhosted.org/packages/0e/79/1be93f32add280461fa4773880196572563e9c8510861ac2da0ea0f892b6/grpcio-1.78.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a9f136fbafe7ccf4ac7e8e0c28b31066e810be52d6e344ef954a3a70234e1702", size = 8251878, upload-time = "2026-02-06T09:56:10.914Z" }, - { url = "https://files.pythonhosted.org/packages/65/65/793f8e95296ab92e4164593674ae6291b204bb5f67f9d4a711489cd30ffa/grpcio-1.78.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:748b6138585379c737adc08aeffd21222abbda1a86a0dca2a39682feb9196c20", size = 7695412, upload-time = "2026-02-06T09:56:13.593Z" }, - { url = "https://files.pythonhosted.org/packages/1c/9f/1e233fe697ecc82845942c2822ed06bb522e70d6771c28d5528e4c50f6a4/grpcio-1.78.0-cp313-cp313-win32.whl", hash = "sha256:271c73e6e5676afe4fc52907686670c7cea22ab2310b76a59b678403ed40d670", size = 4064899, upload-time = "2026-02-06T09:56:15.601Z" }, - { url = "https://files.pythonhosted.org/packages/4d/27/d86b89e36de8a951501fb06a0f38df19853210f341d0b28f83f4aa0ffa08/grpcio-1.78.0-cp313-cp313-win_amd64.whl", hash = "sha256:f2d4e43ee362adfc05994ed479334d5a451ab7bc3f3fee1b796b8ca66895acb4", size = 4797393, upload-time = "2026-02-06T09:56:17.882Z" }, - { url = "https://files.pythonhosted.org/packages/29/f2/b56e43e3c968bfe822fa6ce5bca10d5c723aa40875b48791ce1029bb78c7/grpcio-1.78.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:e87cbc002b6f440482b3519e36e1313eb5443e9e9e73d6a52d43bd2004fcfd8e", size = 5920591, upload-time = "2026-02-06T09:56:20.758Z" }, - { url = "https://files.pythonhosted.org/packages/5d/81/1f3b65bd30c334167bfa8b0d23300a44e2725ce39bba5b76a2460d85f745/grpcio-1.78.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:c41bc64626db62e72afec66b0c8a0da76491510015417c127bfc53b2fe6d7f7f", size = 11813685, upload-time = "2026-02-06T09:56:24.315Z" }, - { url = "https://files.pythonhosted.org/packages/0e/1c/bbe2f8216a5bd3036119c544d63c2e592bdf4a8ec6e4a1867592f4586b26/grpcio-1.78.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8dfffba826efcf366b1e3ccc37e67afe676f290e13a3b48d31a46739f80a8724", size = 6487803, upload-time = "2026-02-06T09:56:27.367Z" }, - { url = "https://files.pythonhosted.org/packages/16/5c/a6b2419723ea7ddce6308259a55e8e7593d88464ce8db9f4aa857aba96fa/grpcio-1.78.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:74be1268d1439eaaf552c698cdb11cd594f0c49295ae6bb72c34ee31abbe611b", size = 7173206, upload-time = "2026-02-06T09:56:29.876Z" }, - { url = "https://files.pythonhosted.org/packages/df/1e/b8801345629a415ea7e26c83d75eb5dbe91b07ffe5210cc517348a8d4218/grpcio-1.78.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be63c88b32e6c0f1429f1398ca5c09bc64b0d80950c8bb7807d7d7fb36fb84c7", size = 6693826, upload-time = "2026-02-06T09:56:32.305Z" }, - { url = "https://files.pythonhosted.org/packages/34/84/0de28eac0377742679a510784f049738a80424b17287739fc47d63c2439e/grpcio-1.78.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3c586ac70e855c721bda8f548d38c3ca66ac791dc49b66a8281a1f99db85e452", size = 7277897, upload-time = "2026-02-06T09:56:34.915Z" }, - { url = "https://files.pythonhosted.org/packages/ca/9c/ad8685cfe20559a9edb66f735afdcb2b7d3de69b13666fdfc542e1916ebd/grpcio-1.78.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:35eb275bf1751d2ffbd8f57cdbc46058e857cf3971041521b78b7db94bdaf127", size = 8252404, upload-time = "2026-02-06T09:56:37.553Z" }, - { url = "https://files.pythonhosted.org/packages/3c/05/33a7a4985586f27e1de4803887c417ec7ced145ebd069bc38a9607059e2b/grpcio-1.78.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:207db540302c884b8848036b80db352a832b99dfdf41db1eb554c2c2c7800f65", size = 7696837, upload-time = "2026-02-06T09:56:40.173Z" }, - { url = "https://files.pythonhosted.org/packages/73/77/7382241caf88729b106e49e7d18e3116216c778e6a7e833826eb96de22f7/grpcio-1.78.0-cp314-cp314-win32.whl", hash = "sha256:57bab6deef2f4f1ca76cc04565df38dc5713ae6c17de690721bdf30cb1e0545c", size = 4142439, upload-time = "2026-02-06T09:56:43.258Z" }, - { url = "https://files.pythonhosted.org/packages/48/b2/b096ccce418882fbfda4f7496f9357aaa9a5af1896a9a7f60d9f2b275a06/grpcio-1.78.0-cp314-cp314-win_amd64.whl", hash = "sha256:dce09d6116df20a96acfdbf85e4866258c3758180e8c49845d6ba8248b6d0bbb", size = 4929852, upload-time = "2026-02-06T09:56:45.885Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/1f/de/de568532d9907552700f80dcec38219d8d298ad9e71f5e0a095abaf2761e/grpcio-1.78.1.tar.gz", hash = "sha256:27c625532d33ace45d57e775edf1982e183ff8641c72e4e91ef7ba667a149d72", size = 12835760, upload-time = "2026-02-20T01:16:10.869Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/1e/ad774af3b2c84f49c6d8c4a7bea4c40f02268ea8380630c28777edda463b/grpcio-1.78.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:3a8aa79bc6e004394c0abefd4b034c14affda7b66480085d87f5fbadf43b593b", size = 5951132, upload-time = "2026-02-20T01:13:05.942Z" }, + { url = "https://files.pythonhosted.org/packages/48/9d/ad3c284bedd88c545e20675d98ae904114d8517a71b0efc0901e9166628f/grpcio-1.78.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:8e1fcb419da5811deb47b7749b8049f7c62b993ba17822e3c7231e3e0ba65b79", size = 11831052, upload-time = "2026-02-20T01:13:09.604Z" }, + { url = "https://files.pythonhosted.org/packages/6d/08/20d12865e47242d03c3ade9bb2127f5b4aded964f373284cfb357d47c5ac/grpcio-1.78.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b071dccac245c32cd6b1dd96b722283b855881ca0bf1c685cf843185f5d5d51e", size = 6524749, upload-time = "2026-02-20T01:13:21.692Z" }, + { url = "https://files.pythonhosted.org/packages/c6/53/a8b72f52b253ec0cfdf88a13e9236a9d717c332b8aa5f0ba9e4699e94b55/grpcio-1.78.1-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:d6fb962947e4fe321eeef3be1ba5ba49d32dea9233c825fcbade8e858c14aaf4", size = 7198995, upload-time = "2026-02-20T01:13:24.275Z" }, + { url = "https://files.pythonhosted.org/packages/13/3c/ac769c8ded1bcb26bb119fb472d3374b481b3cf059a0875db9fc77139c17/grpcio-1.78.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6afd191551fd72e632367dfb083e33cd185bf9ead565f2476bba8ab864ae496", size = 6730770, upload-time = "2026-02-20T01:13:26.522Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c3/2275ef4cc5b942314321f77d66179be4097ff484e82ca34bf7baa5b1ddbc/grpcio-1.78.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b2acd83186305c0802dbc4d81ed0ec2f3e8658d7fde97cfba2f78d7372f05b89", size = 7305036, upload-time = "2026-02-20T01:13:30.923Z" }, + { url = "https://files.pythonhosted.org/packages/91/cb/3c2aa99e12cbbfc72c2ed8aa328e6041709d607d668860380e6cd00ba17d/grpcio-1.78.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5380268ab8513445740f1f77bd966d13043d07e2793487e61fd5b5d0935071eb", size = 8288641, upload-time = "2026-02-20T01:13:39.42Z" }, + { url = "https://files.pythonhosted.org/packages/0d/b2/21b89f492260ac645775d9973752ca873acfd0609d6998e9d3065a21ea2f/grpcio-1.78.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:389b77484959bdaad6a2b7dda44d7d1228381dd669a03f5660392aa0e9385b22", size = 7730967, upload-time = "2026-02-20T01:13:41.697Z" }, + { url = "https://files.pythonhosted.org/packages/24/03/6b89eddf87fdffb8fa9d37375d44d3a798f4b8116ac363a5f7ca84caa327/grpcio-1.78.1-cp311-cp311-win32.whl", hash = "sha256:9dee66d142f4a8cca36b5b98a38f006419138c3c89e72071747f8fca415a6d8f", size = 4076680, upload-time = "2026-02-20T01:13:43.781Z" }, + { url = "https://files.pythonhosted.org/packages/a7/a8/204460b1bc1dff9862e98f56a2d14be3c4171f929f8eaf8c4517174b4270/grpcio-1.78.1-cp311-cp311-win_amd64.whl", hash = "sha256:43b930cf4f9c4a2262bb3e5d5bc40df426a72538b4f98e46f158b7eb112d2d70", size = 4801074, upload-time = "2026-02-20T01:13:46.315Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ed/d2eb9d27fded1a76b2a80eb9aa8b12101da7e41ce2bac0ad3651e88a14ae/grpcio-1.78.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:41e4605c923e0e9a84a2718e4948a53a530172bfaf1a6d1ded16ef9c5849fca2", size = 5913389, upload-time = "2026-02-20T01:13:49.005Z" }, + { url = "https://files.pythonhosted.org/packages/69/1b/40034e9ab010eeb3fa41ec61d8398c6dbf7062f3872c866b8f72700e2522/grpcio-1.78.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:39da1680d260c0c619c3b5fa2dc47480ca24d5704c7a548098bca7de7f5dd17f", size = 11811839, upload-time = "2026-02-20T01:13:51.839Z" }, + { url = "https://files.pythonhosted.org/packages/b4/69/fe16ef2979ea62b8aceb3a3f1e7a8bbb8b717ae2a44b5899d5d426073273/grpcio-1.78.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b5d5881d72a09b8336a8f874784a8eeffacde44a7bc1a148bce5a0243a265ef0", size = 6475805, upload-time = "2026-02-20T01:13:55.423Z" }, + { url = "https://files.pythonhosted.org/packages/5b/1e/069e0a9062167db18446917d7c00ae2e91029f96078a072bedc30aaaa8c3/grpcio-1.78.1-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:888ceb7821acd925b1c90f0cdceaed1386e69cfe25e496e0771f6c35a156132f", size = 7169955, upload-time = "2026-02-20T01:13:59.553Z" }, + { url = "https://files.pythonhosted.org/packages/38/fc/44a57e2bb4a755e309ee4e9ed2b85c9af93450b6d3118de7e69410ee05fa/grpcio-1.78.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8942bdfc143b467c264b048862090c4ba9a0223c52ae28c9ae97754361372e42", size = 6690767, upload-time = "2026-02-20T01:14:02.31Z" }, + { url = "https://files.pythonhosted.org/packages/b8/87/21e16345d4c75046d453916166bc72a3309a382c8e97381ec4b8c1a54729/grpcio-1.78.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:716a544969660ed609164aff27b2effd3ff84e54ac81aa4ce77b1607ca917d22", size = 7266846, upload-time = "2026-02-20T01:14:12.974Z" }, + { url = "https://files.pythonhosted.org/packages/11/df/d6261983f9ca9ef4d69893765007a9a3211b91d9faf85a2591063df381c7/grpcio-1.78.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4d50329b081c223d444751076bb5b389d4f06c2b32d51b31a1e98172e6cecfb9", size = 8253522, upload-time = "2026-02-20T01:14:17.407Z" }, + { url = "https://files.pythonhosted.org/packages/de/7c/4f96a0ff113c5d853a27084d7590cd53fdb05169b596ea9f5f27f17e021e/grpcio-1.78.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7e836778c13ff70edada16567e8da0c431e8818eaae85b80d11c1ba5782eccbb", size = 7698070, upload-time = "2026-02-20T01:14:20.032Z" }, + { url = "https://files.pythonhosted.org/packages/17/3c/7b55c0b5af88fbeb3d0c13e25492d3ace41ac9dbd0f5f8f6c0fb613b6706/grpcio-1.78.1-cp312-cp312-win32.whl", hash = "sha256:07eb016ea7444a22bef465cce045512756956433f54450aeaa0b443b8563b9ca", size = 4066474, upload-time = "2026-02-20T01:14:22.602Z" }, + { url = "https://files.pythonhosted.org/packages/5d/17/388c12d298901b0acf10b612b650692bfed60e541672b1d8965acbf2d722/grpcio-1.78.1-cp312-cp312-win_amd64.whl", hash = "sha256:02b82dcd2fa580f5e82b4cf62ecde1b3c7cc9ba27b946421200706a6e5acaf85", size = 4797537, upload-time = "2026-02-20T01:14:25.444Z" }, + { url = "https://files.pythonhosted.org/packages/df/72/754754639cfd16ad04619e1435a518124b2d858e5752225376f9285d4c51/grpcio-1.78.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:2b7ad2981550ce999e25ce3f10c8863f718a352a2fd655068d29ea3fd37b4907", size = 5919437, upload-time = "2026-02-20T01:14:29.403Z" }, + { url = "https://files.pythonhosted.org/packages/5c/84/6267d1266f8bc335d3a8b7ccf981be7de41e3ed8bd3a49e57e588212b437/grpcio-1.78.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:409bfe22220889b9906739910a0ee4c197a967c21b8dd14b4b06dd477f8819ce", size = 11803701, upload-time = "2026-02-20T01:14:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/f3/56/c9098e8b920a54261cd605bbb040de0cde1ca4406102db0aa2c0b11d1fb4/grpcio-1.78.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:34b6cb16f4b67eeb5206250dc5b4d5e8e3db939535e58efc330e4c61341554bd", size = 6479416, upload-time = "2026-02-20T01:14:35.926Z" }, + { url = "https://files.pythonhosted.org/packages/86/cf/5d52024371ee62658b7ed72480200524087528844ec1b65265bbcd31c974/grpcio-1.78.1-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:39d21fd30d38a5afb93f0e2e71e2ec2bd894605fb75d41d5a40060c2f98f8d11", size = 7174087, upload-time = "2026-02-20T01:14:39.98Z" }, + { url = "https://files.pythonhosted.org/packages/31/e6/5e59551afad4279e27335a6d60813b8aa3ae7b14fb62cea1d329a459c118/grpcio-1.78.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:09fbd4bcaadb6d8604ed1504b0bdf7ac18e48467e83a9d930a70a7fefa27e862", size = 6692881, upload-time = "2026-02-20T01:14:42.466Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/940062de2d14013c02f51b079eb717964d67d46f5d44f22038975c9d9576/grpcio-1.78.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:db681513a1bdd879c0b24a5a6a70398da5eaaba0e077a306410dc6008426847a", size = 7269092, upload-time = "2026-02-20T01:14:45.826Z" }, + { url = "https://files.pythonhosted.org/packages/09/87/9db657a4b5f3b15560ec591db950bc75a1a2f9e07832578d7e2b23d1a7bd/grpcio-1.78.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f81816faa426da461e9a597a178832a351d6f1078102590a4b32c77d251b71eb", size = 8252037, upload-time = "2026-02-20T01:14:48.57Z" }, + { url = "https://files.pythonhosted.org/packages/e2/37/b980e0265479ec65e26b6e300a39ceac33ecb3f762c2861d4bac990317cf/grpcio-1.78.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffbb760df1cd49e0989f9826b2fd48930700db6846ac171eaff404f3cfbe5c28", size = 7695243, upload-time = "2026-02-20T01:14:51.376Z" }, + { url = "https://files.pythonhosted.org/packages/98/46/5fc42c100ab702fa1ea41a75c890c563c3f96432b4a287d5a6369654f323/grpcio-1.78.1-cp313-cp313-win32.whl", hash = "sha256:1a56bf3ee99af5cf32d469de91bf5de79bdac2e18082b495fc1063ea33f4f2d0", size = 4065329, upload-time = "2026-02-20T01:14:53.952Z" }, + { url = "https://files.pythonhosted.org/packages/b0/da/806d60bb6611dfc16cf463d982bd92bd8b6bd5f87dfac66b0a44dfe20995/grpcio-1.78.1-cp313-cp313-win_amd64.whl", hash = "sha256:8991c2add0d8505178ff6c3ae54bd9386279e712be82fa3733c54067aae9eda1", size = 4797637, upload-time = "2026-02-20T01:14:57.276Z" }, + { url = "https://files.pythonhosted.org/packages/96/3a/2d2ec4d2ce2eb9d6a2b862630a0d9d4ff4239ecf1474ecff21442a78612a/grpcio-1.78.1-cp314-cp314-linux_armv7l.whl", hash = "sha256:d101fe49b1e0fb4a7aa36ed0c3821a0f67a5956ef572745452d2cd790d723a3f", size = 5920256, upload-time = "2026-02-20T01:15:00.23Z" }, + { url = "https://files.pythonhosted.org/packages/9c/92/dccb7d087a1220ed358753945230c1ddeeed13684b954cb09db6758f1271/grpcio-1.78.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:5ce1855e8cfc217cdf6bcfe0cf046d7cf81ddcc3e6894d6cfd075f87a2d8f460", size = 11813749, upload-time = "2026-02-20T01:15:03.312Z" }, + { url = "https://files.pythonhosted.org/packages/ef/47/c20e87f87986da9998f30f14776ce27e61f02482a3a030ffe265089342c6/grpcio-1.78.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd26048d066b51f39fe9206e2bcc2cea869a5e5b2d13c8d523f4179193047ebd", size = 6488739, upload-time = "2026-02-20T01:15:14.349Z" }, + { url = "https://files.pythonhosted.org/packages/a6/c2/088bd96e255133d7d87c3eed0d598350d16cde1041bdbe2bb065967aaf91/grpcio-1.78.1-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4b8d7fda614cf2af0f73bbb042f3b7fee2ecd4aea69ec98dbd903590a1083529", size = 7173096, upload-time = "2026-02-20T01:15:17.687Z" }, + { url = "https://files.pythonhosted.org/packages/60/ce/168db121073a03355ce3552b3b1f790b5ded62deffd7d98c5f642b9d3d81/grpcio-1.78.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:656a5bd142caeb8b1efe1fe0b4434ecc7781f44c97cfc7927f6608627cf178c0", size = 6693861, upload-time = "2026-02-20T01:15:20.911Z" }, + { url = "https://files.pythonhosted.org/packages/ae/d0/90b30ec2d9425215dd56922d85a90babbe6ee7e8256ba77d866b9c0d3aba/grpcio-1.78.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:99550e344482e3c21950c034f74668fccf8a546d50c1ecb4f717543bbdc071ba", size = 7278083, upload-time = "2026-02-20T01:15:23.698Z" }, + { url = "https://files.pythonhosted.org/packages/c1/fb/73f9ba0b082bcd385d46205095fd9c917754685885b28fce3741e9f54529/grpcio-1.78.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:8f27683ca68359bd3f0eb4925824d71e538f84338b3ae337ead2ae43977d7541", size = 8252546, upload-time = "2026-02-20T01:15:26.517Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/6a89ea3cb5db6c3d9ed029b0396c49f64328c0cf5d2630ffeed25711920a/grpcio-1.78.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a40515b69ac50792f9b8ead260f194ba2bb3285375b6c40c7ff938f14c3df17d", size = 7696289, upload-time = "2026-02-20T01:15:29.718Z" }, + { url = "https://files.pythonhosted.org/packages/3d/05/63a7495048499ef437b4933d32e59b7f737bd5368ad6fb2479e2bd83bf2c/grpcio-1.78.1-cp314-cp314-win32.whl", hash = "sha256:2c473b54ef1618f4fb85e82ff4994de18143b74efc088b91b5a935a3a45042ba", size = 4142186, upload-time = "2026-02-20T01:15:32.786Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ce/adfe7e5f701d503be7778291757452e3fab6b19acf51917c79f5d1cf7f8a/grpcio-1.78.1-cp314-cp314-win_amd64.whl", hash = "sha256:e2a6b33d1050dce2c6f563c5caf7f7cbeebf7fba8cde37ffe3803d50526900d1", size = 4932000, upload-time = "2026-02-20T01:15:36.127Z" }, ] [[package]] @@ -1586,16 +1586,15 @@ wheels = [ [[package]] name = "lightning-utilities" -version = "0.15.2" +version = "0.15.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, - { name = "setuptools" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b8/39/6fc58ca81492db047149b4b8fd385aa1bfb8c28cd7cacb0c7eb0c44d842f/lightning_utilities-0.15.2.tar.gz", hash = "sha256:cdf12f530214a63dacefd713f180d1ecf5d165338101617b4742e8f22c032e24", size = 31090, upload-time = "2025-08-06T13:57:39.242Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/45/7fa8f56b17dc0f0a41ec70dd307ecd6787254483549843bef4c30ab5adce/lightning_utilities-0.15.3.tar.gz", hash = "sha256:792ae0204c79f6859721ac7f386c237a33b0ed06ba775009cb894e010a842033", size = 33553, upload-time = "2026-02-22T14:48:53.348Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/73/3d757cb3fc16f0f9794dd289bcd0c4a031d9cf54d8137d6b984b2d02edf3/lightning_utilities-0.15.2-py3-none-any.whl", hash = "sha256:ad3ab1703775044bbf880dbf7ddaaac899396c96315f3aa1779cec9d618a9841", size = 29431, upload-time = "2025-08-06T13:57:38.046Z" }, + { url = "https://files.pythonhosted.org/packages/25/f4/ead6e0e37209b07c9baa3e984ccdb0348ca370b77cea3aaea8ddbb097e00/lightning_utilities-0.15.3-py3-none-any.whl", hash = "sha256:6c55f1bee70084a1cbeaa41ada96e4b3a0fea5909e844dd335bd80f5a73c5f91", size = 31906, upload-time = "2026-02-22T14:48:52.488Z" }, ] [[package]] @@ -2520,20 +2519,22 @@ wheels = [ [[package]] name = "python-box" -version = "7.3.2" +version = "7.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/29/f7/635eed8c500adf26208e86e985bbffb6ff039cd8950e3a4749ceca904218/python_box-7.3.2.tar.gz", hash = "sha256:028b9917129e67f311932d93347b8a4f1b500d7a5a2870ee3c035f4e7b19403b", size = 45771, upload-time = "2025-01-16T19:10:05.221Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/0f/34e7ee0a72f1464b4c7a2e8bafb389f230477256af586bc82bcfad85295a/python_box-7.4.1.tar.gz", hash = "sha256:e412e36c25fca8223560516d53ef6c7993591c3b0ec8bb4ec582bf7defdd79f0", size = 49859, upload-time = "2026-02-21T16:21:16.008Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/3f/133619c00d8a9d4f86efd8626c0e4ec356c8b8dabac66da18dac5cfaf70c/python_box-7.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:32163b1cb151883de0da62b0cd3572610dc72ccf0762f2447baf1d2562e25bea", size = 1834122, upload-time = "2025-01-16T19:10:27.886Z" }, - { url = "https://files.pythonhosted.org/packages/b7/52/51b6081562daa864847692536260200b337ccb4176d1e5f626ae48a7d493/python_box-7.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:064cb59b41e25aaf7dbd39efe53151a5f6797cc1cb3c68610f0f21a9d406d67e", size = 4305556, upload-time = "2025-01-16T19:15:29.286Z" }, - { url = "https://files.pythonhosted.org/packages/d4/e2/6cdc8649381ae14def88c3e2e93d5b8b17a622a95896e0d1c92861270b7d/python_box-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:488f0fba9a6416c3334b602366dcd92825adb0811e07e03753dfcf0ed79cd6f7", size = 1232328, upload-time = "2025-01-16T19:11:37.676Z" }, - { url = "https://files.pythonhosted.org/packages/45/68/0c2f289d8055d3e1b156ff258847f0e8f1010063e284cf5a612f09435575/python_box-7.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:39009a2da5c20133718b24891a206592adbe09169856aedc450ad1600fc2e511", size = 1819681, upload-time = "2025-01-16T19:10:25.187Z" }, - { url = "https://files.pythonhosted.org/packages/ce/5d/76b4d6d0e41edb676a229f032848a1ecea166890fa8d501513ea1a030f4d/python_box-7.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e2a72e2f6fb97c7e472ff3272da207ecc615aa222e52e98352391428527c469", size = 4270424, upload-time = "2025-01-16T19:15:32.376Z" }, - { url = "https://files.pythonhosted.org/packages/e4/6b/32484b2a3cd2fb5e5f56bfb53a4537d93a4d2014ccf7fc0c0017fa6f65e9/python_box-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:9eead914b9fb7d98a1473f5027dcfe27d26b3a10ffa33b9ba22cf948a23cd280", size = 1211252, upload-time = "2025-01-16T19:11:00.248Z" }, - { url = "https://files.pythonhosted.org/packages/2f/39/8bec609e93dbc5e0d3ea26cfb5af3ca78915f7a55ef5414713462fedeb59/python_box-7.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1dfc3b9b073f3d7cad1fa90de98eaaa684a494d0574bbc0666f74fa8307fd6b6", size = 1804675, upload-time = "2025-01-16T19:10:23.281Z" }, - { url = "https://files.pythonhosted.org/packages/88/ae/baf3a8057d8129896a7e02619df43ea0d918fc5b2bb66eb6e2470595fbac/python_box-7.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ca4685a7f764b5a71b6e08535ce2a96b7964bb63d8cb4df10f6bb7147b6c54b", size = 4265645, upload-time = "2025-01-16T19:15:34.087Z" }, - { url = "https://files.pythonhosted.org/packages/43/90/72367e03033c11a5e82676ee389b572bf136647ff4e3081557392b37e1ad/python_box-7.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e143295f74d47a9ab24562ead2375c9be10629599b57f2e86717d3fff60f82a9", size = 1206740, upload-time = "2025-01-16T19:11:30.635Z" }, - { url = "https://files.pythonhosted.org/packages/37/13/8a990c6e2b6cc12700dce16f3cb383324e6d9a30f604eca22a2fdf84c923/python_box-7.3.2-py3-none-any.whl", hash = "sha256:fd7d74d5a848623f93b5221fd9fb00b8c00ff0e130fa87f396277aa188659c92", size = 29479, upload-time = "2025-01-16T19:10:02.749Z" }, + { url = "https://files.pythonhosted.org/packages/f8/a8/c8bcd3ff0905ec549273ea3485e6b9f2039f57baab419123fb18f964f829/python_box-7.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3f76dad8be9d57d65a3edc792b952f7afe3991515aa6eba616cf5efb2fbb2e0c", size = 1870869, upload-time = "2026-02-21T16:21:34.16Z" }, + { url = "https://files.pythonhosted.org/packages/0f/bc/9382766d388e258363a18a094e251d2624e3c524614c733d1afa989d9770/python_box-7.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c66582f41a94d46cb0896d468b0efebf9bc4c3a5634cd15373d871767c2e741d", size = 4494287, upload-time = "2026-02-21T16:26:03.131Z" }, + { url = "https://files.pythonhosted.org/packages/8c/cf/b9d1d4550615f69f6f9c72767f026543442739e5c56adf6f844b50d88251/python_box-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:43c62f66d694eb6410f51eb2eb5726f9b466e6f685e5dc90b5cd11f7b3047362", size = 1321085, upload-time = "2026-02-21T16:22:01.093Z" }, + { url = "https://files.pythonhosted.org/packages/4d/d9/d05f317b38b42253422d8483f5d7dc16d382c99ddc253e426639a0f2f235/python_box-7.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dfb91effff00d9e23486c4f0db3b19e03d602ebb7c9e20fc6a287c704fad2552", size = 1849441, upload-time = "2026-02-21T16:21:37.314Z" }, + { url = "https://files.pythonhosted.org/packages/ba/a3/383eb3d658f36c6e531c8cf1e348ccb4b5031231df4aeb7742bb159a3166/python_box-7.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f977f00e715b030cee6ffef2322ff8ce100ffbf1dbcc4ef91099c75752d5f8", size = 4485153, upload-time = "2026-02-21T16:26:04.507Z" }, + { url = "https://files.pythonhosted.org/packages/65/f9/5de3c18415dd6f5286f00e6539c0ae3cceb1c6aaf28d1d5f17b0b568c97f/python_box-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ca9a18fd15326bc267e9cc7e0e6e3a0cb78d11507940f43f687adf7e156d882", size = 1295520, upload-time = "2026-02-21T16:22:26.192Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e9/48d1b1eb21efc3f82a31b037b6903c9139018f686d96d251faa4cb0d593a/python_box-7.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:85db37b43094bf6c4884b931fb149a7850db5ce331f6e191edf98b453e6cf2d6", size = 1845195, upload-time = "2026-02-21T16:21:46.235Z" }, + { url = "https://files.pythonhosted.org/packages/da/79/48d38c855f277223caf3aa79518476f95abc07f04386940855b7bd3d95f6/python_box-7.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bb204822c7638bd2dbed5c55d6ab264c6903c37d18dee5c45bdbda58b2e1e17a", size = 4468245, upload-time = "2026-02-21T16:26:05.701Z" }, + { url = "https://files.pythonhosted.org/packages/17/1d/7a1e04f37674399e0f3076cfe1fa358f6a51540ae98299a06f2c0424c471/python_box-7.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:615da3fafd41572aec1b905832555c0ea08b6fbc27cc917356e257a9a5721af7", size = 1295564, upload-time = "2026-02-21T16:22:36.547Z" }, + { url = "https://files.pythonhosted.org/packages/94/a2/771b5e526bba2214ac2d30e321209a66680c40788616a45cf01005e95204/python_box-7.4.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:33c6701faa51fd87f0dcc538873c0fad2b3a1cc3750eab85835cd071cadf1948", size = 1875508, upload-time = "2026-02-21T16:21:37.432Z" }, + { url = "https://files.pythonhosted.org/packages/a6/5f/0e7ea7640ba60ff459ce37e340d816ac5e91b7a9a7c3c161f9dabe622be6/python_box-7.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:ae8c540a0457f52350211d24690211251912018e1e0c1857f50792729d6f562c", size = 1314304, upload-time = "2026-02-21T16:22:22.173Z" }, + { url = "https://files.pythonhosted.org/packages/06/a6/5d3f3abf46b37aa44b1f6788d287c8b4f2319b55013191dddf25b9e6d62c/python_box-7.4.1-py3-none-any.whl", hash = "sha256:a3b0d84d003882fb6abe505b1b883b3a5dcbf226b0fe168d24bc5ff75d9826e5", size = 30402, upload-time = "2026-02-21T16:21:14.78Z" }, ] [[package]] @@ -3010,27 +3011,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.1" +version = "0.15.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/04/dc/4e6ac71b511b141cf626357a3946679abeba4cf67bc7cc5a17920f31e10d/ruff-0.15.1.tar.gz", hash = "sha256:c590fe13fb57c97141ae975c03a1aedb3d3156030cabd740d6ff0b0d601e203f", size = 4540855, upload-time = "2026-02-12T23:09:09.998Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/04/eab13a954e763b0606f460443fcbf6bb5a0faf06890ea3754ff16523dce5/ruff-0.15.2.tar.gz", hash = "sha256:14b965afee0969e68bb871eba625343b8673375f457af4abe98553e8bbb98342", size = 4558148, upload-time = "2026-02-19T22:32:20.271Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/bf/e6e4324238c17f9d9120a9d60aa99a7daaa21204c07fcd84e2ef03bb5fd1/ruff-0.15.1-py3-none-linux_armv6l.whl", hash = "sha256:b101ed7cf4615bda6ffe65bdb59f964e9f4a0d3f85cbf0e54f0ab76d7b90228a", size = 10367819, upload-time = "2026-02-12T23:09:03.598Z" }, - { url = "https://files.pythonhosted.org/packages/b3/ea/c8f89d32e7912269d38c58f3649e453ac32c528f93bb7f4219258be2e7ed/ruff-0.15.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:939c995e9277e63ea632cc8d3fae17aa758526f49a9a850d2e7e758bfef46602", size = 10798618, upload-time = "2026-02-12T23:09:22.928Z" }, - { url = "https://files.pythonhosted.org/packages/5e/0f/1d0d88bc862624247d82c20c10d4c0f6bb2f346559d8af281674cf327f15/ruff-0.15.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1d83466455fdefe60b8d9c8df81d3c1bbb2115cede53549d3b522ce2bc703899", size = 10148518, upload-time = "2026-02-12T23:08:58.339Z" }, - { url = "https://files.pythonhosted.org/packages/f5/c8/291c49cefaa4a9248e986256df2ade7add79388fe179e0691be06fae6f37/ruff-0.15.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9457e3c3291024866222b96108ab2d8265b477e5b1534c7ddb1810904858d16", size = 10518811, upload-time = "2026-02-12T23:09:31.865Z" }, - { url = "https://files.pythonhosted.org/packages/c3/1a/f5707440e5ae43ffa5365cac8bbb91e9665f4a883f560893829cf16a606b/ruff-0.15.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:92c92b003e9d4f7fbd33b1867bb15a1b785b1735069108dfc23821ba045b29bc", size = 10196169, upload-time = "2026-02-12T23:09:17.306Z" }, - { url = "https://files.pythonhosted.org/packages/2a/ff/26ddc8c4da04c8fd3ee65a89c9fb99eaa5c30394269d424461467be2271f/ruff-0.15.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fe5c41ab43e3a06778844c586251eb5a510f67125427625f9eb2b9526535779", size = 10990491, upload-time = "2026-02-12T23:09:25.503Z" }, - { url = "https://files.pythonhosted.org/packages/fc/00/50920cb385b89413f7cdb4bb9bc8fc59c1b0f30028d8bccc294189a54955/ruff-0.15.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66a6dd6df4d80dc382c6484f8ce1bcceb55c32e9f27a8b94c32f6c7331bf14fb", size = 11843280, upload-time = "2026-02-12T23:09:19.88Z" }, - { url = "https://files.pythonhosted.org/packages/5d/6d/2f5cad8380caf5632a15460c323ae326f1e1a2b5b90a6ee7519017a017ca/ruff-0.15.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a4a42cbb8af0bda9bcd7606b064d7c0bc311a88d141d02f78920be6acb5aa83", size = 11274336, upload-time = "2026-02-12T23:09:14.907Z" }, - { url = "https://files.pythonhosted.org/packages/a3/1d/5f56cae1d6c40b8a318513599b35ea4b075d7dc1cd1d04449578c29d1d75/ruff-0.15.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ab064052c31dddada35079901592dfba2e05f5b1e43af3954aafcbc1096a5b2", size = 11137288, upload-time = "2026-02-12T23:09:07.475Z" }, - { url = "https://files.pythonhosted.org/packages/cd/20/6f8d7d8f768c93b0382b33b9306b3b999918816da46537d5a61635514635/ruff-0.15.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:5631c940fe9fe91f817a4c2ea4e81f47bee3ca4aa646134a24374f3c19ad9454", size = 11070681, upload-time = "2026-02-12T23:08:55.43Z" }, - { url = "https://files.pythonhosted.org/packages/9a/67/d640ac76069f64cdea59dba02af2e00b1fa30e2103c7f8d049c0cff4cafd/ruff-0.15.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:68138a4ba184b4691ccdc39f7795c66b3c68160c586519e7e8444cf5a53e1b4c", size = 10486401, upload-time = "2026-02-12T23:09:27.927Z" }, - { url = "https://files.pythonhosted.org/packages/65/3d/e1429f64a3ff89297497916b88c32a5cc88eeca7e9c787072d0e7f1d3e1e/ruff-0.15.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:518f9af03bfc33c03bdb4cb63fabc935341bb7f54af500f92ac309ecfbba6330", size = 10197452, upload-time = "2026-02-12T23:09:12.147Z" }, - { url = "https://files.pythonhosted.org/packages/78/83/e2c3bade17dad63bf1e1c2ffaf11490603b760be149e1419b07049b36ef2/ruff-0.15.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:da79f4d6a826caaea95de0237a67e33b81e6ec2e25fc7e1993a4015dffca7c61", size = 10693900, upload-time = "2026-02-12T23:09:34.418Z" }, - { url = "https://files.pythonhosted.org/packages/a1/27/fdc0e11a813e6338e0706e8b39bb7a1d61ea5b36873b351acee7e524a72a/ruff-0.15.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3dd86dccb83cd7d4dcfac303ffc277e6048600dfc22e38158afa208e8bf94a1f", size = 11227302, upload-time = "2026-02-12T23:09:36.536Z" }, - { url = "https://files.pythonhosted.org/packages/f6/58/ac864a75067dcbd3b95be5ab4eb2b601d7fbc3d3d736a27e391a4f92a5c1/ruff-0.15.1-py3-none-win32.whl", hash = "sha256:660975d9cb49b5d5278b12b03bb9951d554543a90b74ed5d366b20e2c57c2098", size = 10462555, upload-time = "2026-02-12T23:09:29.899Z" }, - { url = "https://files.pythonhosted.org/packages/e0/5e/d4ccc8a27ecdb78116feac4935dfc39d1304536f4296168f91ed3ec00cd2/ruff-0.15.1-py3-none-win_amd64.whl", hash = "sha256:c820fef9dd5d4172a6570e5721704a96c6679b80cf7be41659ed439653f62336", size = 11599956, upload-time = "2026-02-12T23:09:01.157Z" }, - { url = "https://files.pythonhosted.org/packages/2a/07/5bda6a85b220c64c65686bc85bd0bbb23b29c62b3a9f9433fa55f17cda93/ruff-0.15.1-py3-none-win_arm64.whl", hash = "sha256:5ff7d5f0f88567850f45081fac8f4ec212be8d0b963e385c3f7d0d2eb4899416", size = 10874604, upload-time = "2026-02-12T23:09:05.515Z" }, + { url = "https://files.pythonhosted.org/packages/2f/70/3a4dc6d09b13cb3e695f28307e5d889b2e1a66b7af9c5e257e796695b0e6/ruff-0.15.2-py3-none-linux_armv6l.whl", hash = "sha256:120691a6fdae2f16d65435648160f5b81a9625288f75544dc40637436b5d3c0d", size = 10430565, upload-time = "2026-02-19T22:32:41.824Z" }, + { url = "https://files.pythonhosted.org/packages/71/0b/bb8457b56185ece1305c666dc895832946d24055be90692381c31d57466d/ruff-0.15.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a89056d831256099658b6bba4037ac6dd06f49d194199215befe2bb10457ea5e", size = 10820354, upload-time = "2026-02-19T22:32:07.366Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c1/e0532d7f9c9e0b14c46f61b14afd563298b8b83f337b6789ddd987e46121/ruff-0.15.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e36dee3a64be0ebd23c86ffa3aa3fd3ac9a712ff295e192243f814a830b6bd87", size = 10170767, upload-time = "2026-02-19T22:32:13.188Z" }, + { url = "https://files.pythonhosted.org/packages/47/e8/da1aa341d3af017a21c7a62fb5ec31d4e7ad0a93ab80e3a508316efbcb23/ruff-0.15.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9fb47b6d9764677f8c0a193c0943ce9a05d6763523f132325af8a858eadc2b9", size = 10529591, upload-time = "2026-02-19T22:32:02.547Z" }, + { url = "https://files.pythonhosted.org/packages/93/74/184fbf38e9f3510231fbc5e437e808f0b48c42d1df9434b208821efcd8d6/ruff-0.15.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f376990f9d0d6442ea9014b19621d8f2aaf2b8e39fdbfc79220b7f0c596c9b80", size = 10260771, upload-time = "2026-02-19T22:32:36.938Z" }, + { url = "https://files.pythonhosted.org/packages/05/ac/605c20b8e059a0bc4b42360414baa4892ff278cec1c91fff4be0dceedefd/ruff-0.15.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dcc987551952d73cbf5c88d9fdee815618d497e4df86cd4c4824cc59d5dd75f", size = 11045791, upload-time = "2026-02-19T22:32:31.642Z" }, + { url = "https://files.pythonhosted.org/packages/fd/52/db6e419908f45a894924d410ac77d64bdd98ff86901d833364251bd08e22/ruff-0.15.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42a47fd785cbe8c01b9ff45031af875d101b040ad8f4de7bbb716487c74c9a77", size = 11879271, upload-time = "2026-02-19T22:32:29.305Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d8/7992b18f2008bdc9231d0f10b16df7dda964dbf639e2b8b4c1b4e91b83af/ruff-0.15.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cbe9f49354866e575b4c6943856989f966421870e85cd2ac94dccb0a9dcb2fea", size = 11303707, upload-time = "2026-02-19T22:32:22.492Z" }, + { url = "https://files.pythonhosted.org/packages/d7/02/849b46184bcfdd4b64cde61752cc9a146c54759ed036edd11857e9b8443b/ruff-0.15.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7a672c82b5f9887576087d97be5ce439f04bbaf548ee987b92d3a7dede41d3a", size = 11149151, upload-time = "2026-02-19T22:32:44.234Z" }, + { url = "https://files.pythonhosted.org/packages/70/04/f5284e388bab60d1d3b99614a5a9aeb03e0f333847e2429bebd2aaa1feec/ruff-0.15.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ecc64f46f7019e2bcc3cdc05d4a7da958b629a5ab7033195e11a438403d956", size = 11091132, upload-time = "2026-02-19T22:32:24.691Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ae/88d844a21110e14d92cf73d57363fab59b727ebeabe78009b9ccb23500af/ruff-0.15.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:8dcf243b15b561c655c1ef2f2b0050e5d50db37fe90115507f6ff37d865dc8b4", size = 10504717, upload-time = "2026-02-19T22:32:26.75Z" }, + { url = "https://files.pythonhosted.org/packages/64/27/867076a6ada7f2b9c8292884ab44d08fd2ba71bd2b5364d4136f3cd537e1/ruff-0.15.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dab6941c862c05739774677c6273166d2510d254dac0695c0e3f5efa1b5585de", size = 10263122, upload-time = "2026-02-19T22:32:10.036Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ef/faf9321d550f8ebf0c6373696e70d1758e20ccdc3951ad7af00c0956be7c/ruff-0.15.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b9164f57fc36058e9a6806eb92af185b0697c9fe4c7c52caa431c6554521e5c", size = 10735295, upload-time = "2026-02-19T22:32:39.227Z" }, + { url = "https://files.pythonhosted.org/packages/2f/55/e8089fec62e050ba84d71b70e7834b97709ca9b7aba10c1a0b196e493f97/ruff-0.15.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:80d24fcae24d42659db7e335b9e1531697a7102c19185b8dc4a028b952865fd8", size = 11241641, upload-time = "2026-02-19T22:32:34.617Z" }, + { url = "https://files.pythonhosted.org/packages/23/01/1c30526460f4d23222d0fabd5888868262fd0e2b71a00570ca26483cd993/ruff-0.15.2-py3-none-win32.whl", hash = "sha256:fd5ff9e5f519a7e1bd99cbe8daa324010a74f5e2ebc97c6242c08f26f3714f6f", size = 10507885, upload-time = "2026-02-19T22:32:15.635Z" }, + { url = "https://files.pythonhosted.org/packages/5c/10/3d18e3bbdf8fc50bbb4ac3cc45970aa5a9753c5cb51bf9ed9a3cd8b79fa3/ruff-0.15.2-py3-none-win_amd64.whl", hash = "sha256:d20014e3dfa400f3ff84830dfb5755ece2de45ab62ecea4af6b7262d0fb4f7c5", size = 11623725, upload-time = "2026-02-19T22:32:04.947Z" }, + { url = "https://files.pythonhosted.org/packages/6d/78/097c0798b1dab9f8affe73da9642bb4500e098cb27fd8dc9724816ac747b/ruff-0.15.2-py3-none-win_arm64.whl", hash = "sha256:cabddc5822acdc8f7b5527b36ceac55cc51eec7b1946e60181de8fe83ca8876e", size = 10941649, upload-time = "2026-02-19T22:32:18.108Z" }, ] [[package]] @@ -3101,73 +3102,73 @@ wheels = [ [[package]] name = "scipy" -version = "1.17.0" +version = "1.17.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/56/3e/9cca699f3486ce6bc12ff46dc2031f1ec8eb9ccc9a320fdaf925f1417426/scipy-1.17.0.tar.gz", hash = "sha256:2591060c8e648d8b96439e111ac41fd8342fdeff1876be2e19dea3fe8930454e", size = 30396830, upload-time = "2026-01-10T21:34:23.009Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/4b/c89c131aa87cad2b77a54eb0fb94d633a842420fa7e919dc2f922037c3d8/scipy-1.17.0-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:2abd71643797bd8a106dff97894ff7869eeeb0af0f7a5ce02e4227c6a2e9d6fd", size = 31381316, upload-time = "2026-01-10T21:24:33.42Z" }, - { url = "https://files.pythonhosted.org/packages/5e/5f/a6b38f79a07d74989224d5f11b55267714707582908a5f1ae854cf9a9b84/scipy-1.17.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:ef28d815f4d2686503e5f4f00edc387ae58dfd7a2f42e348bb53359538f01558", size = 27966760, upload-time = "2026-01-10T21:24:38.911Z" }, - { url = "https://files.pythonhosted.org/packages/c1/20/095ad24e031ee8ed3c5975954d816b8e7e2abd731e04f8be573de8740885/scipy-1.17.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:272a9f16d6bb4667e8b50d25d71eddcc2158a214df1b566319298de0939d2ab7", size = 20138701, upload-time = "2026-01-10T21:24:43.249Z" }, - { url = "https://files.pythonhosted.org/packages/89/11/4aad2b3858d0337756f3323f8960755704e530b27eb2a94386c970c32cbe/scipy-1.17.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:7204fddcbec2fe6598f1c5fdf027e9f259106d05202a959a9f1aecf036adc9f6", size = 22480574, upload-time = "2026-01-10T21:24:47.266Z" }, - { url = "https://files.pythonhosted.org/packages/85/bd/f5af70c28c6da2227e510875cadf64879855193a687fb19951f0f44cfd6b/scipy-1.17.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc02c37a5639ee67d8fb646ffded6d793c06c5622d36b35cfa8fe5ececb8f042", size = 32862414, upload-time = "2026-01-10T21:24:52.566Z" }, - { url = "https://files.pythonhosted.org/packages/ef/df/df1457c4df3826e908879fe3d76bc5b6e60aae45f4ee42539512438cfd5d/scipy-1.17.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dac97a27520d66c12a34fd90a4fe65f43766c18c0d6e1c0a80f114d2260080e4", size = 35112380, upload-time = "2026-01-10T21:24:58.433Z" }, - { url = "https://files.pythonhosted.org/packages/5f/bb/88e2c16bd1dd4de19d80d7c5e238387182993c2fb13b4b8111e3927ad422/scipy-1.17.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ebb7446a39b3ae0fe8f416a9a3fdc6fba3f11c634f680f16a239c5187bc487c0", size = 34922676, upload-time = "2026-01-10T21:25:04.287Z" }, - { url = "https://files.pythonhosted.org/packages/02/ba/5120242cc735f71fc002cff0303d536af4405eb265f7c60742851e7ccfe9/scipy-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:474da16199f6af66601a01546144922ce402cb17362e07d82f5a6cf8f963e449", size = 37507599, upload-time = "2026-01-10T21:25:09.851Z" }, - { url = "https://files.pythonhosted.org/packages/52/c8/08629657ac6c0da198487ce8cd3de78e02cfde42b7f34117d56a3fe249dc/scipy-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:255c0da161bd7b32a6c898e7891509e8a9289f0b1c6c7d96142ee0d2b114c2ea", size = 36380284, upload-time = "2026-01-10T21:25:15.632Z" }, - { url = "https://files.pythonhosted.org/packages/6c/4a/465f96d42c6f33ad324a40049dfd63269891db9324aa66c4a1c108c6f994/scipy-1.17.0-cp311-cp311-win_arm64.whl", hash = "sha256:85b0ac3ad17fa3be50abd7e69d583d98792d7edc08367e01445a1e2076005379", size = 24370427, upload-time = "2026-01-10T21:25:20.514Z" }, - { url = "https://files.pythonhosted.org/packages/0b/11/7241a63e73ba5a516f1930ac8d5b44cbbfabd35ac73a2d08ca206df007c4/scipy-1.17.0-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:0d5018a57c24cb1dd828bcf51d7b10e65986d549f52ef5adb6b4d1ded3e32a57", size = 31364580, upload-time = "2026-01-10T21:25:25.717Z" }, - { url = "https://files.pythonhosted.org/packages/ed/1d/5057f812d4f6adc91a20a2d6f2ebcdb517fdbc87ae3acc5633c9b97c8ba5/scipy-1.17.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:88c22af9e5d5a4f9e027e26772cc7b5922fab8bcc839edb3ae33de404feebd9e", size = 27969012, upload-time = "2026-01-10T21:25:30.921Z" }, - { url = "https://files.pythonhosted.org/packages/e3/21/f6ec556c1e3b6ec4e088da667d9987bb77cc3ab3026511f427dc8451187d/scipy-1.17.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f3cd947f20fe17013d401b64e857c6b2da83cae567adbb75b9dcba865abc66d8", size = 20140691, upload-time = "2026-01-10T21:25:34.802Z" }, - { url = "https://files.pythonhosted.org/packages/7a/fe/5e5ad04784964ba964a96f16c8d4676aa1b51357199014dce58ab7ec5670/scipy-1.17.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e8c0b331c2c1f531eb51f1b4fc9ba709521a712cce58f1aa627bc007421a5306", size = 22463015, upload-time = "2026-01-10T21:25:39.277Z" }, - { url = "https://files.pythonhosted.org/packages/4a/69/7c347e857224fcaf32a34a05183b9d8a7aca25f8f2d10b8a698b8388561a/scipy-1.17.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5194c445d0a1c7a6c1a4a4681b6b7c71baad98ff66d96b949097e7513c9d6742", size = 32724197, upload-time = "2026-01-10T21:25:44.084Z" }, - { url = "https://files.pythonhosted.org/packages/d1/fe/66d73b76d378ba8cc2fe605920c0c75092e3a65ae746e1e767d9d020a75a/scipy-1.17.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9eeb9b5f5997f75507814ed9d298ab23f62cf79f5a3ef90031b1ee2506abdb5b", size = 35009148, upload-time = "2026-01-10T21:25:50.591Z" }, - { url = "https://files.pythonhosted.org/packages/af/07/07dec27d9dc41c18d8c43c69e9e413431d20c53a0339c388bcf72f353c4b/scipy-1.17.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:40052543f7bbe921df4408f46003d6f01c6af109b9e2c8a66dd1cf6cf57f7d5d", size = 34798766, upload-time = "2026-01-10T21:25:59.41Z" }, - { url = "https://files.pythonhosted.org/packages/81/61/0470810c8a093cdacd4ba7504b8a218fd49ca070d79eca23a615f5d9a0b0/scipy-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0cf46c8013fec9d3694dc572f0b54100c28405d55d3e2cb15e2895b25057996e", size = 37405953, upload-time = "2026-01-10T21:26:07.75Z" }, - { url = "https://files.pythonhosted.org/packages/92/ce/672ed546f96d5d41ae78c4b9b02006cedd0b3d6f2bf5bb76ea455c320c28/scipy-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:0937a0b0d8d593a198cededd4c439a0ea216a3f36653901ea1f3e4be949056f8", size = 36328121, upload-time = "2026-01-10T21:26:16.509Z" }, - { url = "https://files.pythonhosted.org/packages/9d/21/38165845392cae67b61843a52c6455d47d0cc2a40dd495c89f4362944654/scipy-1.17.0-cp312-cp312-win_arm64.whl", hash = "sha256:f603d8a5518c7426414d1d8f82e253e454471de682ce5e39c29adb0df1efb86b", size = 24314368, upload-time = "2026-01-10T21:26:23.087Z" }, - { url = "https://files.pythonhosted.org/packages/0c/51/3468fdfd49387ddefee1636f5cf6d03ce603b75205bf439bbf0e62069bfd/scipy-1.17.0-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:65ec32f3d32dfc48c72df4291345dae4f048749bc8d5203ee0a3f347f96c5ce6", size = 31344101, upload-time = "2026-01-10T21:26:30.25Z" }, - { url = "https://files.pythonhosted.org/packages/b2/9a/9406aec58268d437636069419e6977af953d1e246df941d42d3720b7277b/scipy-1.17.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:1f9586a58039d7229ce77b52f8472c972448cded5736eaf102d5658bbac4c269", size = 27950385, upload-time = "2026-01-10T21:26:36.801Z" }, - { url = "https://files.pythonhosted.org/packages/4f/98/e7342709e17afdfd1b26b56ae499ef4939b45a23a00e471dfb5375eea205/scipy-1.17.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:9fad7d3578c877d606b1150135c2639e9de9cecd3705caa37b66862977cc3e72", size = 20122115, upload-time = "2026-01-10T21:26:42.107Z" }, - { url = "https://files.pythonhosted.org/packages/fd/0e/9eeeb5357a64fd157cbe0302c213517c541cc16b8486d82de251f3c68ede/scipy-1.17.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:423ca1f6584fc03936972b5f7c06961670dbba9f234e71676a7c7ccf938a0d61", size = 22442402, upload-time = "2026-01-10T21:26:48.029Z" }, - { url = "https://files.pythonhosted.org/packages/c9/10/be13397a0e434f98e0c79552b2b584ae5bb1c8b2be95db421533bbca5369/scipy-1.17.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe508b5690e9eaaa9467fc047f833af58f1152ae51a0d0aed67aa5801f4dd7d6", size = 32696338, upload-time = "2026-01-10T21:26:55.521Z" }, - { url = "https://files.pythonhosted.org/packages/63/1e/12fbf2a3bb240161651c94bb5cdd0eae5d4e8cc6eaeceb74ab07b12a753d/scipy-1.17.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6680f2dfd4f6182e7d6db161344537da644d1cf85cf293f015c60a17ecf08752", size = 34977201, upload-time = "2026-01-10T21:27:03.501Z" }, - { url = "https://files.pythonhosted.org/packages/19/5b/1a63923e23ccd20bd32156d7dd708af5bbde410daa993aa2500c847ab2d2/scipy-1.17.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eec3842ec9ac9de5917899b277428886042a93db0b227ebbe3a333b64ec7643d", size = 34777384, upload-time = "2026-01-10T21:27:11.423Z" }, - { url = "https://files.pythonhosted.org/packages/39/22/b5da95d74edcf81e540e467202a988c50fef41bd2011f46e05f72ba07df6/scipy-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d7425fcafbc09a03731e1bc05581f5fad988e48c6a861f441b7ab729a49a55ea", size = 37379586, upload-time = "2026-01-10T21:27:20.171Z" }, - { url = "https://files.pythonhosted.org/packages/b9/b6/8ac583d6da79e7b9e520579f03007cb006f063642afd6b2eeb16b890bf93/scipy-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:87b411e42b425b84777718cc41516b8a7e0795abfa8e8e1d573bf0ef014f0812", size = 36287211, upload-time = "2026-01-10T21:28:43.122Z" }, - { url = "https://files.pythonhosted.org/packages/55/fb/7db19e0b3e52f882b420417644ec81dd57eeef1bd1705b6f689d8ff93541/scipy-1.17.0-cp313-cp313-win_arm64.whl", hash = "sha256:357ca001c6e37601066092e7c89cca2f1ce74e2a520ca78d063a6d2201101df2", size = 24312646, upload-time = "2026-01-10T21:28:49.893Z" }, - { url = "https://files.pythonhosted.org/packages/20/b6/7feaa252c21cc7aff335c6c55e1b90ab3e3306da3f048109b8b639b94648/scipy-1.17.0-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:ec0827aa4d36cb79ff1b81de898e948a51ac0b9b1c43e4a372c0508c38c0f9a3", size = 31693194, upload-time = "2026-01-10T21:27:27.454Z" }, - { url = "https://files.pythonhosted.org/packages/76/bb/bbb392005abce039fb7e672cb78ac7d158700e826b0515cab6b5b60c26fb/scipy-1.17.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:819fc26862b4b3c73a60d486dbb919202f3d6d98c87cf20c223511429f2d1a97", size = 28365415, upload-time = "2026-01-10T21:27:34.26Z" }, - { url = "https://files.pythonhosted.org/packages/37/da/9d33196ecc99fba16a409c691ed464a3a283ac454a34a13a3a57c0d66f3a/scipy-1.17.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:363ad4ae2853d88ebcde3ae6ec46ccca903ea9835ee8ba543f12f575e7b07e4e", size = 20537232, upload-time = "2026-01-10T21:27:40.306Z" }, - { url = "https://files.pythonhosted.org/packages/56/9d/f4b184f6ddb28e9a5caea36a6f98e8ecd2a524f9127354087ce780885d83/scipy-1.17.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:979c3a0ff8e5ba254d45d59ebd38cde48fce4f10b5125c680c7a4bfe177aab07", size = 22791051, upload-time = "2026-01-10T21:27:46.539Z" }, - { url = "https://files.pythonhosted.org/packages/9b/9d/025cccdd738a72140efc582b1641d0dd4caf2e86c3fb127568dc80444e6e/scipy-1.17.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:130d12926ae34399d157de777472bf82e9061c60cc081372b3118edacafe1d00", size = 32815098, upload-time = "2026-01-10T21:27:54.389Z" }, - { url = "https://files.pythonhosted.org/packages/48/5f/09b879619f8bca15ce392bfc1894bd9c54377e01d1b3f2f3b595a1b4d945/scipy-1.17.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e886000eb4919eae3a44f035e63f0fd8b651234117e8f6f29bad1cd26e7bc45", size = 35031342, upload-time = "2026-01-10T21:28:03.012Z" }, - { url = "https://files.pythonhosted.org/packages/f2/9a/f0f0a9f0aa079d2f106555b984ff0fbb11a837df280f04f71f056ea9c6e4/scipy-1.17.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:13c4096ac6bc31d706018f06a49abe0485f96499deb82066b94d19b02f664209", size = 34893199, upload-time = "2026-01-10T21:28:10.832Z" }, - { url = "https://files.pythonhosted.org/packages/90/b8/4f0f5cf0c5ea4d7548424e6533e6b17d164f34a6e2fb2e43ffebb6697b06/scipy-1.17.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cacbaddd91fcffde703934897c5cd2c7cb0371fac195d383f4e1f1c5d3f3bd04", size = 37438061, upload-time = "2026-01-10T21:28:19.684Z" }, - { url = "https://files.pythonhosted.org/packages/f9/cc/2bd59140ed3b2fa2882fb15da0a9cb1b5a6443d67cfd0d98d4cec83a57ec/scipy-1.17.0-cp313-cp313t-win_amd64.whl", hash = "sha256:edce1a1cf66298cccdc48a1bdf8fb10a3bf58e8b58d6c3883dd1530e103f87c0", size = 36328593, upload-time = "2026-01-10T21:28:28.007Z" }, - { url = "https://files.pythonhosted.org/packages/13/1b/c87cc44a0d2c7aaf0f003aef2904c3d097b422a96c7e7c07f5efd9073c1b/scipy-1.17.0-cp313-cp313t-win_arm64.whl", hash = "sha256:30509da9dbec1c2ed8f168b8d8aa853bc6723fede1dbc23c7d43a56f5ab72a67", size = 24625083, upload-time = "2026-01-10T21:28:35.188Z" }, - { url = "https://files.pythonhosted.org/packages/1a/2d/51006cd369b8e7879e1c630999a19d1fbf6f8b5ed3e33374f29dc87e53b3/scipy-1.17.0-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:c17514d11b78be8f7e6331b983a65a7f5ca1fd037b95e27b280921fe5606286a", size = 31346803, upload-time = "2026-01-10T21:28:57.24Z" }, - { url = "https://files.pythonhosted.org/packages/d6/2e/2349458c3ce445f53a6c93d4386b1c4c5c0c540917304c01222ff95ff317/scipy-1.17.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:4e00562e519c09da34c31685f6acc3aa384d4d50604db0f245c14e1b4488bfa2", size = 27967182, upload-time = "2026-01-10T21:29:04.107Z" }, - { url = "https://files.pythonhosted.org/packages/5e/7c/df525fbfa77b878d1cfe625249529514dc02f4fd5f45f0f6295676a76528/scipy-1.17.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:f7df7941d71314e60a481e02d5ebcb3f0185b8d799c70d03d8258f6c80f3d467", size = 20139125, upload-time = "2026-01-10T21:29:10.179Z" }, - { url = "https://files.pythonhosted.org/packages/33/11/fcf9d43a7ed1234d31765ec643b0515a85a30b58eddccc5d5a4d12b5f194/scipy-1.17.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:aabf057c632798832f071a8dde013c2e26284043934f53b00489f1773b33527e", size = 22443554, upload-time = "2026-01-10T21:29:15.888Z" }, - { url = "https://files.pythonhosted.org/packages/80/5c/ea5d239cda2dd3d31399424967a24d556cf409fbea7b5b21412b0fd0a44f/scipy-1.17.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a38c3337e00be6fd8a95b4ed66b5d988bac4ec888fd922c2ea9fe5fb1603dd67", size = 32757834, upload-time = "2026-01-10T21:29:23.406Z" }, - { url = "https://files.pythonhosted.org/packages/b8/7e/8c917cc573310e5dc91cbeead76f1b600d3fb17cf0969db02c9cf92e3cfa/scipy-1.17.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00fb5f8ec8398ad90215008d8b6009c9db9fa924fd4c7d6be307c6f945f9cd73", size = 34995775, upload-time = "2026-01-10T21:29:31.915Z" }, - { url = "https://files.pythonhosted.org/packages/c5/43/176c0c3c07b3f7df324e7cdd933d3e2c4898ca202b090bd5ba122f9fe270/scipy-1.17.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f2a4942b0f5f7c23c7cd641a0ca1955e2ae83dedcff537e3a0259096635e186b", size = 34841240, upload-time = "2026-01-10T21:29:39.995Z" }, - { url = "https://files.pythonhosted.org/packages/44/8c/d1f5f4b491160592e7f084d997de53a8e896a3ac01cd07e59f43ca222744/scipy-1.17.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:dbf133ced83889583156566d2bdf7a07ff89228fe0c0cb727f777de92092ec6b", size = 37394463, upload-time = "2026-01-10T21:29:48.723Z" }, - { url = "https://files.pythonhosted.org/packages/9f/ec/42a6657f8d2d087e750e9a5dde0b481fd135657f09eaf1cf5688bb23c338/scipy-1.17.0-cp314-cp314-win_amd64.whl", hash = "sha256:3625c631a7acd7cfd929e4e31d2582cf00f42fcf06011f59281271746d77e061", size = 37053015, upload-time = "2026-01-10T21:30:51.418Z" }, - { url = "https://files.pythonhosted.org/packages/27/58/6b89a6afd132787d89a362d443a7bddd511b8f41336a1ae47f9e4f000dc4/scipy-1.17.0-cp314-cp314-win_arm64.whl", hash = "sha256:9244608d27eafe02b20558523ba57f15c689357c85bdcfe920b1828750aa26eb", size = 24951312, upload-time = "2026-01-10T21:30:56.771Z" }, - { url = "https://files.pythonhosted.org/packages/e9/01/f58916b9d9ae0112b86d7c3b10b9e685625ce6e8248df139d0fcb17f7397/scipy-1.17.0-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:2b531f57e09c946f56ad0b4a3b2abee778789097871fc541e267d2eca081cff1", size = 31706502, upload-time = "2026-01-10T21:29:56.326Z" }, - { url = "https://files.pythonhosted.org/packages/59/8e/2912a87f94a7d1f8b38aabc0faf74b82d3b6c9e22be991c49979f0eceed8/scipy-1.17.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:13e861634a2c480bd237deb69333ac79ea1941b94568d4b0efa5db5e263d4fd1", size = 28380854, upload-time = "2026-01-10T21:30:01.554Z" }, - { url = "https://files.pythonhosted.org/packages/bd/1c/874137a52dddab7d5d595c1887089a2125d27d0601fce8c0026a24a92a0b/scipy-1.17.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:eb2651271135154aa24f6481cbae5cc8af1f0dd46e6533fb7b56aa9727b6a232", size = 20552752, upload-time = "2026-01-10T21:30:05.93Z" }, - { url = "https://files.pythonhosted.org/packages/3f/f0/7518d171cb735f6400f4576cf70f756d5b419a07fe1867da34e2c2c9c11b/scipy-1.17.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:c5e8647f60679790c2f5c76be17e2e9247dc6b98ad0d3b065861e082c56e078d", size = 22803972, upload-time = "2026-01-10T21:30:10.651Z" }, - { url = "https://files.pythonhosted.org/packages/7c/74/3498563a2c619e8a3ebb4d75457486c249b19b5b04a30600dfd9af06bea5/scipy-1.17.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5fb10d17e649e1446410895639f3385fd2bf4c3c7dfc9bea937bddcbc3d7b9ba", size = 32829770, upload-time = "2026-01-10T21:30:16.359Z" }, - { url = "https://files.pythonhosted.org/packages/48/d1/7b50cedd8c6c9d6f706b4b36fa8544d829c712a75e370f763b318e9638c1/scipy-1.17.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8547e7c57f932e7354a2319fab613981cde910631979f74c9b542bb167a8b9db", size = 35051093, upload-time = "2026-01-10T21:30:22.987Z" }, - { url = "https://files.pythonhosted.org/packages/e2/82/a2d684dfddb87ba1b3ea325df7c3293496ee9accb3a19abe9429bce94755/scipy-1.17.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33af70d040e8af9d5e7a38b5ed3b772adddd281e3062ff23fec49e49681c38cf", size = 34909905, upload-time = "2026-01-10T21:30:28.704Z" }, - { url = "https://files.pythonhosted.org/packages/ef/5e/e565bd73991d42023eb82bb99e51c5b3d9e2c588ca9d4b3e2cc1d3ca62a6/scipy-1.17.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb55bb97d00f8b7ab95cb64f873eb0bf54d9446264d9f3609130381233483f", size = 37457743, upload-time = "2026-01-10T21:30:34.819Z" }, - { url = "https://files.pythonhosted.org/packages/58/a8/a66a75c3d8f1fb2b83f66007d6455a06a6f6cf5618c3dc35bc9b69dd096e/scipy-1.17.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1ff269abf702f6c7e67a4b7aad981d42871a11b9dd83c58d2d2ea624efbd1088", size = 37098574, upload-time = "2026-01-10T21:30:40.782Z" }, - { url = "https://files.pythonhosted.org/packages/56/a5/df8f46ef7da168f1bc52cd86e09a9de5c6f19cc1da04454d51b7d4f43408/scipy-1.17.0-cp314-cp314t-win_arm64.whl", hash = "sha256:031121914e295d9791319a1875444d55079885bbae5bdc9c5e0f2ee5f09d34ff", size = 25246266, upload-time = "2026-01-10T21:30:45.923Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:1f95b894f13729334fb990162e911c9e5dc1ab390c58aa6cbecb389c5b5e28ec", size = 31613675, upload-time = "2026-02-23T00:16:00.13Z" }, + { url = "https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:e18f12c6b0bc5a592ed23d3f7b891f68fd7f8241d69b7883769eb5d5dfb52696", size = 28162057, upload-time = "2026-02-23T00:16:09.456Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee", size = 20334032, upload-time = "2026-02-23T00:16:17.358Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:766e0dc5a616d026a3a1cffa379af959671729083882f50307e18175797b3dfd", size = 22709533, upload-time = "2026-02-23T00:16:25.791Z" }, + { url = "https://files.pythonhosted.org/packages/4d/60/8804678875fc59362b0fb759ab3ecce1f09c10a735680318ac30da8cd76b/scipy-1.17.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:744b2bf3640d907b79f3fd7874efe432d1cf171ee721243e350f55234b4cec4c", size = 33062057, upload-time = "2026-02-23T00:16:36.931Z" }, + { url = "https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43af8d1f3bea642559019edfe64e9b11192a8978efbd1539d7bc2aaa23d92de4", size = 35349300, upload-time = "2026-02-23T00:16:49.108Z" }, + { url = "https://files.pythonhosted.org/packages/b4/3d/7ccbbdcbb54c8fdc20d3b6930137c782a163fa626f0aef920349873421ba/scipy-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd96a1898c0a47be4520327e01f874acfd61fb48a9420f8aa9f6483412ffa444", size = 35127333, upload-time = "2026-02-23T00:17:01.293Z" }, + { url = "https://files.pythonhosted.org/packages/e8/19/f926cb11c42b15ba08e3a71e376d816ac08614f769b4f47e06c3580c836a/scipy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4eb6c25dd62ee8d5edf68a8e1c171dd71c292fdae95d8aeb3dd7d7de4c364082", size = 37741314, upload-time = "2026-02-23T00:17:12.576Z" }, + { url = "https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff", size = 36607512, upload-time = "2026-02-23T00:17:23.424Z" }, + { url = "https://files.pythonhosted.org/packages/68/7f/bdd79ceaad24b671543ffe0ef61ed8e659440eb683b66f033454dcee90eb/scipy-1.17.1-cp311-cp311-win_arm64.whl", hash = "sha256:9ecb4efb1cd6e8c4afea0daa91a87fbddbce1b99d2895d151596716c0b2e859d", size = 24599248, upload-time = "2026-02-23T00:17:34.561Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, + { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, + { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, + { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, + { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, + { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z" }, + { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z" }, + { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z" }, + { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z" }, + { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z" }, + { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z" }, + { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z" }, + { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z" }, + { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z" }, + { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z" }, + { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510, upload-time = "2026-02-23T00:21:01.015Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131, upload-time = "2026-02-23T00:21:05.888Z" }, + { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032, upload-time = "2026-02-23T00:21:09.904Z" }, + { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766, upload-time = "2026-02-23T00:21:14.313Z" }, + { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007, upload-time = "2026-02-23T00:21:19.663Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333, upload-time = "2026-02-23T00:21:25.278Z" }, + { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066, upload-time = "2026-02-23T00:21:31.358Z" }, + { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763, upload-time = "2026-02-23T00:21:37.247Z" }, + { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984, upload-time = "2026-02-23T00:22:35.023Z" }, + { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877, upload-time = "2026-02-23T00:22:39.798Z" }, + { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750, upload-time = "2026-02-23T00:21:42.289Z" }, + { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858, upload-time = "2026-02-23T00:21:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723, upload-time = "2026-02-23T00:21:52.039Z" }, + { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098, upload-time = "2026-02-23T00:21:56.185Z" }, + { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397, upload-time = "2026-02-23T00:22:01.404Z" }, + { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163, upload-time = "2026-02-23T00:22:07.024Z" }, + { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291, upload-time = "2026-02-23T00:22:12.585Z" }, + { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317, upload-time = "2026-02-23T00:22:18.513Z" }, + { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327, upload-time = "2026-02-23T00:22:24.442Z" }, + { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, ] [[package]] @@ -3327,14 +3328,14 @@ wheels = [ [[package]] name = "tifffile" -version = "2026.2.15" +version = "2026.2.20" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/79/e6/0d04362a57f4c6d59a3392e3c2008d3ba3254e0a9684c683755689003950/tifffile-2026.2.15.tar.gz", hash = "sha256:28fe145c615fe3d33d40c2d4c9cc848f7631fd30af852583c4186069458895b2", size = 375461, upload-time = "2026-02-15T20:16:45.991Z" } +sdist = { url = "https://files.pythonhosted.org/packages/90/80/0ddd8dc74c22e1e5efcfb152303b025f8f4a5010ae9936f1e57f7d7f9256/tifffile-2026.2.20.tar.gz", hash = "sha256:b98a7fc6ea4fa0e9919734857eebc6e2cb2c3a95468a930d4a948a9a49646ab7", size = 377196, upload-time = "2026-02-20T20:09:34.608Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/ac/d0d24cbf1ca2a0220b2797dd9b4e528cda201259d3baa28b40aa53287d4e/tifffile-2026.2.15-py3-none-any.whl", hash = "sha256:d9b427d269a708c58400e8ce5a702b26b2502087537beb88b8e29ba7ba825a90", size = 233248, upload-time = "2026-02-15T20:16:44.256Z" }, + { url = "https://files.pythonhosted.org/packages/86/07/0cd5cad2fdb7d32515561bc26da041654f3b3c0abc299f4730f30b89271d/tifffile-2026.2.20-py3-none-any.whl", hash = "sha256:a83e0e991647e39d5912369998ef02d858f89effe30064403a1a123b5daef8fb", size = 234528, upload-time = "2026-02-20T20:09:33.278Z" }, ] [[package]] @@ -3623,16 +3624,16 @@ wheels = [ [[package]] name = "virtualenv" -version = "20.36.1" +version = "20.38.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/aa/a3/4d310fa5f00863544e1d0f4de93bddec248499ccf97d4791bc3122c9d4f3/virtualenv-20.36.1.tar.gz", hash = "sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba", size = 6032239, upload-time = "2026-01-09T18:21:01.296Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d2/03/a94d404ca09a89a7301a7008467aed525d4cdeb9186d262154dd23208709/virtualenv-20.38.0.tar.gz", hash = "sha256:94f39b1abaea5185bf7ea5a46702b56f1d0c9aa2f41a6c2b8b0af4ddc74c10a7", size = 5864558, upload-time = "2026-02-19T07:48:02.385Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/2a/dc2228b2888f51192c7dc766106cd475f1b768c10caaf9727659726f7391/virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f", size = 6008258, upload-time = "2026-01-09T18:20:59.425Z" }, + { url = "https://files.pythonhosted.org/packages/42/d7/394801755d4c8684b655d35c665aea7836ec68320304f62ab3c94395b442/virtualenv-20.38.0-py3-none-any.whl", hash = "sha256:d6e78e5889de3a4742df2d3d44e779366325a90cf356f15621fddace82431794", size = 5837778, upload-time = "2026-02-19T07:47:59.778Z" }, ] [[package]] @@ -3673,14 +3674,14 @@ wheels = [ [[package]] name = "werkzeug" -version = "3.1.5" +version = "3.1.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5a/70/1469ef1d3542ae7c2c7b72bd5e3a4e6ee69d7978fa8a3af05a38eca5becf/werkzeug-3.1.5.tar.gz", hash = "sha256:6a548b0e88955dd07ccb25539d7d0cc97417ee9e179677d22c7041c8f078ce67", size = 864754, upload-time = "2026-01-08T17:49:23.247Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/f1/ee81806690a87dab5f5653c1f146c92bc066d7f4cebc603ef88eb9e13957/werkzeug-3.1.6.tar.gz", hash = "sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25", size = 864736, upload-time = "2026-02-19T15:17:18.884Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/e4/8d97cca767bcc1be76d16fb76951608305561c6e056811587f36cb1316a8/werkzeug-3.1.5-py3-none-any.whl", hash = "sha256:5111e36e91086ece91f93268bb39b4a35c1e6f1feac762c9c822ded0a4e322dc", size = 225025, upload-time = "2026-01-08T17:49:21.859Z" }, + { url = "https://files.pythonhosted.org/packages/4d/ec/d58832f89ede95652fd01f4f24236af7d32b70cab2196dfcc2d2fd13c5c2/werkzeug-3.1.6-py3-none-any.whl", hash = "sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131", size = 225166, upload-time = "2026-02-19T15:17:17.475Z" }, ] [[package]] From c4c6e8a65f2c318ae07e1f68a564b20c08c647a2 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Feb 2026 13:15:18 -0800 Subject: [PATCH 078/335] Removed profiling stuff --- src/quantem/tomography/tomography.py | 336 +++++++++++---------------- 1 file changed, 142 insertions(+), 194 deletions(-) diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index ff8ab977..cb967104 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -7,7 +7,6 @@ from quantem.core.io.serialize import load as autoserialize_load from quantem.core.ml.ddp import DDPMixin -from quantem.core.ml.profiling import nvtx_range from quantem.tomography.dataset_models import DatasetModelType from quantem.tomography.logger_tomography import LoggerTomography from quantem.tomography.object_models import ObjectModelType @@ -80,43 +79,38 @@ def reconstruct( new_scheduler = reset if optimizer_params is not None: - with nvtx_range(profiling_mode, "Setting Optimizer Params"): - self.optimizer_params = optimizer_params - self.set_optimizers() + self.optimizer_params = optimizer_params + self.set_optimizers() new_scheduler = True if scheduler_params is not None: - with nvtx_range(profiling_mode, "Setting Scheduler Params"): - self.scheduler_params = scheduler_params + self.scheduler_params = scheduler_params new_scheduler = True if constraints is not None: - with nvtx_range(profiling_mode, "Setting Constraints"): - self.obj_model.constraints = constraints + self.obj_model.constraints = constraints if new_scheduler: - with nvtx_range(profiling_mode, "Setting Schedulers"): - self.set_schedulers(scheduler_params, num_iter=num_iter) + self.set_schedulers(scheduler_params, num_iter=num_iter) # Setting up DDP if not hasattr(self, "dataloader") or reset_dset is not None: - with nvtx_range(profiling_mode, "Setting Dataloader"): - if reset_dset is not None: - print("Resetting Dataloader") - print("Putting in params from previous dataset.") - - self.dset = reset_dset - self.dset.to(self.device) - self.optimizer_params = optimizer_params - self.set_optimizers() - self.dataloader, self.sampler, self.val_dataloader, self.val_sampler = ( - self.setup_dataloader( - self.dset, - batch_size, - num_workers=num_workers, - val_fraction=val_fraction, - ) + if reset_dset is not None: + print("Resetting Dataloader") + print("Putting in params from previous dataset.") + + self.dset = reset_dset + self.dset.to(self.device) + self.optimizer_params = optimizer_params + self.set_optimizers() + self.dataloader, self.sampler, self.val_dataloader, self.val_sampler = ( + self.setup_dataloader( + self.dset, + batch_size, + num_workers=num_workers, + val_fraction=val_fraction, ) + ) N = max(self.obj_model.shape) if num_samples_per_ray is None: @@ -129,196 +123,150 @@ def reconstruct( print(f"N: {N}, num_samples_per_ray: {num_samples_per_ray}") for a0 in range(num_iter): - with nvtx_range(profiling_mode, f"Epoch {a0}"): - consistency_loss = 0.0 - total_loss = 0.0 - epoch_soft_constraint_loss = 0.0 - self.obj_model.model.train() - self.dset.train() - # self._reset_iter_constraints() - - if self.sampler is not None: - self.sampler.set_epoch(a0) - - if isinstance(num_samples_per_ray, list): - curr_num_samples_per_ray = num_samples_per_ray[a0][1] - else: - curr_num_samples_per_ray = num_samples_per_ray + consistency_loss = 0.0 + total_loss = 0.0 + epoch_soft_constraint_loss = 0.0 + self.obj_model.model.train() + self.dset.train() + # self._reset_iter_constraints() + + if self.sampler is not None: + self.sampler.set_epoch(a0) + + if isinstance(num_samples_per_ray, list): + curr_num_samples_per_ray = num_samples_per_ray[a0][1] + else: + curr_num_samples_per_ray = num_samples_per_ray - if self.global_rank == 0: - print(f"curr_num_samples_per_ray: {curr_num_samples_per_ray}") - for batch_idx, batch in enumerate(self.dataloader): - with nvtx_range(profiling_mode, f"batch_{batch_idx}"): - self.zero_grad_all() + if self.global_rank == 0: + print(f"curr_num_samples_per_ray: {curr_num_samples_per_ray}") + for batch_idx, batch in enumerate(self.dataloader): + self.zero_grad_all() + with torch.autocast( + device_type=self.device.type, + dtype=torch.bfloat16, + enabled=True, + ): + all_coords = self.dset.get_coords(batch, N, curr_num_samples_per_ray) + + all_densities = self.obj_model.forward(all_coords) + + integrated_densities = self.dset.integrate_rays( + all_densities, + curr_num_samples_per_ray, + len(batch["target_value"]), + ) + + pred = integrated_densities.float() + + target = batch["target_value"].to(self.device, non_blocking=True).float() + + batch_consistency_loss = torch.nn.functional.mse_loss(pred, target) + + soft_constraints_loss = self.obj_model.apply_soft_constraints(all_coords) + + epoch_soft_constraint_loss += soft_constraints_loss.detach() + + batch_loss = batch_consistency_loss.float() + soft_constraints_loss.detach() + + batch_loss.backward() + # Clip gradients + torch.nn.utils.clip_grad_norm_(self.obj_model.model.parameters(), max_norm=1.0) + self.step_optimizers() + total_loss += batch_loss.detach() + consistency_loss += batch_consistency_loss.detach() + + self.step_schedulers(loss=total_loss) + # TODO: Maybe reorganize the losses so that the order makes sense lol. + + total_loss = total_loss.item() / len(self.dataloader) + consistency_loss = consistency_loss.item() / len(self.dataloader) + epoch_soft_constraint_loss = epoch_soft_constraint_loss.item() / len(self.dataloader) + + if self.val_dataloader is not None: + print("Validating...") + self.obj_model.model.eval() + self.dset.eval() + with torch.no_grad(): + val_loss = 0.0 + + for batch in self.val_dataloader: with torch.autocast( device_type=self.device.type, dtype=torch.bfloat16, enabled=True, ): - with nvtx_range(profiling_mode, "Getting Coords"): - all_coords = self.dset.get_coords( - batch, N, curr_num_samples_per_ray - ) - with nvtx_range(profiling_mode, "Forwarding"): - all_densities = self.obj_model.forward(all_coords) - - with nvtx_range(profiling_mode, "Integrating"): - integrated_densities = self.dset.integrate_rays( - all_densities, - curr_num_samples_per_ray, - len(batch["target_value"]), - ) - - pred = integrated_densities.float() - - with nvtx_range(profiling_mode, "Getting Target"): - target = ( - batch["target_value"].to(self.device, non_blocking=True).float() - ) + all_coords = self.dset.get_coords(batch, N, curr_num_samples_per_ray) - with nvtx_range(profiling_mode, "Calculating Loss"): - batch_consistency_loss = torch.nn.functional.mse_loss(pred, target) + all_densities = self.obj_model.forward(all_coords) - with nvtx_range(profiling_mode, "Applying Soft Constraints"): - soft_constraints_loss = self.obj_model.apply_soft_constraints( - all_coords + integrated_densities = self.dset.integrate_rays( + all_densities, + curr_num_samples_per_ray, + len(batch["target_value"]), ) - with nvtx_range( - profiling_mode, "Adding soft constraint loss to epoch loss" - ): - epoch_soft_constraint_loss += soft_constraints_loss.detach() - - with nvtx_range(profiling_mode, "Calculating Batch Loss"): - batch_loss = ( - batch_consistency_loss.float() + soft_constraints_loss.detach() + target = ( + batch["target_value"].to(self.device, non_blocking=True).float() ) - with nvtx_range(profiling_mode, "Backwarding"): - batch_loss.backward() - with nvtx_range(profiling_mode, "Clipping Gradients"): - # Clip gradients - torch.nn.utils.clip_grad_norm_( - self.obj_model.model.parameters(), max_norm=1.0 + batch_val_loss = torch.nn.functional.mse_loss( + integrated_densities, target ) - with nvtx_range(profiling_mode, "Stepping Optimizers"): - self.step_optimizers() - with nvtx_range(profiling_mode, "Adding batch loss to total loss"): - total_loss += batch_loss.detach() - with nvtx_range( - profiling_mode, "Adding batch consistency loss to consistency loss" - ): - consistency_loss += batch_consistency_loss.detach() - with nvtx_range(profiling_mode, "Stepping Schedulers"): - self.step_schedulers(loss=total_loss) - # TODO: Maybe reorganize the losses so that the order makes sense lol. + val_loss += batch_val_loss.detach() + soft_constraints_loss.detach() - total_loss = total_loss.item() / len(self.dataloader) - consistency_loss = consistency_loss.item() / len(self.dataloader) - epoch_soft_constraint_loss = epoch_soft_constraint_loss.item() / len( - self.dataloader - ) + avg_val_loss = val_loss.item() / len(self.val_dataloader) - if self.val_dataloader is not None: - print("Validating...") - self.obj_model.model.eval() - self.dset.eval() - with torch.no_grad(): - val_loss = 0.0 - - for batch in self.val_dataloader: - with torch.autocast( - device_type=self.device.type, - dtype=torch.bfloat16, - enabled=True, - ): - with nvtx_range(profiling_mode, "Getting Coords"): - all_coords = self.dset.get_coords( - batch, N, curr_num_samples_per_ray - ) - - with nvtx_range(profiling_mode, "Forwarding"): - all_densities = self.obj_model.forward(all_coords) - - with nvtx_range(profiling_mode, "Integrating"): - integrated_densities = self.dset.integrate_rays( - all_densities, - curr_num_samples_per_ray, - len(batch["target_value"]), - ) - - with nvtx_range(profiling_mode, "Getting Target"): - target = ( - batch["target_value"] - .to(self.device, non_blocking=True) - .float() - ) - - with nvtx_range(profiling_mode, "Calculating Loss"): - batch_val_loss = torch.nn.functional.mse_loss( - integrated_densities, target - ) - - with nvtx_range(profiling_mode, "Adding batch loss to total loss"): - val_loss += ( - batch_val_loss.detach() + soft_constraints_loss.detach() - ) - - avg_val_loss = val_loss.item() / len(self.val_dataloader) - - metrics = torch.tensor( - [total_loss, consistency_loss, epoch_soft_constraint_loss], device=self.device - ) + metrics = torch.tensor( + [total_loss, consistency_loss, epoch_soft_constraint_loss], device=self.device + ) + + if self.world_size > 1: + dist.all_reduce(metrics, dist.ReduceOp.AVG) - if self.world_size > 1: - dist.all_reduce(metrics, dist.ReduceOp.AVG) + total_loss, consistency_loss, epoch_soft_constraint_loss = metrics.tolist() - total_loss, consistency_loss, epoch_soft_constraint_loss = metrics.tolist() + if self.global_rank == 0: + print(f"Total Loss: {total_loss:.4f}, Consistency Loss: {consistency_loss:.4f}") + + if self.val_dataloader: + print(f"Validation loss: {avg_val_loss:4f}") + + self._epoch_losses.append(total_loss) + self._consistency_losses.append(consistency_loss) + self.obj_model._soft_constraint_losses.append(epoch_soft_constraint_loss) + if self.val_dataloader is not None: + self._val_losses.append(avg_val_loss) + + if self.logger is not None: + if ( + self.logger.log_images_every > 0 + and self.num_epochs % self.logger.log_images_every == 0 + ): + print("Creating volume...") + pred_full = self.obj_model.create_volume(return_vol=True) + + if self.global_rank == 0: + print("Logging images...") + self.logger.log_iter_images( + pred_volume=pred_full, + dataset_model=self.dset, + iter=self.num_epochs, + ) if self.global_rank == 0: - print( - f"Total Loss: {total_loss:.4f}, Consistency Loss: {consistency_loss:.4f}" + self.logger.log_iter( + object_model=self.obj_model, + iter=self.num_epochs, + consistency_loss=consistency_loss, + total_loss=total_loss, + learning_rates=self.get_current_lrs(), + num_samples_per_ray=curr_num_samples_per_ray, + val_loss=avg_val_loss if self.val_dataloader is not None else None, ) - if self.val_dataloader: - print(f"Validation loss: {avg_val_loss:4f}") - - self._epoch_losses.append(total_loss) - self._consistency_losses.append(consistency_loss) - self.obj_model._soft_constraint_losses.append(epoch_soft_constraint_loss) - if self.val_dataloader is not None: - self._val_losses.append(avg_val_loss) - - with nvtx_range(profiling_mode, "Logging"): - if self.logger is not None: - if ( - self.logger.log_images_every > 0 - and self.num_epochs % self.logger.log_images_every == 0 - ): - print("Creating volume...") - pred_full = self.obj_model.create_volume(return_vol=True) - - if self.global_rank == 0: - print("Logging images...") - self.logger.log_iter_images( - pred_volume=pred_full, - dataset_model=self.dset, - iter=self.num_epochs, - ) - - if self.global_rank == 0: - self.logger.log_iter( - object_model=self.obj_model, - iter=self.num_epochs, - consistency_loss=consistency_loss, - total_loss=total_loss, - learning_rates=self.get_current_lrs(), - num_samples_per_ray=curr_num_samples_per_ray, - val_loss=avg_val_loss if self.val_dataloader is not None else None, - ) - - self.logger.flush() + self.logger.flush() # --- Helper Functions --- From 162485fa55b1c5c6c138c03ec85e3898b57e6eb7 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Feb 2026 13:28:51 -0800 Subject: [PATCH 079/335] Merged object_models.py from eds_tomography, this allows for multimodal object INRs, adding a channel dimension at the end. --- src/quantem/tomography/object_models.py | 68 ++++++++++++++++--------- src/quantem/tomography/tomography.py | 2 +- 2 files changed, 44 insertions(+), 26 deletions(-) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index c70c9c3f..a553eaa2 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -321,23 +321,25 @@ def apply_soft_constraints( ) -> torch.Tensor: soft_loss = torch.tensor(0.0, device=coords.device) if self.constraints.tv_vol > 0: - num_tv_samples = min(10000, coords.shape[0]) + num_tv_samples = min(10_000, coords.shape[0]) tv_indices = torch.randperm(coords.shape[0], device=coords.device)[:num_tv_samples] tv_coords = coords[tv_indices].detach().requires_grad_(True) - tv_densities_recomputed = self.model(tv_coords) - if tv_densities_recomputed.dim() > 1: - tv_densities_recomputed = tv_densities_recomputed.squeeze(-1) + # Ensure shape is [num_samples, num_channels] + if tv_densities_recomputed.dim() == 1: + tv_densities_recomputed = tv_densities_recomputed.unsqueeze(-1) + # Compute gradients for each channel grad_outputs = torch.autograd.grad( outputs=tv_densities_recomputed, inputs=tv_coords, grad_outputs=torch.ones_like(tv_densities_recomputed), create_graph=True, - )[0] + )[0] # Shape: [num_samples, coord_dim] - grad_norm = torch.norm(grad_outputs, dim=1) + # Compute TV loss - gradient magnitude per sample + grad_norm = torch.norm(grad_outputs, dim=1) # Shape: [num_samples] soft_loss += self.constraints.tv_vol * grad_norm.mean() return soft_loss @@ -418,11 +420,13 @@ def forward(self, coords: torch.Tensor) -> torch.Tensor: if all_densities.dim() > 1: all_densities = all_densities.squeeze(-1) - valid_mask = ( (coords[:, 0] >= -1) & (coords[:, 0] <= 1) & (coords[:, 1] >= -1) & (coords[:, 1] <= 1) ).float() + if all_densities.dim() > 1: + valid_mask = valid_mask.unsqueeze(-1) + # Multi-dimensional mask all_densities = all_densities * valid_mask all_densities = self.apply_hard_constraints(all_densities) @@ -519,10 +523,7 @@ def _pretrain( ) self._pretrain_lrs.append(optimizer.param_groups[0]["lr"]) - def create_volume( - self, - return_vol: bool = False, - ): + def create_volume(self, return_vol: bool = False): N = max(self._shape) with torch.no_grad(): coords_1d = torch.linspace(-1, 1, N) @@ -535,30 +536,40 @@ def create_volume( total_samples = N**3 samples_per_gpu = total_samples // self.world_size remainder = total_samples % self.world_size + if self.global_rank < remainder: start_idx = self.global_rank * (samples_per_gpu + 1) end_idx = start_idx + samples_per_gpu + 1 else: start_idx = self.global_rank * samples_per_gpu + remainder end_idx = start_idx + samples_per_gpu + inputs_subset = inputs[start_idx:end_idx] num_samples = inputs_subset.shape[0] + outputs_list = [] for batch_start in range(0, num_samples, inference_batch_size): batch_end = min(batch_start + inference_batch_size, num_samples) batch_coords = inputs_subset[batch_start:batch_end].to( self.device, non_blocking=True ) - batch_outputs = model(batch_coords) + + batch_outputs = model(batch_coords) # (B, C) or (B,) etc. batch_outputs = self.apply_hard_constraints(batch_outputs) - if batch_outputs.dim() > 1: - batch_outputs = batch_outputs.squeeze(-1) + + # Ensure shape is (B, C) + if batch_outputs.dim() == 1: + batch_outputs = batch_outputs.unsqueeze(-1) # (B, 1) + outputs_list.append(batch_outputs.cpu()) - outputs = torch.cat(outputs_list, dim=0) + outputs = torch.cat(outputs_list, dim=0) # (local_B, C) + C = outputs.shape[-1] # e.g. 5 if self.world_size > 1: - output_size = torch.tensor(outputs.shape[0], device=self.device, dtype=torch.long) + # gather variable-sized first dimension (local_B) while keeping channels + local_B = outputs.shape[0] + output_size = torch.tensor(local_B, device=self.device, dtype=torch.long) all_sizes = [ torch.zeros(1, device=self.device, dtype=torch.long) for _ in range(self.world_size) @@ -566,26 +577,33 @@ def create_volume( dist.all_gather(all_sizes, output_size) max_size = max(size.item() for size in all_sizes) - if outputs.shape[0] < max_size: - padding = torch.zeros( - max_size - outputs.shape[0], device=outputs.device, dtype=outputs.dtype + outputs_dev = outputs.to(self.device) # (local_B, C) + if local_B < max_size: + pad = torch.zeros( + (max_size - local_B, C), + device=self.device, + dtype=outputs_dev.dtype, ) - outputs_padded = torch.cat([outputs, padding], dim=0).to(self.device) + outputs_padded = torch.cat([outputs_dev, pad], dim=0) # (max_size, C) else: - outputs_padded = outputs.to(self.device) + outputs_padded = outputs_dev gathered_outputs = [ - torch.empty(max_size, device=self.device, dtype=outputs.dtype) + torch.empty((max_size, C), device=self.device, dtype=outputs_dev.dtype) for _ in range(self.world_size) ] dist.all_gather(gathered_outputs, outputs_padded.contiguous()) + trimmed_outputs = [] for rank, size in enumerate(all_sizes): - trimmed_outputs.append(gathered_outputs[rank][: size.item()]) + trimmed_outputs.append(gathered_outputs[rank][: size.item(), :]) - pred_full = torch.cat(trimmed_outputs, dim=0).reshape(N, N, N).float() + pred_full = torch.cat(trimmed_outputs, dim=0).reshape(N, N, N, C).float() else: - pred_full = outputs.reshape(N, N, N).float() + pred_full = outputs.reshape(N, N, N, C).float() + + # If you prefer channels-first volumes, uncomment: + # pred_full = pred_full.permute(3, 0, 1, 2).contiguous() # (C, N, N, N) if return_vol: return pred_full.detach().cpu() diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index cb967104..de91107e 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -167,7 +167,7 @@ def reconstruct( epoch_soft_constraint_loss += soft_constraints_loss.detach() - batch_loss = batch_consistency_loss.float() + soft_constraints_loss.detach() + batch_loss = batch_consistency_loss.float() + soft_constraints_loss.float() batch_loss.backward() # Clip gradients From 75721e183afa8e1ecceff9cfef7cae710c0151fa Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Feb 2026 13:55:42 -0800 Subject: [PATCH 080/335] Object creation - channels on the last axis. --- src/quantem/tomography/object_models.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index a553eaa2..90d72ca1 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -598,12 +598,9 @@ def create_volume(self, return_vol: bool = False): for rank, size in enumerate(all_sizes): trimmed_outputs.append(gathered_outputs[rank][: size.item(), :]) - pred_full = torch.cat(trimmed_outputs, dim=0).reshape(N, N, N, C).float() + pred_full = torch.cat(trimmed_outputs, dim=0).reshape(C, N, N, N).float() else: - pred_full = outputs.reshape(N, N, N, C).float() - - # If you prefer channels-first volumes, uncomment: - # pred_full = pred_full.permute(3, 0, 1, 2).contiguous() # (C, N, N, N) + pred_full = outputs.reshape(C, N, N, N).float() if return_vol: return pred_full.detach().cpu() From 46dda7c000f6d873ff5da8bab01d101ecb11fc98 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Feb 2026 16:26:21 -0800 Subject: [PATCH 081/335] constraints.py; Fixed type hinting and added more description to the docstring --- src/quantem/core/ml/constraints.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/quantem/core/ml/constraints.py b/src/quantem/core/ml/constraints.py index 5b19293f..cce43659 100644 --- a/src/quantem/core/ml/constraints.py +++ b/src/quantem/core/ml/constraints.py @@ -5,12 +5,13 @@ import numpy as np import torch +from numpy.typing import NDArray @dataclass(slots=True) class Constraints(ABC): """ - Needs to be implemented in all object models that inherit from BaseConstraints. + Any model that inherits from BaseConstraints will contain a Constraints instance that contains soft and hard constraints. """ soft_constraint_keys = [] @@ -34,9 +35,9 @@ def __str__(self) -> str: soft = "\n".join(f"{key}: {getattr(self, key)}" for key in self.soft_constraint_keys) # Fix: Move the replace operations outside the f-string or assign to variables - hard_indented = hard.replace('\n', '\n ') - soft_indented = soft.replace('\n', '\n ') - + hard_indented = hard.replace("\n", "\n ") + soft_indented = soft.replace("\n", "\n ") + return ( "Constraints:\n" " Hard constraints:\n" @@ -60,13 +61,13 @@ def __init__(self, *args, **kwargs): self.constraints = self.DEFAULT_CONSTRAINTS.copy() @property - def soft_constraint_losses(self) -> list[float]: - return np.array(self._soft_constraint_losses) + def soft_constraint_losses(self) -> NDArray[np.float32]: + return np.array(self._soft_constraint_losses, dtype=np.float32) @property def constraints(self) -> Constraints: """ - Constraints for the object model. + Constraints for the model. """ return self._constraints @@ -87,13 +88,13 @@ def constraints(self, constraints: Constraints | dict[str, Any]): @abstractmethod def apply_hard_constraints(self, *args, **kwargs) -> torch.Tensor: """ - Apply hard constraints to the object model. + Apply hard constraints to the model. """ raise NotImplementedError @abstractmethod def apply_soft_constraints(self, *args, **kwargs) -> torch.Tensor: """ - Apply soft constraints to the object model. + Apply soft constraints to the model. """ raise NotImplementedError From 5c617d3521bb55c70dc788884461c30f636b7033 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Feb 2026 16:54:29 -0800 Subject: [PATCH 082/335] Cleaned up DDP module; offloaded loading of weights to the object_models.py, renamed to distribute_model --- src/quantem/core/ml/ddp.py | 9 ++------- src/quantem/tomography/object_models.py | 11 ++++++----- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/quantem/core/ml/ddp.py b/src/quantem/core/ml/ddp.py index 114b06b9..52bed46a 100644 --- a/src/quantem/core/ml/ddp.py +++ b/src/quantem/core/ml/ddp.py @@ -18,7 +18,7 @@ def __init__( ): self.setup_distributed() - def setup_distributed(self, device: str | None = None): + def setup_distributed(self, device: str | torch.device | None = None): """ Initializes parameters depending if multiple-GPU training, single-GPU training, or CPU training. """ @@ -134,21 +134,16 @@ def setup_dataloader( return train_dataloader, train_sampler, val_dataloader, val_sampler - def build_model( + def distribute_model( self, model: nn.Module, - pretrained_weights: dict[str, torch.Tensor] | None = None, ) -> nn.Module | nn.parallel.DistributedDataParallel: """ Wraps the model with DistributedDataParallel if mulitple GPUs are available. Returns the model. """ - print("Building Model on device: ", self.device) model = model.to(self.device) - if pretrained_weights is not None: - model.load_state_dict(pretrained_weights.copy()) - if self.world_size > 1: model = torch.nn.parallel.DistributedDataParallel( model, diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 90d72ca1..1342d3c8 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -271,7 +271,7 @@ def __init__( # Register the network submodule (important: real nn.Module attribute) if model is not None: self.setup_distributed(device=device) - self._model = self.build_model(model) + self._model = self.distribute_model(model) @classmethod def from_model( @@ -400,14 +400,15 @@ def shape(self, shape: tuple[int, int, int]): # --- Helper Functions --- def rebuild_model(self): - self._model = self.build_model(self._model) + self._model = self.distribute_model(self._model) # Reset method that goes back to the pretrained weights. def reset(self): """reset the model to the pretrained weights""" - self._model = self.build_model( - self._model, self._pretrained_weights - ) # Since loading the pretrained weights needs to be done in build_model. + self.model.load_state_dict(self._pretrained_weights.copy()) + self._model = self.distribute_model( + self.model + ) # Maybe add a check to see if distributed or not, but not very computationally expensive to do this. def get_optimization_parameters(self): return self.params From 9465aa772da4f9099a3fd3414dc37b600b03dada Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Feb 2026 17:02:53 -0800 Subject: [PATCH 083/335] Added deterministic random winner initialization in inr.py --- src/quantem/core/ml/inr.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/quantem/core/ml/inr.py b/src/quantem/core/ml/inr.py index de43b6c4..a2a2f4b7 100644 --- a/src/quantem/core/ml/inr.py +++ b/src/quantem/core/ml/inr.py @@ -23,7 +23,7 @@ def __init__( hsiren: bool = False, dtype: torch.dtype = torch.float32, final_activation: str | Callable = "identity", - winner_initialization: bool = False, + winner_initialization: bool | int = False, ) -> None: """Initialize Siren. @@ -111,6 +111,9 @@ def _build(self) -> None: self.net = nn.Sequential(*net_list) if self.winner_initialization: + if isinstance(self.winner_initialization, int): + rng = torch.Generator() + rng.manual_seed(self.winner_initialization) with torch.no_grad(): self.net[0].linear.weight += ( torch.randn_like(self.net[0].linear.weight) * 5 / self.first_omega_0 From 05901bf97f459102833cf643a1a26bd52b78145d Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Feb 2026 17:12:31 -0800 Subject: [PATCH 084/335] inr.py, ignore some type hinting stuff that seems not correct...? --- src/quantem/core/ml/inr.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/quantem/core/ml/inr.py b/src/quantem/core/ml/inr.py index a2a2f4b7..15cc6057 100644 --- a/src/quantem/core/ml/inr.py +++ b/src/quantem/core/ml/inr.py @@ -115,11 +115,11 @@ def _build(self) -> None: rng = torch.Generator() rng.manual_seed(self.winner_initialization) with torch.no_grad(): - self.net[0].linear.weight += ( - torch.randn_like(self.net[0].linear.weight) * 5 / self.first_omega_0 + self.net[0].linear.weight += ( # pyright: ignore[reportAttributeAccessIssue] + torch.randn_like(self.net[0].linear.weight) * 5 / self.first_omega_0 # type:ignore ) - self.net[1].linear.weight += ( - torch.randn_like(self.net[1].linear.weight) * 0.1 / self.hidden_omega_0 + self.net[1].linear.weight += ( # pyright: ignore[reportAttributeAccessIssue] + torch.randn_like(self.net[1].linear.weight) * 0.1 / self.hidden_omega_0 # type:ignore ) def forward(self, coords: torch.Tensor) -> torch.Tensor: From 953ee9443755f2a9c616f7b240655307f6d27316 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Feb 2026 21:14:02 -0800 Subject: [PATCH 085/335] Removed MSE and L1 implementations in --- src/quantem/core/ml/loss_functions.py | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/src/quantem/core/ml/loss_functions.py b/src/quantem/core/ml/loss_functions.py index 00069eaa..1ffc3006 100644 --- a/src/quantem/core/ml/loss_functions.py +++ b/src/quantem/core/ml/loss_functions.py @@ -146,30 +146,6 @@ def combined_l2(pred: torch.Tensor, target: torch.Tensor, alpha: float = 0.7) -> # TODO: Better loss function implementation? More torch-like. -class L1Loss(nn.Module): - def __init__( - self, - reduction: str = "mean", - ): - super(L1Loss, self).__init__() - self.reduction = reduction - - def forward(self, pred: torch.Tensor, target: torch.Tensor) -> torch.Tensor: - return torch.nn.functional.l1_loss(pred, target, reduction=self.reduction) - - -class MSELoss(nn.Module): - def __init__( - self, - reduction: str = "mean", - ): - super(MSELoss, self).__init__() - self.reduction = reduction - - def forward(self, pred: torch.Tensor, target: torch.Tensor) -> torch.Tensor: - return torch.nn.functional.mse_loss(pred, target, reduction=self.reduction) - - class MSELogMSELoss(nn.Module): def __init__( self, From bde606fd48f70dd3471df0d4b201a12c20b263c9 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Feb 2026 21:21:52 -0800 Subject: [PATCH 086/335] Background subtraction in imaging_utils.py --- src/quantem/core/utils/imaging_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/quantem/core/utils/imaging_utils.py b/src/quantem/core/utils/imaging_utils.py index 5d706452..aaa86468 100644 --- a/src/quantem/core/utils/imaging_utils.py +++ b/src/quantem/core/utils/imaging_utils.py @@ -849,7 +849,7 @@ def add_edges(i1, i2): inc = _find_wrap(phi_f[i1], phi_f[i2]) rel = rel_f[i1] + rel_f[i2] - edges.append( # ty:ignore[possibly-missing-attribute] + edges.append( # type: ignore[attr-defined] torch.stack([i1, i2, rel, inc], dim=1) ) @@ -1099,7 +1099,7 @@ def background_subtract( sigma: Optional[float] = None, num_iter: int = 10, plot_result: bool = True, - axsize: Tuple[int, int] = (3.1, 3), + axsize: Tuple[int, int] = (3, 3), cmap: str = "turbo", return_background_and_mask: bool = False, **show_kwargs, From c0b61a36511557d6ba90c37a11575ab7ed2c33f5 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Feb 2026 21:41:31 -0800 Subject: [PATCH 087/335] Fixed _token calling .from_data in TomographyDatasetBase --- src/quantem/tomography/dataset_models.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index eba21ef3..1fb454b2 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -50,8 +50,8 @@ def __init__( AutoSerialize.__init__(self) OptimizerMixin.__init__(self) nn.Module.__init__(self) - # if _token is not self._token: TODO: Idk why this isn't working. - # raise RuntimeError("Use TomographyPixDataset.from_* to instantiate this class.") + if _token is not self._token: + raise RuntimeError("Use TomographyPixDataset.from_* to instantiate this class.") if not ( tilt_stack.shape[0] < tilt_stack.shape[1] or tilt_stack.shape[0] < tilt_stack.shape[2] @@ -104,6 +104,7 @@ def from_data( tilt_angles=tilt_angles, learn_shift=learn_shift, learn_tilt_axis=learn_tilt_axis, + _token=cls._token, ) # --- Optimization Parameters --- From d42611bddfd7bc7a5c6f01e57a996249dd284c91 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Feb 2026 21:56:21 -0800 Subject: [PATCH 088/335] Reconciled params and get_optimization_parameters type-hinting. Additional fix to TomographyLiteINR where H-Siren Winner initialization was still inputting a bool --- src/quantem/tomography/dataset_models.py | 40 ++++++++++------------- src/quantem/tomography/tomography_lite.py | 2 +- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index 1fb454b2..0533e7da 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -1,6 +1,6 @@ from abc import abstractmethod from dataclasses import dataclass -from typing import Any +from typing import Any, Generator import numpy as np import torch @@ -108,10 +108,21 @@ def from_data( ) # --- Optimization Parameters --- - # @property - # def params(self): - # # TODO: Need to double check if this is correct way, also need to implement get_optimization_parameters @Arthur!! - # raise NotImplementedError("This method should be implemented in subclasses.") + + @property + def params(self) -> Generator[torch.nn.Parameter, None, None]: + """ + Returns the parameters that should be optimized for this dataset. + + Should be implemented in subclasses. + """ + raise NotImplementedError("This method should be implemented in subclasses.") + + def get_optimization_parameters(self) -> list[nn.Parameter]: + """ + Get the parameters that should be optimized for this model. + """ + return list(self.params) # --- Forward pass --- @abstractmethod @@ -181,15 +192,6 @@ def learnable_tilts(self) -> int: def learnable_tilts(self, learnable_tilts: int): self._learnable_tilts = learnable_tilts - @property - def params(self) -> dict[str, torch.nn.Parameter]: - """ - Returns the parameters that should be optimized for this dataset. - - Should be implemented in subclasses. - """ - raise NotImplementedError("This method should be implemented in subclasses.") - @property def z1_params(self) -> torch.nn.Parameter: return self._z1_params @@ -240,12 +242,6 @@ def to(self, device: str): self.device = device - def get_optimization_parameters(self) -> list[nn.Parameter]: - """ - Get the parameters that should be optimized for this model. - """ - return list[nn.Parameter](self.params) - class TomographyPixDataset(TomographyDatasetBase): """ @@ -305,9 +301,9 @@ def __init__( learn_shift: bool = True, learn_tilt_axis: bool = True, seed: int = 42, - token: object | None = None, + _token: object | None = None, ): - super().__init__(tilt_stack, tilt_angles, learn_shift, learn_tilt_axis, token) + super().__init__(tilt_stack, tilt_angles, learn_shift, learn_tilt_axis, _token=_token) # --- Forward Pass w/ Params Method for OptimizerMixin --- def forward(self, dummy_input: Any = None): diff --git a/src/quantem/tomography/tomography_lite.py b/src/quantem/tomography/tomography_lite.py index 786c333a..57a78446 100644 --- a/src/quantem/tomography/tomography_lite.py +++ b/src/quantem/tomography/tomography_lite.py @@ -27,7 +27,7 @@ def from_dataset( dset_model = dset # Define the object model - model = HSiren(alpha=1, winner_initialization=True) + model = HSiren(alpha=1, winner_initialization=72) obj_model = ObjectINR.from_model( model=model, shape=( From aea34b1b19421b1b41b812fd1b211ba78de30a9d Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Feb 2026 22:02:19 -0800 Subject: [PATCH 089/335] Object models also consistent with dataset models in terms of params and get_optimization_parameters --- src/quantem/tomography/object_models.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 1342d3c8..deb5eb31 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -1,7 +1,7 @@ from abc import abstractmethod from copy import deepcopy from dataclasses import dataclass -from typing import Any, Callable +from typing import Any, Callable, Generator import numpy as np import torch @@ -107,9 +107,11 @@ def reset(self) -> None: raise NotImplementedError @property - def params(self) -> list[nn.Parameter]: + def params(self) -> Generator[torch.nn.Parameter, None, None]: """ Get the parameters that should be optimized for this model. + + Should be implemented in subclasses. """ raise NotImplementedError @@ -118,7 +120,7 @@ def get_optimization_parameters(self) -> list[nn.Parameter]: """ Get the parameters that should be optimized for this model. """ - return list[nn.Parameter](self.params()) + return list(self.params) @abstractmethod # Each subclass should implement this. def to(self, *args, **kwargs): @@ -357,8 +359,11 @@ def apply_hard_constraints(self, pred: torch.Tensor) -> torch.Tensor: return pred @property - def params(self): - return self.model.parameters() + def params(self) -> Generator[torch.nn.Parameter, None, None]: + return self.model.parameters() # type: ignore[attr-defined] + + def get_optimization_parameters(self): + return self.params # Pretraining @property @@ -410,9 +415,6 @@ def reset(self): self.model ) # Maybe add a check to see if distributed or not, but not very computationally expensive to do this. - def get_optimization_parameters(self): - return self.params - # --- Forward Method --- def forward(self, coords: torch.Tensor) -> torch.Tensor: From 038e94d4d1267ec7a5ca92096e3caf7c2784ff87 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Feb 2026 22:03:36 -0800 Subject: [PATCH 090/335] More type hinting --- src/quantem/tomography/object_models.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index deb5eb31..6d8bc49e 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -358,12 +358,13 @@ def apply_hard_constraints(self, pred: torch.Tensor) -> torch.Tensor: return pred + # --- Optimization Parameters --- @property def params(self) -> Generator[torch.nn.Parameter, None, None]: return self.model.parameters() # type: ignore[attr-defined] - def get_optimization_parameters(self): - return self.params + def get_optimization_parameters(self) -> list[nn.Parameter]: + return list(self.params) # Pretraining @property From edab1120c639b831ba7c139123d277400d2605cc Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Feb 2026 22:11:29 -0800 Subject: [PATCH 091/335] Remove params function, use nn.Module.parameters() directly in get_optimization_parameters in dataset_models.py --- src/quantem/tomography/dataset_models.py | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index 0533e7da..d0e8df09 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -1,6 +1,6 @@ from abc import abstractmethod from dataclasses import dataclass -from typing import Any, Generator +from typing import Any import numpy as np import torch @@ -109,20 +109,11 @@ def from_data( # --- Optimization Parameters --- - @property - def params(self) -> Generator[torch.nn.Parameter, None, None]: - """ - Returns the parameters that should be optimized for this dataset. - - Should be implemented in subclasses. - """ - raise NotImplementedError("This method should be implemented in subclasses.") - def get_optimization_parameters(self) -> list[nn.Parameter]: """ Get the parameters that should be optimized for this model. """ - return list(self.params) + return list(self.parameters()) # --- Forward pass --- @abstractmethod @@ -335,10 +326,6 @@ def forward(self, dummy_input: Any = None): else: return torch.zeros_like(shifts), torch.zeros_like(z1), torch.zeros_like(z3) - @property - def params(self): - return self.parameters() - def get_coords( self, batch: dict[str, torch.Tensor], N: int, num_samples_per_ray: int ) -> torch.Tensor: From 888e6b946e441b6001beb7942790d287878e49da Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Feb 2026 22:14:07 -0800 Subject: [PATCH 092/335] Updated TomographyPixDataset docstring --- src/quantem/tomography/dataset_models.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index d0e8df09..84a1d108 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -238,9 +238,7 @@ class TomographyPixDataset(TomographyDatasetBase): """ Dataset class for pixel-based tomography, i.e AD, SIRT, WBP, etc... - TODO: - - What should the forward pass return? In AD, it should be both the tilt image and the pose. - In SIRT, it's only the tilt image. + These algorithms only require the tilt image in the forward call. """ def __init__( From c0a8777409282cffe8b3f9a2a86dd165b803314d Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Feb 2026 22:15:27 -0800 Subject: [PATCH 093/335] Removed DatasetValue from the forward call in TomgoraphyINRDataset, torch does not like dataclasses to be outputted in the __getitem__ --- src/quantem/tomography/dataset_models.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index 84a1d108..27d30a93 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -449,12 +449,6 @@ def __getitem__( pixel_i = remaining // self.tilt_stack.shape[1] pixel_j = remaining % self.tilt_stack.shape[1] - # return DatasetValue( - # target=self.tilt_stack[projection_idx, pixel_i, pixel_j], - # tilt_angle=self.tilt_angles[projection_idx], - # projection_idx=projection_idx, - # pixel_loc=(pixel_i, pixel_j), - # ) return { "projection_idx": torch.tensor(projection_idx), "pixel_i": torch.tensor(pixel_i), From 0c9967605923ff507dccab153c08e9e59728290a Mon Sep 17 00:00:00 2001 From: Georgios Varnavides Date: Tue, 24 Feb 2026 12:30:53 +0100 Subject: [PATCH 094/335] adding from_abtem reader --- src/quantem/core/io/file_readers.py | 124 ++++++++++++++++++++++++++-- 1 file changed, 117 insertions(+), 7 deletions(-) diff --git a/src/quantem/core/io/file_readers.py b/src/quantem/core/io/file_readers.py index cb36f1de..d537e125 100644 --- a/src/quantem/core/io/file_readers.py +++ b/src/quantem/core/io/file_readers.py @@ -115,7 +115,7 @@ def read_2d( if file_type is None: file_type = Path(file_path).suffix.lower().lstrip(".") - file_reader = importlib.import_module(f"rsciio.{file_type}").file_reader # type: ignore + file_reader = importlib.import_module(f"rsciio.{file_type}").file_reader imported_data = file_reader(file_path)[0] dataset = Dataset2d.from_array( @@ -162,7 +162,7 @@ def read_emdfile_to_4dstem( try: data = file for key in data_keys: - data = data[key] # type: ignore + data = data[key] except KeyError: raise KeyError(f"Could not find key {data_keys} in {file_path}") @@ -175,13 +175,13 @@ def read_emdfile_to_4dstem( try: calibration = file for key in calibration_keys: - calibration = calibration[key] # type: ignore + calibration = calibration[key] except KeyError: raise KeyError(f"Could not find calibration key {calibration_keys} in {file_path}") - r_pixel_size = calibration["R_pixel_size"][()] # type: ignore - q_pixel_size = calibration["Q_pixel_size"][()] # type: ignore - r_pixel_units = calibration["R_pixel_units"][()] # type: ignore - q_pixel_units = calibration["Q_pixel_units"][()] # type: ignore + r_pixel_size = calibration["R_pixel_size"][()] + q_pixel_size = calibration["Q_pixel_size"][()] + r_pixel_units = calibration["R_pixel_units"][()] + q_pixel_units = calibration["Q_pixel_units"][()] dataset = Dataset4dstem.from_array( array=data, @@ -191,3 +191,113 @@ def read_emdfile_to_4dstem( dataset.file_path = file_path return dataset + + +def read_abtem(url: str | PathLike): + """ + Read canonical abTEM Zarr file(s) into quantem Dataset(s). + + Returns + ------- + Dataset or list[Dataset] + """ + + def _open_zarr(url): + import zarr + + if url.endswith(".zip"): + store = zarr.storage.ZipStore(url, mode="r") # ty:ignore[possibly-missing-attribute] + return zarr.open(store=store, mode="r") + return zarr.open(url, mode="r") + + def _validate_canonical_format(root): + if "metadata0" in root.attrs: + return + + if "kwargs0" in root.attrs: + raise ValueError( + "Legacy abTEM Zarr format detected.\n\n" + "quantem supports only canonical abTEM Zarr format.\n" + "Re-save using abtem>=1.1.0:\n\n" + " measurement = abtem.from_zarr()\n" + " measurement.to_zarr()" + ) + + raise ValueError("Unrecognized Zarr format.") + + def _iter_metadata_indices(root): + i = 0 + while f"metadata{i}" in root.attrs: + yield i + i += 1 + + def _decode_types(obj): + if isinstance(obj, dict): + if obj.get("_type") == "tuple": + return tuple(_decode_types(v) for v in obj["_value"]) + return {k: _decode_types(v) for k, v in obj.items()} + if isinstance(obj, list): + return [_decode_types(v) for v in obj] + return obj + + def _normalize_unit(unit): + if unit is None: + return "pixels" + + unit = unit.strip() + + UNIT_MAP = { + "Å": "A", + "Ångström": "A", + "Angstrom": "A", + "1/Å": "A^-1", + "Å^-1": "A^-1", + "1/A": "A^-1", + } + + return UNIT_MAP.get(unit, unit) + + def _convert_axes(axes_dict): + sampling = [] + origin = [] + units = [] + + for key in sorted(axes_dict, key=lambda x: int(x.split("_")[1])): + axis = axes_dict[key] + + sampling.append(axis.get("sampling", 1.0)) + units.append(_normalize_unit(axis.get("units", None))) + origin.append(0.0) # deliberate design choice + + return tuple(origin), tuple(sampling), tuple(units) + + def _read_single_dataset(root, index): + metadata = _decode_types(root.attrs[f"metadata{index}"]).copy() + + axes_dict = metadata.pop("axes") + dataset_type = metadata.pop("type") + metadata.pop("data_origin", None) + + origin, sampling, units = _convert_axes(axes_dict) + + array = root[f"array{index}"] + signal_units = metadata.get("units", "arb. units") + + dataset = Dataset.from_array( + array=array, + name=dataset_type, + origin=origin, + sampling=sampling, + units=units, + signal_units=signal_units, + ) + + dataset._metadata = metadata + return dataset + + root = _open_zarr(url) + _validate_canonical_format(root) + + datasets = [_read_single_dataset(root, i) for i in _iter_metadata_indices(root)] + + return datasets[0] if len(datasets) == 1 else datasets From 6d9cbf6dd3a4891f6b7feb3a8b9b41e49b8d44cc Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 24 Feb 2026 13:09:49 -0800 Subject: [PATCH 095/335] Class instantiation for ObjectPixelated; from_uniform and from_array --- src/quantem/tomography/object_models.py | 32 +++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 6d8bc49e..1c1249a7 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -62,8 +62,7 @@ def __init__( RNGMixin.__init__(self, rng=rng, device=device) OptimizerMixin.__init__(self) - # Initialize a torch.zeros volume with the given shape - self._obj = torch.zeros(self._shape, device=device, dtype=torch.float32) + # --- Instantiation ---- # --- Properties --- @property @@ -196,6 +195,35 @@ def __init__( _token=self._token, ) + # --- Instantiation ---- + @classmethod + def from_uniform( + cls, + shape: tuple[int, int, int], + device: str = "cpu", + rng: np.random.Generator | int | None = None, + ): + # Initialize a torch.zeros volume with the given shape + obj = torch.zeros(shape, device=device, dtype=torch.float32) + obj_model = cls(shape=shape, device=device, rng=rng) + obj_model._obj = obj + return obj_model + + @classmethod + def from_array( + cls, + initial_obj: torch.Tensor | np.ndarray, + device: str = "cpu", + rng: np.random.Generator | int | None = None, + ): + obj_model = cls(shape=initial_obj.shape, device=device, rng=rng) + if isinstance(initial_obj, np.ndarray): + initial_obj = torch.tensor(initial_obj, dtype=torch.float32) + else: + initial_obj = initial_obj.clone() + obj_model._obj = initial_obj.to(device) + return obj_model + # --- Properties ---- @property def obj(self) -> torch.Tensor: From ddd473d02aa5fb39b6a953cebafcf7126391b7c4 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 24 Feb 2026 13:13:10 -0800 Subject: [PATCH 096/335] Pretraining in ObjectINR, implemented reset() --- src/quantem/tomography/object_models.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 1c1249a7..33b40b2a 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -500,9 +500,7 @@ def pretrain( self.set_scheduler(scheduler_params, num_iters) if reset: - raise NotImplementedError( - "TODO: Resseting the model to the pretrained weights is not implemented yet. To make this work I would have to reinstantiate the model I think." - ) + self.reset() loss_fn = get_loss_function(loss_fn, self.dtype) From d51aa51336aea67134f3bd0a482f1ecd404dbd5d Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 24 Feb 2026 13:23:28 -0800 Subject: [PATCH 097/335] .to(device) called again at the reconstruct call just to make sure device consistency --- src/quantem/tomography/dataset_models.py | 2 ++ src/quantem/tomography/tomography.py | 14 +++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index 27d30a93..4a1f9722 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -220,6 +220,7 @@ def to(self, device: str): """ Moves the dataset to the device, and also insantiates the aux params to the device. """ + self.tilt_stack = self.tilt_stack.to(device) self.tilt_angles = self.tilt_angles.to(device) @@ -467,6 +468,7 @@ def __len__( return self.tilt_stack.shape[0] * N * N def to(self, device: str): + super().to(device) self._z1_params = nn.Parameter(self._z1_angles.to(device)) self._z3_params = nn.Parameter(self._z3_angles.to(device)) self._shifts_params = nn.Parameter(self._shifts.to(device)) diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index de91107e..0a089ab3 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -1,4 +1,4 @@ -from typing import List, Literal, Optional, Self, Tuple +from typing import Literal, Optional, Self import numpy as np import torch @@ -51,8 +51,8 @@ def reconstruct( optimizer_params: dict | None = None, scheduler_params: dict | None = {}, constraints: dict = {}, # TODO: What to pass into the constraints? - loss_func: Tuple[str, Optional[float]] = ("smooth_l1", 0.07), - num_samples_per_ray: int | List[Tuple[int, int]] = None, + loss_func: tuple[str, Optional[float]] = ("smooth_l1", 0.07), + num_samples_per_ray: int | list[tuple[int, int]] = None, profiling_mode: bool = False, val_fraction: float = 0.0, # reset_dset: bool = False, @@ -65,10 +65,10 @@ def reconstruct( # TODO: Prior to reconstruction, it is assumed that object + dataset are both in the correct devices. Need to implement a way to check this. - # if self.obj_model.device != self.dset.device: - # raise ValueError( - # f"Should never happen! obj_model and dset must be on the same device, got {self.obj_model.device} and {self.dset.device}" - # ) + # Check device consistency + self.obj_model.to(self.device) + self.dset.to(self.device) + if profiling_mode: if self.global_rank == 0: print("Profiling mode enabled.") From 16aa0f222e9f1ce6fb58fb9f6eff675a68534d68 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 24 Feb 2026 13:27:47 -0800 Subject: [PATCH 098/335] For TomographyLite classes, .from_dataset now only allows for tilt_series and tilt_angles to be inputted; No longer needed to give the dataset. --- src/quantem/core/ml/inr.py | 2 +- src/quantem/tomography/tomography_lite.py | 32 ++++++++++++++++------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/quantem/core/ml/inr.py b/src/quantem/core/ml/inr.py index 15cc6057..46447660 100644 --- a/src/quantem/core/ml/inr.py +++ b/src/quantem/core/ml/inr.py @@ -195,7 +195,7 @@ def __init__( alpha: float = 1.0, dtype: torch.dtype = torch.float32, final_activation: str | Callable = "identity", - winner_initialization: bool = False, + winner_initialization: bool | int = False, ) -> None: """Initialize HSiren. diff --git a/src/quantem/tomography/tomography_lite.py b/src/quantem/tomography/tomography_lite.py index 57a78446..6bc6995e 100644 --- a/src/quantem/tomography/tomography_lite.py +++ b/src/quantem/tomography/tomography_lite.py @@ -2,9 +2,15 @@ from typing import Literal, Self import numpy as np +import torch +from numpy.typing import NDArray +from quantem.core.datastructures.dataset3d import Dataset3d from quantem.core.ml.inr import HSiren -from quantem.tomography.dataset_models import DatasetModelType +from quantem.tomography.dataset_models import ( + TomographyINRDataset, + TomographyPixDataset, +) from quantem.tomography.logger_tomography import LoggerTomography from quantem.tomography.object_models import ObjectINR, ObjectPixelated from quantem.tomography.tomography import Tomography, TomographyConventional @@ -18,13 +24,17 @@ class TomographyLiteINR(Tomography): @classmethod def from_dataset( cls, - dset: DatasetModelType, + tilt_series: Dataset3d | NDArray | torch.Tensor, + tilt_angles: NDArray | torch.Tensor, device: str = "cuda", log_dir: os.PathLike | str | None = None, log_images_every: int = 10, rng: np.random.Generator | int | None = None, ) -> Self: - dset_model = dset + dset_model = TomographyINRDataset.from_data( + tilt_stack=tilt_series, + tilt_angles=tilt_angles, + ) # Define the object model model = HSiren(alpha=1, winner_initialization=72) @@ -125,17 +135,21 @@ class TomographyLiteConv(TomographyConventional): @classmethod def from_dataset( cls, - dset: DatasetModelType, + tilt_series: Dataset3d | NDArray | torch.Tensor, + tilt_angles: NDArray | torch.Tensor, device: str = "cuda", rng: np.random.Generator | int | None = None, ) -> Self: - dset_model = dset + dset_model = TomographyPixDataset.from_data( + tilt_stack=tilt_series, + tilt_angles=tilt_angles, + ) - obj_model = ObjectPixelated( + obj_model = ObjectPixelated.from_uniform( shape=( - max(dset_model.tilt_stack.shape), - max(dset_model.tilt_stack.shape), - max(dset_model.tilt_stack.shape), + max(tilt_series.shape), + max(tilt_series.shape), + max(tilt_series.shape), ), device=device, rng=rng, From 945064e2b5b6b5c0df284d0f3db15b3452bff52c Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 24 Feb 2026 13:40:05 -0800 Subject: [PATCH 099/335] Made tomography_utils.py; moved background_subtraction and other utility functions. Only remaining things in utils.py is rot_ZXZ. Gaussian filters added to filters.py --- src/quantem/core/utils/filter.py | 51 ++++ src/quantem/core/utils/imaging_utils.py | 257 +++++++---------- src/quantem/core/utils/tomography_utils.py | 316 +++++++++++++++++++++ src/quantem/tomography/tomography.py | 9 +- src/quantem/tomography/utils.py | 231 --------------- 5 files changed, 476 insertions(+), 388 deletions(-) create mode 100644 src/quantem/core/utils/tomography_utils.py diff --git a/src/quantem/core/utils/filter.py b/src/quantem/core/utils/filter.py index 589d84ca..3f9d13b7 100644 --- a/src/quantem/core/utils/filter.py +++ b/src/quantem/core/utils/filter.py @@ -1,5 +1,6 @@ import numpy as np import scipy.ndimage as ndi +import torch from quantem.core.utils.utils import extract_patches @@ -109,3 +110,53 @@ def otsu_threshold(img: np.ndarray, bins: int = 256) -> float: current_max = between_var threshold = bin_edges[i] return threshold + + +# --- Gaussian filters --- + + +def gaussian_kernel_1d(sigma: float, num_sigmas: float = 3.0) -> torch.Tensor: + radius = np.ceil(num_sigmas * sigma) + support = torch.arange(-radius, radius + 1, dtype=torch.float) + kernel = torch.distributions.Normal(loc=0, scale=sigma).log_prob(support).exp_() + # Ensure kernel weights sum to 1, so that image brightness is not altered + return kernel.mul_(1 / kernel.sum()) + + +def gaussian_filter_2d( + img: torch.Tensor, sigma: float, kernel_1d: torch.Tensor +) -> torch.Tensor: # Add kernel_1d as an argument + # kernel_1d = gaussian_kernel_1d(sigma) # Create 1D Gaussian kernel - Moved outside function + padding = len(kernel_1d) // 2 # Ensure that image size does not change + img = img.unsqueeze(0).unsqueeze_(0) # Make copy, make 4D for ``conv2d()`` + # Convolve along columns and rows + img = torch.nn.functional.conv2d(img, weight=kernel_1d.view(1, 1, -1, 1), padding=(padding, 0)) + img = torch.nn.functional.conv2d(img, weight=kernel_1d.view(1, 1, 1, -1), padding=(0, padding)) + return img.squeeze_(0).squeeze_(0) # Make 2D again + + +def gaussian_filter_2d_stack(stack: torch.Tensor, kernel_1d: torch.Tensor) -> torch.Tensor: + """ + Apply 2D Gaussian blur to each slice stack[:, i, :] in a vectorized way. + + Args: + stack (torch.Tensor): Tensor of shape (H, N, W) where N is num_sinograms + kernel_1d (torch.Tensor): 1D Gaussian kernel + + Returns: + torch.Tensor: Blurred stack of same shape (H, N, W) + """ + H, N, W = stack.shape + padding = len(kernel_1d) // 2 + + # Reshape to (N, 1, H, W) for conv2d + stack_reshaped = stack.permute(1, 0, 2).unsqueeze(1) # (N, 1, H, W) + + # Apply separable conv2d: vertical then horizontal + out = torch.nn.functional.conv2d( + stack_reshaped, kernel_1d.view(1, 1, -1, 1), padding=(padding, 0) + ) + out = torch.nn.functional.conv2d(out, kernel_1d.view(1, 1, 1, -1), padding=(0, padding)) + + # Restore shape to (H, N, W) + return out.squeeze(1).permute(1, 0, 2) diff --git a/src/quantem/core/utils/imaging_utils.py b/src/quantem/core/utils/imaging_utils.py index aaa86468..d352a051 100644 --- a/src/quantem/core/utils/imaging_utils.py +++ b/src/quantem/core/utils/imaging_utils.py @@ -1,22 +1,14 @@ # Utilities for processing images import math -from typing import Any, Optional, Tuple +from typing import Optional, Tuple import numpy as np import torch - -# TODO: Temporary -from matplotlib import cm from numpy.typing import NDArray from scipy.ndimage import gaussian_filter -from scipy.special import comb from quantem.core.utils.utils import generate_batches -from quantem.core.visualization import show_2d - -ImageType = NDArray[Any] -BoolArray = NDArray[np.bool_] def dft_upsample( @@ -849,7 +841,7 @@ def add_edges(i1, i2): inc = _find_wrap(phi_f[i1], phi_f[i2]) rel = rel_f[i1] + rel_f[i2] - edges.append( # type: ignore[attr-defined] + edges.append( # ty:ignore[possibly-missing-attribute] torch.stack([i1, i2, rel, inc], dim=1) ) @@ -1058,157 +1050,118 @@ def unwrap_phase_2d_torch( ) -# Background subtraction +def radially_project_fourier_tensor( + corner_centered_array: torch.Tensor, + sampling: Tuple[float, float], + q_bins: torch.Tensor | None = None, +): + """ + Radially project a corner-centered Fourier array onto radial bins. -# single TypeVar: works for both numpy and + Supports: + - single array: (kx, ky) + - batched arrays: (n, kx, ky) + - implicit bins (from grid) or explicit external bins + Parameters + ---------- + corner_centered_array : torch.Tensor + Shape (kx, ky) or (n, kx, ky) + sampling : (float, float) + Real-space sampling (sx, sy) + q_bins : torch.Tensor, optional + 1D tensor of radial bin centers -def _as_array(x: ImageType) -> NDArray[Any]: - return ( - x.array - if isinstance( - x, + Returns + ------- + q_bins_out : torch.Tensor + Radial bin centers + array_1d : torch.Tensor + Shape (n, nq) or (nq,) + """ + + if corner_centered_array.is_complex(): + q, real_part = radially_project_fourier_tensor( + corner_centered_array.real, sampling, q_bins ) - else np.asarray(x) - ) + # zero by symmetry + # _, imag_part = radially_project_fourier_tensor( + # corner_centered_array.imag, sampling, q_bins + # ) + return q, real_part # + 1j * imag_part + device = corner_centered_array.device + sx, sy = sampling -def _bernstein_basis_1d(n: int, t: NDArray[Any]) -> NDArray[Any]: - k = np.arange(n + 1, dtype=int) - return ( - comb(n, k)[None, :] * (t[:, None] ** k[None, :]) * ((1.0 - t)[:, None] ** (n - k)[None, :]) - ) + # --- normalize shape to (batch, kx, ky) + if corner_centered_array.ndim == 2: + array = corner_centered_array[None, ...] + squeeze_output = True + elif corner_centered_array.ndim == 3: + array = corner_centered_array + squeeze_output = False + else: + raise ValueError("Input must be 2D or 3D tensor") + n_batch, nkx, nky = array.shape -def _build_basis_matrix(im_shape: Tuple[int, int], order: Tuple[int, int]) -> NDArray[Any]: - H, W = im_shape - ou, ov = int(order[0]), int(order[1]) - u = np.linspace(0.0, 1.0, H) - v = np.linspace(0.0, 1.0, W) - Bu = _bernstein_basis_1d(ou, u) - Bv = _bernstein_basis_1d(ov, v) - basis_cube = np.einsum("ik,jl->ijkl", Bu, Bv) - return basis_cube.reshape(H * W, (ou + 1) * (ov + 1)) - - -def background_subtract( - image: ImageType, - mask: Optional[BoolArray] = None, - thresh_bg: Optional[float] = None, - order: Tuple[int, int] = (1, 1), - sigma: Optional[float] = None, - num_iter: int = 10, - plot_result: bool = True, - axsize: Tuple[int, int] = (3, 3), - cmap: str = "turbo", - return_background_and_mask: bool = False, - **show_kwargs, -) -> ImageType | Tuple[ImageType, NDArray[Any], BoolArray]: - """ - Background subtraction via bivariate Bernstein polynomial fitting. + # --- build k-grid + kx = torch.fft.fftfreq(nkx, d=sx, device=device) + ky = torch.fft.fftfreq(nky, d=sy, device=device) + k = torch.sqrt(kx[:, None] ** 2 + ky[None, :] ** 2).reshape(-1) - Returns - ------- - - If `return_background_and_mask=False`: ImageType (same as input) - - If `True`: (ImageType, numpy.ndarray, numpy.ndarray[bool]) - where background and mask are always NumPy. - """ - im = _as_array(image).astype(float, copy=True) - if im.ndim != 2: - raise ValueError("`image` must be 2D") + # --- determine radial bins + k_max = min(0.5 / sx, 0.5 / sy) - mask_arr: BoolArray = ( - np.ones_like(im, dtype=bool) if mask is None else np.asarray(mask, dtype=bool) - ) - if mask_arr.shape != im.shape: - raise ValueError("`mask` must match `image` shape") - - order = (int(order[0]), int(order[1])) - A_full = _build_basis_matrix(im.shape, order) - H, W = im.shape - im_flat = im.ravel() - - im_bg = np.zeros_like(im) - thresh_val = np.median(im[mask_arr]) if thresh_bg is None else float(thresh_bg) - - resid = im - im_bg - if sigma and sigma > 0: - resid = gaussian_filter(resid, sigma=sigma, mode="nearest") - mask_bg: BoolArray = (resid < thresh_val) & mask_arr - - for _ in range(int(num_iter)): - idx = mask_bg.ravel() - if not np.any(idx): - idx = mask_arr.ravel() - coefs, *_ = np.linalg.lstsq(A_full[idx, :], im_flat[idx], rcond=None) - im_bg = (A_full @ coefs).reshape(H, W) - - resid = im - im_bg - if sigma and sigma > 0: - resid = gaussian_filter(resid, sigma=sigma, mode="nearest") - - thr = thresh_val if thresh_bg is None else float(thresh_bg) - mask_bg = (resid < thr) & mask_arr - - im_sub_np = im - im_bg - - if plot_result: - vals = im_sub_np[mask_arr] - vals = vals[np.isfinite(vals)] - if vals.size == 0: - vals = np.array([0.0]) - vmin_sub = float(np.min(vals)) - vmax_sub = float(np.max(vals)) - vrange = float(max(abs(vmin_sub), abs(vmax_sub))) or 1e-12 - - bg_disp = (im_bg - np.mean(im_bg)).copy() - bg_disp[~mask_bg] = np.nan - - cmap_base = cm.get_cmap(cmap).with_extremes(bad="black") - cmap_div = "RdBu_r" - - disp = [im - np.mean(im_bg), bg_disp, im_sub_np] - norm = [ - { - "interval_type": "manual", - "stretch_type": "linear", - "vmin": vmin_sub, - "vmax": vmax_sub, - }, - { - "interval_type": "manual", - "stretch_type": "linear", - "vmin": vmin_sub, - "vmax": vmax_sub, - }, - { - "interval_type": "centered", - "stretch_type": "linear", - "vcenter": 0.0, - "half_range": vrange, - }, - ] - - show_2d( - disp, - cmap=[cmap_base, cmap_base, cmap_div], - norm=norm, - cbar=[False, False, True], - title=["Input Image", "Background (fit region)", "Background Subtracted"], - axsize=axsize, - **show_kwargs, + if q_bins is None: + dk = kx[1] - kx[0] + num_bins = int(torch.floor(k_max / dk).item()) + 1 + dq = dk + q_bins_out = torch.arange(num_bins, device=device, dtype=k.dtype) * dk + else: + q_bins = q_bins.to(device) + dq = q_bins[1] - q_bins[0] + q_bins_out = q_bins[q_bins <= k_max] + num_bins = q_bins_out.numel() + + # ---- INTERNAL padding (key change) + num_bins_internal = num_bins + 2 + + # --- map k -> bin indices (NO CLIPPING) + inds = k / dq + inds_f = torch.floor(inds).long() + inds_f = torch.clamp(inds_f, 0, num_bins_internal - 2) + + d_ind = inds - inds_f + w0 = 1.0 - d_ind + w1 = d_ind + + # --- flatten spatial dims + array_f = array.reshape(n_batch, -1) + + # --- accumulate per batch + out = [] + for b in range(n_batch): + a = array_f[b] + + num = torch.bincount(inds_f, weights=a * w0, minlength=num_bins_internal) + torch.bincount( + inds_f + 1, weights=a * w1, minlength=num_bins_internal ) - # # preserve if needed - # if isinstance( - # image, - # ): - # meta = dict(origin=image.origin, sampling=image.sampling, units=image.units) - # name_base = getattr(image, "name", "image") - # im_sub: ImageType = .from_array(im_sub_np, name=f"{name_base} (bg-sub)", **meta) # type: ignore[assignment] - # else: - im_sub = im_sub_np # type: ignore[assignment] - - if return_background_and_mask: - return im_sub, im_bg, mask_bg - return im_sub + den = ( + torch.bincount(inds_f, weights=w0, minlength=num_bins_internal) + + torch.bincount(inds_f + 1, weights=w1, minlength=num_bins_internal) + ).clamp_min(1) + + out.append(num / den) + + array_1d = torch.stack(out, dim=0) + + # ---- truncate to physical bins (key change) + array_1d = array_1d[..., :num_bins] + q_bins_out = q_bins_out[:num_bins] + + if squeeze_output: + array_1d = array_1d[0] + + return q_bins_out, array_1d diff --git a/src/quantem/core/utils/tomography_utils.py b/src/quantem/core/utils/tomography_utils.py new file mode 100644 index 00000000..241687bc --- /dev/null +++ b/src/quantem/core/utils/tomography_utils.py @@ -0,0 +1,316 @@ +from typing import Any, Optional, Tuple + +import numpy as np +import torch +import torch.nn.functional as F +from matplotlib import cm # TODO: Temporary +from numpy.typing import NDArray +from scipy.ndimage import center_of_mass, gaussian_filter, shift +from scipy.special import comb +from tqdm.auto import tqdm + +from quantem.core.utils.imaging_utils import cross_correlation_shift +from quantem.core.visualization import show_2d + +ImageType = NDArray[Any] +BoolArray = NDArray[np.bool_] + + +def _as_array(x: ImageType) -> NDArray[Any]: + return ( + x.array + if isinstance( + x, + ) + else np.asarray(x) + ) + + +def _bernstein_basis_1d(n: int, t: NDArray[Any]) -> NDArray[Any]: + k = np.arange(n + 1, dtype=int) + return ( + comb(n, k)[None, :] * (t[:, None] ** k[None, :]) * ((1.0 - t)[:, None] ** (n - k)[None, :]) + ) + + +def _build_basis_matrix(im_shape: Tuple[int, int], order: Tuple[int, int]) -> NDArray[Any]: + H, W = im_shape + ou, ov = int(order[0]), int(order[1]) + u = np.linspace(0.0, 1.0, H) + v = np.linspace(0.0, 1.0, W) + Bu = _bernstein_basis_1d(ou, u) + Bv = _bernstein_basis_1d(ov, v) + basis_cube = np.einsum("ik,jl->ijkl", Bu, Bv) + return basis_cube.reshape(H * W, (ou + 1) * (ov + 1)) + + +def background_subtract( + image: ImageType, + mask: Optional[BoolArray] = None, + thresh_bg: Optional[float] = None, + order: Tuple[int, int] = (1, 1), + sigma: Optional[float] = None, + num_iter: int = 10, + plot_result: bool = True, + axsize: Tuple[int, int] = (3, 3), + cmap: str = "turbo", + return_background_and_mask: bool = False, + **show_kwargs, +) -> ImageType | Tuple[ImageType, NDArray[Any], BoolArray]: + """ + Background subtraction via bivariate Bernstein polynomial fitting. + + Returns + ------- + - If `return_background_and_mask=False`: ImageType (same as input) + - If `True`: (ImageType, numpy.ndarray, numpy.ndarray[bool]) + where background and mask are always NumPy. + """ + im = _as_array(image).astype(float, copy=True) + if im.ndim != 2: + raise ValueError("`image` must be 2D") + + mask_arr: BoolArray = ( + np.ones_like(im, dtype=bool) if mask is None else np.asarray(mask, dtype=bool) + ) + if mask_arr.shape != im.shape: + raise ValueError("`mask` must match `image` shape") + + order = (int(order[0]), int(order[1])) + A_full = _build_basis_matrix(im.shape, order) + H, W = im.shape + im_flat = im.ravel() + + im_bg = np.zeros_like(im) + thresh_val = np.median(im[mask_arr]) if thresh_bg is None else float(thresh_bg) + + resid = im - im_bg + if sigma and sigma > 0: + resid = gaussian_filter(resid, sigma=sigma, mode="nearest") + mask_bg: BoolArray = (resid < thresh_val) & mask_arr + + for _ in range(int(num_iter)): + idx = mask_bg.ravel() + if not np.any(idx): + idx = mask_arr.ravel() + coefs, *_ = np.linalg.lstsq(A_full[idx, :], im_flat[idx], rcond=None) + im_bg = (A_full @ coefs).reshape(H, W) + + resid = im - im_bg + if sigma and sigma > 0: + resid = gaussian_filter(resid, sigma=sigma, mode="nearest") + + thr = thresh_val if thresh_bg is None else float(thresh_bg) + mask_bg = (resid < thr) & mask_arr + + im_sub_np = im - im_bg + + if plot_result: + vals = im_sub_np[mask_arr] + vals = vals[np.isfinite(vals)] + if vals.size == 0: + vals = np.array([0.0]) + vmin_sub = float(np.min(vals)) + vmax_sub = float(np.max(vals)) + vrange = float(max(abs(vmin_sub), abs(vmax_sub))) or 1e-12 + + bg_disp = (im_bg - np.mean(im_bg)).copy() + bg_disp[~mask_bg] = np.nan + + cmap_base = cm.get_cmap(cmap).with_extremes(bad="black") + cmap_div = "RdBu_r" + + disp = [im - np.mean(im_bg), bg_disp, im_sub_np] + norm = [ + { + "interval_type": "manual", + "stretch_type": "linear", + "vmin": vmin_sub, + "vmax": vmax_sub, + }, + { + "interval_type": "manual", + "stretch_type": "linear", + "vmin": vmin_sub, + "vmax": vmax_sub, + }, + { + "interval_type": "centered", + "stretch_type": "linear", + "vcenter": 0.0, + "half_range": vrange, + }, + ] + + show_2d( + disp, + cmap=[cmap_base, cmap_base, cmap_div], + norm=norm, + cbar=[False, False, True], + title=["Input Image", "Background (fit region)", "Background Subtracted"], + axsize=axsize, + **show_kwargs, + ) + + # # preserve if needed + # if isinstance( + # image, + # ): + # meta = dict(origin=image.origin, sampling=image.sampling, units=image.units) + # name_base = getattr(image, "name", "image") + # im_sub: ImageType = .from_array(im_sub_np, name=f"{name_base} (bg-sub)", **meta) # type: ignore[assignment] + # else: + im_sub = im_sub_np # type: ignore[assignment] + + if return_background_and_mask: + return im_sub, im_bg, mask_bg + return im_sub + + +# --- Tilt Series Processing Utility Functions --- + + +def fourier_binning(img, crop_size): + """ + Crop the img in Fourier space to the specified size. + """ + center = np.array(img.shape) // 2 + + fft_img = np.fft.fftshift(np.fft.fft2(img)) + + cropped_fft = fft_img[ + center[0] - crop_size[0] // 2 : center[0] + crop_size[0] // 2, + center[1] - crop_size[1] // 2 : center[1] + crop_size[1] // 2, + ] + cropped_img = np.fft.ifft2(np.fft.ifftshift(cropped_fft)).real + return cropped_img + + +def cross_correlation_align_stack(ref_img, stack, print_pred=False): + """ + Aligns a stack of images to a reference image using cross-correlation. + + This function assumes the stack does not contain the reference image itself. + + Stack shape should be (N, H, W) where N is the number of images. + """ + + new_images = [] + pred_shifts = [] + + prev_img = ref_img + for img in tqdm(stack): + shift_pred = cross_correlation_shift(prev_img, img) + if print_pred: + print(f"Shift prediction: {shift_pred}") + shifted_image = shift(img, shift=shift_pred, mode="constant", cval=0.0) + + pred_shifts.append(shift_pred) + new_images.append(shifted_image) + + prev_img = shifted_image + + return new_images, pred_shifts + + +def centering_com_alignment(image_stack): + """ + Aligns the image stack to the center of mass of the whole image_stack to the + image center. This is useful for aligning the tilt series to the invariant line. + """ + + aligned_stack = np.zeros_like(image_stack) + h, w = image_stack.shape[1:] + image_center = np.array([h // 2, w // 2]) + + com_reference = np.array(center_of_mass(image_stack.mean(axis=0))) + + for i, img in enumerate(image_stack): + com_img = np.array(center_of_mass(img)) + shift_vec = com_reference - com_img + aligned_stack[i] = shift(img, shift=shift_vec, mode="constant", cval=0.0) + + final_shift = image_center - com_reference + for i in range(aligned_stack.shape[0]): + aligned_stack[i] = shift(aligned_stack[i], shift=final_shift, mode="constant", cval=0.0) + + return aligned_stack + + +def differentiable_shift_2d(image, shift_x, shift_y, sampling_rate): + """ + Shifts a 2D image using grid_sample in a differentiable manner. + + Args: + image: Tensor of shape [H, W] + shift_x: Scalar tensor (dx) for shift in x-direction (in physical units) + shift_y: Scalar tensor (dy) for shift in y-direction (in physical units) + sampling_rate: Scalar value (physical units per pixel) to correctly normalize shifts + + Returns: + Shifted image of shape [H, W] + """ + H, W = image.shape + + # Convert physical shift to pixel shift + shift_x_pixel = shift_x + shift_y_pixel = shift_y + + # Normalize shift for grid_sample (assuming align_corners=True) + normalized_shift_x = shift_x_pixel * 2 / (W - 1) + normalized_shift_y = shift_y_pixel * 2 / (H - 1) + + # Create normalized grid + grid_y, grid_x = torch.meshgrid( + torch.linspace(-1, 1, H, device=image.device), + torch.linspace(-1, 1, W, device=image.device), + indexing="ij", + ) + + grid = torch.stack((grid_x, grid_y), dim=-1).unsqueeze(0) # [1, H, W, 2] + + # Apply shift (ensure it's differentiable) + grid[:, :, :, 0] -= normalized_shift_x + grid[:, :, :, 1] -= normalized_shift_y + + # Add batch and channel dimensions + image = image.unsqueeze(0).unsqueeze(0) # [1, 1, H, W] + + # Sample using grid_sample (fully differentiable) + shifted_image = F.grid_sample( + image, grid, mode="bicubic", padding_mode="zeros", align_corners=True + ) + + return shifted_image.squeeze(0).squeeze(0) # Back to [H, W] + + +# --- TV loss --- + + +def get_TV_loss(tensor, factor=1e-3): + tv_d = torch.pow(tensor[:, :, 1:, :, :] - tensor[:, :, :-1, :, :], 2).sum() + tv_h = torch.pow(tensor[:, :, :, 1:, :] - tensor[:, :, :, :-1, :], 2).sum() + tv_w = torch.pow(tensor[:, :, :, :, 1:] - tensor[:, :, :, :, :-1], 2).sum() + tv_loss = tv_d + tv_h + tv_w + + return tv_loss * factor / (torch.prod(torch.tensor(tensor.shape))) + + +# Circular mask + + +def torch_phase_cross_correlation(im1, im2): + f1 = torch.fft.fft2(im1) + f2 = torch.fft.fft2(im2) + cc = torch.fft.ifft2(f1 * torch.conj(f2)) + cc_abs = torch.abs(cc) + + max_idx = torch.argmax(cc_abs) + shifts = torch.tensor(np.unravel_index(max_idx.item(), im1.shape), device=im1.device).float() + + for i, dim in enumerate(im1.shape): + if shifts[i] > dim // 2: + shifts[i] -= dim + + # return shifts.flip(0) # (dx, dy) + return shifts diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 0a089ab3..004fff7c 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -7,17 +7,16 @@ from quantem.core.io.serialize import load as autoserialize_load from quantem.core.ml.ddp import DDPMixin +from quantem.core.utils.filter import gaussian_filter_2d_stack, gaussian_kernel_1d +from quantem.core.utils.tomography_utils import ( + torch_phase_cross_correlation, +) from quantem.tomography.dataset_models import DatasetModelType from quantem.tomography.logger_tomography import LoggerTomography from quantem.tomography.object_models import ObjectModelType from quantem.tomography.radon.radon import iradon_torch, radon_torch from quantem.tomography.tomography_base import TomographyBase from quantem.tomography.tomography_opt import TomographyOpt -from quantem.tomography.utils import ( - gaussian_filter_2d_stack, - gaussian_kernel_1d, - torch_phase_cross_correlation, -) class Tomography(TomographyOpt, TomographyBase, DDPMixin): diff --git a/src/quantem/tomography/utils.py b/src/quantem/tomography/utils.py index 82c1c9d5..9a009002 100644 --- a/src/quantem/tomography/utils.py +++ b/src/quantem/tomography/utils.py @@ -1,11 +1,5 @@ -import numpy as np import torch import torch.nn.functional as F -from scipy.ndimage import center_of_mass, gaussian_filter, shift -from scipy.stats import norm -from tqdm.auto import tqdm - -from quantem.core.utils.imaging_utils import cross_correlation_shift # --- Projection Operator Utils --- @@ -73,228 +67,3 @@ def transform_slice(mag_slice): rotated_mags = torch.vmap(transform_slice)(mags) return rotated_mags.permute(1, 2, 3, 0) - - -def differentiable_shift_2d(image, shift_x, shift_y, sampling_rate): - """ - Shifts a 2D image using grid_sample in a differentiable manner. - - Args: - image: Tensor of shape [H, W] - shift_x: Scalar tensor (dx) for shift in x-direction (in physical units) - shift_y: Scalar tensor (dy) for shift in y-direction (in physical units) - sampling_rate: Scalar value (physical units per pixel) to correctly normalize shifts - - Returns: - Shifted image of shape [H, W] - """ - H, W = image.shape - - # Convert physical shift to pixel shift - shift_x_pixel = shift_x - shift_y_pixel = shift_y - - # Normalize shift for grid_sample (assuming align_corners=True) - normalized_shift_x = shift_x_pixel * 2 / (W - 1) - normalized_shift_y = shift_y_pixel * 2 / (H - 1) - - # Create normalized grid - grid_y, grid_x = torch.meshgrid( - torch.linspace(-1, 1, H, device=image.device), - torch.linspace(-1, 1, W, device=image.device), - indexing="ij", - ) - - grid = torch.stack((grid_x, grid_y), dim=-1).unsqueeze(0) # [1, H, W, 2] - - # Apply shift (ensure it's differentiable) - grid[:, :, :, 0] -= normalized_shift_x - grid[:, :, :, 1] -= normalized_shift_y - - # Add batch and channel dimensions - image = image.unsqueeze(0).unsqueeze(0) # [1, 1, H, W] - - # Sample using grid_sample (fully differentiable) - shifted_image = F.grid_sample( - image, grid, mode="bicubic", padding_mode="zeros", align_corners=True - ) - - return shifted_image.squeeze(0).squeeze(0) # Back to [H, W] - - -# --- TV loss --- - - -def get_TV_loss(tensor, factor=1e-3): - tv_d = torch.pow(tensor[:, :, 1:, :, :] - tensor[:, :, :-1, :, :], 2).sum() - tv_h = torch.pow(tensor[:, :, :, 1:, :] - tensor[:, :, :, :-1, :], 2).sum() - tv_w = torch.pow(tensor[:, :, :, :, 1:] - tensor[:, :, :, :, :-1], 2).sum() - tv_loss = tv_d + tv_h + tv_w - - return tv_loss * factor / (torch.prod(torch.tensor(tensor.shape))) - - -# --- Gaussian filters --- - - -def gaussian_kernel_1d(sigma: float, num_sigmas: float = 3.0) -> torch.Tensor: - radius = np.ceil(num_sigmas * sigma) - support = torch.arange(-radius, radius + 1, dtype=torch.float) - kernel = torch.distributions.Normal(loc=0, scale=sigma).log_prob(support).exp_() - # Ensure kernel weights sum to 1, so that image brightness is not altered - return kernel.mul_(1 / kernel.sum()) - - -def gaussian_filter_2d( - img: torch.Tensor, sigma: float, kernel_1d: torch.Tensor -) -> torch.Tensor: # Add kernel_1d as an argument - # kernel_1d = gaussian_kernel_1d(sigma) # Create 1D Gaussian kernel - Moved outside function - padding = len(kernel_1d) // 2 # Ensure that image size does not change - img = img.unsqueeze(0).unsqueeze_(0) # Make copy, make 4D for ``conv2d()`` - # Convolve along columns and rows - img = torch.nn.functional.conv2d(img, weight=kernel_1d.view(1, 1, -1, 1), padding=(padding, 0)) - img = torch.nn.functional.conv2d(img, weight=kernel_1d.view(1, 1, 1, -1), padding=(0, padding)) - return img.squeeze_(0).squeeze_(0) # Make 2D again - - -def gaussian_filter_2d_stack(stack: torch.Tensor, kernel_1d: torch.Tensor) -> torch.Tensor: - """ - Apply 2D Gaussian blur to each slice stack[:, i, :] in a vectorized way. - - Args: - stack (torch.Tensor): Tensor of shape (H, N, W) where N is num_sinograms - kernel_1d (torch.Tensor): 1D Gaussian kernel - - Returns: - torch.Tensor: Blurred stack of same shape (H, N, W) - """ - H, N, W = stack.shape - padding = len(kernel_1d) // 2 - - # Reshape to (N, 1, H, W) for conv2d - stack_reshaped = stack.permute(1, 0, 2).unsqueeze(1) # (N, 1, H, W) - - # Apply separable conv2d: vertical then horizontal - out = torch.nn.functional.conv2d( - stack_reshaped, kernel_1d.view(1, 1, -1, 1), padding=(padding, 0) - ) - out = torch.nn.functional.conv2d(out, kernel_1d.view(1, 1, 1, -1), padding=(0, padding)) - - # Restore shape to (H, N, W) - return out.squeeze(1).permute(1, 0, 2) - - -# Circular mask - - -def torch_phase_cross_correlation(im1, im2): - f1 = torch.fft.fft2(im1) - f2 = torch.fft.fft2(im2) - cc = torch.fft.ifft2(f1 * torch.conj(f2)) - cc_abs = torch.abs(cc) - - max_idx = torch.argmax(cc_abs) - shifts = torch.tensor(np.unravel_index(max_idx.item(), im1.shape), device=im1.device).float() - - for i, dim in enumerate(im1.shape): - if shifts[i] > dim // 2: - shifts[i] -= dim - - # return shifts.flip(0) # (dx, dy) - return shifts - - -# --- Tilt Series Processing Utility Functions --- - - -def fourier_cropping(img, crop_size): - """ - Crop the img in Fourier space to the specified size. - """ - center = np.array(img.shape) // 2 - - fft_img = np.fft.fftshift(np.fft.fft2(img)) - - cropped_fft = fft_img[ - center[0] - crop_size[0] // 2 : center[0] + crop_size[0] // 2, - center[1] - crop_size[1] // 2 : center[1] + crop_size[1] // 2, - ] - cropped_img = np.fft.ifft2(np.fft.ifftshift(cropped_fft)).real - return cropped_img - - -def estimate_background( - img, - num_iterations=10, - cutoff=3, - smoothing_sigma=1.0, -): - """ - Estimate the background of the image using a Gaussian filter. - """ - if smoothing_sigma > 0: - img = gaussian_filter(img, sigma=smoothing_sigma) - pixel_vals = img.ravel() - - for i in range(num_iterations): - mu, std = norm.fit(pixel_vals) - - # Set cutoff threshold (e.g., 3 standard deviations) - lower = mu - cutoff * std - upper = mu + cutoff * std - - # Mask pixel values within ±3σ - pixel_vals = pixel_vals[(pixel_vals >= lower) & (pixel_vals <= upper)] - - return mu - - -def cross_correlation_align_stack(ref_img, stack, print_pred=False): - """ - Aligns a stack of images to a reference image using cross-correlation. - - This function assumes the stack does not contain the reference image itself. - - Stack shape should be (N, H, W) where N is the number of images. - """ - - new_images = [] - pred_shifts = [] - - prev_img = ref_img - for img in tqdm(stack): - shift_pred = cross_correlation_shift(prev_img, img) - if print_pred: - print(f"Shift prediction: {shift_pred}") - shifted_image = shift(img, shift=shift_pred, mode="constant", cval=0.0) - - pred_shifts.append(shift_pred) - new_images.append(shifted_image) - - prev_img = shifted_image - - return new_images, pred_shifts - - -def centering_com_alignment(image_stack): - """ - Aligns the image stack to the center of mass of the whole image_stack to the - image center. This is useful for aligning the tilt series to the invariant line. - """ - - aligned_stack = np.zeros_like(image_stack) - h, w = image_stack.shape[1:] - image_center = np.array([h // 2, w // 2]) - - com_reference = np.array(center_of_mass(image_stack.mean(axis=0))) - - for i, img in enumerate(image_stack): - com_img = np.array(center_of_mass(img)) - shift_vec = com_reference - com_img - aligned_stack[i] = shift(img, shift=shift_vec, mode="constant", cval=0.0) - - final_shift = image_center - com_reference - for i in range(aligned_stack.shape[0]): - aligned_stack[i] = shift(aligned_stack[i], shift=final_shift, mode="constant", cval=0.0) - - return aligned_stack From c2b7d264297ff7f7596e350c1b315eb04acf2862 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 24 Feb 2026 13:49:00 -0800 Subject: [PATCH 100/335] Made save_volume more explicit in top-level Tomography.py --- src/quantem/tomography/tomography.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 004fff7c..3d26b63f 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -1,3 +1,4 @@ +import os from typing import Literal, Optional, Self import numpy as np @@ -269,9 +270,15 @@ def reconstruct( # --- Helper Functions --- - def save_volume(self, path: str = "recon_volume.npz"): - # TODO: Temporary, need to talk to Arthur what the correct way of saving results is. + def save_volume(self, path: str = "recon_volume.npz", overwrite: bool = False): + """ + Saves volume to a numpy array file. Does not save the full Tomography object. + """ if self.global_rank == 0: + if not overwrite and os.path.exists(path): + raise FileExistsError( + f"File {path} already exists. Use overwrite=True to overwrite." + ) print(f"Saving volume to {path}") np.savez(path, volume=self.obj_model.obj.detach().cpu().numpy()) From 2b7848dd407e43d8c3812cddeedc0652be67cb83 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 24 Feb 2026 14:06:18 -0800 Subject: [PATCH 101/335] Logging now logs all channels if there are any additional ones. In the .to calls in dataset_models.py, since the PixDataset requires the tilt series to be in GPU memory, while the INR needs it to be in CPU for dataloader stuff I've just made .to as an abstract method for the base Dataset. --- src/quantem/core/ml/inr.py | 5 +++- src/quantem/tomography/dataset_models.py | 30 ++++++++++++--------- src/quantem/tomography/logger_tomography.py | 14 +++++++--- src/quantem/tomography/object_models.py | 3 +-- src/quantem/tomography/tomography.py | 1 - 5 files changed, 34 insertions(+), 19 deletions(-) diff --git a/src/quantem/core/ml/inr.py b/src/quantem/core/ml/inr.py index 46447660..d2ea24e8 100644 --- a/src/quantem/core/ml/inr.py +++ b/src/quantem/core/ml/inr.py @@ -111,9 +111,12 @@ def _build(self) -> None: self.net = nn.Sequential(*net_list) if self.winner_initialization: - if isinstance(self.winner_initialization, int): + if type(self.winner_initialization) is int: rng = torch.Generator() rng.manual_seed(self.winner_initialization) + else: + rng = torch.Generator() + rng.manual_seed(42) with torch.no_grad(): self.net[0].linear.weight += ( # pyright: ignore[reportAttributeAccessIssue] torch.randn_like(self.net[0].linear.weight) * 5 / self.first_omega_0 # type:ignore diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index 4a1f9722..ec049279 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -221,18 +221,7 @@ def to(self, device: str): Moves the dataset to the device, and also insantiates the aux params to the device. """ - self.tilt_stack = self.tilt_stack.to(device) - self.tilt_angles = self.tilt_angles.to(device) - - self._z1_params = nn.Parameter(self._z1_angles.to(device)) - self._z3_params = nn.Parameter(self._z3_angles.to(device)) - self._shifts_params = nn.Parameter(self._shifts.to(device)) - - self._z1_ref = self._z1_ref.to(device) - self._z3_ref = self._z3_ref.to(device) - self._shifts_ref = self._shifts_ref.to(device) - - self.device = device + raise NotImplementedError("This method should be implemented in subclasses.") class TomographyPixDataset(TomographyDatasetBase): @@ -273,6 +262,23 @@ def forward( pixel_loc=None, ) + def to(self, device: str): + """ + Moves the tilt stack and tilt_angles to the device, along with other nn.Parameters to the device. + """ + self.tilt_stack = self.tilt_stack.to(device) + self.tilt_angles = self.tilt_angles.to(device) + + self._z1_params = nn.Parameter(self._z1_angles.to(device)) + self._z3_params = nn.Parameter(self._z3_angles.to(device)) + self._shifts_params = nn.Parameter(self._shifts.to(device)) + + self._z1_ref = self._z1_ref.to(device) + self._z3_ref = self._z3_ref.to(device) + self._shifts_ref = self._shifts_ref.to(device) + + self.device = device + class TomographyINRDataset(TomographyDatasetBase, Dataset): """ diff --git a/src/quantem/tomography/logger_tomography.py b/src/quantem/tomography/logger_tomography.py index 4d850e18..c6726dac 100644 --- a/src/quantem/tomography/logger_tomography.py +++ b/src/quantem/tomography/logger_tomography.py @@ -57,9 +57,17 @@ def log_iter_images( shifts_vals = dataset_model.shifts_params.detach().cpu().numpy() print("Logging volume...") - self.log_image("volume/sum_z", pred_volume.sum(axis=0), iter, logger_cmap) - self.log_image("volume/sum_y", pred_volume.sum(axis=1), iter, logger_cmap) - self.log_image("volume/sum_x", pred_volume.sum(axis=2), iter, logger_cmap) + + for channel in range(pred_volume.shape[0]): + self.log_image( + f"volume/sum_z_{channel}", pred_volume[channel].sum(axis=0), iter, logger_cmap + ) + self.log_image( + f"volume/sum_y_{channel}", pred_volume[channel].sum(axis=1), iter, logger_cmap + ) + self.log_image( + f"volume/sum_x_{channel}", pred_volume[channel].sum(axis=2), iter, logger_cmap + ) # Plotting z1 and z3 vals print("Plotting z1 and z3 angles...") diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 33b40b2a..34414277 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -666,9 +666,8 @@ def get_tv_loss( return tv_loss def to(self, device: str): - # self._model = self._model.to(device) self.device = device - self._obj = self._obj.to(self.device) + # self._obj = self._obj.to(self.device) self.reconnect_optimizer_to_parameters() diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 3d26b63f..e8aded8d 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -67,7 +67,6 @@ def reconstruct( # Check device consistency self.obj_model.to(self.device) - self.dset.to(self.device) if profiling_mode: if self.global_rank == 0: From 384ce2fe39a36355790526e85906db8c86e92bba Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 24 Feb 2026 14:06:38 -0800 Subject: [PATCH 102/335] Added abstract method decorator --- src/quantem/tomography/dataset_models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index ec049279..257b2c88 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -216,6 +216,7 @@ def device(self, device: str): self._device = device # --- Helper Functions --- + @abstractmethod def to(self, device: str): """ Moves the dataset to the device, and also insantiates the aux params to the device. From 446a6da05edbbed5b00de6339236e0073bbc4c3f Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 24 Feb 2026 17:13:36 -0800 Subject: [PATCH 103/335] core/ml/loss_functions; Changed everything to modules check if this works at Ptycho --- src/quantem/core/ml/loss_functions.py | 200 +++++++----------- .../diffractive_imaging/object_models.py | 4 +- .../diffractive_imaging/probe_models.py | 4 +- src/quantem/tomography/object_models.py | 4 +- 4 files changed, 82 insertions(+), 130 deletions(-) diff --git a/src/quantem/core/ml/loss_functions.py b/src/quantem/core/ml/loss_functions.py index 1ffc3006..c99b6494 100644 --- a/src/quantem/core/ml/loss_functions.py +++ b/src/quantem/core/ml/loss_functions.py @@ -11,139 +11,91 @@ import torch -def get_loss_function(name: str | Callable, dtype: torch.dtype) -> Callable: - """Get a loss function by name or return callable if provided. - - Parameters - ---------- - name : str or Callable - Loss function name or callable function. - dtype : torch.dtype - Data type (used to determine complex vs real loss functions). - - Returns - ------- - Callable - Loss function. - - Raises - ------ - ValueError - If loss function name is unknown for the given dtype. - """ - if isinstance(name, Callable): +def get_loss_module(name: str | nn.Module | Callable, dtype: torch.dtype) -> nn.Module: + """Return a loss *module* by name, or wrap/return what was provided.""" + if isinstance(name, nn.Module): return name - else: - name = name.lower() + + if callable(name) and not isinstance(name, str): + # Wrap a bare callable into an nn.Module + class _CallableLoss(nn.Module): + def __init__(self, fn: Callable): + super().__init__() + self.fn = fn + + def forward(self, pred: torch.Tensor, target: torch.Tensor) -> torch.Tensor: + return self.fn(pred, target) + + return _CallableLoss(name) + + loss_name = str(name).lower() + if dtype.is_complex: - if name in ["l2", "complex_l2"]: - return complex_l2 - elif name in ["complex_cartesian_l2"]: - return complex_cartesian_l2 - elif name in ["amp_phase_l2"]: - return amp_phase_l2 - elif name in ["combined_l2"]: - return combined_l2 - else: - raise ValueError(f"Unknown loss function for complex dtype: {name}") - else: - if name in ["l2"]: - return torch.nn.functional.mse_loss - elif name in ["l1"]: - return torch.nn.functional.l1_loss - else: - raise ValueError(f"Unknown loss function for real dtype: {name}") - - -def complex_l2(pred: torch.Tensor, target: torch.Tensor) -> torch.Tensor: - """Compute L2 loss for complex tensors (separate real and imaginary parts). - - Parameters - ---------- - pred : torch.Tensor - Predicted complex tensor. - target : torch.Tensor - Target complex tensor. - - Returns - ------- - torch.Tensor - L2 loss value. - """ - real_l2 = torch.mean((pred.real - target.real) ** 2) - imag_l2 = torch.mean((pred.imag - target.imag) ** 2) - return (real_l2 + imag_l2) / 2 + if loss_name in {"l2", "complex_l2"}: + return ComplexL2Loss() + if loss_name in {"complex_cartesian_l2"}: + return ComplexCartesianL2Loss() + if loss_name in {"amp_phase_l2"}: + return AmpPhaseL2Loss() + if loss_name in {"combined_l2"}: + return CombinedL2Loss() + raise ValueError(f"Unknown loss module for complex dtype: {loss_name}") + + # real dtype + if loss_name in {"l2"}: + return nn.MSELoss() + if loss_name in {"l1"}: + return nn.L1Loss() + raise ValueError(f"Unknown loss module for real dtype: {loss_name}") + + +class ComplexL2Loss(nn.Module): + """L2 loss for complex tensors (separate real/imag, then average).""" + def forward(self, pred: torch.Tensor, target: torch.Tensor) -> torch.Tensor: + real_l2 = torch.mean((pred.real - target.real) ** 2) + imag_l2 = torch.mean((pred.imag - target.imag) ** 2) + return (real_l2 + imag_l2) / 2 -def complex_cartesian_l2(pred: torch.Tensor, target: torch.Tensor) -> torch.Tensor: - """Compute L2 loss for complex tensors in Cartesian coordinates. - Parameters - ---------- - pred : torch.Tensor - Predicted complex tensor. - target : torch.Tensor - Target complex tensor. +class ComplexCartesianL2Loss(nn.Module): + """L2 loss for complex tensors in Cartesian form: E[(Δre^2 + Δim^2)].""" - Returns - ------- - torch.Tensor - L2 loss value. - """ - real_dif = pred.real - target.real - imag_dif = pred.imag - target.imag - loss = torch.mean(real_dif**2 + imag_dif**2) - return loss - - -def amp_phase_l2(pred: torch.Tensor, target: torch.Tensor) -> torch.Tensor: - """Compute L2 loss for complex tensors in amplitude-phase representation. - - Parameters - ---------- - pred : torch.Tensor - Predicted complex tensor. - target : torch.Tensor - Target complex tensor. - - Returns - ------- - torch.Tensor - L2 loss value (amplitude + phase). - """ - amp_l2 = ((target.abs() - pred.abs()) ** 2).mean() - phase_dif = torch.abs(target.angle() - pred.angle()) - phase_dif = torch.min(phase_dif, 2 * torch.pi - phase_dif) # phase wrapping - phase_l2 = torch.mean(phase_dif**2) - return amp_l2 + phase_l2 - - -def combined_l2(pred: torch.Tensor, target: torch.Tensor, alpha: float = 0.7) -> torch.Tensor: - """Combined L2 loss: weighted sum of amplitude-phase and complex L2 losses. - - Parameters - ---------- - pred : torch.Tensor - Predicted complex tensor. - target : torch.Tensor - Target complex tensor. - alpha : float, optional - Weight for amplitude-phase loss. Larger alpha gives more weight to - amp/phase, smaller alpha gives more weight to real/imag, by default 0.7 - - Returns - ------- - torch.Tensor - Combined L2 loss value. - - different alpha values can affect stability of training. + def forward(self, pred: torch.Tensor, target: torch.Tensor) -> torch.Tensor: + real_dif = pred.real - target.real + imag_dif = pred.imag - target.imag + return torch.mean(real_dif**2 + imag_dif**2) + + +class AmpPhaseL2Loss(nn.Module): + """L2 loss on amplitude + wrapped phase.""" + + def forward(self, pred: torch.Tensor, target: torch.Tensor) -> torch.Tensor: + amp_l2 = ((target.abs() - pred.abs()) ** 2).mean() + + phase_dif = torch.abs(target.angle() - pred.angle()) + phase_dif = torch.min(phase_dif, 2 * torch.pi - phase_dif) # wrap to [0, pi] + phase_l2 = torch.mean(phase_dif**2) + + return amp_l2 + phase_l2 + + +class CombinedL2Loss(nn.Module): + """Weighted sum of AmpPhaseL2 and ComplexL2. + + loss = alpha * amp_phase + (1 - alpha) * complex_l2 """ - comp_l2 = complex_l2(pred, target) - amp_ph_l2 = amp_phase_l2(pred, target) - return alpha * amp_ph_l2 + (1 - alpha) * comp_l2 + def __init__(self, alpha: float = 0.7): + super().__init__() + self.alpha = float(alpha) + self.complex_l2 = ComplexL2Loss() + self.amp_phase_l2 = AmpPhaseL2Loss() -# TODO: Better loss function implementation? More torch-like. + def forward(self, pred: torch.Tensor, target: torch.Tensor) -> torch.Tensor: + comp_l2 = self.complex_l2(pred, target) + amp_ph_l2 = self.amp_phase_l2(pred, target) + return self.alpha * amp_ph_l2 + (1 - self.alpha) * comp_l2 class MSELogMSELoss(nn.Module): diff --git a/src/quantem/diffractive_imaging/object_models.py b/src/quantem/diffractive_imaging/object_models.py index 0c66629e..9623e7b3 100644 --- a/src/quantem/diffractive_imaging/object_models.py +++ b/src/quantem/diffractive_imaging/object_models.py @@ -13,7 +13,7 @@ from quantem.core import config from quantem.core.io.serialize import AutoSerialize from quantem.core.ml.blocks import reset_weights -from quantem.core.ml.loss_functions import get_loss_function +from quantem.core.ml.loss_functions import get_loss_module from quantem.core.ml.optimizer_mixin import OptimizerMixin from quantem.core.utils.rng import RNGMixin from quantem.core.utils.validators import ( @@ -1085,7 +1085,7 @@ def pretrain( "No pretrain target set. Provide pretrain_target or set it beforehand." ) - loss_fn = get_loss_function(loss_fn, self.dtype) + loss_fn = get_loss_module(loss_fn, self.dtype) self._pretrain( num_iters=num_iters, loss_fn=loss_fn, diff --git a/src/quantem/diffractive_imaging/probe_models.py b/src/quantem/diffractive_imaging/probe_models.py index 77dbb1c1..e9027620 100644 --- a/src/quantem/diffractive_imaging/probe_models.py +++ b/src/quantem/diffractive_imaging/probe_models.py @@ -14,7 +14,7 @@ from quantem.core.datastructures import Dataset2d, Dataset4dstem from quantem.core.io.serialize import AutoSerialize from quantem.core.ml.blocks import reset_weights -from quantem.core.ml.loss_functions import get_loss_function +from quantem.core.ml.loss_functions import get_loss_module from quantem.core.ml.optimizer_mixin import OptimizerMixin from quantem.core.utils.rng import RNGMixin from quantem.core.utils.utils import electron_wavelength_angstrom, to_numpy @@ -1391,7 +1391,7 @@ def pretrain( elif self.pretrain_target is None: self.pretrain_target = self._initial_probe.clone().detach() - loss_fn = get_loss_function(loss_fn, self.dtype) + loss_fn = get_loss_module(loss_fn, self.dtype) self._pretrain( num_iters=num_iters, loss_fn=loss_fn, diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 34414277..f71964f1 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -11,7 +11,7 @@ from quantem.core.io.serialize import AutoSerialize from quantem.core.ml.constraints import BaseConstraints, Constraints from quantem.core.ml.ddp import DDPMixin -from quantem.core.ml.loss_functions import get_loss_function +from quantem.core.ml.loss_functions import get_loss_module from quantem.core.ml.optimizer_mixin import OptimizerMixin from quantem.core.utils.rng import RNGMixin from quantem.tomography.dataset_models import TomographyINRPretrainDataset @@ -502,7 +502,7 @@ def pretrain( if reset: self.reset() - loss_fn = get_loss_function(loss_fn, self.dtype) + loss_fn = get_loss_module(loss_fn, self.dtype) self._pretrain( num_iters=num_iters, From c65b63baf7e724a0c62213b65774480b4922fbdf Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Wed, 25 Feb 2026 23:24:46 -0800 Subject: [PATCH 104/335] dataset_models.py bug fixes. Distribute model is not working with cudatoolkit/13.0 --- src/quantem/core/ml/ddp.py | 1 - src/quantem/tomography/dataset_models.py | 1 - 2 files changed, 2 deletions(-) diff --git a/src/quantem/core/ml/ddp.py b/src/quantem/core/ml/ddp.py index 52bed46a..0e60e398 100644 --- a/src/quantem/core/ml/ddp.py +++ b/src/quantem/core/ml/ddp.py @@ -31,7 +31,6 @@ def setup_distributed(self, device: str | torch.device | None = None): self.world_size = dist.get_world_size() self.global_rank = dist.get_rank() self.local_rank = int(os.environ["LOCAL_RANK"]) - torch.cuda.set_device(self.local_rank) device = torch.device("cuda", self.local_rank) else: diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index 257b2c88..ee7d74f7 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -475,7 +475,6 @@ def __len__( return self.tilt_stack.shape[0] * N * N def to(self, device: str): - super().to(device) self._z1_params = nn.Parameter(self._z1_angles.to(device)) self._z3_params = nn.Parameter(self._z3_angles.to(device)) self._shifts_params = nn.Parameter(self._shifts.to(device)) From 093ffed16e22ec0d0eb05cd63fb9701fa300b54d Mon Sep 17 00:00:00 2001 From: cophus Date: Sun, 1 Mar 2026 10:46:23 -0800 Subject: [PATCH 105/335] New Vector class --- src/quantem/core/datastructures/vector.py | 1550 ++++++++------------- tests/datastructures/test_vector.py | 569 +++----- 2 files changed, 794 insertions(+), 1325 deletions(-) diff --git a/src/quantem/core/datastructures/vector.py b/src/quantem/core/datastructures/vector.py index 9bc513a4..4717fe64 100644 --- a/src/quantem/core/datastructures/vector.py +++ b/src/quantem/core/datastructures/vector.py @@ -1,1026 +1,694 @@ -from typing import ( - Any, - List, - Optional, - Tuple, - Union, - cast, - overload, -) +from __future__ import annotations + +import copy +from dataclasses import dataclass +from typing import Any, Iterable, Sequence, overload import numpy as np -from numpy.typing import ArrayLike, NDArray +from numpy.typing import NDArray from quantem.core.io.serialize import AutoSerialize from quantem.core.utils.validators import ( validate_fields, validate_num_fields, validate_shape, - validate_vector_data, - validate_vector_data_for_inference, validate_vector_units, ) +@dataclass +class _VectorState: + shape: tuple[int, ...] + data: NDArray[np.object_] + fields: list[str] + units: list[str] + name: str + metadata: dict[str, Any] + + class Vector(AutoSerialize): - """ - A class for holding vector data with ragged array lengths. This class supports any number of fixed dimensions - (indexed first) followed by a ragged numpy array that can have any number of entries (rows) and columns (fields). - Inherits from AutoSerialize for serialization support. - - Basic Usage: - ----------- - # Create a 2D vector with shape=(4, 3) and 3 named fields - v = Vector.from_shape(shape=(4, 3), fields=['field0', 'field1', 'field2']) - - # Alternative creation with num_fields instead of fields - v = Vector.from_shape(shape=(4, 3), num_fields=3) # Fields will be named field_0, field_1, field_2 - - # Create with custom name and units - v = Vector.from_shape( - shape=(4, 3), - fields=['field0', 'field1', 'field2'], - name='my_vector', - units=['unit0', 'unit1', 'unit2'], - ) - - # Access data at specific indices - data = v[0, 1] # Returns numpy array at position (0,1) - - # Set data at specific indices - v[0, 1] = np.array([[1.0, 2.0, 3.0]]) # Must match num_fields - - # Create a deep copy - v_copy = v.copy() - - Example usage of from_data: - ----------------------------------- - data = [ - np.array([[1, 2], [3, 4]]), - np.array([[5, 6], [7, 8], [9, 10]]) - ] - v = Vector.from_data( - data, - fields=['x', 'y'], - name='my_ragged_vector', - units=['m', 'm'] - ) - - # Or using lists instead of numpy arrays: - data = [ - [[1, 2], [3, 4]], - [[5, 6], [7, 8], [9, 10]], - ] - v = Vector.from_data( - data, - fields=['x', 'y'], - name='my_ragged_vector', - units=['m', 'm'] - ) - - Field Operations: - ---------------- - # Access a specific field - field_data = v['field0'] # Returns a FieldView object - - # Perform operations on a field - v['field0'] += 16 # Add 16 to all field0 values - - # Apply a function to a field - v['field2'] = lambda x: x * 2 # Double all field2 values - - # Get flattened field data - field_flat = v['field0'].flatten() # Returns 1D numpy array - - # Set field data from flattened array - v['field2'].set_flattened(new_values) # Must match total length - - Advanced Operations: - ------------------- - # Complex field calculations - scale = v['field0'].flatten() / (v['field0'].flatten()**2 + v['field1'].flatten()**2) - v['field2'].set_flattened(v['field2'].flatten() * scale) - - # Slicing and assignment - v[2:4, 1] = v[1:3, 1] # Copy data from one region to another - - # Boolean indexing - mask = v['field0'].flatten() > 0 - v['field2'].set_flattened(v['field2'].flatten() * mask) - - # Field management - v.add_fields(('field3', 'field4', 'field5')) # Add new fields - v.remove_fields(('field3', 'field4', 'field5')) # Remove fields - - Direct Data Access: - ------------------ - # Get data with integer indexing - data = v.get_data(0, 1) # Returns numpy array at (0,1) - - # Get data with slice indexing - data = v.get_data(slice(0, 2), 1) # Returns list of arrays for rows 0-1 at column 1 - - # Set data with integer indexing - v.set_data(np.array([[1.0, 2.0, 3.0]]), 0, 1) # Set data at (0,1) - - # Set data with slice indexing - v.set_data([np.array([[1.0, 2.0, 3.0]]), np.array([[4.0, 5.0, 6.0]])], - slice(0, 2), 1) # Set data for rows 0-1 at column 1 - - Notes: - ----- - - All numpy arrays stored in the vector must have the same number of columns (fields) - - Field names must be unique - - Slicing operations return new Vector instances - - Field operations are performed in-place - - Units are stored for each field and can be accessed via the units attribute - - The name attribute can be used to identify the vector in a larger context - """ + """Ragged cell data on a fixed grid. - _token = object() + A ``Vector`` has fixed grid dimensions (``shape``). Each fixed-grid cell stores a + NumPy array with shape ``(n_rows, num_fields)``. Selections always return new + ``Vector`` views over the same backing store, while raw NumPy extraction is explicit + via ``.array`` and ``.flatten()``. + """ def __init__( self, - shape: Tuple[int, ...], - fields: List[str], - units: List[str], - name: str, - metadata: dict = {}, - _token: object | None = None, + shape: tuple[int, ...], + fields: Sequence[str], + units: Sequence[str] | None = None, + name: str | None = None, + metadata: dict[str, Any] | None = None, ) -> None: - if _token is not self._token: - raise RuntimeError("Use Vector.from_shape() or Vector.from_data() to instantiate.") + validated_shape = validate_shape(shape) + validated_fields = validate_fields(list(fields)) + validated_units = validate_vector_units(list(units) if units is not None else None, len(validated_fields)) + self._state = _VectorState( + shape=validated_shape, + data=_empty_storage(validated_shape, len(validated_fields)), + fields=list(validated_fields), + units=list(validated_units), + name=name or f"{len(validated_shape)}d ragged array", + metadata=dict(metadata or {}), + ) + self._coords = _root_coords(validated_shape) + self._field_names = tuple(validated_fields) - self.shape = shape - self.fields = fields - self.units = units - self.name = name - self._data = nested_list(self.shape, fill=None) - self._metadata = metadata + @classmethod + def _from_state( + cls, + state: _VectorState, + coords: NDArray[np.object_], + field_names: Sequence[str], + ) -> "Vector": + obj = cls.__new__(cls) + obj._state = state + obj._coords = coords + obj._field_names = tuple(field_names) + return obj @classmethod def from_shape( cls, - shape: Tuple[int, ...], - num_fields: Optional[int] = None, - fields: Optional[List[str]] = None, - units: Optional[List[str]] = None, - name: Optional[str] = None, + shape: tuple[int, ...], + num_fields: int | None = None, + fields: Sequence[str] | None = None, + units: Sequence[str] | None = None, + name: str | None = None, ) -> "Vector": - """ - Factory method to create a Vector with the specified shape and fields. - - Parameters - ---------- - shape : Tuple[int, ...] - The shape of the vector (dimensions) - num_fields : Optional[int] - Number of fields in the vector - name : Optional[str] - Name of the vector - fields : Optional[List[str]] - List of field names - units : Optional[List[str]] - List of units for each field - - Returns - ------- - Vector - A new Vector instance - """ - validated_shape = validate_shape(shape) - ndim = len(validated_shape) - if fields is not None: - validated_fields = validate_fields(fields) - validated_num_fields = len(validated_fields) - if num_fields is not None and validated_num_fields != num_fields: + validated_fields = validate_fields(list(fields)) + if num_fields is not None and len(validated_fields) != num_fields: raise ValueError( - f"num_fields ({num_fields}) does not match length of fields ({validated_num_fields})" + f"num_fields ({num_fields}) does not match length of fields ({len(validated_fields)})" ) elif num_fields is not None: - validated_num_fields = validate_num_fields(num_fields) - validated_fields = [f"field_{i}" for i in range(validated_num_fields)] + validated_count = validate_num_fields(num_fields) + validated_fields = [f"field_{i}" for i in range(validated_count)] else: raise ValueError("Must specify either 'fields' or 'num_fields'.") - validated_units = validate_vector_units(units, validated_num_fields) - name = name or f"{ndim}d ragged array" - return cls( - shape=validated_shape, + shape=shape, fields=validated_fields, - units=validated_units, + units=units, name=name, - _token=cls._token, ) @classmethod def from_data( cls, - data: List[Any], - num_fields: Optional[int] = None, - fields: Optional[List[str]] = None, - units: Optional[List[str]] = None, - name: Optional[str] = None, + data: list[Any], + num_fields: int | None = None, + fields: Sequence[str] | None = None, + units: Sequence[str] | None = None, + name: str | None = None, ) -> "Vector": - """ - Factory method to create a Vector from a list of - ragged lists or ragged numpy arrays. - - Parameters - ---------- - data : List[Any] - A list of ragged lists containing the vector data. - Each element should be a numpy array with shape (n, num_fields). - num_fields : Optional[int] - Number of fields in the vector. If not provided, it will be inferred from the data. - fields : Optional[List[str]] - List of field names - units : Optional[List[str]] - List of units for each field - name : Optional[str] - Name of the vector - - Returns - ------- - Vector - A new Vector instance with the provided data - - Raises - ------ - ValueError - If the data structure is invalid or inconsistent - TypeError - If the data contains invalid types - """ - inferred_shape, inferred_num_fields = validate_vector_data_for_inference(data) - - final_num_fields = num_fields or inferred_num_fields - if num_fields is not None and num_fields != inferred_num_fields: - raise ValueError( - f"Provided num_fields ({num_fields}) does not match inferred ({inferred_num_fields})." - ) + inferred_shape, normalized_cells = _normalize_nested_data(data) + inferred_num_fields = normalized_cells[0].shape[1] if normalized_cells else 0 - vector = cls.from_shape( - shape=inferred_shape, - num_fields=final_num_fields, - fields=fields, - units=units, - name=name, - ) - - # Now fully validate and set the data - vector.data = data - return vector - - def get_data( - self, *indices: Union[int, slice, List[int], np.ndarray[Any, np.dtype[Any]]] - ) -> Union[NDArray, List[NDArray]]: - """ - Get data at specified indices. - - Parameters: - ----------- - *indices : Union[int, slice, List[int], np.ndarray] - Indices to access. Must match the number of dimensions in the vector. - Supports fancy indexing with lists or numpy arrays. - - Returns: - -------- - numpy.ndarray or list - The data at the specified indices. - - Raises: - ------- - IndexError - If indices are out of bounds. - ValueError - If the number of indices does not match the vector dimensions. - """ - if len(indices) != len(self._shape): - raise ValueError(f"Expected {len(self._shape)} indices, got {len(indices)}") - - # Handle fancy indexing and slicing - def get_indices(dim_idx: Any, dim_size: int) -> np.ndarray: - if isinstance(dim_idx, slice): - start, stop, step = dim_idx.indices(dim_size) - return np.arange(start, stop, step) - elif isinstance(dim_idx, (np.ndarray, list)): - idx = np.asarray(dim_idx) - if np.any((idx < 0) | (idx >= dim_size)): - raise IndexError(f"Index out of bounds for axis with size {dim_size}") - return idx - elif isinstance(dim_idx, (int, np.integer)): - if dim_idx < 0 or dim_idx >= dim_size: - raise IndexError( - f"Index {dim_idx} out of bounds for axis with size {dim_size}" - ) - return np.array([dim_idx]) - return np.arange(dim_size) - - # Get indices for each dimension - indices_arrays = [get_indices(i, s) for i, s in zip(indices, self._shape)] - - # If all indices are single integers, return a single array - if all(len(i) == 1 for i in indices_arrays): - ref = self._data - for idx in (i[0] for i in indices_arrays): - ref = ref[idx] - return ref - - # Create result structure for fancy indexing - result = [] - for idx in np.ndindex(*[len(i) for i in indices_arrays]): - src_idx = tuple(ind[i] for ind, i in zip(indices_arrays, idx)) - result.append(self._data[src_idx[0]][src_idx[1]]) - - return result - - def set_data( - self, - value: Union[NDArray, List[NDArray]], - *indices: Union[int, slice, List[int], np.ndarray[Any, np.dtype[Any]]], - ) -> None: - """ - Set data at specified indices. - - Parameters - ---------- - value : Union[NDArray, List[NDArray]] - The numpy array(s) to set at the specified indices. Must have shape (_, num_fields). - For fancy indexing, can be a list of arrays. - *indices : Union[int, slice, List[int], np.ndarray] - Indices to set data at. Must match the number of dimensions in the vector. - Supports fancy indexing with lists or numpy arrays. - - Raises - ------ - IndexError - If indices are out of bounds. - ValueError - If the number of indices does not match the vector dimensions, - or if the value shape doesn't match the expected shape. - TypeError - If the value is not a numpy array or list of numpy arrays. - """ - if len(indices) != len(self._shape): - raise ValueError(f"Expected {len(self._shape)} indices, got {len(indices)}") - - # Handle fancy indexing and slicing - def get_indices(dim_idx: Any, dim_size: int) -> np.ndarray: - if isinstance(dim_idx, slice): - start, stop, step = dim_idx.indices(dim_size) - return np.arange(start, stop, step) - elif isinstance(dim_idx, (np.ndarray, list)): - idx = np.asarray(dim_idx) - if np.any((idx < 0) | (idx >= dim_size)): - raise IndexError(f"Index out of bounds for axis with size {dim_size}") - return idx - elif isinstance(dim_idx, (int, np.integer)): - if dim_idx < 0 or dim_idx >= dim_size: - raise IndexError( - f"Index {dim_idx} out of bounds for axis with size {dim_size}" - ) - return np.array([dim_idx]) - return np.arange(dim_size) - - # Get indices for each dimension - indices_arrays = [get_indices(i, s) for i, s in zip(indices, self._shape)] - - # If all indices are single integers, handle as single value - if all(len(i) == 1 for i in indices_arrays): - if not isinstance(value, np.ndarray): - raise TypeError(f"Value must be a numpy array, got {type(value).__name__}") - if value.ndim != 2 or value.shape[1] != self.num_fields: + if fields is not None: + validated_fields = validate_fields(list(fields)) + if len(validated_fields) != inferred_num_fields: raise ValueError( - f"Expected a numpy array with shape (_, {self.num_fields}), got {value.shape}" + f"num_fields ({inferred_num_fields}) does not match length of fields ({len(validated_fields)})" ) - ref = self._data - for idx in (i[0] for i in indices_arrays[:-1]): - ref = ref[idx] - ref[indices_arrays[-1][0]] = value - return - - # Handle fancy indexing - if not isinstance(value, list): - raise TypeError("For fancy indexing, value must be a list of numpy arrays") - - # Validate and set values - for idx in np.ndindex(*[len(i) for i in indices_arrays]): - src_idx = tuple(ind[i] for ind, i in zip(indices_arrays, idx)) - if not isinstance(value[idx[0]], np.ndarray): - raise TypeError(f"Expected numpy array, got {type(value[idx[0]]).__name__}") - if value[idx[0]].ndim != 2 or value[idx[0]].shape[1] != self.num_fields: + elif num_fields is not None: + validated_num_fields = validate_num_fields(num_fields) + if validated_num_fields != inferred_num_fields: raise ValueError( - f"Expected array with shape (_, {self.num_fields}), got {value[idx[0]].shape}" + f"Provided num_fields ({validated_num_fields}) does not match inferred ({inferred_num_fields})." ) - ref = self._data - for i in src_idx[:-1]: - ref = ref[i] - ref[src_idx[-1]] = value[idx[0]] - - @overload - def __getitem__(self, idx: str) -> "_FieldView": ... - @overload - def __getitem__( - self, - idx: Union[Tuple[Union[int, slice, List[int]], ...], int, slice, List[int]], - ) -> Union[NDArray, "Vector"]: ... + validated_fields = [f"field_{i}" for i in range(validated_num_fields)] + else: + validated_fields = [f"field_{i}" for i in range(inferred_num_fields)] - def __getitem__( - self, - idx: Union[str, Tuple[Union[int, slice, List[int]], ...], int, slice, List[int]], - ) -> Union["_FieldView", NDArray, "Vector"]: - """Get data or a view of the vector at specified indices.""" - if isinstance(idx, str): - if idx not in self._fields: - raise KeyError(f"Field '{idx}' not found.") - return _FieldView(self, idx) - - # Normalize idx to tuple - normalized: Tuple[Any, ...] = (idx,) if not isinstance(idx, tuple) else idx - - # Convert lists/arrays to ndarray - idx_converted: Tuple[Union[int, slice, np.ndarray[Any, np.dtype[Any]]], ...] = tuple( - np.asarray(i) if isinstance(i, (list, np.ndarray)) else i for i in normalized + vector = cls( + shape=inferred_shape, + fields=validated_fields, + units=units, + name=name, ) + for coord, array in zip(np.ndindex(inferred_shape), normalized_cells): + vector._state.data[coord] = array.copy() + return vector - # Check if we should return a numpy array (all indices are integers) - return_np = all(isinstance(i, (int, np.integer)) for i in idx_converted[: len(self.shape)]) - if len(idx_converted) < len(self.shape): - return_np = False - - if return_np: - view = self._data - for i in idx_converted: - view = view[i] - return cast(NDArray[Any], view) - - # Handle fancy indexing and slicing - def get_indices(dim_idx: Any, dim_size: int) -> np.ndarray: - if isinstance(dim_idx, slice): - start, stop, step = dim_idx.indices(dim_size) - return np.arange(start, stop, step) - elif isinstance(dim_idx, (np.ndarray, list)): - return np.asarray(dim_idx) - elif isinstance(dim_idx, (int, np.integer)): - return np.array([dim_idx]) - return np.arange(dim_size) - - # Get indices for each dimension - full_idx = list(idx_converted) + [slice(None)] * (len(self.shape) - len(idx_converted)) - indices = [get_indices(i, s) for i, s in zip(full_idx, self.shape)] - - # Create new shape and data - new_shape = [len(i) for i in indices] - new_data = [[None] * new_shape[-1] for _ in range(new_shape[0])] - - # Fill the new data structure - for out_idx in np.ndindex(*new_shape): - src_idx = tuple(ind[i] for ind, i in zip(indices, out_idx)) - new_data[out_idx[0]][out_idx[1]] = self._data[src_idx[0]][src_idx[1]] - - # Create new Vector - vector_new = Vector.from_shape( - shape=tuple(new_shape), - num_fields=self.num_fields, - name=self.name + "[view]", - fields=self.fields, - units=self.units, - ) - vector_new._data = new_data - return vector_new + @property + def shape(self) -> tuple[int, ...]: + return self._coords.shape - def __setitem__( - self, - idx: Union[Tuple[Union[int, slice, List[int]], ...], int, slice, List[int], str], - value: Union[NDArray, List[NDArray]], - ) -> None: - """Set data at specified indices.""" - if isinstance(idx, str): - if idx not in self._fields: - raise KeyError(f"Field '{idx}' not found.") - field_view = _FieldView(self, idx) - field_view.set_flattened(value) - return + @property + def fields(self) -> list[str]: + return list(self._field_names) - # Normalize idx to tuple - normalized: Tuple[Any, ...] = (idx,) if not isinstance(idx, tuple) else idx + @property + def units(self) -> list[str]: + lookup = {name: unit for name, unit in zip(self._state.fields, self._state.units)} + return [lookup[name] for name in self._field_names] - # Convert lists/arrays to ndarray - idx_converted: Tuple[Union[int, slice, np.ndarray[Any, np.dtype[Any]]], ...] = tuple( - np.asarray(i) if isinstance(i, (list, np.ndarray)) else i for i in normalized - ) + @property + def num_fields(self) -> int: + return len(self._field_names) - # Check if we're doing slice‐ or array‐based (multi‐cell) indexing - has_fancy = any( - isinstance(i, slice) or (isinstance(i, np.ndarray) and i.size > 1) - for i in idx_converted[: len(self.shape)] - ) + @property + def name(self) -> str: + return self._state.name - if has_fancy: - # If user passed a Vector, extract its cell arrays - if isinstance(value, Vector): + @name.setter + def name(self, value: str) -> None: + self._state.name = str(value) - def _flatten_cells(data): - if isinstance(data, np.ndarray): - return [data] - out = [] - for sub in data: - out.extend(_flatten_cells(sub)) - return out + @property + def metadata(self) -> dict[str, Any]: + return self._state.metadata - value = _flatten_cells(value._data) + @property + def array(self) -> NDArray[np.generic]: + if self.shape != (): + raise ValueError(".array is only valid when the selection contains exactly one cell.") + coord = self._coords[()] + cell = self._state.data[coord] + indices = self._field_indices() + if len(indices) == len(self._state.fields) and indices == list(range(len(self._state.fields))): + return cell + if len(indices) == 1: + idx = indices[0] + return cell[:, idx : idx + 1] + if _is_contiguous(indices): + return cell[:, indices[0] : indices[-1] + 1] + return cell[:, indices].copy() + + def __len__(self) -> int: + if self.shape == (): + raise TypeError("len() of unsized 0D Vector") + return self.shape[0] - # For fancy indexing, value should be a list of arrays - if not isinstance(value, list): - raise TypeError( - "For fancy/slice indexing, value must be a list of numpy arrays or a Vector" - ) + def __repr__(self) -> str: + return f"quantem.Vector(shape={self.shape}, fields={self.fields}, name={self.name!r})" - # Get indices for each dimension - def get_indices(dim_idx: Any, dim_size: int) -> np.ndarray: - if isinstance(dim_idx, slice): - start, stop, step = dim_idx.indices(dim_size) - return np.arange(start, stop, step) - elif isinstance(dim_idx, (np.ndarray, list)): - idx = np.asarray(dim_idx) - if np.any((idx < 0) | (idx >= dim_size)): - raise IndexError(f"Index out of bounds for axis with size {dim_size}") - return idx - elif isinstance(dim_idx, (int, np.integer)): - if dim_idx < 0 or dim_idx >= dim_size: - raise IndexError(f"Index out of bounds for axis with size {dim_size}") - return np.array([dim_idx]) - return np.arange(dim_size) - - indices_arrays = [get_indices(i, s) for i, s in zip(idx_converted, self._shape)] - total_indices = np.prod([len(i) for i in indices_arrays]) - - if len(value) != total_indices: - raise ValueError(f"Expected {total_indices} arrays, got {len(value)}") - - # Validate and set values - for array_idx, idx in enumerate(np.ndindex(*[len(i) for i in indices_arrays])): - src_idx = tuple(ind[i] for ind, i in zip(indices_arrays, idx)) - if not isinstance(value[array_idx], np.ndarray): - raise TypeError(f"Expected numpy array, got {type(value[array_idx]).__name__}") - if value[array_idx].ndim != 2 or value[array_idx].shape[1] != self.num_fields: - raise ValueError( - f"Expected array with shape (_, {self.num_fields}), got {value[array_idx].shape}" - ) - ref = self._data - for i in src_idx[:-1]: - ref = ref[i] - ref[src_idx[-1]] = value[array_idx] - else: - # For single value assignment - if not isinstance(value, np.ndarray): - raise TypeError(f"Value must be a numpy array, got {type(value).__name__}") - if value.ndim != 2 or value.shape[1] != self.num_fields: - raise ValueError( - f"Expected a numpy array with shape (_, {self.num_fields}), got {value.shape}" - ) - ref = self._data - for i in idx_converted[:-1]: - ref = ref[i] - ref[idx_converted[-1]] = value - - def add_fields(self, new_fields: Union[str, List[str]]) -> None: - """ - Add new fields to the vector. - - Parameters - ---------- - new_fields : Union[str, List[str]] - Field name(s) to add. Must be unique and not already present. - - Raises - ------ - ValueError - If any field name already exists or if there are duplicates - """ - if isinstance(new_fields, str): - new_fields = [new_fields] - else: - new_fields = list(new_fields) + __str__ = __repr__ - if any(name in self._fields for name in new_fields): + def copy(self) -> "Vector": + copied = self.__class__._from_state( + _VectorState( + shape=self.shape, + data=_empty_storage(self.shape, self.num_fields), + fields=self.fields, + units=self.units, + name=self.name, + metadata=copy.deepcopy(self.metadata), + ), + _root_coords_allow_empty(self.shape), + self.fields, + ) + for coord, array in zip(np.ndindex(self.shape) if self.shape != () else [()], self._iter_selected_arrays()): + copied._state.data[coord] = array.copy() + return copied + + def flatten(self) -> NDArray[np.generic]: + arrays = [array for array in self._iter_selected_arrays() if array.shape[0] > 0] + if arrays: + return np.vstack(arrays) + dtype = float + for array in self._iter_selected_arrays(): + dtype = array.dtype + break + return np.empty((0, self.num_fields), dtype=dtype) + + def select_fields(self, field_names: str | Sequence[str]) -> "Vector": + normalized = _normalize_field_names(field_names) + available = set(self._field_names) + missing = [name for name in normalized if name not in available] + if missing: + raise KeyError(f"Unknown field(s): {missing}") + return self._from_state(self._state, self._coords, normalized) + + def add_fields( + self, + names: str | Sequence[str], + values: Any | None = None, + units: str | Sequence[str] | None = None, + ) -> None: + new_names = _normalize_field_names(names) + if any(name in self._state.fields for name in new_names): raise ValueError("One or more new field names already exist.") - if len(set(new_fields)) != len(new_fields): - raise ValueError("Duplicate field names in input are not allowed.") - - self._fields = list(self._fields) + list(new_fields) - self._units = list(self._units) + ["none"] * len(new_fields) - - def expand_array(arr: Any) -> Any: - if isinstance(arr, np.ndarray): - if arr.shape[1] != self.num_fields - len(new_fields): - raise ValueError( - f"Expected arrays with {self.num_fields - len(new_fields)} fields, got {arr.shape[1]}" - ) - pad = np.zeros((arr.shape[0], len(new_fields))) - return np.hstack([arr, pad]) - elif isinstance(arr, list): - return [expand_array(sub) for sub in arr] - else: - return arr - - self._data = expand_array(self._data) - - def remove_fields(self, fields_to_remove: Union[str, List[str]]) -> None: - """ - Remove fields from the vector. - - Parameters - ---------- - fields_to_remove : Union[str, List[str]] - Field name(s) to remove. Must exist in the vector. - - Raises - ------ - ValueError - If any field doesn't exist - """ - if isinstance(fields_to_remove, str): - fields_to_remove = [fields_to_remove] - else: - fields_to_remove = list(fields_to_remove) - - field_to_index = {name: i for i, name in enumerate(self._fields)} - indices_to_remove = [] - for field in fields_to_remove: - if field not in field_to_index: - print(f"Warning: field '{field}' not found.") - else: - indices_to_remove.append(field_to_index[field]) - - if not indices_to_remove: + new_units = _normalize_units(units, len(new_names)) + old_fields = list(self._state.fields) + self._state.fields.extend(new_names) + self._state.units.extend(new_units) + + old_width = len(old_fields) + new_width = len(self._state.fields) + for coord in np.ndindex(self._state.shape) if self._state.shape != () else [()]: + current = self._state.data[coord] + promoted = np.result_type(current.dtype, float) + expanded = np.full((current.shape[0], new_width), np.nan, dtype=promoted) + expanded[:, :old_width] = current + self._state.data[coord] = expanded + + if list(self._field_names) == old_fields: + self._field_names = tuple(self._state.fields) + + target = self._from_state(self._state, self._coords, new_names) + if values is None: return - indices_to_remove = sorted(set(indices_to_remove)) - keep_indices = [i for i in range(self.num_fields) if i not in indices_to_remove] - - # Update metadata - self._fields = [self._fields[i] for i in keep_indices] - self._units = [self._units[i] for i in keep_indices] - - def prune_array(arr: Any) -> Any: - if isinstance(arr, np.ndarray): - if arr.shape[1] < max(indices_to_remove) + 1: - raise ValueError( - f"Cannot remove field index {max(indices_to_remove)} from array with shape {arr.shape}" - ) - return arr[:, keep_indices] - elif isinstance(arr, list): - return [prune_array(sub) for sub in arr] - else: - return arr - - self._data = prune_array(self._data) + if len(new_names) > 1 and isinstance(values, (list, tuple)) and len(values) == len(new_names): + for name, value in zip(new_names, values): + target.select_fields(name)._assign(value) + else: + target._assign(values) + + def remove_fields(self, names: str | Sequence[str]) -> None: + to_remove = _normalize_field_names(names) + missing = [name for name in to_remove if name not in self._state.fields] + if missing: + raise KeyError(f"Unknown field(s): {missing}") + if len(to_remove) == len(self._state.fields): + raise ValueError("Cannot remove all fields from a Vector.") + + remove_set = set(to_remove) + keep_names = [name for name in self._state.fields if name not in remove_set] + keep_indices = [self._state.fields.index(name) for name in keep_names] + keep_units = [self._state.units[index] for index in keep_indices] + + for coord in np.ndindex(self._state.shape) if self._state.shape != () else [()]: + self._state.data[coord] = self._state.data[coord][:, keep_indices] + + self._state.fields = keep_names + self._state.units = keep_units + self._field_names = tuple(name for name in self._field_names if name in keep_names) + + def get_data(self, *indices: Any) -> NDArray[np.generic] | list[NDArray[np.generic]]: + if len(indices) != len(self.shape): + raise ValueError(f"Expected {len(self.shape)} indices, got {len(indices)}") + selection = self[indices if len(indices) != 1 else indices[0]] + if selection.shape == (): + return selection.array + return [array.copy() for array in selection._iter_selected_arrays()] + + def set_data(self, value: Any, *indices: Any) -> None: + if len(indices) != len(self.shape): + raise ValueError(f"Expected {len(self.shape)} indices, got {len(indices)}") + self[indices if len(indices) != 1 else indices[0]] = value - def copy(self) -> "Vector": - """ - Create a deep copy of the vector. - - Returns - ------- - Vector - A new Vector instance with the same data, shape, fields, and units. - """ - import copy - - vector_copy = Vector.from_shape( - shape=self.shape, - name=self.name, - fields=self.fields, - units=self.units, - ) - vector_copy._data = copy.deepcopy(self._data) - return vector_copy - - def flatten(self) -> NDArray: - """ - Flatten the vector into a 2D numpy array. - - Returns - ------- - NDArray - A 2D numpy array containing all data, with shape (total_rows, num_fields). - """ - - def collect_arrays(data: Any) -> List[NDArray]: - if isinstance(data, np.ndarray): - return [data] - elif isinstance(data, list): - arrays = [] - for item in data: - arrays.extend(collect_arrays(item)) - return arrays - else: - return [] + @overload + def __getitem__(self, idx: Any) -> "Vector": ... - arrays = collect_arrays(self._data) - if not arrays: - return np.empty((0, self.num_fields)) - return np.vstack(arrays) + def __getitem__(self, idx: Any) -> "Vector": + if _is_field_selector(idx): + raise TypeError("Use select_fields(...) for field selection.") + coords = _select_coords(self._coords, idx) + return self._from_state(self._state, coords, self._field_names) - def __repr__(self) -> str: - description = [ - f"quantem.Vector, shape={self._shape}, name={self._name}", - f" fields = {self._fields}", - f" units: {self._units}", - ] - return "\n".join(description) - - def __str__(self) -> str: - description = [ - f"quantem.Vector, shape={self._shape}, name={self._name}", - f" fields = {self._fields}", - f" units: {self._units}", - ] - return "\n".join(description) + def __setitem__(self, idx: Any, value: Any) -> None: + if _is_field_selector(idx): + raise TypeError("Use select_fields(...) for field selection.") + self[idx]._assign(value) - @property - def metadata(self) -> dict: - return self._metadata + def __add__(self, other: Any) -> "Vector": + return self._binary_op(other, np.add) - @property - def shape(self) -> Tuple[int, ...]: - """ - Get the shape of the vector. - - Returns - ------- - Tuple[int, ...] - The dimensions of the vector. - """ - return self._shape - - @shape.setter - def shape(self, value: Tuple[int, ...]) -> None: - """ - Set the shape of the vector. - - Parameters - ---------- - value : Tuple[int, ...] - The new shape. All dimensions must be positive. - - Raises - ------ - ValueError - If any dimension is not positive. - TypeError - If value is not a tuple or contains non-integer values. - """ - self._shape = validate_shape(value) + def __sub__(self, other: Any) -> "Vector": + return self._binary_op(other, np.subtract) - @property - def num_fields(self) -> int: - """ - Get the number of fields in the vector. + def __mul__(self, other: Any) -> "Vector": + return self._binary_op(other, np.multiply) - Returns - ------- - int - The number of fields. - """ - return len(self._fields) + def __truediv__(self, other: Any) -> "Vector": + return self._binary_op(other, np.divide) - @property - def name(self) -> str: - """ - Get the name of the vector. + def __pow__(self, other: Any) -> "Vector": + return self._binary_op(other, np.power) - Returns - ------- - str - The name of the vector - """ - return self._name + def __iadd__(self, other: Any) -> "Vector": + return self._binary_op_inplace(other, np.add) - @name.setter - def name(self, value: str) -> None: - """ - Set the name of the vector. + def __isub__(self, other: Any) -> "Vector": + return self._binary_op_inplace(other, np.subtract) - Parameters - ---------- - value : str - The new name of the vector - """ - self._name = str(value) + def __imul__(self, other: Any) -> "Vector": + return self._binary_op_inplace(other, np.multiply) - @property - def fields(self) -> List[str]: - """ - Get the field names of the vector. - - Returns - ------- - List[str] - The list of field names. - """ - return self._fields - - @fields.setter - def fields(self, value: List[str]) -> None: - """ - Set the field names of the vector. - - Parameters - ---------- - value : List[str] - The new field names. Must match num_fields and be unique. - - Raises - ------ - ValueError - If length doesn't match num_fields or if there are duplicates. - TypeError - If value is not a list or contains non-string values. - """ - self._fields = validate_fields(value) + def __itruediv__(self, other: Any) -> "Vector": + return self._binary_op_inplace(other, np.divide) - @property - def units(self) -> List[str]: - """ - Get the units of the vector's fields. - - Returns - ------- - List[str] - The list of units, one per field. - """ - return self._units - - @units.setter - def units(self, value: List[str]) -> None: - """ - Set the units of the vector's fields. - - Parameters - ---------- - value : List[str] - The new units. Must match num_fields. - - Raises - ------ - ValueError - If length doesn't match num_fields. - TypeError - If value is not a list or contains non-string values. - """ - self._units = validate_vector_units(value, self.num_fields) + def __ipow__(self, other: Any) -> "Vector": + return self._binary_op_inplace(other, np.power) - @property - def data(self) -> List[Any]: - """ - Get the raw data of the vector. - - Returns - ------- - List[Any] - The nested list structure containing the vector's data. - """ - return self._data - - @data.setter - def data(self, value: List[Any]) -> None: - """ - Set the raw data of the vector. - - Parameters - ---------- - value : List[Any] - The new data structure. Must match the vector's shape and num_fields. - - Raises - ------ - ValueError - If the data structure doesn't match shape or num_fields. - TypeError - If value is not a list or contains invalid data types. - """ - self._data = validate_vector_data(value, self.shape, self.num_fields) - - -# Helper function for nesting lists -def nested_list(shape: Tuple[int, ...], fill: Any = None) -> Any: - if len(shape) == 0: - return fill - return [nested_list(shape[1:], fill) for _ in range(shape[0])] - - -# Helper class for numerical field operations -class _FieldView: - def __init__(self, vector: Vector, field_name: str) -> None: - self.vector = vector - self.field_name = field_name - self.field_index = vector._fields.index(field_name) - - def _apply_op(self, op: Any) -> None: - def apply(arr: Any) -> None: - if isinstance(arr, np.ndarray): - arr[:, self.field_index] = op(arr[:, self.field_index]) - elif isinstance(arr, list): - for sub in arr: - apply(sub) - - apply(self.vector._data) - - def __iadd__(self, other: Union[float, int, np.ndarray]) -> "_FieldView": - """Handle in-place addition (+=).""" - self._apply_op(lambda x: x + other) - return self + def _binary_op(self, other: Any, op: Any) -> "Vector": + result = self.copy() + result._binary_op_inplace(other, op) + return result - def __isub__(self, other: Union[float, int, np.ndarray]) -> "_FieldView": - """Handle in-place subtraction (-=).""" - self._apply_op(lambda x: x - other) + def _binary_op_inplace(self, other: Any, op: Any) -> "Vector": + row_counts = self._row_counts() + targets = list(self._iter_coords()) + + if isinstance(other, Vector): + self._validate_vector_rhs(other, require_matching_rows=True) + source_arrays = list(other._iter_selected_arrays()) + for coord, current, rhs in zip(targets, self._iter_selected_arrays(), source_arrays): + updated = current.copy() + op(updated, rhs, out=updated, casting="same_kind") + self._write_selected_array(coord, updated) + return self + + rhs_matrix = _broadcast_rhs(np.asarray(other), sum(row_counts), self.num_fields) + cursor = 0 + for coord, current, row_count in zip(targets, self._iter_selected_arrays(), row_counts): + chunk = rhs_matrix[cursor : cursor + row_count] + cursor += row_count + updated = current.copy() + op(updated, chunk, out=updated, casting="same_kind") + self._write_selected_array(coord, updated) return self - def __imul__(self, other: Union[float, int, np.ndarray]) -> "_FieldView": - """Handle in-place multiplication (*=).""" - self._apply_op(lambda x: x * other) - return self + def _assign(self, value: Any) -> None: + if self._is_full_field_selection(): + self._assign_full_cells(value) + else: + self._assign_selected_fields(value) + + def _assign_full_cells(self, value: Any) -> None: + targets = list(self._iter_coords()) + if isinstance(value, Vector): + self._validate_vector_rhs(value, require_matching_rows=False) + for target, source in zip(targets, value._iter_selected_arrays()): + self._state.data[target] = source.copy() + return - def __itruediv__(self, other: Union[float, int, np.ndarray]) -> "_FieldView": - """Handle in-place division (/=).""" - self._apply_op(lambda x: x / other) - return self + array = _coerce_cell_array(value, self.num_fields) + for target in targets: + self._state.data[target] = array.copy() + + def _assign_selected_fields(self, value: Any) -> None: + targets = list(self._iter_coords()) + row_counts = self._row_counts() + if isinstance(value, Vector): + self._validate_vector_rhs(value, require_matching_rows=True) + for coord, source in zip(targets, value._iter_selected_arrays()): + self._write_selected_array(coord, source) + return - def __ifloordiv__(self, other: Union[float, int, np.ndarray]) -> "_FieldView": - """Handle in-place floor division (//=).""" - self._apply_op(lambda x: x // other) - return self + if np.isscalar(value): + for coord in targets: + current = self._selected_array_for_coord(coord) + fill = np.full(current.shape, value) + self._write_selected_array(coord, fill) + return - def __imod__(self, other: Union[float, int, np.ndarray]) -> "_FieldView": - """Handle in-place modulo (%=).""" - self._apply_op(lambda x: x % other) - return self + rhs_matrix = _broadcast_rhs(np.asarray(value), sum(row_counts), self.num_fields) + cursor = 0 + for coord, row_count in zip(targets, row_counts): + chunk = rhs_matrix[cursor : cursor + row_count] + cursor += row_count + self._write_selected_array(coord, chunk) + + def _validate_vector_rhs(self, other: "Vector", require_matching_rows: bool) -> None: + if self.num_fields != other.num_fields: + raise ValueError(f"Expected {self.num_fields} fields, got {other.num_fields}") + if self._coords.size != other._coords.size: + raise ValueError(f"Expected {self._coords.size} cells, got {other._coords.size}") + if require_matching_rows: + left = self._row_counts() + right = other._row_counts() + if left != right: + raise ValueError(f"Per-cell row counts must match: {left} != {right}") + + def _row_counts(self) -> list[int]: + return [self._state.data[coord].shape[0] for coord in self._iter_coords()] + + def _iter_coords(self) -> Iterable[tuple[int, ...]]: + return iter(self._coords.flat) + + def _iter_selected_arrays(self) -> Iterable[NDArray[np.generic]]: + for coord in self._iter_coords(): + yield self._selected_array_for_coord(coord) + + def _selected_array_for_coord(self, coord: tuple[int, ...]) -> NDArray[np.generic]: + cell = self._state.data[coord] + indices = self._field_indices() + if self._is_full_field_selection(): + return cell + if len(indices) == 1: + idx = indices[0] + return cell[:, idx : idx + 1] + if _is_contiguous(indices): + return cell[:, indices[0] : indices[-1] + 1] + return cell[:, indices].copy() + + def _write_selected_array(self, coord: tuple[int, ...], values: NDArray[np.generic]) -> None: + cell = self._state.data[coord] + if self._is_full_field_selection(): + replacement = _coerce_cell_array(values, self.num_fields) + self._state.data[coord] = replacement.copy() + return - def __ipow__(self, other: Union[float, int, np.ndarray]) -> "_FieldView": - """Handle in-place power (**=).""" - self._apply_op(lambda x: x**other) - return self + if values.ndim != 2 or values.shape[1] != self.num_fields: + raise ValueError( + f"Expected array with shape (_, {self.num_fields}), got {values.shape}" + ) + if values.shape[0] != cell.shape[0]: + raise ValueError( + f"Expected {cell.shape[0]} rows for in-place field update, got {values.shape[0]}" + ) + cell[:, self._field_indices()] = values + + def _field_indices(self) -> list[int]: + lookup = {name: idx for idx, name in enumerate(self._state.fields)} + try: + return [lookup[name] for name in self._field_names] + except KeyError as exc: + raise KeyError(f"Unknown field '{exc.args[0]}'") from exc + + def _is_full_field_selection(self) -> bool: + return list(self._field_names) == self._state.fields + + +def _empty_storage(shape: tuple[int, ...], num_fields: int) -> NDArray[np.object_]: + storage = np.empty(shape if shape != () else (), dtype=object) + for coord in np.ndindex(shape) if shape != () else [()]: + storage[coord] = np.empty((0, num_fields), dtype=float) + return storage + + +def _root_coords(shape: tuple[int, ...]) -> NDArray[np.object_]: + return _root_coords_allow_empty(shape) + + +def _root_coords_allow_empty(shape: tuple[int, ...]) -> NDArray[np.object_]: + coords = np.empty(shape if shape != () else (), dtype=object) + for coord in np.ndindex(shape) if shape != () else [()]: + coords[coord] = coord + return coords + + +def _normalize_field_names(field_names: str | Sequence[str]) -> list[str]: + if isinstance(field_names, str): + names = [field_names] + elif isinstance(field_names, Sequence): + names = [str(name) for name in field_names] + else: + raise TypeError("Field names must be a string or a sequence of strings.") + if not names: + raise ValueError("Must select at least one field.") + if len(set(names)) != len(names): + raise ValueError("Duplicate field names are not allowed.") + return names + + +def _normalize_units(units: str | Sequence[str] | None, count: int) -> list[str]: + if units is None: + return ["none"] * count + if isinstance(units, str): + if count != 1: + raise ValueError("A single unit string is only valid for a single new field.") + return [units] + normalized = [str(unit) for unit in units] + if len(normalized) != count: + raise ValueError(f"Expected {count} units, got {len(normalized)}") + return normalized + + +def _coerce_cell_array(value: Any, num_fields: int) -> NDArray[np.generic]: + array = np.asarray(value) + if array.ndim != 2 or array.shape[1] != num_fields: + raise ValueError(f"Expected a numpy array with shape (_, {num_fields}), got {array.shape}") + if not np.issubdtype(array.dtype, np.number): + raise TypeError("Cell arrays must contain numeric values.") + return array + + +def _broadcast_rhs(value: NDArray[np.generic], total_rows: int, num_fields: int) -> NDArray[np.generic]: + if num_fields == 1 and value.ndim == 1: + value = value.reshape(-1, 1) + try: + return np.broadcast_to(value, (total_rows, num_fields)) + except ValueError as exc: + raise ValueError( + f"RHS is not broadcast-compatible with flattened target shape ({total_rows}, {num_fields})." + ) from exc + + +def _is_field_selector(idx: Any) -> bool: + if isinstance(idx, str): + return True + if isinstance(idx, list | tuple) and idx: + if all(isinstance(item, str) for item in idx): + return True + return any(isinstance(item, str) for item in idx) + return False + + +def _is_contiguous(indices: Sequence[int]) -> bool: + if not indices: + return False + return list(indices) == list(range(indices[0], indices[0] + len(indices))) + + +def _select_coords(coords: NDArray[np.object_], idx: Any) -> NDArray[np.object_]: + shape = coords.shape + selectors, scalar_axes = _normalize_indices(shape, idx) + out_shape = tuple(len(selector) for selector, scalar in zip(selectors, scalar_axes) if not scalar) + result = np.empty(out_shape if out_shape != () else (), dtype=object) + + if out_shape == (): + source = tuple(selector[0] for selector in selectors) + result[()] = coords[source] + return result - def flatten(self) -> NDArray: - def collect(arr: Any) -> List[NDArray]: - if isinstance(arr, np.ndarray): - return [arr[:, self.field_index]] - elif isinstance(arr, list): - result = [] - for sub in arr: - result.extend(collect(sub)) - return result + for out_index in np.ndindex(out_shape): + out_cursor = 0 + source = [] + for axis, selector in enumerate(selectors): + if scalar_axes[axis]: + source.append(selector[0]) else: - return [] - - arrays = collect(self.vector._data) - if not arrays: - return np.empty((0,), dtype=float) - return np.concatenate(arrays, axis=0) - - def set_flattened(self, values: ArrayLike) -> None: - """ - Set the field values across the entire Vector from a 1D flattened array. - """ - - def fill(arr: Any, values: NDArray, cursor: int) -> int: - if isinstance(arr, np.ndarray): - n = arr.shape[0] - arr[:, self.field_index] = values[cursor : cursor + n] - return cursor + n - elif isinstance(arr, list): - for sub in arr: - cursor = fill(sub, values, cursor) - return cursor - return cursor - - values = np.asarray(values) - if values.ndim != 1: - raise ValueError("Input to set_flattened must be a 1D array.") - - expected = self.flatten().shape[0] - if values.shape[0] != expected: - raise ValueError(f"Expected {expected} values, got {values.shape[0]}") - - fill(self.vector._data, values, cursor=0) - - def __getitem__( - self, idx: Union[Tuple[Union[int, slice], ...], int, slice] - ) -> Union[NDArray, "_FieldView"]: - # Optionally allow v['field0'][0, 1] to get subregion, or v['field0'][...] slice - sub = self.vector[idx] - if isinstance(sub, Vector): - return sub[self.field_name] - elif isinstance(sub, np.ndarray): - return sub[:, self.field_index] - return cast(NDArray, None) - - def __array__(self) -> np.ndarray: - """Convert to numpy array when needed.""" - return self.flatten() + source.append(selector[out_index[out_cursor]]) + out_cursor += 1 + result[out_index] = coords[tuple(source)] + return result + + +def _normalize_indices(shape: tuple[int, ...], idx: Any) -> tuple[list[np.ndarray], list[bool]]: + if shape == (): + if idx in ((), Ellipsis, slice(None), None): + return [], [] + raise IndexError("Too many indices for a 0D Vector.") + + normalized = idx if isinstance(idx, tuple) else (idx,) + normalized = _expand_ellipsis(normalized, len(shape)) + if len(normalized) > len(shape): + raise IndexError(f"Expected at most {len(shape)} indices, got {len(normalized)}") + normalized = normalized + (slice(None),) * (len(shape) - len(normalized)) + + selectors: list[np.ndarray] = [] + scalar_axes: list[bool] = [] + for axis_value, axis_size in zip(normalized, shape): + selector, scalar = _normalize_axis_index(axis_value, axis_size) + selectors.append(selector) + scalar_axes.append(scalar) + return selectors, scalar_axes + + +def _expand_ellipsis(idx: tuple[Any, ...], ndim: int) -> tuple[Any, ...]: + if not any(item is Ellipsis for item in idx): + return idx + if sum(item is Ellipsis for item in idx) > 1: + raise IndexError("Only one ellipsis is allowed.") + ellipsis_pos = next(i for i, item in enumerate(idx) if item is Ellipsis) + fill = ndim - (len(idx) - 1) + return idx[:ellipsis_pos] + (slice(None),) * fill + idx[ellipsis_pos + 1 :] + + +def _normalize_axis_index(value: Any, axis_size: int) -> tuple[np.ndarray, bool]: + if isinstance(value, (int, np.integer)): + index = int(value) + if index < 0: + index += axis_size + if index < 0 or index >= axis_size: + raise IndexError(f"Index {value} out of bounds for axis with size {axis_size}") + return np.array([index], dtype=int), True + + if isinstance(value, slice): + return np.arange(axis_size)[value], False + + if isinstance(value, np.ndarray) and value.ndim == 0: + return _normalize_axis_index(value.item(), axis_size) + + array = np.asarray(value) + if array.ndim != 1: + raise IndexError("Only 1D per-axis fancy or boolean indexing is supported.") + if array.size == 0: + return np.array([], dtype=int), False + + if array.dtype == bool or np.issubdtype(array.dtype, np.bool_): + if array.shape[0] != axis_size: + raise IndexError( + f"Boolean mask length {array.shape[0]} does not match axis size {axis_size}" + ) + return np.flatnonzero(array), False + + if not np.issubdtype(array.dtype, np.integer): + raise TypeError(f"Unsupported index type {type(value).__name__}") + + normalized = array.astype(int, copy=True) + normalized[normalized < 0] += axis_size + if np.any((normalized < 0) | (normalized >= axis_size)): + raise IndexError(f"Index out of bounds for axis with size {axis_size}") + return normalized, False + + +def _normalize_nested_data(data: list[Any]) -> tuple[tuple[int, ...], list[NDArray[np.generic]]]: + if not isinstance(data, list): + raise TypeError("Data must be a list.") + if not data: + raise ValueError("Data list cannot be empty.") + + leaves: list[NDArray[np.generic]] = [] + num_fields: int | None = None + + def walk(node: Any) -> tuple[int, ...]: + nonlocal num_fields + if _is_leaf_cell(node): + array = np.asarray(node) + if array.ndim != 2: + raise ValueError(f"Cell arrays must be 2D, got shape {array.shape}") + if not np.issubdtype(array.dtype, np.number): + raise TypeError("Cell arrays must contain numeric values.") + if num_fields is None: + num_fields = array.shape[1] + elif array.shape[1] != num_fields: + raise ValueError("All data arrays must have same number of fields.") + leaves.append(array) + return () + + if not isinstance(node, list): + raise TypeError("Data elements must be numpy arrays, numeric 2D lists, or nested lists thereof.") + if not node: + raise ValueError("Nested data lists cannot be empty.") + child_shapes = [walk(child) for child in node] + first = child_shapes[0] + if any(shape != first for shape in child_shapes[1:]): + raise ValueError("Nested data structure must have a consistent fixed-grid shape.") + return (len(node), *first) + + inferred_shape = walk(data) + return inferred_shape, leaves + + +def _is_leaf_cell(node: Any) -> bool: + if isinstance(node, np.ndarray): + return True + if not isinstance(node, list): + return False + try: + array = np.asarray(node) + except Exception: + return False + return array.ndim == 2 and np.issubdtype(array.dtype, np.number) diff --git a/tests/datastructures/test_vector.py b/tests/datastructures/test_vector.py index e2085b76..f03a08b4 100644 --- a/tests/datastructures/test_vector.py +++ b/tests/datastructures/test_vector.py @@ -4,430 +4,231 @@ from quantem.core.datastructures.vector import Vector -class TestVector: - """Test suite for the Vector class.""" - def test_initialization(self): - """Test Vector initialization with different parameters.""" - # Test with fields - v1 = Vector.from_shape(shape=(2, 3), fields=["field0", "field1", "field2"]) +def make_line_vector() -> Vector: + v = Vector.from_shape( + shape=(4,), + fields=["intensity", "kx", "ky"], + units=["a.u.", "px", "px"], + name="line", + ) + v[0] = np.array([[1.0, 10.0, 100.0], [2.0, 20.0, 200.0]]) + v[1] = np.array([[3.0, 30.0, 300.0]]) + v[2] = np.array([[4.0, 40.0, 400.0], [5.0, 50.0, 500.0]]) + v[3] = np.array([[6.0, 60.0, 600.0]]) + return v + + + +def make_grid_vector() -> Vector: + v = Vector.from_shape(shape=(3, 2), fields=["intensity", "kx", "ky"]) + for i in range(3): + for j in range(2): + base = float(i * 10 + j) + v[i, j] = np.array([[base, base + 100.0, base + 200.0]]) + return v + + +class TestVector: + def test_initialization_and_len(self): + v1 = Vector.from_shape(shape=(2, 3), fields=["a", "b", "c"]) assert v1.shape == (2, 3) + assert len(v1) == 2 assert v1.num_fields == 3 - assert v1.fields == ["field0", "field1", "field2"] + assert v1.fields == ["a", "b", "c"] assert v1.units == ["none", "none", "none"] assert v1.name == "2d ragged array" - assert hasattr(v1, "metadata") - - # Test with num_fields - v2 = Vector.from_shape(shape=(2, 3), num_fields=3) - assert v2.shape == (2, 3) - assert v2.num_fields == 3 - assert v2.fields == ["field_0", "field_1", "field_2"] - assert v2.units == ["none", "none", "none"] - assert hasattr(v2, "metadata") - - # Test with custom name and units - v3 = Vector.from_shape( - shape=(2, 3), - fields=["field0", "field1", "field2"], - name="my_vector", - units=["unit0", "unit1", "unit2"], - ) - assert v3.name == "my_vector" - assert v3.units == ["unit0", "unit1", "unit2"] - assert hasattr(v3, "metadata") + assert v1[0, 0].array.shape == (0, 3) + np.testing.assert_array_equal(v1[0, 0].flatten(), v1[0, 0].array) + + v2 = Vector.from_shape(shape=(2, 3), num_fields=2) + assert v2.fields == ["field_0", "field_1"] + + with pytest.raises(TypeError): + len(v1[0, 0]) - # Test error cases with pytest.raises(ValueError, match="Must specify either 'fields' or 'num_fields'."): Vector.from_shape(shape=(2, 3)) with pytest.raises(ValueError, match="does not match length of fields"): - Vector.from_shape(shape=(2, 3), num_fields=3, fields=["field0", "field1"]) + Vector.from_shape(shape=(2, 3), num_fields=2, fields=["a", "b", "c"]) with pytest.raises(ValueError, match="Duplicate field names"): - Vector.from_shape(shape=(2, 3), fields=["field0", "field0", "field2"]) + Vector.from_shape(shape=(2, 3), fields=["a", "a"]) - def test_data_access(self): - """Test data access and assignment.""" - v = Vector.from_shape(shape=(2, 3), fields=["field0", "field1", "field2"]) + def test_indexing_and_array_contract(self): + v = make_grid_vector() - # Set data at specific indices - data1 = np.array([[1.0, 2.0, 3.0]]) - v[0, 0] = data1 - np.testing.assert_array_equal(v.get_data(0, 0), data1) # type: ignore + assert isinstance(v[:2, 1], Vector) + assert v[:2, 1].shape == (2,) + assert v[1].shape == (2,) + assert v[1, 1].shape == () + np.testing.assert_array_equal(v[-1, -1].array, np.array([[21.0, 121.0, 221.0]])) - # Test get_data method - assert np.array_equal(v.get_data(0, 0), data1) + with pytest.raises(ValueError): + _ = v[:, 1].array - # Test set_data method - data2 = np.array([[4.0, 5.0, 6.0]]) - v.set_data(data2, 0, 1) - assert np.array_equal(v.get_data(0, 1), data2) + result = v[[-1, 0], 1] + assert result.shape == (2,) + np.testing.assert_array_equal(result[0].array, np.array([[21.0, 121.0, 221.0]])) + np.testing.assert_array_equal(result[1].array, np.array([[1.0, 101.0, 201.0]])) - # Test error cases - with pytest.raises(IndexError): - v[2, 0] = data1 # Out of bounds + def test_select_fields_and_chaining_equivalence(self): + v = make_line_vector() - with pytest.raises(ValueError): - v[0, 0] = np.array([[1.0, 2.0]]) # Wrong number of fields + selected = v.select_fields("kx") + assert selected.fields == ["kx"] + assert selected.units == ["px"] + assert selected.shape == v.shape - with pytest.raises(ValueError): - v.set_data(np.array([[1.0, 2.0]]), 0, 0) # Wrong number of fields - - def test_field_operations(self): - """Test field-level operations.""" - v = Vector.from_shape(shape=(2, 3), fields=["field0", "field1", "field2"]) - - # Set initial data - v[0, 0] = np.array([[1.0, 2.0, 3.0]]) - v[0, 1] = np.array([[4.0, 5.0, 6.0]]) - v[0, 2] = np.array([[7.0, 8.0, 9.0]]) - - # Test field access - field_view = v["field0"] - assert ( - hasattr(field_view, "vector") - and hasattr(field_view, "field_name") - and hasattr(field_view, "field_index") + np.testing.assert_array_equal( + v.select_fields("kx")[2].array, + v[2].select_fields("kx").array, ) - # Test field operations - v["field0"] += 10 # type: ignore - np.testing.assert_array_equal(v.get_data(0, 0)[:, 0], np.array([11.0])) # type: ignore - np.testing.assert_array_equal(v.get_data(0, 1)[:, 0], np.array([14.0])) # type: ignore - np.testing.assert_array_equal(v.get_data(0, 2)[:, 0], np.array([17.0])) # type: ignore + with pytest.raises(KeyError): + v.select_fields("missing") - # Test applying a function to a field - v["field1"] *= 2 # Using multiplication instead of lambda # type: ignore - np.testing.assert_array_equal(v.get_data(0, 0)[:, 1], np.array([4.0])) # type: ignore - np.testing.assert_array_equal(v.get_data(0, 1)[:, 1], np.array([10.0])) # type: ignore - np.testing.assert_array_equal(v.get_data(0, 2)[:, 1], np.array([16.0])) # type: ignore + with pytest.raises(TypeError): + _ = v["kx"] - # Test field flattening - flat = v["field2"].flatten() - np.testing.assert_array_equal(flat, np.array([3.0, 6.0, 9.0])) # type: ignore + with pytest.raises(TypeError): + _ = v[1, "kx"] - # Test setting flattened data - v["field2"].set_flattened(np.array([18.0, 18.0, 18.0])) + def test_array_mutation_writes_through_for_single_field(self): + v = make_line_vector() + cell = v.select_fields("kx")[1].array + cell[0, 0] = 99.0 + assert v[1].array[0, 1] == 99.0 - # Test error cases - with pytest.raises(KeyError): - v["nonexistent_field"] + def test_field_arithmetic_with_scalar_and_ndarray(self): + v = make_line_vector() - with pytest.raises(ValueError): - v["field0"].set_flattened(np.array([1.0, 2.0])) # Wrong length - - def test_slicing(self): - """Test slicing operations.""" - v = Vector.from_shape(shape=(4, 3), fields=["field0", "field1", "field2"]) - - # Set data - for i in range(4): - for j in range(3): - v[i, j] = np.array( - [[float(i * 3 + j), float(i * 3 + j + 1), float(i * 3 + j + 2)]] - ) - - # Test slicing - sliced = v[1:3, 1] - assert isinstance(sliced, Vector) - assert sliced.shape == (2, 1) - - # Compare arrays directly - expected1 = np.array([[4.0, 5.0, 6.0]]) - expected2 = np.array([[7.0, 8.0, 9.0]]) - np.testing.assert_array_equal(sliced.get_data(0, 0), expected1) # type: ignore - np.testing.assert_array_equal(sliced.get_data(1, 0), expected2) # type: ignore - - # Test field access on sliced vector - field_sliced = sliced["field1"] - np.testing.assert_array_equal(field_sliced.flatten(), np.array([5.0, 8.0])) # type: ignore - - # Test copying slices of vectors - v[2:4, 1] = v[1:3, 1] - - # Test copying slices of vectors with fancy indexing - v[[0, 1], 1] = v[[2, 3], 0] - - def test_field_management(self): - """Test adding and removing fields.""" - v = Vector.from_shape(shape=(2, 3), fields=["field0", "field1", "field2"]) - - # Set initial data - v[0, 0] = np.array([[1.0, 2.0, 3.0]]) - - # Test adding fields - v.add_fields(["field3", "field4"]) - assert v.num_fields == 5 - assert v.fields == ["field0", "field1", "field2", "field3", "field4"] - assert v.units == ["none", "none", "none", "none", "none"] - - # Check that new fields are initialized to zeros - np.testing.assert_array_equal(v.get_data(0, 0)[:, 3:5], np.array([[0.0, 0.0]])) # type: ignore - - # Test removing fields - v.remove_fields(["field1", "field3"]) - assert v.num_fields == 3 - assert v.fields == ["field0", "field2", "field4"] - assert v.units == ["none", "none", "none"] - - # Check that data is preserved for remaining fields - np.testing.assert_array_equal(v.get_data(0, 0)[:, 0], np.array([1.0])) # type: ignore - np.testing.assert_array_equal(v.get_data(0, 0)[:, 1], np.array([3.0])) # type: ignore - np.testing.assert_array_equal(v.get_data(0, 0)[:, 2], np.array([0.0])) # type: ignore - - # Test error cases - with pytest.raises(ValueError): - v.add_fields(["field0"]) # Duplicate field - - v.remove_fields(["nonexistent_field"]) # Should just print a warning - - def test_copy(self): - """Test deep copying.""" - v = Vector.from_shape(shape=(2, 3), fields=["field0", "field1", "field2"]) - v[0, 0] = np.array([[1.0, 2.0, 3.0]]) - - # Create a copy - v_copy = v.copy() - - # Check that it's a deep copy - assert v_copy is not v - assert v_copy.shape == v.shape - assert v_copy.fields == v.fields - assert v_copy.units == v.units - np.testing.assert_array_equal(v_copy.get_data(0, 0), v.get_data(0, 0)) # type: ignore - - # Modify the copy and check that the original is unchanged - v_copy[0, 0] = np.array([[4.0, 5.0, 6.0]]) - np.testing.assert_array_equal(v.get_data(0, 0), np.array([[1.0, 2.0, 3.0]])) # type: ignore - - def test_flatten(self): - """Test flattening the entire vector.""" - v = Vector.from_shape(shape=(2, 3), fields=["field0", "field1", "field2"]) - - # Set data - v[0, 0] = np.array([[1.0, 2.0, 3.0]]) - v[0, 1] = np.array([[4.0, 5.0, 6.0]]) - v[0, 2] = np.array([[7.0, 8.0, 9.0]]) - v[1, 0] = np.array([[10.0, 11.0, 12.0]]) - v[1, 1] = np.array([[13.0, 14.0, 15.0]]) - v[1, 2] = np.array([[16.0, 17.0, 18.0]]) - - # Flatten the vector - flattened = v.flatten() - - # Check the flattened array - expected = np.array( - [ - [1.0, 2.0, 3.0], - [4.0, 5.0, 6.0], - [7.0, 8.0, 9.0], - [10.0, 11.0, 12.0], - [13.0, 14.0, 15.0], - [16.0, 17.0, 18.0], - ] + kx = v.select_fields("kx") + kx += 10 + np.testing.assert_array_equal(v.select_fields("kx").flatten(), np.array([[20.0], [30.0], [40.0], [50.0], [60.0], [70.0]])) + + v.select_fields("kx")[...] += np.arange(6) + np.testing.assert_array_equal(v.select_fields("kx").flatten(), np.array([[20.0], [31.0], [42.0], [53.0], [64.0], [75.0]])) + + summed = v.select_fields("intensity") + v.select_fields("ky") + np.testing.assert_array_equal( + summed.flatten(), + np.array([[101.0], [202.0], [303.0], [404.0], [505.0], [606.0]]), ) - np.testing.assert_array_equal(flattened, expected) # type: ignore - def test_from_data(self): - """Test creating a Vector from ragged lists or numpy arrays.""" - # Create test data - data = [ - np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), - np.array([[7.0, 8.0, 9.0]]), - np.array([[10.0, 11.0, 12.0], [13.0, 14.0, 15.0], [16.0, 17.0, 18.0]]), - ] + def test_field_assignment_from_vector_expression(self): + v = make_line_vector() + scale = 2.5 - # Test with explicit fields - v1 = Vector.from_data( - data=data, - fields=["field0", "field1", "field2"], - name="test_vector", - units=["unit0", "unit1", "unit2"], + v[:2].select_fields("intensity")[...] = v[2:4].select_fields("intensity") * scale + np.testing.assert_array_equal( + v[:2].select_fields("intensity").flatten(), + np.array([[10.0], [12.5], [15.0]]), ) - # Check properties - assert v1.shape == (3,) - assert v1.num_fields == 3 - assert v1.fields == ["field0", "field1", "field2"] - assert v1.units == ["unit0", "unit1", "unit2"] - assert v1.name == "test_vector" - - # Check data - np.testing.assert_array_equal(v1.get_data(0), data[0]) # type: ignore - np.testing.assert_array_equal(v1.get_data(1), data[1]) # type: ignore - np.testing.assert_array_equal(v1.get_data(2), data[2]) # type: ignore - - # Test with inferred fields - v2 = Vector.from_data(data=data, num_fields=3) - - # Check properties - assert v2.shape == (3,) - assert v2.num_fields == 3 - assert v2.fields == ["field_0", "field_1", "field_2"] - assert v2.units == ["none", "none", "none"] - - # Check data - np.testing.assert_array_equal(v2.get_data(0), data[0]) # type: ignore - np.testing.assert_array_equal(v2.get_data(1), data[1]) # type: ignore - np.testing.assert_array_equal(v2.get_data(2), data[2]) # type: ignore - - # Test error cases - with pytest.raises(TypeError, match="Data must be a list"): - Vector.from_data(data=np.array([1, 2, 3])) # type: ignore + def test_field_assignment_requires_matching_per_cell_row_counts(self): + v = make_line_vector() + with pytest.raises(ValueError, match="Per-cell row counts must match"): + v[:2].select_fields("intensity")[...] = v[1:3].select_fields("intensity") - with pytest.raises(ValueError, match="does not match length of fields"): - Vector.from_data( - data=data, - fields=["field0", "field1"], # Wrong number of fields - ) + def test_full_cell_assignment_allows_row_count_changes(self): + v = make_line_vector() - with pytest.raises(ValueError, match="Duplicate field names"): - Vector.from_data( - data=data, - fields=["field0", "field0", "field2"], # Duplicate field names - ) - - def test_fancy_indexing(self): - """Test fancy indexing with __getitem__ and __setitem__.""" - v = Vector.from_shape(shape=(3, 2), fields=["field0", "field1", "field2"]) - - # Set initial data - v[0, 0] = np.array([[1.0, 2.0, 3.0]]) - v[0, 1] = np.array([[4.0, 5.0, 6.0]]) - v[1, 0] = np.array([[7.0, 8.0, 9.0]]) - v[1, 1] = np.array([[10.0, 11.0, 12.0]]) - v[2, 0] = np.array([[13.0, 14.0, 15.0]]) - v[2, 1] = np.array([[16.0, 17.0, 18.0]]) - - # Test list indexing with __getitem__ - result = v[[0, 1], 0] - assert isinstance(result, Vector) - assert result.shape == (2, 1) - np.testing.assert_array_equal(result.get_data(0, 0), np.array([[1.0, 2.0, 3.0]])) - np.testing.assert_array_equal(result.get_data(1, 0), np.array([[7.0, 8.0, 9.0]])) - - # Test numpy array indexing with __getitem__ - result = v[np.array([1, 2]), 1] # type: ignore - assert isinstance(result, Vector) - assert result.shape == (2, 1) - np.testing.assert_array_equal(result.get_data(0, 0), np.array([[10.0, 11.0, 12.0]])) - np.testing.assert_array_equal(result.get_data(1, 0), np.array([[16.0, 17.0, 18.0]])) - - # Test fancy indexing with __setitem__ - new_data = [np.array([[20.0, 21.0, 22.0]]), np.array([[23.0, 24.0, 25.0]])] - v[[0, 2], 1] = new_data - np.testing.assert_array_equal(v.get_data(0, 1), new_data[0]) - np.testing.assert_array_equal(v.get_data(2, 1), new_data[1]) - - # Test numpy array fancy indexing with __setitem__ - new_data = [np.array([[26.0, 27.0, 28.0]]), np.array([[29.0, 30.0, 31.0]])] - v[np.array([1, 2]), 0] = new_data # type: ignore - np.testing.assert_array_equal(v.get_data(1, 0), new_data[0]) - np.testing.assert_array_equal(v.get_data(2, 0), new_data[1]) - - # Test error cases - with pytest.raises(IndexError): - v[[3, 4], 0] # Index out of bounds + v[1] = v[0] + assert v[1].array.shape == (2, 3) + np.testing.assert_array_equal(v[1].array, v[0].array) - with pytest.raises(IndexError): - v[[0, 1], 2] # Index out of bounds + v[0:2] = v[1:3] + assert v[0].array.shape == (2, 3) + assert v[1].array.shape == (2, 3) - with pytest.raises(ValueError): - v[[0, 1], 0] = [np.array([[1.0]])] # Wrong number of arrays + broadcast_cell = np.array([[9.0, 8.0, 7.0]]) + v[[0, 3]] = broadcast_cell + np.testing.assert_array_equal(v[0].array, broadcast_cell) + np.testing.assert_array_equal(v[3].array, broadcast_cell) - with pytest.raises(ValueError): - v[[0, 1], 0] = [ - np.array([[1.0]]), - np.array([[2.0]]), - ] # Wrong number of fields - - def test_get_data_methods(self): - """Test get_data method with various indexing scenarios.""" - v = Vector.from_shape(shape=(3, 2), fields=["field0", "field1", "field2"]) - - # Set some test data - v[0, 0] = np.array([[1.0, 2.0, 3.0]]) - v[0, 1] = np.array([[4.0, 5.0, 6.0]]) - v[1, 0] = np.array([[7.0, 8.0, 9.0]]) - v[1, 1] = np.array([[10.0, 11.0, 12.0]]) - v[2, 0] = np.array([[13.0, 14.0, 15.0]]) - v[2, 1] = np.array([[16.0, 17.0, 18.0]]) - - # Test single integer indexing - result = v.get_data(0, 0) - np.testing.assert_array_equal(result, np.array([[1.0, 2.0, 3.0]])) - - # Test list indexing - result = v.get_data([0, 1], 0) - np.testing.assert_array_equal(result[0], np.array([[1.0, 2.0, 3.0]])) - np.testing.assert_array_equal(result[1], np.array([[7.0, 8.0, 9.0]])) - - # Test numpy array indexing - result = v.get_data(np.array([1, 2]), 1) - np.testing.assert_array_equal(result[0], np.array([[10.0, 11.0, 12.0]])) - np.testing.assert_array_equal(result[1], np.array([[16.0, 17.0, 18.0]])) - - # Test slice indexing - result = v.get_data(slice(1, 3), 0) - np.testing.assert_array_equal(result[0], np.array([[7.0, 8.0, 9.0]])) - np.testing.assert_array_equal(result[1], np.array([[13.0, 14.0, 15.0]])) - - # Test error cases - with pytest.raises(ValueError, match="Expected 2 indices"): - v.get_data(0) # Too few indices - - with pytest.raises(ValueError, match="Expected 2 indices"): - v.get_data(0, 0, 0) # Too many indices + def test_boolean_indexing_is_axis_wise(self): + v = make_grid_vector() - with pytest.raises(IndexError): - v.get_data(3, 0) # Index out of bounds + rows = np.array([True, False, True]) + cols = np.array([False, True]) + selected = v[rows, cols] - with pytest.raises(IndexError): - v.get_data([3, 4], 0) # List index out of bounds - - def test_set_data_methods(self): - """Test set_data method with various indexing scenarios.""" - v = Vector.from_shape(shape=(3, 2), fields=["field0", "field1", "field2"]) - - # Test single integer indexing - data1 = np.array([[1.0, 2.0, 3.0]]) - v.set_data(data1, 0, 0) - np.testing.assert_array_equal(v.get_data(0, 0), data1) - - # Test list indexing - data2 = [np.array([[4.0, 5.0, 6.0]]), np.array([[7.0, 8.0, 9.0]])] - v.set_data(data2, [0, 1], 1) - np.testing.assert_array_equal(v.get_data(0, 1), data2[0]) - np.testing.assert_array_equal(v.get_data(1, 1), data2[1]) - - # Test numpy array indexing - data3 = [np.array([[10.0, 11.0, 12.0]]), np.array([[13.0, 14.0, 15.0]])] - v.set_data(data3, np.array([1, 2]), 0) - np.testing.assert_array_equal(v.get_data(1, 0), data3[0]) - np.testing.assert_array_equal(v.get_data(2, 0), data3[1]) - - # Test slice indexing - data4 = [np.array([[16.0, 17.0, 18.0]]), np.array([[19.0, 20.0, 21.0]])] - v.set_data(data4, slice(1, 3), 1) - np.testing.assert_array_equal(v.get_data(1, 1), data4[0]) - np.testing.assert_array_equal(v.get_data(2, 1), data4[1]) - - # Test error cases - with pytest.raises(ValueError, match="Expected 2 indices"): - v.set_data(data1, 0) # Too few indices - - with pytest.raises(ValueError, match="Expected 2 indices"): - v.set_data(data1, 0, 0, 0) # Too many indices + assert selected.shape == (2, 1) + np.testing.assert_array_equal(selected[0, 0].array, np.array([[1.0, 101.0, 201.0]])) + np.testing.assert_array_equal(selected[1, 0].array, np.array([[21.0, 121.0, 221.0]])) with pytest.raises(IndexError): - v.set_data(data1, 3, 0) # Index out of bounds + _ = v[np.array([[True, False], [False, True]])] - with pytest.raises(IndexError): - v.set_data([data1, data1], [3, 4], 0) # List index out of bounds + def test_empty_selection_is_valid_and_no_op_for_scalar_math(self): + v = make_grid_vector() + before = v.copy().flatten() - with pytest.raises(TypeError): - v.set_data([1, 2, 3], 0, 0) # Invalid data type # type: ignore + empty = v[[], :] + assert empty.shape == (0, 2) + assert empty.flatten().shape == (0, 3) - with pytest.raises(ValueError): - v.set_data(np.array([[1.0]]), 0, 0) # Wrong number of fields + empty.select_fields("kx")[...] += 1 + np.testing.assert_array_equal(v.flatten(), before) + + def test_add_fields_defaults_expression_and_multiple_values(self): + v = make_line_vector() + + v.add_fields(("h", "k")) + assert v.fields == ["intensity", "kx", "ky", "h", "k"] + assert np.isnan(v[0].array[:, 3:5]).all() + + v.add_fields("field_out", v.select_fields("kx") + v.select_fields("ky")) + np.testing.assert_array_equal( + v.select_fields("field_out").flatten(), + np.array([[110.0], [220.0], [330.0], [440.0], [550.0], [660.0]]), + ) + + v2 = make_line_vector() + v2.add_fields(("h", "k"), (1.0, np.array([5.0, 6.0, 7.0, 8.0, 9.0, 10.0]))) + np.testing.assert_array_equal(v2.select_fields("h").flatten(), np.ones((6, 1))) + np.testing.assert_array_equal( + v2.select_fields("k").flatten(), + np.array([[5.0], [6.0], [7.0], [8.0], [9.0], [10.0]]), + ) + + def test_remove_fields_preserves_remaining_data(self): + v = make_line_vector() + v.add_fields("extra", 1.0) + v.remove_fields(("kx", "extra")) + + assert v.fields == ["intensity", "ky"] + np.testing.assert_array_equal( + v[0].array, + np.array([[1.0, 100.0], [2.0, 200.0]]), + ) + + def test_copy_is_deep(self): + v = make_line_vector() + v_copy = v.select_fields(["intensity", "kx"]).copy() + + v_copy[0].array[0, 0] = -1.0 + assert v[0].array[0, 0] == 1.0 + assert v_copy.fields == ["intensity", "kx"] + assert v_copy.shape == (4,) + + def test_from_data_supports_nested_fixed_grid(self): + data = [ + [np.array([[1.0, 2.0]]), np.array([[3.0, 4.0], [5.0, 6.0]])], + [np.array([[7.0, 8.0]]), np.array([[9.0, 10.0]])], + ] + v = Vector.from_data(data=data, fields=["a", "b"], units=["u1", "u2"], name="nested") + + assert v.shape == (2, 2) + assert v.fields == ["a", "b"] + assert v.units == ["u1", "u2"] + assert v.name == "nested" + np.testing.assert_array_equal(v[0, 1].array, np.array([[3.0, 4.0], [5.0, 6.0]])) + + with pytest.raises(TypeError, match="Data must be a list"): + Vector.from_data(data=np.array([1, 2, 3])) # type: ignore[arg-type] + + with pytest.raises(ValueError, match="same number of fields"): + Vector.from_data(data=[np.array([[1.0, 2.0]]), np.array([[1.0, 2.0, 3.0]])]) From 7abc130b56aa2720e7991bb287cbfe3ba6173593 Mon Sep 17 00:00:00 2001 From: cophus Date: Sun, 1 Mar 2026 11:48:36 -0800 Subject: [PATCH 106/335] Fixes for writing / reading, new internals --- src/quantem/core/datastructures/vector.py | 992 ++++++++++-------- .../core/visualization/visualization_utils.py | 17 + tests/datastructures/test_vector.py | 41 + .../visualization/test_visualization_utils.py | 8 + 4 files changed, 631 insertions(+), 427 deletions(-) diff --git a/src/quantem/core/datastructures/vector.py b/src/quantem/core/datastructures/vector.py index 4717fe64..53592dcd 100644 --- a/src/quantem/core/datastructures/vector.py +++ b/src/quantem/core/datastructures/vector.py @@ -1,8 +1,7 @@ from __future__ import annotations import copy -from dataclasses import dataclass -from typing import Any, Iterable, Sequence, overload +from typing import Any, Sequence, overload import numpy as np from numpy.typing import NDArray @@ -16,23 +15,13 @@ ) -@dataclass -class _VectorState: - shape: tuple[int, ...] - data: NDArray[np.object_] - fields: list[str] - units: list[str] - name: str - metadata: dict[str, Any] - - class Vector(AutoSerialize): """Ragged cell data on a fixed grid. - A ``Vector`` has fixed grid dimensions (``shape``). Each fixed-grid cell stores a - NumPy array with shape ``(n_rows, num_fields)``. Selections always return new - ``Vector`` views over the same backing store, while raw NumPy extraction is explicit - via ``.array`` and ``.flatten()``. + Storage is compact and AutoSerialize-friendly: + - one 2D numeric row buffer for all ragged rows + - one start array and one length array for cell boundaries + - optional selection state for views """ def __init__( @@ -43,31 +32,40 @@ def __init__( name: str | None = None, metadata: dict[str, Any] | None = None, ) -> None: - validated_shape = validate_shape(shape) - validated_fields = validate_fields(list(fields)) - validated_units = validate_vector_units(list(units) if units is not None else None, len(validated_fields)) - self._state = _VectorState( - shape=validated_shape, - data=_empty_storage(validated_shape, len(validated_fields)), - fields=list(validated_fields), - units=list(validated_units), - name=name or f"{len(validated_shape)}d ragged array", - metadata=dict(metadata or {}), + root_shape = validate_shape(shape) + root_fields = validate_fields(list(fields)) + root_units = validate_vector_units( + list(units) if units is not None else None, + len(root_fields), ) - self._coords = _root_coords(validated_shape) - self._field_names = tuple(validated_fields) + + self._state = { + "shape": root_shape, + "fields": list(root_fields), + "units": list(root_units), + "name": name or f"{len(root_shape)}d ragged array", + "metadata": dict(metadata or {}), + "data": np.empty((0, len(root_fields)), dtype=float), + "cell_starts": np.zeros(_cell_count(root_shape), dtype=np.int64), + "cell_lengths": np.zeros(_cell_count(root_shape), dtype=np.int64), + } + self._selection_shape = root_shape + self._selection_indices: NDArray[np.int64] | None = None + self._selected_fields: tuple[str, ...] | None = None @classmethod - def _from_state( + def _from_view( cls, - state: _VectorState, - coords: NDArray[np.object_], - field_names: Sequence[str], + state: dict[str, Any], + selection_shape: tuple[int, ...], + selection_indices: NDArray[np.int64] | None, + selected_fields: tuple[str, ...] | None, ) -> "Vector": obj = cls.__new__(cls) obj._state = state - obj._coords = coords - obj._field_names = tuple(field_names) + obj._selection_shape = selection_shape + obj._selection_indices = None if selection_indices is None else selection_indices.astype(np.int64, copy=False) + obj._selected_fields = selected_fields return obj @classmethod @@ -80,23 +78,18 @@ def from_shape( name: str | None = None, ) -> "Vector": if fields is not None: - validated_fields = validate_fields(list(fields)) - if num_fields is not None and len(validated_fields) != num_fields: + root_fields = validate_fields(list(fields)) + if num_fields is not None and len(root_fields) != num_fields: raise ValueError( - f"num_fields ({num_fields}) does not match length of fields ({len(validated_fields)})" + f"num_fields ({num_fields}) does not match length of fields ({len(root_fields)})" ) elif num_fields is not None: - validated_count = validate_num_fields(num_fields) - validated_fields = [f"field_{i}" for i in range(validated_count)] + count = validate_num_fields(num_fields) + root_fields = [f"field_{i}" for i in range(count)] else: raise ValueError("Must specify either 'fields' or 'num_fields'.") - return cls( - shape=shape, - fields=validated_fields, - units=units, - name=name, - ) + return cls(shape=shape, fields=root_fields, units=units, name=name) @classmethod def from_data( @@ -107,79 +100,80 @@ def from_data( units: Sequence[str] | None = None, name: str | None = None, ) -> "Vector": - inferred_shape, normalized_cells = _normalize_nested_data(data) - inferred_num_fields = normalized_cells[0].shape[1] if normalized_cells else 0 + root_shape, cell_arrays = _normalize_nested_data(data) + inferred_counts = {array.shape[1] for array in cell_arrays} + if len(inferred_counts) > 1: + raise ValueError("All cell arrays must have the same number of fields.") + inferred_fields = cell_arrays[0].shape[1] if cell_arrays else 0 if fields is not None: - validated_fields = validate_fields(list(fields)) - if len(validated_fields) != inferred_num_fields: + root_fields = validate_fields(list(fields)) + if len(root_fields) != inferred_fields: raise ValueError( - f"num_fields ({inferred_num_fields}) does not match length of fields ({len(validated_fields)})" + f"num_fields ({inferred_fields}) does not match length of fields ({len(root_fields)})" ) elif num_fields is not None: - validated_num_fields = validate_num_fields(num_fields) - if validated_num_fields != inferred_num_fields: + count = validate_num_fields(num_fields) + if count != inferred_fields: raise ValueError( - f"Provided num_fields ({validated_num_fields}) does not match inferred ({inferred_num_fields})." + f"Provided num_fields ({count}) does not match inferred ({inferred_fields})." ) - validated_fields = [f"field_{i}" for i in range(validated_num_fields)] + root_fields = [f"field_{i}" for i in range(count)] else: - validated_fields = [f"field_{i}" for i in range(inferred_num_fields)] + root_fields = [f"field_{i}" for i in range(inferred_fields)] - vector = cls( - shape=inferred_shape, - fields=validated_fields, - units=units, - name=name, - ) - for coord, array in zip(np.ndindex(inferred_shape), normalized_cells): - vector._state.data[coord] = array.copy() + vector = cls(shape=root_shape, fields=root_fields, units=units, name=name) + vector._replace_cells(np.arange(len(cell_arrays), dtype=np.int64), cell_arrays) return vector @property def shape(self) -> tuple[int, ...]: - return self._coords.shape + return self._selection_shape @property def fields(self) -> list[str]: - return list(self._field_names) + if self._selected_fields is None: + return list(self._state["fields"]) + return list(self._selected_fields) @property def units(self) -> list[str]: - lookup = {name: unit for name, unit in zip(self._state.fields, self._state.units)} - return [lookup[name] for name in self._field_names] + lookup = { + field: unit + for field, unit in zip(self._state["fields"], self._state["units"]) + } + return [lookup[field] for field in self.fields] @property def num_fields(self) -> int: - return len(self._field_names) + return len(self.fields) @property def name(self) -> str: - return self._state.name + return self._state["name"] @name.setter def name(self, value: str) -> None: - self._state.name = str(value) + self._state["name"] = str(value) @property def metadata(self) -> dict[str, Any]: - return self._state.metadata + return self._state["metadata"] @property def array(self) -> NDArray[np.generic]: if self.shape != (): raise ValueError(".array is only valid when the selection contains exactly one cell.") - coord = self._coords[()] - cell = self._state.data[coord] - indices = self._field_indices() - if len(indices) == len(self._state.fields) and indices == list(range(len(self._state.fields))): + cell = self._cell_matrix(self._selected_cell_indices()[0]) + cols = self._field_indices() + if cols.size == self._full_num_fields: return cell - if len(indices) == 1: - idx = indices[0] - return cell[:, idx : idx + 1] - if _is_contiguous(indices): - return cell[:, indices[0] : indices[-1] + 1] - return cell[:, indices].copy() + if cols.size == 1: + col = int(cols[0]) + return cell[:, col : col + 1] + if _is_contiguous(cols): + return cell[:, int(cols[0]) : int(cols[-1]) + 1] + return cell[:, cols].copy() def __len__(self) -> int: if self.shape == (): @@ -187,44 +181,58 @@ def __len__(self) -> int: return self.shape[0] def __repr__(self) -> str: - return f"quantem.Vector(shape={self.shape}, fields={self.fields}, name={self.name!r})" + return "\n".join( + [ + f"quantem.Vector, shape={self.shape}, name={self.name}", + f" fields = {self.fields}", + f" units: {self.units}", + ] + ) __str__ = __repr__ def copy(self) -> "Vector": - copied = self.__class__._from_state( - _VectorState( - shape=self.shape, - data=_empty_storage(self.shape, self.num_fields), - fields=self.fields, - units=self.units, - name=self.name, - metadata=copy.deepcopy(self.metadata), - ), - _root_coords_allow_empty(self.shape), - self.fields, + copied = self.__class__( + shape=self.shape, + fields=self.fields, + units=self.units, + name=self.name, + metadata=copy.deepcopy(self.metadata), ) - for coord, array in zip(np.ndindex(self.shape) if self.shape != () else [()], self._iter_selected_arrays()): - copied._state.data[coord] = array.copy() + target_cells = copied._selected_cell_indices() + source_arrays = [self._selected_cell_matrix(index).copy() for index in self._selected_cell_indices()] + copied._replace_cells(target_cells, source_arrays) return copied def flatten(self) -> NDArray[np.generic]: - arrays = [array for array in self._iter_selected_arrays() if array.shape[0] > 0] + arrays = [ + self._selected_cell_matrix(index) + for index in self._selected_cell_indices() + if self._cell_row_count(index) > 0 + ] if arrays: return np.vstack(arrays) - dtype = float - for array in self._iter_selected_arrays(): - dtype = array.dtype - break + + dtype = self._state["data"].dtype if self._state["data"].ndim == 2 else float return np.empty((0, self.num_fields), dtype=dtype) def select_fields(self, field_names: str | Sequence[str]) -> "Vector": - normalized = _normalize_field_names(field_names) - available = set(self._field_names) - missing = [name for name in normalized if name not in available] + selected = _normalize_field_names(field_names) + available = set(self.fields) + missing = [field for field in selected if field not in available] if missing: raise KeyError(f"Unknown field(s): {missing}") - return self._from_state(self._state, self._coords, normalized) + + if selected == tuple(self._state["fields"]): + selected_fields = None + else: + selected_fields = selected + return self._from_view( + self._state, + self.shape, + self._selection_indices, + selected_fields, + ) def add_fields( self, @@ -232,56 +240,49 @@ def add_fields( values: Any | None = None, units: str | Sequence[str] | None = None, ) -> None: - new_names = _normalize_field_names(names) - if any(name in self._state.fields for name in new_names): + new_fields = _normalize_field_names(names) + if any(field in self._state["fields"] for field in new_fields): raise ValueError("One or more new field names already exist.") - new_units = _normalize_units(units, len(new_names)) - old_fields = list(self._state.fields) - self._state.fields.extend(new_names) - self._state.units.extend(new_units) - - old_width = len(old_fields) - new_width = len(self._state.fields) - for coord in np.ndindex(self._state.shape) if self._state.shape != () else [()]: - current = self._state.data[coord] - promoted = np.result_type(current.dtype, float) - expanded = np.full((current.shape[0], new_width), np.nan, dtype=promoted) - expanded[:, :old_width] = current - self._state.data[coord] = expanded - - if list(self._field_names) == old_fields: - self._field_names = tuple(self._state.fields) + new_units = _normalize_units(units, len(new_fields)) + old_fields = list(self._state["fields"]) + self._state["fields"].extend(new_fields) + self._state["units"].extend(new_units) + self._expand_storage(len(new_fields)) - target = self._from_state(self._state, self._coords, new_names) if values is None: return - if len(new_names) > 1 and isinstance(values, (list, tuple)) and len(values) == len(new_names): - for name, value in zip(new_names, values): - target.select_fields(name)._assign(value) + target = self.select_fields(list(new_fields)) + if len(new_fields) > 1 and isinstance(values, (list, tuple)) and len(values) == len(new_fields): + for field, value in zip(new_fields, values): + target.select_fields(field)[...] = value else: - target._assign(values) + target[...] = values + + if self._selected_fields is not None and tuple(old_fields) == self._selected_fields: + self._selected_fields = None def remove_fields(self, names: str | Sequence[str]) -> None: - to_remove = _normalize_field_names(names) - missing = [name for name in to_remove if name not in self._state.fields] + to_remove = set(_normalize_field_names(names)) + old_fields = self._state["fields"] + old_units = self._state["units"] + + missing = [field for field in to_remove if field not in old_fields] if missing: raise KeyError(f"Unknown field(s): {missing}") - if len(to_remove) == len(self._state.fields): + if len(to_remove) == len(old_fields): raise ValueError("Cannot remove all fields from a Vector.") - remove_set = set(to_remove) - keep_names = [name for name in self._state.fields if name not in remove_set] - keep_indices = [self._state.fields.index(name) for name in keep_names] - keep_units = [self._state.units[index] for index in keep_indices] + keep = [i for i, field in enumerate(old_fields) if field not in to_remove] + self._state["fields"] = [old_fields[i] for i in keep] + self._state["units"] = [old_units[i] for i in keep] + self._state["data"] = self._state["data"][:, keep] - for coord in np.ndindex(self._state.shape) if self._state.shape != () else [()]: - self._state.data[coord] = self._state.data[coord][:, keep_indices] - - self._state.fields = keep_names - self._state.units = keep_units - self._field_names = tuple(name for name in self._field_names if name in keep_names) + if self._selected_fields is not None: + self._selected_fields = tuple(field for field in self._selected_fields if field in self._state["fields"]) + if len(self._selected_fields) == len(self._state["fields"]): + self._selected_fields = None def get_data(self, *indices: Any) -> NDArray[np.generic] | list[NDArray[np.generic]]: if len(indices) != len(self.shape): @@ -289,7 +290,7 @@ def get_data(self, *indices: Any) -> NDArray[np.generic] | list[NDArray[np.gener selection = self[indices if len(indices) != 1 else indices[0]] if selection.shape == (): return selection.array - return [array.copy() for array in selection._iter_selected_arrays()] + return [selection._selected_cell_matrix(index).copy() for index in selection._selected_cell_indices()] def set_data(self, value: Any, *indices: Any) -> None: if len(indices) != len(self.shape): @@ -300,15 +301,29 @@ def set_data(self, value: Any, *indices: Any) -> None: def __getitem__(self, idx: Any) -> "Vector": ... def __getitem__(self, idx: Any) -> "Vector": - if _is_field_selector(idx): + if _looks_like_field_selector(idx): raise TypeError("Use select_fields(...) for field selection.") - coords = _select_coords(self._coords, idx) - return self._from_state(self._state, coords, self._field_names) + if idx is Ellipsis: + return self + + selection_shape, selection_indices = _select_linear_indices( + self.shape, + self._selected_cell_indices(), + idx, + ) + return self._from_view( + self._state, + selection_shape, + selection_indices, + self._selected_fields, + ) def __setitem__(self, idx: Any, value: Any) -> None: - if _is_field_selector(idx): - raise TypeError("Use select_fields(...) for field selection.") - self[idx]._assign(value) + if idx is Ellipsis: + target = self + else: + target = self[idx] + target._assign(value) def __add__(self, other: Any) -> "Vector": return self._binary_op(other, np.add) @@ -322,51 +337,137 @@ def __mul__(self, other: Any) -> "Vector": def __truediv__(self, other: Any) -> "Vector": return self._binary_op(other, np.divide) - def __pow__(self, other: Any) -> "Vector": - return self._binary_op(other, np.power) + def __radd__(self, other: Any) -> "Vector": + return self._binary_op(other, np.add, reverse=True) + + def __rmul__(self, other: Any) -> "Vector": + return self._binary_op(other, np.multiply, reverse=True) + + def __rsub__(self, other: Any) -> "Vector": + return self._binary_op(other, np.subtract, reverse=True) + + def __rtruediv__(self, other: Any) -> "Vector": + return self._binary_op(other, np.divide, reverse=True) def __iadd__(self, other: Any) -> "Vector": - return self._binary_op_inplace(other, np.add) + self._inplace_op(other, np.add) + return self def __isub__(self, other: Any) -> "Vector": - return self._binary_op_inplace(other, np.subtract) + self._inplace_op(other, np.subtract) + return self def __imul__(self, other: Any) -> "Vector": - return self._binary_op_inplace(other, np.multiply) + self._inplace_op(other, np.multiply) + return self def __itruediv__(self, other: Any) -> "Vector": - return self._binary_op_inplace(other, np.divide) + self._inplace_op(other, np.divide) + return self - def __ipow__(self, other: Any) -> "Vector": - return self._binary_op_inplace(other, np.power) + @property + def _full_num_fields(self) -> int: + return len(self._state["fields"]) - def _binary_op(self, other: Any, op: Any) -> "Vector": - result = self.copy() - result._binary_op_inplace(other, op) - return result + def _field_indices(self) -> NDArray[np.int64]: + if self._selected_fields is None: + return np.arange(self._full_num_fields, dtype=np.int64) - def _binary_op_inplace(self, other: Any, op: Any) -> "Vector": - row_counts = self._row_counts() - targets = list(self._iter_coords()) + lookup = {field: i for i, field in enumerate(self._state["fields"])} + try: + return np.array([lookup[field] for field in self._selected_fields], dtype=np.int64) + except KeyError as exc: + raise KeyError(f"Unknown field(s): {[str(exc.args[0])]}") from exc - if isinstance(other, Vector): - self._validate_vector_rhs(other, require_matching_rows=True) - source_arrays = list(other._iter_selected_arrays()) - for coord, current, rhs in zip(targets, self._iter_selected_arrays(), source_arrays): - updated = current.copy() - op(updated, rhs, out=updated, casting="same_kind") - self._write_selected_array(coord, updated) - return self + def _selected_cell_indices(self) -> NDArray[np.int64]: + if self._selection_indices is None: + return np.arange(_cell_count(self._state["shape"]), dtype=np.int64) + return self._selection_indices + + def _is_full_field_selection(self) -> bool: + return self._selected_fields is None + + def _cell_row_count(self, linear_index: int) -> int: + return int(self._state["cell_lengths"][linear_index]) + + def _cell_matrix(self, linear_index: int) -> NDArray[np.generic]: + start = int(self._state["cell_starts"][linear_index]) + length = int(self._state["cell_lengths"][linear_index]) + if length == 0: + return self._state["data"][0:0] + return self._state["data"][start : start + length] + + def _selected_cell_matrix(self, linear_index: int) -> NDArray[np.generic]: + cell = self._cell_matrix(linear_index) + cols = self._field_indices() + if cols.size == self._full_num_fields: + return cell + if cols.size == 1: + col = int(cols[0]) + return cell[:, col : col + 1] + if _is_contiguous(cols): + return cell[:, int(cols[0]) : int(cols[-1]) + 1] + return cell[:, cols].copy() + + def _replace_cells(self, targets: NDArray[np.int64], arrays: Sequence[NDArray[np.generic]]) -> None: + if len(targets) != len(arrays): + raise ValueError("Target cell count does not match source cell count.") + if len(targets) == 0: + return + + normalized = [_coerce_cell_array(array, self._full_num_fields) for array in arrays] + payloads = [array for array in normalized if array.shape[0] > 0] + if payloads: + appended = np.vstack(payloads) + data = self._state["data"] + if data.shape[0] == 0: + self._state["data"] = appended.copy() + else: + self._state["data"] = np.concatenate((data, appended), axis=0) + + cursor = self._state["data"].shape[0] - sum(array.shape[0] for array in normalized) + for target, array in zip(targets, normalized): + self._state["cell_starts"][target] = cursor + self._state["cell_lengths"][target] = array.shape[0] + cursor += array.shape[0] + + self._maybe_compact_storage() + + def _expand_storage(self, num_new_fields: int) -> None: + data = self._state["data"] + if data.shape[0] == 0: + dtype = np.result_type(data.dtype, float) + self._state["data"] = np.empty((0, data.shape[1] + num_new_fields), dtype=dtype) + return + + dtype = np.result_type(data.dtype, float) + filler = np.full((data.shape[0], num_new_fields), np.nan, dtype=dtype) + self._state["data"] = np.concatenate((data.astype(dtype, copy=False), filler), axis=1) - rhs_matrix = _broadcast_rhs(np.asarray(other), sum(row_counts), self.num_fields) + def _maybe_compact_storage(self) -> None: + data = self._state["data"] + used_rows = int(self._state["cell_lengths"].sum()) + if data.shape[0] <= used_rows + 1024: + return + if used_rows == 0: + self._state["data"] = np.empty((0, self._full_num_fields), dtype=data.dtype) + self._state["cell_starts"].fill(0) + return + if data.shape[0] <= 2 * used_rows: + return + + compacted = np.empty((used_rows, self._full_num_fields), dtype=data.dtype) + starts = np.zeros_like(self._state["cell_starts"]) cursor = 0 - for coord, current, row_count in zip(targets, self._iter_selected_arrays(), row_counts): - chunk = rhs_matrix[cursor : cursor + row_count] - cursor += row_count - updated = current.copy() - op(updated, chunk, out=updated, casting="same_kind") - self._write_selected_array(coord, updated) - return self + for linear_index in range(_cell_count(self._state["shape"])): + length = self._cell_row_count(linear_index) + starts[linear_index] = cursor + if length > 0: + cell = self._cell_matrix(linear_index) + compacted[cursor : cursor + length] = cell + cursor += length + self._state["data"] = compacted + self._state["cell_starts"] = starts def _assign(self, value: Any) -> None: if self._is_full_field_selection(): @@ -375,131 +476,122 @@ def _assign(self, value: Any) -> None: self._assign_selected_fields(value) def _assign_full_cells(self, value: Any) -> None: - targets = list(self._iter_coords()) + targets = self._selected_cell_indices() if isinstance(value, Vector): - self._validate_vector_rhs(value, require_matching_rows=False) - for target, source in zip(targets, value._iter_selected_arrays()): - self._state.data[target] = source.copy() + source_cells = value._selected_cell_indices() + if len(targets) != len(source_cells): + raise ValueError(f"Expected {len(targets)} cells, got {len(source_cells)}") + if value.num_fields != self.num_fields: + raise ValueError( + f"Expected {self.num_fields} fields, got {value.num_fields}" + ) + arrays = [value._selected_cell_matrix(index).copy() for index in source_cells] + self._replace_cells(targets, arrays) return array = _coerce_cell_array(value, self.num_fields) - for target in targets: - self._state.data[target] = array.copy() + self._replace_cells(targets, [array] * len(targets)) def _assign_selected_fields(self, value: Any) -> None: - targets = list(self._iter_coords()) - row_counts = self._row_counts() + targets = self._selected_cell_indices() + field_indices = self._field_indices() + row_counts = [self._cell_row_count(index) for index in targets] + total_rows = sum(row_counts) + if isinstance(value, Vector): - self._validate_vector_rhs(value, require_matching_rows=True) - for coord, source in zip(targets, value._iter_selected_arrays()): - self._write_selected_array(coord, source) + source_cells = value._selected_cell_indices() + if len(targets) != len(source_cells): + raise ValueError(f"Expected {len(targets)} cells, got {len(source_cells)}") + if value.num_fields != self.num_fields: + raise ValueError( + f"Expected {self.num_fields} fields, got {value.num_fields}" + ) + source_counts = [value._cell_row_count(index) for index in source_cells] + if row_counts != source_counts: + raise ValueError("Per-cell row counts must match for field-selected assignment.") + snapshots = [value._selected_cell_matrix(index).copy() for index in source_cells] + for target, array in zip(targets, snapshots): + cell = self._cell_matrix(int(target)) + if array.shape[0] > 0: + cell[:, field_indices] = array return if np.isscalar(value): - for coord in targets: - current = self._selected_array_for_coord(coord) - fill = np.full(current.shape, value) - self._write_selected_array(coord, fill) + for target in targets: + cell = self._cell_matrix(int(target)) + if cell.shape[0] > 0: + cell[:, field_indices] = value return - rhs_matrix = _broadcast_rhs(np.asarray(value), sum(row_counts), self.num_fields) + broadcast = _broadcast_field_values(value, total_rows, self.num_fields) cursor = 0 - for coord, row_count in zip(targets, row_counts): - chunk = rhs_matrix[cursor : cursor + row_count] - cursor += row_count - self._write_selected_array(coord, chunk) - - def _validate_vector_rhs(self, other: "Vector", require_matching_rows: bool) -> None: - if self.num_fields != other.num_fields: - raise ValueError(f"Expected {self.num_fields} fields, got {other.num_fields}") - if self._coords.size != other._coords.size: - raise ValueError(f"Expected {self._coords.size} cells, got {other._coords.size}") - if require_matching_rows: - left = self._row_counts() - right = other._row_counts() - if left != right: - raise ValueError(f"Per-cell row counts must match: {left} != {right}") - - def _row_counts(self) -> list[int]: - return [self._state.data[coord].shape[0] for coord in self._iter_coords()] - - def _iter_coords(self) -> Iterable[tuple[int, ...]]: - return iter(self._coords.flat) - - def _iter_selected_arrays(self) -> Iterable[NDArray[np.generic]]: - for coord in self._iter_coords(): - yield self._selected_array_for_coord(coord) - - def _selected_array_for_coord(self, coord: tuple[int, ...]) -> NDArray[np.generic]: - cell = self._state.data[coord] - indices = self._field_indices() - if self._is_full_field_selection(): - return cell - if len(indices) == 1: - idx = indices[0] - return cell[:, idx : idx + 1] - if _is_contiguous(indices): - return cell[:, indices[0] : indices[-1] + 1] - return cell[:, indices].copy() - - def _write_selected_array(self, coord: tuple[int, ...], values: NDArray[np.generic]) -> None: - cell = self._state.data[coord] - if self._is_full_field_selection(): - replacement = _coerce_cell_array(values, self.num_fields) - self._state.data[coord] = replacement.copy() - return - - if values.ndim != 2 or values.shape[1] != self.num_fields: - raise ValueError( - f"Expected array with shape (_, {self.num_fields}), got {values.shape}" - ) - if values.shape[0] != cell.shape[0]: - raise ValueError( - f"Expected {cell.shape[0]} rows for in-place field update, got {values.shape[0]}" - ) - cell[:, self._field_indices()] = values - - def _field_indices(self) -> list[int]: - lookup = {name: idx for idx, name in enumerate(self._state.fields)} - try: - return [lookup[name] for name in self._field_names] - except KeyError as exc: - raise KeyError(f"Unknown field '{exc.args[0]}'") from exc - - def _is_full_field_selection(self) -> bool: - return list(self._field_names) == self._state.fields + for target, rows in zip(targets, row_counts): + chunk = broadcast[cursor : cursor + rows] + cell = self._cell_matrix(int(target)) + if rows > 0: + cell[:, field_indices] = chunk + cursor += rows + + def _binary_op(self, other: Any, op: Any, reverse: bool = False) -> "Vector": + result = self.copy() + result._inplace_op(other, op, reverse=reverse) + return result + def _inplace_op(self, other: Any, op: Any, reverse: bool = False) -> None: + targets = self._selected_cell_indices() + field_indices = self._field_indices() + row_counts = [self._cell_row_count(index) for index in targets] + total_rows = sum(row_counts) -def _empty_storage(shape: tuple[int, ...], num_fields: int) -> NDArray[np.object_]: - storage = np.empty(shape if shape != () else (), dtype=object) - for coord in np.ndindex(shape) if shape != () else [()]: - storage[coord] = np.empty((0, num_fields), dtype=float) - return storage + if isinstance(other, Vector): + source_cells = other._selected_cell_indices() + if len(targets) != len(source_cells): + raise ValueError(f"Expected {len(targets)} cells, got {len(source_cells)}") + if other.num_fields != self.num_fields: + raise ValueError(f"Expected {self.num_fields} fields, got {other.num_fields}") + source_counts = [other._cell_row_count(index) for index in source_cells] + if row_counts != source_counts: + raise ValueError("Per-cell row counts must match for Vector arithmetic.") + snapshots = [other._selected_cell_matrix(index).copy() for index in source_cells] + for target, rhs in zip(targets, snapshots): + cell = self._cell_matrix(int(target)) + lhs = cell[:, field_indices] + cell[:, field_indices] = op(rhs, lhs) if reverse else op(lhs, rhs) + return + if np.isscalar(other): + for target in targets: + cell = self._cell_matrix(int(target)) + lhs = cell[:, field_indices] + if lhs.shape[0] > 0: + cell[:, field_indices] = op(other, lhs) if reverse else op(lhs, other) + return -def _root_coords(shape: tuple[int, ...]) -> NDArray[np.object_]: - return _root_coords_allow_empty(shape) + broadcast = _broadcast_field_values(other, total_rows, self.num_fields) + cursor = 0 + for target, rows in zip(targets, row_counts): + chunk = broadcast[cursor : cursor + rows] + cell = self._cell_matrix(int(target)) + lhs = cell[:, field_indices] + if rows > 0: + cell[:, field_indices] = op(chunk, lhs) if reverse else op(lhs, chunk) + cursor += rows -def _root_coords_allow_empty(shape: tuple[int, ...]) -> NDArray[np.object_]: - coords = np.empty(shape if shape != () else (), dtype=object) - for coord in np.ndindex(shape) if shape != () else [()]: - coords[coord] = coord - return coords +def _cell_count(shape: tuple[int, ...]) -> int: + return int(np.prod(shape, dtype=np.int64)) if shape else 1 -def _normalize_field_names(field_names: str | Sequence[str]) -> list[str]: +def _normalize_field_names(field_names: str | Sequence[str]) -> tuple[str, ...]: if isinstance(field_names, str): - names = [field_names] - elif isinstance(field_names, Sequence): - names = [str(name) for name in field_names] + normalized = (field_names,) else: - raise TypeError("Field names must be a string or a sequence of strings.") - if not names: - raise ValueError("Must select at least one field.") - if len(set(names)) != len(names): - raise ValueError("Duplicate field names are not allowed.") - return names + normalized = tuple(field_names) + if not normalized: + raise ValueError("At least one field name is required.") + validate_fields(list(normalized)) + return normalized + def _normalize_units(units: str | Sequence[str] | None, count: int) -> list[str]: @@ -507,188 +599,234 @@ def _normalize_units(units: str | Sequence[str] | None, count: int) -> list[str] return ["none"] * count if isinstance(units, str): if count != 1: - raise ValueError("A single unit string is only valid for a single new field.") + raise ValueError("A single unit can only be provided for a single field.") return [units] - normalized = [str(unit) for unit in units] + normalized = list(units) if len(normalized) != count: raise ValueError(f"Expected {count} units, got {len(normalized)}") return normalized + +def _looks_like_field_selector(idx: Any) -> bool: + if isinstance(idx, str): + return True + if isinstance(idx, tuple) and any(_looks_like_field_selector(item) for item in idx): + return True + if isinstance(idx, (list, tuple)) and idx and all(isinstance(item, str) for item in idx): + return True + return False + + + def _coerce_cell_array(value: Any, num_fields: int) -> NDArray[np.generic]: - array = np.asarray(value) - if array.ndim != 2 or array.shape[1] != num_fields: - raise ValueError(f"Expected a numpy array with shape (_, {num_fields}), got {array.shape}") - if not np.issubdtype(array.dtype, np.number): - raise TypeError("Cell arrays must contain numeric values.") + if isinstance(value, Vector): + if value.shape != (): + raise ValueError("Expected a 0D Vector for single-cell assignment.") + array = value.array.copy() + else: + array = np.asarray(value) + + if array.ndim == 0: + raise ValueError("Cell assignment requires a 2D array.") + if array.ndim == 1: + if array.size == 0: + array = np.empty((0, num_fields), dtype=float) + elif num_fields == 1: + array = array.reshape(-1, 1) + else: + array = array.reshape(1, -1) + if array.ndim != 2: + raise ValueError("Cell assignment requires a 2D array.") + if array.shape[1] != num_fields: + raise ValueError(f"Expected {num_fields} fields, got {array.shape[1]}") return array -def _broadcast_rhs(value: NDArray[np.generic], total_rows: int, num_fields: int) -> NDArray[np.generic]: - if num_fields == 1 and value.ndim == 1: - value = value.reshape(-1, 1) - try: - return np.broadcast_to(value, (total_rows, num_fields)) - except ValueError as exc: - raise ValueError( - f"RHS is not broadcast-compatible with flattened target shape ({total_rows}, {num_fields})." - ) from exc +def _normalize_nested_data(data: list[Any]) -> tuple[tuple[int, ...], list[NDArray[np.generic]]]: + if not isinstance(data, list): + raise TypeError("Data must be a list") + if not data: + return (0,), [] + return _flatten_fixed_grid(data) -def _is_field_selector(idx: Any) -> bool: - if isinstance(idx, str): + + +def _flatten_fixed_grid(node: Any) -> tuple[tuple[int, ...], list[NDArray[np.generic]]]: + if isinstance(node, np.ndarray): + return (), [_coerce_inferred_cell_array(node)] + if not isinstance(node, (list, tuple)): + raise TypeError("Data must be a nested list/tuple of cell arrays or row sequences.") + if _looks_like_cell_rows(node): + return (), [_coerce_inferred_cell_array(node)] + if len(node) == 0: + return (0,), [] + + child_shape: tuple[int, ...] | None = None + cells: list[NDArray[np.generic]] = [] + for child in node: + shape, child_cells = _flatten_fixed_grid(child) + if child_shape is None: + child_shape = shape + elif child_shape != shape: + raise ValueError("All nested fixed-grid branches must have matching shapes.") + cells.extend(child_cells) + + assert child_shape is not None + return (len(node),) + child_shape, cells + + + +def _looks_like_cell_rows(node: Sequence[Any]) -> bool: + if len(node) == 0: return True - if isinstance(idx, list | tuple) and idx: - if all(isinstance(item, str) for item in idx): - return True - return any(isinstance(item, str) for item in idx) - return False + return all(_is_row_like(item) for item in node) + -def _is_contiguous(indices: Sequence[int]) -> bool: - if not indices: +def _is_row_like(item: Any) -> bool: + if isinstance(item, np.ndarray): + return item.ndim == 1 + if not isinstance(item, (list, tuple)): return False - return list(indices) == list(range(indices[0], indices[0] + len(indices))) + return all(np.isscalar(value) for value in item) -def _select_coords(coords: NDArray[np.object_], idx: Any) -> NDArray[np.object_]: - shape = coords.shape - selectors, scalar_axes = _normalize_indices(shape, idx) - out_shape = tuple(len(selector) for selector, scalar in zip(selectors, scalar_axes) if not scalar) - result = np.empty(out_shape if out_shape != () else (), dtype=object) - if out_shape == (): - source = tuple(selector[0] for selector in selectors) - result[()] = coords[source] - return result +def _coerce_inferred_cell_array(value: Any) -> NDArray[np.generic]: + array = np.asarray(value) + if array.ndim == 0: + raise ValueError("Cell data must be 1D or 2D.") + if array.ndim == 1: + if array.size == 0: + return np.empty((0, 0), dtype=float) + return array.reshape(1, -1) + if array.ndim != 2: + raise ValueError("Cell data must be 1D or 2D.") + return array - for out_index in np.ndindex(out_shape): - out_cursor = 0 - source = [] - for axis, selector in enumerate(selectors): - if scalar_axes[axis]: - source.append(selector[0]) - else: - source.append(selector[out_index[out_cursor]]) - out_cursor += 1 - result[out_index] = coords[tuple(source)] - return result -def _normalize_indices(shape: tuple[int, ...], idx: Any) -> tuple[list[np.ndarray], list[bool]]: +def _select_linear_indices( + shape: tuple[int, ...], + current_indices: NDArray[np.int64], + idx: Any, +) -> tuple[tuple[int, ...], NDArray[np.int64]]: if shape == (): - if idx in ((), Ellipsis, slice(None), None): - return [], [] - raise IndexError("Too many indices for a 0D Vector.") + if idx in ((), Ellipsis): + return (), np.array([int(current_indices[0])], dtype=np.int64) + raise IndexError("Too many indices for 0D Vector") - normalized = idx if isinstance(idx, tuple) else (idx,) - normalized = _expand_ellipsis(normalized, len(shape)) - if len(normalized) > len(shape): - raise IndexError(f"Expected at most {len(shape)} indices, got {len(normalized)}") - normalized = normalized + (slice(None),) * (len(shape) - len(normalized)) + index_tuple = _normalize_index_tuple(idx, len(shape)) + current_grid = current_indices.reshape(shape) - selectors: list[np.ndarray] = [] + axis_positions: list[NDArray[np.int64]] = [] + out_shape: list[int] = [] scalar_axes: list[bool] = [] - for axis_value, axis_size in zip(normalized, shape): - selector, scalar = _normalize_axis_index(axis_value, axis_size) - selectors.append(selector) - scalar_axes.append(scalar) - return selectors, scalar_axes - - -def _expand_ellipsis(idx: tuple[Any, ...], ndim: int) -> tuple[Any, ...]: - if not any(item is Ellipsis for item in idx): - return idx - if sum(item is Ellipsis for item in idx) > 1: - raise IndexError("Only one ellipsis is allowed.") - ellipsis_pos = next(i for i, item in enumerate(idx) if item is Ellipsis) - fill = ndim - (len(idx) - 1) - return idx[:ellipsis_pos] + (slice(None),) * fill + idx[ellipsis_pos + 1 :] - - -def _normalize_axis_index(value: Any, axis_size: int) -> tuple[np.ndarray, bool]: - if isinstance(value, (int, np.integer)): - index = int(value) + for axis, axis_index in enumerate(index_tuple): + positions, is_scalar = _positions_for_axis(axis_index, shape[axis]) + axis_positions.append(positions) + scalar_axes.append(is_scalar) + if not is_scalar: + out_shape.append(len(positions)) + + if all(scalar_axes): + scalar_key = tuple(int(positions[0]) for positions in axis_positions) + value = int(current_grid[scalar_key]) + return (), np.array([value], dtype=np.int64) + + mesh_inputs = [positions if not is_scalar else positions[:1] for positions, is_scalar in zip(axis_positions, scalar_axes)] + grids = np.meshgrid(*mesh_inputs, indexing="ij") + selected = np.asarray(current_grid[tuple(grids)], dtype=np.int64).reshape(-1) + return tuple(out_shape), selected + + + +def _normalize_index_tuple(idx: Any, ndim: int) -> tuple[Any, ...]: + if idx is Ellipsis: + return (slice(None),) * ndim + if not isinstance(idx, tuple): + idx = (idx,) + + ellipsis_count = sum(item is Ellipsis for item in idx) + if ellipsis_count > 1: + raise IndexError("An index can only have a single ellipsis.") + if ellipsis_count == 1: + ellipsis_pos = idx.index(Ellipsis) + fill = ndim - (len(idx) - 1) + idx = idx[:ellipsis_pos] + (slice(None),) * fill + idx[ellipsis_pos + 1 :] + if len(idx) > ndim: + raise IndexError(f"Too many indices for Vector: expected {ndim}, got {len(idx)}") + if len(idx) < ndim: + idx = idx + (slice(None),) * (ndim - len(idx)) + return idx + + + +def _positions_for_axis(axis_index: Any, size: int) -> tuple[NDArray[np.int64], bool]: + if isinstance(axis_index, (bool, np.bool_)): + raise TypeError("Boolean scalars are not valid Vector indices.") + + if isinstance(axis_index, (int, np.integer)): + index = int(axis_index) if index < 0: - index += axis_size - if index < 0 or index >= axis_size: - raise IndexError(f"Index {value} out of bounds for axis with size {axis_size}") - return np.array([index], dtype=int), True + index += size + if index < 0 or index >= size: + raise IndexError("Vector index out of range") + return np.array([index], dtype=np.int64), True - if isinstance(value, slice): - return np.arange(axis_size)[value], False + if isinstance(axis_index, slice): + return np.arange(size, dtype=np.int64)[axis_index], False - if isinstance(value, np.ndarray) and value.ndim == 0: - return _normalize_axis_index(value.item(), axis_size) + array = np.asarray(axis_index) + if array.ndim == 0: + if np.issubdtype(array.dtype, np.integer): + return _positions_for_axis(int(array.item()), size) + raise TypeError(f"Unsupported index type: {type(axis_index)!r}") + + if array.dtype == bool or np.issubdtype(array.dtype, np.bool_): + if array.ndim != 1: + raise IndexError("Full-grid boolean masks are not supported.") + if array.shape[0] != size: + raise IndexError(f"Boolean mask length {array.shape[0]} does not match axis length {size}") + return np.flatnonzero(array).astype(np.int64, copy=False), False - array = np.asarray(value) if array.ndim != 1: - raise IndexError("Only 1D per-axis fancy or boolean indexing is supported.") + raise IndexError("Fancy indexing arrays must be one-dimensional.") if array.size == 0: - return np.array([], dtype=int), False + return np.array([], dtype=np.int64), False + if not np.issubdtype(array.dtype, np.integer): + raise TypeError("Fancy indices must be integers or booleans.") - if array.dtype == bool or np.issubdtype(array.dtype, np.bool_): - if array.shape[0] != axis_size: - raise IndexError( - f"Boolean mask length {array.shape[0]} does not match axis size {axis_size}" - ) - return np.flatnonzero(array), False + positions = array.astype(np.int64, copy=True) + positions[positions < 0] += size + if np.any((positions < 0) | (positions >= size)): + raise IndexError("Vector index out of range") + return positions, False - if not np.issubdtype(array.dtype, np.integer): - raise TypeError(f"Unsupported index type {type(value).__name__}") - normalized = array.astype(int, copy=True) - normalized[normalized < 0] += axis_size - if np.any((normalized < 0) | (normalized >= axis_size)): - raise IndexError(f"Index out of bounds for axis with size {axis_size}") - return normalized, False +def _broadcast_field_values(value: Any, total_rows: int, num_fields: int) -> NDArray[np.generic]: + array = np.asarray(value) + if array.ndim == 0: + return np.broadcast_to(array.reshape(1, 1), (total_rows, num_fields)) + if num_fields == 1 and array.ndim == 1: + if total_rows == 0 and array.shape[0] == 0: + return array.reshape(0, 1) + if array.shape[0] != total_rows: + raise ValueError(f"Expected {total_rows} values, got {array.shape[0]}") + return array.reshape(total_rows, 1) + try: + return np.broadcast_to(array, (total_rows, num_fields)) + except ValueError as exc: + raise ValueError( + f"Cannot broadcast value with shape {array.shape} to ({total_rows}, {num_fields})" + ) from exc -def _normalize_nested_data(data: list[Any]) -> tuple[tuple[int, ...], list[NDArray[np.generic]]]: - if not isinstance(data, list): - raise TypeError("Data must be a list.") - if not data: - raise ValueError("Data list cannot be empty.") - - leaves: list[NDArray[np.generic]] = [] - num_fields: int | None = None - - def walk(node: Any) -> tuple[int, ...]: - nonlocal num_fields - if _is_leaf_cell(node): - array = np.asarray(node) - if array.ndim != 2: - raise ValueError(f"Cell arrays must be 2D, got shape {array.shape}") - if not np.issubdtype(array.dtype, np.number): - raise TypeError("Cell arrays must contain numeric values.") - if num_fields is None: - num_fields = array.shape[1] - elif array.shape[1] != num_fields: - raise ValueError("All data arrays must have same number of fields.") - leaves.append(array) - return () - - if not isinstance(node, list): - raise TypeError("Data elements must be numpy arrays, numeric 2D lists, or nested lists thereof.") - if not node: - raise ValueError("Nested data lists cannot be empty.") - child_shapes = [walk(child) for child in node] - first = child_shapes[0] - if any(shape != first for shape in child_shapes[1:]): - raise ValueError("Nested data structure must have a consistent fixed-grid shape.") - return (len(node), *first) - - inferred_shape = walk(data) - return inferred_shape, leaves - - -def _is_leaf_cell(node: Any) -> bool: - if isinstance(node, np.ndarray): + + +def _is_contiguous(indices: NDArray[np.int64]) -> bool: + if indices.size <= 1: return True - if not isinstance(node, list): - return False - try: - array = np.asarray(node) - except Exception: - return False - return array.ndim == 2 and np.issubdtype(array.dtype, np.number) + return bool(np.all(indices[1:] - indices[:-1] == 1)) diff --git a/src/quantem/core/visualization/visualization_utils.py b/src/quantem/core/visualization/visualization_utils.py index afe475b1..b69e3c86 100644 --- a/src/quantem/core/visualization/visualization_utils.py +++ b/src/quantem/core/visualization/visualization_utils.py @@ -503,6 +503,14 @@ def bilinear_histogram_2d( x0, y0 = origin x1, y1 = x0 + Nx * dx, y0 + Ny * dy + x = _as_histogram_vector(x, "x") + y = _as_histogram_vector(y, "y") + weight = _as_histogram_vector(weight, "weight") + if not (x.shape == y.shape == weight.shape): + raise ValueError( + f"x, y, and weight must have matching shapes after coercion, got {x.shape}, {y.shape}, {weight.shape}" + ) + # Convert shape tuple to list for binned_statistic_2d bins: Sequence[int] = [Nx, Ny] hist, _, _, _ = binned_statistic_2d( @@ -517,6 +525,15 @@ def bilinear_histogram_2d( return hist # shape = (Nx, Ny), i.e., array[x, y] +def _as_histogram_vector(value: NDArray, name: str) -> NDArray: + array = np.asarray(value) + if array.ndim == 1: + return array + if array.ndim == 2 and 1 in array.shape: + return array.reshape(-1) + raise ValueError(f"{name} must be 1D or shape (N, 1)/(1, N), got {array.shape}") + + def axes_with_inset( axsize=(4, 4), ax_size_embed=None, # None -> 0.25 of main axes in each dimension (fractional) diff --git a/tests/datastructures/test_vector.py b/tests/datastructures/test_vector.py index f03a08b4..52afd40e 100644 --- a/tests/datastructures/test_vector.py +++ b/tests/datastructures/test_vector.py @@ -1,6 +1,9 @@ +import zipfile + import numpy as np import pytest +from quantem.core.io.serialize import load from quantem.core.datastructures.vector import Vector @@ -56,6 +59,12 @@ def test_initialization_and_len(self): with pytest.raises(ValueError, match="Duplicate field names"): Vector.from_shape(shape=(2, 3), fields=["a", "a"]) + assert str(v1) == ( + "quantem.Vector, shape=(2, 3), name=2d ragged array\n" + " fields = ['a', 'b', 'c']\n" + " units: ['none', 'none', 'none']" + ) + def test_indexing_and_array_contract(self): v = make_grid_vector() @@ -227,8 +236,40 @@ def test_from_data_supports_nested_fixed_grid(self): assert v.name == "nested" np.testing.assert_array_equal(v[0, 1].array, np.array([[3.0, 4.0], [5.0, 6.0]])) + tuple_cells = [ + ([1.0, 2.0], [3.0, 4.0]), + ([5.0, 6.0], [7.0, 8.0], [9.0, 10.0]), + ] + tuple_vector = Vector.from_data(data=tuple_cells, fields=["a", "b"]) + assert tuple_vector.shape == (2,) + np.testing.assert_array_equal(tuple_vector[0].array, np.array([[1.0, 2.0], [3.0, 4.0]])) + np.testing.assert_array_equal( + tuple_vector[1].array, + np.array([[5.0, 6.0], [7.0, 8.0], [9.0, 10.0]]), + ) + with pytest.raises(TypeError, match="Data must be a list"): Vector.from_data(data=np.array([1, 2, 3])) # type: ignore[arg-type] with pytest.raises(ValueError, match="same number of fields"): Vector.from_data(data=[np.array([[1.0, 2.0]]), np.array([[1.0, 2.0, 3.0]])]) + + def test_save_and_load_round_trip(self, tmp_path): + v = make_grid_vector() + v.add_fields("extra", v.select_fields("intensity") + 1.0) + + path = tmp_path / "vector_test.zip" + v.save(path, mode="o", compression_level=4) + + with zipfile.ZipFile(path) as zf: + names = [info.filename for info in zf.infolist()] + assert len(names) < 30 + assert "_state/data/zarr.json" in names + assert all(not name.startswith("_selection_coords/") for name in names) + + loaded = load(path) + assert isinstance(loaded, Vector) + assert loaded.shape == v.shape + assert loaded.fields == v.fields + assert loaded.units == v.units + np.testing.assert_array_equal(loaded[2, 1].array, v[2, 1].array) diff --git a/tests/visualization/test_visualization_utils.py b/tests/visualization/test_visualization_utils.py index c3a1f048..ca114774 100644 --- a/tests/visualization/test_visualization_utils.py +++ b/tests/visualization/test_visualization_utils.py @@ -297,3 +297,11 @@ def test_bilinear_histogram_2d_with_custom_statistic(self): hist = bilinear_histogram_2d(shape, x, y, weight, statistic="mean") assert hist.shape == shape assert np.all(~np.isnan(hist[hist > 0])) # Only check non-zero values for NaN + + def test_bilinear_histogram_2d_accepts_column_vectors(self): + shape = (8, 8) + x = np.array([[1.0], [2.0], [3.0]]) + y = np.array([[1.5], [2.5], [3.5]]) + weight = np.array([[2.0], [3.0], [4.0]]) + hist = bilinear_histogram_2d(shape, x, y, weight) + assert hist.shape == shape From d50fa5a3db0b618f9e070ef9999f1dbd6a93bb7d Mon Sep 17 00:00:00 2001 From: cophus Date: Sun, 1 Mar 2026 13:34:49 -0800 Subject: [PATCH 107/335] minor fixes --- src/quantem/core/datastructures/vector.py | 6 ++++++ tests/datastructures/test_vector.py | 3 +++ 2 files changed, 9 insertions(+) diff --git a/src/quantem/core/datastructures/vector.py b/src/quantem/core/datastructures/vector.py index 53592dcd..b69d917c 100644 --- a/src/quantem/core/datastructures/vector.py +++ b/src/quantem/core/datastructures/vector.py @@ -240,6 +240,7 @@ def add_fields( values: Any | None = None, units: str | Sequence[str] | None = None, ) -> None: + self._require_full_field_view("add_fields") new_fields = _normalize_field_names(names) if any(field in self._state["fields"] for field in new_fields): raise ValueError("One or more new field names already exist.") @@ -264,6 +265,7 @@ def add_fields( self._selected_fields = None def remove_fields(self, names: str | Sequence[str]) -> None: + self._require_full_field_view("remove_fields") to_remove = set(_normalize_field_names(names)) old_fields = self._state["fields"] old_units = self._state["units"] @@ -379,6 +381,10 @@ def _field_indices(self) -> NDArray[np.int64]: except KeyError as exc: raise KeyError(f"Unknown field(s): {[str(exc.args[0])]}") from exc + def _require_full_field_view(self, operation: str) -> None: + if self._selected_fields is not None: + raise ValueError(f"{operation} is only allowed when all fields are selected.") + def _selected_cell_indices(self) -> NDArray[np.int64]: if self._selection_indices is None: return np.arange(_cell_count(self._state["shape"]), dtype=np.int64) diff --git a/tests/datastructures/test_vector.py b/tests/datastructures/test_vector.py index 52afd40e..6091f832 100644 --- a/tests/datastructures/test_vector.py +++ b/tests/datastructures/test_vector.py @@ -203,6 +203,9 @@ def test_add_fields_defaults_expression_and_multiple_values(self): np.array([[5.0], [6.0], [7.0], [8.0], [9.0], [10.0]]), ) + with pytest.raises(ValueError, match="all fields are selected"): + v2.select_fields("kx").add_fields("bad") + def test_remove_fields_preserves_remaining_data(self): v = make_line_vector() v.add_fields("extra", 1.0) From a741fe6e5b0c526e749efe76e8238e6c78bc8e51 Mon Sep 17 00:00:00 2001 From: cophus Date: Sun, 1 Mar 2026 13:54:12 -0800 Subject: [PATCH 108/335] More small fixes --- src/quantem/core/datastructures/vector.py | 276 ++++++++++++++++++++-- tests/datastructures/test_vector.py | 37 +++ 2 files changed, 291 insertions(+), 22 deletions(-) diff --git a/src/quantem/core/datastructures/vector.py b/src/quantem/core/datastructures/vector.py index b69d917c..dc8865c0 100644 --- a/src/quantem/core/datastructures/vector.py +++ b/src/quantem/core/datastructures/vector.py @@ -18,10 +18,75 @@ class Vector(AutoSerialize): """Ragged cell data on a fixed grid. - Storage is compact and AutoSerialize-friendly: - - one 2D numeric row buffer for all ragged rows - - one start array and one length array for cell boundaries - - optional selection state for views + A ``Vector`` has two independent axes of structure: + - fixed-grid dimensions given by ``shape`` + - ragged rows stored inside each fixed-grid cell + + Each ragged row has one value per named field, so each cell behaves like a + small 2D array with shape ``(n_rows, num_fields)``, where ``n_rows`` may + vary from cell to cell. + + Parameters + ---------- + shape : tuple of int + Fixed-grid shape. + fields : sequence of str + Field names in column order. + units : sequence of str, optional + Units corresponding to ``fields``. If omitted, units default to + ``"none"`` for all fields. + name : str, optional + Descriptive name for the Vector. + metadata : dict, optional + Additional user metadata. + + Notes + ----- + The public API keeps fixed-grid indexing and field selection separate: + - use ``[]`` for fixed-grid indexing + - use ``select_fields(...)`` for field selection + + Fixed-grid indexing always returns a ``Vector``. A 0D selection exposes its + underlying cell array through ``.array``. Multi-cell selections can be + concatenated with ``flatten()``. + + The internal representation is compact: + - ``_state["data"]`` stores all ragged rows in one numeric 2D array + - ``_state["cell_starts"]`` stores the start offset for each cell + - ``_state["cell_lengths"]`` stores the row count for each cell + + A ``Vector`` selection is a write-through view over shared storage. Views + track only the selected fixed-grid shape, selected cell indices, and selected + field names. + + Examples + -------- + Create a Vector and assign one cell: + + >>> import numpy as np + >>> v = Vector.from_shape((2, 2), fields=("kx", "ky", "intensity")) + >>> v[0, 0] = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]) + >>> v[0, 0].array.shape + (2, 3) + + Select fields and apply in-place arithmetic: + + >>> kx = v.select_fields("kx") + >>> kx += 16 + >>> kx.flatten().shape + (2, 1) + + Apply a rowwise transform with ``flatten()`` and ``set_flattened()``: + + >>> kx = v.select_fields("kx") + >>> ky = v.select_fields("ky") + >>> kx.set_flattened( + ... np.where( + ... ((kx.flatten() - 16) ** 2 + (ky.flatten() - 16) ** 2) < 12, + ... 10, + ... kx.flatten(), + ... ) + ... ) """ def __init__( @@ -61,6 +126,7 @@ def _from_view( selection_indices: NDArray[np.int64] | None, selected_fields: tuple[str, ...] | None, ) -> "Vector": + """Build a view that shares backing storage with another Vector.""" obj = cls.__new__(cls) obj._state = state obj._selection_shape = selection_shape @@ -77,6 +143,7 @@ def from_shape( units: Sequence[str] | None = None, name: str | None = None, ) -> "Vector": + """Create an empty Vector with the given fixed-grid shape and fields.""" if fields is not None: root_fields = validate_fields(list(fields)) if num_fields is not None and len(root_fields) != num_fields: @@ -100,6 +167,11 @@ def from_data( units: Sequence[str] | None = None, name: str | None = None, ) -> "Vector": + """Create a Vector from nested fixed-grid data. + + The outer nesting defines the fixed-grid shape. Each leaf must coerce to a + 2D cell array with consistent field count across all cells. + """ root_shape, cell_arrays = _normalize_nested_data(data) inferred_counts = {array.shape[1] for array in cell_arrays} if len(inferred_counts) > 1: @@ -128,16 +200,19 @@ def from_data( @property def shape(self) -> tuple[int, ...]: + """Return the fixed-grid shape of this selection.""" return self._selection_shape @property def fields(self) -> list[str]: + """Return selected field names in column order.""" if self._selected_fields is None: return list(self._state["fields"]) return list(self._selected_fields) @property def units(self) -> list[str]: + """Return units for the selected fields.""" lookup = { field: unit for field, unit in zip(self._state["fields"], self._state["units"]) @@ -146,10 +221,12 @@ def units(self) -> list[str]: @property def num_fields(self) -> int: + """Return the number of selected fields.""" return len(self.fields) @property def name(self) -> str: + """Human-readable Vector name.""" return self._state["name"] @name.setter @@ -158,10 +235,18 @@ def name(self, value: str) -> None: @property def metadata(self) -> dict[str, Any]: + """Mutable metadata dictionary shared by all views.""" return self._state["metadata"] @property def array(self) -> NDArray[np.generic]: + """Return the selected cell as a NumPy array. + + This is only valid for 0D selections. Single-field and contiguous + multi-field selections return writable views into the backing storage. + Non-contiguous multi-field selections return a copy because NumPy cannot + expose a writable column-subset view for that layout. + """ if self.shape != (): raise ValueError(".array is only valid when the selection contains exactly one cell.") cell = self._cell_matrix(self._selected_cell_indices()[0]) @@ -176,6 +261,7 @@ def array(self) -> NDArray[np.generic]: return cell[:, cols].copy() def __len__(self) -> int: + """Return ``shape[0]`` for non-scalar selections.""" if self.shape == (): raise TypeError("len() of unsized 0D Vector") return self.shape[0] @@ -192,6 +278,7 @@ def __repr__(self) -> str: __str__ = __repr__ def copy(self) -> "Vector": + """Return a deep copy of the current selection.""" copied = self.__class__( shape=self.shape, fields=self.fields, @@ -205,6 +292,11 @@ def copy(self) -> "Vector": return copied def flatten(self) -> NDArray[np.generic]: + """Concatenate selected cells in row-major order. + + Returns a 2D array with shape ``(total_rows, num_fields)`` even for + single-field selections. + """ arrays = [ self._selected_cell_matrix(index) for index in self._selected_cell_indices() @@ -216,8 +308,24 @@ def flatten(self) -> NDArray[np.generic]: dtype = self._state["data"].dtype if self._state["data"].ndim == 2 else float return np.empty((0, self.num_fields), dtype=dtype) - def select_fields(self, field_names: str | Sequence[str]) -> "Vector": - selected = _normalize_field_names(field_names) + @property + def total_rows(self) -> int: + """Return the total ragged-row count in the current selection.""" + return int(self._state["cell_lengths"][self._selected_cell_indices()].sum()) + + def row_counts(self) -> list[int]: + """Return per-cell row counts in the current selection order.""" + return [self._cell_row_count(int(index)) for index in self._selected_cell_indices()] + + def select_fields(self, *field_names: str | Sequence[str]) -> "Vector": + """Return a view containing only the requested fields. + + Accepted forms: + - ``select_fields("kx")`` + - ``select_fields("kx", "ky")`` + - ``select_fields(["kx", "ky"])`` + """ + selected = _normalize_select_field_args(*field_names) available = set(self.fields) missing = [field for field in selected if field not in available] if missing: @@ -234,12 +342,71 @@ def select_fields(self, field_names: str | Sequence[str]) -> "Vector": selected_fields, ) + def set_flattened(self, values: Any) -> None: + """Write values back in flattened row-major order. + + This updates existing rows without changing per-cell row counts. It is + the rowwise companion to ``flatten()`` and is especially useful for + NumPy-based transforms that operate on all selected rows at once. + """ + field_indices = self._field_indices() + targets = self._selected_cell_indices() + row_counts = self.row_counts() + total_rows = sum(row_counts) + + if isinstance(values, Vector): + if values.num_fields != self.num_fields: + raise ValueError(f"Expected {self.num_fields} fields, got {values.num_fields}") + flat_values = values.flatten() + if flat_values.shape[0] != total_rows: + raise ValueError(f"Expected {total_rows} rows, got {flat_values.shape[0]}") + else: + flat_values = _broadcast_field_values(values, total_rows, self.num_fields) + + cursor = 0 + for target, rows in zip(targets, row_counts): + cell = self._cell_matrix(int(target)) + if rows > 0: + cell[:, field_indices] = flat_values[cursor : cursor + rows] + cursor += rows + + def compact(self) -> None: + """Repack the backing row buffer to remove dead rows. + + Whole-cell replacement appends new rows and leaves previous rows unused + until compaction. Calling ``compact()`` makes memory usage and save size + predictable at the cost of reallocating the backing buffer. + """ + self._compact_storage() + + def append_rows(self, idx: Any, rows: Any) -> None: + """Append one or more rows to a single selected cell. + + ``idx`` is interpreted with the same fixed-grid indexing rules as + ``__getitem__`` and must resolve to exactly one cell. Appending rows is a + full-cell operation, so all fields must be selected. + """ + target = self[idx] + if target.shape != (): + raise ValueError("append_rows requires an index that selects exactly one cell.") + target._require_full_field_view("append_rows") + + new_rows = _coerce_cell_array(rows, target.num_fields) + if new_rows.shape[0] == 0: + return + + cell_index = int(target._selected_cell_indices()[0]) + existing = target._cell_matrix(cell_index) + combined = np.vstack((existing, new_rows)) if existing.shape[0] > 0 else new_rows.copy() + target._replace_cells(np.array([cell_index], dtype=np.int64), [combined]) + def add_fields( self, names: str | Sequence[str], values: Any | None = None, units: str | Sequence[str] | None = None, ) -> None: + """Add one or more new fields to the full Vector schema.""" self._require_full_field_view("add_fields") new_fields = _normalize_field_names(names) if any(field in self._state["fields"] for field in new_fields): @@ -254,7 +421,7 @@ def add_fields( if values is None: return - target = self.select_fields(list(new_fields)) + target = self.select_fields(*new_fields) if len(new_fields) > 1 and isinstance(values, (list, tuple)) and len(values) == len(new_fields): for field, value in zip(new_fields, values): target.select_fields(field)[...] = value @@ -265,6 +432,7 @@ def add_fields( self._selected_fields = None def remove_fields(self, names: str | Sequence[str]) -> None: + """Remove one or more fields from the full Vector schema.""" self._require_full_field_view("remove_fields") to_remove = set(_normalize_field_names(names)) old_fields = self._state["fields"] @@ -286,23 +454,11 @@ def remove_fields(self, names: str | Sequence[str]) -> None: if len(self._selected_fields) == len(self._state["fields"]): self._selected_fields = None - def get_data(self, *indices: Any) -> NDArray[np.generic] | list[NDArray[np.generic]]: - if len(indices) != len(self.shape): - raise ValueError(f"Expected {len(self.shape)} indices, got {len(indices)}") - selection = self[indices if len(indices) != 1 else indices[0]] - if selection.shape == (): - return selection.array - return [selection._selected_cell_matrix(index).copy() for index in selection._selected_cell_indices()] - - def set_data(self, value: Any, *indices: Any) -> None: - if len(indices) != len(self.shape): - raise ValueError(f"Expected {len(self.shape)} indices, got {len(indices)}") - self[indices if len(indices) != 1 else indices[0]] = value - @overload def __getitem__(self, idx: Any) -> "Vector": ... def __getitem__(self, idx: Any) -> "Vector": + """Return a fixed-grid selection as another Vector view.""" if _looks_like_field_selector(idx): raise TypeError("Use select_fields(...) for field selection.") if idx is Ellipsis: @@ -321,6 +477,7 @@ def __getitem__(self, idx: Any) -> "Vector": ) def __setitem__(self, idx: Any, value: Any) -> None: + """Assign to a fixed-grid selection.""" if idx is Ellipsis: target = self else: @@ -372,6 +529,7 @@ def _full_num_fields(self) -> int: return len(self._state["fields"]) def _field_indices(self) -> NDArray[np.int64]: + """Map selected field names to column indices in the backing buffer.""" if self._selected_fields is None: return np.arange(self._full_num_fields, dtype=np.int64) @@ -382,10 +540,12 @@ def _field_indices(self) -> NDArray[np.int64]: raise KeyError(f"Unknown field(s): {[str(exc.args[0])]}") from exc def _require_full_field_view(self, operation: str) -> None: + """Raise if a schema-changing/full-row operation is attempted on a field view.""" if self._selected_fields is not None: raise ValueError(f"{operation} is only allowed when all fields are selected.") def _selected_cell_indices(self) -> NDArray[np.int64]: + """Return linear cell indices for the current fixed-grid selection.""" if self._selection_indices is None: return np.arange(_cell_count(self._state["shape"]), dtype=np.int64) return self._selection_indices @@ -394,9 +554,11 @@ def _is_full_field_selection(self) -> bool: return self._selected_fields is None def _cell_row_count(self, linear_index: int) -> int: + """Return the row count for one cell in the backing buffer.""" return int(self._state["cell_lengths"][linear_index]) def _cell_matrix(self, linear_index: int) -> NDArray[np.generic]: + """Return the full backing matrix for one cell.""" start = int(self._state["cell_starts"][linear_index]) length = int(self._state["cell_lengths"][linear_index]) if length == 0: @@ -404,6 +566,7 @@ def _cell_matrix(self, linear_index: int) -> NDArray[np.generic]: return self._state["data"][start : start + length] def _selected_cell_matrix(self, linear_index: int) -> NDArray[np.generic]: + """Return one cell with the current field selection applied.""" cell = self._cell_matrix(linear_index) cols = self._field_indices() if cols.size == self._full_num_fields: @@ -416,6 +579,14 @@ def _selected_cell_matrix(self, linear_index: int) -> NDArray[np.generic]: return cell[:, cols].copy() def _replace_cells(self, targets: NDArray[np.int64], arrays: Sequence[NDArray[np.generic]]) -> None: + """Replace complete cells in the compact row buffer. + + Whole-cell replacement is implemented by appending the new payload rows to + the end of the backing buffer and then updating ``cell_starts`` / + ``cell_lengths`` for the targeted cells. This keeps the operation simple + and makes overlapping assignment semantics easy to reason about, but it + leaves the previous rows unreachable until compaction removes them. + """ if len(targets) != len(arrays): raise ValueError("Target cell count does not match source cell count.") if len(targets) == 0: @@ -440,6 +611,7 @@ def _replace_cells(self, targets: NDArray[np.int64], arrays: Sequence[NDArray[np self._maybe_compact_storage() def _expand_storage(self, num_new_fields: int) -> None: + """Append new ``np.nan``-initialized columns for added fields.""" data = self._state["data"] if data.shape[0] == 0: dtype = np.result_type(data.dtype, float) @@ -451,16 +623,23 @@ def _expand_storage(self, num_new_fields: int) -> None: self._state["data"] = np.concatenate((data.astype(dtype, copy=False), filler), axis=1) def _maybe_compact_storage(self) -> None: + """Compact automatically once dead rows become materially larger than live rows.""" data = self._state["data"] used_rows = int(self._state["cell_lengths"].sum()) if data.shape[0] <= used_rows + 1024: return + if data.shape[0] <= 2 * used_rows: + return + self._compact_storage() + + def _compact_storage(self) -> None: + """Rebuild the row buffer so only live rows remain.""" + data = self._state["data"] + used_rows = int(self._state["cell_lengths"].sum()) if used_rows == 0: self._state["data"] = np.empty((0, self._full_num_fields), dtype=data.dtype) self._state["cell_starts"].fill(0) return - if data.shape[0] <= 2 * used_rows: - return compacted = np.empty((used_rows, self._full_num_fields), dtype=data.dtype) starts = np.zeros_like(self._state["cell_starts"]) @@ -476,12 +655,18 @@ def _maybe_compact_storage(self) -> None: self._state["cell_starts"] = starts def _assign(self, value: Any) -> None: + """Dispatch assignment based on whether all fields or a subset are selected.""" if self._is_full_field_selection(): self._assign_full_cells(value) else: self._assign_selected_fields(value) def _assign_full_cells(self, value: Any) -> None: + """Replace full cell payloads. + + Full-cell assignment may change the ragged row count of each targeted + cell, because the existing cell matrix is replaced as a whole. + """ targets = self._selected_cell_indices() if isinstance(value, Vector): source_cells = value._selected_cell_indices() @@ -499,6 +684,13 @@ def _assign_full_cells(self, value: Any) -> None: self._replace_cells(targets, [array] * len(targets)) def _assign_selected_fields(self, value: Any) -> None: + """Update only the selected columns while preserving row counts. + + This is the in-place path for assignments such as + ``vector.select_fields("kx")[...] = rhs``. The target cell structure is + preserved, so each target cell keeps its existing row count and only the + selected columns are overwritten. + """ targets = self._selected_cell_indices() field_indices = self._field_indices() row_counts = [self._cell_row_count(index) for index in targets] @@ -539,11 +731,13 @@ def _assign_selected_fields(self, value: Any) -> None: cursor += rows def _binary_op(self, other: Any, op: Any, reverse: bool = False) -> "Vector": + """Return a new Vector produced by elementwise arithmetic.""" result = self.copy() result._inplace_op(other, op, reverse=reverse) return result def _inplace_op(self, other: Any, op: Any, reverse: bool = False) -> None: + """Apply elementwise arithmetic in-place to the selected fields.""" targets = self._selected_cell_indices() field_indices = self._field_indices() row_counts = [self._cell_row_count(index) for index in targets] @@ -585,10 +779,12 @@ def _inplace_op(self, other: Any, op: Any, reverse: bool = False) -> None: def _cell_count(shape: tuple[int, ...]) -> int: + """Return the number of fixed-grid cells in a shape.""" return int(np.prod(shape, dtype=np.int64)) if shape else 1 def _normalize_field_names(field_names: str | Sequence[str]) -> tuple[str, ...]: + """Normalize one-or-many field names into a validated tuple.""" if isinstance(field_names, str): normalized = (field_names,) else: @@ -599,8 +795,25 @@ def _normalize_field_names(field_names: str | Sequence[str]) -> tuple[str, ...]: return normalized +def _normalize_select_field_args(*field_names: str | Sequence[str]) -> tuple[str, ...]: + """Normalize ``select_fields`` arguments. + + This accepts either ``select_fields("a", "b")`` or + ``select_fields(["a", "b"])`` while keeping ``add_fields`` / + ``remove_fields`` on the simpler single-argument path. + """ + if not field_names: + raise ValueError("At least one field name is required.") + if len(field_names) == 1 and not isinstance(field_names[0], str): + return _normalize_field_names(field_names[0]) + if not all(isinstance(name, str) for name in field_names): + raise TypeError("select_fields(...) expects field names as strings or one sequence of strings.") + return _normalize_field_names(field_names) + + def _normalize_units(units: str | Sequence[str] | None, count: int) -> list[str]: + """Normalize field units to a list matching ``count``.""" if units is None: return ["none"] * count if isinstance(units, str): @@ -615,6 +828,7 @@ def _normalize_units(units: str | Sequence[str] | None, count: int) -> list[str] def _looks_like_field_selector(idx: Any) -> bool: + """Return True for indices that look like field selection by mistake.""" if isinstance(idx, str): return True if isinstance(idx, tuple) and any(_looks_like_field_selector(item) for item in idx): @@ -626,6 +840,7 @@ def _looks_like_field_selector(idx: Any) -> bool: def _coerce_cell_array(value: Any, num_fields: int) -> NDArray[np.generic]: + """Normalize a single-cell payload to shape ``(n_rows, num_fields)``.""" if isinstance(value, Vector): if value.shape != (): raise ValueError("Expected a 0D Vector for single-cell assignment.") @@ -651,6 +866,7 @@ def _coerce_cell_array(value: Any, num_fields: int) -> NDArray[np.generic]: def _normalize_nested_data(data: list[Any]) -> tuple[tuple[int, ...], list[NDArray[np.generic]]]: + """Normalize nested input data into ``(shape, flat_cell_arrays)``.""" if not isinstance(data, list): raise TypeError("Data must be a list") if not data: @@ -660,6 +876,7 @@ def _normalize_nested_data(data: list[Any]) -> tuple[tuple[int, ...], list[NDArr def _flatten_fixed_grid(node: Any) -> tuple[tuple[int, ...], list[NDArray[np.generic]]]: + """Recursively flatten nested fixed-grid input into row-major cell order.""" if isinstance(node, np.ndarray): return (), [_coerce_inferred_cell_array(node)] if not isinstance(node, (list, tuple)): @@ -685,6 +902,7 @@ def _flatten_fixed_grid(node: Any) -> tuple[tuple[int, ...], list[NDArray[np.gen def _looks_like_cell_rows(node: Sequence[Any]) -> bool: + """Return True when a sequence should be interpreted as cell rows, not grid nesting.""" if len(node) == 0: return True return all(_is_row_like(item) for item in node) @@ -692,6 +910,7 @@ def _looks_like_cell_rows(node: Sequence[Any]) -> bool: def _is_row_like(item: Any) -> bool: + """Return True for a single row of scalar values.""" if isinstance(item, np.ndarray): return item.ndim == 1 if not isinstance(item, (list, tuple)): @@ -701,6 +920,7 @@ def _is_row_like(item: Any) -> bool: def _coerce_inferred_cell_array(value: Any) -> NDArray[np.generic]: + """Infer a 2D cell array from row-like input during ``from_data``.""" array = np.asarray(value) if array.ndim == 0: raise ValueError("Cell data must be 1D or 2D.") @@ -719,6 +939,14 @@ def _select_linear_indices( current_indices: NDArray[np.int64], idx: Any, ) -> tuple[tuple[int, ...], NDArray[np.int64]]: + """Apply fixed-grid indexing to a flattened cell-index view. + + ``current_indices`` stores the linear cell indices represented by the current + selection. This helper reshapes those indices to the current selection shape, + applies NumPy-like indexing on the fixed-grid axes, and then returns: + - the output fixed-grid shape + - the flattened linear indices of the selected cells, in row-major order + """ if shape == (): if idx in ((), Ellipsis): return (), np.array([int(current_indices[0])], dtype=np.int64) @@ -750,6 +978,7 @@ def _select_linear_indices( def _normalize_index_tuple(idx: Any, ndim: int) -> tuple[Any, ...]: + """Normalize fixed-grid indexing to a full ``ndim``-length tuple.""" if idx is Ellipsis: return (slice(None),) * ndim if not isinstance(idx, tuple): @@ -771,6 +1000,7 @@ def _normalize_index_tuple(idx: Any, ndim: int) -> tuple[Any, ...]: def _positions_for_axis(axis_index: Any, size: int) -> tuple[NDArray[np.int64], bool]: + """Resolve one axis index into concrete positions and scalar-vs-vector shape behavior.""" if isinstance(axis_index, (bool, np.bool_)): raise TypeError("Boolean scalars are not valid Vector indices.") @@ -814,6 +1044,7 @@ def _positions_for_axis(axis_index: Any, size: int) -> tuple[NDArray[np.int64], def _broadcast_field_values(value: Any, total_rows: int, num_fields: int) -> NDArray[np.generic]: + """Broadcast array-like input to flattened rowwise assignment shape.""" array = np.asarray(value) if array.ndim == 0: return np.broadcast_to(array.reshape(1, 1), (total_rows, num_fields)) @@ -833,6 +1064,7 @@ def _broadcast_field_values(value: Any, total_rows: int, num_fields: int) -> NDA def _is_contiguous(indices: NDArray[np.int64]) -> bool: + """Return True when integer column indices form one contiguous slice.""" if indices.size <= 1: return True return bool(np.all(indices[1:] - indices[:-1] == 1)) diff --git a/tests/datastructures/test_vector.py b/tests/datastructures/test_vector.py index 6091f832..4086d4d5 100644 --- a/tests/datastructures/test_vector.py +++ b/tests/datastructures/test_vector.py @@ -104,12 +104,31 @@ def test_select_fields_and_chaining_equivalence(self): with pytest.raises(TypeError): _ = v[1, "kx"] + multi = v.select_fields("intensity", "kx") + assert multi.fields == ["intensity", "kx"] + assert multi.total_rows == 6 + assert multi.row_counts() == [2, 1, 2, 1] + def test_array_mutation_writes_through_for_single_field(self): v = make_line_vector() cell = v.select_fields("kx")[1].array cell[0, 0] = 99.0 assert v[1].array[0, 1] == 99.0 + def test_set_flattened_updates_rowwise(self): + v = make_line_vector() + kx = v.select_fields("kx") + + flat_kx = kx.flatten() + mask = flat_kx >= 30.0 + flat_kx[mask[:, 0], 0] = -1.0 + kx.set_flattened(flat_kx) + + np.testing.assert_array_equal( + kx.flatten(), + np.array([[10.0], [20.0], [-1.0], [-1.0], [-1.0], [-1.0]]), + ) + def test_field_arithmetic_with_scalar_and_ndarray(self): v = make_line_vector() @@ -157,6 +176,24 @@ def test_full_cell_assignment_allows_row_count_changes(self): np.testing.assert_array_equal(v[0].array, broadcast_cell) np.testing.assert_array_equal(v[3].array, broadcast_cell) + def test_append_rows_and_compact(self): + v = make_line_vector() + + v.append_rows(1, np.array([[7.0, 70.0, 700.0]])) + np.testing.assert_array_equal( + v[1].array, + np.array([[3.0, 30.0, 300.0], [7.0, 70.0, 700.0]]), + ) + + v[1] = np.array([[8.0, 80.0, 800.0]]) + assert v._state["data"].shape[0] > v.total_rows + + v.compact() + assert v._state["data"].shape[0] == v.total_rows + + with pytest.raises(ValueError, match="exactly one cell"): + v.append_rows(slice(None), np.array([[1.0, 2.0, 3.0]])) + def test_boolean_indexing_is_axis_wise(self): v = make_grid_vector() From 7d2dc222c1c8d60ffbfc206171edb73678623902 Mon Sep 17 00:00:00 2001 From: cophus Date: Sun, 1 Mar 2026 13:56:48 -0800 Subject: [PATCH 109/335] add dtype and num_cells --- src/quantem/core/datastructures/vector.py | 10 ++++++++++ tests/datastructures/test_vector.py | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/src/quantem/core/datastructures/vector.py b/src/quantem/core/datastructures/vector.py index dc8865c0..a57ef7dc 100644 --- a/src/quantem/core/datastructures/vector.py +++ b/src/quantem/core/datastructures/vector.py @@ -224,6 +224,11 @@ def num_fields(self) -> int: """Return the number of selected fields.""" return len(self.fields) + @property + def dtype(self) -> np.dtype[Any]: + """Return the NumPy dtype of the backing row buffer.""" + return self._state["data"].dtype + @property def name(self) -> str: """Human-readable Vector name.""" @@ -313,6 +318,11 @@ def total_rows(self) -> int: """Return the total ragged-row count in the current selection.""" return int(self._state["cell_lengths"][self._selected_cell_indices()].sum()) + @property + def num_cells(self) -> int: + """Return the number of fixed-grid cells in the current selection.""" + return int(self._selected_cell_indices().size) + def row_counts(self) -> list[int]: """Return per-cell row counts in the current selection order.""" return [self._cell_row_count(int(index)) for index in self._selected_cell_indices()] diff --git a/tests/datastructures/test_vector.py b/tests/datastructures/test_vector.py index 4086d4d5..f9071644 100644 --- a/tests/datastructures/test_vector.py +++ b/tests/datastructures/test_vector.py @@ -37,7 +37,9 @@ def test_initialization_and_len(self): v1 = Vector.from_shape(shape=(2, 3), fields=["a", "b", "c"]) assert v1.shape == (2, 3) assert len(v1) == 2 + assert v1.num_cells == 6 assert v1.num_fields == 3 + assert v1.dtype == np.dtype(float) assert v1.fields == ["a", "b", "c"] assert v1.units == ["none", "none", "none"] assert v1.name == "2d ragged array" @@ -79,6 +81,7 @@ def test_indexing_and_array_contract(self): result = v[[-1, 0], 1] assert result.shape == (2,) + assert result.num_cells == 2 np.testing.assert_array_equal(result[0].array, np.array([[21.0, 121.0, 221.0]])) np.testing.assert_array_equal(result[1].array, np.array([[1.0, 101.0, 201.0]])) @@ -106,6 +109,7 @@ def test_select_fields_and_chaining_equivalence(self): multi = v.select_fields("intensity", "kx") assert multi.fields == ["intensity", "kx"] + assert multi.dtype == np.dtype(float) assert multi.total_rows == 6 assert multi.row_counts() == [2, 1, 2, 1] From d4de7fe6307d8764b7d6aa10bcf85a4f9a9d12ee Mon Sep 17 00:00:00 2001 From: quantem-bot Date: Mon, 2 Mar 2026 09:58:28 +0000 Subject: [PATCH 110/335] chore: update lock file --- uv.lock | 284 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 151 insertions(+), 133 deletions(-) diff --git a/uv.lock b/uv.lock index 2d2559a0..13d0ff9e 100644 --- a/uv.lock +++ b/uv.lock @@ -196,11 +196,11 @@ css = [ [[package]] name = "certifi" -version = "2026.1.4" +version = "2026.2.25" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, ] [[package]] @@ -636,10 +636,10 @@ wheels = [ [[package]] name = "cuda-pathfinder" -version = "1.3.4" +version = "1.4.0" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/5e/db279a3bfbd18d59d0598922a3b3c1454908d0969e8372260afec9736376/cuda_pathfinder-1.3.4-py3-none-any.whl", hash = "sha256:fb983f6e0d43af27ef486e14d5989b5f904ef45cedf40538bfdcbffa6bb01fb2", size = 30878, upload-time = "2026-02-11T18:50:31.008Z" }, + { url = "https://files.pythonhosted.org/packages/ff/60/d8f1dbfb7f06b94c662e98c95189e6f39b817da638bc8fcea0d003f89e5d/cuda_pathfinder-1.4.0-py3-none-any.whl", hash = "sha256:437079ca59e7b61ae439ecc501d69ed87b3accc34d58153ef1e54815e2c2e118", size = 38406, upload-time = "2026-02-25T22:13:00.807Z" }, ] [[package]] @@ -768,11 +768,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.24.3" +version = "3.25.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/73/92/a8e2479937ff39185d20dd6a851c1a63e55849e447a55e798cc2e1f49c65/filelock-3.24.3.tar.gz", hash = "sha256:011a5644dc937c22699943ebbfc46e969cdde3e171470a6e40b9533e5a72affa", size = 37935, upload-time = "2026-02-19T00:48:20.543Z" } +sdist = { url = "https://files.pythonhosted.org/packages/77/18/a1fd2231c679dcb9726204645721b12498aeac28e1ad0601038f94b42556/filelock-3.25.0.tar.gz", hash = "sha256:8f00faf3abf9dc730a1ffe9c354ae5c04e079ab7d3a683b7c32da5dd05f26af3", size = 40158, upload-time = "2026-03-01T15:08:45.916Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/0f/5d0c71a1aefeb08efff26272149e07ab922b64f46c63363756224bd6872e/filelock-3.24.3-py3-none-any.whl", hash = "sha256:426e9a4660391f7f8a810d71b0555bce9008b0a1cc342ab1f6947d37639e002d", size = 24331, upload-time = "2026-02-19T00:48:18.465Z" }, + { url = "https://files.pythonhosted.org/packages/f9/0b/de6f54d4a8bedfe8645c41497f3c18d749f0bd3218170c667bf4b81d0cdd/filelock-3.25.0-py3-none-any.whl", hash = "sha256:5ccf8069f7948f494968fc0713c10e5c182a9c9d9eef3a636307a20c2490f047", size = 26427, upload-time = "2026-03-01T15:08:44.593Z" }, ] [[package]] @@ -945,53 +945,53 @@ wheels = [ [[package]] name = "grpcio" -version = "1.78.1" +version = "1.78.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1f/de/de568532d9907552700f80dcec38219d8d298ad9e71f5e0a095abaf2761e/grpcio-1.78.1.tar.gz", hash = "sha256:27c625532d33ace45d57e775edf1982e183ff8641c72e4e91ef7ba667a149d72", size = 12835760, upload-time = "2026-02-20T01:16:10.869Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/1e/ad774af3b2c84f49c6d8c4a7bea4c40f02268ea8380630c28777edda463b/grpcio-1.78.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:3a8aa79bc6e004394c0abefd4b034c14affda7b66480085d87f5fbadf43b593b", size = 5951132, upload-time = "2026-02-20T01:13:05.942Z" }, - { url = "https://files.pythonhosted.org/packages/48/9d/ad3c284bedd88c545e20675d98ae904114d8517a71b0efc0901e9166628f/grpcio-1.78.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:8e1fcb419da5811deb47b7749b8049f7c62b993ba17822e3c7231e3e0ba65b79", size = 11831052, upload-time = "2026-02-20T01:13:09.604Z" }, - { url = "https://files.pythonhosted.org/packages/6d/08/20d12865e47242d03c3ade9bb2127f5b4aded964f373284cfb357d47c5ac/grpcio-1.78.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b071dccac245c32cd6b1dd96b722283b855881ca0bf1c685cf843185f5d5d51e", size = 6524749, upload-time = "2026-02-20T01:13:21.692Z" }, - { url = "https://files.pythonhosted.org/packages/c6/53/a8b72f52b253ec0cfdf88a13e9236a9d717c332b8aa5f0ba9e4699e94b55/grpcio-1.78.1-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:d6fb962947e4fe321eeef3be1ba5ba49d32dea9233c825fcbade8e858c14aaf4", size = 7198995, upload-time = "2026-02-20T01:13:24.275Z" }, - { url = "https://files.pythonhosted.org/packages/13/3c/ac769c8ded1bcb26bb119fb472d3374b481b3cf059a0875db9fc77139c17/grpcio-1.78.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6afd191551fd72e632367dfb083e33cd185bf9ead565f2476bba8ab864ae496", size = 6730770, upload-time = "2026-02-20T01:13:26.522Z" }, - { url = "https://files.pythonhosted.org/packages/dc/c3/2275ef4cc5b942314321f77d66179be4097ff484e82ca34bf7baa5b1ddbc/grpcio-1.78.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b2acd83186305c0802dbc4d81ed0ec2f3e8658d7fde97cfba2f78d7372f05b89", size = 7305036, upload-time = "2026-02-20T01:13:30.923Z" }, - { url = "https://files.pythonhosted.org/packages/91/cb/3c2aa99e12cbbfc72c2ed8aa328e6041709d607d668860380e6cd00ba17d/grpcio-1.78.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5380268ab8513445740f1f77bd966d13043d07e2793487e61fd5b5d0935071eb", size = 8288641, upload-time = "2026-02-20T01:13:39.42Z" }, - { url = "https://files.pythonhosted.org/packages/0d/b2/21b89f492260ac645775d9973752ca873acfd0609d6998e9d3065a21ea2f/grpcio-1.78.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:389b77484959bdaad6a2b7dda44d7d1228381dd669a03f5660392aa0e9385b22", size = 7730967, upload-time = "2026-02-20T01:13:41.697Z" }, - { url = "https://files.pythonhosted.org/packages/24/03/6b89eddf87fdffb8fa9d37375d44d3a798f4b8116ac363a5f7ca84caa327/grpcio-1.78.1-cp311-cp311-win32.whl", hash = "sha256:9dee66d142f4a8cca36b5b98a38f006419138c3c89e72071747f8fca415a6d8f", size = 4076680, upload-time = "2026-02-20T01:13:43.781Z" }, - { url = "https://files.pythonhosted.org/packages/a7/a8/204460b1bc1dff9862e98f56a2d14be3c4171f929f8eaf8c4517174b4270/grpcio-1.78.1-cp311-cp311-win_amd64.whl", hash = "sha256:43b930cf4f9c4a2262bb3e5d5bc40df426a72538b4f98e46f158b7eb112d2d70", size = 4801074, upload-time = "2026-02-20T01:13:46.315Z" }, - { url = "https://files.pythonhosted.org/packages/ab/ed/d2eb9d27fded1a76b2a80eb9aa8b12101da7e41ce2bac0ad3651e88a14ae/grpcio-1.78.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:41e4605c923e0e9a84a2718e4948a53a530172bfaf1a6d1ded16ef9c5849fca2", size = 5913389, upload-time = "2026-02-20T01:13:49.005Z" }, - { url = "https://files.pythonhosted.org/packages/69/1b/40034e9ab010eeb3fa41ec61d8398c6dbf7062f3872c866b8f72700e2522/grpcio-1.78.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:39da1680d260c0c619c3b5fa2dc47480ca24d5704c7a548098bca7de7f5dd17f", size = 11811839, upload-time = "2026-02-20T01:13:51.839Z" }, - { url = "https://files.pythonhosted.org/packages/b4/69/fe16ef2979ea62b8aceb3a3f1e7a8bbb8b717ae2a44b5899d5d426073273/grpcio-1.78.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b5d5881d72a09b8336a8f874784a8eeffacde44a7bc1a148bce5a0243a265ef0", size = 6475805, upload-time = "2026-02-20T01:13:55.423Z" }, - { url = "https://files.pythonhosted.org/packages/5b/1e/069e0a9062167db18446917d7c00ae2e91029f96078a072bedc30aaaa8c3/grpcio-1.78.1-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:888ceb7821acd925b1c90f0cdceaed1386e69cfe25e496e0771f6c35a156132f", size = 7169955, upload-time = "2026-02-20T01:13:59.553Z" }, - { url = "https://files.pythonhosted.org/packages/38/fc/44a57e2bb4a755e309ee4e9ed2b85c9af93450b6d3118de7e69410ee05fa/grpcio-1.78.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8942bdfc143b467c264b048862090c4ba9a0223c52ae28c9ae97754361372e42", size = 6690767, upload-time = "2026-02-20T01:14:02.31Z" }, - { url = "https://files.pythonhosted.org/packages/b8/87/21e16345d4c75046d453916166bc72a3309a382c8e97381ec4b8c1a54729/grpcio-1.78.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:716a544969660ed609164aff27b2effd3ff84e54ac81aa4ce77b1607ca917d22", size = 7266846, upload-time = "2026-02-20T01:14:12.974Z" }, - { url = "https://files.pythonhosted.org/packages/11/df/d6261983f9ca9ef4d69893765007a9a3211b91d9faf85a2591063df381c7/grpcio-1.78.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4d50329b081c223d444751076bb5b389d4f06c2b32d51b31a1e98172e6cecfb9", size = 8253522, upload-time = "2026-02-20T01:14:17.407Z" }, - { url = "https://files.pythonhosted.org/packages/de/7c/4f96a0ff113c5d853a27084d7590cd53fdb05169b596ea9f5f27f17e021e/grpcio-1.78.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7e836778c13ff70edada16567e8da0c431e8818eaae85b80d11c1ba5782eccbb", size = 7698070, upload-time = "2026-02-20T01:14:20.032Z" }, - { url = "https://files.pythonhosted.org/packages/17/3c/7b55c0b5af88fbeb3d0c13e25492d3ace41ac9dbd0f5f8f6c0fb613b6706/grpcio-1.78.1-cp312-cp312-win32.whl", hash = "sha256:07eb016ea7444a22bef465cce045512756956433f54450aeaa0b443b8563b9ca", size = 4066474, upload-time = "2026-02-20T01:14:22.602Z" }, - { url = "https://files.pythonhosted.org/packages/5d/17/388c12d298901b0acf10b612b650692bfed60e541672b1d8965acbf2d722/grpcio-1.78.1-cp312-cp312-win_amd64.whl", hash = "sha256:02b82dcd2fa580f5e82b4cf62ecde1b3c7cc9ba27b946421200706a6e5acaf85", size = 4797537, upload-time = "2026-02-20T01:14:25.444Z" }, - { url = "https://files.pythonhosted.org/packages/df/72/754754639cfd16ad04619e1435a518124b2d858e5752225376f9285d4c51/grpcio-1.78.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:2b7ad2981550ce999e25ce3f10c8863f718a352a2fd655068d29ea3fd37b4907", size = 5919437, upload-time = "2026-02-20T01:14:29.403Z" }, - { url = "https://files.pythonhosted.org/packages/5c/84/6267d1266f8bc335d3a8b7ccf981be7de41e3ed8bd3a49e57e588212b437/grpcio-1.78.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:409bfe22220889b9906739910a0ee4c197a967c21b8dd14b4b06dd477f8819ce", size = 11803701, upload-time = "2026-02-20T01:14:32.624Z" }, - { url = "https://files.pythonhosted.org/packages/f3/56/c9098e8b920a54261cd605bbb040de0cde1ca4406102db0aa2c0b11d1fb4/grpcio-1.78.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:34b6cb16f4b67eeb5206250dc5b4d5e8e3db939535e58efc330e4c61341554bd", size = 6479416, upload-time = "2026-02-20T01:14:35.926Z" }, - { url = "https://files.pythonhosted.org/packages/86/cf/5d52024371ee62658b7ed72480200524087528844ec1b65265bbcd31c974/grpcio-1.78.1-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:39d21fd30d38a5afb93f0e2e71e2ec2bd894605fb75d41d5a40060c2f98f8d11", size = 7174087, upload-time = "2026-02-20T01:14:39.98Z" }, - { url = "https://files.pythonhosted.org/packages/31/e6/5e59551afad4279e27335a6d60813b8aa3ae7b14fb62cea1d329a459c118/grpcio-1.78.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:09fbd4bcaadb6d8604ed1504b0bdf7ac18e48467e83a9d930a70a7fefa27e862", size = 6692881, upload-time = "2026-02-20T01:14:42.466Z" }, - { url = "https://files.pythonhosted.org/packages/db/8f/940062de2d14013c02f51b079eb717964d67d46f5d44f22038975c9d9576/grpcio-1.78.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:db681513a1bdd879c0b24a5a6a70398da5eaaba0e077a306410dc6008426847a", size = 7269092, upload-time = "2026-02-20T01:14:45.826Z" }, - { url = "https://files.pythonhosted.org/packages/09/87/9db657a4b5f3b15560ec591db950bc75a1a2f9e07832578d7e2b23d1a7bd/grpcio-1.78.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f81816faa426da461e9a597a178832a351d6f1078102590a4b32c77d251b71eb", size = 8252037, upload-time = "2026-02-20T01:14:48.57Z" }, - { url = "https://files.pythonhosted.org/packages/e2/37/b980e0265479ec65e26b6e300a39ceac33ecb3f762c2861d4bac990317cf/grpcio-1.78.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffbb760df1cd49e0989f9826b2fd48930700db6846ac171eaff404f3cfbe5c28", size = 7695243, upload-time = "2026-02-20T01:14:51.376Z" }, - { url = "https://files.pythonhosted.org/packages/98/46/5fc42c100ab702fa1ea41a75c890c563c3f96432b4a287d5a6369654f323/grpcio-1.78.1-cp313-cp313-win32.whl", hash = "sha256:1a56bf3ee99af5cf32d469de91bf5de79bdac2e18082b495fc1063ea33f4f2d0", size = 4065329, upload-time = "2026-02-20T01:14:53.952Z" }, - { url = "https://files.pythonhosted.org/packages/b0/da/806d60bb6611dfc16cf463d982bd92bd8b6bd5f87dfac66b0a44dfe20995/grpcio-1.78.1-cp313-cp313-win_amd64.whl", hash = "sha256:8991c2add0d8505178ff6c3ae54bd9386279e712be82fa3733c54067aae9eda1", size = 4797637, upload-time = "2026-02-20T01:14:57.276Z" }, - { url = "https://files.pythonhosted.org/packages/96/3a/2d2ec4d2ce2eb9d6a2b862630a0d9d4ff4239ecf1474ecff21442a78612a/grpcio-1.78.1-cp314-cp314-linux_armv7l.whl", hash = "sha256:d101fe49b1e0fb4a7aa36ed0c3821a0f67a5956ef572745452d2cd790d723a3f", size = 5920256, upload-time = "2026-02-20T01:15:00.23Z" }, - { url = "https://files.pythonhosted.org/packages/9c/92/dccb7d087a1220ed358753945230c1ddeeed13684b954cb09db6758f1271/grpcio-1.78.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:5ce1855e8cfc217cdf6bcfe0cf046d7cf81ddcc3e6894d6cfd075f87a2d8f460", size = 11813749, upload-time = "2026-02-20T01:15:03.312Z" }, - { url = "https://files.pythonhosted.org/packages/ef/47/c20e87f87986da9998f30f14776ce27e61f02482a3a030ffe265089342c6/grpcio-1.78.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd26048d066b51f39fe9206e2bcc2cea869a5e5b2d13c8d523f4179193047ebd", size = 6488739, upload-time = "2026-02-20T01:15:14.349Z" }, - { url = "https://files.pythonhosted.org/packages/a6/c2/088bd96e255133d7d87c3eed0d598350d16cde1041bdbe2bb065967aaf91/grpcio-1.78.1-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4b8d7fda614cf2af0f73bbb042f3b7fee2ecd4aea69ec98dbd903590a1083529", size = 7173096, upload-time = "2026-02-20T01:15:17.687Z" }, - { url = "https://files.pythonhosted.org/packages/60/ce/168db121073a03355ce3552b3b1f790b5ded62deffd7d98c5f642b9d3d81/grpcio-1.78.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:656a5bd142caeb8b1efe1fe0b4434ecc7781f44c97cfc7927f6608627cf178c0", size = 6693861, upload-time = "2026-02-20T01:15:20.911Z" }, - { url = "https://files.pythonhosted.org/packages/ae/d0/90b30ec2d9425215dd56922d85a90babbe6ee7e8256ba77d866b9c0d3aba/grpcio-1.78.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:99550e344482e3c21950c034f74668fccf8a546d50c1ecb4f717543bbdc071ba", size = 7278083, upload-time = "2026-02-20T01:15:23.698Z" }, - { url = "https://files.pythonhosted.org/packages/c1/fb/73f9ba0b082bcd385d46205095fd9c917754685885b28fce3741e9f54529/grpcio-1.78.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:8f27683ca68359bd3f0eb4925824d71e538f84338b3ae337ead2ae43977d7541", size = 8252546, upload-time = "2026-02-20T01:15:26.517Z" }, - { url = "https://files.pythonhosted.org/packages/85/c5/6a89ea3cb5db6c3d9ed029b0396c49f64328c0cf5d2630ffeed25711920a/grpcio-1.78.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a40515b69ac50792f9b8ead260f194ba2bb3285375b6c40c7ff938f14c3df17d", size = 7696289, upload-time = "2026-02-20T01:15:29.718Z" }, - { url = "https://files.pythonhosted.org/packages/3d/05/63a7495048499ef437b4933d32e59b7f737bd5368ad6fb2479e2bd83bf2c/grpcio-1.78.1-cp314-cp314-win32.whl", hash = "sha256:2c473b54ef1618f4fb85e82ff4994de18143b74efc088b91b5a935a3a45042ba", size = 4142186, upload-time = "2026-02-20T01:15:32.786Z" }, - { url = "https://files.pythonhosted.org/packages/1c/ce/adfe7e5f701d503be7778291757452e3fab6b19acf51917c79f5d1cf7f8a/grpcio-1.78.1-cp314-cp314-win_amd64.whl", hash = "sha256:e2a6b33d1050dce2c6f563c5caf7f7cbeebf7fba8cde37ffe3803d50526900d1", size = 4932000, upload-time = "2026-02-20T01:15:36.127Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/06/8a/3d098f35c143a89520e568e6539cc098fcd294495910e359889ce8741c84/grpcio-1.78.0.tar.gz", hash = "sha256:7382b95189546f375c174f53a5fa873cef91c4b8005faa05cc5b3beea9c4f1c5", size = 12852416, upload-time = "2026-02-06T09:57:18.093Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/c7/d0b780a29b0837bf4ca9580904dfb275c1fc321ded7897d620af7047ec57/grpcio-1.78.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:2777b783f6c13b92bd7b716667452c329eefd646bfb3f2e9dabea2e05dbd34f6", size = 5951525, upload-time = "2026-02-06T09:55:01.989Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b1/96920bf2ee61df85a9503cb6f733fe711c0ff321a5a697d791b075673281/grpcio-1.78.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:9dca934f24c732750389ce49d638069c3892ad065df86cb465b3fa3012b70c9e", size = 11830418, upload-time = "2026-02-06T09:55:04.462Z" }, + { url = "https://files.pythonhosted.org/packages/83/0c/7c1528f098aeb75a97de2bae18c530f56959fb7ad6c882db45d9884d6edc/grpcio-1.78.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:459ab414b35f4496138d0ecd735fed26f1318af5e52cb1efbc82a09f0d5aa911", size = 6524477, upload-time = "2026-02-06T09:55:07.111Z" }, + { url = "https://files.pythonhosted.org/packages/8d/52/e7c1f3688f949058e19a011c4e0dec973da3d0ae5e033909677f967ae1f4/grpcio-1.78.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:082653eecbdf290e6e3e2c276ab2c54b9e7c299e07f4221872380312d8cf395e", size = 7198266, upload-time = "2026-02-06T09:55:10.016Z" }, + { url = "https://files.pythonhosted.org/packages/e5/61/8ac32517c1e856677282c34f2e7812d6c328fa02b8f4067ab80e77fdc9c9/grpcio-1.78.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85f93781028ec63f383f6bc90db785a016319c561cc11151fbb7b34e0d012303", size = 6730552, upload-time = "2026-02-06T09:55:12.207Z" }, + { url = "https://files.pythonhosted.org/packages/bd/98/b8ee0158199250220734f620b12e4a345955ac7329cfd908d0bf0fda77f0/grpcio-1.78.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f12857d24d98441af6a1d5c87442d624411db486f7ba12550b07788f74b67b04", size = 7304296, upload-time = "2026-02-06T09:55:15.044Z" }, + { url = "https://files.pythonhosted.org/packages/bd/0f/7b72762e0d8840b58032a56fdbd02b78fc645b9fa993d71abf04edbc54f4/grpcio-1.78.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5397fff416b79e4b284959642a4e95ac4b0f1ece82c9993658e0e477d40551ec", size = 8288298, upload-time = "2026-02-06T09:55:17.276Z" }, + { url = "https://files.pythonhosted.org/packages/24/ae/ae4ce56bc5bb5caa3a486d60f5f6083ac3469228faa734362487176c15c5/grpcio-1.78.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fbe6e89c7ffb48518384068321621b2a69cab509f58e40e4399fdd378fa6d074", size = 7730953, upload-time = "2026-02-06T09:55:19.545Z" }, + { url = "https://files.pythonhosted.org/packages/b5/6e/8052e3a28eb6a820c372b2eb4b5e32d195c661e137d3eca94d534a4cfd8a/grpcio-1.78.0-cp311-cp311-win32.whl", hash = "sha256:6092beabe1966a3229f599d7088b38dfc8ffa1608b5b5cdda31e591e6500f856", size = 4076503, upload-time = "2026-02-06T09:55:21.521Z" }, + { url = "https://files.pythonhosted.org/packages/08/62/f22c98c5265dfad327251fa2f840b591b1df5f5e15d88b19c18c86965b27/grpcio-1.78.0-cp311-cp311-win_amd64.whl", hash = "sha256:1afa62af6e23f88629f2b29ec9e52ec7c65a7176c1e0a83292b93c76ca882558", size = 4799767, upload-time = "2026-02-06T09:55:24.107Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f4/7384ed0178203d6074446b3c4f46c90a22ddf7ae0b3aee521627f54cfc2a/grpcio-1.78.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:f9ab915a267fc47c7e88c387a3a28325b58c898e23d4995f765728f4e3dedb97", size = 5913985, upload-time = "2026-02-06T09:55:26.832Z" }, + { url = "https://files.pythonhosted.org/packages/81/ed/be1caa25f06594463f685b3790b320f18aea49b33166f4141bfdc2bfb236/grpcio-1.78.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3f8904a8165ab21e07e58bf3e30a73f4dffc7a1e0dbc32d51c61b5360d26f43e", size = 11811853, upload-time = "2026-02-06T09:55:29.224Z" }, + { url = "https://files.pythonhosted.org/packages/24/a7/f06d151afc4e64b7e3cc3e872d331d011c279aaab02831e40a81c691fb65/grpcio-1.78.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:859b13906ce098c0b493af92142ad051bf64c7870fa58a123911c88606714996", size = 6475766, upload-time = "2026-02-06T09:55:31.825Z" }, + { url = "https://files.pythonhosted.org/packages/8a/a8/4482922da832ec0082d0f2cc3a10976d84a7424707f25780b82814aafc0a/grpcio-1.78.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b2342d87af32790f934a79c3112641e7b27d63c261b8b4395350dad43eff1dc7", size = 7170027, upload-time = "2026-02-06T09:55:34.7Z" }, + { url = "https://files.pythonhosted.org/packages/54/bf/f4a3b9693e35d25b24b0b39fa46d7d8a3c439e0a3036c3451764678fec20/grpcio-1.78.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12a771591ae40bc65ba67048fa52ef4f0e6db8279e595fd349f9dfddeef571f9", size = 6690766, upload-time = "2026-02-06T09:55:36.902Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b9/521875265cc99fe5ad4c5a17010018085cae2810a928bf15ebe7d8bcd9cc/grpcio-1.78.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:185dea0d5260cbb2d224c507bf2a5444d5abbb1fa3594c1ed7e4c709d5eb8383", size = 7266161, upload-time = "2026-02-06T09:55:39.824Z" }, + { url = "https://files.pythonhosted.org/packages/05/86/296a82844fd40a4ad4a95f100b55044b4f817dece732bf686aea1a284147/grpcio-1.78.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51b13f9aed9d59ee389ad666b8c2214cc87b5de258fa712f9ab05f922e3896c6", size = 8253303, upload-time = "2026-02-06T09:55:42.353Z" }, + { url = "https://files.pythonhosted.org/packages/f3/e4/ea3c0caf5468537f27ad5aab92b681ed7cc0ef5f8c9196d3fd42c8c2286b/grpcio-1.78.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd5f135b1bd58ab088930b3c613455796dfa0393626a6972663ccdda5b4ac6ce", size = 7698222, upload-time = "2026-02-06T09:55:44.629Z" }, + { url = "https://files.pythonhosted.org/packages/d7/47/7f05f81e4bb6b831e93271fb12fd52ba7b319b5402cbc101d588f435df00/grpcio-1.78.0-cp312-cp312-win32.whl", hash = "sha256:94309f498bcc07e5a7d16089ab984d42ad96af1d94b5a4eb966a266d9fcabf68", size = 4066123, upload-time = "2026-02-06T09:55:47.644Z" }, + { url = "https://files.pythonhosted.org/packages/ad/e7/d6914822c88aa2974dbbd10903d801a28a19ce9cd8bad7e694cbbcf61528/grpcio-1.78.0-cp312-cp312-win_amd64.whl", hash = "sha256:9566fe4ababbb2610c39190791e5b829869351d14369603702e890ef3ad2d06e", size = 4797657, upload-time = "2026-02-06T09:55:49.86Z" }, + { url = "https://files.pythonhosted.org/packages/05/a9/8f75894993895f361ed8636cd9237f4ab39ef87fd30db17467235ed1c045/grpcio-1.78.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:ce3a90455492bf8bfa38e56fbbe1dbd4f872a3d8eeaf7337dc3b1c8aa28c271b", size = 5920143, upload-time = "2026-02-06T09:55:52.035Z" }, + { url = "https://files.pythonhosted.org/packages/55/06/0b78408e938ac424100100fd081189451b472236e8a3a1f6500390dc4954/grpcio-1.78.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:2bf5e2e163b356978b23652c4818ce4759d40f4712ee9ec5a83c4be6f8c23a3a", size = 11803926, upload-time = "2026-02-06T09:55:55.494Z" }, + { url = "https://files.pythonhosted.org/packages/88/93/b59fe7832ff6ae3c78b813ea43dac60e295fa03606d14d89d2e0ec29f4f3/grpcio-1.78.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8f2ac84905d12918e4e55a16da17939eb63e433dc11b677267c35568aa63fc84", size = 6478628, upload-time = "2026-02-06T09:55:58.533Z" }, + { url = "https://files.pythonhosted.org/packages/ed/df/e67e3734527f9926b7d9c0dde6cd998d1d26850c3ed8eeec81297967ac67/grpcio-1.78.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b58f37edab4a3881bc6c9bca52670610e0c9ca14e2ea3cf9debf185b870457fb", size = 7173574, upload-time = "2026-02-06T09:56:01.786Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/cc03fffb07bfba982a9ec097b164e8835546980aec25ecfa5f9c1a47e022/grpcio-1.78.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:735e38e176a88ce41840c21bb49098ab66177c64c82426e24e0082500cc68af5", size = 6692639, upload-time = "2026-02-06T09:56:04.529Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9a/289c32e301b85bdb67d7ec68b752155e674ee3ba2173a1858f118e399ef3/grpcio-1.78.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2045397e63a7a0ee7957c25f7dbb36ddc110e0cfb418403d110c0a7a68a844e9", size = 7268838, upload-time = "2026-02-06T09:56:08.397Z" }, + { url = "https://files.pythonhosted.org/packages/0e/79/1be93f32add280461fa4773880196572563e9c8510861ac2da0ea0f892b6/grpcio-1.78.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a9f136fbafe7ccf4ac7e8e0c28b31066e810be52d6e344ef954a3a70234e1702", size = 8251878, upload-time = "2026-02-06T09:56:10.914Z" }, + { url = "https://files.pythonhosted.org/packages/65/65/793f8e95296ab92e4164593674ae6291b204bb5f67f9d4a711489cd30ffa/grpcio-1.78.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:748b6138585379c737adc08aeffd21222abbda1a86a0dca2a39682feb9196c20", size = 7695412, upload-time = "2026-02-06T09:56:13.593Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9f/1e233fe697ecc82845942c2822ed06bb522e70d6771c28d5528e4c50f6a4/grpcio-1.78.0-cp313-cp313-win32.whl", hash = "sha256:271c73e6e5676afe4fc52907686670c7cea22ab2310b76a59b678403ed40d670", size = 4064899, upload-time = "2026-02-06T09:56:15.601Z" }, + { url = "https://files.pythonhosted.org/packages/4d/27/d86b89e36de8a951501fb06a0f38df19853210f341d0b28f83f4aa0ffa08/grpcio-1.78.0-cp313-cp313-win_amd64.whl", hash = "sha256:f2d4e43ee362adfc05994ed479334d5a451ab7bc3f3fee1b796b8ca66895acb4", size = 4797393, upload-time = "2026-02-06T09:56:17.882Z" }, + { url = "https://files.pythonhosted.org/packages/29/f2/b56e43e3c968bfe822fa6ce5bca10d5c723aa40875b48791ce1029bb78c7/grpcio-1.78.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:e87cbc002b6f440482b3519e36e1313eb5443e9e9e73d6a52d43bd2004fcfd8e", size = 5920591, upload-time = "2026-02-06T09:56:20.758Z" }, + { url = "https://files.pythonhosted.org/packages/5d/81/1f3b65bd30c334167bfa8b0d23300a44e2725ce39bba5b76a2460d85f745/grpcio-1.78.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:c41bc64626db62e72afec66b0c8a0da76491510015417c127bfc53b2fe6d7f7f", size = 11813685, upload-time = "2026-02-06T09:56:24.315Z" }, + { url = "https://files.pythonhosted.org/packages/0e/1c/bbe2f8216a5bd3036119c544d63c2e592bdf4a8ec6e4a1867592f4586b26/grpcio-1.78.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8dfffba826efcf366b1e3ccc37e67afe676f290e13a3b48d31a46739f80a8724", size = 6487803, upload-time = "2026-02-06T09:56:27.367Z" }, + { url = "https://files.pythonhosted.org/packages/16/5c/a6b2419723ea7ddce6308259a55e8e7593d88464ce8db9f4aa857aba96fa/grpcio-1.78.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:74be1268d1439eaaf552c698cdb11cd594f0c49295ae6bb72c34ee31abbe611b", size = 7173206, upload-time = "2026-02-06T09:56:29.876Z" }, + { url = "https://files.pythonhosted.org/packages/df/1e/b8801345629a415ea7e26c83d75eb5dbe91b07ffe5210cc517348a8d4218/grpcio-1.78.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be63c88b32e6c0f1429f1398ca5c09bc64b0d80950c8bb7807d7d7fb36fb84c7", size = 6693826, upload-time = "2026-02-06T09:56:32.305Z" }, + { url = "https://files.pythonhosted.org/packages/34/84/0de28eac0377742679a510784f049738a80424b17287739fc47d63c2439e/grpcio-1.78.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3c586ac70e855c721bda8f548d38c3ca66ac791dc49b66a8281a1f99db85e452", size = 7277897, upload-time = "2026-02-06T09:56:34.915Z" }, + { url = "https://files.pythonhosted.org/packages/ca/9c/ad8685cfe20559a9edb66f735afdcb2b7d3de69b13666fdfc542e1916ebd/grpcio-1.78.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:35eb275bf1751d2ffbd8f57cdbc46058e857cf3971041521b78b7db94bdaf127", size = 8252404, upload-time = "2026-02-06T09:56:37.553Z" }, + { url = "https://files.pythonhosted.org/packages/3c/05/33a7a4985586f27e1de4803887c417ec7ced145ebd069bc38a9607059e2b/grpcio-1.78.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:207db540302c884b8848036b80db352a832b99dfdf41db1eb554c2c2c7800f65", size = 7696837, upload-time = "2026-02-06T09:56:40.173Z" }, + { url = "https://files.pythonhosted.org/packages/73/77/7382241caf88729b106e49e7d18e3116216c778e6a7e833826eb96de22f7/grpcio-1.78.0-cp314-cp314-win32.whl", hash = "sha256:57bab6deef2f4f1ca76cc04565df38dc5713ae6c17de690721bdf30cb1e0545c", size = 4142439, upload-time = "2026-02-06T09:56:43.258Z" }, + { url = "https://files.pythonhosted.org/packages/48/b2/b096ccce418882fbfda4f7496f9357aaa9a5af1896a9a7f60d9f2b275a06/grpcio-1.78.0-cp314-cp314-win_amd64.whl", hash = "sha256:dce09d6116df20a96acfdbf85e4866258c3758180e8c49845d6ba8248b6d0bbb", size = 4929852, upload-time = "2026-02-06T09:56:45.885Z" }, ] [[package]] @@ -1092,11 +1092,11 @@ wheels = [ [[package]] name = "identify" -version = "2.6.16" +version = "2.6.17" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5b/8d/e8b97e6bd3fb6fb271346f7981362f1e04d6a7463abd0de79e1fda17c067/identify-2.6.16.tar.gz", hash = "sha256:846857203b5511bbe94d5a352a48ef2359532bc8f6727b5544077a0dcfb24980", size = 99360, upload-time = "2026-01-12T18:58:58.201Z" } +sdist = { url = "https://files.pythonhosted.org/packages/57/84/376a3b96e5a8d33a7aa2c5b3b31a4b3c364117184bf0b17418055f6ace66/identify-2.6.17.tar.gz", hash = "sha256:f816b0b596b204c9fdf076ded172322f2723cf958d02f9c3587504834c8ff04d", size = 99579, upload-time = "2026-03-01T20:04:12.702Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/58/40fbbcefeda82364720eba5cf2270f98496bdfa19ea75b4cccae79c698e6/identify-2.6.16-py2.py3-none-any.whl", hash = "sha256:391ee4d77741d994189522896270b787aed8670389bfd60f326d677d64a6dfb0", size = 99202, upload-time = "2026-01-12T18:58:56.627Z" }, + { url = "https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl", hash = "sha256:be5f8412d5ed4b20f2bd41a65f920990bdccaa6a4a18a08f1eefdcd0bdd885f0", size = 99382, upload-time = "2026-03-01T20:04:11.439Z" }, ] [[package]] @@ -1415,7 +1415,7 @@ wheels = [ [[package]] name = "jupyterlab" -version = "4.5.4" +version = "4.5.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "async-lru" }, @@ -1432,9 +1432,9 @@ dependencies = [ { name = "tornado" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7c/6b/21af7c0512bdf67e0c54c121779a1f2a97a164a7657e13fced79db8fa5a0/jupyterlab-4.5.4.tar.gz", hash = "sha256:c215f48d8e4582bd2920ad61cc6a40d8ebfef7e5a517ae56b8a9413c9789fdfb", size = 23943597, upload-time = "2026-02-11T00:26:55.308Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/2d/953a5612a34a3c799a62566a548e711d103f631672fd49650e0f2de80870/jupyterlab-4.5.5.tar.gz", hash = "sha256:eac620698c59eb810e1729909be418d9373d18137cac66637141abba613b3fda", size = 23968441, upload-time = "2026-02-23T18:57:34.339Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/9f/a70972ece62ead2d81acc6223188f6d18a92f665ccce17796a0cdea4fcf5/jupyterlab-4.5.4-py3-none-any.whl", hash = "sha256:cc233f70539728534669fb0015331f2a3a87656207b3bb2d07916e9289192f12", size = 12391867, upload-time = "2026-02-11T00:26:51.23Z" }, + { url = "https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl", hash = "sha256:a35694a40a8e7f2e82f387472af24e61b22adcce87b5a8ab97a5d9c486202a6d", size = 12446824, upload-time = "2026-02-23T18:57:30.398Z" }, ] [[package]] @@ -2372,17 +2372,17 @@ wheels = [ [[package]] name = "protobuf" -version = "6.33.5" +version = "7.34.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465, upload-time = "2026-01-29T21:51:33.494Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/00/04a2ab36b70a52d0356852979e08b44edde0435f2115dc66e25f2100f3ab/protobuf-7.34.0.tar.gz", hash = "sha256:3871a3df67c710aaf7bb8d214cc997342e63ceebd940c8c7fc65c9b3d697591a", size = 454726, upload-time = "2026-02-27T00:30:25.421Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769, upload-time = "2026-01-29T21:51:21.751Z" }, - { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118, upload-time = "2026-01-29T21:51:24.022Z" }, - { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766, upload-time = "2026-01-29T21:51:25.413Z" }, - { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638, upload-time = "2026-01-29T21:51:26.423Z" }, - { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411, upload-time = "2026-01-29T21:51:27.446Z" }, - { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465, upload-time = "2026-01-29T21:51:28.925Z" }, - { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" }, + { url = "https://files.pythonhosted.org/packages/13/c4/6322ab5c8f279c4c358bc14eb8aefc0550b97222a39f04eb3c1af7a830fa/protobuf-7.34.0-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e329966799f2c271d5e05e236459fe1cbfdb8755aaa3b0914fa60947ddea408", size = 429248, upload-time = "2026-02-27T00:30:14.924Z" }, + { url = "https://files.pythonhosted.org/packages/45/99/b029bbbc61e8937545da5b79aa405ab2d9cf307a728f8c9459ad60d7a481/protobuf-7.34.0-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:9d7a5005fb96f3c1e64f397f91500b0eb371b28da81296ae73a6b08a5b76cdd6", size = 325753, upload-time = "2026-02-27T00:30:17.247Z" }, + { url = "https://files.pythonhosted.org/packages/cc/79/09f02671eb75b251c5550a1c48e7b3d4b0623efd7c95a15a50f6f9fc1e2e/protobuf-7.34.0-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:4a72a8ec94e7a9f7ef7fe818ed26d073305f347f8b3b5ba31e22f81fd85fca02", size = 340200, upload-time = "2026-02-27T00:30:18.672Z" }, + { url = "https://files.pythonhosted.org/packages/b5/57/89727baef7578897af5ed166735ceb315819f1c184da8c3441271dbcfde7/protobuf-7.34.0-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:964cf977e07f479c0697964e83deda72bcbc75c3badab506fb061b352d991b01", size = 324268, upload-time = "2026-02-27T00:30:20.088Z" }, + { url = "https://files.pythonhosted.org/packages/1f/3e/38ff2ddee5cc946f575c9d8cc822e34bde205cf61acf8099ad88ef19d7d2/protobuf-7.34.0-cp310-abi3-win32.whl", hash = "sha256:f791ec509707a1d91bd02e07df157e75e4fb9fbdad12a81b7396201ec244e2e3", size = 426628, upload-time = "2026-02-27T00:30:21.555Z" }, + { url = "https://files.pythonhosted.org/packages/cb/71/7c32eaf34a61a1bae1b62a2ac4ffe09b8d1bb0cf93ad505f42040023db89/protobuf-7.34.0-cp310-abi3-win_amd64.whl", hash = "sha256:9f9079f1dde4e32342ecbd1c118d76367090d4aaa19da78230c38101c5b3dd40", size = 437901, upload-time = "2026-02-27T00:30:22.836Z" }, + { url = "https://files.pythonhosted.org/packages/a4/e7/14dc9366696dcb53a413449881743426ed289d687bcf3d5aee4726c32ebb/protobuf-7.34.0-py3-none-any.whl", hash = "sha256:e3b914dd77fa33fa06ab2baa97937746ab25695f389869afdf03e81f34e45dc7", size = 170716, upload-time = "2026-02-27T00:30:23.994Z" }, ] [[package]] @@ -2549,6 +2549,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] +[[package]] +name = "python-discovery" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/bb/93a3e83bdf9322c7e21cafd092e56a4a17c4d8ef4277b6eb01af1a540a6f/python_discovery-1.1.0.tar.gz", hash = "sha256:447941ba1aed8cc2ab7ee3cb91be5fc137c5bdbb05b7e6ea62fbdcb66e50b268", size = 55674, upload-time = "2026-02-26T09:42:49.668Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/54/82a6e2ef37f0f23dccac604b9585bdcbd0698604feb64807dcb72853693e/python_discovery-1.1.0-py3-none-any.whl", hash = "sha256:a162893b8809727f54594a99ad2179d2ede4bf953e12d4c7abc3cc9cdbd1437b", size = 30687, upload-time = "2026-02-26T09:42:48.548Z" }, +] + [[package]] name = "python-json-logger" version = "4.0.0" @@ -3011,27 +3024,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/06/04/eab13a954e763b0606f460443fcbf6bb5a0faf06890ea3754ff16523dce5/ruff-0.15.2.tar.gz", hash = "sha256:14b965afee0969e68bb871eba625343b8673375f457af4abe98553e8bbb98342", size = 4558148, upload-time = "2026-02-19T22:32:20.271Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/70/3a4dc6d09b13cb3e695f28307e5d889b2e1a66b7af9c5e257e796695b0e6/ruff-0.15.2-py3-none-linux_armv6l.whl", hash = "sha256:120691a6fdae2f16d65435648160f5b81a9625288f75544dc40637436b5d3c0d", size = 10430565, upload-time = "2026-02-19T22:32:41.824Z" }, - { url = "https://files.pythonhosted.org/packages/71/0b/bb8457b56185ece1305c666dc895832946d24055be90692381c31d57466d/ruff-0.15.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a89056d831256099658b6bba4037ac6dd06f49d194199215befe2bb10457ea5e", size = 10820354, upload-time = "2026-02-19T22:32:07.366Z" }, - { url = "https://files.pythonhosted.org/packages/2d/c1/e0532d7f9c9e0b14c46f61b14afd563298b8b83f337b6789ddd987e46121/ruff-0.15.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e36dee3a64be0ebd23c86ffa3aa3fd3ac9a712ff295e192243f814a830b6bd87", size = 10170767, upload-time = "2026-02-19T22:32:13.188Z" }, - { url = "https://files.pythonhosted.org/packages/47/e8/da1aa341d3af017a21c7a62fb5ec31d4e7ad0a93ab80e3a508316efbcb23/ruff-0.15.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9fb47b6d9764677f8c0a193c0943ce9a05d6763523f132325af8a858eadc2b9", size = 10529591, upload-time = "2026-02-19T22:32:02.547Z" }, - { url = "https://files.pythonhosted.org/packages/93/74/184fbf38e9f3510231fbc5e437e808f0b48c42d1df9434b208821efcd8d6/ruff-0.15.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f376990f9d0d6442ea9014b19621d8f2aaf2b8e39fdbfc79220b7f0c596c9b80", size = 10260771, upload-time = "2026-02-19T22:32:36.938Z" }, - { url = "https://files.pythonhosted.org/packages/05/ac/605c20b8e059a0bc4b42360414baa4892ff278cec1c91fff4be0dceedefd/ruff-0.15.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dcc987551952d73cbf5c88d9fdee815618d497e4df86cd4c4824cc59d5dd75f", size = 11045791, upload-time = "2026-02-19T22:32:31.642Z" }, - { url = "https://files.pythonhosted.org/packages/fd/52/db6e419908f45a894924d410ac77d64bdd98ff86901d833364251bd08e22/ruff-0.15.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42a47fd785cbe8c01b9ff45031af875d101b040ad8f4de7bbb716487c74c9a77", size = 11879271, upload-time = "2026-02-19T22:32:29.305Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d8/7992b18f2008bdc9231d0f10b16df7dda964dbf639e2b8b4c1b4e91b83af/ruff-0.15.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cbe9f49354866e575b4c6943856989f966421870e85cd2ac94dccb0a9dcb2fea", size = 11303707, upload-time = "2026-02-19T22:32:22.492Z" }, - { url = "https://files.pythonhosted.org/packages/d7/02/849b46184bcfdd4b64cde61752cc9a146c54759ed036edd11857e9b8443b/ruff-0.15.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7a672c82b5f9887576087d97be5ce439f04bbaf548ee987b92d3a7dede41d3a", size = 11149151, upload-time = "2026-02-19T22:32:44.234Z" }, - { url = "https://files.pythonhosted.org/packages/70/04/f5284e388bab60d1d3b99614a5a9aeb03e0f333847e2429bebd2aaa1feec/ruff-0.15.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ecc64f46f7019e2bcc3cdc05d4a7da958b629a5ab7033195e11a438403d956", size = 11091132, upload-time = "2026-02-19T22:32:24.691Z" }, - { url = "https://files.pythonhosted.org/packages/fa/ae/88d844a21110e14d92cf73d57363fab59b727ebeabe78009b9ccb23500af/ruff-0.15.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:8dcf243b15b561c655c1ef2f2b0050e5d50db37fe90115507f6ff37d865dc8b4", size = 10504717, upload-time = "2026-02-19T22:32:26.75Z" }, - { url = "https://files.pythonhosted.org/packages/64/27/867076a6ada7f2b9c8292884ab44d08fd2ba71bd2b5364d4136f3cd537e1/ruff-0.15.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dab6941c862c05739774677c6273166d2510d254dac0695c0e3f5efa1b5585de", size = 10263122, upload-time = "2026-02-19T22:32:10.036Z" }, - { url = "https://files.pythonhosted.org/packages/e7/ef/faf9321d550f8ebf0c6373696e70d1758e20ccdc3951ad7af00c0956be7c/ruff-0.15.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b9164f57fc36058e9a6806eb92af185b0697c9fe4c7c52caa431c6554521e5c", size = 10735295, upload-time = "2026-02-19T22:32:39.227Z" }, - { url = "https://files.pythonhosted.org/packages/2f/55/e8089fec62e050ba84d71b70e7834b97709ca9b7aba10c1a0b196e493f97/ruff-0.15.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:80d24fcae24d42659db7e335b9e1531697a7102c19185b8dc4a028b952865fd8", size = 11241641, upload-time = "2026-02-19T22:32:34.617Z" }, - { url = "https://files.pythonhosted.org/packages/23/01/1c30526460f4d23222d0fabd5888868262fd0e2b71a00570ca26483cd993/ruff-0.15.2-py3-none-win32.whl", hash = "sha256:fd5ff9e5f519a7e1bd99cbe8daa324010a74f5e2ebc97c6242c08f26f3714f6f", size = 10507885, upload-time = "2026-02-19T22:32:15.635Z" }, - { url = "https://files.pythonhosted.org/packages/5c/10/3d18e3bbdf8fc50bbb4ac3cc45970aa5a9753c5cb51bf9ed9a3cd8b79fa3/ruff-0.15.2-py3-none-win_amd64.whl", hash = "sha256:d20014e3dfa400f3ff84830dfb5755ece2de45ab62ecea4af6b7262d0fb4f7c5", size = 11623725, upload-time = "2026-02-19T22:32:04.947Z" }, - { url = "https://files.pythonhosted.org/packages/6d/78/097c0798b1dab9f8affe73da9642bb4500e098cb27fd8dc9724816ac747b/ruff-0.15.2-py3-none-win_arm64.whl", hash = "sha256:cabddc5822acdc8f7b5527b36ceac55cc51eec7b1946e60181de8fe83ca8876e", size = 10941649, upload-time = "2026-02-19T22:32:18.108Z" }, +version = "0.15.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/31/d6e536cdebb6568ae75a7f00e4b4819ae0ad2640c3604c305a0428680b0c/ruff-0.15.4.tar.gz", hash = "sha256:3412195319e42d634470cc97aa9803d07e9d5c9223b99bcb1518f0c725f26ae1", size = 4569550, upload-time = "2026-02-26T20:04:14.959Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/82/c11a03cfec3a4d26a0ea1e571f0f44be5993b923f905eeddfc397c13d360/ruff-0.15.4-py3-none-linux_armv6l.whl", hash = "sha256:a1810931c41606c686bae8b5b9a8072adac2f611bb433c0ba476acba17a332e0", size = 10453333, upload-time = "2026-02-26T20:04:20.093Z" }, + { url = "https://files.pythonhosted.org/packages/ce/5d/6a1f271f6e31dffb31855996493641edc3eef8077b883eaf007a2f1c2976/ruff-0.15.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5a1632c66672b8b4d3e1d1782859e98d6e0b4e70829530666644286600a33992", size = 10853356, upload-time = "2026-02-26T20:04:05.808Z" }, + { url = "https://files.pythonhosted.org/packages/b1/d8/0fab9f8842b83b1a9c2bf81b85063f65e93fb512e60effa95b0be49bfc54/ruff-0.15.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a4386ba2cd6c0f4ff75252845906acc7c7c8e1ac567b7bc3d373686ac8c222ba", size = 10187434, upload-time = "2026-02-26T20:03:54.656Z" }, + { url = "https://files.pythonhosted.org/packages/85/cc/cc220fd9394eff5db8d94dec199eec56dd6c9f3651d8869d024867a91030/ruff-0.15.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2496488bdfd3732747558b6f95ae427ff066d1fcd054daf75f5a50674411e75", size = 10535456, upload-time = "2026-02-26T20:03:52.738Z" }, + { url = "https://files.pythonhosted.org/packages/fa/0f/bced38fa5cf24373ec767713c8e4cadc90247f3863605fb030e597878661/ruff-0.15.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3f1c4893841ff2d54cbda1b2860fa3260173df5ddd7b95d370186f8a5e66a4ac", size = 10287772, upload-time = "2026-02-26T20:04:08.138Z" }, + { url = "https://files.pythonhosted.org/packages/2b/90/58a1802d84fed15f8f281925b21ab3cecd813bde52a8ca033a4de8ab0e7a/ruff-0.15.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:820b8766bd65503b6c30aaa6331e8ef3a6e564f7999c844e9a547c40179e440a", size = 11049051, upload-time = "2026-02-26T20:04:03.53Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ac/b7ad36703c35f3866584564dc15f12f91cb1a26a897dc2fd13d7cb3ae1af/ruff-0.15.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9fb74bab47139c1751f900f857fa503987253c3ef89129b24ed375e72873e85", size = 11890494, upload-time = "2026-02-26T20:04:10.497Z" }, + { url = "https://files.pythonhosted.org/packages/93/3d/3eb2f47a39a8b0da99faf9c54d3eb24720add1e886a5309d4d1be73a6380/ruff-0.15.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f80c98765949c518142b3a50a5db89343aa90f2c2bf7799de9986498ae6176db", size = 11326221, upload-time = "2026-02-26T20:04:12.84Z" }, + { url = "https://files.pythonhosted.org/packages/ff/90/bf134f4c1e5243e62690e09d63c55df948a74084c8ac3e48a88468314da6/ruff-0.15.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:451a2e224151729b3b6c9ffb36aed9091b2996fe4bdbd11f47e27d8f2e8888ec", size = 11168459, upload-time = "2026-02-26T20:04:00.969Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e5/a64d27688789b06b5d55162aafc32059bb8c989c61a5139a36e1368285eb/ruff-0.15.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a8f157f2e583c513c4f5f896163a93198297371f34c04220daf40d133fdd4f7f", size = 11104366, upload-time = "2026-02-26T20:03:48.099Z" }, + { url = "https://files.pythonhosted.org/packages/f1/f6/32d1dcb66a2559763fc3027bdd65836cad9eb09d90f2ed6a63d8e9252b02/ruff-0.15.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:917cc68503357021f541e69b35361c99387cdbbf99bd0ea4aa6f28ca99ff5338", size = 10510887, upload-time = "2026-02-26T20:03:45.771Z" }, + { url = "https://files.pythonhosted.org/packages/ff/92/22d1ced50971c5b6433aed166fcef8c9343f567a94cf2b9d9089f6aa80fe/ruff-0.15.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e9737c8161da79fd7cfec19f1e35620375bd8b2a50c3e77fa3d2c16f574105cc", size = 10285939, upload-time = "2026-02-26T20:04:22.42Z" }, + { url = "https://files.pythonhosted.org/packages/e6/f4/7c20aec3143837641a02509a4668fb146a642fd1211846634edc17eb5563/ruff-0.15.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:291258c917539e18f6ba40482fe31d6f5ac023994ee11d7bdafd716f2aab8a68", size = 10765471, upload-time = "2026-02-26T20:03:58.924Z" }, + { url = "https://files.pythonhosted.org/packages/d0/09/6d2f7586f09a16120aebdff8f64d962d7c4348313c77ebb29c566cefc357/ruff-0.15.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3f83c45911da6f2cd5936c436cf86b9f09f09165f033a99dcf7477e34041cbc3", size = 11263382, upload-time = "2026-02-26T20:04:24.424Z" }, + { url = "https://files.pythonhosted.org/packages/1b/fa/2ef715a1cd329ef47c1a050e10dee91a9054b7ce2fcfdd6a06d139afb7ec/ruff-0.15.4-py3-none-win32.whl", hash = "sha256:65594a2d557d4ee9f02834fcdf0a28daa8b3b9f6cb2cb93846025a36db47ef22", size = 10506664, upload-time = "2026-02-26T20:03:50.56Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a8/c688ef7e29983976820d18710f955751d9f4d4eb69df658af3d006e2ba3e/ruff-0.15.4-py3-none-win_amd64.whl", hash = "sha256:04196ad44f0df220c2ece5b0e959c2f37c777375ec744397d21d15b50a75264f", size = 11651048, upload-time = "2026-02-26T20:04:17.191Z" }, + { url = "https://files.pythonhosted.org/packages/3e/0a/9e1be9035b37448ce2e68c978f0591da94389ade5a5abafa4cf99985d1b2/ruff-0.15.4-py3-none-win_arm64.whl", hash = "sha256:60d5177e8cfc70e51b9c5fad936c634872a74209f934c1e79107d11787ad5453", size = 10966776, upload-time = "2026-02-26T20:03:56.908Z" }, ] [[package]] @@ -3209,51 +3222,55 @@ wheels = [ [[package]] name = "sqlalchemy" -version = "2.0.46" +version = "2.0.47" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/aa/9ce0f3e7a9829ead5c8ce549392f33a12c4555a6c0609bb27d882e9c7ddf/sqlalchemy-2.0.46.tar.gz", hash = "sha256:cf36851ee7219c170bb0793dbc3da3e80c582e04a5437bc601bfe8c85c9216d7", size = 9865393, upload-time = "2026-01-21T18:03:45.119Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/69/ac/b42ad16800d0885105b59380ad69aad0cce5a65276e269ce2729a2343b6a/sqlalchemy-2.0.46-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:261c4b1f101b4a411154f1da2b76497d73abbfc42740029205d4d01fa1052684", size = 2154851, upload-time = "2026-01-21T18:27:30.54Z" }, - { url = "https://files.pythonhosted.org/packages/a0/60/d8710068cb79f64d002ebed62a7263c00c8fd95f4ebd4b5be8f7ca93f2bc/sqlalchemy-2.0.46-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:181903fe8c1b9082995325f1b2e84ac078b1189e2819380c2303a5f90e114a62", size = 3311241, upload-time = "2026-01-21T18:32:33.45Z" }, - { url = "https://files.pythonhosted.org/packages/2b/0f/20c71487c7219ab3aa7421c7c62d93824c97c1460f2e8bb72404b0192d13/sqlalchemy-2.0.46-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:590be24e20e2424a4c3c1b0835e9405fa3d0af5823a1a9fc02e5dff56471515f", size = 3310741, upload-time = "2026-01-21T18:44:57.887Z" }, - { url = "https://files.pythonhosted.org/packages/65/80/d26d00b3b249ae000eee4db206fcfc564bf6ca5030e4747adf451f4b5108/sqlalchemy-2.0.46-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7568fe771f974abadce52669ef3a03150ff03186d8eb82613bc8adc435a03f01", size = 3263116, upload-time = "2026-01-21T18:32:35.044Z" }, - { url = "https://files.pythonhosted.org/packages/da/ee/74dda7506640923821340541e8e45bd3edd8df78664f1f2e0aae8077192b/sqlalchemy-2.0.46-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf7e1e78af38047e08836d33502c7a278915698b7c2145d045f780201679999", size = 3285327, upload-time = "2026-01-21T18:44:59.254Z" }, - { url = "https://files.pythonhosted.org/packages/9f/25/6dcf8abafff1389a21c7185364de145107b7394ecdcb05233815b236330d/sqlalchemy-2.0.46-cp311-cp311-win32.whl", hash = "sha256:9d80ea2ac519c364a7286e8d765d6cd08648f5b21ca855a8017d9871f075542d", size = 2114564, upload-time = "2026-01-21T18:33:15.85Z" }, - { url = "https://files.pythonhosted.org/packages/93/5f/e081490f8523adc0088f777e4ebad3cac21e498ec8a3d4067074e21447a1/sqlalchemy-2.0.46-cp311-cp311-win_amd64.whl", hash = "sha256:585af6afe518732d9ccd3aea33af2edaae4a7aa881af5d8f6f4fe3a368699597", size = 2139233, upload-time = "2026-01-21T18:33:17.528Z" }, - { url = "https://files.pythonhosted.org/packages/b6/35/d16bfa235c8b7caba3730bba43e20b1e376d2224f407c178fbf59559f23e/sqlalchemy-2.0.46-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3a9a72b0da8387f15d5810f1facca8f879de9b85af8c645138cba61ea147968c", size = 2153405, upload-time = "2026-01-21T19:05:54.143Z" }, - { url = "https://files.pythonhosted.org/packages/06/6c/3192e24486749862f495ddc6584ed730c0c994a67550ec395d872a2ad650/sqlalchemy-2.0.46-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2347c3f0efc4de367ba00218e0ae5c4ba2306e47216ef80d6e31761ac97cb0b9", size = 3334702, upload-time = "2026-01-21T18:46:45.384Z" }, - { url = "https://files.pythonhosted.org/packages/ea/a2/b9f33c8d68a3747d972a0bb758c6b63691f8fb8a49014bc3379ba15d4274/sqlalchemy-2.0.46-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9094c8b3197db12aa6f05c51c05daaad0a92b8c9af5388569847b03b1007fb1b", size = 3347664, upload-time = "2026-01-21T18:40:09.979Z" }, - { url = "https://files.pythonhosted.org/packages/aa/d2/3e59e2a91eaec9db7e8dc6b37b91489b5caeb054f670f32c95bcba98940f/sqlalchemy-2.0.46-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37fee2164cf21417478b6a906adc1a91d69ae9aba8f9533e67ce882f4bb1de53", size = 3277372, upload-time = "2026-01-21T18:46:47.168Z" }, - { url = "https://files.pythonhosted.org/packages/dd/dd/67bc2e368b524e2192c3927b423798deda72c003e73a1e94c21e74b20a85/sqlalchemy-2.0.46-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b1e14b2f6965a685c7128bd315e27387205429c2e339eeec55cb75ca4ab0ea2e", size = 3312425, upload-time = "2026-01-21T18:40:11.548Z" }, - { url = "https://files.pythonhosted.org/packages/43/82/0ecd68e172bfe62247e96cb47867c2d68752566811a4e8c9d8f6e7c38a65/sqlalchemy-2.0.46-cp312-cp312-win32.whl", hash = "sha256:412f26bb4ba942d52016edc8d12fb15d91d3cd46b0047ba46e424213ad407bcb", size = 2113155, upload-time = "2026-01-21T18:42:49.748Z" }, - { url = "https://files.pythonhosted.org/packages/bc/2a/2821a45742073fc0331dc132552b30de68ba9563230853437cac54b2b53e/sqlalchemy-2.0.46-cp312-cp312-win_amd64.whl", hash = "sha256:ea3cd46b6713a10216323cda3333514944e510aa691c945334713fca6b5279ff", size = 2140078, upload-time = "2026-01-21T18:42:51.197Z" }, - { url = "https://files.pythonhosted.org/packages/b3/4b/fa7838fe20bb752810feed60e45625a9a8b0102c0c09971e2d1d95362992/sqlalchemy-2.0.46-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:93a12da97cca70cea10d4b4fc602589c4511f96c1f8f6c11817620c021d21d00", size = 2150268, upload-time = "2026-01-21T19:05:56.621Z" }, - { url = "https://files.pythonhosted.org/packages/46/c1/b34dccd712e8ea846edf396e00973dda82d598cb93762e55e43e6835eba9/sqlalchemy-2.0.46-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af865c18752d416798dae13f83f38927c52f085c52e2f32b8ab0fef46fdd02c2", size = 3276511, upload-time = "2026-01-21T18:46:49.022Z" }, - { url = "https://files.pythonhosted.org/packages/96/48/a04d9c94753e5d5d096c628c82a98c4793b9c08ca0e7155c3eb7d7db9f24/sqlalchemy-2.0.46-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8d679b5f318423eacb61f933a9a0f75535bfca7056daeadbf6bd5bcee6183aee", size = 3292881, upload-time = "2026-01-21T18:40:13.089Z" }, - { url = "https://files.pythonhosted.org/packages/be/f4/06eda6e91476f90a7d8058f74311cb65a2fb68d988171aced81707189131/sqlalchemy-2.0.46-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64901e08c33462acc9ec3bad27fc7a5c2b6491665f2aa57564e57a4f5d7c52ad", size = 3224559, upload-time = "2026-01-21T18:46:50.974Z" }, - { url = "https://files.pythonhosted.org/packages/ab/a2/d2af04095412ca6345ac22b33b89fe8d6f32a481e613ffcb2377d931d8d0/sqlalchemy-2.0.46-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e8ac45e8f4eaac0f9f8043ea0e224158855c6a4329fd4ee37c45c61e3beb518e", size = 3262728, upload-time = "2026-01-21T18:40:14.883Z" }, - { url = "https://files.pythonhosted.org/packages/31/48/1980c7caa5978a3b8225b4d230e69a2a6538a3562b8b31cea679b6933c83/sqlalchemy-2.0.46-cp313-cp313-win32.whl", hash = "sha256:8d3b44b3d0ab2f1319d71d9863d76eeb46766f8cf9e921ac293511804d39813f", size = 2111295, upload-time = "2026-01-21T18:42:52.366Z" }, - { url = "https://files.pythonhosted.org/packages/2d/54/f8d65bbde3d877617c4720f3c9f60e99bb7266df0d5d78b6e25e7c149f35/sqlalchemy-2.0.46-cp313-cp313-win_amd64.whl", hash = "sha256:77f8071d8fbcbb2dd11b7fd40dedd04e8ebe2eb80497916efedba844298065ef", size = 2137076, upload-time = "2026-01-21T18:42:53.924Z" }, - { url = "https://files.pythonhosted.org/packages/56/ba/9be4f97c7eb2b9d5544f2624adfc2853e796ed51d2bb8aec90bc94b7137e/sqlalchemy-2.0.46-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1e8cc6cc01da346dc92d9509a63033b9b1bda4fed7a7a7807ed385c7dccdc10", size = 3556533, upload-time = "2026-01-21T18:33:06.636Z" }, - { url = "https://files.pythonhosted.org/packages/20/a6/b1fc6634564dbb4415b7ed6419cdfeaadefd2c39cdab1e3aa07a5f2474c2/sqlalchemy-2.0.46-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:96c7cca1a4babaaf3bfff3e4e606e38578856917e52f0384635a95b226c87764", size = 3523208, upload-time = "2026-01-21T18:45:08.436Z" }, - { url = "https://files.pythonhosted.org/packages/a1/d8/41e0bdfc0f930ff236f86fccd12962d8fa03713f17ed57332d38af6a3782/sqlalchemy-2.0.46-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b2a9f9aee38039cf4755891a1e50e1effcc42ea6ba053743f452c372c3152b1b", size = 3464292, upload-time = "2026-01-21T18:33:08.208Z" }, - { url = "https://files.pythonhosted.org/packages/f0/8b/9dcbec62d95bea85f5ecad9b8d65b78cc30fb0ffceeb3597961f3712549b/sqlalchemy-2.0.46-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:db23b1bf8cfe1f7fda19018e7207b20cdb5168f83c437ff7e95d19e39289c447", size = 3473497, upload-time = "2026-01-21T18:45:10.552Z" }, - { url = "https://files.pythonhosted.org/packages/e9/f8/5ecdfc73383ec496de038ed1614de9e740a82db9ad67e6e4514ebc0708a3/sqlalchemy-2.0.46-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:56bdd261bfd0895452006d5316cbf35739c53b9bb71a170a331fa0ea560b2ada", size = 2152079, upload-time = "2026-01-21T19:05:58.477Z" }, - { url = "https://files.pythonhosted.org/packages/e5/bf/eba3036be7663ce4d9c050bc3d63794dc29fbe01691f2bf5ccb64e048d20/sqlalchemy-2.0.46-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33e462154edb9493f6c3ad2125931e273bbd0be8ae53f3ecd1c161ea9a1dd366", size = 3272216, upload-time = "2026-01-21T18:46:52.634Z" }, - { url = "https://files.pythonhosted.org/packages/05/45/1256fb597bb83b58a01ddb600c59fe6fdf0e5afe333f0456ed75c0f8d7bd/sqlalchemy-2.0.46-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bcdce05f056622a632f1d44bb47dbdb677f58cad393612280406ce37530eb6d", size = 3277208, upload-time = "2026-01-21T18:40:16.38Z" }, - { url = "https://files.pythonhosted.org/packages/d9/a0/2053b39e4e63b5d7ceb3372cface0859a067c1ddbd575ea7e9985716f771/sqlalchemy-2.0.46-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e84b09a9b0f19accedcbeff5c2caf36e0dd537341a33aad8d680336152dc34e", size = 3221994, upload-time = "2026-01-21T18:46:54.622Z" }, - { url = "https://files.pythonhosted.org/packages/1e/87/97713497d9502553c68f105a1cb62786ba1ee91dea3852ae4067ed956a50/sqlalchemy-2.0.46-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4f52f7291a92381e9b4de9050b0a65ce5d6a763333406861e33906b8aa4906bf", size = 3243990, upload-time = "2026-01-21T18:40:18.253Z" }, - { url = "https://files.pythonhosted.org/packages/a8/87/5d1b23548f420ff823c236f8bea36b1a997250fd2f892e44a3838ca424f4/sqlalchemy-2.0.46-cp314-cp314-win32.whl", hash = "sha256:70ed2830b169a9960193f4d4322d22be5c0925357d82cbf485b3369893350908", size = 2114215, upload-time = "2026-01-21T18:42:55.232Z" }, - { url = "https://files.pythonhosted.org/packages/3a/20/555f39cbcf0c10cf452988b6a93c2a12495035f68b3dbd1a408531049d31/sqlalchemy-2.0.46-cp314-cp314-win_amd64.whl", hash = "sha256:3c32e993bc57be6d177f7d5d31edb93f30726d798ad86ff9066d75d9bf2e0b6b", size = 2139867, upload-time = "2026-01-21T18:42:56.474Z" }, - { url = "https://files.pythonhosted.org/packages/3e/f0/f96c8057c982d9d8a7a68f45d69c674bc6f78cad401099692fe16521640a/sqlalchemy-2.0.46-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4dafb537740eef640c4d6a7c254611dca2df87eaf6d14d6a5fca9d1f4c3fc0fa", size = 3561202, upload-time = "2026-01-21T18:33:10.337Z" }, - { url = "https://files.pythonhosted.org/packages/d7/53/3b37dda0a5b137f21ef608d8dfc77b08477bab0fe2ac9d3e0a66eaeab6fc/sqlalchemy-2.0.46-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42a1643dc5427b69aca967dae540a90b0fbf57eaf248f13a90ea5930e0966863", size = 3526296, upload-time = "2026-01-21T18:45:12.657Z" }, - { url = "https://files.pythonhosted.org/packages/33/75/f28622ba6dde79cd545055ea7bd4062dc934e0621f7b3be2891f8563f8de/sqlalchemy-2.0.46-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ff33c6e6ad006bbc0f34f5faf941cfc62c45841c64c0a058ac38c799f15b5ede", size = 3470008, upload-time = "2026-01-21T18:33:11.725Z" }, - { url = "https://files.pythonhosted.org/packages/a9/42/4afecbbc38d5e99b18acef446453c76eec6fbd03db0a457a12a056836e22/sqlalchemy-2.0.46-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:82ec52100ec1e6ec671563bbd02d7c7c8d0b9e71a0723c72f22ecf52d1755330", size = 3476137, upload-time = "2026-01-21T18:45:15.001Z" }, - { url = "https://files.pythonhosted.org/packages/fc/a1/9c4efa03300926601c19c18582531b45aededfb961ab3c3585f1e24f120b/sqlalchemy-2.0.46-py3-none-any.whl", hash = "sha256:f9c11766e7e7c0a2767dda5acb006a118640c9fc0a4104214b96269bfb78399e", size = 1937882, upload-time = "2026-01-21T18:22:10.456Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/cd/4b/1e00561093fe2cd8eef09d406da003c8a118ff02d6548498c1ae677d68d9/sqlalchemy-2.0.47.tar.gz", hash = "sha256:e3e7feb57b267fe897e492b9721ae46d5c7de6f9e8dee58aacf105dc4e154f3d", size = 9886323, upload-time = "2026-02-24T16:34:27.947Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/13/886338d3e8ab5ddcfe84d54302c749b1793e16c4bba63d7004e3f7baa8ec/sqlalchemy-2.0.47-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a1dbf0913879c443617d6b64403cf2801c941651db8c60e96d204ed9388d6b0", size = 2157124, upload-time = "2026-02-24T16:43:54.706Z" }, + { url = "https://files.pythonhosted.org/packages/b6/bb/a897f6a66c9986aa9f27f5cf8550637d8a5ea368fd7fb42f6dac3105b4dc/sqlalchemy-2.0.47-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:775effbb97ea3b00c4dd3aeaf3ba8acba6e3e2b4b41d17d67a27e696843dbc95", size = 3313513, upload-time = "2026-02-24T17:29:00.527Z" }, + { url = "https://files.pythonhosted.org/packages/59/fb/69bfae022b681507565ab0d34f0c80aa1e9f954a5a7cbfb0ed054966ac8d/sqlalchemy-2.0.47-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56cc834a3ffac34270cc2a41875e0f40e97aa651f4f3ca1cfbbf421c044cb62b", size = 3313014, upload-time = "2026-02-24T17:27:11.679Z" }, + { url = "https://files.pythonhosted.org/packages/04/f3/0eba329f7c182d53205a228c4fd24651b95489b431ea2bd830887b4c13c4/sqlalchemy-2.0.47-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:49b5e0c7244262f39e767c018e4fdb5e5dbc23cd54c5ddac8eea8f0ba32ef890", size = 3265389, upload-time = "2026-02-24T17:29:02.497Z" }, + { url = "https://files.pythonhosted.org/packages/5c/06/654edc084b3b46ac79e04200d7c46467ae80c759c4ee41c897f9272b036f/sqlalchemy-2.0.47-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:15cd822a3f1f6f77b5b841a30c1a07a07f7dee3385f17e638e1722de9ab683be", size = 3287604, upload-time = "2026-02-24T17:27:13.295Z" }, + { url = "https://files.pythonhosted.org/packages/78/33/c18c8f63b61981219d3aa12321bb7ccee605034d195e868ed94f9727b27c/sqlalchemy-2.0.47-cp311-cp311-win32.whl", hash = "sha256:9847a19548cd283a65e1ce0afd54016598d55ff72682d6fd3e493af6fc044064", size = 2116916, upload-time = "2026-02-24T17:14:37.392Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c6/a59e3f9796fff844e16afbd821db9abfd6e12698db9441a231a96193a100/sqlalchemy-2.0.47-cp311-cp311-win_amd64.whl", hash = "sha256:722abf1c82aeca46a1a0803711244a48a298279eeaec9e02f7bfee9e064182e5", size = 2141587, upload-time = "2026-02-24T17:14:39.746Z" }, + { url = "https://files.pythonhosted.org/packages/80/88/74eb470223ff88ea6572a132c0b8de8c1d8ed7b843d3b44a8a3c77f31d39/sqlalchemy-2.0.47-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4fa91b19d6b9821c04cc8f7aa2476429cc8887b9687c762815aa629f5c0edec1", size = 2155687, upload-time = "2026-02-24T17:05:46.451Z" }, + { url = "https://files.pythonhosted.org/packages/ef/ba/1447d3d558971b036cb93b557595cb5dcdfe728f1c7ac4dec16505ef5756/sqlalchemy-2.0.47-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c5bbbd14eff577c8c79cbfe39a0771eecd20f430f3678533476f0087138f356", size = 3336978, upload-time = "2026-02-24T17:18:04.597Z" }, + { url = "https://files.pythonhosted.org/packages/8a/07/b47472d2ffd0776826f17ccf0b4d01b224c99fbd1904aeb103dffbb4b1cc/sqlalchemy-2.0.47-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5a6c555da8d4280a3c4c78c5b7a3f990cee2b2884e5f934f87a226191682ff7", size = 3349939, upload-time = "2026-02-24T17:27:18.937Z" }, + { url = "https://files.pythonhosted.org/packages/bb/c6/95fa32b79b57769da3e16f054cf658d90940317b5ca0ec20eac84aa19c4f/sqlalchemy-2.0.47-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ed48a1701d24dff3bb49a5bce94d6bc84cbe33d98af2aa2d3cdcce3dea1709ec", size = 3279648, upload-time = "2026-02-24T17:18:07.038Z" }, + { url = "https://files.pythonhosted.org/packages/bb/c8/3d07e7c73928dc59a0bed40961ca4e313e797bce650b088e8d5fdd3ad939/sqlalchemy-2.0.47-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4f3178c920ad98158f0b6309382194df04b14808fa6052ae07099fdde29d5602", size = 3314695, upload-time = "2026-02-24T17:27:20.93Z" }, + { url = "https://files.pythonhosted.org/packages/6b/d2/ed32b1611c1e19fdb028eee1adc5a9aa138c2952d09ae11f1670170f80ae/sqlalchemy-2.0.47-cp312-cp312-win32.whl", hash = "sha256:b9c11ac9934dd59ece9619fe42780a08abe2faab7b0543bb00d5eabea4f421b9", size = 2115502, upload-time = "2026-02-24T17:22:52.546Z" }, + { url = "https://files.pythonhosted.org/packages/fd/52/9de590356a4dd8e9ef5a881dbba64b2bbc4cbc71bf02bc68e775fb9b1899/sqlalchemy-2.0.47-cp312-cp312-win_amd64.whl", hash = "sha256:db43b72cf8274a99e089755c9c1e0b947159b71adbc2c83c3de2e38d5d607acb", size = 2142435, upload-time = "2026-02-24T17:22:54.268Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e5/0af64ce7d8f60ec5328c10084e2f449e7912a9b8bdbefdcfb44454a25f49/sqlalchemy-2.0.47-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:456a135b790da5d3c6b53d0ef71ac7b7d280b7f41eb0c438986352bf03ca7143", size = 2152551, upload-time = "2026-02-24T17:05:47.675Z" }, + { url = "https://files.pythonhosted.org/packages/63/79/746b8d15f6940e2ac469ce22d7aa5b1124b1ab820bad9b046eb3000c88a6/sqlalchemy-2.0.47-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09a2f7698e44b3135433387da5d8846cf7cc7c10e5425af7c05fee609df978b6", size = 3278782, upload-time = "2026-02-24T17:18:10.012Z" }, + { url = "https://files.pythonhosted.org/packages/91/b1/bd793ddb34345d1ed43b13ab2d88c95d7d4eb2e28f5b5a99128b9cc2bca2/sqlalchemy-2.0.47-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0bbc72e6a177c78d724f9106aaddc0d26a2ada89c6332b5935414eccf04cbd5", size = 3295155, upload-time = "2026-02-24T17:27:22.827Z" }, + { url = "https://files.pythonhosted.org/packages/97/84/7213def33f94e5ca6f5718d259bc9f29de0363134648425aa218d4356b23/sqlalchemy-2.0.47-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:75460456b043b78b6006e41bdf5b86747ee42eafaf7fffa3b24a6e9a456a2092", size = 3226834, upload-time = "2026-02-24T17:18:11.465Z" }, + { url = "https://files.pythonhosted.org/packages/ef/06/456810204f4dc29b5f025b1b0a03b4bd6b600ebf3c1040aebd90a257fa33/sqlalchemy-2.0.47-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5d9adaa616c3bc7d80f9ded57cd84b51d6617cad6a5456621d858c9f23aaee01", size = 3265001, upload-time = "2026-02-24T17:27:24.813Z" }, + { url = "https://files.pythonhosted.org/packages/fb/20/df3920a4b2217dbd7390a5bd277c1902e0393f42baaf49f49b3c935e7328/sqlalchemy-2.0.47-cp313-cp313-win32.whl", hash = "sha256:76e09f974382a496a5ed985db9343628b1cb1ac911f27342e4cc46a8bac10476", size = 2113647, upload-time = "2026-02-24T17:22:55.747Z" }, + { url = "https://files.pythonhosted.org/packages/46/06/7873ddf69918efbfabd7211829f4bd8019739d0a719253112d305d3ba51d/sqlalchemy-2.0.47-cp313-cp313-win_amd64.whl", hash = "sha256:0664089b0bf6724a0bfb49a0cf4d4da24868a0a5c8e937cd7db356d5dcdf2c66", size = 2139425, upload-time = "2026-02-24T17:22:57.033Z" }, + { url = "https://files.pythonhosted.org/packages/54/fa/61ad9731370c90ac7ea5bf8f5eaa12c48bb4beec41c0fa0360becf4ac10d/sqlalchemy-2.0.47-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed0c967c701ae13da98eb220f9ddab3044ab63504c1ba24ad6a59b26826ad003", size = 3558809, upload-time = "2026-02-24T17:12:15.232Z" }, + { url = "https://files.pythonhosted.org/packages/33/d5/221fac96f0529391fe374875633804c866f2b21a9c6d3a6ca57d9c12cfd7/sqlalchemy-2.0.47-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3537943a61fd25b241e976426a0c6814434b93cf9b09d39e8e78f3c9eb9a487", size = 3525480, upload-time = "2026-02-24T17:27:59.602Z" }, + { url = "https://files.pythonhosted.org/packages/ec/55/8247d53998c3673e4a8d1958eba75c6f5cc3b39082029d400bb1f2a911ae/sqlalchemy-2.0.47-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:57f7e336a64a0dba686c66392d46b9bc7af2c57d55ce6dc1697b4ef32b043ceb", size = 3466569, upload-time = "2026-02-24T17:12:16.94Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b5/c1f0eea1bac6790845f71420a7fe2f2a0566203aa57543117d4af3b77d1c/sqlalchemy-2.0.47-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dff735a621858680217cb5142b779bad40ef7322ddbb7c12062190db6879772e", size = 3475770, upload-time = "2026-02-24T17:28:02.034Z" }, + { url = "https://files.pythonhosted.org/packages/c5/ed/2f43f92474ea0c43c204657dc47d9d002cd738b96ca2af8e6d29a9b5e42d/sqlalchemy-2.0.47-cp313-cp313t-win32.whl", hash = "sha256:3893dc096bb3cca9608ea3487372ffcea3ae9b162f40e4d3c51dd49db1d1b2dc", size = 2141300, upload-time = "2026-02-24T17:14:37.024Z" }, + { url = "https://files.pythonhosted.org/packages/cc/a9/8b73f9f1695b6e92f7aaf1711135a1e3bbeb78bca9eded35cb79180d3c6d/sqlalchemy-2.0.47-cp313-cp313t-win_amd64.whl", hash = "sha256:b5103427466f4b3e61f04833ae01f9a914b1280a2a8bcde3a9d7ab11f3755b42", size = 2173053, upload-time = "2026-02-24T17:14:38.688Z" }, + { url = "https://files.pythonhosted.org/packages/c1/30/98243209aae58ed80e090ea988d5182244ca7ab3ff59e6d850c3dfc7651e/sqlalchemy-2.0.47-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b03010a5a5dfe71676bc83f2473ebe082478e32d77e6f082c8fe15a31c3b42a6", size = 2154355, upload-time = "2026-02-24T17:05:48.959Z" }, + { url = "https://files.pythonhosted.org/packages/ab/62/12ca6ea92055fe486d6558a2a4efe93e194ff597463849c01f88e5adb99d/sqlalchemy-2.0.47-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8e3371aa9024520883a415a09cc20c33cfd3eeccf9e0f4f4c367f940b9cbd44", size = 3274486, upload-time = "2026-02-24T17:18:13.659Z" }, + { url = "https://files.pythonhosted.org/packages/97/88/7dfbdeaa8d42b1584e65d6cc713e9d33b6fa563e0d546d5cb87e545bb0e5/sqlalchemy-2.0.47-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9449f747e50d518c6e1b40cc379e48bfc796453c47b15e627ea901c201e48a6", size = 3279481, upload-time = "2026-02-24T17:27:26.491Z" }, + { url = "https://files.pythonhosted.org/packages/d0/b7/75e1c1970616a9dd64a8a6fd788248da2ddaf81c95f4875f2a1e8aee4128/sqlalchemy-2.0.47-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:21410f60d5cac1d6bfe360e05bd91b179be4fa0aa6eea6be46054971d277608f", size = 3224269, upload-time = "2026-02-24T17:18:15.078Z" }, + { url = "https://files.pythonhosted.org/packages/31/ac/eec1a13b891df9a8bc203334caf6e6aac60b02f61b018ef3b4124b8c4120/sqlalchemy-2.0.47-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:819841dd5bb4324c284c09e2874cf96fe6338bfb57a64548d9b81a4e39c9871f", size = 3246262, upload-time = "2026-02-24T17:27:27.986Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b0/661b0245b06421058610da39f8ceb34abcc90b49f90f256380968d761dbe/sqlalchemy-2.0.47-cp314-cp314-win32.whl", hash = "sha256:e255ee44821a7ef45649c43064cf94e74f81f61b4df70547304b97a351e9b7db", size = 2116528, upload-time = "2026-02-24T17:22:59.363Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ef/1035a90d899e61810791c052004958be622a2cf3eb3df71c3fe20778c5d0/sqlalchemy-2.0.47-cp314-cp314-win_amd64.whl", hash = "sha256:209467ff73ea1518fe1a5aaed9ba75bb9e33b2666e2553af9ccd13387bf192cb", size = 2142181, upload-time = "2026-02-24T17:23:01.001Z" }, + { url = "https://files.pythonhosted.org/packages/76/bb/17a1dd09cbba91258218ceb582225f14b5364d2683f9f5a274f72f2d764f/sqlalchemy-2.0.47-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e78fd9186946afaa287f8a1fe147ead06e5d566b08c0afcb601226e9c7322a64", size = 3563477, upload-time = "2026-02-24T17:12:18.46Z" }, + { url = "https://files.pythonhosted.org/packages/66/8f/1a03d24c40cc321ef2f2231f05420d140bb06a84f7047eaa7eaa21d230ba/sqlalchemy-2.0.47-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5740e2f31b5987ed9619d6912ae5b750c03637f2078850da3002934c9532f172", size = 3528568, upload-time = "2026-02-24T17:28:03.732Z" }, + { url = "https://files.pythonhosted.org/packages/fd/53/d56a213055d6b038a5384f0db5ece7343334aca230ff3f0fa1561106f22c/sqlalchemy-2.0.47-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb9ac00d03de93acb210e8ec7243fefe3e012515bf5fd2f0898c8dff38bc77a4", size = 3472284, upload-time = "2026-02-24T17:12:20.319Z" }, + { url = "https://files.pythonhosted.org/packages/ff/19/c235d81b9cfdd6130bf63143b7bade0dc4afa46c4b634d5d6b2a96bea233/sqlalchemy-2.0.47-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c72a0b9eb2672d70d112cb149fbaf172d466bc691014c496aaac594f1988e706", size = 3478410, upload-time = "2026-02-24T17:28:05.892Z" }, + { url = "https://files.pythonhosted.org/packages/0e/db/cafdeca5ecdaa3bb0811ba5449501da677ce0d83be8d05c5822da72d2e86/sqlalchemy-2.0.47-cp314-cp314t-win32.whl", hash = "sha256:c200db1128d72a71dc3c31c24b42eb9fd85b2b3e5a3c9ba1e751c11ac31250ff", size = 2147164, upload-time = "2026-02-24T17:14:40.783Z" }, + { url = "https://files.pythonhosted.org/packages/fc/5e/ff41a010e9e0f76418b02ad352060a4341bb15f0af66cedc924ab376c7c6/sqlalchemy-2.0.47-cp314-cp314t-win_amd64.whl", hash = "sha256:669837759b84e575407355dcff912835892058aea9b80bd1cb76d6a151cf37f7", size = 2182154, upload-time = "2026-02-24T17:14:43.205Z" }, + { url = "https://files.pythonhosted.org/packages/15/9f/7c378406b592fcf1fc157248607b495a40e3202ba4a6f1372a2ba6447717/sqlalchemy-2.0.47-py3-none-any.whl", hash = "sha256:e2647043599297a1ef10e720cf310846b7f31b6c841fee093d2b09d81215eb93", size = 1940159, upload-time = "2026-02-24T17:15:07.158Z" }, ] [[package]] @@ -3328,14 +3345,14 @@ wheels = [ [[package]] name = "tifffile" -version = "2026.2.20" +version = "2026.2.24" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/90/80/0ddd8dc74c22e1e5efcfb152303b025f8f4a5010ae9936f1e57f7d7f9256/tifffile-2026.2.20.tar.gz", hash = "sha256:b98a7fc6ea4fa0e9919734857eebc6e2cb2c3a95468a930d4a948a9a49646ab7", size = 377196, upload-time = "2026-02-20T20:09:34.608Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/1c/19fc653e2b05ec0defae511b03b330ca60c95f2c47fcaaf21c52c6e84aa8/tifffile-2026.2.24.tar.gz", hash = "sha256:d73cfa6d7a8f5775a1e3c9f3bfca77c992946639fb41a5bbe888878cb6964dc6", size = 387373, upload-time = "2026-02-24T23:59:11.706Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/86/07/0cd5cad2fdb7d32515561bc26da041654f3b3c0abc299f4730f30b89271d/tifffile-2026.2.20-py3-none-any.whl", hash = "sha256:a83e0e991647e39d5912369998ef02d858f89effe30064403a1a123b5daef8fb", size = 234528, upload-time = "2026-02-20T20:09:33.278Z" }, + { url = "https://files.pythonhosted.org/packages/ee/fe/80250dc06cd4a3a5afe7059875a8d53e97a78528c5dd9ea8c3f981fb897a/tifffile-2026.2.24-py3-none-any.whl", hash = "sha256:38ef6258c2bd8dd3551c7480c6d75a36c041616262e6cd55a50dd16046b71863", size = 243223, upload-time = "2026-02-24T23:59:10.131Z" }, ] [[package]] @@ -3624,16 +3641,17 @@ wheels = [ [[package]] name = "virtualenv" -version = "20.38.0" +version = "21.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, { name = "filelock" }, { name = "platformdirs" }, + { name = "python-discovery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d2/03/a94d404ca09a89a7301a7008467aed525d4cdeb9186d262154dd23208709/virtualenv-20.38.0.tar.gz", hash = "sha256:94f39b1abaea5185bf7ea5a46702b56f1d0c9aa2f41a6c2b8b0af4ddc74c10a7", size = 5864558, upload-time = "2026-02-19T07:48:02.385Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/c9/18d4b36606d6091844daa3bd93cf7dc78e6f5da21d9f21d06c221104b684/virtualenv-21.1.0.tar.gz", hash = "sha256:1990a0188c8f16b6b9cf65c9183049007375b26aad415514d377ccacf1e4fb44", size = 5840471, upload-time = "2026-02-27T08:49:29.702Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/d7/394801755d4c8684b655d35c665aea7836ec68320304f62ab3c94395b442/virtualenv-20.38.0-py3-none-any.whl", hash = "sha256:d6e78e5889de3a4742df2d3d44e779366325a90cf356f15621fddace82431794", size = 5837778, upload-time = "2026-02-19T07:47:59.778Z" }, + { url = "https://files.pythonhosted.org/packages/78/55/896b06bf93a49bec0f4ae2a6f1ed12bd05c8860744ac3a70eda041064e4d/virtualenv-21.1.0-py3-none-any.whl", hash = "sha256:164f5e14c5587d170cf98e60378eb91ea35bf037be313811905d3a24ea33cc07", size = 5825072, upload-time = "2026-02-27T08:49:27.516Z" }, ] [[package]] From f7df0a4bf7283ea74a253afe517fc32538eebcbb Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 2 Mar 2026 13:13:13 -0800 Subject: [PATCH 111/335] Sparsity loss implemented from origin/f_inr_tomography --- src/quantem/tomography/object_models.py | 24 ++++++++++++++++++++++-- src/quantem/tomography/tomography.py | 2 +- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index f71964f1..a7098c79 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -348,14 +348,18 @@ def obj(self) -> torch.Tensor: def apply_soft_constraints( self, coords: torch.Tensor, + pred: torch.Tensor, ) -> torch.Tensor: - soft_loss = torch.tensor(0.0, device=coords.device) + soft_loss = torch.tensor(0.0, device=pred.device) if self.constraints.tv_vol > 0: num_tv_samples = min(10_000, coords.shape[0]) tv_indices = torch.randperm(coords.shape[0], device=coords.device)[:num_tv_samples] tv_coords = coords[tv_indices].detach().requires_grad_(True) tv_densities_recomputed = self.model(tv_coords) + if isinstance(tv_densities_recomputed, tuple): + tv_densities_recomputed = tv_densities_recomputed[0] + # Ensure shape is [num_samples, num_channels] if tv_densities_recomputed.dim() == 1: tv_densities_recomputed = tv_densities_recomputed.unsqueeze(-1) @@ -372,6 +376,10 @@ def apply_soft_constraints( grad_norm = torch.norm(grad_outputs, dim=1) # Shape: [num_samples] soft_loss += self.constraints.tv_vol * grad_norm.mean() + if self.constraints.sparsity > 0: + sparsity_loss = self.constraints.sparsity * torch.norm(pred, p=1) + soft_loss += sparsity_loss + return soft_loss def apply_hard_constraints(self, pred: torch.Tensor) -> torch.Tensor: @@ -449,6 +457,11 @@ def reset(self): def forward(self, coords: torch.Tensor) -> torch.Tensor: """forward pass for the INR model""" all_densities = self.model(coords) + if isinstance(all_densities, tuple): + all_densities = all_densities[0] + ot_reg = all_densities[1] + else: + ot_reg = None if all_densities.dim() > 1: all_densities = all_densities.squeeze(-1) @@ -462,7 +475,11 @@ def forward(self, coords: torch.Tensor) -> torch.Tensor: all_densities = all_densities * valid_mask all_densities = self.apply_hard_constraints(all_densities) - return all_densities + + if ot_reg is not None: + return all_densities, ot_reg + else: + return all_densities # Pretrain Loop @@ -585,6 +602,9 @@ def create_volume(self, return_vol: bool = False): ) batch_outputs = model(batch_coords) # (B, C) or (B,) etc. + + if isinstance(batch_outputs, tuple): + batch_outputs = batch_outputs[0] batch_outputs = self.apply_hard_constraints(batch_outputs) # Ensure shape is (B, C) diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index e8aded8d..d57c012e 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -162,7 +162,7 @@ def reconstruct( batch_consistency_loss = torch.nn.functional.mse_loss(pred, target) - soft_constraints_loss = self.obj_model.apply_soft_constraints(all_coords) + soft_constraints_loss = self.obj_model.apply_soft_constraints(all_coords, pred) epoch_soft_constraint_loss += soft_constraints_loss.detach() From 50e21b0ad35b8dc65f65a736c4825a88942f343e Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 2 Mar 2026 13:45:56 -0800 Subject: [PATCH 112/335] get_loss_module functionality, allowing for kwargs in the loss functon. --- src/quantem/core/ml/loss_functions.py | 23 ++++++++++++++++------- src/quantem/tomography/object_models.py | 3 ++- src/quantem/tomography/tomography.py | 20 +++++++++++++++----- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/quantem/core/ml/loss_functions.py b/src/quantem/core/ml/loss_functions.py index c99b6494..a81b4fb0 100644 --- a/src/quantem/core/ml/loss_functions.py +++ b/src/quantem/core/ml/loss_functions.py @@ -11,7 +11,7 @@ import torch -def get_loss_module(name: str | nn.Module | Callable, dtype: torch.dtype) -> nn.Module: +def get_loss_module(name: str | nn.Module | Callable, dtype: torch.dtype, **kwargs) -> nn.Module: """Return a loss *module* by name, or wrap/return what was provided.""" if isinstance(name, nn.Module): return name @@ -32,20 +32,29 @@ def forward(self, pred: torch.Tensor, target: torch.Tensor) -> torch.Tensor: if dtype.is_complex: if loss_name in {"l2", "complex_l2"}: - return ComplexL2Loss() + return ComplexL2Loss(**kwargs) if loss_name in {"complex_cartesian_l2"}: - return ComplexCartesianL2Loss() + return ComplexCartesianL2Loss(**kwargs) if loss_name in {"amp_phase_l2"}: - return AmpPhaseL2Loss() + return AmpPhaseL2Loss(**kwargs) if loss_name in {"combined_l2"}: - return CombinedL2Loss() + return CombinedL2Loss(**kwargs) raise ValueError(f"Unknown loss module for complex dtype: {loss_name}") # real dtype if loss_name in {"l2"}: - return nn.MSELoss() + return nn.MSELoss(**kwargs) if loss_name in {"l1"}: - return nn.L1Loss() + return nn.L1Loss(**kwargs) + if loss_name in {"smooth_l1"}: + return nn.SmoothL1Loss(**kwargs) + if loss_name in {"charbonnier"}: + return CharbonnierLoss(**kwargs) + if loss_name in {"llmse"}: + return LLMSELoss(**kwargs) + if loss_name in {"mse_log_mse"}: + return MSELogMSELoss(**kwargs) + raise ValueError(f"Unknown loss module for real dtype: {loss_name}") diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index a7098c79..2e7c6377 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -31,8 +31,9 @@ class DefaultConstraintsTomography(Constraints): # Soft Constraints tv_vol: float = 0.0 + sparsity: float = 0.0 - soft_constraint_keys = ["tv_vol"] + soft_constraint_keys = ["tv_vol", "sparsity"] hard_constraint_keys = ["positivity", "shrinkage", "circular_mask", "fourier_filter"] diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index d57c012e..800eca1d 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -1,5 +1,5 @@ import os -from typing import Literal, Optional, Self +from typing import Literal, Self import numpy as np import torch @@ -8,6 +8,7 @@ from quantem.core.io.serialize import load as autoserialize_load from quantem.core.ml.ddp import DDPMixin +from quantem.core.ml.loss_functions import get_loss_module from quantem.core.utils.filter import gaussian_filter_2d_stack, gaussian_kernel_1d from quantem.core.utils.tomography_utils import ( torch_phase_cross_correlation, @@ -51,11 +52,18 @@ def reconstruct( optimizer_params: dict | None = None, scheduler_params: dict | None = {}, constraints: dict = {}, # TODO: What to pass into the constraints? - loss_func: tuple[str, Optional[float]] = ("smooth_l1", 0.07), - num_samples_per_ray: int | list[tuple[int, int]] = None, + num_samples_per_ray: int | list[tuple[int, int]] = 1, profiling_mode: bool = False, val_fraction: float = 0.0, - # reset_dset: bool = False, + loss_type: Literal[ + "l2", + "l1", + "smooth_l1", + "charbonnier", + "llmse", + "mse_log_mse", + ] = "l2", + loss_func_kwargs: dict = {}, reset_dset: DatasetModelType | None = None, ): """ @@ -120,6 +128,8 @@ def reconstruct( else: print("num_samples_per_ray schedule provided.") + loss_func = get_loss_module(name=loss_type, dtype=self.obj_model.dtype, **loss_func_kwargs) + print(f"N: {N}, num_samples_per_ray: {num_samples_per_ray}") for a0 in range(num_iter): consistency_loss = 0.0 @@ -160,7 +170,7 @@ def reconstruct( target = batch["target_value"].to(self.device, non_blocking=True).float() - batch_consistency_loss = torch.nn.functional.mse_loss(pred, target) + batch_consistency_loss = loss_func(pred, target) soft_constraints_loss = self.obj_model.apply_soft_constraints(all_coords, pred) From 1b4aeeb6626b1d14de741caac3abc2bdc0be633f Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 2 Mar 2026 13:57:22 -0800 Subject: [PATCH 113/335] Removed KE regularization from DINR paper -- will probably move off to F-INR and TensoRF decomps. Fixing some linting issues --- src/quantem/tomography/object_models.py | 27 +++++++++---------------- src/quantem/tomography/tomography.py | 3 +-- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 2e7c6377..38aba292 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -1,7 +1,7 @@ from abc import abstractmethod from copy import deepcopy from dataclasses import dataclass -from typing import Any, Callable, Generator +from typing import Any, Callable, Generator, Literal import numpy as np import torch @@ -16,6 +16,8 @@ from quantem.core.utils.rng import RNGMixin from quantem.tomography.dataset_models import TomographyINRPretrainDataset +object_type = Literal["potential"] + @dataclass(slots=True) class DefaultConstraintsTomography(Constraints): @@ -48,7 +50,7 @@ class ObjectBase(AutoSerialize, nn.Module, RNGMixin, OptimizerMixin): def __init__( self, - shape: tuple[int, int, int], + shape: tuple[int, int, int], # pyright: ignore[reportRedeclaration] device: str = "cpu", rng: np.random.Generator | int | None = None, _token: object | None = None, @@ -74,8 +76,8 @@ def shape(self) -> tuple[int, int, int]: return self._shape @shape.setter - def shape(self, shape: tuple[int, int, int]): - self._shape = shape + def shape(self, new_shape: tuple[int, int, int]): + self._shape = new_shape @property def obj(self) -> torch.Tensor: @@ -136,18 +138,18 @@ class ObjectConstraints(BaseConstraints, ObjectBase): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.constraints = self.DEFAULT_CONSTRAINTS.copy() + self.constraints: DefaultConstraintsTomography = self.DEFAULT_CONSTRAINTS.copy() def apply_hard_constraints( self, - obj: torch.Tensor, + pred: torch.Tensor, ) -> torch.Tensor: """ Apply hard constraints to the object model. Only hard constraint here is the positivity and shrinkage. TODO: Add the other hard constraints. """ - obj2 = obj.clone() + obj2 = pred.clone() if self.constraints.positivity: obj2 = torch.clamp(obj2, min=0.0, max=None) if self.constraints.shrinkage: @@ -297,7 +299,6 @@ def __init__( ) self._pretrain_losses = [] self._pretrain_lrs = [] - self.device = device # Register the network submodule (important: real nn.Module attribute) if model is not None: @@ -458,11 +459,6 @@ def reset(self): def forward(self, coords: torch.Tensor) -> torch.Tensor: """forward pass for the INR model""" all_densities = self.model(coords) - if isinstance(all_densities, tuple): - all_densities = all_densities[0] - ot_reg = all_densities[1] - else: - ot_reg = None if all_densities.dim() > 1: all_densities = all_densities.squeeze(-1) @@ -477,10 +473,7 @@ def forward(self, coords: torch.Tensor) -> torch.Tensor: all_densities = self.apply_hard_constraints(all_densities) - if ot_reg is not None: - return all_densities, ot_reg - else: - return all_densities + return all_densities # Pretrain Loop diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 800eca1d..654bf529 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -7,7 +7,6 @@ from tqdm.auto import tqdm from quantem.core.io.serialize import load as autoserialize_load -from quantem.core.ml.ddp import DDPMixin from quantem.core.ml.loss_functions import get_loss_module from quantem.core.utils.filter import gaussian_filter_2d_stack, gaussian_kernel_1d from quantem.core.utils.tomography_utils import ( @@ -21,7 +20,7 @@ from quantem.tomography.tomography_opt import TomographyOpt -class Tomography(TomographyOpt, TomographyBase, DDPMixin): +class Tomography(TomographyOpt, TomographyBase): """ Class for handling all ML tomography reconstruction methods. Automatic handling between AD and INR-based tomography. From bfc6e062365d5613a4b8dc38c6a6c6d222553a72 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 2 Mar 2026 14:20:20 -0800 Subject: [PATCH 114/335] Fixed all linting issues in object_models.py. @amccray, ask about ignoring --- src/quantem/core/ml/ddp.py | 2 +- src/quantem/tomography/object_models.py | 28 ++++++++++++++----------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/quantem/core/ml/ddp.py b/src/quantem/core/ml/ddp.py index 0e60e398..9cc02ee3 100644 --- a/src/quantem/core/ml/ddp.py +++ b/src/quantem/core/ml/ddp.py @@ -171,7 +171,7 @@ def device(self) -> torch.device: return self._device @device.setter - def device(self, device: torch.device): + def device(self, device: torch.device | str): if isinstance(device, str): device = torch.device(device) self._device = device diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 38aba292..5f093155 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -171,7 +171,7 @@ def apply_hard_constraints( # return NotImplementedError("Subclasses must implement this method.") @abstractmethod - def get_tv_loss(self, obj: torch.Tensor, tv_weight: float = 0.0) -> torch.Tensor: + def get_tv_loss(self, **kwargs) -> torch.Tensor: """ Get the TV loss for the object model. Must be implemented in each subclass. """ @@ -260,7 +260,7 @@ def apply_soft_constraints(self, obj: torch.Tensor) -> torch.Tensor: soft_loss = torch.tensor(0.0, device=obj.device, dtype=obj.dtype, requires_grad=True) if self.constraints.tv_vol > 0: tv_loss = self.get_tv_loss( - obj.unsqueeze(0).unsqueeze(0), factor=self.constraints.tv_vol + obj.unsqueeze(0).unsqueeze(0), tv_weight=self.constraints.tv_vol ) soft_loss += tv_loss return soft_loss @@ -270,7 +270,7 @@ def forward(self, dummy_input=None) -> torch.Tensor: return self.obj # --- Defining the TV loss --- - def get_tv_loss(self, obj: torch.Tensor, tv_weight: float = 1e-3) -> torch.Tensor: + def get_tv_loss(self, obj: torch.Tensor, tv_weight: float = 1e-3) -> torch.Tensor: # pyright: ignore[reportIncompatibleMethodOverride] -> get_tv_loss has different arguments depending on the object. tv_d = torch.pow(obj[:, :, 1:, :, :] - obj[:, :, :-1, :, :], 2).sum() tv_h = torch.pow(obj[:, :, :, 1:, :] - obj[:, :, :, :-1, :], 2).sum() tv_w = torch.pow(obj[:, :, :, :, 1:] - obj[:, :, :, :, :-1], 2).sum() @@ -279,7 +279,7 @@ def get_tv_loss(self, obj: torch.Tensor, tv_weight: float = 1e-3) -> torch.Tenso return tv_loss * tv_weight / (torch.prod(torch.tensor(obj.shape))) # --- Helper Functions --- - def to(self, device: str): + def to(self, device: str): # pyright: ignore[reportIncompatibleMethodOverride] -> better to do this device change self._obj = self._obj.to(device) @@ -327,7 +327,7 @@ def from_model( # --- Properties --- @property - def model(self) -> "nn.Module": + def model(self) -> nn.Module: """ Returns the INR model. """ @@ -527,6 +527,11 @@ def _pretrain( loss_fn: Callable, apply_constraints: bool, ): + if self.optimizer is None: + raise RuntimeError("Optimizer not set. Call set_optimizer() first.") + if self.scheduler is None: + raise RuntimeError("Scheduler not set. Call set_scheduler() first.") + self.model.train() optimizer = self.optimizer scheduler = self.scheduler @@ -570,8 +575,7 @@ def create_volume(self, return_vol: bool = False): coords_1d = torch.linspace(-1, 1, N) x, y, z = torch.meshgrid(coords_1d, coords_1d, coords_1d, indexing="ij") inputs = torch.stack([x, y, z], dim=-1).reshape(-1, 3) - - model = self.model.module if hasattr(self.model, "module") else self.model + model = self.model.module if isinstance(self.model, nn.DataParallel) else self.model inference_batch_size = 5 * N * N total_samples = N**3 @@ -624,7 +628,7 @@ def create_volume(self, return_vol: bool = False): outputs_dev = outputs.to(self.device) # (local_B, C) if local_B < max_size: pad = torch.zeros( - (max_size - local_B, C), + (max_size - local_B, C), # type: ignore device=self.device, dtype=outputs_dev.dtype, ) @@ -633,7 +637,7 @@ def create_volume(self, return_vol: bool = False): outputs_padded = outputs_dev gathered_outputs = [ - torch.empty((max_size, C), device=self.device, dtype=outputs_dev.dtype) + torch.empty((max_size, C), device=self.device, dtype=outputs_dev.dtype) # type: ignore for _ in range(self.world_size) ] dist.all_gather(gathered_outputs, outputs_padded.contiguous()) @@ -651,10 +655,10 @@ def create_volume(self, return_vol: bool = False): self._obj = pred_full.detach().cpu() - def get_tv_loss( + def get_tv_loss( # pyright: ignore[reportIncompatibleMethodOverride] self, coords: torch.Tensor, - ): + ) -> torch.Tensor: tv_loss = torch.tensor(0.0, device=coords.device) num_tv_samples = min(10000, coords.shape[0]) @@ -679,7 +683,7 @@ def get_tv_loss( tv_loss += self.constraints.tv_vol * grad_norm.mean() return tv_loss - def to(self, device: str): + def to(self, device: str): # pyright: ignore[reportIncompatibleMethodOverride] -> better to do this device change self.device = device # self._obj = self._obj.to(self.device) self.reconnect_optimizer_to_parameters() From 886ef22f2484970056977b7b0dcad326fdb9a964 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 2 Mar 2026 14:26:58 -0800 Subject: [PATCH 115/335] ddp.py linting errors addressed by ignoring. Again @arthurmccray, since Dataset doesn't have a length attribute in torch --- src/quantem/core/ml/ddp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/quantem/core/ml/ddp.py b/src/quantem/core/ml/ddp.py index 9cc02ee3..18d104f6 100644 --- a/src/quantem/core/ml/ddp.py +++ b/src/quantem/core/ml/ddp.py @@ -122,14 +122,14 @@ def setup_dataloader( if self.global_rank == 0: print("Dataloader setup complete:") - print(f" Total train samples: {len(train_dataset)}") + print(f" Total train samples: {len(train_dataset)}") # pyright: ignore[reportArgumentType] --> Torch datasets do not have a len method, but still works. print(f" Local batch size: {batch_size}") print(f" Global batch size: {batch_size * self.world_size}") print(f" Train batches per GPU per epoch: {len(train_dataloader)}") if val_dataset: print(f" Total val samples: {len(val_dataset)}") - print(f" Val batches per GPU per epoch: {len(val_dataloader)}") + print(f" Val batches per GPU per epoch: {len(val_dataloader)}") # pyright: ignore[reportArgumentType] --> Torch datasets do not have a len method, but still works. return train_dataloader, train_sampler, val_dataloader, val_sampler From 471bd6bc3fb4870fbd7384324179060e711ca7f1 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 2 Mar 2026 14:38:04 -0800 Subject: [PATCH 116/335] Cleaned up tomography_base.py; inheritting from ObjectConstraints to get correct typing for and relying on DDPMixin to set the correct devices. --- src/quantem/core/ml/ddp.py | 5 --- src/quantem/tomography/tomography_base.py | 42 +++++------------------ 2 files changed, 8 insertions(+), 39 deletions(-) diff --git a/src/quantem/core/ml/ddp.py b/src/quantem/core/ml/ddp.py index 18d104f6..ca572a32 100644 --- a/src/quantem/core/ml/ddp.py +++ b/src/quantem/core/ml/ddp.py @@ -13,11 +13,6 @@ class DDPMixin: - """ - def __init__( - self, - ): - self.setup_distributed() - def setup_distributed(self, device: str | torch.device | None = None): """ Initializes parameters depending if multiple-GPU training, single-GPU training, or CPU training. diff --git a/src/quantem/tomography/tomography_base.py b/src/quantem/tomography/tomography_base.py index 7661c301..2c06c20f 100644 --- a/src/quantem/tomography/tomography_base.py +++ b/src/quantem/tomography/tomography_base.py @@ -1,5 +1,4 @@ import numpy as np -import torch from numpy.typing import NDArray from quantem.core.io.serialize import AutoSerialize @@ -9,8 +8,8 @@ from quantem.tomography.logger_tomography import LoggerTomography from quantem.tomography.object_models import ( DefaultConstraintsTomography, - ObjectBase, - ObjectPixelated, + ObjectConstraints, + ObjectINR, ) @@ -26,14 +25,14 @@ class TomographyBase(AutoSerialize, RNGMixin, DDPMixin): def __init__( self, dset: DatasetModelType, - obj_model: ObjectBase, + obj_model: ObjectConstraints, logger: LoggerTomography | None = None, device: str = "cuda", rng: np.random.Generator | int | None = None, _token: object | None = None, ): - # if _token is not self._token: # TODO: Idk why this isn't working. - # raise RuntimeError("Use Dataset.from_* to instantiate this class.") + if _token is not self._token: + raise RuntimeError("Use .from_* to instantiate this class.") super().__init__() self.obj_model = obj_model @@ -48,12 +47,9 @@ def __init__( self._consistency_losses: list[float] = [] self._val_losses: list[float] = [] # DDP Initialization - # print("Checking if obj_model is a ObjectPixelated: ", not isinstance(obj_model, ObjectPixelated)) - if not isinstance(obj_model, ObjectPixelated): + if isinstance(obj_model, ObjectINR): print("Setting up DDP for obj_model") self.setup_distributed(device=device) - # self._obj_model._model = self.build_model(obj_model) # Assuming when object is initialized it's already wrapped in DDP? - # print("After DDP Setup", self._obj_model) self.dset = dset self.dset.to(device) @@ -72,17 +68,11 @@ def dset(self, new_dset: DatasetModelType): self._dset = new_dset @property - def obj_type(self) -> str: - return self.obj_model.obj_type - - @property - def obj_model(self) -> ObjectBase: + def obj_model(self) -> ObjectConstraints: return self._obj_model @obj_model.setter - def obj_model(self, obj_model: ObjectBase): - # if not isinstance(obj_model, ObjectBase): - # raise TypeError(f"obj_model should be a ObjectBase, got {type(obj_model)}") + def obj_model(self, obj_model: ObjectConstraints): self._obj_model = obj_model @property @@ -91,10 +81,6 @@ def constraints(self) -> DefaultConstraintsTomography: @constraints.setter def constraints(self, constraints: DefaultConstraintsTomography): - if not isinstance(constraints, DefaultConstraintsTomography): - raise TypeError( - f"constraints should be a DefaultConstraintsTomography, got {type(constraints)}" - ) self.obj_model.constraints = constraints @property @@ -107,18 +93,6 @@ def logger(self, logger: LoggerTomography | None): raise TypeError(f"logger should be a LoggerTomography, got {type(logger)}") self._logger = logger - @property - def device(self) -> str: - return torch.device(self._device) - - @device.setter - def device(self, device: str): - print("Device trying to set: ", device) - # if not isinstance(device, str): - # raise TypeError(f"device should be a str, got {type(device)}") - self._device = device - # self.to(device) - @property def epoch_losses(self) -> NDArray: """ From 9336e4f5ba5fa54d415ea1afc48bb6bfebeed02a Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 2 Mar 2026 15:09:53 -0800 Subject: [PATCH 117/335] Cleaned up some tomography.py; lots of linter errors still but meh --- src/quantem/core/ml/ddp.py | 4 ++- src/quantem/tomography/dataset_models.py | 12 +++++---- src/quantem/tomography/object_models.py | 19 ++++++++++--- src/quantem/tomography/tomography.py | 33 ++++++++++++++--------- src/quantem/tomography/tomography_base.py | 8 +++--- 5 files changed, 49 insertions(+), 27 deletions(-) diff --git a/src/quantem/core/ml/ddp.py b/src/quantem/core/ml/ddp.py index ca572a32..0570322f 100644 --- a/src/quantem/core/ml/ddp.py +++ b/src/quantem/core/ml/ddp.py @@ -5,6 +5,8 @@ import torch.nn as nn from torch.utils.data import DataLoader, Dataset, DistributedSampler, random_split +from quantem.tomography.dataset_models import DatasetModelType + class DDPMixin: """ @@ -50,7 +52,7 @@ def setup_distributed(self, device: str | torch.device | None = None): def setup_dataloader( self, - dataset: Dataset, + dataset: Dataset | DatasetModelType, batch_size: int, num_workers: int = 0, val_fraction: float = 0.0, diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index ee7d74f7..50c1a7e1 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -208,16 +208,18 @@ def shifts_params(self, shifts: torch.Tensor, device: str): self._shifts_params = nn.Parameter(shifts.to(device)) @property - def device(self) -> str: + def device(self) -> torch.device: return self._device @device.setter - def device(self, device: str): + def device(self, device: torch.device | str): + if isinstance(device, str): + device = torch.device(device) self._device = device # --- Helper Functions --- @abstractmethod - def to(self, device: str): + def to(self, device: torch.device | str): """ Moves the dataset to the device, and also insantiates the aux params to the device. """ @@ -263,7 +265,7 @@ def forward( pixel_loc=None, ) - def to(self, device: str): + def to(self, device: str | torch.device): """ Moves the tilt stack and tilt_angles to the device, along with other nn.Parameters to the device. """ @@ -474,7 +476,7 @@ def __len__( N = max(self.tilt_stack.shape) return self.tilt_stack.shape[0] * N * N - def to(self, device: str): + def to(self, device: torch.device | str): self._z1_params = nn.Parameter(self._z1_angles.to(device)) self._z3_params = nn.Parameter(self._z3_angles.to(device)) self._shifts_params = nn.Parameter(self._shifts.to(device)) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 5f093155..1f55aa8e 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -256,6 +256,10 @@ def name(self) -> str: def obj_type(self) -> str: return "pixelated" + @property + def dtype(self) -> torch.dtype: + return self._obj.dtype + def apply_soft_constraints(self, obj: torch.Tensor) -> torch.Tensor: soft_loss = torch.tensor(0.0, device=obj.device, dtype=obj.dtype, requires_grad=True) if self.constraints.tv_vol > 0: @@ -279,8 +283,13 @@ def get_tv_loss(self, obj: torch.Tensor, tv_weight: float = 1e-3) -> torch.Tenso return tv_loss * tv_weight / (torch.prod(torch.tensor(obj.shape))) # --- Helper Functions --- - def to(self, device: str): # pyright: ignore[reportIncompatibleMethodOverride] -> better to do this device change + def to(self, device: str | torch.device): # pyright: ignore[reportIncompatibleMethodOverride] -> better to do this device change + if isinstance(device, str): + device = torch.device(device) + self._device = device self._obj = self._obj.to(device) + self.reconnect_optimizer_to_parameters() + return self class ObjectINR(ObjectConstraints, DDPMixin): @@ -327,7 +336,7 @@ def from_model( # --- Properties --- @property - def model(self) -> nn.Module: + def model(self) -> nn.Module | nn.parallel.DistributedDataParallel: """ Returns the INR model. """ @@ -683,8 +692,10 @@ def get_tv_loss( # pyright: ignore[reportIncompatibleMethodOverride] tv_loss += self.constraints.tv_vol * grad_norm.mean() return tv_loss - def to(self, device: str): # pyright: ignore[reportIncompatibleMethodOverride] -> better to do this device change - self.device = device + def to(self, device: str | torch.device): # pyright: ignore[reportIncompatibleMethodOverride] -> better to do this device change + if isinstance(device, str): + device = torch.device(device) + self._device = device # self._obj = self._obj.to(self.device) self.reconnect_optimizer_to_parameters() diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 654bf529..f3023048 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -1,5 +1,5 @@ import os -from typing import Literal, Self +from typing import Literal, Self, cast import numpy as np import torch @@ -14,7 +14,11 @@ ) from quantem.tomography.dataset_models import DatasetModelType from quantem.tomography.logger_tomography import LoggerTomography -from quantem.tomography.object_models import ObjectModelType +from quantem.tomography.object_models import ( + DefaultConstraintsTomography, + ObjectINR, + ObjectPixelated, +) from quantem.tomography.radon.radon import iradon_torch, radon_torch from quantem.tomography.tomography_base import TomographyBase from quantem.tomography.tomography_opt import TomographyOpt @@ -30,7 +34,7 @@ class Tomography(TomographyOpt, TomographyBase): def from_models( cls, dset: DatasetModelType, - obj_model: ObjectModelType, + obj_model: ObjectINR, logger: LoggerTomography | None = None, device: str = "cuda", rng: np.random.Generator | int | None = None, @@ -49,7 +53,7 @@ def reconstruct( num_workers: int = 32, reset: bool = False, optimizer_params: dict | None = None, - scheduler_params: dict | None = {}, + scheduler_params: dict | None = None, constraints: dict = {}, # TODO: What to pass into the constraints? num_samples_per_ray: int | list[tuple[int, int]] = 1, profiling_mode: bool = False, @@ -83,7 +87,6 @@ def reconstruct( raise NotImplementedError("Reset is not implemented yet.") new_scheduler = reset - if optimizer_params is not None: self.optimizer_params = optimizer_params self.set_optimizers() @@ -93,11 +96,11 @@ def reconstruct( self.scheduler_params = scheduler_params new_scheduler = True - if constraints is not None: - self.obj_model.constraints = constraints - if new_scheduler: - self.set_schedulers(scheduler_params, num_iter=num_iter) + self.set_schedulers(self.scheduler_params, num_iter=num_iter) + + if constraints is not None: + self.obj_model.constraints = cast(DefaultConstraintsTomography, constraints) # Setting up DDP if not hasattr(self, "dataloader") or reset_dset is not None: @@ -107,8 +110,13 @@ def reconstruct( self.dset = reset_dset self.dset.to(self.device) - self.optimizer_params = optimizer_params - self.set_optimizers() + if optimizer_params is not None: + self.optimizer_params = optimizer_params + self.set_optimizers() + if scheduler_params is not None: + self.scheduler_params = scheduler_params + self.set_schedulers(self.scheduler_params, num_iter=num_iter) + self.dataloader, self.sampler, self.val_dataloader, self.val_sampler = ( self.setup_dataloader( self.dset, @@ -320,7 +328,7 @@ class TomographyConventional(TomographyBase): def from_models( cls, dset: DatasetModelType, - obj_model: ObjectModelType, + obj_model: ObjectPixelated, logger: LoggerTomography | None = None, device: str = "cuda", rng: np.random.Generator | int | None = None, @@ -425,7 +433,6 @@ def _reconstruction_epoch( torch.ones_like(error), theta=self.dset.tilt_angles, device=self.device, - filter_name=None, circle=True, ) diff --git a/src/quantem/tomography/tomography_base.py b/src/quantem/tomography/tomography_base.py index 2c06c20f..def4867f 100644 --- a/src/quantem/tomography/tomography_base.py +++ b/src/quantem/tomography/tomography_base.py @@ -8,8 +8,8 @@ from quantem.tomography.logger_tomography import LoggerTomography from quantem.tomography.object_models import ( DefaultConstraintsTomography, - ObjectConstraints, ObjectINR, + ObjectModelType, ) @@ -25,7 +25,7 @@ class TomographyBase(AutoSerialize, RNGMixin, DDPMixin): def __init__( self, dset: DatasetModelType, - obj_model: ObjectConstraints, + obj_model: ObjectModelType, logger: LoggerTomography | None = None, device: str = "cuda", rng: np.random.Generator | int | None = None, @@ -68,11 +68,11 @@ def dset(self, new_dset: DatasetModelType): self._dset = new_dset @property - def obj_model(self) -> ObjectConstraints: + def obj_model(self) -> ObjectModelType: return self._obj_model @obj_model.setter - def obj_model(self, obj_model: ObjectConstraints): + def obj_model(self, obj_model: ObjectModelType): self._obj_model = obj_model @property From ce28e29ed5a7c9948623c3345d7d0fd6f8a7730e Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 2 Mar 2026 15:15:23 -0800 Subject: [PATCH 118/335] _token fix for TomographyBase and some other stuff --- src/quantem/core/ml/ddp.py | 6 +++--- src/quantem/tomography/tomography.py | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/quantem/core/ml/ddp.py b/src/quantem/core/ml/ddp.py index 0570322f..66b23273 100644 --- a/src/quantem/core/ml/ddp.py +++ b/src/quantem/core/ml/ddp.py @@ -61,7 +61,7 @@ def setup_dataloader( persist = num_workers > 0 if val_fraction > 0.0: - train_dataset, val_dataset = random_split(dataset, [1 - val_fraction, val_fraction]) + train_dataset, val_dataset = random_split(dataset, [1 - val_fraction, val_fraction]) # type: ignore[reportArgumentType] --> dataset inherits from torch Dataset so this is fine. else: train_dataset = dataset val_dataset = None @@ -69,7 +69,7 @@ def setup_dataloader( if self.world_size > 1: shuffle = True train_sampler = DistributedSampler( - train_dataset, + train_dataset, # type: ignore[reportArgumentType] --> Torch datasets do not have a len method, but still works. num_replicas=self.world_size, rank=self.global_rank, shuffle=shuffle, @@ -92,7 +92,7 @@ def setup_dataloader( shuffle = True train_dataloader = DataLoader( - train_dataset, + train_dataset, # type: ignore[reportArgumentType] --> Torch datasets do not have a len method, but still works. batch_size=batch_size, num_workers=num_workers, sampler=train_sampler, diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index f3023048..e0e72f69 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -44,6 +44,8 @@ def from_models( obj_model=obj_model, logger=logger, device=device, + rng=rng, + _token=cls._token, ) def reconstruct( From 9ecd8d7aeaec14d81a5d5cb805ef02723e53c449 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 2 Mar 2026 16:05:19 -0800 Subject: [PATCH 119/335] Added TomographyDatasetConstraints to dataset_models.py. Perhaps there's a better way to do this across both object_models.py and dataset_models.py i.e, an overall tomography_constraints.py which is then shared between the two Objects. --- src/quantem/core/ml/constraints.py | 2 +- src/quantem/tomography/dataset_models.py | 53 ++++++++++++++++++++++-- src/quantem/tomography/object_models.py | 14 +------ src/quantem/tomography/tomography.py | 7 +++- src/quantem/tomography/utils.py | 28 +++++++++++++ 5 files changed, 85 insertions(+), 19 deletions(-) diff --git a/src/quantem/core/ml/constraints.py b/src/quantem/core/ml/constraints.py index cce43659..553b0611 100644 --- a/src/quantem/core/ml/constraints.py +++ b/src/quantem/core/ml/constraints.py @@ -8,7 +8,7 @@ from numpy.typing import NDArray -@dataclass(slots=True) +@dataclass(slots=False) class Constraints(ABC): """ Any model that inherits from BaseConstraints will contain a Constraints instance that contains soft and hard constraints. diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index 50c1a7e1..cae642eb 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -10,7 +10,24 @@ from quantem.core.datastructures.dataset3d import Dataset3d from quantem.core.io.serialize import AutoSerialize +from quantem.core.ml.constraints import BaseConstraints, Constraints from quantem.core.ml.optimizer_mixin import OptimizerMixin +from quantem.tomography.utils import tv_loss_1d + + +# --- Constraints --- +@dataclass(slots=False) +class DefaultTomographyDatasetConstraints(Constraints): + """ + Data class for constraints that can be applied to the parameters of a tomography dataset. + """ + + # Soft Constraints + tv_zs: float = 0.0 + tv_shifts: float = 0.0 + + soft_constraint_keys = ["tv_zs", "tv_shifts"] + hard_constraint_keys = [] @dataclass @@ -169,7 +186,7 @@ def learn_tilt_axis(self, learn_tilt_axis: bool): @property def reference_tilt_idx(self) -> int: - return self._reference_tilt_angle_idx + return int(self._reference_tilt_angle_idx) @reference_tilt_idx.setter def reference_tilt_idx(self, reference_tilt_idx: int): @@ -227,7 +244,35 @@ def to(self, device: torch.device | str): raise NotImplementedError("This method should be implemented in subclasses.") -class TomographyPixDataset(TomographyDatasetBase): +class TomographyDatasetConstraints(BaseConstraints, TomographyDatasetBase): + DEFAULT_CONSTRAINTS = DefaultTomographyDatasetConstraints() + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.constraints: DefaultTomographyDatasetConstraints = self.DEFAULT_CONSTRAINTS.copy() + + def apply_soft_constraints(self) -> torch.Tensor: + soft_loss = torch.tensor(0.0, device=self.z1_params.device) + if self.constraints.tv_zs > 0: + tv_loss_zs = tv_loss_1d(self.z1_params) + tv_loss_zs += tv_loss_1d(self.z3_params) + tv_loss_zs = self.constraints.tv_zs * tv_loss_zs + soft_loss += tv_loss_zs + + if self.constraints.tv_shifts > 0: + # Shift params is of shape (N, 2) + tv_loss_shifts = tv_loss_1d(self.shifts_params[:, 0]) + tv_loss_shifts += tv_loss_1d(self.shifts_params[:, 1]) + tv_loss_shifts = self.constraints.tv_shifts * tv_loss_shifts + soft_loss += tv_loss_shifts + + return soft_loss + + def apply_hard_constraints(self): + pass + + +class TomographyPixDataset(TomographyDatasetConstraints): """ Dataset class for pixel-based tomography, i.e AD, SIRT, WBP, etc... @@ -283,7 +328,7 @@ def to(self, device: str | torch.device): self.device = device -class TomographyINRDataset(TomographyDatasetBase, Dataset): +class TomographyINRDataset(TomographyDatasetConstraints, Dataset): """ Dataset class for INR-based tomography. @@ -446,7 +491,7 @@ def integrate_rays( def __getitem__( self, idx: int, - ) -> DatasetValue: + ) -> dict: """ Gets the item for INR i.e, the project index, pixel value at (i, j), and the tilt angle. """ diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 1f55aa8e..4a67aeb6 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -19,7 +19,7 @@ object_type = Literal["potential"] -@dataclass(slots=True) +@dataclass(slots=False) class DefaultConstraintsTomography(Constraints): """ Data class for all constraints that can be applied to the object model. @@ -158,18 +158,6 @@ def apply_hard_constraints( # TODO: Need to implement the other hard constraints: Fourier Filter and Circular Mask. return obj2 - # def apply_soft_constraints( - # self, - # obj: torch.Tensor, - # ) -> torch.Tensor: - # """ - # TODO: Already in BaseConstraints class. - # Apply soft constraints to the object model. - - # Only soft constraint here is the TV loss. - # """ - # return NotImplementedError("Subclasses must implement this method.") - @abstractmethod def get_tv_loss(self, **kwargs) -> torch.Tensor: """ diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index e0e72f69..b631b2e1 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -12,7 +12,7 @@ from quantem.core.utils.tomography_utils import ( torch_phase_cross_correlation, ) -from quantem.tomography.dataset_models import DatasetModelType +from quantem.tomography.dataset_models import DatasetModelType, DefaultTomographyDatasetConstraints from quantem.tomography.logger_tomography import LoggerTomography from quantem.tomography.object_models import ( DefaultConstraintsTomography, @@ -57,6 +57,7 @@ def reconstruct( optimizer_params: dict | None = None, scheduler_params: dict | None = None, constraints: dict = {}, # TODO: What to pass into the constraints? + dataset_constraints: dict = {}, num_samples_per_ray: int | list[tuple[int, int]] = 1, profiling_mode: bool = False, val_fraction: float = 0.0, @@ -104,6 +105,8 @@ def reconstruct( if constraints is not None: self.obj_model.constraints = cast(DefaultConstraintsTomography, constraints) + if dataset_constraints is not None: + self.dset.constraints = cast(DefaultTomographyDatasetConstraints, dataset_constraints) # Setting up DDP if not hasattr(self, "dataloader") or reset_dset is not None: if reset_dset is not None: @@ -112,6 +115,7 @@ def reconstruct( self.dset = reset_dset self.dset.to(self.device) + if optimizer_params is not None: self.optimizer_params = optimizer_params self.set_optimizers() @@ -182,6 +186,7 @@ def reconstruct( batch_consistency_loss = loss_func(pred, target) soft_constraints_loss = self.obj_model.apply_soft_constraints(all_coords, pred) + soft_constraints_loss += self.dset.apply_soft_constraints() epoch_soft_constraint_loss += soft_constraints_loss.detach() diff --git a/src/quantem/tomography/utils.py b/src/quantem/tomography/utils.py index 9a009002..08093925 100644 --- a/src/quantem/tomography/utils.py +++ b/src/quantem/tomography/utils.py @@ -67,3 +67,31 @@ def transform_slice(mag_slice): rotated_mags = torch.vmap(transform_slice)(mags) return rotated_mags.permute(1, 2, 3, 0) + + +def tv_loss_1d(x: torch.Tensor, reduction: str = "mean") -> torch.Tensor: + """ + 1D Total Variation Loss. + + Encourages piecewise smoothness by penalizing differences between + adjacent elements. + + Args: + x: Input tensor of shape (N, C, L) or (N, L) or (L,) + reduction: 'mean' | 'sum' | 'none' + + Returns: + Scalar loss (or per-sample tensor if reduction='none') + """ + # Difference between adjacent elements along the last dimension + diff = x[..., 1:] - x[..., :-1] # shape: (..., L-1) + tv = diff.abs() # L1 variant ← most common + + if reduction == "mean": + return tv.mean() + elif reduction == "sum": + return tv.sum() + elif reduction == "none": + return tv + else: + raise ValueError(f"Unknown reduction: {reduction!r}") From a535083de92ddc77c79560f231d7affe5c0ff760 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 2 Mar 2026 16:16:21 -0800 Subject: [PATCH 120/335] Reworded in top-level tomography: obj_constraints and dset_constraints --- src/quantem/core/ml/ddp.py | 2 -- src/quantem/tomography/object_models.py | 1 + src/quantem/tomography/tomography.py | 14 ++++++++------ 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/quantem/core/ml/ddp.py b/src/quantem/core/ml/ddp.py index 66b23273..d081f757 100644 --- a/src/quantem/core/ml/ddp.py +++ b/src/quantem/core/ml/ddp.py @@ -38,10 +38,8 @@ def setup_distributed(self, device: str | torch.device | None = None): if torch.cuda.is_available(): device = torch.device("cuda:0" if device is None else device) torch.cuda.set_device(device.index) - print("Single GPU training") else: device = torch.device("cpu") - print("CPU training") if device.type == "cuda": torch.backends.cudnn.benchmark = True diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 4a67aeb6..2f57da24 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -287,6 +287,7 @@ def __init__( device: str = "cpu", rng: np.random.Generator | int | None = None, model: nn.Module | None = None, + _token: object | None = None, ): super().__init__( shape=shape, diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index b631b2e1..c7e9462c 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -56,8 +56,8 @@ def reconstruct( reset: bool = False, optimizer_params: dict | None = None, scheduler_params: dict | None = None, - constraints: dict = {}, # TODO: What to pass into the constraints? - dataset_constraints: dict = {}, + obj_constraints: dict = {}, # TODO: What to pass into the constraints? + dset_constraints: dict = {}, num_samples_per_ray: int | list[tuple[int, int]] = 1, profiling_mode: bool = False, val_fraction: float = 0.0, @@ -102,11 +102,11 @@ def reconstruct( if new_scheduler: self.set_schedulers(self.scheduler_params, num_iter=num_iter) - if constraints is not None: - self.obj_model.constraints = cast(DefaultConstraintsTomography, constraints) + if obj_constraints is not None: + self.obj_model.constraints = cast(DefaultConstraintsTomography, obj_constraints) - if dataset_constraints is not None: - self.dset.constraints = cast(DefaultTomographyDatasetConstraints, dataset_constraints) + if dset_constraints is not None: + self.dset.constraints = cast(DefaultTomographyDatasetConstraints, dset_constraints) # Setting up DDP if not hasattr(self, "dataloader") or reset_dset is not None: if reset_dset is not None: @@ -346,6 +346,7 @@ def from_models( logger=logger, device=device, rng=rng, + _token=cls._token, ) def reconstruct( @@ -441,6 +442,7 @@ def _reconstruction_epoch( theta=self.dset.tilt_angles, device=self.device, circle=True, + filter_name=None, ) normalization[normalization == 0] = 1e-6 From 2e87529c4ac8b813b166f4a67bd05b31b357dc07 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 2 Mar 2026 16:23:48 -0800 Subject: [PATCH 121/335] Added pbar to INR reconstruction with verbose option. Deleted print statements (Add an option for HPC, or rely on TensorBoard is enough?) --- src/quantem/tomography/tomography.py | 24 +++++++++++++---------- src/quantem/tomography/tomography_base.py | 10 ++++++++++ 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index c7e9462c..ff8523dd 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -37,6 +37,7 @@ def from_models( obj_model: ObjectINR, logger: LoggerTomography | None = None, device: str = "cuda", + verbose: int | bool = True, rng: np.random.Generator | int | None = None, ) -> Self: return cls( @@ -45,6 +46,7 @@ def from_models( logger=logger, device=device, rng=rng, + verbose=verbose, _token=cls._token, ) @@ -143,8 +145,8 @@ def reconstruct( loss_func = get_loss_module(name=loss_type, dtype=self.obj_model.dtype, **loss_func_kwargs) - print(f"N: {N}, num_samples_per_ray: {num_samples_per_ray}") - for a0 in range(num_iter): + pbar = tqdm(range(num_iter), desc="Reconstruction", disable=not self.verbose) + for a0 in pbar: consistency_loss = 0.0 total_loss = 0.0 epoch_soft_constraint_loss = 0.0 @@ -160,8 +162,6 @@ def reconstruct( else: curr_num_samples_per_ray = num_samples_per_ray - if self.global_rank == 0: - print(f"curr_num_samples_per_ray: {curr_num_samples_per_ray}") for batch_idx, batch in enumerate(self.dataloader): self.zero_grad_all() with torch.autocast( @@ -206,6 +206,10 @@ def reconstruct( consistency_loss = consistency_loss.item() / len(self.dataloader) epoch_soft_constraint_loss = epoch_soft_constraint_loss.item() / len(self.dataloader) + pbar.set_description( + f"Reconstruction | Loss: {total_loss:.4f}, Consistency Loss: {consistency_loss:.4f}, Soft Constraint Loss: {epoch_soft_constraint_loss:.4f}" + ) + if self.val_dataloader is not None: print("Validating...") self.obj_model.model.eval() @@ -250,11 +254,9 @@ def reconstruct( total_loss, consistency_loss, epoch_soft_constraint_loss = metrics.tolist() - if self.global_rank == 0: - print(f"Total Loss: {total_loss:.4f}, Consistency Loss: {consistency_loss:.4f}") - - if self.val_dataloader: - print(f"Validation loss: {avg_val_loss:4f}") + pbar.set_description( + f"Reconstruction | Loss: {total_loss:.4f}, Consistency Loss: {consistency_loss:.4f}, Soft Constraint Loss: {epoch_soft_constraint_loss:.4f}" + ) self._epoch_losses.append(total_loss) self._consistency_losses.append(consistency_loss) @@ -338,6 +340,7 @@ def from_models( obj_model: ObjectPixelated, logger: LoggerTomography | None = None, device: str = "cuda", + verbose: int | bool = True, rng: np.random.Generator | int | None = None, ) -> Self: return cls( @@ -346,6 +349,7 @@ def from_models( logger=logger, device=device, rng=rng, + verbose=verbose, _token=cls._token, ) @@ -357,7 +361,7 @@ def reconstruct( inline_alignment: bool = False, smoothing_sigma: float | None = None, ): - pbar = tqdm(range(num_iter), desc=f"{mode} Reconstruction") + pbar = tqdm(range(num_iter), desc=f"{mode} Reconstruction", disable=not self.verbose) if mode == "sirt" or mode == "fbp": proj_forward = torch.zeros_like(self.dset.tilt_stack).permute(2, 0, 1) else: diff --git a/src/quantem/tomography/tomography_base.py b/src/quantem/tomography/tomography_base.py index def4867f..f4d80096 100644 --- a/src/quantem/tomography/tomography_base.py +++ b/src/quantem/tomography/tomography_base.py @@ -29,6 +29,7 @@ def __init__( logger: LoggerTomography | None = None, device: str = "cuda", rng: np.random.Generator | int | None = None, + verbose: int | bool = True, _token: object | None = None, ): if _token is not self._token: @@ -38,6 +39,7 @@ def __init__( self.obj_model = obj_model self.dset = dset + self.verbose = verbose self.rng = rng self.device = device self.logger = logger @@ -67,6 +69,14 @@ def dset(self, new_dset: DatasetModelType): raise TypeError(f"dset should be a TomographyDataset, got {type(new_dset)}") self._dset = new_dset + @property + def verbose(self) -> int | bool: + return self._verbose + + @verbose.setter + def verbose(self, verbose: int | bool): + self._verbose = verbose + @property def obj_model(self) -> ObjectModelType: return self._obj_model From bd9dd80749a42f9700733240973c6343a9883209 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 2 Mar 2026 16:26:08 -0800 Subject: [PATCH 122/335] Deleted some more unnecessary prints in the logger. --- src/quantem/tomography/logger_tomography.py | 4 ---- src/quantem/tomography/tomography.py | 6 ++++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/quantem/tomography/logger_tomography.py b/src/quantem/tomography/logger_tomography.py index c6726dac..821f1814 100644 --- a/src/quantem/tomography/logger_tomography.py +++ b/src/quantem/tomography/logger_tomography.py @@ -56,8 +56,6 @@ def log_iter_images( z3_vals = dataset_model.z3_params.detach().cpu().numpy() shifts_vals = dataset_model.shifts_params.detach().cpu().numpy() - print("Logging volume...") - for channel in range(pred_volume.shape[0]): self.log_image( f"volume/sum_z_{channel}", pred_volume[channel].sum(axis=0), iter, logger_cmap @@ -70,7 +68,6 @@ def log_iter_images( ) # Plotting z1 and z3 vals - print("Plotting z1 and z3 angles...") fig, ax = plt.subplots() ax.plot(z1_vals, label="Z1") ax.plot(z3_vals, label="Z3") @@ -82,7 +79,6 @@ def log_iter_images( plt.close(fig) # Plotting shifts - print("Plotting shifts...") fig, ax = plt.subplots() ax.plot(shifts_vals[:, 0], label="Shifts X") ax.plot(shifts_vals[:, 1], label="Shifts Y") diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index ff8523dd..9a470abe 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -269,11 +269,9 @@ def reconstruct( self.logger.log_images_every > 0 and self.num_epochs % self.logger.log_images_every == 0 ): - print("Creating volume...") pred_full = self.obj_model.create_volume(return_vol=True) if self.global_rank == 0: - print("Logging images...") self.logger.log_iter_images( pred_volume=pred_full, dataset_model=self.dset, @@ -293,6 +291,10 @@ def reconstruct( self.logger.flush() + pbar.set_description( + f"Reconstruction | Loss: {total_loss:.4f}, Consistency Loss: {consistency_loss:.4f}, Soft Constraint Loss: {epoch_soft_constraint_loss:.4f} | Logger Updated" + ) + # --- Helper Functions --- def save_volume(self, path: str = "recon_volume.npz", overwrite: bool = False): From c7a6bda6444fdff8d551bad2948a0c89648cfe5e Mon Sep 17 00:00:00 2001 From: cophus Date: Mon, 2 Mar 2026 16:50:29 -0800 Subject: [PATCH 123/335] adding numpy math functions --- src/quantem/core/datastructures/vector.py | 137 ++++++++++++++++++++++ tests/datastructures/test_vector.py | 88 ++++++++++++++ 2 files changed, 225 insertions(+) diff --git a/src/quantem/core/datastructures/vector.py b/src/quantem/core/datastructures/vector.py index a57ef7dc..12ce2801 100644 --- a/src/quantem/core/datastructures/vector.py +++ b/src/quantem/core/datastructures/vector.py @@ -89,6 +89,8 @@ class Vector(AutoSerialize): ... ) """ + __array_priority__ = 1000 + def __init__( self, shape: tuple[int, ...], @@ -494,6 +496,39 @@ def __setitem__(self, idx: Any, value: Any) -> None: target = self[idx] target._assign(value) + def __array_ufunc__(self, ufunc: Any, method: str, *inputs: Any, **kwargs: Any) -> Any: + """Apply supported NumPy ufuncs elementwise. + + Supported operations are limited to elementwise ``__call__`` ufuncs. The + result preserves the current selection shape and fields. + """ + if method != "__call__": + return NotImplemented + + out = kwargs.get("out") + if out is not None: + return NotImplemented + + vector_inputs = [value for value in inputs if isinstance(value, Vector)] + if not vector_inputs: + return NotImplemented + + template = vector_inputs[0] + row_counts = template.row_counts() + total_rows = sum(row_counts) + + for other in vector_inputs[1:]: + if other.shape != template.shape: + raise ValueError("Vector ufunc inputs must have matching fixed-grid shapes.") + if other.num_fields != template.num_fields: + raise ValueError("Vector ufunc inputs must have matching field counts.") + if other.row_counts() != row_counts: + raise ValueError("Vector ufunc inputs must have matching per-cell row counts.") + + flat_inputs = [_normalize_ufunc_input(value, total_rows, template.num_fields) for value in inputs] + result = ufunc(*flat_inputs, **kwargs) + return _wrap_ufunc_result(template, result, row_counts) + def __add__(self, other: Any) -> "Vector": return self._binary_op(other, np.add) @@ -506,6 +541,15 @@ def __mul__(self, other: Any) -> "Vector": def __truediv__(self, other: Any) -> "Vector": return self._binary_op(other, np.divide) + def __floordiv__(self, other: Any) -> "Vector": + return self._binary_op(other, np.floor_divide) + + def __mod__(self, other: Any) -> "Vector": + return self._binary_op(other, np.mod) + + def __pow__(self, other: Any) -> "Vector": + return self._binary_op(other, np.power) + def __radd__(self, other: Any) -> "Vector": return self._binary_op(other, np.add, reverse=True) @@ -518,6 +562,15 @@ def __rsub__(self, other: Any) -> "Vector": def __rtruediv__(self, other: Any) -> "Vector": return self._binary_op(other, np.divide, reverse=True) + def __rfloordiv__(self, other: Any) -> "Vector": + return self._binary_op(other, np.floor_divide, reverse=True) + + def __rmod__(self, other: Any) -> "Vector": + return self._binary_op(other, np.mod, reverse=True) + + def __rpow__(self, other: Any) -> "Vector": + return self._binary_op(other, np.power, reverse=True) + def __iadd__(self, other: Any) -> "Vector": self._inplace_op(other, np.add) return self @@ -534,6 +587,29 @@ def __itruediv__(self, other: Any) -> "Vector": self._inplace_op(other, np.divide) return self + def __ifloordiv__(self, other: Any) -> "Vector": + self._inplace_op(other, np.floor_divide) + return self + + def __imod__(self, other: Any) -> "Vector": + self._inplace_op(other, np.mod) + return self + + def __ipow__(self, other: Any) -> "Vector": + self._inplace_op(other, np.power) + return self + + def __neg__(self) -> "Vector": + return self._binary_op(-1, np.multiply) + + def __pos__(self) -> "Vector": + return self.copy() + + def __abs__(self) -> "Vector": + result = self.copy() + result._inplace_unary(np.abs) + return result + @property def _full_num_fields(self) -> int: return len(self._state["fields"]) @@ -746,6 +822,16 @@ def _binary_op(self, other: Any, op: Any, reverse: bool = False) -> "Vector": result._inplace_op(other, op, reverse=reverse) return result + def _inplace_unary(self, op: Any) -> None: + """Apply a unary elementwise operation in-place to the selected fields.""" + targets = self._selected_cell_indices() + field_indices = self._field_indices() + for target in targets: + cell = self._cell_matrix(int(target)) + lhs = cell[:, field_indices] + if lhs.shape[0] > 0: + cell[:, field_indices] = op(lhs) + def _inplace_op(self, other: Any, op: Any, reverse: bool = False) -> None: """Apply elementwise arithmetic in-place to the selected fields.""" targets = self._selected_cell_indices() @@ -1072,6 +1158,57 @@ def _broadcast_field_values(value: Any, total_rows: int, num_fields: int) -> NDA ) from exc +def _normalize_ufunc_input(value: Any, total_rows: int, num_fields: int) -> Any: + """Normalize one ufunc input to flattened Vector-compatible form.""" + if isinstance(value, Vector): + return value.flatten() + if np.isscalar(value): + return value + return _broadcast_field_values(value, total_rows, num_fields) + + +def _wrap_ufunc_result( + template: Vector, + result: Any, + row_counts: list[int], +) -> Any: + """Convert elementwise ufunc output back into Vector selections.""" + if isinstance(result, tuple): + return tuple(_vector_from_flat_result(template, item, row_counts) for item in result) + return _vector_from_flat_result(template, result, row_counts) + + +def _vector_from_flat_result( + template: Vector, + values: Any, + row_counts: list[int], +) -> Vector: + """Build a Vector from flattened rowwise result data.""" + total_rows = sum(row_counts) + flat_values = _broadcast_field_values(values, total_rows, template.num_fields) + + result = Vector.from_shape( + shape=template.shape, + fields=template.fields, + units=template.units, + name=template.name, + ) + result._state["metadata"] = copy.deepcopy(template.metadata) + + if total_rows == 0: + result._state["data"] = np.empty((0, template.num_fields), dtype=flat_values.dtype) + return result + + cursor = 0 + cells: list[NDArray[np.generic]] = [] + for rows in row_counts: + cells.append(flat_values[cursor : cursor + rows].copy()) + cursor += rows + + result._replace_cells(result._selected_cell_indices(), cells) + return result + + def _is_contiguous(indices: NDArray[np.int64]) -> bool: """Return True when integer column indices form one contiguous slice.""" diff --git a/tests/datastructures/test_vector.py b/tests/datastructures/test_vector.py index f9071644..a8d6d226 100644 --- a/tests/datastructures/test_vector.py +++ b/tests/datastructures/test_vector.py @@ -149,6 +149,94 @@ def test_field_arithmetic_with_scalar_and_ndarray(self): np.array([[101.0], [202.0], [303.0], [404.0], [505.0], [606.0]]), ) + def test_power_operations(self): + v = make_line_vector() + + squared = v.select_fields("intensity") ** 2 + np.testing.assert_array_equal( + squared.flatten(), + np.array([[1.0], [4.0], [9.0], [16.0], [25.0], [36.0]]), + ) + + intensity = v.select_fields("intensity") + intensity **= 2 + np.testing.assert_array_equal( + intensity.flatten(), + np.array([[1.0], [4.0], [9.0], [16.0], [25.0], [36.0]]), + ) + + reverse = 2 ** v.select_fields("intensity") + np.testing.assert_array_equal( + reverse.flatten(), + np.array([[2.0], [16.0], [512.0], [65536.0], [33554432.0], [68719476736.0]]), + ) + + def test_unary_mod_and_floor_division_operations(self): + v = make_line_vector() + + negative = -v.select_fields("intensity") + np.testing.assert_array_equal( + negative.flatten(), + np.array([[-1.0], [-2.0], [-3.0], [-4.0], [-5.0], [-6.0]]), + ) + + absolute = abs(negative) + np.testing.assert_array_equal( + absolute.flatten(), + np.array([[1.0], [2.0], [3.0], [4.0], [5.0], [6.0]]), + ) + + floored = v.select_fields("ky") // 150 + np.testing.assert_array_equal( + floored.flatten(), + np.array([[0.0], [1.0], [2.0], [2.0], [3.0], [4.0]]), + ) + + modded = v.select_fields("ky") % 150 + np.testing.assert_array_equal( + modded.flatten(), + np.array([[100.0], [50.0], [0.0], [100.0], [50.0], [0.0]]), + ) + + ky = v.select_fields("ky") + ky //= 150 + np.testing.assert_array_equal( + ky.flatten(), + np.array([[0.0], [1.0], [2.0], [2.0], [3.0], [4.0]]), + ) + + intensity = v.select_fields("intensity") + intensity %= 2 + np.testing.assert_array_equal( + intensity.flatten(), + np.array([[1.0], [0.0], [1.0], [0.0], [1.0], [0.0]]), + ) + + def test_numpy_ufunc_support(self): + v = make_line_vector() + + sine = np.sin(v.select_fields("kx")) + np.testing.assert_allclose( + sine.flatten(), + np.sin(v.select_fields("kx").flatten()), + ) + + maximum = np.maximum(v.select_fields("intensity"), 3.0) + np.testing.assert_array_equal( + maximum.flatten(), + np.array([[3.0], [3.0], [3.0], [4.0], [5.0], [6.0]]), + ) + + frac, whole = np.modf(v.select_fields("intensity") / 2.0) + np.testing.assert_allclose( + frac.flatten(), + np.array([[0.5], [0.0], [0.5], [0.0], [0.5], [0.0]]), + ) + np.testing.assert_allclose( + whole.flatten(), + np.array([[0.0], [1.0], [1.0], [2.0], [2.0], [3.0]]), + ) + def test_field_assignment_from_vector_expression(self): v = make_line_vector() scale = 2.5 From 4ebf7c246f426ffd281c77b2b3ce5320400b0881 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 2 Mar 2026 16:59:22 -0800 Subject: [PATCH 124/335] Scientific notation for the outputs in the progress bar --- src/quantem/tomography/dataset_models.py | 6 +++--- src/quantem/tomography/tomography.py | 6 +++--- src/quantem/tomography/tomography_lite.py | 9 +++++---- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index cae642eb..942e7cac 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -556,12 +556,12 @@ def __init__( data_quantile = torch.quantile(data, 0.95) data = data / data_quantile - data = torch.permute(data, (2, 1, 0)) + data = torch.permute(data, (0, 3, 2, 1)) # data = torch.flip(data, dims=(2,)) self.volume = data.cpu() - self.N = pretrain_target.shape[0] # Assumes cubic volume. - self.total_samples = pretrain_target.shape[0] ** 3 + self.N = pretrain_target.shape[1] # Assumes cubic volume. + self.total_samples = pretrain_target.shape[1] ** 3 coords_1d = torch.linspace(-1, 1, self.N) x, y, z = torch.meshgrid(coords_1d, coords_1d, coords_1d, indexing="ij") diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 9a470abe..6c6a3b09 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -60,7 +60,7 @@ def reconstruct( scheduler_params: dict | None = None, obj_constraints: dict = {}, # TODO: What to pass into the constraints? dset_constraints: dict = {}, - num_samples_per_ray: int | list[tuple[int, int]] = 1, + num_samples_per_ray: int | list[tuple[int, int]] = None, profiling_mode: bool = False, val_fraction: float = 0.0, loss_type: Literal[ @@ -145,7 +145,7 @@ def reconstruct( loss_func = get_loss_module(name=loss_type, dtype=self.obj_model.dtype, **loss_func_kwargs) - pbar = tqdm(range(num_iter), desc="Reconstruction", disable=not self.verbose) + pbar = tqdm(range(num_iter), disable=not self.verbose) for a0 in pbar: consistency_loss = 0.0 total_loss = 0.0 @@ -255,7 +255,7 @@ def reconstruct( total_loss, consistency_loss, epoch_soft_constraint_loss = metrics.tolist() pbar.set_description( - f"Reconstruction | Loss: {total_loss:.4f}, Consistency Loss: {consistency_loss:.4f}, Soft Constraint Loss: {epoch_soft_constraint_loss:.4f}" + f"Reconstruction | Loss: {total_loss:.5e}, Consistency Loss: {consistency_loss:.5e}, Soft Constraint Loss: {epoch_soft_constraint_loss:.5e}" ) self._epoch_losses.append(total_loss) diff --git a/src/quantem/tomography/tomography_lite.py b/src/quantem/tomography/tomography_lite.py index 6bc6995e..032762af 100644 --- a/src/quantem/tomography/tomography_lite.py +++ b/src/quantem/tomography/tomography_lite.py @@ -82,11 +82,12 @@ def reconstruct( learn_pose: bool = True, warmup_routine: bool = True, scheduler_type: Literal[ - "exp", "cyclic", "plateau", "cosine_annealing", "linear", "full_warmup" + "exp", "cyclic", "plateau", "cosine_annealing", "linear", "full_warmup", "none" ] = "none", scheduler_factor: float = 0.5, new_optimizers: bool = False, - constraints: dict = {}, + obj_constraints: dict = {}, + dset_constraints: dict = {}, ): if self.num_epochs == 0: opt_params = { @@ -117,7 +118,6 @@ def reconstruct( opt_params = None scheduler_params = None - constraints = constraints num_samples_per_ray = int(max(self.dset.tilt_stack.shape)) return super().reconstruct( num_iter=num_iter, @@ -127,7 +127,8 @@ def reconstruct( num_samples_per_ray=num_samples_per_ray, optimizer_params=opt_params, scheduler_params=scheduler_params, - constraints=constraints, + obj_constraints=obj_constraints, + dset_constraints=dset_constraints, ) From f2d21423854730c656cbf2a65cb46a74afe22c96 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 3 Mar 2026 11:16:40 -0800 Subject: [PATCH 125/335] Added an obj_view method in objectinr that does the transpose for comparing to conventional reconstruction methods --- src/quantem/core/ml/optimizer_mixin.py | 2 +- src/quantem/tomography/object_models.py | 10 ++++++++++ src/quantem/tomography/tomography_lite.py | 1 - 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index 92db06f1..eadc1408 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -154,7 +154,7 @@ def set_scheduler( mode=params.get("mode", "triangular2"), cycle_momentum=params.get("momentum", False), ) - elif sched_type.startswith(("plat", "reducelronplat")): + elif sched_type.startswith(("plateau", "plat", "reducelronplat")): self._scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau( optimizer, mode="min", diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 2f57da24..754b1029 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -345,6 +345,16 @@ def model(self) -> nn.Module | nn.parallel.DistributedDataParallel: def obj(self) -> torch.Tensor: return self._obj + @property + def obj_view(self) -> np.ndarray: + """ + Returns the object as a view of the x, y, z axes. + + Matches the axes of conventionally reconstructed objects, this is the object that will be saved. + """ + self.create_volume() + return self._obj.cpu().numpy()[0].transpose(0, 2, 1) + def apply_soft_constraints( self, coords: torch.Tensor, diff --git a/src/quantem/tomography/tomography_lite.py b/src/quantem/tomography/tomography_lite.py index 032762af..4a6d4c86 100644 --- a/src/quantem/tomography/tomography_lite.py +++ b/src/quantem/tomography/tomography_lite.py @@ -111,7 +111,6 @@ def reconstruct( } scheduler_params["pose"] = { "type": scheduler_type, - "factor": scheduler_factor, } else: From ceea35521884ea64dca2075f607a113367c803ae Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 3 Mar 2026 12:02:57 -0800 Subject: [PATCH 126/335] DDP errors when saving the dataloader, added a rebuild dataloader method to reload the whole object --- src/quantem/tomography/object_models.py | 4 ++-- src/quantem/tomography/tomography.py | 27 ++++++++++++++++++++++--- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 754b1029..cd8a6092 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -353,7 +353,7 @@ def obj_view(self) -> np.ndarray: Matches the axes of conventionally reconstructed objects, this is the object that will be saved. """ self.create_volume() - return self._obj.cpu().numpy()[0].transpose(0, 2, 1) + return self._obj.cpu().numpy().transpose(0, 1, 3, 2) def apply_soft_constraints( self, @@ -695,7 +695,7 @@ def to(self, device: str | torch.device): # pyright: ignore[reportIncompatibleM if isinstance(device, str): device = torch.device(device) self._device = device - # self._obj = self._obj.to(self.device) + self._model = self._model.to(device) self.reconnect_optimizer_to_parameters() diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 6c6a3b09..97af71fd 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -79,11 +79,14 @@ def reconstruct( I.e, auto-detection through the obj model type, while both share the same pose optimization. """ - # TODO: Prior to reconstruction, it is assumed that object + dataset are both in the correct devices. Need to implement a way to check this. - # Check device consistency self.obj_model.to(self.device) + # Saving batch size, num workers, and val fraction for reloading + self.batch_size = batch_size + self.num_workers = num_workers + self.val_fraction = val_fraction + if profiling_mode: if self.global_rank == 0: print("Profiling mode enabled.") @@ -307,7 +310,7 @@ def save_volume(self, path: str = "recon_volume.npz", overwrite: bool = False): f"File {path} already exists. Use overwrite=True to overwrite." ) print(f"Saving volume to {path}") - np.savez(path, volume=self.obj_model.obj.detach().cpu().numpy()) + np.savez(path, volume=self.obj_model.obj_view) if torch.distributed.is_initialized(): print("Barrier") @@ -326,8 +329,26 @@ def from_file( ) -> Self: tomography = cls._recursive_load_from_path(path) tomography.to(device) + tomography._rebuild_dataloader( + batch_size=tomography.batch_size, + num_workers=tomography.num_workers, + val_fraction=tomography.val_fraction, + ) return tomography + def _rebuild_dataloader(self, batch_size: int, num_workers: int, val_fraction: float): + """ + Rebuilds the dataloader due to persistent workers error when reloading the object. + """ + self.dataloader, self.sampler, self.val_dataloader, self.val_sampler = ( + self.setup_dataloader( + self.dset, + batch_size, + num_workers=num_workers, + val_fraction=val_fraction, + ) + ) + class TomographyConventional(TomographyBase): """ From b21fc07e3b1f2541fd728eeb7b3b1296be33cc29 Mon Sep 17 00:00:00 2001 From: Art-MC Date: Tue, 3 Mar 2026 13:21:55 -0800 Subject: [PATCH 127/335] fixing linter errors and testing that I can commit directly --- src/quantem/core/datastructures/vector.py | 72 +++++++++++------------ 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/src/quantem/core/datastructures/vector.py b/src/quantem/core/datastructures/vector.py index 12ce2801..2c0f2dbf 100644 --- a/src/quantem/core/datastructures/vector.py +++ b/src/quantem/core/datastructures/vector.py @@ -1,7 +1,7 @@ from __future__ import annotations import copy -from typing import Any, Sequence, overload +from typing import Any, Sequence, cast import numpy as np from numpy.typing import NDArray @@ -132,7 +132,9 @@ def _from_view( obj = cls.__new__(cls) obj._state = state obj._selection_shape = selection_shape - obj._selection_indices = None if selection_indices is None else selection_indices.astype(np.int64, copy=False) + obj._selection_indices = ( + None if selection_indices is None else selection_indices.astype(np.int64, copy=False) + ) obj._selected_fields = selected_fields return obj @@ -215,10 +217,7 @@ def fields(self) -> list[str]: @property def units(self) -> list[str]: """Return units for the selected fields.""" - lookup = { - field: unit - for field, unit in zip(self._state["fields"], self._state["units"]) - } + lookup = {field: unit for field, unit in zip(self._state["fields"], self._state["units"])} return [lookup[field] for field in self.fields] @property @@ -294,7 +293,9 @@ def copy(self) -> "Vector": metadata=copy.deepcopy(self.metadata), ) target_cells = copied._selected_cell_indices() - source_arrays = [self._selected_cell_matrix(index).copy() for index in self._selected_cell_indices()] + source_arrays = [ + self._selected_cell_matrix(index).copy() for index in self._selected_cell_indices() + ] copied._replace_cells(target_cells, source_arrays) return copied @@ -434,7 +435,11 @@ def add_fields( return target = self.select_fields(*new_fields) - if len(new_fields) > 1 and isinstance(values, (list, tuple)) and len(values) == len(new_fields): + if ( + len(new_fields) > 1 + and isinstance(values, (list, tuple)) + and len(values) == len(new_fields) + ): for field, value in zip(new_fields, values): target.select_fields(field)[...] = value else: @@ -462,13 +467,12 @@ def remove_fields(self, names: str | Sequence[str]) -> None: self._state["data"] = self._state["data"][:, keep] if self._selected_fields is not None: - self._selected_fields = tuple(field for field in self._selected_fields if field in self._state["fields"]) + self._selected_fields = tuple( + field for field in self._selected_fields if field in self._state["fields"] + ) if len(self._selected_fields) == len(self._state["fields"]): self._selected_fields = None - @overload - def __getitem__(self, idx: Any) -> "Vector": ... - def __getitem__(self, idx: Any) -> "Vector": """Return a fixed-grid selection as another Vector view.""" if _looks_like_field_selector(idx): @@ -525,7 +529,9 @@ def __array_ufunc__(self, ufunc: Any, method: str, *inputs: Any, **kwargs: Any) if other.row_counts() != row_counts: raise ValueError("Vector ufunc inputs must have matching per-cell row counts.") - flat_inputs = [_normalize_ufunc_input(value, total_rows, template.num_fields) for value in inputs] + flat_inputs = [ + _normalize_ufunc_input(value, total_rows, template.num_fields) for value in inputs + ] result = ufunc(*flat_inputs, **kwargs) return _wrap_ufunc_result(template, result, row_counts) @@ -664,7 +670,9 @@ def _selected_cell_matrix(self, linear_index: int) -> NDArray[np.generic]: return cell[:, int(cols[0]) : int(cols[-1]) + 1] return cell[:, cols].copy() - def _replace_cells(self, targets: NDArray[np.int64], arrays: Sequence[NDArray[np.generic]]) -> None: + def _replace_cells( + self, targets: NDArray[np.int64], arrays: Sequence[NDArray[np.generic]] + ) -> None: """Replace complete cells in the compact row buffer. Whole-cell replacement is implemented by appending the new payload rows to @@ -759,9 +767,7 @@ def _assign_full_cells(self, value: Any) -> None: if len(targets) != len(source_cells): raise ValueError(f"Expected {len(targets)} cells, got {len(source_cells)}") if value.num_fields != self.num_fields: - raise ValueError( - f"Expected {self.num_fields} fields, got {value.num_fields}" - ) + raise ValueError(f"Expected {self.num_fields} fields, got {value.num_fields}") arrays = [value._selected_cell_matrix(index).copy() for index in source_cells] self._replace_cells(targets, arrays) return @@ -787,9 +793,7 @@ def _assign_selected_fields(self, value: Any) -> None: if len(targets) != len(source_cells): raise ValueError(f"Expected {len(targets)} cells, got {len(source_cells)}") if value.num_fields != self.num_fields: - raise ValueError( - f"Expected {self.num_fields} fields, got {value.num_fields}" - ) + raise ValueError(f"Expected {self.num_fields} fields, got {value.num_fields}") source_counts = [value._cell_row_count(index) for index in source_cells] if row_counts != source_counts: raise ValueError("Per-cell row counts must match for field-selected assignment.") @@ -903,9 +907,10 @@ def _normalize_select_field_args(*field_names: str | Sequence[str]) -> tuple[str if len(field_names) == 1 and not isinstance(field_names[0], str): return _normalize_field_names(field_names[0]) if not all(isinstance(name, str) for name in field_names): - raise TypeError("select_fields(...) expects field names as strings or one sequence of strings.") - return _normalize_field_names(field_names) - + raise TypeError( + "select_fields(...) expects field names as strings or one sequence of strings." + ) + return _normalize_field_names(cast(Sequence[str], field_names)) def _normalize_units(units: str | Sequence[str] | None, count: int) -> list[str]: @@ -922,7 +927,6 @@ def _normalize_units(units: str | Sequence[str] | None, count: int) -> list[str] return normalized - def _looks_like_field_selector(idx: Any) -> bool: """Return True for indices that look like field selection by mistake.""" if isinstance(idx, str): @@ -934,7 +938,6 @@ def _looks_like_field_selector(idx: Any) -> bool: return False - def _coerce_cell_array(value: Any, num_fields: int) -> NDArray[np.generic]: """Normalize a single-cell payload to shape ``(n_rows, num_fields)``.""" if isinstance(value, Vector): @@ -960,7 +963,6 @@ def _coerce_cell_array(value: Any, num_fields: int) -> NDArray[np.generic]: return array - def _normalize_nested_data(data: list[Any]) -> tuple[tuple[int, ...], list[NDArray[np.generic]]]: """Normalize nested input data into ``(shape, flat_cell_arrays)``.""" if not isinstance(data, list): @@ -970,7 +972,6 @@ def _normalize_nested_data(data: list[Any]) -> tuple[tuple[int, ...], list[NDArr return _flatten_fixed_grid(data) - def _flatten_fixed_grid(node: Any) -> tuple[tuple[int, ...], list[NDArray[np.generic]]]: """Recursively flatten nested fixed-grid input into row-major cell order.""" if isinstance(node, np.ndarray): @@ -996,7 +997,6 @@ def _flatten_fixed_grid(node: Any) -> tuple[tuple[int, ...], list[NDArray[np.gen return (len(node),) + child_shape, cells - def _looks_like_cell_rows(node: Sequence[Any]) -> bool: """Return True when a sequence should be interpreted as cell rows, not grid nesting.""" if len(node) == 0: @@ -1004,7 +1004,6 @@ def _looks_like_cell_rows(node: Sequence[Any]) -> bool: return all(_is_row_like(item) for item in node) - def _is_row_like(item: Any) -> bool: """Return True for a single row of scalar values.""" if isinstance(item, np.ndarray): @@ -1014,7 +1013,6 @@ def _is_row_like(item: Any) -> bool: return all(np.isscalar(value) for value in item) - def _coerce_inferred_cell_array(value: Any) -> NDArray[np.generic]: """Infer a 2D cell array from row-like input during ``from_data``.""" array = np.asarray(value) @@ -1029,7 +1027,6 @@ def _coerce_inferred_cell_array(value: Any) -> NDArray[np.generic]: return array - def _select_linear_indices( shape: tuple[int, ...], current_indices: NDArray[np.int64], @@ -1066,13 +1063,15 @@ def _select_linear_indices( value = int(current_grid[scalar_key]) return (), np.array([value], dtype=np.int64) - mesh_inputs = [positions if not is_scalar else positions[:1] for positions, is_scalar in zip(axis_positions, scalar_axes)] + mesh_inputs = [ + positions if not is_scalar else positions[:1] + for positions, is_scalar in zip(axis_positions, scalar_axes) + ] grids = np.meshgrid(*mesh_inputs, indexing="ij") selected = np.asarray(current_grid[tuple(grids)], dtype=np.int64).reshape(-1) return tuple(out_shape), selected - def _normalize_index_tuple(idx: Any, ndim: int) -> tuple[Any, ...]: """Normalize fixed-grid indexing to a full ``ndim``-length tuple.""" if idx is Ellipsis: @@ -1094,7 +1093,6 @@ def _normalize_index_tuple(idx: Any, ndim: int) -> tuple[Any, ...]: return idx - def _positions_for_axis(axis_index: Any, size: int) -> tuple[NDArray[np.int64], bool]: """Resolve one axis index into concrete positions and scalar-vs-vector shape behavior.""" if isinstance(axis_index, (bool, np.bool_)): @@ -1121,7 +1119,9 @@ def _positions_for_axis(axis_index: Any, size: int) -> tuple[NDArray[np.int64], if array.ndim != 1: raise IndexError("Full-grid boolean masks are not supported.") if array.shape[0] != size: - raise IndexError(f"Boolean mask length {array.shape[0]} does not match axis length {size}") + raise IndexError( + f"Boolean mask length {array.shape[0]} does not match axis length {size}" + ) return np.flatnonzero(array).astype(np.int64, copy=False), False if array.ndim != 1: @@ -1138,7 +1138,6 @@ def _positions_for_axis(axis_index: Any, size: int) -> tuple[NDArray[np.int64], return positions, False - def _broadcast_field_values(value: Any, total_rows: int, num_fields: int) -> NDArray[np.generic]: """Broadcast array-like input to flattened rowwise assignment shape.""" array = np.asarray(value) @@ -1209,7 +1208,6 @@ def _vector_from_flat_result( return result - def _is_contiguous(indices: NDArray[np.int64]) -> bool: """Return True when integer column indices form one contiguous slice.""" if indices.size <= 1: From bf34a1e79ba42fc7e4c6776d0732efea2e8cd765 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 3 Mar 2026 13:50:13 -0800 Subject: [PATCH 128/335] Updates from comments from PR from quantem-tutorials --- src/quantem/tomography/object_models.py | 19 +++++++++++++------ src/quantem/tomography/tomography.py | 19 ++++++++++++++++++- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index cd8a6092..2fc0c58c 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -7,6 +7,7 @@ import torch import torch.distributed as dist import torch.nn as nn +from tqdm.auto import tqdm from quantem.core.io.serialize import AutoSerialize from quantem.core.ml.constraints import BaseConstraints, Constraints @@ -218,12 +219,18 @@ def from_array( # --- Properties ---- @property def obj(self) -> torch.Tensor: - return self.apply_hard_constraints(self._obj) + return self.apply_hard_constraints( + self._obj + ) # TODO: Normalization factor to ensure object agrees with INR. @obj.setter def obj(self, obj: torch.Tensor): self._obj = obj + @property + def obj_view(self) -> np.ndarray: + return self.obj.cpu().unsqueeze(0).numpy() + @property def shape(self) -> tuple[int, int, int]: return self._shape @@ -495,8 +502,7 @@ def pretrain( optimizer_params: dict | None = None, scheduler_params: dict | None = None, loss_fn: Callable | str = "l1", - apply_constraints: bool = False, - show: bool = True, + verbose: bool = True, ): """ Pretrain the INR model to fit target volume. @@ -526,14 +532,14 @@ def pretrain( self._pretrain( num_iters=num_iters, loss_fn=loss_fn, - apply_constraints=apply_constraints, + verbose=verbose, ) def _pretrain( self, num_iters: int, loss_fn: Callable, - apply_constraints: bool, + verbose: bool, ): if self.optimizer is None: raise RuntimeError("Optimizer not set. Call set_optimizer() first.") @@ -544,7 +550,8 @@ def _pretrain( optimizer = self.optimizer scheduler = self.scheduler - for a0 in range(num_iters): + pbar = tqdm(range(num_iters), desc="Pretraining", disable=not verbose) + for a0 in pbar: epoch_loss = 0 for batch_idx, batch in enumerate[Any](self.pretraining_dataloader): coords = batch["coords"].to(self.device, non_blocking=True) diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 97af71fd..b8a7c6a4 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -1,5 +1,6 @@ import os -from typing import Literal, Self, cast +from pathlib import Path +from typing import Literal, Self, Sequence, cast import numpy as np import torch @@ -349,6 +350,22 @@ def _rebuild_dataloader(self, batch_size: int, num_workers: int, val_fraction: f ) ) + def save( + self, + path: str | Path, + mode: Literal["w", "o"] = "w", + store: Literal["auto", "zip", "dir"] = "auto", + skip: str | type | Sequence[str | type] = ["dataloader"], + compression_level: int | None = 4, + ) -> None: + super(Tomography, self).save( + path=path, + mode=mode, + store=store, + skip=skip, + compression_level=compression_level, + ) + class TomographyConventional(TomographyBase): """ From d372c1ff5bccd6e8d27ee8c586e9c37d21c16793 Mon Sep 17 00:00:00 2001 From: Art-MC Date: Tue, 3 Mar 2026 16:13:12 -0800 Subject: [PATCH 129/335] removing unecessary methods, changing dtype to Any instead of np.generic --- src/quantem/core/datastructures/vector.py | 227 +++++++++++----------- tests/datastructures/test_vector.py | 22 ++- 2 files changed, 125 insertions(+), 124 deletions(-) diff --git a/src/quantem/core/datastructures/vector.py b/src/quantem/core/datastructures/vector.py index 2c0f2dbf..4470ad75 100644 --- a/src/quantem/core/datastructures/vector.py +++ b/src/quantem/core/datastructures/vector.py @@ -1,7 +1,7 @@ from __future__ import annotations import copy -from typing import Any, Sequence, cast +from typing import Any, Sequence import numpy as np from numpy.typing import NDArray @@ -90,6 +90,7 @@ class Vector(AutoSerialize): """ __array_priority__ = 1000 + _token = object() def __init__( self, @@ -98,7 +99,12 @@ def __init__( units: Sequence[str] | None = None, name: str | None = None, metadata: dict[str, Any] | None = None, + _token: object | None = None, ) -> None: + if _token is not self._token: + raise RuntimeError( + "Use Vector.from_shape() or Vector.from_data() to instantiate this class." + ) root_shape = validate_shape(shape) root_fields = validate_fields(list(fields)) root_units = validate_vector_units( @@ -146,59 +152,50 @@ def from_shape( fields: Sequence[str] | None = None, units: Sequence[str] | None = None, name: str | None = None, + metadata: dict[str, Any] | None = None, ) -> "Vector": """Create an empty Vector with the given fixed-grid shape and fields.""" - if fields is not None: - root_fields = validate_fields(list(fields)) - if num_fields is not None and len(root_fields) != num_fields: - raise ValueError( - f"num_fields ({num_fields}) does not match length of fields ({len(root_fields)})" - ) - elif num_fields is not None: - count = validate_num_fields(num_fields) - root_fields = [f"field_{i}" for i in range(count)] - else: - raise ValueError("Must specify either 'fields' or 'num_fields'.") - - return cls(shape=shape, fields=root_fields, units=units, name=name) + fields = _resolve_fields(fields, num_fields, None) + return cls( + shape=shape, + fields=fields, + units=units, + name=name, + metadata=metadata, + _token=cls._token, + ) @classmethod def from_data( cls, - data: list[Any], + data: Sequence[Any], num_fields: int | None = None, fields: Sequence[str] | None = None, units: Sequence[str] | None = None, name: str | None = None, + metadata: dict[str, Any] | None = None, ) -> "Vector": """Create a Vector from nested fixed-grid data. The outer nesting defines the fixed-grid shape. Each leaf must coerce to a 2D cell array with consistent field count across all cells. """ - root_shape, cell_arrays = _normalize_nested_data(data) + if not isinstance(data, (list, tuple)): + raise TypeError(f"Data must be a list or tuple, got {type(data)}") + root_shape, cell_arrays = _flatten_fixed_grid(data) if len(data) > 0 else ((0,), []) inferred_counts = {array.shape[1] for array in cell_arrays} if len(inferred_counts) > 1: raise ValueError("All cell arrays must have the same number of fields.") inferred_fields = cell_arrays[0].shape[1] if cell_arrays else 0 - if fields is not None: - root_fields = validate_fields(list(fields)) - if len(root_fields) != inferred_fields: - raise ValueError( - f"num_fields ({inferred_fields}) does not match length of fields ({len(root_fields)})" - ) - elif num_fields is not None: - count = validate_num_fields(num_fields) - if count != inferred_fields: - raise ValueError( - f"Provided num_fields ({count}) does not match inferred ({inferred_fields})." - ) - root_fields = [f"field_{i}" for i in range(count)] - else: - root_fields = [f"field_{i}" for i in range(inferred_fields)] - - vector = cls(shape=root_shape, fields=root_fields, units=units, name=name) + vector = cls( + shape=root_shape, + fields=_resolve_fields(fields, num_fields, inferred_fields), + units=units, + name=name, + metadata=metadata, + _token=cls._token, + ) vector._replace_cells(np.arange(len(cell_arrays), dtype=np.int64), cell_arrays) return vector @@ -245,7 +242,7 @@ def metadata(self) -> dict[str, Any]: return self._state["metadata"] @property - def array(self) -> NDArray[np.generic]: + def array(self) -> NDArray[Any]: """Return the selected cell as a NumPy array. This is only valid for 0D selections. Single-field and contiguous @@ -291,6 +288,7 @@ def copy(self) -> "Vector": units=self.units, name=self.name, metadata=copy.deepcopy(self.metadata), + _token=self.__class__._token, ) target_cells = copied._selected_cell_indices() source_arrays = [ @@ -299,7 +297,7 @@ def copy(self) -> "Vector": copied._replace_cells(target_cells, source_arrays) return copied - def flatten(self) -> NDArray[np.generic]: + def flatten(self) -> NDArray[Any]: """Concatenate selected cells in row-major order. Returns a 2D array with shape ``(total_rows, num_fields)`` even for @@ -338,7 +336,16 @@ def select_fields(self, *field_names: str | Sequence[str]) -> "Vector": - ``select_fields("kx", "ky")`` - ``select_fields(["kx", "ky"])`` """ - selected = _normalize_select_field_args(*field_names) + if not field_names: + raise ValueError("At least one field name is required.") + if len(field_names) == 1 and not isinstance(field_names[0], str): + selected = _normalize_field_names(field_names[0]) + elif not all(isinstance(n, str) for n in field_names): + raise TypeError( + "select_fields(...) expects field names as strings or one sequence of strings." + ) + else: + selected = _normalize_field_names(field_names) # type: ignore[arg-type] available = set(self.fields) missing = [field for field in selected if field not in available] if missing: @@ -390,7 +397,25 @@ def compact(self) -> None: until compaction. Calling ``compact()`` makes memory usage and save size predictable at the cost of reallocating the backing buffer. """ - self._compact_storage() + data = self._state["data"] + used_rows = int(self._state["cell_lengths"].sum()) + if used_rows == 0: + self._state["data"] = np.empty((0, self._full_num_fields), dtype=data.dtype) + self._state["cell_starts"].fill(0) + return + + compacted = np.empty((used_rows, self._full_num_fields), dtype=data.dtype) + starts = np.zeros_like(self._state["cell_starts"]) + cursor = 0 + for linear_index in range(_cell_count(self._state["shape"])): + length = self._cell_row_count(linear_index) + starts[linear_index] = cursor + if length > 0: + cell = self._cell_matrix(linear_index) + compacted[cursor : cursor + length] = cell + cursor += length + self._state["data"] = compacted + self._state["cell_starts"] = starts def append_rows(self, idx: Any, rows: Any) -> None: """Append one or more rows to a single selected cell. @@ -533,7 +558,9 @@ def __array_ufunc__(self, ufunc: Any, method: str, *inputs: Any, **kwargs: Any) _normalize_ufunc_input(value, total_rows, template.num_fields) for value in inputs ] result = ufunc(*flat_inputs, **kwargs) - return _wrap_ufunc_result(template, result, row_counts) + if isinstance(result, tuple): + return tuple(_vector_from_flat_result(template, item, row_counts) for item in result) + return _vector_from_flat_result(template, result, row_counts) def __add__(self, other: Any) -> "Vector": return self._binary_op(other, np.add) @@ -642,14 +669,11 @@ def _selected_cell_indices(self) -> NDArray[np.int64]: return np.arange(_cell_count(self._state["shape"]), dtype=np.int64) return self._selection_indices - def _is_full_field_selection(self) -> bool: - return self._selected_fields is None - def _cell_row_count(self, linear_index: int) -> int: """Return the row count for one cell in the backing buffer.""" return int(self._state["cell_lengths"][linear_index]) - def _cell_matrix(self, linear_index: int) -> NDArray[np.generic]: + def _cell_matrix(self, linear_index: int) -> NDArray[Any]: """Return the full backing matrix for one cell.""" start = int(self._state["cell_starts"][linear_index]) length = int(self._state["cell_lengths"][linear_index]) @@ -657,7 +681,7 @@ def _cell_matrix(self, linear_index: int) -> NDArray[np.generic]: return self._state["data"][0:0] return self._state["data"][start : start + length] - def _selected_cell_matrix(self, linear_index: int) -> NDArray[np.generic]: + def _selected_cell_matrix(self, linear_index: int) -> NDArray[Any]: """Return one cell with the current field selection applied.""" cell = self._cell_matrix(linear_index) cols = self._field_indices() @@ -670,9 +694,7 @@ def _selected_cell_matrix(self, linear_index: int) -> NDArray[np.generic]: return cell[:, int(cols[0]) : int(cols[-1]) + 1] return cell[:, cols].copy() - def _replace_cells( - self, targets: NDArray[np.int64], arrays: Sequence[NDArray[np.generic]] - ) -> None: + def _replace_cells(self, targets: NDArray[np.int64], arrays: Sequence[NDArray[Any]]) -> None: """Replace complete cells in the compact row buffer. Whole-cell replacement is implemented by appending the new payload rows to @@ -707,12 +729,11 @@ def _replace_cells( def _expand_storage(self, num_new_fields: int) -> None: """Append new ``np.nan``-initialized columns for added fields.""" data = self._state["data"] + dtype = np.result_type(data.dtype, float) if data.shape[0] == 0: - dtype = np.result_type(data.dtype, float) self._state["data"] = np.empty((0, data.shape[1] + num_new_fields), dtype=dtype) return - dtype = np.result_type(data.dtype, float) filler = np.full((data.shape[0], num_new_fields), np.nan, dtype=dtype) self._state["data"] = np.concatenate((data.astype(dtype, copy=False), filler), axis=1) @@ -724,33 +745,11 @@ def _maybe_compact_storage(self) -> None: return if data.shape[0] <= 2 * used_rows: return - self._compact_storage() - - def _compact_storage(self) -> None: - """Rebuild the row buffer so only live rows remain.""" - data = self._state["data"] - used_rows = int(self._state["cell_lengths"].sum()) - if used_rows == 0: - self._state["data"] = np.empty((0, self._full_num_fields), dtype=data.dtype) - self._state["cell_starts"].fill(0) - return - - compacted = np.empty((used_rows, self._full_num_fields), dtype=data.dtype) - starts = np.zeros_like(self._state["cell_starts"]) - cursor = 0 - for linear_index in range(_cell_count(self._state["shape"])): - length = self._cell_row_count(linear_index) - starts[linear_index] = cursor - if length > 0: - cell = self._cell_matrix(linear_index) - compacted[cursor : cursor + length] = cell - cursor += length - self._state["data"] = compacted - self._state["cell_starts"] = starts + self.compact() def _assign(self, value: Any) -> None: """Dispatch assignment based on whether all fields or a subset are selected.""" - if self._is_full_field_selection(): + if self._selected_fields is None: self._assign_full_cells(value) else: self._assign_selected_fields(value) @@ -878,6 +877,38 @@ def _inplace_op(self, other: Any, op: Any, reverse: bool = False) -> None: cursor += rows +def _resolve_fields( + fields: Sequence[str] | None, + num_fields: int | None, + inferred: int | None, +) -> list[str]: + """Resolve field names from constructor arguments. + + ``inferred`` is the field count inferred from data; pass ``None`` when there + is no data source and explicit fields/num_fields are required. + """ + if fields is not None: + root_fields = validate_fields(list(fields)) + count = len(root_fields) + if num_fields is not None and count != num_fields: + raise ValueError( + f"num_fields ({num_fields}) does not match length of fields ({count})" + ) + if inferred is not None and count != inferred: + raise ValueError(f"num_fields ({inferred}) does not match length of fields ({count})") + return root_fields + if num_fields is not None: + count = validate_num_fields(num_fields) + if inferred is not None and count != inferred: + raise ValueError( + f"Provided num_fields ({count}) does not match inferred ({inferred})." + ) + return [f"field_{i}" for i in range(count)] + if inferred is not None: + return [f"field_{i}" for i in range(inferred)] + raise ValueError("Must specify either 'fields' or 'num_fields'.") + + def _cell_count(shape: tuple[int, ...]) -> int: """Return the number of fixed-grid cells in a shape.""" return int(np.prod(shape, dtype=np.int64)) if shape else 1 @@ -895,24 +926,6 @@ def _normalize_field_names(field_names: str | Sequence[str]) -> tuple[str, ...]: return normalized -def _normalize_select_field_args(*field_names: str | Sequence[str]) -> tuple[str, ...]: - """Normalize ``select_fields`` arguments. - - This accepts either ``select_fields("a", "b")`` or - ``select_fields(["a", "b"])`` while keeping ``add_fields`` / - ``remove_fields`` on the simpler single-argument path. - """ - if not field_names: - raise ValueError("At least one field name is required.") - if len(field_names) == 1 and not isinstance(field_names[0], str): - return _normalize_field_names(field_names[0]) - if not all(isinstance(name, str) for name in field_names): - raise TypeError( - "select_fields(...) expects field names as strings or one sequence of strings." - ) - return _normalize_field_names(cast(Sequence[str], field_names)) - - def _normalize_units(units: str | Sequence[str] | None, count: int) -> list[str]: """Normalize field units to a list matching ``count``.""" if units is None: @@ -933,12 +946,12 @@ def _looks_like_field_selector(idx: Any) -> bool: return True if isinstance(idx, tuple) and any(_looks_like_field_selector(item) for item in idx): return True - if isinstance(idx, (list, tuple)) and idx and all(isinstance(item, str) for item in idx): + if isinstance(idx, list) and idx and all(isinstance(item, str) for item in idx): return True return False -def _coerce_cell_array(value: Any, num_fields: int) -> NDArray[np.generic]: +def _coerce_cell_array(value: Any, num_fields: int) -> NDArray[Any]: """Normalize a single-cell payload to shape ``(n_rows, num_fields)``.""" if isinstance(value, Vector): if value.shape != (): @@ -963,16 +976,7 @@ def _coerce_cell_array(value: Any, num_fields: int) -> NDArray[np.generic]: return array -def _normalize_nested_data(data: list[Any]) -> tuple[tuple[int, ...], list[NDArray[np.generic]]]: - """Normalize nested input data into ``(shape, flat_cell_arrays)``.""" - if not isinstance(data, list): - raise TypeError("Data must be a list") - if not data: - return (0,), [] - return _flatten_fixed_grid(data) - - -def _flatten_fixed_grid(node: Any) -> tuple[tuple[int, ...], list[NDArray[np.generic]]]: +def _flatten_fixed_grid(node: Any) -> tuple[tuple[int, ...], list[NDArray[Any]]]: """Recursively flatten nested fixed-grid input into row-major cell order.""" if isinstance(node, np.ndarray): return (), [_coerce_inferred_cell_array(node)] @@ -984,7 +988,7 @@ def _flatten_fixed_grid(node: Any) -> tuple[tuple[int, ...], list[NDArray[np.gen return (0,), [] child_shape: tuple[int, ...] | None = None - cells: list[NDArray[np.generic]] = [] + cells: list[NDArray[Any]] = [] for child in node: shape, child_cells = _flatten_fixed_grid(child) if child_shape is None: @@ -1013,7 +1017,7 @@ def _is_row_like(item: Any) -> bool: return all(np.isscalar(value) for value in item) -def _coerce_inferred_cell_array(value: Any) -> NDArray[np.generic]: +def _coerce_inferred_cell_array(value: Any) -> NDArray[Any]: """Infer a 2D cell array from row-like input during ``from_data``.""" array = np.asarray(value) if array.ndim == 0: @@ -1138,7 +1142,7 @@ def _positions_for_axis(axis_index: Any, size: int) -> tuple[NDArray[np.int64], return positions, False -def _broadcast_field_values(value: Any, total_rows: int, num_fields: int) -> NDArray[np.generic]: +def _broadcast_field_values(value: Any, total_rows: int, num_fields: int) -> NDArray[Any]: """Broadcast array-like input to flattened rowwise assignment shape.""" array = np.asarray(value) if array.ndim == 0: @@ -1166,17 +1170,6 @@ def _normalize_ufunc_input(value: Any, total_rows: int, num_fields: int) -> Any: return _broadcast_field_values(value, total_rows, num_fields) -def _wrap_ufunc_result( - template: Vector, - result: Any, - row_counts: list[int], -) -> Any: - """Convert elementwise ufunc output back into Vector selections.""" - if isinstance(result, tuple): - return tuple(_vector_from_flat_result(template, item, row_counts) for item in result) - return _vector_from_flat_result(template, result, row_counts) - - def _vector_from_flat_result( template: Vector, values: Any, @@ -1199,7 +1192,7 @@ def _vector_from_flat_result( return result cursor = 0 - cells: list[NDArray[np.generic]] = [] + cells: list[NDArray[Any]] = [] for rows in row_counts: cells.append(flat_values[cursor : cursor + rows].copy()) cursor += rows diff --git a/tests/datastructures/test_vector.py b/tests/datastructures/test_vector.py index a8d6d226..87c88673 100644 --- a/tests/datastructures/test_vector.py +++ b/tests/datastructures/test_vector.py @@ -3,9 +3,8 @@ import numpy as np import pytest -from quantem.core.io.serialize import load from quantem.core.datastructures.vector import Vector - +from quantem.core.io.serialize import load def make_line_vector() -> Vector: @@ -22,7 +21,6 @@ def make_line_vector() -> Vector: return v - def make_grid_vector() -> Vector: v = Vector.from_shape(shape=(3, 2), fields=["intensity", "kx", "ky"]) for i in range(3): @@ -138,10 +136,16 @@ def test_field_arithmetic_with_scalar_and_ndarray(self): kx = v.select_fields("kx") kx += 10 - np.testing.assert_array_equal(v.select_fields("kx").flatten(), np.array([[20.0], [30.0], [40.0], [50.0], [60.0], [70.0]])) + np.testing.assert_array_equal( + v.select_fields("kx").flatten(), + np.array([[20.0], [30.0], [40.0], [50.0], [60.0], [70.0]]), + ) v.select_fields("kx")[...] += np.arange(6) - np.testing.assert_array_equal(v.select_fields("kx").flatten(), np.array([[20.0], [31.0], [42.0], [53.0], [64.0], [75.0]])) + np.testing.assert_array_equal( + v.select_fields("kx").flatten(), + np.array([[20.0], [31.0], [42.0], [53.0], [64.0], [75.0]]), + ) summed = v.select_fields("intensity") + v.select_fields("ky") np.testing.assert_array_equal( @@ -221,7 +225,7 @@ def test_numpy_ufunc_support(self): np.sin(v.select_fields("kx").flatten()), ) - maximum = np.maximum(v.select_fields("intensity"), 3.0) + maximum = np.maximum(v.select_fields("intensity"), 3.0) # type: ignore[arg-type] np.testing.assert_array_equal( maximum.flatten(), np.array([[3.0], [3.0], [3.0], [4.0], [5.0], [6.0]]), @@ -380,7 +384,11 @@ def test_from_data_supports_nested_fixed_grid(self): np.array([[5.0, 6.0], [7.0, 8.0], [9.0, 10.0]]), ) - with pytest.raises(TypeError, match="Data must be a list"): + tuple_data = (np.array([[1.0, 2.0]]), np.array([[3.0, 4.0]])) + tuple_outer = Vector.from_data(data=tuple_data, fields=["a", "b"]) + assert tuple_outer.shape == (2,) + + with pytest.raises(TypeError, match="Data must be a list or tuple"): Vector.from_data(data=np.array([1, 2, 3])) # type: ignore[arg-type] with pytest.raises(ValueError, match="same number of fields"): From 039c102efef7933e9e4016bae72e52c1023249ab Mon Sep 17 00:00:00 2001 From: Art-MC Date: Tue, 3 Mar 2026 16:35:27 -0800 Subject: [PATCH 130/335] adding rename_fields method --- src/quantem/core/datastructures/vector.py | 24 +++++++++++++++++++++++ tests/datastructures/test_vector.py | 21 ++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/quantem/core/datastructures/vector.py b/src/quantem/core/datastructures/vector.py index 4470ad75..7a8ef417 100644 --- a/src/quantem/core/datastructures/vector.py +++ b/src/quantem/core/datastructures/vector.py @@ -473,6 +473,30 @@ def add_fields( if self._selected_fields is not None and tuple(old_fields) == self._selected_fields: self._selected_fields = None + def rename_fields(self, mapping: dict[str, str]) -> None: + """Rename one or more fields in-place. + + Parameters + ---------- + mapping : dict + Maps each old field name to its new name, e.g. + ``{"kx": "qx", "ky": "qy"}``. + """ + old_field_set = set(self._state["fields"]) + missing = [old for old in mapping if old not in old_field_set] + if missing: + raise KeyError(f"Unknown field(s): {missing}") + new_names = list(mapping.values()) + conflicts = [n for n in new_names if n in old_field_set and n not in mapping] + if conflicts: + raise ValueError(f"New field name(s) already exist: {conflicts}") + validate_fields(new_names) + + rename = {old: new for old, new in mapping.items()} + self._state["fields"] = [rename.get(f, f) for f in self._state["fields"]] + if self._selected_fields is not None: + self._selected_fields = tuple(rename.get(f, f) for f in self._selected_fields) + def remove_fields(self, names: str | Sequence[str]) -> None: """Remove one or more fields from the full Vector schema.""" self._require_full_field_view("remove_fields") diff --git a/tests/datastructures/test_vector.py b/tests/datastructures/test_vector.py index 87c88673..954059d6 100644 --- a/tests/datastructures/test_vector.py +++ b/tests/datastructures/test_vector.py @@ -339,6 +339,27 @@ def test_add_fields_defaults_expression_and_multiple_values(self): with pytest.raises(ValueError, match="all fields are selected"): v2.select_fields("kx").add_fields("bad") + def test_rename_fields(self): + v = make_line_vector() + kx_data = v.select_fields("kx").flatten().copy() + + v.rename_fields({"kx": "qx", "ky": "qy"}) + assert v.fields == ["intensity", "qx", "qy"] + np.testing.assert_array_equal(v.select_fields("qx").flatten(), kx_data) + + # Renaming through a field-selected view updates that view's selected names + view = v.select_fields("qx") + assert view.fields == ["qx"] + view.rename_fields({"qx": "px"}) + assert view.fields == ["px"] + assert v.fields == ["intensity", "px", "qy"] + + with pytest.raises(KeyError, match="Unknown field"): + v.rename_fields({"nonexistent": "x"}) + + with pytest.raises(ValueError, match="already exist"): + v.rename_fields({"px": "intensity"}) + def test_remove_fields_preserves_remaining_data(self): v = make_line_vector() v.add_fields("extra", 1.0) From 9fce382434341ecd0b9aa56e62597f3c5fca3772 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 3 Mar 2026 17:03:28 -0800 Subject: [PATCH 131/335] Starting OptimizerParams and SchedulerParams stuff in optimizer_mixin.py --- src/quantem/core/ml/optimizer_mixin.py | 40 +++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index eadc1408..a3e3a85d 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -1,5 +1,6 @@ from abc import abstractmethod -from typing import TYPE_CHECKING, Generator, Iterator, Sequence +from dataclasses import dataclass +from typing import TYPE_CHECKING, Generator, Iterator, Literal, Sequence from quantem.core import config @@ -144,6 +145,8 @@ def set_scheduler( if sched_type == "none": self._scheduler = None + # elif sched_type._name == "cyclic": + # blah = torch.optim.lr_scheduler.CyclicLR(opt, sched_type.parse_params()) elif sched_type == "cyclic": self._scheduler = torch.optim.lr_scheduler.CyclicLR( optimizer, @@ -292,3 +295,38 @@ def reconnect_optimizer_to_parameters(self) -> None: if self._scheduler is not None and self._optimizer is not None: self._scheduler.optimizer = self._optimizer return + + +@dataclass +class OptimizerParams: + type: ( + str + | Literal[ + "adam", + "adamw", + ] + ) + + +@dataclass +class SchedulerParams: + @dataclass + class Plateau: + _name: str = "plateau" + factor: float = 0.5 + patience: int = 10 + threshold: float = 1e-3 + min_lr: float = 1e-7 + cooldown: int = 50 + + def parse_params() -> dict: + pass + + @dataclass + class Exponential: + _name: str = "exponential" + gamma: float = 0.9 + factor: float | None = 0.5 + + def parse_params() -> dict: + pass From 6f89ac055b04d582f69662967a9344cf1ff226ef Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Wed, 4 Mar 2026 11:42:08 -0800 Subject: [PATCH 132/335] More scheduler support for optimizer_mixin.py; still allows for dictionary inputs which parses it to the correct dataclass. Note that type has been renamed to name to follow convention. --- src/quantem/core/ml/optimizer_mixin.py | 296 +++++++++++++++-------- src/quantem/tomography/tomography.py | 6 +- src/quantem/tomography/tomography_opt.py | 6 +- 3 files changed, 196 insertions(+), 112 deletions(-) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index a3e3a85d..f3029101 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -1,6 +1,6 @@ from abc import abstractmethod from dataclasses import dataclass -from typing import TYPE_CHECKING, Generator, Iterator, Literal, Sequence +from typing import TYPE_CHECKING, Generator, Iterator, Literal, Protocol, Sequence from quantem.core import config @@ -11,6 +11,154 @@ import torch +@dataclass +class OptimizerParams: + type: ( + str + | Literal[ + "adam", + "adamw", + ] + ) + + +class SchedulerParams(Protocol): + """ + Nested dataclass for scheduler parameters. + """ + + @dataclass + class Plateau: + mode: Literal["min", "max"] = "min" + min_lr_factor: float = 1 / 20 + min_lr: float | None = None + factor: float = 0.5 + patience: int = 10 + threshold: float = 1e-5 + cooldown: int = 50 + _name: str = "plateau" + + def params(self, base_LR: float) -> dict: + if self.min_lr is None: + self.min_lr = self.min_lr_factor * base_LR + return { + "mode": self.mode, + "factor": self.factor, + "patience": self.patience, + "threshold": self.threshold, + "min_lr": self.min_lr, + "cooldown": self.cooldown, + } + + @dataclass + class Exponential: + gamma: float = 0.9 + factor: float | None = 0.5 + num_iter: int | None = None + _name: str = "exponential" + + def params(self, base_LR: float) -> dict: + return { + "gamma": self.gamma, + "factor": self.factor, + } + + @dataclass + class Cyclic: + base_lr_factor: float = 1 / 4 + max_lr_factor: float = 4 + base_lr: float | None = None + max_lr: float | None = None + step_size_up: int = 100 + step_size_down: int = 100 + mode: Literal["triangular2", "triangular", "exp_range"] = "triangular2" + cycle_momentum: bool = False + _name: str = "cyclic" + + def params(self, base_LR: float) -> dict: + if self.base_lr is None: + self.base_lr = self.base_lr_factor * base_LR + if self.max_lr is None: + self.max_lr = self.max_lr_factor * base_LR + return { + "base_lr": self.base_lr, + "max_lr": self.max_lr, + "step_size_up": self.step_size_up, + "step_size_down": self.step_size_down, + "mode": self.mode, + "cycle_momentum": self.cycle_momentum, + } + + @dataclass + class Linear: + total_iters: int + start_factor: float = 0.1 + end_factor: float = 1.0 + _name: str = "linear" + + def params(self, base_LR: float) -> dict: + return { + "start_factor": self.start_factor, + "end_factor": self.end_factor, + "total_iters": self.total_iters, + } + + @dataclass + class CosineAnnealing: + T_max: int + eta_min: float = 1e-7 + _name: str = "cosine_annealing" + + def params(self, base_LR: float) -> dict: + return { + "T_max": self.T_max, + "eta_min": self.eta_min, + } + + @dataclass + class NoneScheduler: + _name: str = "none" + + def params(self, base_LR: float) -> dict: + return {} + + @classmethod + def parse_dict(cls, d: dict): + """ + Parse dictionary to a scheduler params object. + """ + if d["name"] == "plateau": + d.pop("name") + return SchedulerParams.Plateau(**d) + elif d["name"] == "exponential": + d.pop("name") + return SchedulerParams.Exponential(**d) + elif d["name"] == "cyclic": + d.pop("name") + return SchedulerParams.Cyclic(**d) + elif d["name"] == "linear": + d.pop("name") + return SchedulerParams.Linear(**d) + elif d["name"] == "cosine_annealing": + d.pop("name") + return SchedulerParams.CosineAnnealing(**d) + elif d["name"] == "none": + d.pop("name") + return SchedulerParams.NoneScheduler() + else: + raise ValueError(f"Unknown scheduler type: {d['name']}") + + +SchedulerType = ( + SchedulerParams.Plateau + | SchedulerParams.Exponential + | SchedulerParams.Cyclic + | SchedulerParams.Linear + | SchedulerParams.CosineAnnealing + | SchedulerParams.NoneScheduler +) + + class OptimizerMixin: """ Mixin class for handling optimizer and scheduler management. @@ -24,7 +172,7 @@ def __init__(self): self._optimizer = None self._scheduler = None self._optimizer_params = {} - self._scheduler_params = {} + self._scheduler_params: SchedulerType = SchedulerParams.NoneScheduler() # Don't call super().__init__() in mixin classes to avoid MRO issues @property @@ -48,29 +196,18 @@ def optimizer_params(self, params: dict): self._optimizer_params = params.copy() if params else {} @property - def scheduler_params(self) -> dict: + def scheduler_params(self) -> SchedulerType: """Get the scheduler parameters.""" return self._scheduler_params @scheduler_params.setter - def scheduler_params(self, params: dict): + def scheduler_params(self, params: SchedulerType | dict): """Set the scheduler parameters.""" - if params: - if params["type"].lower() not in [ - "cyclic", - "plateau", - "exp", - "gamma", - "linear", - "cosine_annealing", - "none", - ]: - raise ValueError( - f"Unknown scheduler type: {params['type']}, expected one of ['cyclic', 'plateau', 'exp', 'gamma', 'linear', 'cosine_annealing', 'none']" - ) - self._scheduler_params = params.copy() - else: - self._scheduler_params = {} + if isinstance(params, dict): + params = SchedulerParams.parse_dict(d=params) + if not isinstance(params, SchedulerType): + raise TypeError(f"scheduler parameters must be a SchedulerType, got {type(params)}") + self._scheduler_params = params @abstractmethod def get_optimization_parameters( @@ -128,7 +265,8 @@ def set_optimizer(self, opt_params: dict | None = None) -> None: raise TypeError(f"optimizer type must be string or type, got {type(opt_type)}") def set_scheduler( - self, scheduler_params: dict | None = None, num_iter: int | None = None + self, + scheduler_params: SchedulerType | None = None, ) -> None: """Set the scheduler for this model.""" if scheduler_params is not None: @@ -138,59 +276,40 @@ def set_scheduler( self._scheduler = None return - params = self._scheduler_params - sched_type = params.get("type", "none").lower() optimizer = self._optimizer base_LR = optimizer.param_groups[0]["lr"] - if sched_type == "none": - self._scheduler = None - # elif sched_type._name == "cyclic": - # blah = torch.optim.lr_scheduler.CyclicLR(opt, sched_type.parse_params()) - elif sched_type == "cyclic": - self._scheduler = torch.optim.lr_scheduler.CyclicLR( - optimizer, - base_lr=params.get("base_lr", base_LR / 4), - max_lr=params.get("max_lr", base_LR * 4), - step_size_up=params.get("step_size_up", 100), - step_size_down=params.get("step_size_down", params.get("step_size_up", 100)), - mode=params.get("mode", "triangular2"), - cycle_momentum=params.get("momentum", False), - ) - elif sched_type.startswith(("plateau", "plat", "reducelronplat")): - self._scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau( - optimizer, - mode="min", - factor=params.get("factor", 0.5), - patience=params.get("patience", 10), - threshold=params.get("threshold", 1e-3), - min_lr=params.get("min_lr", base_LR / 20), - cooldown=params.get("cooldown", 50), - ) - elif sched_type in ["exp", "gamma", "exponential"]: - if "gamma" in params: - gamma = params["gamma"] - elif num_iter is not None: - fac = params.get("factor", 0.01) - gamma = fac ** (1.0 / num_iter) - else: - gamma = 0.9 - self._scheduler = torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma=gamma) - elif sched_type == "linear": - self._scheduler = torch.optim.lr_scheduler.LinearLR( - optimizer, - start_factor=params.get("start_factor", 0.1), - end_factor=params.get("end_factor", 1.0), - total_iters=params.get("total_iters", num_iter), - ) - elif sched_type == "cosine_annealing": - self._scheduler = torch.optim.lr_scheduler.CosineAnnealingLR( - optimizer, - T_max=params.get("T_max", num_iter), - eta_min=params.get("eta_min", 1e-7), - ) - else: - raise ValueError(f"Unknown scheduler type: {sched_type}") + params = self._scheduler_params.params(base_LR) + match self.scheduler_params: + case SchedulerParams.NoneScheduler(): + self._scheduler = None + case SchedulerParams.Cyclic(): + self._scheduler = torch.optim.lr_scheduler.CyclicLR( + optimizer, + **params, + ) + case SchedulerParams.Plateau(): + self._scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau( + optimizer, + **params, + ) + case SchedulerParams.Exponential(): + self._scheduler = torch.optim.lr_scheduler.ExponentialLR( + optimizer, + **params, + ) + case SchedulerParams.Linear(): + self._scheduler = torch.optim.lr_scheduler.LinearLR( + optimizer, + **params, + ) + case SchedulerParams.CosineAnnealing(): + self._scheduler = torch.optim.lr_scheduler.CosineAnnealingLR( + optimizer, + **params, + ) + case _: + raise ValueError(f"Unknown scheduler type: {self.scheduler_params}") def step_optimizer(self) -> None: """Step the optimizer if it exists.""" @@ -295,38 +414,3 @@ def reconnect_optimizer_to_parameters(self) -> None: if self._scheduler is not None and self._optimizer is not None: self._scheduler.optimizer = self._optimizer return - - -@dataclass -class OptimizerParams: - type: ( - str - | Literal[ - "adam", - "adamw", - ] - ) - - -@dataclass -class SchedulerParams: - @dataclass - class Plateau: - _name: str = "plateau" - factor: float = 0.5 - patience: int = 10 - threshold: float = 1e-3 - min_lr: float = 1e-7 - cooldown: int = 50 - - def parse_params() -> dict: - pass - - @dataclass - class Exponential: - _name: str = "exponential" - gamma: float = 0.9 - factor: float | None = 0.5 - - def parse_params() -> dict: - pass diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index b8a7c6a4..ac4a9f1d 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -61,7 +61,7 @@ def reconstruct( scheduler_params: dict | None = None, obj_constraints: dict = {}, # TODO: What to pass into the constraints? dset_constraints: dict = {}, - num_samples_per_ray: int | list[tuple[int, int]] = None, + num_samples_per_ray: int | list[tuple[int, int]] | None = None, profiling_mode: bool = False, val_fraction: float = 0.0, loss_type: Literal[ @@ -106,7 +106,7 @@ def reconstruct( new_scheduler = True if new_scheduler: - self.set_schedulers(self.scheduler_params, num_iter=num_iter) + self.set_schedulers(self.scheduler_params) if obj_constraints is not None: self.obj_model.constraints = cast(DefaultConstraintsTomography, obj_constraints) @@ -127,7 +127,7 @@ def reconstruct( self.set_optimizers() if scheduler_params is not None: self.scheduler_params = scheduler_params - self.set_schedulers(self.scheduler_params, num_iter=num_iter) + self.set_schedulers(self.scheduler_params) self.dataloader, self.sampler, self.val_dataloader, self.val_sampler = ( self.setup_dataloader( diff --git a/src/quantem/tomography/tomography_opt.py b/src/quantem/tomography/tomography_opt.py index c7bc6b38..1490293a 100644 --- a/src/quantem/tomography/tomography_opt.py +++ b/src/quantem/tomography/tomography_opt.py @@ -126,12 +126,12 @@ def schedulers(self) -> dict[str, torch.optim.lr_scheduler._LRScheduler]: return schedulers - def set_schedulers(self, params: dict[str, dict], num_iter: int | None = None): + def set_schedulers(self, params: dict[str, dict]): for key, scheduler_params in params.items(): if key == "object": - self.obj_model.set_scheduler(scheduler_params, num_iter) + self.obj_model.set_scheduler(scheduler_params) elif key == "pose": - self.dset.set_scheduler(scheduler_params, num_iter) + self.dset.set_scheduler(scheduler_params) else: raise ValueError(f"Unknown optimization key: {key}") From 18d186f57a93ae9f2d84602339b2ba34cd8cc332 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Wed, 4 Mar 2026 12:27:05 -0800 Subject: [PATCH 133/335] Optimizer and Scheduler hinting - tested on tomography tutorials. --- src/quantem/core/ml/optimizer_mixin.py | 144 +++++++++++++++++------ src/quantem/tomography/tomography.py | 6 +- src/quantem/tomography/tomography_opt.py | 24 ++-- 3 files changed, 124 insertions(+), 50 deletions(-) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index f3029101..66e4c736 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -1,6 +1,6 @@ from abc import abstractmethod from dataclasses import dataclass -from typing import TYPE_CHECKING, Generator, Iterator, Literal, Protocol, Sequence +from typing import TYPE_CHECKING, Generator, Iterator, Literal, Sequence from quantem.core import config @@ -11,20 +11,97 @@ import torch -@dataclass class OptimizerParams: - type: ( - str - | Literal[ - "adam", - "adamw", - ] - ) + """ + Nested class for optimizer parameters. + """ + + @dataclass + class Adam: + lr: float = 1e-3 + betas: tuple[float, float] = (0.9, 0.999) + eps: float = 1e-8 + weight_decay: float = 0 + _name: str = "adam" + + def params(self) -> dict: + return { + "lr": self.lr, + "betas": self.betas, + "eps": self.eps, + "weight_decay": self.weight_decay, + } + @dataclass + class AdamW: + lr: float = 1e-3 + betas: tuple[float, float] = (0.9, 0.999) + eps: float = 1e-8 + weight_decay: float = 0 + _name: str = "adamw" + + def params(self) -> dict: + return { + "lr": self.lr, + "betas": self.betas, + "eps": self.eps, + "weight_decay": self.weight_decay, + } + + @dataclass + class SGD: + lr: float = 1e-3 + momentum: float = 0 + dampening: float = 0 + weight_decay: float = 0 + nesterov: bool = False + _name: str = "sgd" + + def params(self) -> dict: + return { + "lr": self.lr, + "momentum": self.momentum, + "dampening": self.dampening, + "weight_decay": self.weight_decay, + "nesterov": self.nesterov, + } + + @dataclass + class NoneOptimizer: + _name: str = "none" + + def params(self) -> dict: + return {} -class SchedulerParams(Protocol): + @classmethod + def parse_dict(cls, d: dict): + """ + Parse dictionary to a optimizer params object. + """ + if d["name"] == "adam": + d.pop("name") + return OptimizerParams.Adam(**d) + elif d["name"] == "adamw": + d.pop("name") + return OptimizerParams.AdamW(**d) + elif d["name"] == "sgd": + d.pop("name") + return OptimizerParams.SGD(**d) + else: + raise ValueError(f"Unknown optimizer type: {d['name']}") + + +OptimizerType = ( + OptimizerParams.Adam + | OptimizerParams.AdamW + | OptimizerParams.SGD + | OptimizerParams.NoneOptimizer +) + + +class SchedulerParams: """ - Nested dataclass for scheduler parameters. + Nested class for scheduler parameters. """ @dataclass @@ -171,7 +248,7 @@ def __init__(self): """Initialize the optimizer mixin.""" self._optimizer = None self._scheduler = None - self._optimizer_params = {} + self._optimizer_params: OptimizerType = OptimizerParams.NoneOptimizer() self._scheduler_params: SchedulerType = SchedulerParams.NoneScheduler() # Don't call super().__init__() in mixin classes to avoid MRO issues @@ -186,14 +263,18 @@ def scheduler(self) -> "torch.optim.lr_scheduler.LRScheduler | None": return self._scheduler @property - def optimizer_params(self) -> dict: + def optimizer_params(self) -> OptimizerType: """Get the optimizer parameters.""" return self._optimizer_params @optimizer_params.setter - def optimizer_params(self, params: dict): + def optimizer_params(self, params: OptimizerType | dict): """Set the optimizer parameters.""" - self._optimizer_params = params.copy() if params else {} + if isinstance(params, dict): + params = OptimizerParams.parse_dict(d=params) + if not isinstance(params, OptimizerType): + raise TypeError(f"optimizer parameters must be a OptimizerType, got {type(params)}") + self._optimizer_params = params @property def scheduler_params(self) -> SchedulerType: @@ -220,7 +301,7 @@ def get_optimization_parameters( """ raise NotImplementedError("Subclasses must implement get_optimization_parameters") - def set_optimizer(self, opt_params: dict | None = None) -> None: + def set_optimizer(self, opt_params: OptimizerType | None = None) -> None: """ Set the optimizer for this model. Currently supports single LR for all parameters, TODO allow for per parameter LRs by @@ -233,10 +314,7 @@ def set_optimizer(self, opt_params: dict | None = None) -> None: self._optimizer = None return - opt_params = self._optimizer_params.copy() - opt_type = opt_params.pop("type", self.DEFAULT_OPTIMIZER_TYPE) - - if opt_type == "none": + if isinstance(self._optimizer_params, OptimizerParams.NoneOptimizer): self.remove_optimizer() return @@ -250,19 +328,15 @@ def set_optimizer(self, opt_params: dict | None = None) -> None: for p in params: p.requires_grad_(True) - if isinstance(opt_type, type): - self._optimizer = opt_type(params, **opt_params) - elif isinstance(opt_type, str): - if opt_type.lower() == "adam": - self._optimizer = torch.optim.Adam(params, **opt_params) - elif opt_type.lower() == "adamw": - self._optimizer = torch.optim.AdamW(params, **opt_params) - elif opt_type.lower() == "sgd": - self._optimizer = torch.optim.SGD(params, **opt_params) - else: - raise NotImplementedError(f"Unknown optimizer type: {opt_type}") - else: - raise TypeError(f"optimizer type must be string or type, got {type(opt_type)}") + match self._optimizer_params: + case OptimizerParams.Adam(): + self._optimizer = torch.optim.Adam(params, **self._optimizer_params.params()) + case OptimizerParams.AdamW(): + self._optimizer = torch.optim.AdamW(params, **self._optimizer_params.params()) + case OptimizerParams.SGD(): + self._optimizer = torch.optim.SGD(params, **self._optimizer_params.params()) + case _: + raise NotImplementedError(f"Unknown optimizer type: {self._optimizer_params}") def set_scheduler( self, @@ -343,9 +417,9 @@ def get_current_lr(self) -> float: def remove_optimizer(self) -> None: """Remove the optimizer and scheduler.""" self._optimizer = None - self._optimizer_params = {} + self._optimizer_params = OptimizerParams.NoneOptimizer() self._scheduler = None - self._scheduler_params = {} + self._scheduler_params = SchedulerParams.NoneScheduler() def reset_optimizer(self) -> None: """Reset the optimizer and scheduler.""" diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index ac4a9f1d..3e2d6418 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -210,10 +210,6 @@ def reconstruct( consistency_loss = consistency_loss.item() / len(self.dataloader) epoch_soft_constraint_loss = epoch_soft_constraint_loss.item() / len(self.dataloader) - pbar.set_description( - f"Reconstruction | Loss: {total_loss:.4f}, Consistency Loss: {consistency_loss:.4f}, Soft Constraint Loss: {epoch_soft_constraint_loss:.4f}" - ) - if self.val_dataloader is not None: print("Validating...") self.obj_model.model.eval() @@ -296,7 +292,7 @@ def reconstruct( self.logger.flush() pbar.set_description( - f"Reconstruction | Loss: {total_loss:.4f}, Consistency Loss: {consistency_loss:.4f}, Soft Constraint Loss: {epoch_soft_constraint_loss:.4f} | Logger Updated" + f"Reconstruction | Loss: {total_loss:.5e}, Consistency Loss: {consistency_loss:.5e}, Soft Constraint Loss: {epoch_soft_constraint_loss:.5e} | Logger Updated" ) # --- Helper Functions --- diff --git a/src/quantem/tomography/tomography_opt.py b/src/quantem/tomography/tomography_opt.py index 1490293a..37675b24 100644 --- a/src/quantem/tomography/tomography_opt.py +++ b/src/quantem/tomography/tomography_opt.py @@ -1,5 +1,6 @@ import torch +from quantem.core.ml.optimizer_mixin import OptimizerType from quantem.tomography.tomography_base import TomographyBase @@ -35,23 +36,26 @@ def optimizer_params(self) -> dict[str, dict]: } @optimizer_params.setter - def optimizer_params(self, d: dict): + def optimizer_params(self, d: dict[str, OptimizerType]): """Set the optimizer parameters.""" if isinstance(d, (tuple, list)): d = {k: {} for k in d} + targets = { + "object": self.obj_model, + "pose": self.dset, + } + for k, v in d.items(): - if "type" not in v.keys(): - v["type"] = self.DEFAULT_OPTIMIZER_TYPE - if "lr" not in v.keys(): - v["lr"] = self._get_default_lr(k) - if k == "object": - self.obj_model.optimizer_params = v - elif k == "pose": - self.dset.optimizer_params = v - else: + if k not in targets: raise ValueError(f"Unknown optimization key: {k}") + if not isinstance(v, OptimizerType): + if "lr" not in v: + v["lr"] = self._get_default_lr(k) + + targets[k].optimizer_params = v + @property def optimizers(self) -> dict[str, torch.optim.Optimizer]: return { From 213988cd22a7d52a0e77be2cf7438aae80dd5a9f Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Wed, 4 Mar 2026 12:56:41 -0800 Subject: [PATCH 134/335] Fixes --- src/quantem/core/ml/optimizer_mixin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index 66e4c736..91e2327a 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -301,7 +301,7 @@ def get_optimization_parameters( """ raise NotImplementedError("Subclasses must implement get_optimization_parameters") - def set_optimizer(self, opt_params: OptimizerType | None = None) -> None: + def set_optimizer(self, opt_params: OptimizerType | dict | None = None) -> None: """ Set the optimizer for this model. Currently supports single LR for all parameters, TODO allow for per parameter LRs by @@ -340,7 +340,7 @@ def set_optimizer(self, opt_params: OptimizerType | None = None) -> None: def set_scheduler( self, - scheduler_params: SchedulerType | None = None, + scheduler_params: SchedulerType | dict | None = None, ) -> None: """Set the scheduler for this model.""" if scheduler_params is not None: From 059903ab8ba2748c6ee23144f0b2dc34874f7d0f Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Wed, 4 Mar 2026 13:01:44 -0800 Subject: [PATCH 135/335] Only dev stuff --- src/quantem/core/ml/optimizer_mixin.py | 375 +++++++++++++++++++------ 1 file changed, 289 insertions(+), 86 deletions(-) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index e2d2e89a..91e2327a 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -1,5 +1,6 @@ from abc import abstractmethod -from typing import TYPE_CHECKING, Generator, Iterator, Sequence +from dataclasses import dataclass +from typing import TYPE_CHECKING, Generator, Iterator, Literal, Sequence from quantem.core import config @@ -10,6 +11,231 @@ import torch +class OptimizerParams: + """ + Nested class for optimizer parameters. + """ + + @dataclass + class Adam: + lr: float = 1e-3 + betas: tuple[float, float] = (0.9, 0.999) + eps: float = 1e-8 + weight_decay: float = 0 + _name: str = "adam" + + def params(self) -> dict: + return { + "lr": self.lr, + "betas": self.betas, + "eps": self.eps, + "weight_decay": self.weight_decay, + } + + @dataclass + class AdamW: + lr: float = 1e-3 + betas: tuple[float, float] = (0.9, 0.999) + eps: float = 1e-8 + weight_decay: float = 0 + _name: str = "adamw" + + def params(self) -> dict: + return { + "lr": self.lr, + "betas": self.betas, + "eps": self.eps, + "weight_decay": self.weight_decay, + } + + @dataclass + class SGD: + lr: float = 1e-3 + momentum: float = 0 + dampening: float = 0 + weight_decay: float = 0 + nesterov: bool = False + _name: str = "sgd" + + def params(self) -> dict: + return { + "lr": self.lr, + "momentum": self.momentum, + "dampening": self.dampening, + "weight_decay": self.weight_decay, + "nesterov": self.nesterov, + } + + @dataclass + class NoneOptimizer: + _name: str = "none" + + def params(self) -> dict: + return {} + + @classmethod + def parse_dict(cls, d: dict): + """ + Parse dictionary to a optimizer params object. + """ + if d["name"] == "adam": + d.pop("name") + return OptimizerParams.Adam(**d) + elif d["name"] == "adamw": + d.pop("name") + return OptimizerParams.AdamW(**d) + elif d["name"] == "sgd": + d.pop("name") + return OptimizerParams.SGD(**d) + else: + raise ValueError(f"Unknown optimizer type: {d['name']}") + + +OptimizerType = ( + OptimizerParams.Adam + | OptimizerParams.AdamW + | OptimizerParams.SGD + | OptimizerParams.NoneOptimizer +) + + +class SchedulerParams: + """ + Nested class for scheduler parameters. + """ + + @dataclass + class Plateau: + mode: Literal["min", "max"] = "min" + min_lr_factor: float = 1 / 20 + min_lr: float | None = None + factor: float = 0.5 + patience: int = 10 + threshold: float = 1e-5 + cooldown: int = 50 + _name: str = "plateau" + + def params(self, base_LR: float) -> dict: + if self.min_lr is None: + self.min_lr = self.min_lr_factor * base_LR + return { + "mode": self.mode, + "factor": self.factor, + "patience": self.patience, + "threshold": self.threshold, + "min_lr": self.min_lr, + "cooldown": self.cooldown, + } + + @dataclass + class Exponential: + gamma: float = 0.9 + factor: float | None = 0.5 + num_iter: int | None = None + _name: str = "exponential" + + def params(self, base_LR: float) -> dict: + return { + "gamma": self.gamma, + "factor": self.factor, + } + + @dataclass + class Cyclic: + base_lr_factor: float = 1 / 4 + max_lr_factor: float = 4 + base_lr: float | None = None + max_lr: float | None = None + step_size_up: int = 100 + step_size_down: int = 100 + mode: Literal["triangular2", "triangular", "exp_range"] = "triangular2" + cycle_momentum: bool = False + _name: str = "cyclic" + + def params(self, base_LR: float) -> dict: + if self.base_lr is None: + self.base_lr = self.base_lr_factor * base_LR + if self.max_lr is None: + self.max_lr = self.max_lr_factor * base_LR + return { + "base_lr": self.base_lr, + "max_lr": self.max_lr, + "step_size_up": self.step_size_up, + "step_size_down": self.step_size_down, + "mode": self.mode, + "cycle_momentum": self.cycle_momentum, + } + + @dataclass + class Linear: + total_iters: int + start_factor: float = 0.1 + end_factor: float = 1.0 + _name: str = "linear" + + def params(self, base_LR: float) -> dict: + return { + "start_factor": self.start_factor, + "end_factor": self.end_factor, + "total_iters": self.total_iters, + } + + @dataclass + class CosineAnnealing: + T_max: int + eta_min: float = 1e-7 + _name: str = "cosine_annealing" + + def params(self, base_LR: float) -> dict: + return { + "T_max": self.T_max, + "eta_min": self.eta_min, + } + + @dataclass + class NoneScheduler: + _name: str = "none" + + def params(self, base_LR: float) -> dict: + return {} + + @classmethod + def parse_dict(cls, d: dict): + """ + Parse dictionary to a scheduler params object. + """ + if d["name"] == "plateau": + d.pop("name") + return SchedulerParams.Plateau(**d) + elif d["name"] == "exponential": + d.pop("name") + return SchedulerParams.Exponential(**d) + elif d["name"] == "cyclic": + d.pop("name") + return SchedulerParams.Cyclic(**d) + elif d["name"] == "linear": + d.pop("name") + return SchedulerParams.Linear(**d) + elif d["name"] == "cosine_annealing": + d.pop("name") + return SchedulerParams.CosineAnnealing(**d) + elif d["name"] == "none": + d.pop("name") + return SchedulerParams.NoneScheduler() + else: + raise ValueError(f"Unknown scheduler type: {d['name']}") + + +SchedulerType = ( + SchedulerParams.Plateau + | SchedulerParams.Exponential + | SchedulerParams.Cyclic + | SchedulerParams.Linear + | SchedulerParams.CosineAnnealing + | SchedulerParams.NoneScheduler +) + + class OptimizerMixin: """ Mixin class for handling optimizer and scheduler management. @@ -22,8 +248,8 @@ def __init__(self): """Initialize the optimizer mixin.""" self._optimizer = None self._scheduler = None - self._optimizer_params = {} - self._scheduler_params = {} + self._optimizer_params: OptimizerType = OptimizerParams.NoneOptimizer() + self._scheduler_params: SchedulerType = SchedulerParams.NoneScheduler() # Don't call super().__init__() in mixin classes to avoid MRO issues @property @@ -37,38 +263,32 @@ def scheduler(self) -> "torch.optim.lr_scheduler.LRScheduler | None": return self._scheduler @property - def optimizer_params(self) -> dict: + def optimizer_params(self) -> OptimizerType: """Get the optimizer parameters.""" return self._optimizer_params @optimizer_params.setter - def optimizer_params(self, params: dict): + def optimizer_params(self, params: OptimizerType | dict): """Set the optimizer parameters.""" - self._optimizer_params = params.copy() if params else {} + if isinstance(params, dict): + params = OptimizerParams.parse_dict(d=params) + if not isinstance(params, OptimizerType): + raise TypeError(f"optimizer parameters must be a OptimizerType, got {type(params)}") + self._optimizer_params = params @property - def scheduler_params(self) -> dict: + def scheduler_params(self) -> SchedulerType: """Get the scheduler parameters.""" return self._scheduler_params @scheduler_params.setter - def scheduler_params(self, params: dict): + def scheduler_params(self, params: SchedulerType | dict): """Set the scheduler parameters.""" - if params: - if params["type"].lower() not in [ - "cyclic", - "plateau", - "exp", - "gamma", - "linear", - "none", - ]: - raise ValueError( - f"Unknown scheduler type: {params['type']}, expected one of ['cyclic', 'plateau', 'exp', 'gamma', 'none']" - ) - self._scheduler_params = params.copy() - else: - self._scheduler_params = {} + if isinstance(params, dict): + params = SchedulerParams.parse_dict(d=params) + if not isinstance(params, SchedulerType): + raise TypeError(f"scheduler parameters must be a SchedulerType, got {type(params)}") + self._scheduler_params = params @abstractmethod def get_optimization_parameters( @@ -81,7 +301,7 @@ def get_optimization_parameters( """ raise NotImplementedError("Subclasses must implement get_optimization_parameters") - def set_optimizer(self, opt_params: dict | None = None) -> None: + def set_optimizer(self, opt_params: OptimizerType | dict | None = None) -> None: """ Set the optimizer for this model. Currently supports single LR for all parameters, TODO allow for per parameter LRs by @@ -94,10 +314,7 @@ def set_optimizer(self, opt_params: dict | None = None) -> None: self._optimizer = None return - opt_params = self._optimizer_params.copy() - opt_type = opt_params.pop("type", self.DEFAULT_OPTIMIZER_TYPE) - - if opt_type == "none": + if isinstance(self._optimizer_params, OptimizerParams.NoneOptimizer): self.remove_optimizer() return @@ -111,22 +328,19 @@ def set_optimizer(self, opt_params: dict | None = None) -> None: for p in params: p.requires_grad_(True) - if isinstance(opt_type, type): - self._optimizer = opt_type(params, **opt_params) - elif isinstance(opt_type, str): - if opt_type.lower() == "adam": - self._optimizer = torch.optim.Adam(params, **opt_params) - elif opt_type.lower() == "adamw": - self._optimizer = torch.optim.AdamW(params, **opt_params) - elif opt_type.lower() == "sgd": - self._optimizer = torch.optim.SGD(params, **opt_params) - else: - raise NotImplementedError(f"Unknown optimizer type: {opt_type}") - else: - raise TypeError(f"optimizer type must be string or type, got {type(opt_type)}") + match self._optimizer_params: + case OptimizerParams.Adam(): + self._optimizer = torch.optim.Adam(params, **self._optimizer_params.params()) + case OptimizerParams.AdamW(): + self._optimizer = torch.optim.AdamW(params, **self._optimizer_params.params()) + case OptimizerParams.SGD(): + self._optimizer = torch.optim.SGD(params, **self._optimizer_params.params()) + case _: + raise NotImplementedError(f"Unknown optimizer type: {self._optimizer_params}") def set_scheduler( - self, scheduler_params: dict | None = None, num_iter: int | None = None + self, + scheduler_params: SchedulerType | dict | None = None, ) -> None: """Set the scheduler for this model.""" if scheduler_params is not None: @@ -136,51 +350,40 @@ def set_scheduler( self._scheduler = None return - params = self._scheduler_params - sched_type = params.get("type", "none").lower() optimizer = self._optimizer base_LR = optimizer.param_groups[0]["lr"] - if sched_type == "none": - self._scheduler = None - elif sched_type == "cyclic": - self._scheduler = torch.optim.lr_scheduler.CyclicLR( - optimizer, - base_lr=params.get("base_lr", base_LR / 4), - max_lr=params.get("max_lr", base_LR * 4), - step_size_up=params.get("step_size_up", 100), - step_size_down=params.get("step_size_down", params.get("step_size_up", 100)), - mode=params.get("mode", "triangular2"), - cycle_momentum=params.get("momentum", False), - ) - elif sched_type.startswith(("plat", "reducelronplat")): - self._scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau( - optimizer, - mode="min", - factor=params.get("factor", 0.5), - patience=params.get("patience", 10), - threshold=params.get("threshold", 1e-3), - min_lr=params.get("min_lr", base_LR / 20), - cooldown=params.get("cooldown", 50), - ) - elif sched_type in ["exp", "gamma", "exponential"]: - if "gamma" in params: - gamma = params["gamma"] - elif num_iter is not None: - fac = params.get("factor", 0.01) - gamma = fac ** (1.0 / num_iter) - else: - gamma = 0.9 - self._scheduler = torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma=gamma) - elif sched_type == "linear": - self._scheduler = torch.optim.lr_scheduler.LinearLR( - optimizer, - start_factor=params.get("start_factor", 0.1), - end_factor=params.get("end_factor", 1.0), - total_iters=params.get("total_iters", num_iter), - ) - else: - raise ValueError(f"Unknown scheduler type: {sched_type}") + params = self._scheduler_params.params(base_LR) + match self.scheduler_params: + case SchedulerParams.NoneScheduler(): + self._scheduler = None + case SchedulerParams.Cyclic(): + self._scheduler = torch.optim.lr_scheduler.CyclicLR( + optimizer, + **params, + ) + case SchedulerParams.Plateau(): + self._scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau( + optimizer, + **params, + ) + case SchedulerParams.Exponential(): + self._scheduler = torch.optim.lr_scheduler.ExponentialLR( + optimizer, + **params, + ) + case SchedulerParams.Linear(): + self._scheduler = torch.optim.lr_scheduler.LinearLR( + optimizer, + **params, + ) + case SchedulerParams.CosineAnnealing(): + self._scheduler = torch.optim.lr_scheduler.CosineAnnealingLR( + optimizer, + **params, + ) + case _: + raise ValueError(f"Unknown scheduler type: {self.scheduler_params}") def step_optimizer(self) -> None: """Step the optimizer if it exists.""" @@ -214,9 +417,9 @@ def get_current_lr(self) -> float: def remove_optimizer(self) -> None: """Remove the optimizer and scheduler.""" self._optimizer = None - self._optimizer_params = {} + self._optimizer_params = OptimizerParams.NoneOptimizer() self._scheduler = None - self._scheduler_params = {} + self._scheduler_params = SchedulerParams.NoneScheduler() def reset_optimizer(self) -> None: """Reset the optimizer and scheduler.""" From 28f50cda562657e98dd8a70c4e201e1a0f9f18b1 Mon Sep 17 00:00:00 2001 From: Art-MC Date: Wed, 4 Mar 2026 14:09:55 -0800 Subject: [PATCH 136/335] adding docstrings to OptimizerParams and SchedulerParams and promoting in namespace --- src/quantem/core/ml/__init__.py | 4 + src/quantem/core/ml/optimizer_mixin.py | 216 ++++++++++++++++++++++++- 2 files changed, 218 insertions(+), 2 deletions(-) diff --git a/src/quantem/core/ml/__init__.py b/src/quantem/core/ml/__init__.py index ce1f6de0..af2d5b64 100644 --- a/src/quantem/core/ml/__init__.py +++ b/src/quantem/core/ml/__init__.py @@ -1,4 +1,8 @@ from quantem.core.ml.cnn import CNN2d as CNN2d, CNN3d as CNN3d +from quantem.core.ml.optimizer_mixin import ( + OptimizerParams as OptimizerParams, + SchedulerParams as SchedulerParams, +) from quantem.core.ml.inr import HSiren as HSiren from quantem.core.ml.dense_nn import DenseNN as DenseNN from quantem.core.ml.cnn_dense import CNNDense as CNNDense diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index 91e2327a..c318d9a1 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -13,11 +13,47 @@ class OptimizerParams: """ - Nested class for optimizer parameters. + Container for optimizer parameter dataclasses. + + Each nested class configures a specific PyTorch optimizer and can be passed + directly to ``OptimizerMixin.set_optimizer``, or constructed from a dict via + ``OptimizerParams.parse_dict``. + + Supported optimizers + -------------------- + Adam + ``torch.optim.Adam`` — adaptive moment estimation. + AdamW + ``torch.optim.AdamW`` — Adam with decoupled weight decay. + SGD + ``torch.optim.SGD`` — stochastic gradient descent with optional momentum and Nesterov. + NoneOptimizer + Sentinel that disables / removes the optimizer. + + Examples + -------- + >>> obj.set_optimizer(OptimizerParams.Adam(lr=1e-4)) + >>> obj.set_optimizer({"name": "adam", "lr": 1e-4}) # equivalent dict form """ @dataclass class Adam: + """ + Adam optimizer (``torch.optim.Adam``). + + Parameters + ---------- + lr : float + Learning rate. Default: 1e-3. + betas : tuple[float, float] + Coefficients for computing running averages of the gradient and its square. + Default: (0.9, 0.999). + eps : float + Term added to the denominator for numerical stability. Default: 1e-8. + weight_decay : float + L2 regularisation penalty. Default: 0. + """ + lr: float = 1e-3 betas: tuple[float, float] = (0.9, 0.999) eps: float = 1e-8 @@ -34,6 +70,25 @@ def params(self) -> dict: @dataclass class AdamW: + """ + AdamW optimizer (``torch.optim.AdamW``). + + Identical to Adam but applies weight decay directly to the parameters + rather than folding it into the gradient update (decoupled weight decay). + + Parameters + ---------- + lr : float + Learning rate. Default: 1e-3. + betas : tuple[float, float] + Coefficients for computing running averages of the gradient and its square. + Default: (0.9, 0.999). + eps : float + Term added to the denominator for numerical stability. Default: 1e-8. + weight_decay : float + Decoupled L2 regularisation penalty. Default: 0. + """ + lr: float = 1e-3 betas: tuple[float, float] = (0.9, 0.999) eps: float = 1e-8 @@ -50,6 +105,23 @@ def params(self) -> dict: @dataclass class SGD: + """ + SGD optimizer (``torch.optim.SGD``). + + Parameters + ---------- + lr : float + Learning rate. Default: 1e-3. + momentum : float + Momentum factor. Default: 0. + dampening : float + Dampening for momentum. Default: 0. + weight_decay : float + L2 regularisation penalty. Default: 0. + nesterov : bool + Enables Nesterov momentum. Default: False. + """ + lr: float = 1e-3 momentum: float = 0 dampening: float = 0 @@ -68,6 +140,13 @@ def params(self) -> dict: @dataclass class NoneOptimizer: + """ + Sentinel optimizer that disables optimization. + + Passing this to ``set_optimizer`` will call ``remove_optimizer``, + clearing both the optimizer and scheduler. + """ + _name: str = "none" def params(self) -> dict: @@ -101,11 +180,61 @@ def parse_dict(cls, d: dict): class SchedulerParams: """ - Nested class for scheduler parameters. + Container for learning-rate scheduler parameter dataclasses. + + Each nested class configures a specific PyTorch LR scheduler and can be passed + directly to ``OptimizerMixin.set_scheduler``, or constructed from a dict via + ``SchedulerParams.parse_dict``. + + Supported schedulers + -------------------- + Plateau + ``torch.optim.lr_scheduler.ReduceLROnPlateau`` — reduce LR when a metric stops improving. + Exponential + ``torch.optim.lr_scheduler.ExponentialLR`` — multiply LR by ``gamma`` each step. + Cyclic + ``torch.optim.lr_scheduler.CyclicLR`` — cycle LR between ``base_lr`` and ``max_lr``. + Linear + ``torch.optim.lr_scheduler.LinearLR`` — linearly interpolate LR over ``total_iters`` steps. + CosineAnnealing + ``torch.optim.lr_scheduler.CosineAnnealingLR`` — cosine-annealing LR schedule. + NoneScheduler + Sentinel that disables / removes the scheduler. + + Examples + -------- + >>> obj.set_scheduler(SchedulerParams.Plateau(factor=0.5, patience=10, cooldown=20)) + >>> obj.set_scheduler({"name": "plateau", "factor": 0.5}) # equivalent dict form """ @dataclass class Plateau: + """ + ReduceLROnPlateau scheduler (``torch.optim.lr_scheduler.ReduceLROnPlateau``). + + Reduces the learning rate when a monitored metric stops improving. + + Parameters + ---------- + mode : {'min', 'max'} + Whether the monitored metric should be minimised or maximised. Default: 'min'. + min_lr_factor : float + Sets ``min_lr = min_lr_factor * base_lr`` when ``min_lr`` is not provided. + Default: 1/20. + min_lr : float or None + Absolute lower bound on the learning rate. Overrides ``min_lr_factor`` when set. + Default: None. + factor : float + Factor by which the LR is reduced: ``new_lr = lr * factor``. Default: 0.5. + patience : int + Number of epochs with no improvement before reducing LR. Default: 10. + threshold : float + Minimum change in the monitored metric to qualify as an improvement. Default: 1e-5. + cooldown : int + Number of epochs to wait after a LR reduction before resuming normal operation. + Default: 50. + """ + mode: Literal["min", "max"] = "min" min_lr_factor: float = 1 / 20 min_lr: float | None = None @@ -129,6 +258,21 @@ def params(self, base_LR: float) -> dict: @dataclass class Exponential: + """ + ExponentialLR scheduler (``torch.optim.lr_scheduler.ExponentialLR``). + + Multiplies the learning rate by ``gamma`` after each step. + + Parameters + ---------- + gamma : float + Multiplicative decay factor applied each step. Default: 0.9. + factor : float or None + Reserved for future use. Default: 0.5. + num_iter : int or None + Reserved for future use. Default: None. + """ + gamma: float = 0.9 factor: float | None = 0.5 num_iter: int | None = None @@ -142,6 +286,37 @@ def params(self, base_LR: float) -> dict: @dataclass class Cyclic: + """ + CyclicLR scheduler (``torch.optim.lr_scheduler.CyclicLR``). + + Cycles the learning rate between a lower bound (``base_lr``) and an upper + bound (``max_lr``). Bounds can be set directly or derived from the optimizer's + base LR via the factor parameters. + + Parameters + ---------- + base_lr_factor : float + Sets ``base_lr = base_lr_factor * optimizer_lr`` when ``base_lr`` is not provided. + Default: 1/4. + max_lr_factor : float + Sets ``max_lr = max_lr_factor * optimizer_lr`` when ``max_lr`` is not provided. + Default: 4. + base_lr : float or None + Absolute lower bound of the LR cycle. Overrides ``base_lr_factor`` when set. + Default: None. + max_lr : float or None + Absolute upper bound of the LR cycle. Overrides ``max_lr_factor`` when set. + Default: None. + step_size_up : int + Number of steps in the increasing half of each cycle. Default: 100. + step_size_down : int + Number of steps in the decreasing half of each cycle. Default: 100. + mode : {'triangular2', 'triangular', 'exp_range'} + Cycling policy. Default: 'triangular2'. + cycle_momentum : bool + If True, cycles momentum inversely to the learning rate. Default: False. + """ + base_lr_factor: float = 1 / 4 max_lr_factor: float = 4 base_lr: float | None = None @@ -168,6 +343,22 @@ def params(self, base_LR: float) -> dict: @dataclass class Linear: + """ + LinearLR scheduler (``torch.optim.lr_scheduler.LinearLR``). + + Linearly interpolates the learning rate from ``start_factor * base_lr`` to + ``end_factor * base_lr`` over ``total_iters`` steps. + + Parameters + ---------- + total_iters : int + Number of steps over which to interpolate the LR. Required. + start_factor : float + Multiplicative factor applied to the LR at the first step. Default: 0.1. + end_factor : float + Multiplicative factor applied to the LR at the final step. Default: 1.0. + """ + total_iters: int start_factor: float = 0.1 end_factor: float = 1.0 @@ -182,6 +373,20 @@ def params(self, base_LR: float) -> dict: @dataclass class CosineAnnealing: + """ + CosineAnnealingLR scheduler (``torch.optim.lr_scheduler.CosineAnnealingLR``). + + Anneals the learning rate following a cosine curve from the base LR down to + ``eta_min`` over ``T_max`` steps, then restarts. + + Parameters + ---------- + T_max : int + Maximum number of iterations (half-period of the cosine cycle). Required. + eta_min : float + Minimum learning rate at the trough of the cosine curve. Default: 1e-7. + """ + T_max: int eta_min: float = 1e-7 _name: str = "cosine_annealing" @@ -194,6 +399,13 @@ def params(self, base_LR: float) -> dict: @dataclass class NoneScheduler: + """ + Sentinel scheduler that disables LR scheduling. + + Passing this to ``set_scheduler`` clears the active scheduler without + affecting the optimizer. + """ + _name: str = "none" def params(self, base_LR: float) -> dict: From 825c50cf130f1a0f3282dfe1b78c67dc6616de67 Mon Sep 17 00:00:00 2001 From: Art-MC Date: Wed, 4 Mar 2026 15:45:21 -0800 Subject: [PATCH 137/335] adding backwards compatibility for "type" rather than "name" --- src/quantem/core/ml/optimizer_mixin.py | 50 +++++++++++++++----------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index c318d9a1..e7cb887a 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -156,18 +156,25 @@ def params(self) -> dict: def parse_dict(cls, d: dict): """ Parse dictionary to a optimizer params object. + Accepts either ``"name"`` or ``"type"`` as the optimizer key. """ - if d["name"] == "adam": - d.pop("name") + print("d: ", d) + d = dict(d) # avoid mutating caller's dict + name = d.pop("name", None) or d.pop("type", None) + if isinstance(name, type): + name = name.__name__.lower() + elif isinstance(name, str): + name = name.lower() + else: + raise ValueError(f"Unknown optimizer type: {name}") + if name == "adam": return OptimizerParams.Adam(**d) - elif d["name"] == "adamw": - d.pop("name") + elif name == "adamw": return OptimizerParams.AdamW(**d) - elif d["name"] == "sgd": - d.pop("name") + elif name == "sgd": return OptimizerParams.SGD(**d) else: - raise ValueError(f"Unknown optimizer type: {d['name']}") + raise ValueError(f"Unknown optimizer type: {name.lower()}") OptimizerType = ( @@ -415,27 +422,30 @@ def params(self, base_LR: float) -> dict: def parse_dict(cls, d: dict): """ Parse dictionary to a scheduler params object. + Accepts either ``"name"`` or ``"type"`` as the scheduler key. """ - if d["name"] == "plateau": - d.pop("name") + d = dict(d) # avoid mutating caller's dict + name = d.pop("name", None) or d.pop("type", None) + if isinstance(name, type): + name = name.__name__.lower() + elif isinstance(name, str): + name = name.lower() + else: + raise ValueError(f"Unknown scheduler type: {name}") + if name == "plateau": return SchedulerParams.Plateau(**d) - elif d["name"] == "exponential": - d.pop("name") + elif name == "exponential": return SchedulerParams.Exponential(**d) - elif d["name"] == "cyclic": - d.pop("name") + elif name == "cyclic": return SchedulerParams.Cyclic(**d) - elif d["name"] == "linear": - d.pop("name") + elif name == "linear": return SchedulerParams.Linear(**d) - elif d["name"] == "cosine_annealing": - d.pop("name") + elif name == "cosine_annealing": return SchedulerParams.CosineAnnealing(**d) - elif d["name"] == "none": - d.pop("name") + elif name == "none": return SchedulerParams.NoneScheduler() else: - raise ValueError(f"Unknown scheduler type: {d['name']}") + raise ValueError(f"Unknown scheduler type: {name}") SchedulerType = ( From 5cbcd888b5c309a6574656420799dccbb0357abd Mon Sep 17 00:00:00 2001 From: Art-MC Date: Wed, 4 Mar 2026 15:48:57 -0800 Subject: [PATCH 138/335] left a print --- src/quantem/core/ml/optimizer_mixin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index e7cb887a..f912b205 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -158,7 +158,6 @@ def parse_dict(cls, d: dict): Parse dictionary to a optimizer params object. Accepts either ``"name"`` or ``"type"`` as the optimizer key. """ - print("d: ", d) d = dict(d) # avoid mutating caller's dict name = d.pop("name", None) or d.pop("type", None) if isinstance(name, type): From 1946c8752ba060fe54f3cb787c1daa09bb0f20a9 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Wed, 4 Mar 2026 16:13:28 -0800 Subject: [PATCH 139/335] Added num_iters back in when calling .params --- src/quantem/core/ml/optimizer_mixin.py | 45 ++++++++++++++++++-------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index f912b205..eb4a8419 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -250,7 +250,7 @@ class Plateau: cooldown: int = 50 _name: str = "plateau" - def params(self, base_LR: float) -> dict: + def params(self, base_LR: float, num_iter: int | None = None) -> dict: if self.min_lr is None: self.min_lr = self.min_lr_factor * base_LR return { @@ -280,11 +280,20 @@ class Exponential: """ gamma: float = 0.9 - factor: float | None = 0.5 + factor: float | None = None num_iter: int | None = None _name: str = "exponential" - def params(self, base_LR: float) -> dict: + def params(self, base_LR: float, num_iter: int | None = None) -> dict: + effective_num_iter = self.num_iter if self.num_iter is not None else num_iter + if effective_num_iter is None: + raise ValueError("num_iter must be set if num_iter is not provided") + + self.num_iter = effective_num_iter + + if self.factor is not None: + self.gamma = self.factor ** (1.0 / effective_num_iter) + return { "gamma": self.gamma, "factor": self.factor, @@ -333,7 +342,7 @@ class Cyclic: cycle_momentum: bool = False _name: str = "cyclic" - def params(self, base_LR: float) -> dict: + def params(self, base_LR: float, num_iter: int | None = None) -> dict: if self.base_lr is None: self.base_lr = self.base_lr_factor * base_LR if self.max_lr is None: @@ -365,12 +374,18 @@ class Linear: Multiplicative factor applied to the LR at the final step. Default: 1.0. """ - total_iters: int + total_iters: int | None = None start_factor: float = 0.1 end_factor: float = 1.0 _name: str = "linear" - def params(self, base_LR: float) -> dict: + def params(self, base_LR: float, num_iter: int | None = None) -> dict: + if num_iter is None and self.total_iters is None: + raise ValueError( + "total_iters must be set if num_iter is not provided" + ) # Should never be reached + if self.total_iters is None: + self.total_iters = num_iter return { "start_factor": self.start_factor, "end_factor": self.end_factor, @@ -393,11 +408,17 @@ class CosineAnnealing: Minimum learning rate at the trough of the cosine curve. Default: 1e-7. """ - T_max: int eta_min: float = 1e-7 + T_max: int | None = None _name: str = "cosine_annealing" - def params(self, base_LR: float) -> dict: + def params(self, base_LR: float, num_iter: int | None = None) -> dict: + if num_iter is None and self.T_max is None: + raise ValueError( + "T_max must be set if num_iter is not provided" + ) # Should never be reached + if self.T_max is None: + self.T_max = num_iter return { "T_max": self.T_max, "eta_min": self.eta_min, @@ -414,7 +435,7 @@ class NoneScheduler: _name: str = "none" - def params(self, base_LR: float) -> dict: + def params(self, base_LR: float, num_iter: int | None = None) -> dict: return {} @classmethod @@ -560,8 +581,7 @@ def set_optimizer(self, opt_params: OptimizerType | dict | None = None) -> None: raise NotImplementedError(f"Unknown optimizer type: {self._optimizer_params}") def set_scheduler( - self, - scheduler_params: SchedulerType | dict | None = None, + self, scheduler_params: SchedulerType | dict | None = None, num_iter: int | None = None ) -> None: """Set the scheduler for this model.""" if scheduler_params is not None: @@ -573,8 +593,7 @@ def set_scheduler( optimizer = self._optimizer base_LR = optimizer.param_groups[0]["lr"] - - params = self._scheduler_params.params(base_LR) + params = self._scheduler_params.params(base_LR, num_iter=num_iter) match self.scheduler_params: case SchedulerParams.NoneScheduler(): self._scheduler = None From a46a75ad1f91cad33ec71146928d4331895af006 Mon Sep 17 00:00:00 2001 From: Art-MC Date: Wed, 4 Mar 2026 16:27:02 -0800 Subject: [PATCH 140/335] fixing Exponential scheduler --- src/quantem/core/ml/optimizer_mixin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index eb4a8419..96c11f29 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -296,7 +296,6 @@ def params(self, base_LR: float, num_iter: int | None = None) -> dict: return { "gamma": self.gamma, - "factor": self.factor, } @dataclass From 29c8b7f9acc88243433fd60ad78d8a97fe8f99ea Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Wed, 4 Mar 2026 16:37:08 -0800 Subject: [PATCH 141/335] adapting the new SchedulerParams and OptimizerParams for ptycho --- .../diffractive_imaging/ptychography_lite.py | 24 ++--- .../diffractive_imaging/ptychography_opt.py | 96 +++++++++---------- 2 files changed, 56 insertions(+), 64 deletions(-) diff --git a/src/quantem/diffractive_imaging/ptychography_lite.py b/src/quantem/diffractive_imaging/ptychography_lite.py index 4470b53a..9fc7d039 100644 --- a/src/quantem/diffractive_imaging/ptychography_lite.py +++ b/src/quantem/diffractive_imaging/ptychography_lite.py @@ -180,23 +180,23 @@ def reconstruct( # type:ignore could do overloads but this is simpler... if new_optimizers or reset or self.num_iters == 0: opt_params = { "object": { - "type": "adamw", + "name": "adamw", "lr": lr_obj, }, } scheduler_params = { "object": { - "type": scheduler_type, + "name": scheduler_type, "factor": scheduler_factor, } } if learn_probe: opt_params["probe"] = { - "type": "adamw", + "name": "adamw", "lr": lr_probe, } scheduler_params["probe"] = { - "type": scheduler_type, + "name": scheduler_type, "factor": scheduler_factor, } else: @@ -314,11 +314,11 @@ def from_ptycholite( reset=True, num_iters=pretrain_iters, optimizer_params={ - "type": "adamw", + "name": "adamw", "lr": pretrain_lr, }, scheduler_params={ - "type": "plateau", + "name": "plateau", "factor": 0.5, }, apply_constraints=False, @@ -329,11 +329,11 @@ def from_ptycholite( reset=True, num_iters=pretrain_iters, optimizer_params={ - "type": "adamw", + "name": "adamw", "lr": 1e-3, }, scheduler_params={ - "type": "plateau", + "name": "plateau", "factor": 0.5, }, apply_constraints=False, @@ -390,23 +390,23 @@ def reconstruct( # type:ignore could do overloads but this is simpler... if new_optimizers or reset or self.num_iters == 0: opt_params = { "object": { - "type": "adamw", + "name": "adamw", "lr": lr_obj, }, } scheduler_params = { "object": { - "type": scheduler_type, + "name": scheduler_type, "factor": scheduler_factor, } } if learn_probe: opt_params["probe"] = { - "type": "adamw", + "name": "adamw", "lr": lr_probe, } scheduler_params["probe"] = { - "type": scheduler_type, + "name": scheduler_type, "factor": scheduler_factor, } else: diff --git a/src/quantem/diffractive_imaging/ptychography_opt.py b/src/quantem/diffractive_imaging/ptychography_opt.py index f9b5aae4..c18150e0 100644 --- a/src/quantem/diffractive_imaging/ptychography_opt.py +++ b/src/quantem/diffractive_imaging/ptychography_opt.py @@ -1,6 +1,13 @@ +from dataclasses import replace from typing import TYPE_CHECKING from quantem.core import config +from quantem.core.ml.optimizer_mixin import ( + OptimizerParams, + OptimizerType, + SchedulerParams, + SchedulerType, +) from quantem.diffractive_imaging.ptychography_base import PtychographyBase if TYPE_CHECKING: @@ -16,12 +23,10 @@ class PtychographyOpt(PtychographyBase): """ OPTIMIZABLE_VALS = ["object", "probe", "dataset"] - DEFAULT_OPTIMIZER_TYPE = "adam" + DEFAULT_OPTIMIZER_TYPE: OptimizerType = OptimizerParams.Adam() def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - # self._optimizer_params = {} - # self._scheduler_params = {} def _get_default_lr(self, key: str) -> float: """Get default learning rate for a given optimization key.""" @@ -37,7 +42,7 @@ def _get_default_lr(self, key: str) -> float: # region --- explicit properties and setters --- @property - def optimizer_params(self) -> dict[str, dict]: + def optimizer_params(self) -> dict[str, OptimizerType]: return { key: params for key, params in [ @@ -45,45 +50,40 @@ def optimizer_params(self) -> dict[str, dict]: ("probe", self.probe_model.optimizer_params), ("dataset", self.dset.optimizer_params), ] - if params + if not isinstance(params, OptimizerParams.NoneOptimizer) } @optimizer_params.setter def optimizer_params(self, d: dict) -> None: """ - Takes a dictionary: - { - "object": { - "type": "adam", - "lr": 0.001, - }, - "probe": { - "type": "adam", - "lr": 0.001, - }, - "dataset": { - "type": "adam", - "lr": 0.001, - }, - ... - } + Takes a dictionary mapping optimizable keys to either an ``OptimizerType`` + dataclass or a plain dict (with optional ``"name"``/``"type"`` and ``"lr"`` + keys). Missing ``"name"`` / ``"lr"`` are filled from ``DEFAULT_OPTIMIZER_TYPE`` + and ``_get_default_lr`` respectively. + + Examples + -------- + >>> ptycho.optimizer_params = {"object": OptimizerParams.Adam(lr=5e-3)} + >>> ptycho.optimizer_params = {"object": {"name": "adam", "lr": 5e-3}} + >>> ptycho.optimizer_params = ["object", "probe"] # use all defaults """ if isinstance(d, (tuple, list)): d = {k: {} for k in d} - ## previously removed unspecified optimizers, but I think its better to keep them - ## and only remove if type is none - # for key in self.OPTIMIZABLE_VALS: - # # if not specified, remove the scheduler for that model - # if key not in d: - # d[key] = {"type": "none"} - for k, v in d.items(): - if "type" not in v.keys(): - v["type"] = self.DEFAULT_OPTIMIZER_TYPE - if "lr" not in v.keys(): - v["lr"] = self._get_default_lr(k) - # self._optimizer_params[k] = v + if isinstance(v, OptimizerType): + pass # already a dataclass, pass through + elif isinstance(v, dict): + if not v: + v = replace(self.DEFAULT_OPTIMIZER_TYPE, lr=self._get_default_lr(k)) + else: + if "name" not in v and "type" not in v: + v["name"] = self.DEFAULT_OPTIMIZER_TYPE._name + if "lr" not in v: + v["lr"] = self._get_default_lr(k) + else: + raise TypeError(f"Expected OptimizerType or dict for key '{k}', got {type(v)}") + if k == "object": self.obj_model.optimizer_params = v elif k == "probe": @@ -131,7 +131,7 @@ def remove_optimizer(self, key: str) -> None: self.dset.remove_optimizer() @property - def scheduler_params(self) -> dict[str, dict]: + def scheduler_params(self) -> dict[str, SchedulerType]: """Returns the parameters used to set the schedulers.""" return { "object": self.obj_model.scheduler_params, @@ -142,22 +142,18 @@ def scheduler_params(self) -> dict[str, dict]: @scheduler_params.setter def scheduler_params(self, d: dict) -> None: """ - Takes a dictionary: - { - "object": { - "type": "cyclic", - "base_lr": 0.001, - }, - "probe": { - ... - }, - ... - } + Takes a dictionary mapping optimizable keys to either a ``SchedulerType`` + dataclass or a plain dict. Keys not present in ``d`` are set to + ``SchedulerParams.NoneScheduler()`` (disables scheduling for that model). + + Examples + -------- + >>> ptycho.scheduler_params = {"object": SchedulerParams.Plateau(factor=0.5)} + >>> ptycho.scheduler_params = {"object": {"name": "plateau", "factor": 0.5}} """ for key in self.OPTIMIZABLE_VALS: - # if not specified, remove the scheduler for that model if key not in d: - d[key] = {} + d[key] = SchedulerParams.NoneScheduler() for k, v in d.items(): if k == "object": self.obj_model.scheduler_params = v @@ -182,11 +178,7 @@ def schedulers(self) -> dict[str, "torch.optim.lr_scheduler._LRScheduler"]: schedulers["dataset"] = self.dset.scheduler return schedulers - def set_schedulers( - self, - params: dict[str, dict], - num_iter: int | None = None, - ): + def set_schedulers(self, params: dict[str, SchedulerType], num_iter: int | None = None): """Set schedulers for each model.""" for key, scheduler_params in params.items(): if key not in self.OPTIMIZABLE_VALS: From 737a7bc5aa63ae4187fcd5079d9315dea4426f92 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Wed, 4 Mar 2026 16:47:25 -0800 Subject: [PATCH 142/335] Fixes to TomographyOpt.py --- src/quantem/core/ml/optimizer_mixin.py | 2 +- src/quantem/tomography/tomography.py | 2 +- src/quantem/tomography/tomography_opt.py | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index eb4a8419..4674e5ce 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -445,7 +445,7 @@ def parse_dict(cls, d: dict): Accepts either ``"name"`` or ``"type"`` as the scheduler key. """ d = dict(d) # avoid mutating caller's dict - name = d.pop("name", None) or d.pop("type", None) + name = d.pop("name", "none") or d.pop("type", "none") if isinstance(name, type): name = name.__name__.lower() elif isinstance(name, str): diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 3e2d6418..f0b05b52 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -106,7 +106,7 @@ def reconstruct( new_scheduler = True if new_scheduler: - self.set_schedulers(self.scheduler_params) + self.set_schedulers(self.scheduler_params, num_iter=num_iter) if obj_constraints is not None: self.obj_model.constraints = cast(DefaultConstraintsTomography, obj_constraints) diff --git a/src/quantem/tomography/tomography_opt.py b/src/quantem/tomography/tomography_opt.py index 37675b24..404e2f4b 100644 --- a/src/quantem/tomography/tomography_opt.py +++ b/src/quantem/tomography/tomography_opt.py @@ -130,29 +130,29 @@ def schedulers(self) -> dict[str, torch.optim.lr_scheduler._LRScheduler]: return schedulers - def set_schedulers(self, params: dict[str, dict]): + def set_schedulers(self, params: dict[str, dict], num_iter: int | None = None): for key, scheduler_params in params.items(): if key == "object": - self.obj_model.set_scheduler(scheduler_params) + self.obj_model.set_scheduler(scheduler_params, num_iter=num_iter) elif key == "pose": - self.dset.set_scheduler(scheduler_params) + self.dset.set_scheduler(scheduler_params, num_iter=num_iter) else: raise ValueError(f"Unknown optimization key: {key}") def step_optimizers(self): for key in self.optimizer_params.keys(): - if key == "object" and self.obj_model.has_optimizer(): + if self.obj_model.has_optimizer(): self.obj_model.step_optimizer() - elif key == "pose" and self.dset.has_optimizer(): + elif self.dset.has_optimizer(): self.dset.step_optimizer() else: raise ValueError(f"Unknown optimization key: {key}") def zero_grad_all(self): for key in self.optimizer_params.keys(): - if key == "object" and self.obj_model.has_optimizer(): + if self.obj_model.has_optimizer(): self.obj_model.zero_optimizer_grad() - elif key == "pose" and self.dset.has_optimizer(): + elif self.dset.has_optimizer(): self.dset.zero_optimizer_grad() else: raise ValueError(f"Unknown optimization key: {key}") From 4d9b9faa20a1685e0e0531ef21382428f587600b Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Wed, 4 Mar 2026 16:49:57 -0800 Subject: [PATCH 143/335] Fixed bug in parse_dict None -> none --- src/quantem/core/ml/optimizer_mixin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index eb4a8419..30cb07ef 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -159,7 +159,7 @@ def parse_dict(cls, d: dict): Accepts either ``"name"`` or ``"type"`` as the optimizer key. """ d = dict(d) # avoid mutating caller's dict - name = d.pop("name", None) or d.pop("type", None) + name = d.pop("name", "none") or d.pop("type", "none") if isinstance(name, type): name = name.__name__.lower() elif isinstance(name, str): @@ -445,7 +445,7 @@ def parse_dict(cls, d: dict): Accepts either ``"name"`` or ``"type"`` as the scheduler key. """ d = dict(d) # avoid mutating caller's dict - name = d.pop("name", None) or d.pop("type", None) + name = d.pop("name", "none") or d.pop("type", "none") if isinstance(name, type): name = name.__name__.lower() elif isinstance(name, str): From 342bae6fc1405b2ad22da4cc78e1e8263b96d926 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Wed, 4 Mar 2026 16:59:23 -0800 Subject: [PATCH 144/335] Pytests and changed some defaults in optimizer_mixin.py --- tests/ml/test_optimizermixin.py | 295 ++++++++++++++++++++++++++++++++ 1 file changed, 295 insertions(+) create mode 100644 tests/ml/test_optimizermixin.py diff --git a/tests/ml/test_optimizermixin.py b/tests/ml/test_optimizermixin.py new file mode 100644 index 00000000..6fd6c22a --- /dev/null +++ b/tests/ml/test_optimizermixin.py @@ -0,0 +1,295 @@ +"""Tests for OptimizerParams and SchedulerParams dataclasses.""" + +import pytest + +# Now import the module under test — adjust the path if needed +from quantem.core.ml.optimizer_mixin import ( + OptimizerParams, + SchedulerParams, +) + +# ─── OptimizerParams defaults ─────────────────────────────────────────────── + + +class TestAdamDefaults: + def test_defaults(self): + adam = OptimizerParams.Adam() + assert adam.lr == 1e-3 + assert adam.betas == (0.9, 0.999) + assert adam.eps == 1e-8 + assert adam.weight_decay == 0 + assert adam._name == "adam" + + def test_params_dict(self): + adam = OptimizerParams.Adam(lr=0.01, weight_decay=1e-4) + p = adam.params() + assert p == { + "lr": 0.01, + "betas": (0.9, 0.999), + "eps": 1e-8, + "weight_decay": 1e-4, + } + + def test_custom_betas(self): + adam = OptimizerParams.Adam(betas=(0.8, 0.99)) + assert adam.params()["betas"] == (0.8, 0.99) + + +class TestAdamWDefaults: + def test_defaults(self): + adamw = OptimizerParams.AdamW() + assert adamw.lr == 1e-3 + assert adamw._name == "adamw" + + def test_params_dict(self): + adamw = OptimizerParams.AdamW(lr=5e-4, eps=1e-7) + p = adamw.params() + assert p["lr"] == 5e-4 + assert p["eps"] == 1e-7 + + +class TestSGDDefaults: + def test_defaults(self): + sgd = OptimizerParams.SGD() + assert sgd.lr == 1e-3 + assert sgd.momentum == 0 + assert sgd.dampening == 0 + assert sgd.nesterov is False + assert sgd._name == "sgd" + + def test_params_dict(self): + sgd = OptimizerParams.SGD(lr=0.1, momentum=0.9, nesterov=True) + p = sgd.params() + assert p == { + "lr": 0.1, + "momentum": 0.9, + "dampening": 0, + "weight_decay": 0, + "nesterov": True, + } + + +class TestNoneOptimizer: + def test_defaults(self): + none_opt = OptimizerParams.NoneOptimizer() + assert none_opt._name == "none" + assert none_opt.params() == {} + + +# ─── OptimizerParams.parse_dict ───────────────────────────────────────────── + + +class TestOptimizerParseDict: + def test_parse_adam(self): + result = OptimizerParams.parse_dict({"name": "adam", "lr": 0.01}) + assert isinstance(result, OptimizerParams.Adam) + assert result.lr == 0.01 + + def test_parse_adamw(self): + result = OptimizerParams.parse_dict({"name": "adamw", "weight_decay": 0.1}) + assert isinstance(result, OptimizerParams.AdamW) + assert result.weight_decay == 0.1 + + def test_parse_sgd(self): + result = OptimizerParams.parse_dict({"name": "sgd", "momentum": 0.9}) + assert isinstance(result, OptimizerParams.SGD) + assert result.momentum == 0.9 + + def test_parse_case_insensitive(self): + result = OptimizerParams.parse_dict({"name": "Adam"}) + assert isinstance(result, OptimizerParams.Adam) + + def test_parse_unknown_raises(self): + with pytest.raises(ValueError, match="Unknown optimizer type"): + OptimizerParams.parse_dict({"name": "rmsprop"}) + + def test_parse_does_not_mutate_input(self): + d = {"name": "adam", "lr": 0.01} + original = dict(d) + OptimizerParams.parse_dict(d) + assert d == original + + def test_parse_invalid_name_type_raises(self): + with pytest.raises(ValueError, match="Unknown optimizer type"): + OptimizerParams.parse_dict({"name": 42}) + + +# ─── SchedulerParams defaults ─────────────────────────────────────────────── + + +class TestPlateauDefaults: + def test_defaults(self): + p = SchedulerParams.Plateau() + assert p.mode == "min" + assert p.factor == 0.5 + assert p.patience == 10 + assert p.cooldown == 50 + assert p.min_lr is None + assert p._name == "plateau" + + def test_params_computes_min_lr(self): + p = SchedulerParams.Plateau() + result = p.params(base_LR=0.01) + assert result["min_lr"] == pytest.approx(0.01 / 20) + + def test_params_explicit_min_lr(self): + p = SchedulerParams.Plateau(min_lr=1e-6) + result = p.params(base_LR=0.01) + assert result["min_lr"] == 1e-6 + + +class TestExponentialDefaults: + def test_defaults(self): + e = SchedulerParams.Exponential() + assert e.gamma == 0.9 + assert e._name == "exponential" + + def test_params_with_num_iter(self): + e = SchedulerParams.Exponential(factor=None) + result = e.params(base_LR=0.01, num_iter=100) + assert result == {"gamma": 0.9} + + def test_params_factor_overrides_gamma(self): + e = SchedulerParams.Exponential(factor=0.01) + result = e.params(base_LR=0.01, num_iter=100) + expected_gamma = 0.01 ** (1.0 / 100) + assert result["gamma"] == pytest.approx(expected_gamma) + + def test_params_no_num_iter_raises(self): + e = SchedulerParams.Exponential() + with pytest.raises(ValueError, match="num_iter must be set"): + e.params(base_LR=0.01, num_iter=None) + + def test_params_uses_own_num_iter(self): + e = SchedulerParams.Exponential(num_iter=50, factor=None) + result = e.params(base_LR=0.01, num_iter=None) + assert result == {"gamma": 0.9} + + +class TestCyclicDefaults: + def test_defaults(self): + c = SchedulerParams.Cyclic() + assert c.mode == "triangular2" + assert c.cycle_momentum is False + assert c._name == "cyclic" + + def test_params_computes_lr_bounds(self): + c = SchedulerParams.Cyclic() + result = c.params(base_LR=0.01) + assert result["base_lr"] == pytest.approx(0.01 / 4) + assert result["max_lr"] == pytest.approx(0.01 * 4) + + def test_params_explicit_lr_bounds(self): + c = SchedulerParams.Cyclic(base_lr=0.001, max_lr=0.1) + result = c.params(base_LR=999.0) # should be ignored + assert result["base_lr"] == 0.001 + assert result["max_lr"] == 0.1 + + +class TestLinearDefaults: + def test_defaults(self): + test = SchedulerParams.Linear() + assert test.start_factor == 0.1 + assert test.end_factor == 1.0 + assert test._name == "linear" + + def test_params_uses_num_iter(self): + test = SchedulerParams.Linear() + result = test.params(base_LR=0.01, num_iter=200) + assert result["total_iters"] == 200 + + def test_params_explicit_total_iters(self): + test = SchedulerParams.Linear(total_iters=50) + result = test.params(base_LR=0.01, num_iter=200) + assert result["total_iters"] == 50 + + def test_params_no_iters_raises(self): + test = SchedulerParams.Linear() + with pytest.raises(ValueError, match="total_iters must be set"): + test.params(base_LR=0.01, num_iter=None) + + +class TestCosineAnnealingDefaults: + def test_defaults(self): + ca = SchedulerParams.CosineAnnealing() + assert ca.eta_min == 1e-7 + assert ca.T_max is None + assert ca._name == "cosine_annealing" + + def test_params_uses_num_iter(self): + ca = SchedulerParams.CosineAnnealing() + result = ca.params(base_LR=0.01, num_iter=300) + assert result["T_max"] == 300 + + def test_params_explicit_T_max(self): + ca = SchedulerParams.CosineAnnealing(T_max=150) + result = ca.params(base_LR=0.01, num_iter=300) + assert result["T_max"] == 150 + + def test_params_no_T_max_raises(self): + ca = SchedulerParams.CosineAnnealing() + with pytest.raises(ValueError, match="T_max must be set"): + ca.params(base_LR=0.01, num_iter=None) + + +class TestNoneScheduler: + def test_defaults(self): + ns = SchedulerParams.NoneScheduler() + assert ns._name == "none" + assert ns.params(base_LR=0.01) == {} + + +# ─── SchedulerParams.parse_dict ───────────────────────────────────────────── + + +class TestSchedulerParseDict: + def test_parse_plateau(self): + result = SchedulerParams.parse_dict({"name": "plateau", "patience": 20}) + assert isinstance(result, SchedulerParams.Plateau) + assert result.patience == 20 + + def test_parse_exponential(self): + result = SchedulerParams.parse_dict({"name": "exponential", "gamma": 0.95}) + assert isinstance(result, SchedulerParams.Exponential) + assert result.gamma == 0.95 + + def test_parse_cyclic(self): + result = SchedulerParams.parse_dict({"name": "cyclic", "step_size_up": 50}) + assert isinstance(result, SchedulerParams.Cyclic) + assert result.step_size_up == 50 + + def test_parse_linear(self): + result = SchedulerParams.parse_dict({"name": "linear", "start_factor": 0.5}) + assert isinstance(result, SchedulerParams.Linear) + assert result.start_factor == 0.5 + + def test_parse_cosine_annealing(self): + result = SchedulerParams.parse_dict({"name": "cosine_annealing", "T_max": 100}) + assert isinstance(result, SchedulerParams.CosineAnnealing) + assert result.T_max == 100 + + def test_parse_none(self): + result = SchedulerParams.parse_dict({"name": "none"}) + assert isinstance(result, SchedulerParams.NoneScheduler) + + def test_parse_case_insensitive(self): + result = SchedulerParams.parse_dict({"name": "Plateau"}) + assert isinstance(result, SchedulerParams.Plateau) + + def test_parse_unknown_raises(self): + with pytest.raises(ValueError, match="Unknown scheduler type"): + SchedulerParams.parse_dict({"name": "warmup"}) + + def test_parse_does_not_mutate_input(self): + d = {"name": "plateau", "patience": 5} + original = dict(d) + SchedulerParams.parse_dict(d) + assert d == original + + def test_parse_invalid_name_type_raises(self): + with pytest.raises(ValueError, match="Unknown scheduler type"): + SchedulerParams.parse_dict({"name": 3.14}) + + def test_parse_default_name_is_none(self): + result = SchedulerParams.parse_dict({}) + assert isinstance(result, SchedulerParams.NoneScheduler) From 4fb58629ff2703713d396f7be917a613abd38f48 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Wed, 4 Mar 2026 19:26:39 -0800 Subject: [PATCH 145/335] Bugs in tomography_opt fixed; scheduler and optimizer of pose not being stepped due to new type-hinting way we're doing it. --- src/quantem/tomography/object_models.py | 113 ++++++++++++++-------- src/quantem/tomography/tomography.py | 32 ++++-- src/quantem/tomography/tomography_base.py | 16 ++- src/quantem/tomography/tomography_opt.py | 14 +-- 4 files changed, 115 insertions(+), 60 deletions(-) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 2fc0c58c..0acf6aab 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -20,24 +20,52 @@ object_type = Literal["potential"] -@dataclass(slots=False) -class DefaultConstraintsTomography(Constraints): - """ - Data class for all constraints that can be applied to the object model. - """ +class ObjConstraintParams: + @dataclass + class ObjPixelatedConstraints(Constraints): + positivity: bool = True + shrinkage: float = 0.0 + tv_vol: float = 0.0 + _name: str = "obj_pixelated" + + soft_constraint_keys = ["tv_vol"] + hard_constraint_keys = ["positivity", "shrinkage"] + + @dataclass + class ObjINRConstraints(Constraints): + positivity: bool = True + shrinkage: float = 0.0 + tv_vol: float = 0.0 + sparsity: float = 0.0 + _name: str = "obj_inr" + + soft_constraint_keys = ["tv_vol", "sparsity"] + hard_constraint_keys = ["positivity", "shrinkage"] - # Hard Constraints - positivity: bool = True - shrinkage: float = 0.0 - circular_mask: bool = False - fourier_filter: str | None = None # Hamming, etc... + @classmethod + def parse_dict(cls, d: dict) -> ObjPixelatedConstraints | ObjINRConstraints: + """ + Parse dictionary to an object constraint params object. + """ + d = dict(d) + name = d.pop("name", "none") or d.pop("type", "none") + if isinstance(name, type): + name = name.__name__.lower() + elif isinstance(name, str): + name = name.lower() + else: + raise ValueError(f"Unknown object constraint type: {name}") + if name == "obj_pixelated": + return ObjConstraintParams.ObjPixelatedConstraints(**d) + elif name == "obj_inr": + return ObjConstraintParams.ObjINRConstraints(**d) + else: + raise ValueError(f"Unknown object constraint type: {name.lower()}") - # Soft Constraints - tv_vol: float = 0.0 - sparsity: float = 0.0 - soft_constraint_keys = ["tv_vol", "sparsity"] - hard_constraint_keys = ["positivity", "shrinkage", "circular_mask", "fourier_filter"] +ObjConstraintsType = ( + ObjConstraintParams.ObjPixelatedConstraints | ObjConstraintParams.ObjINRConstraints +) class ObjectBase(AutoSerialize, nn.Module, RNGMixin, OptimizerMixin): @@ -134,30 +162,9 @@ def to(self, *args, **kwargs): raise NotImplementedError -class ObjectConstraints(BaseConstraints, ObjectBase): - DEFAULT_CONSTRAINTS = DefaultConstraintsTomography() - +class ObjectConstraints(BaseConstraints, ObjectBase): # TODO: Ask Arthur why we still need this def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.constraints: DefaultConstraintsTomography = self.DEFAULT_CONSTRAINTS.copy() - - def apply_hard_constraints( - self, - pred: torch.Tensor, - ) -> torch.Tensor: - """ - Apply hard constraints to the object model. - - Only hard constraint here is the positivity and shrinkage. TODO: Add the other hard constraints. - """ - obj2 = pred.clone() - if self.constraints.positivity: - obj2 = torch.clamp(obj2, min=0.0, max=None) - if self.constraints.shrinkage: - obj2 = torch.max(obj2 - self.constraints.shrinkage, torch.zeros_like(obj2)) - - # TODO: Need to implement the other hard constraints: Fourier Filter and Circular Mask. - return obj2 @abstractmethod def get_tv_loss(self, **kwargs) -> torch.Tensor: @@ -174,6 +181,8 @@ class ObjectPixelated(ObjectConstraints): Supports: Conventional algorithms (SIRT, FBP), and AD-based reconstructions. """ + DEFAULT_CONSTRAINTS = ObjConstraintParams.ObjPixelatedConstraints() + def __init__( self, shape: tuple[int, int, int], @@ -186,6 +195,7 @@ def __init__( rng=rng, _token=self._token, ) + self.constraints: ObjConstraintsType = self.DEFAULT_CONSTRAINTS.copy() # --- Instantiation ---- @classmethod @@ -245,7 +255,7 @@ def soft_loss(self) -> torch.Tensor: @property def name(self) -> str: - return "ObjectPixelated" + return "obj_pixelated" @property def obj_type(self) -> str: @@ -255,6 +265,24 @@ def obj_type(self) -> str: def dtype(self) -> torch.dtype: return self._obj.dtype + def apply_hard_constraints( + self, + pred: torch.Tensor, + ) -> torch.Tensor: + """ + Apply hard constraints to the object model. + + Only hard constraint here is the positivity and shrinkage. TODO: Add the other hard constraints. + """ + obj2 = pred.clone() + if self.constraints.positivity: + obj2 = torch.clamp(obj2, min=0.0, max=None) + if self.constraints.shrinkage: + obj2 = torch.max(obj2 - self.constraints.shrinkage, torch.zeros_like(obj2)) + + # TODO: Need to implement the other hard constraints: Fourier Filter and Circular Mask. + return obj2 + def apply_soft_constraints(self, obj: torch.Tensor) -> torch.Tensor: soft_loss = torch.tensor(0.0, device=obj.device, dtype=obj.dtype, requires_grad=True) if self.constraints.tv_vol > 0: @@ -288,6 +316,8 @@ def to(self, device: str | torch.device): # pyright: ignore[reportIncompatibleM class ObjectINR(ObjectConstraints, DDPMixin): + DEFAULT_CONSTRAINTS = ObjConstraintParams.ObjINRConstraints() + def __init__( self, shape: tuple[int, int, int], @@ -304,7 +334,7 @@ def __init__( ) self._pretrain_losses = [] self._pretrain_lrs = [] - + self.constraints: ObjConstraintsType = self.DEFAULT_CONSTRAINTS.copy() # Register the network submodule (important: real nn.Module attribute) if model is not None: self.setup_distributed(device=device) @@ -393,7 +423,10 @@ def apply_soft_constraints( grad_norm = torch.norm(grad_outputs, dim=1) # Shape: [num_samples] soft_loss += self.constraints.tv_vol * grad_norm.mean() - if self.constraints.sparsity > 0: + if ( + isinstance(self.constraints, ObjConstraintParams.ObjINRConstraints) + and self.constraints.sparsity > 0 + ): # NOTE: For the linter, I must make this :) sparsity_loss = self.constraints.sparsity * torch.norm(pred, p=1) soft_loss += sparsity_loss diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index f0b05b52..2e20e717 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -16,7 +16,8 @@ from quantem.tomography.dataset_models import DatasetModelType, DefaultTomographyDatasetConstraints from quantem.tomography.logger_tomography import LoggerTomography from quantem.tomography.object_models import ( - DefaultConstraintsTomography, + ObjConstraintParams, + ObjConstraintsType, ObjectINR, ObjectPixelated, ) @@ -59,7 +60,7 @@ def reconstruct( reset: bool = False, optimizer_params: dict | None = None, scheduler_params: dict | None = None, - obj_constraints: dict = {}, # TODO: What to pass into the constraints? + obj_constraints: dict | ObjConstraintsType | None = None, dset_constraints: dict = {}, num_samples_per_ray: int | list[tuple[int, int]] | None = None, profiling_mode: bool = False, @@ -109,7 +110,10 @@ def reconstruct( self.set_schedulers(self.scheduler_params, num_iter=num_iter) if obj_constraints is not None: - self.obj_model.constraints = cast(DefaultConstraintsTomography, obj_constraints) + if isinstance(obj_constraints, dict): + obj_constraints = ObjConstraintParams.parse_dict(obj_constraints) + + self.obj_model.constraints = obj_constraints if dset_constraints is not None: self.dset.constraints = cast(DefaultTomographyDatasetConstraints, dset_constraints) @@ -277,6 +281,9 @@ def reconstruct( dataset_model=self.dset, iter=self.num_epochs, ) + pbar.set_description( + f"Reconstruction | Loss: {total_loss:.5e}, Consistency Loss: {consistency_loss:.5e}, Soft Constraint Loss: {epoch_soft_constraint_loss:.5e} | Images Logged" + ) if self.global_rank == 0: self.logger.log_iter( @@ -291,10 +298,6 @@ def reconstruct( self.logger.flush() - pbar.set_description( - f"Reconstruction | Loss: {total_loss:.5e}, Consistency Loss: {consistency_loss:.5e}, Soft Constraint Loss: {epoch_soft_constraint_loss:.5e} | Logger Updated" - ) - # --- Helper Functions --- def save_volume(self, path: str = "recon_volume.npz", overwrite: bool = False): @@ -392,18 +395,27 @@ def from_models( def reconstruct( self, num_iter: int = 10, + obj_constraints: dict | ObjConstraintsType | None = None, mode: Literal["sirt", "fbp"] = "sirt", reset: bool = False, inline_alignment: bool = False, smoothing_sigma: float | None = None, ): - pbar = tqdm(range(num_iter), desc=f"{mode} Reconstruction", disable=not self.verbose) + if obj_constraints is not None: + if isinstance(obj_constraints, dict): + obj_constraints = ObjConstraintParams.parse_dict(obj_constraints) + + self.obj_model.constraints = obj_constraints + + pbar = tqdm( + range(num_iter), + desc=f"{mode} Reconstruction | Loss: {0:.4f}", + disable=not self.verbose, + ) if mode == "sirt" or mode == "fbp": proj_forward = torch.zeros_like(self.dset.tilt_stack).permute(2, 0, 1) else: proj_forward = torch.zeros_like(self.dset.tilt_stack) - print("proj_forward.shape", proj_forward.shape) - print("self.dset.tilt_stack.shape", self.dset.tilt_stack.shape) if smoothing_sigma is not None: gaussian_kernel = gaussian_kernel_1d(smoothing_sigma).to(self.device) diff --git a/src/quantem/tomography/tomography_base.py b/src/quantem/tomography/tomography_base.py index f4d80096..50a16024 100644 --- a/src/quantem/tomography/tomography_base.py +++ b/src/quantem/tomography/tomography_base.py @@ -7,7 +7,8 @@ from quantem.tomography.dataset_models import DatasetModelType, TomographyDatasetBase from quantem.tomography.logger_tomography import LoggerTomography from quantem.tomography.object_models import ( - DefaultConstraintsTomography, + ObjConstraintParams, + ObjConstraintsType, ObjectINR, ObjectModelType, ) @@ -86,12 +87,19 @@ def obj_model(self, obj_model: ObjectModelType): self._obj_model = obj_model @property - def constraints(self) -> DefaultConstraintsTomography: + def constraints(self) -> ObjConstraintsType: # TODO: Also looks at Dataset constraints return self.obj_model.constraints @constraints.setter - def constraints(self, constraints: DefaultConstraintsTomography): - self.obj_model.constraints = constraints + def constraints(self, constraints: ObjConstraintsType | dict | None): + if constraints is None: + return + elif isinstance(constraints, dict): + self.obj_model.constraints = ObjConstraintParams.parse_dict(constraints) + elif isinstance(constraints, ObjConstraintsType): + self.obj_model.constraints = constraints + else: + raise ValueError(f"Invalid constraints type: {type(constraints)}") @property def logger(self) -> LoggerTomography | None: diff --git a/src/quantem/tomography/tomography_opt.py b/src/quantem/tomography/tomography_opt.py index 404e2f4b..633767e0 100644 --- a/src/quantem/tomography/tomography_opt.py +++ b/src/quantem/tomography/tomography_opt.py @@ -143,23 +143,25 @@ def step_optimizers(self): for key in self.optimizer_params.keys(): if self.obj_model.has_optimizer(): self.obj_model.step_optimizer() - elif self.dset.has_optimizer(): + if self.dset.has_optimizer(): self.dset.step_optimizer() - else: + if key not in self.OPTIMIZABLE_VALS: raise ValueError(f"Unknown optimization key: {key}") def zero_grad_all(self): for key in self.optimizer_params.keys(): if self.obj_model.has_optimizer(): self.obj_model.zero_optimizer_grad() - elif self.dset.has_optimizer(): + if self.dset.has_optimizer(): self.dset.zero_optimizer_grad() - else: + if key not in self.OPTIMIZABLE_VALS: raise ValueError(f"Unknown optimization key: {key}") def step_schedulers(self, loss: float | None = None): for key in self.scheduler_params.keys(): - if key == "object" and self.obj_model.scheduler is not None: + if self.obj_model.scheduler is not None and key == "object": self.obj_model.step_scheduler(loss) - elif self.dset.scheduler is not None and key == "pose": + if self.dset.scheduler is not None and key == "pose": self.dset.step_scheduler(loss) + if key not in self.OPTIMIZABLE_VALS: + raise ValueError(f"Unknown optimization key: {key}") From 04a7d65cbd457d9fbe76fd2ea07358a59ee057c0 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Wed, 4 Mar 2026 19:49:00 -0800 Subject: [PATCH 146/335] Bug fixes in OptimizerMixin --- src/quantem/core/ml/optimizer_mixin.py | 4 +++- src/quantem/tomography/object_models.py | 2 +- src/quantem/tomography/tomography_opt.py | 7 +++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index e9dc9599..a14fc6c8 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -159,7 +159,7 @@ def parse_dict(cls, d: dict): Accepts either ``"name"`` or ``"type"`` as the optimizer key. """ d = dict(d) # avoid mutating caller's dict - name = d.pop("name", "none") or d.pop("type", "none") + name = d.pop("name", None) or d.pop("type", "none") if isinstance(name, type): name = name.__name__.lower() elif isinstance(name, str): @@ -172,6 +172,8 @@ def parse_dict(cls, d: dict): return OptimizerParams.AdamW(**d) elif name == "sgd": return OptimizerParams.SGD(**d) + elif name == "none": + return OptimizerParams.NoneOptimizer() else: raise ValueError(f"Unknown optimizer type: {name.lower()}") diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 0acf6aab..c3a47fb9 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -48,7 +48,7 @@ def parse_dict(cls, d: dict) -> ObjPixelatedConstraints | ObjINRConstraints: Parse dictionary to an object constraint params object. """ d = dict(d) - name = d.pop("name", "none") or d.pop("type", "none") + name = d.pop("name", None) or d.pop("type", "none") if isinstance(name, type): name = name.__name__.lower() elif isinstance(name, str): diff --git a/src/quantem/tomography/tomography_opt.py b/src/quantem/tomography/tomography_opt.py index 633767e0..1dd406ab 100644 --- a/src/quantem/tomography/tomography_opt.py +++ b/src/quantem/tomography/tomography_opt.py @@ -1,6 +1,6 @@ import torch -from quantem.core.ml.optimizer_mixin import OptimizerType +from quantem.core.ml.optimizer_mixin import OptimizerParams, OptimizerType from quantem.tomography.tomography_base import TomographyBase @@ -36,7 +36,7 @@ def optimizer_params(self) -> dict[str, dict]: } @optimizer_params.setter - def optimizer_params(self, d: dict[str, OptimizerType]): + def optimizer_params(self, d: dict[str, OptimizerType] | dict[str, dict]): """Set the optimizer parameters.""" if isinstance(d, (tuple, list)): d = {k: {} for k in d} @@ -51,8 +51,7 @@ def optimizer_params(self, d: dict[str, OptimizerType]): raise ValueError(f"Unknown optimization key: {k}") if not isinstance(v, OptimizerType): - if "lr" not in v: - v["lr"] = self._get_default_lr(k) + v = OptimizerParams.parse_dict(v) targets[k].optimizer_params = v From d3beb502662b252f097ea3b0602cba7745f060a5 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Wed, 4 Mar 2026 19:51:29 -0800 Subject: [PATCH 147/335] Scheduler bug fix as well in parse_dict --- src/quantem/core/ml/optimizer_mixin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index a14fc6c8..94c12518 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -446,7 +446,7 @@ def parse_dict(cls, d: dict): Accepts either ``"name"`` or ``"type"`` as the scheduler key. """ d = dict(d) # avoid mutating caller's dict - name = d.pop("name", "none") or d.pop("type", "none") + name = d.pop("name", None) or d.pop("type", "none") if isinstance(name, type): name = name.__name__.lower() elif isinstance(name, str): From d96808ba4994b434a51fbe16e893d3be35ee48dd Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Wed, 4 Mar 2026 20:00:42 -0800 Subject: [PATCH 148/335] Hot-fix for OptimizerMixin bug with or statement --- src/quantem/core/ml/optimizer_mixin.py | 618 +++++++++++++++++++++---- 1 file changed, 531 insertions(+), 87 deletions(-) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index e2d2e89a..94c12518 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -1,5 +1,6 @@ from abc import abstractmethod -from typing import TYPE_CHECKING, Generator, Iterator, Sequence +from dataclasses import dataclass +from typing import TYPE_CHECKING, Generator, Iterator, Literal, Sequence from quantem.core import config @@ -10,6 +11,474 @@ import torch +class OptimizerParams: + """ + Container for optimizer parameter dataclasses. + + Each nested class configures a specific PyTorch optimizer and can be passed + directly to ``OptimizerMixin.set_optimizer``, or constructed from a dict via + ``OptimizerParams.parse_dict``. + + Supported optimizers + -------------------- + Adam + ``torch.optim.Adam`` — adaptive moment estimation. + AdamW + ``torch.optim.AdamW`` — Adam with decoupled weight decay. + SGD + ``torch.optim.SGD`` — stochastic gradient descent with optional momentum and Nesterov. + NoneOptimizer + Sentinel that disables / removes the optimizer. + + Examples + -------- + >>> obj.set_optimizer(OptimizerParams.Adam(lr=1e-4)) + >>> obj.set_optimizer({"name": "adam", "lr": 1e-4}) # equivalent dict form + """ + + @dataclass + class Adam: + """ + Adam optimizer (``torch.optim.Adam``). + + Parameters + ---------- + lr : float + Learning rate. Default: 1e-3. + betas : tuple[float, float] + Coefficients for computing running averages of the gradient and its square. + Default: (0.9, 0.999). + eps : float + Term added to the denominator for numerical stability. Default: 1e-8. + weight_decay : float + L2 regularisation penalty. Default: 0. + """ + + lr: float = 1e-3 + betas: tuple[float, float] = (0.9, 0.999) + eps: float = 1e-8 + weight_decay: float = 0 + _name: str = "adam" + + def params(self) -> dict: + return { + "lr": self.lr, + "betas": self.betas, + "eps": self.eps, + "weight_decay": self.weight_decay, + } + + @dataclass + class AdamW: + """ + AdamW optimizer (``torch.optim.AdamW``). + + Identical to Adam but applies weight decay directly to the parameters + rather than folding it into the gradient update (decoupled weight decay). + + Parameters + ---------- + lr : float + Learning rate. Default: 1e-3. + betas : tuple[float, float] + Coefficients for computing running averages of the gradient and its square. + Default: (0.9, 0.999). + eps : float + Term added to the denominator for numerical stability. Default: 1e-8. + weight_decay : float + Decoupled L2 regularisation penalty. Default: 0. + """ + + lr: float = 1e-3 + betas: tuple[float, float] = (0.9, 0.999) + eps: float = 1e-8 + weight_decay: float = 0 + _name: str = "adamw" + + def params(self) -> dict: + return { + "lr": self.lr, + "betas": self.betas, + "eps": self.eps, + "weight_decay": self.weight_decay, + } + + @dataclass + class SGD: + """ + SGD optimizer (``torch.optim.SGD``). + + Parameters + ---------- + lr : float + Learning rate. Default: 1e-3. + momentum : float + Momentum factor. Default: 0. + dampening : float + Dampening for momentum. Default: 0. + weight_decay : float + L2 regularisation penalty. Default: 0. + nesterov : bool + Enables Nesterov momentum. Default: False. + """ + + lr: float = 1e-3 + momentum: float = 0 + dampening: float = 0 + weight_decay: float = 0 + nesterov: bool = False + _name: str = "sgd" + + def params(self) -> dict: + return { + "lr": self.lr, + "momentum": self.momentum, + "dampening": self.dampening, + "weight_decay": self.weight_decay, + "nesterov": self.nesterov, + } + + @dataclass + class NoneOptimizer: + """ + Sentinel optimizer that disables optimization. + + Passing this to ``set_optimizer`` will call ``remove_optimizer``, + clearing both the optimizer and scheduler. + """ + + _name: str = "none" + + def params(self) -> dict: + return {} + + @classmethod + def parse_dict(cls, d: dict): + """ + Parse dictionary to a optimizer params object. + Accepts either ``"name"`` or ``"type"`` as the optimizer key. + """ + d = dict(d) # avoid mutating caller's dict + name = d.pop("name", None) or d.pop("type", "none") + if isinstance(name, type): + name = name.__name__.lower() + elif isinstance(name, str): + name = name.lower() + else: + raise ValueError(f"Unknown optimizer type: {name}") + if name == "adam": + return OptimizerParams.Adam(**d) + elif name == "adamw": + return OptimizerParams.AdamW(**d) + elif name == "sgd": + return OptimizerParams.SGD(**d) + elif name == "none": + return OptimizerParams.NoneOptimizer() + else: + raise ValueError(f"Unknown optimizer type: {name.lower()}") + + +OptimizerType = ( + OptimizerParams.Adam + | OptimizerParams.AdamW + | OptimizerParams.SGD + | OptimizerParams.NoneOptimizer +) + + +class SchedulerParams: + """ + Container for learning-rate scheduler parameter dataclasses. + + Each nested class configures a specific PyTorch LR scheduler and can be passed + directly to ``OptimizerMixin.set_scheduler``, or constructed from a dict via + ``SchedulerParams.parse_dict``. + + Supported schedulers + -------------------- + Plateau + ``torch.optim.lr_scheduler.ReduceLROnPlateau`` — reduce LR when a metric stops improving. + Exponential + ``torch.optim.lr_scheduler.ExponentialLR`` — multiply LR by ``gamma`` each step. + Cyclic + ``torch.optim.lr_scheduler.CyclicLR`` — cycle LR between ``base_lr`` and ``max_lr``. + Linear + ``torch.optim.lr_scheduler.LinearLR`` — linearly interpolate LR over ``total_iters`` steps. + CosineAnnealing + ``torch.optim.lr_scheduler.CosineAnnealingLR`` — cosine-annealing LR schedule. + NoneScheduler + Sentinel that disables / removes the scheduler. + + Examples + -------- + >>> obj.set_scheduler(SchedulerParams.Plateau(factor=0.5, patience=10, cooldown=20)) + >>> obj.set_scheduler({"name": "plateau", "factor": 0.5}) # equivalent dict form + """ + + @dataclass + class Plateau: + """ + ReduceLROnPlateau scheduler (``torch.optim.lr_scheduler.ReduceLROnPlateau``). + + Reduces the learning rate when a monitored metric stops improving. + + Parameters + ---------- + mode : {'min', 'max'} + Whether the monitored metric should be minimised or maximised. Default: 'min'. + min_lr_factor : float + Sets ``min_lr = min_lr_factor * base_lr`` when ``min_lr`` is not provided. + Default: 1/20. + min_lr : float or None + Absolute lower bound on the learning rate. Overrides ``min_lr_factor`` when set. + Default: None. + factor : float + Factor by which the LR is reduced: ``new_lr = lr * factor``. Default: 0.5. + patience : int + Number of epochs with no improvement before reducing LR. Default: 10. + threshold : float + Minimum change in the monitored metric to qualify as an improvement. Default: 1e-5. + cooldown : int + Number of epochs to wait after a LR reduction before resuming normal operation. + Default: 50. + """ + + mode: Literal["min", "max"] = "min" + min_lr_factor: float = 1 / 20 + min_lr: float | None = None + factor: float = 0.5 + patience: int = 10 + threshold: float = 1e-5 + cooldown: int = 50 + _name: str = "plateau" + + def params(self, base_LR: float, num_iter: int | None = None) -> dict: + if self.min_lr is None: + self.min_lr = self.min_lr_factor * base_LR + return { + "mode": self.mode, + "factor": self.factor, + "patience": self.patience, + "threshold": self.threshold, + "min_lr": self.min_lr, + "cooldown": self.cooldown, + } + + @dataclass + class Exponential: + """ + ExponentialLR scheduler (``torch.optim.lr_scheduler.ExponentialLR``). + + Multiplies the learning rate by ``gamma`` after each step. + + Parameters + ---------- + gamma : float + Multiplicative decay factor applied each step. Default: 0.9. + factor : float or None + Reserved for future use. Default: 0.5. + num_iter : int or None + Reserved for future use. Default: None. + """ + + gamma: float = 0.9 + factor: float | None = None + num_iter: int | None = None + _name: str = "exponential" + + def params(self, base_LR: float, num_iter: int | None = None) -> dict: + effective_num_iter = self.num_iter if self.num_iter is not None else num_iter + if effective_num_iter is None: + raise ValueError("num_iter must be set if num_iter is not provided") + + self.num_iter = effective_num_iter + + if self.factor is not None: + self.gamma = self.factor ** (1.0 / effective_num_iter) + + return { + "gamma": self.gamma, + } + + @dataclass + class Cyclic: + """ + CyclicLR scheduler (``torch.optim.lr_scheduler.CyclicLR``). + + Cycles the learning rate between a lower bound (``base_lr``) and an upper + bound (``max_lr``). Bounds can be set directly or derived from the optimizer's + base LR via the factor parameters. + + Parameters + ---------- + base_lr_factor : float + Sets ``base_lr = base_lr_factor * optimizer_lr`` when ``base_lr`` is not provided. + Default: 1/4. + max_lr_factor : float + Sets ``max_lr = max_lr_factor * optimizer_lr`` when ``max_lr`` is not provided. + Default: 4. + base_lr : float or None + Absolute lower bound of the LR cycle. Overrides ``base_lr_factor`` when set. + Default: None. + max_lr : float or None + Absolute upper bound of the LR cycle. Overrides ``max_lr_factor`` when set. + Default: None. + step_size_up : int + Number of steps in the increasing half of each cycle. Default: 100. + step_size_down : int + Number of steps in the decreasing half of each cycle. Default: 100. + mode : {'triangular2', 'triangular', 'exp_range'} + Cycling policy. Default: 'triangular2'. + cycle_momentum : bool + If True, cycles momentum inversely to the learning rate. Default: False. + """ + + base_lr_factor: float = 1 / 4 + max_lr_factor: float = 4 + base_lr: float | None = None + max_lr: float | None = None + step_size_up: int = 100 + step_size_down: int = 100 + mode: Literal["triangular2", "triangular", "exp_range"] = "triangular2" + cycle_momentum: bool = False + _name: str = "cyclic" + + def params(self, base_LR: float, num_iter: int | None = None) -> dict: + if self.base_lr is None: + self.base_lr = self.base_lr_factor * base_LR + if self.max_lr is None: + self.max_lr = self.max_lr_factor * base_LR + return { + "base_lr": self.base_lr, + "max_lr": self.max_lr, + "step_size_up": self.step_size_up, + "step_size_down": self.step_size_down, + "mode": self.mode, + "cycle_momentum": self.cycle_momentum, + } + + @dataclass + class Linear: + """ + LinearLR scheduler (``torch.optim.lr_scheduler.LinearLR``). + + Linearly interpolates the learning rate from ``start_factor * base_lr`` to + ``end_factor * base_lr`` over ``total_iters`` steps. + + Parameters + ---------- + total_iters : int + Number of steps over which to interpolate the LR. Required. + start_factor : float + Multiplicative factor applied to the LR at the first step. Default: 0.1. + end_factor : float + Multiplicative factor applied to the LR at the final step. Default: 1.0. + """ + + total_iters: int | None = None + start_factor: float = 0.1 + end_factor: float = 1.0 + _name: str = "linear" + + def params(self, base_LR: float, num_iter: int | None = None) -> dict: + if num_iter is None and self.total_iters is None: + raise ValueError( + "total_iters must be set if num_iter is not provided" + ) # Should never be reached + if self.total_iters is None: + self.total_iters = num_iter + return { + "start_factor": self.start_factor, + "end_factor": self.end_factor, + "total_iters": self.total_iters, + } + + @dataclass + class CosineAnnealing: + """ + CosineAnnealingLR scheduler (``torch.optim.lr_scheduler.CosineAnnealingLR``). + + Anneals the learning rate following a cosine curve from the base LR down to + ``eta_min`` over ``T_max`` steps, then restarts. + + Parameters + ---------- + T_max : int + Maximum number of iterations (half-period of the cosine cycle). Required. + eta_min : float + Minimum learning rate at the trough of the cosine curve. Default: 1e-7. + """ + + eta_min: float = 1e-7 + T_max: int | None = None + _name: str = "cosine_annealing" + + def params(self, base_LR: float, num_iter: int | None = None) -> dict: + if num_iter is None and self.T_max is None: + raise ValueError( + "T_max must be set if num_iter is not provided" + ) # Should never be reached + if self.T_max is None: + self.T_max = num_iter + return { + "T_max": self.T_max, + "eta_min": self.eta_min, + } + + @dataclass + class NoneScheduler: + """ + Sentinel scheduler that disables LR scheduling. + + Passing this to ``set_scheduler`` clears the active scheduler without + affecting the optimizer. + """ + + _name: str = "none" + + def params(self, base_LR: float, num_iter: int | None = None) -> dict: + return {} + + @classmethod + def parse_dict(cls, d: dict): + """ + Parse dictionary to a scheduler params object. + Accepts either ``"name"`` or ``"type"`` as the scheduler key. + """ + d = dict(d) # avoid mutating caller's dict + name = d.pop("name", None) or d.pop("type", "none") + if isinstance(name, type): + name = name.__name__.lower() + elif isinstance(name, str): + name = name.lower() + else: + raise ValueError(f"Unknown scheduler type: {name}") + if name == "plateau": + return SchedulerParams.Plateau(**d) + elif name == "exponential": + return SchedulerParams.Exponential(**d) + elif name == "cyclic": + return SchedulerParams.Cyclic(**d) + elif name == "linear": + return SchedulerParams.Linear(**d) + elif name == "cosine_annealing": + return SchedulerParams.CosineAnnealing(**d) + elif name == "none": + return SchedulerParams.NoneScheduler() + else: + raise ValueError(f"Unknown scheduler type: {name}") + + +SchedulerType = ( + SchedulerParams.Plateau + | SchedulerParams.Exponential + | SchedulerParams.Cyclic + | SchedulerParams.Linear + | SchedulerParams.CosineAnnealing + | SchedulerParams.NoneScheduler +) + + class OptimizerMixin: """ Mixin class for handling optimizer and scheduler management. @@ -22,8 +491,8 @@ def __init__(self): """Initialize the optimizer mixin.""" self._optimizer = None self._scheduler = None - self._optimizer_params = {} - self._scheduler_params = {} + self._optimizer_params: OptimizerType = OptimizerParams.NoneOptimizer() + self._scheduler_params: SchedulerType = SchedulerParams.NoneScheduler() # Don't call super().__init__() in mixin classes to avoid MRO issues @property @@ -37,38 +506,32 @@ def scheduler(self) -> "torch.optim.lr_scheduler.LRScheduler | None": return self._scheduler @property - def optimizer_params(self) -> dict: + def optimizer_params(self) -> OptimizerType: """Get the optimizer parameters.""" return self._optimizer_params @optimizer_params.setter - def optimizer_params(self, params: dict): + def optimizer_params(self, params: OptimizerType | dict): """Set the optimizer parameters.""" - self._optimizer_params = params.copy() if params else {} + if isinstance(params, dict): + params = OptimizerParams.parse_dict(d=params) + if not isinstance(params, OptimizerType): + raise TypeError(f"optimizer parameters must be a OptimizerType, got {type(params)}") + self._optimizer_params = params @property - def scheduler_params(self) -> dict: + def scheduler_params(self) -> SchedulerType: """Get the scheduler parameters.""" return self._scheduler_params @scheduler_params.setter - def scheduler_params(self, params: dict): + def scheduler_params(self, params: SchedulerType | dict): """Set the scheduler parameters.""" - if params: - if params["type"].lower() not in [ - "cyclic", - "plateau", - "exp", - "gamma", - "linear", - "none", - ]: - raise ValueError( - f"Unknown scheduler type: {params['type']}, expected one of ['cyclic', 'plateau', 'exp', 'gamma', 'none']" - ) - self._scheduler_params = params.copy() - else: - self._scheduler_params = {} + if isinstance(params, dict): + params = SchedulerParams.parse_dict(d=params) + if not isinstance(params, SchedulerType): + raise TypeError(f"scheduler parameters must be a SchedulerType, got {type(params)}") + self._scheduler_params = params @abstractmethod def get_optimization_parameters( @@ -81,7 +544,7 @@ def get_optimization_parameters( """ raise NotImplementedError("Subclasses must implement get_optimization_parameters") - def set_optimizer(self, opt_params: dict | None = None) -> None: + def set_optimizer(self, opt_params: OptimizerType | dict | None = None) -> None: """ Set the optimizer for this model. Currently supports single LR for all parameters, TODO allow for per parameter LRs by @@ -94,10 +557,7 @@ def set_optimizer(self, opt_params: dict | None = None) -> None: self._optimizer = None return - opt_params = self._optimizer_params.copy() - opt_type = opt_params.pop("type", self.DEFAULT_OPTIMIZER_TYPE) - - if opt_type == "none": + if isinstance(self._optimizer_params, OptimizerParams.NoneOptimizer): self.remove_optimizer() return @@ -111,22 +571,18 @@ def set_optimizer(self, opt_params: dict | None = None) -> None: for p in params: p.requires_grad_(True) - if isinstance(opt_type, type): - self._optimizer = opt_type(params, **opt_params) - elif isinstance(opt_type, str): - if opt_type.lower() == "adam": - self._optimizer = torch.optim.Adam(params, **opt_params) - elif opt_type.lower() == "adamw": - self._optimizer = torch.optim.AdamW(params, **opt_params) - elif opt_type.lower() == "sgd": - self._optimizer = torch.optim.SGD(params, **opt_params) - else: - raise NotImplementedError(f"Unknown optimizer type: {opt_type}") - else: - raise TypeError(f"optimizer type must be string or type, got {type(opt_type)}") + match self._optimizer_params: + case OptimizerParams.Adam(): + self._optimizer = torch.optim.Adam(params, **self._optimizer_params.params()) + case OptimizerParams.AdamW(): + self._optimizer = torch.optim.AdamW(params, **self._optimizer_params.params()) + case OptimizerParams.SGD(): + self._optimizer = torch.optim.SGD(params, **self._optimizer_params.params()) + case _: + raise NotImplementedError(f"Unknown optimizer type: {self._optimizer_params}") def set_scheduler( - self, scheduler_params: dict | None = None, num_iter: int | None = None + self, scheduler_params: SchedulerType | dict | None = None, num_iter: int | None = None ) -> None: """Set the scheduler for this model.""" if scheduler_params is not None: @@ -136,51 +592,39 @@ def set_scheduler( self._scheduler = None return - params = self._scheduler_params - sched_type = params.get("type", "none").lower() optimizer = self._optimizer base_LR = optimizer.param_groups[0]["lr"] - - if sched_type == "none": - self._scheduler = None - elif sched_type == "cyclic": - self._scheduler = torch.optim.lr_scheduler.CyclicLR( - optimizer, - base_lr=params.get("base_lr", base_LR / 4), - max_lr=params.get("max_lr", base_LR * 4), - step_size_up=params.get("step_size_up", 100), - step_size_down=params.get("step_size_down", params.get("step_size_up", 100)), - mode=params.get("mode", "triangular2"), - cycle_momentum=params.get("momentum", False), - ) - elif sched_type.startswith(("plat", "reducelronplat")): - self._scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau( - optimizer, - mode="min", - factor=params.get("factor", 0.5), - patience=params.get("patience", 10), - threshold=params.get("threshold", 1e-3), - min_lr=params.get("min_lr", base_LR / 20), - cooldown=params.get("cooldown", 50), - ) - elif sched_type in ["exp", "gamma", "exponential"]: - if "gamma" in params: - gamma = params["gamma"] - elif num_iter is not None: - fac = params.get("factor", 0.01) - gamma = fac ** (1.0 / num_iter) - else: - gamma = 0.9 - self._scheduler = torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma=gamma) - elif sched_type == "linear": - self._scheduler = torch.optim.lr_scheduler.LinearLR( - optimizer, - start_factor=params.get("start_factor", 0.1), - end_factor=params.get("end_factor", 1.0), - total_iters=params.get("total_iters", num_iter), - ) - else: - raise ValueError(f"Unknown scheduler type: {sched_type}") + params = self._scheduler_params.params(base_LR, num_iter=num_iter) + match self.scheduler_params: + case SchedulerParams.NoneScheduler(): + self._scheduler = None + case SchedulerParams.Cyclic(): + self._scheduler = torch.optim.lr_scheduler.CyclicLR( + optimizer, + **params, + ) + case SchedulerParams.Plateau(): + self._scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau( + optimizer, + **params, + ) + case SchedulerParams.Exponential(): + self._scheduler = torch.optim.lr_scheduler.ExponentialLR( + optimizer, + **params, + ) + case SchedulerParams.Linear(): + self._scheduler = torch.optim.lr_scheduler.LinearLR( + optimizer, + **params, + ) + case SchedulerParams.CosineAnnealing(): + self._scheduler = torch.optim.lr_scheduler.CosineAnnealingLR( + optimizer, + **params, + ) + case _: + raise ValueError(f"Unknown scheduler type: {self.scheduler_params}") def step_optimizer(self) -> None: """Step the optimizer if it exists.""" @@ -214,9 +658,9 @@ def get_current_lr(self) -> float: def remove_optimizer(self) -> None: """Remove the optimizer and scheduler.""" self._optimizer = None - self._optimizer_params = {} + self._optimizer_params = OptimizerParams.NoneOptimizer() self._scheduler = None - self._scheduler_params = {} + self._scheduler_params = SchedulerParams.NoneScheduler() def reset_optimizer(self) -> None: """Reset the optimizer and scheduler.""" From 895cb1b50c9f3feb8b21ff8c64fd765ed0af7430 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Wed, 4 Mar 2026 20:03:34 -0800 Subject: [PATCH 149/335] Hotfix from tomo_refactor --- src/quantem/core/ml/optimizer_mixin.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index e9dc9599..94c12518 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -159,7 +159,7 @@ def parse_dict(cls, d: dict): Accepts either ``"name"`` or ``"type"`` as the optimizer key. """ d = dict(d) # avoid mutating caller's dict - name = d.pop("name", "none") or d.pop("type", "none") + name = d.pop("name", None) or d.pop("type", "none") if isinstance(name, type): name = name.__name__.lower() elif isinstance(name, str): @@ -172,6 +172,8 @@ def parse_dict(cls, d: dict): return OptimizerParams.AdamW(**d) elif name == "sgd": return OptimizerParams.SGD(**d) + elif name == "none": + return OptimizerParams.NoneOptimizer() else: raise ValueError(f"Unknown optimizer type: {name.lower()}") @@ -444,7 +446,7 @@ def parse_dict(cls, d: dict): Accepts either ``"name"`` or ``"type"`` as the scheduler key. """ d = dict(d) # avoid mutating caller's dict - name = d.pop("name", "none") or d.pop("type", "none") + name = d.pop("name", None) or d.pop("type", "none") if isinstance(name, type): name = name.__name__.lower() elif isinstance(name, str): From 8f5d3cb66669c13b79f0c81e556065ba1e3bceda Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Wed, 4 Mar 2026 20:13:00 -0800 Subject: [PATCH 150/335] Hotfix w/ better name or type checking --- src/quantem/core/ml/optimizer_mixin.py | 12 +++++- tests/ml/test_optimizermixin.py | 59 +++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index 94c12518..d30229da 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -159,7 +159,11 @@ def parse_dict(cls, d: dict): Accepts either ``"name"`` or ``"type"`` as the optimizer key. """ d = dict(d) # avoid mutating caller's dict - name = d.pop("name", None) or d.pop("type", "none") + name = d.pop("name", None) + type_ = d.pop("type", None) + name = name or type_ + if name is None: + raise ValueError("Must provide either 'name' or 'type' key") if isinstance(name, type): name = name.__name__.lower() elif isinstance(name, str): @@ -446,7 +450,11 @@ def parse_dict(cls, d: dict): Accepts either ``"name"`` or ``"type"`` as the scheduler key. """ d = dict(d) # avoid mutating caller's dict - name = d.pop("name", None) or d.pop("type", "none") + name = d.pop("name", None) + type_ = d.pop("type", None) + name = name or type_ + if name is None: + raise ValueError("Must provide either 'name' or 'type' key") if isinstance(name, type): name = name.__name__.lower() elif isinstance(name, str): diff --git a/tests/ml/test_optimizermixin.py b/tests/ml/test_optimizermixin.py index 6fd6c22a..3682d452 100644 --- a/tests/ml/test_optimizermixin.py +++ b/tests/ml/test_optimizermixin.py @@ -114,6 +114,61 @@ def test_parse_invalid_name_type_raises(self): OptimizerParams.parse_dict({"name": 42}) +# ─── parse_dict "name" vs "type" key handling ─────────────────────────────── + + +class TestOptimizerParseDictKeyHandling: + def test_parse_with_type_key(self): + result = OptimizerParams.parse_dict({"type": "adam", "lr": 0.01}) + assert isinstance(result, OptimizerParams.Adam) + assert result.lr == 0.01 + + def test_name_takes_precedence_over_type(self): + result = OptimizerParams.parse_dict({"name": "adam", "type": "sgd"}) + assert isinstance(result, OptimizerParams.Adam) + + def test_neither_name_nor_type_raises(self): + with pytest.raises(ValueError, match="Must provide either"): + OptimizerParams.parse_dict({"lr": 0.01}) + + def test_type_key_not_leaked_into_constructor(self): + """'type' should be popped from d so it doesn't become an unexpected kwarg.""" + result = OptimizerParams.parse_dict({"type": "sgd", "momentum": 0.9}) + assert isinstance(result, OptimizerParams.SGD) + assert result.momentum == 0.9 + + def test_both_keys_popped_when_name_used(self): + """Even when 'name' is used, 'type' should be popped so it doesn't leak.""" + result = OptimizerParams.parse_dict({"name": "adam", "type": "ignored", "lr": 0.05}) + assert isinstance(result, OptimizerParams.Adam) + assert result.lr == 0.05 + + +class TestSchedulerParseDictKeyHandling: + def test_parse_with_type_key(self): + result = SchedulerParams.parse_dict({"type": "plateau", "patience": 20}) + assert isinstance(result, SchedulerParams.Plateau) + assert result.patience == 20 + + def test_name_takes_precedence_over_type(self): + result = SchedulerParams.parse_dict({"name": "plateau", "type": "linear"}) + assert isinstance(result, SchedulerParams.Plateau) + + def test_neither_name_nor_type_raises(self): + with pytest.raises(ValueError, match="Must provide either"): + SchedulerParams.parse_dict({"patience": 20}) + + def test_type_key_not_leaked_into_constructor(self): + result = SchedulerParams.parse_dict({"type": "cyclic", "step_size_up": 50}) + assert isinstance(result, SchedulerParams.Cyclic) + assert result.step_size_up == 50 + + def test_both_keys_popped_when_name_used(self): + result = SchedulerParams.parse_dict({"name": "plateau", "type": "ignored", "patience": 5}) + assert isinstance(result, SchedulerParams.Plateau) + assert result.patience == 5 + + # ─── SchedulerParams defaults ─────────────────────────────────────────────── @@ -291,5 +346,5 @@ def test_parse_invalid_name_type_raises(self): SchedulerParams.parse_dict({"name": 3.14}) def test_parse_default_name_is_none(self): - result = SchedulerParams.parse_dict({}) - assert isinstance(result, SchedulerParams.NoneScheduler) + with pytest.raises(ValueError, match="Must provide either"): + SchedulerParams.parse_dict({}) From b40f94695022415c1520552385286d47188289d8 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Wed, 4 Mar 2026 20:14:01 -0800 Subject: [PATCH 151/335] Optimizer mixin hotfix fastforward --- src/quantem/core/ml/optimizer_mixin.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index 94c12518..d30229da 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -159,7 +159,11 @@ def parse_dict(cls, d: dict): Accepts either ``"name"`` or ``"type"`` as the optimizer key. """ d = dict(d) # avoid mutating caller's dict - name = d.pop("name", None) or d.pop("type", "none") + name = d.pop("name", None) + type_ = d.pop("type", None) + name = name or type_ + if name is None: + raise ValueError("Must provide either 'name' or 'type' key") if isinstance(name, type): name = name.__name__.lower() elif isinstance(name, str): @@ -446,7 +450,11 @@ def parse_dict(cls, d: dict): Accepts either ``"name"`` or ``"type"`` as the scheduler key. """ d = dict(d) # avoid mutating caller's dict - name = d.pop("name", None) or d.pop("type", "none") + name = d.pop("name", None) + type_ = d.pop("type", None) + name = name or type_ + if name is None: + raise ValueError("Must provide either 'name' or 'type' key") if isinstance(name, type): name = name.__name__.lower() elif isinstance(name, str): From aacd82d9b2da33de7b8ec232600555745cfe06a5 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Wed, 4 Mar 2026 20:16:38 -0800 Subject: [PATCH 152/335] Added new tests as well --- tests/ml/test_optimizermixin.py | 59 +++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/tests/ml/test_optimizermixin.py b/tests/ml/test_optimizermixin.py index 6fd6c22a..3682d452 100644 --- a/tests/ml/test_optimizermixin.py +++ b/tests/ml/test_optimizermixin.py @@ -114,6 +114,61 @@ def test_parse_invalid_name_type_raises(self): OptimizerParams.parse_dict({"name": 42}) +# ─── parse_dict "name" vs "type" key handling ─────────────────────────────── + + +class TestOptimizerParseDictKeyHandling: + def test_parse_with_type_key(self): + result = OptimizerParams.parse_dict({"type": "adam", "lr": 0.01}) + assert isinstance(result, OptimizerParams.Adam) + assert result.lr == 0.01 + + def test_name_takes_precedence_over_type(self): + result = OptimizerParams.parse_dict({"name": "adam", "type": "sgd"}) + assert isinstance(result, OptimizerParams.Adam) + + def test_neither_name_nor_type_raises(self): + with pytest.raises(ValueError, match="Must provide either"): + OptimizerParams.parse_dict({"lr": 0.01}) + + def test_type_key_not_leaked_into_constructor(self): + """'type' should be popped from d so it doesn't become an unexpected kwarg.""" + result = OptimizerParams.parse_dict({"type": "sgd", "momentum": 0.9}) + assert isinstance(result, OptimizerParams.SGD) + assert result.momentum == 0.9 + + def test_both_keys_popped_when_name_used(self): + """Even when 'name' is used, 'type' should be popped so it doesn't leak.""" + result = OptimizerParams.parse_dict({"name": "adam", "type": "ignored", "lr": 0.05}) + assert isinstance(result, OptimizerParams.Adam) + assert result.lr == 0.05 + + +class TestSchedulerParseDictKeyHandling: + def test_parse_with_type_key(self): + result = SchedulerParams.parse_dict({"type": "plateau", "patience": 20}) + assert isinstance(result, SchedulerParams.Plateau) + assert result.patience == 20 + + def test_name_takes_precedence_over_type(self): + result = SchedulerParams.parse_dict({"name": "plateau", "type": "linear"}) + assert isinstance(result, SchedulerParams.Plateau) + + def test_neither_name_nor_type_raises(self): + with pytest.raises(ValueError, match="Must provide either"): + SchedulerParams.parse_dict({"patience": 20}) + + def test_type_key_not_leaked_into_constructor(self): + result = SchedulerParams.parse_dict({"type": "cyclic", "step_size_up": 50}) + assert isinstance(result, SchedulerParams.Cyclic) + assert result.step_size_up == 50 + + def test_both_keys_popped_when_name_used(self): + result = SchedulerParams.parse_dict({"name": "plateau", "type": "ignored", "patience": 5}) + assert isinstance(result, SchedulerParams.Plateau) + assert result.patience == 5 + + # ─── SchedulerParams defaults ─────────────────────────────────────────────── @@ -291,5 +346,5 @@ def test_parse_invalid_name_type_raises(self): SchedulerParams.parse_dict({"name": 3.14}) def test_parse_default_name_is_none(self): - result = SchedulerParams.parse_dict({}) - assert isinstance(result, SchedulerParams.NoneScheduler) + with pytest.raises(ValueError, match="Must provide either"): + SchedulerParams.parse_dict({}) From 1fb2ca96e7ef9102d35dd9880d5f6aca5c44b35e Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Wed, 4 Mar 2026 20:44:24 -0800 Subject: [PATCH 153/335] Added DatasetConstraintParams, works with dictionary inputs still --- src/quantem/core/ml/optimizer_mixin.py | 2 +- src/quantem/tomography/dataset_models.py | 60 ++++++++++++++++++------ src/quantem/tomography/object_models.py | 6 ++- src/quantem/tomography/tomography.py | 17 ++++--- 4 files changed, 63 insertions(+), 22 deletions(-) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index d30229da..148c3ac3 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -454,7 +454,7 @@ def parse_dict(cls, d: dict): type_ = d.pop("type", None) name = name or type_ if name is None: - raise ValueError("Must provide either 'name' or 'type' key") + name = "none" if isinstance(name, type): name = name.__name__.lower() elif isinstance(name, str): diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index 942e7cac..df5c8849 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -14,20 +14,51 @@ from quantem.core.ml.optimizer_mixin import OptimizerMixin from quantem.tomography.utils import tv_loss_1d - # --- Constraints --- -@dataclass(slots=False) -class DefaultTomographyDatasetConstraints(Constraints): - """ - Data class for constraints that can be applied to the parameters of a tomography dataset. - """ - # Soft Constraints - tv_zs: float = 0.0 - tv_shifts: float = 0.0 - soft_constraint_keys = ["tv_zs", "tv_shifts"] - hard_constraint_keys = [] +class DatasetConstraintParams: + @dataclass + class BaseTomographyDatasetConstraints(Constraints): + tv_zs: float = 0.0 + tv_shifts: float = 0.0 + + soft_constraint_keys = ["tv_zs", "tv_shifts"] + hard_constraint_keys = [] + + @dataclass + class ThroughFocalDatasetConstraints(BaseTomographyDatasetConstraints): + pass + + @classmethod + def parse_dict( + cls, d: dict + ) -> BaseTomographyDatasetConstraints | ThroughFocalDatasetConstraints: + d = dict(d) + name = d.pop("name", None) + type_ = d.pop("type", None) + name = name or type_ + if name is None: + raise ValueError("Must provide either 'name' or 'type' key") + if isinstance(name, type): + name = name.__name__.lower() + elif isinstance(name, str): + name = name.lower() + else: + raise ValueError(f"Unknown dataset constraint type: {name}") + if name == "base_tomography_dataset": + return DatasetConstraintParams.BaseTomographyDatasetConstraints(**d) + elif name == "through_focal_dataset": + # return DatasetConstraintParams.ThroughFocalDatasetConstraints(**d) + raise NotImplementedError("Through focal dataset constraints are not implemented yet.") + else: + raise ValueError(f"Unknown dataset constraint type: {name.lower()}") + + +DatasetConstraintsType = ( + DatasetConstraintParams.BaseTomographyDatasetConstraints + | DatasetConstraintParams.ThroughFocalDatasetConstraints +) @dataclass @@ -245,11 +276,13 @@ def to(self, device: torch.device | str): class TomographyDatasetConstraints(BaseConstraints, TomographyDatasetBase): - DEFAULT_CONSTRAINTS = DefaultTomographyDatasetConstraints() + DEFAULT_CONSTRAINTS = DatasetConstraintParams.BaseTomographyDatasetConstraints() def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.constraints: DefaultTomographyDatasetConstraints = self.DEFAULT_CONSTRAINTS.copy() + self.constraints: DatasetConstraintParams.BaseTomographyDatasetConstraints = ( + self.DEFAULT_CONSTRAINTS.copy() + ) def apply_soft_constraints(self) -> torch.Tensor: soft_loss = torch.tensor(0.0, device=self.z1_params.device) @@ -265,7 +298,6 @@ def apply_soft_constraints(self) -> torch.Tensor: tv_loss_shifts += tv_loss_1d(self.shifts_params[:, 1]) tv_loss_shifts = self.constraints.tv_shifts * tv_loss_shifts soft_loss += tv_loss_shifts - return soft_loss def apply_hard_constraints(self): diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index c3a47fb9..d057ebca 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -48,7 +48,11 @@ def parse_dict(cls, d: dict) -> ObjPixelatedConstraints | ObjINRConstraints: Parse dictionary to an object constraint params object. """ d = dict(d) - name = d.pop("name", None) or d.pop("type", "none") + name = d.pop("name", None) + type_ = d.pop("type", None) + name = name or type_ + if name is None: + raise ValueError("Must provide either 'name' or 'type' key") if isinstance(name, type): name = name.__name__.lower() elif isinstance(name, str): diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 2e20e717..8b1ebd9f 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -1,6 +1,6 @@ import os from pathlib import Path -from typing import Literal, Self, Sequence, cast +from typing import Literal, Self, Sequence import numpy as np import torch @@ -10,10 +10,12 @@ from quantem.core.io.serialize import load as autoserialize_load from quantem.core.ml.loss_functions import get_loss_module from quantem.core.utils.filter import gaussian_filter_2d_stack, gaussian_kernel_1d -from quantem.core.utils.tomography_utils import ( - torch_phase_cross_correlation, +from quantem.core.utils.tomography_utils import torch_phase_cross_correlation +from quantem.tomography.dataset_models import ( + DatasetConstraintParams, + DatasetConstraintsType, + DatasetModelType, ) -from quantem.tomography.dataset_models import DatasetModelType, DefaultTomographyDatasetConstraints from quantem.tomography.logger_tomography import LoggerTomography from quantem.tomography.object_models import ( ObjConstraintParams, @@ -61,7 +63,7 @@ def reconstruct( optimizer_params: dict | None = None, scheduler_params: dict | None = None, obj_constraints: dict | ObjConstraintsType | None = None, - dset_constraints: dict = {}, + dset_constraints: dict | DatasetConstraintsType = None, num_samples_per_ray: int | list[tuple[int, int]] | None = None, profiling_mode: bool = False, val_fraction: float = 0.0, @@ -116,7 +118,10 @@ def reconstruct( self.obj_model.constraints = obj_constraints if dset_constraints is not None: - self.dset.constraints = cast(DefaultTomographyDatasetConstraints, dset_constraints) + if isinstance(dset_constraints, dict): + dset_constraints = DatasetConstraintParams.parse_dict(dset_constraints) + + self.dset.constraints = dset_constraints # Setting up DDP if not hasattr(self, "dataloader") or reset_dset is not None: if reset_dset is not None: From f5d635134159428f097b191ebb896004882e9b48 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Wed, 4 Mar 2026 22:23:34 -0800 Subject: [PATCH 154/335] OptimizerMixin fix tests --- tests/ml/test_optimizermixin.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/ml/test_optimizermixin.py b/tests/ml/test_optimizermixin.py index 3682d452..d829af41 100644 --- a/tests/ml/test_optimizermixin.py +++ b/tests/ml/test_optimizermixin.py @@ -154,9 +154,9 @@ def test_name_takes_precedence_over_type(self): result = SchedulerParams.parse_dict({"name": "plateau", "type": "linear"}) assert isinstance(result, SchedulerParams.Plateau) - def test_neither_name_nor_type_raises(self): - with pytest.raises(ValueError, match="Must provide either"): - SchedulerParams.parse_dict({"patience": 20}) + def test_neither_name_nor_type_defaults_to_none(self): + result = SchedulerParams.parse_dict({"patience": 20}) + assert isinstance(result, SchedulerParams.NoneScheduler) def test_type_key_not_leaked_into_constructor(self): result = SchedulerParams.parse_dict({"type": "cyclic", "step_size_up": 50}) @@ -346,5 +346,5 @@ def test_parse_invalid_name_type_raises(self): SchedulerParams.parse_dict({"name": 3.14}) def test_parse_default_name_is_none(self): - with pytest.raises(ValueError, match="Must provide either"): - SchedulerParams.parse_dict({}) + result = SchedulerParams.parse_dict({}) + assert isinstance(result, SchedulerParams.NoneScheduler) From c1a7e23da4571e9d8676206866b6e36193662b2e Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Thu, 5 Mar 2026 09:15:16 -0800 Subject: [PATCH 155/335] Docstrings for dataset_models and object_models --- src/quantem/tomography/dataset_models.py | 62 ++++++++++++++++++++--- src/quantem/tomography/object_models.py | 63 ++++++++++++++++++++++-- 2 files changed, 112 insertions(+), 13 deletions(-) diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index df5c8849..cbc10884 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -18,8 +18,25 @@ class DatasetConstraintParams: + """ + Namespace class for dataset constraint parameter dataclasses and parsing utilities. + + Contains constraint definitions for different tomography dataset types and a + factory method for instantiating the appropriate constraint class from a dict. + """ + @dataclass class BaseTomographyDatasetConstraints(Constraints): + """ + Soft constraints for a base tomography dataset. + + Attributes: + tv_zs: Total variation regularization weight for z-positions. + tv_shifts: Total variation regularization weight for lateral shifts. + soft_constraint_keys: Constraint fields penalized softly during optimization. + hard_constraint_keys: Constraint fields enforced strictly (none for this class). + """ + tv_zs: float = 0.0 tv_shifts: float = 0.0 @@ -28,12 +45,48 @@ class BaseTomographyDatasetConstraints(Constraints): @dataclass class ThroughFocalDatasetConstraints(BaseTomographyDatasetConstraints): + """ + Constraints for a through-focal tomography dataset. + + Inherits all constraints from BaseTomographyDatasetConstraints. + Currently not implemented — instantiation will raise NotImplementedError. + """ + pass @classmethod def parse_dict( cls, d: dict - ) -> BaseTomographyDatasetConstraints | ThroughFocalDatasetConstraints: + ) -> "DatasetConstraintParams.BaseTomographyDatasetConstraints | DatasetConstraintParams.ThroughFocalDatasetConstraints": + """ + Instantiate a dataset constraint dataclass from a configuration dictionary. + + The dictionary must contain a ``'name'`` or ``'type'`` key identifying + which constraint class to construct. All remaining keys are forwarded as + keyword arguments to the selected dataclass. + + Args: + d: Configuration dictionary. Must include ``'name'`` or ``'type'`` + with one of the following values (case-insensitive): + + - ``'base_tomography_dataset'`` → + :class:`BaseTomographyDatasetConstraints` + - ``'through_focal_dataset'`` → + :class:`ThroughFocalDatasetConstraints` *(not yet implemented)* + + The key may also be a class ``type`` object, in which case its + ``__name__`` is used after lower-casing. + + Returns: + An instance of the appropriate constraint dataclass. + + Raises: + ValueError: If neither ``'name'`` nor ``'type'`` is present, if the + value is not a string or type, or if the name does not match any + known dataset constraint type. + NotImplementedError: If ``'through_focal_dataset'`` is requested, as + it is not yet implemented. + """ d = dict(d) name = d.pop("name", None) type_ = d.pop("type", None) @@ -49,18 +102,11 @@ def parse_dict( if name == "base_tomography_dataset": return DatasetConstraintParams.BaseTomographyDatasetConstraints(**d) elif name == "through_focal_dataset": - # return DatasetConstraintParams.ThroughFocalDatasetConstraints(**d) raise NotImplementedError("Through focal dataset constraints are not implemented yet.") else: raise ValueError(f"Unknown dataset constraint type: {name.lower()}") -DatasetConstraintsType = ( - DatasetConstraintParams.BaseTomographyDatasetConstraints - | DatasetConstraintParams.ThroughFocalDatasetConstraints -) - - @dataclass class DatasetValue: """ diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index d057ebca..f831338e 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -1,7 +1,7 @@ from abc import abstractmethod from copy import deepcopy from dataclasses import dataclass -from typing import Any, Callable, Generator, Literal +from typing import Any, Callable, Generator import numpy as np import torch @@ -17,12 +17,29 @@ from quantem.core.utils.rng import RNGMixin from quantem.tomography.dataset_models import TomographyINRPretrainDataset -object_type = Literal["potential"] - class ObjConstraintParams: + """ + Namespace class for object reconstruction constraint dataclasses and parsing utilities. + + Contains constraint definitions for pixelated and implicit neural representation (INR) + object types, along with a factory method for instantiating the appropriate class + from a configuration dictionary. + """ + @dataclass class ObjPixelatedConstraints(Constraints): + """ + Constraints for a pixelated object representation. + + Attributes: + positivity: If True, enforces non-negative values in the reconstruction. + shrinkage: Shrinkage regularization strength; pushes values toward zero. + tv_vol: Total variation regularization weight for the 3-D volume. + soft_constraint_keys: Constraint fields penalized softly during optimization. + hard_constraint_keys: Constraint fields enforced strictly during optimization. + """ + positivity: bool = True shrinkage: float = 0.0 tv_vol: float = 0.0 @@ -33,6 +50,18 @@ class ObjPixelatedConstraints(Constraints): @dataclass class ObjINRConstraints(Constraints): + """ + Constraints for an implicit neural representation (INR) object. + + Attributes: + positivity: If True, enforces non-negative values in the reconstruction. + shrinkage: Shrinkage regularization strength; pushes values toward zero. + tv_vol: Total variation regularization weight for the 3-D volume. + sparsity: Sparsity regularization weight; encourages near-zero activations. + soft_constraint_keys: Constraint fields penalized softly during optimization. + hard_constraint_keys: Constraint fields enforced strictly during optimization. + """ + positivity: bool = True shrinkage: float = 0.0 tv_vol: float = 0.0 @@ -43,9 +72,33 @@ class ObjINRConstraints(Constraints): hard_constraint_keys = ["positivity", "shrinkage"] @classmethod - def parse_dict(cls, d: dict) -> ObjPixelatedConstraints | ObjINRConstraints: + def parse_dict( + cls, d: dict + ) -> "ObjConstraintParams.ObjPixelatedConstraints | ObjConstraintParams.ObjINRConstraints": """ - Parse dictionary to an object constraint params object. + Instantiate an object constraint dataclass from a configuration dictionary. + + The dictionary must contain a ``'name'`` or ``'type'`` key identifying + which constraint class to construct. All remaining keys are forwarded as + keyword arguments to the selected dataclass. + + Args: + d: Configuration dictionary. Must include ``'name'`` or ``'type'`` + with one of the following values (case-insensitive): + + - ``'obj_pixelated'`` → :class:`ObjPixelatedConstraints` + - ``'obj_inr'`` → :class:`ObjINRConstraints` + + The key may also be a class ``type`` object, in which case its + ``__name__`` is used after lower-casing. + + Returns: + An instance of the appropriate object constraint dataclass. + + Raises: + ValueError: If neither ``'name'`` nor ``'type'`` is present, if the + value is not a string or type, or if the name does not match any + known object constraint type. """ d = dict(d) name = d.pop("name", None) From 2c910642a6f91689ab06c76e324a04029ebcb77f Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Thu, 5 Mar 2026 09:16:55 -0800 Subject: [PATCH 156/335] Readded dataset models type back in --- src/quantem/tomography/dataset_models.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index cbc10884..00594cc1 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -107,6 +107,12 @@ def parse_dict( raise ValueError(f"Unknown dataset constraint type: {name.lower()}") +DatasetConstraintType = ( + DatasetConstraintParams.BaseTomographyDatasetConstraints + | DatasetConstraintParams.ThroughFocalDatasetConstraints +) + + @dataclass class DatasetValue: """ From 882d9e9e61ad154be5c54283a6893b1dfc7a61ca Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Thu, 5 Mar 2026 09:17:58 -0800 Subject: [PATCH 157/335] Bug --- src/quantem/tomography/dataset_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index 00594cc1..f7c7fc5d 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -107,7 +107,7 @@ def parse_dict( raise ValueError(f"Unknown dataset constraint type: {name.lower()}") -DatasetConstraintType = ( +DatasetConstraintsType = ( DatasetConstraintParams.BaseTomographyDatasetConstraints | DatasetConstraintParams.ThroughFocalDatasetConstraints ) From 64ee01e4d023f6e6f849d2a5bc18f3affce142d4 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Thu, 5 Mar 2026 09:21:57 -0800 Subject: [PATCH 158/335] NumPy style docstrings --- src/quantem/tomography/dataset_models.py | 70 +++++++++++------ src/quantem/tomography/object_models.py | 97 ++++++++++++++++-------- 2 files changed, 114 insertions(+), 53 deletions(-) diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index f7c7fc5d..301427ad 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -23,6 +23,20 @@ class DatasetConstraintParams: Contains constraint definitions for different tomography dataset types and a factory method for instantiating the appropriate constraint class from a dict. + + Supported constraint types + -------------------------- + BaseTomographyDatasetConstraints + Base soft constraints for z-position and lateral shift regularization. + ThroughFocalDatasetConstraints + Inherits base constraints; not yet implemented. + + Examples + -------- + >>> DatasetConstraintParams.parse_dict({"name": "base_tomography_dataset", "tv_zs": 0.1}) + BaseTomographyDatasetConstraints(tv_zs=0.1, tv_shifts=0.0) + >>> DatasetConstraintParams.parse_dict({"type": "base_tomography_dataset"}) + BaseTomographyDatasetConstraints(tv_zs=0.0, tv_shifts=0.0) """ @dataclass @@ -30,11 +44,16 @@ class BaseTomographyDatasetConstraints(Constraints): """ Soft constraints for a base tomography dataset. - Attributes: - tv_zs: Total variation regularization weight for z-positions. - tv_shifts: Total variation regularization weight for lateral shifts. - soft_constraint_keys: Constraint fields penalized softly during optimization. - hard_constraint_keys: Constraint fields enforced strictly (none for this class). + Attributes + ---------- + tv_zs : float + Total variation regularization weight for z-positions. + tv_shifts : float + Total variation regularization weight for lateral shifts. + soft_constraint_keys : list[str] + Constraint fields penalized softly during optimization. + hard_constraint_keys : list[str] + Constraint fields enforced strictly (none for this class). """ tv_zs: float = 0.0 @@ -48,8 +67,8 @@ class ThroughFocalDatasetConstraints(BaseTomographyDatasetConstraints): """ Constraints for a through-focal tomography dataset. - Inherits all constraints from BaseTomographyDatasetConstraints. - Currently not implemented — instantiation will raise NotImplementedError. + Inherits all constraints from ``BaseTomographyDatasetConstraints``. + Currently not implemented — instantiation will raise ``NotImplementedError``. """ pass @@ -65,27 +84,32 @@ def parse_dict( which constraint class to construct. All remaining keys are forwarded as keyword arguments to the selected dataclass. - Args: - d: Configuration dictionary. Must include ``'name'`` or ``'type'`` - with one of the following values (case-insensitive): + Parameters + ---------- + d : dict + Configuration dictionary. Must include ``'name'`` or ``'type'`` + with one of the following values (case-insensitive): - - ``'base_tomography_dataset'`` → - :class:`BaseTomographyDatasetConstraints` - - ``'through_focal_dataset'`` → - :class:`ThroughFocalDatasetConstraints` *(not yet implemented)* + - ``'base_tomography_dataset'`` → :class:`BaseTomographyDatasetConstraints` + - ``'through_focal_dataset'`` → :class:`ThroughFocalDatasetConstraints` + *(not yet implemented)* - The key may also be a class ``type`` object, in which case its - ``__name__`` is used after lower-casing. + The value may also be a class ``type`` object, in which case its + ``__name__`` is used after lower-casing. - Returns: + Returns + ------- + BaseTomographyDatasetConstraints or ThroughFocalDatasetConstraints An instance of the appropriate constraint dataclass. - Raises: - ValueError: If neither ``'name'`` nor ``'type'`` is present, if the - value is not a string or type, or if the name does not match any - known dataset constraint type. - NotImplementedError: If ``'through_focal_dataset'`` is requested, as - it is not yet implemented. + Raises + ------ + ValueError + If neither ``'name'`` nor ``'type'`` is present, if the value is not + a string or type, or if the name does not match any known dataset + constraint type. + NotImplementedError + If ``'through_focal_dataset'`` is requested, as it is not yet implemented. """ d = dict(d) name = d.pop("name", None) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index f831338e..6ef9eb59 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -22,22 +22,43 @@ class ObjConstraintParams: """ Namespace class for object reconstruction constraint dataclasses and parsing utilities. - Contains constraint definitions for pixelated and implicit neural representation (INR) - object types, along with a factory method for instantiating the appropriate class - from a configuration dictionary. + Contains constraint definitions for pixelated and implicit neural representation + (INR) object types, along with a factory method for instantiating the appropriate + class from a configuration dictionary. + + Supported constraint types + -------------------------- + ObjPixelatedConstraints + Constraints for a voxel-grid (pixelated) object representation. + ObjINRConstraints + Constraints for a network-parameterized (INR) object representation; + adds a sparsity term not present in the pixelated variant. + + Examples + -------- + >>> ObjConstraintParams.parse_dict({"name": "obj_pixelated", "tv_vol": 0.01}) + ObjPixelatedConstraints(positivity=True, shrinkage=0.0, tv_vol=0.01) + >>> ObjConstraintParams.parse_dict({"type": "obj_inr", "sparsity": 0.05}) + ObjINRConstraints(positivity=True, shrinkage=0.0, tv_vol=0.0, sparsity=0.05) """ @dataclass class ObjPixelatedConstraints(Constraints): """ - Constraints for a pixelated object representation. - - Attributes: - positivity: If True, enforces non-negative values in the reconstruction. - shrinkage: Shrinkage regularization strength; pushes values toward zero. - tv_vol: Total variation regularization weight for the 3-D volume. - soft_constraint_keys: Constraint fields penalized softly during optimization. - hard_constraint_keys: Constraint fields enforced strictly during optimization. + Constraints for a pixelated (voxel-grid) object representation. + + Attributes + ---------- + positivity : bool + If ``True``, enforces non-negative values in the reconstruction. + shrinkage : float + Shrinkage regularization strength; pushes values toward zero. + tv_vol : float + Total variation regularization weight for the 3-D volume. + soft_constraint_keys : list[str] + Constraint fields penalized softly during optimization. + hard_constraint_keys : list[str] + Constraint fields enforced strictly during optimization. """ positivity: bool = True @@ -53,13 +74,23 @@ class ObjINRConstraints(Constraints): """ Constraints for an implicit neural representation (INR) object. - Attributes: - positivity: If True, enforces non-negative values in the reconstruction. - shrinkage: Shrinkage regularization strength; pushes values toward zero. - tv_vol: Total variation regularization weight for the 3-D volume. - sparsity: Sparsity regularization weight; encourages near-zero activations. - soft_constraint_keys: Constraint fields penalized softly during optimization. - hard_constraint_keys: Constraint fields enforced strictly during optimization. + Extends pixelated constraints with an additional sparsity term suited + to the continuous, network-parameterized object representation. + + Attributes + ---------- + positivity : bool + If ``True``, enforces non-negative values in the reconstruction. + shrinkage : float + Shrinkage regularization strength; pushes values toward zero. + tv_vol : float + Total variation regularization weight for the 3-D volume. + sparsity : float + Sparsity regularization weight; encourages near-zero activations. + soft_constraint_keys : list[str] + Constraint fields penalized softly during optimization. + hard_constraint_keys : list[str] + Constraint fields enforced strictly during optimization. """ positivity: bool = True @@ -82,23 +113,29 @@ def parse_dict( which constraint class to construct. All remaining keys are forwarded as keyword arguments to the selected dataclass. - Args: - d: Configuration dictionary. Must include ``'name'`` or ``'type'`` - with one of the following values (case-insensitive): + Parameters + ---------- + d : dict + Configuration dictionary. Must include ``'name'`` or ``'type'`` + with one of the following values (case-insensitive): - - ``'obj_pixelated'`` → :class:`ObjPixelatedConstraints` - - ``'obj_inr'`` → :class:`ObjINRConstraints` + - ``'obj_pixelated'`` → :class:`ObjPixelatedConstraints` + - ``'obj_inr'`` → :class:`ObjINRConstraints` - The key may also be a class ``type`` object, in which case its - ``__name__`` is used after lower-casing. + The value may also be a class ``type`` object, in which case its + ``__name__`` is used after lower-casing. - Returns: + Returns + ------- + ObjPixelatedConstraints or ObjINRConstraints An instance of the appropriate object constraint dataclass. - Raises: - ValueError: If neither ``'name'`` nor ``'type'`` is present, if the - value is not a string or type, or if the name does not match any - known object constraint type. + Raises + ------ + ValueError + If neither ``'name'`` nor ``'type'`` is present, if the value is not + a string or type, or if the name does not match any known object + constraint type. """ d = dict(d) name = d.pop("name", None) From 47f31eaec69ed82d6193549645107e9bfcfb97d7 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Thu, 5 Mar 2026 09:24:02 -0800 Subject: [PATCH 159/335] Some typos in the docstrings --- src/quantem/tomography/dataset_models.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index 301427ad..c1e362da 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -47,9 +47,9 @@ class BaseTomographyDatasetConstraints(Constraints): Attributes ---------- tv_zs : float - Total variation regularization weight for z-positions. + Total variation regularization weight for Z1 and Z3 Euler angles. tv_shifts : float - Total variation regularization weight for lateral shifts. + Total variation regularization weight for X and Y shifts. soft_constraint_keys : list[str] Constraint fields penalized softly during optimization. hard_constraint_keys : list[str] @@ -58,6 +58,7 @@ class BaseTomographyDatasetConstraints(Constraints): tv_zs: float = 0.0 tv_shifts: float = 0.0 + _name: str = "base_tomography_dataset" soft_constraint_keys = ["tv_zs", "tv_shifts"] hard_constraint_keys = [] From 586f3bceaf8c3aa3cb24a6e37aa792c619f51f14 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Thu, 5 Mar 2026 11:38:49 -0800 Subject: [PATCH 160/335] Added some __str__ for the OptimizerParams; not sure if needed but better for printing? --- src/quantem/core/ml/optimizer_mixin.py | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index 148c3ac3..c07fefc9 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -1,3 +1,4 @@ +import textwrap from abc import abstractmethod from dataclasses import dataclass from typing import TYPE_CHECKING, Generator, Iterator, Literal, Sequence @@ -68,6 +69,16 @@ def params(self) -> dict: "weight_decay": self.weight_decay, } + def __str__(self) -> str: + return textwrap.dedent(f""" + OptimizerParams.Adam( + lr = {self.lr}, + betas = {self.betas}, + eps = {self.eps}, + weight_decay = {self.weight_decay}, + ) + """).strip() + @dataclass class AdamW: """ @@ -103,6 +114,16 @@ def params(self) -> dict: "weight_decay": self.weight_decay, } + def __str__(self) -> str: + return textwrap.dedent(f""" + OptimizerParams.AdamW( + lr = {self.lr}, + betas = {self.betas}, + eps = {self.eps}, + weight_decay = {self.weight_decay}, + ) + """).strip() + @dataclass class SGD: """ @@ -138,6 +159,17 @@ def params(self) -> dict: "nesterov": self.nesterov, } + def __str__(self) -> str: + return textwrap.dedent(f""" + OptimizerParams.SGD( + lr = {self.lr}, + momentum = {self.momentum}, + dampening = {self.dampening}, + weight_decay = {self.weight_decay}, + nesterov = {self.nesterov}, + ) + """).strip() + @dataclass class NoneOptimizer: """ @@ -152,6 +184,11 @@ class NoneOptimizer: def params(self) -> dict: return {} + def __str__(self) -> str: + return textwrap.dedent(""" + OptimizerParams.NoneOptimizer() + """).strip() + @classmethod def parse_dict(cls, d: dict): """ From 9693b11ebf3db45fd16d85b1c1d086eab9ac19e5 Mon Sep 17 00:00:00 2001 From: Art-MC Date: Thu, 5 Mar 2026 15:37:48 -0800 Subject: [PATCH 161/335] updating Vector save to call compact() first --- src/quantem/core/datastructures/vector.py | 43 ++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/src/quantem/core/datastructures/vector.py b/src/quantem/core/datastructures/vector.py index 7a8ef417..13906c01 100644 --- a/src/quantem/core/datastructures/vector.py +++ b/src/quantem/core/datastructures/vector.py @@ -1,7 +1,8 @@ from __future__ import annotations import copy -from typing import Any, Sequence +from pathlib import Path +from typing import Any, Literal, Sequence import numpy as np from numpy.typing import NDArray @@ -900,6 +901,46 @@ def _inplace_op(self, other: Any, op: Any, reverse: bool = False) -> None: cell[:, field_indices] = op(chunk, lhs) if reverse else op(lhs, chunk) cursor += rows + def save( + self, + path: str | Path, + mode: Literal["w", "o"] = "w", + store: Literal["auto", "zip", "dir"] = "auto", + skip: str | type | Sequence[str | type] = (), + compression_level: int | None = 4, + ) -> None: + """ + Save the Vector object to disk using Zarr serialization. self.compact() is called before + saving to reduce file size if possible. + + Parameters + ---------- + path : str or Path + Target file path. Use '.zip' extension for zip format, otherwise a directory. + mode : {'w', 'o'} + 'w' = write only if file doesn't exist, 'o' = overwrite if it does. + store : {'auto', 'zip', 'dir'} + Storage format. 'auto' infers from file extension. + skip : str, type, or list of (str or type) + Attribute names/types to skip (by name or type) during serialization. + compression_level : int or None + If set (0–9), applies Zstandard compression with Blosc backend at that level. + Level 0 disables compression. Raises ValueError if > 9. + + Notes + ----- + Skipped attribute names and types are also stored in the file metadata for correct + round-trip skipping during load(). + """ + self.compact() + super().save( + path, + mode=mode, + store=store, + skip=skip, + compression_level=compression_level, + ) + def _resolve_fields( fields: Sequence[str] | None, From f72bea2d6fe01b534ffabccef1d3e763a16bec81 Mon Sep 17 00:00:00 2001 From: Art-MC Date: Thu, 5 Mar 2026 15:47:48 -0800 Subject: [PATCH 162/335] reorganizing methods and attributes --- src/quantem/core/datastructures/vector.py | 394 ++++++++++++---------- 1 file changed, 221 insertions(+), 173 deletions(-) diff --git a/src/quantem/core/datastructures/vector.py b/src/quantem/core/datastructures/vector.py index 13906c01..a956bd7e 100644 --- a/src/quantem/core/datastructures/vector.py +++ b/src/quantem/core/datastructures/vector.py @@ -93,6 +93,10 @@ class Vector(AutoSerialize): __array_priority__ = 1000 _token = object() + # ------------------------------------------------------------------ # + # Construction + # ------------------------------------------------------------------ # + def __init__( self, shape: tuple[int, ...], @@ -200,6 +204,28 @@ def from_data( vector._replace_cells(np.arange(len(cell_arrays), dtype=np.int64), cell_arrays) return vector + # ------------------------------------------------------------------ # + # Identity properties + # ------------------------------------------------------------------ # + + @property + def name(self) -> str: + """Human-readable Vector name.""" + return self._state["name"] + + @name.setter + def name(self, value: str) -> None: + self._state["name"] = str(value) + + @property + def metadata(self) -> dict[str, Any]: + """Mutable metadata dictionary shared by all views.""" + return self._state["metadata"] + + # ------------------------------------------------------------------ # + # Shape & structure properties + # ------------------------------------------------------------------ # + @property def shape(self) -> tuple[int, ...]: """Return the fixed-grid shape of this selection.""" @@ -224,23 +250,23 @@ def num_fields(self) -> int: return len(self.fields) @property - def dtype(self) -> np.dtype[Any]: - """Return the NumPy dtype of the backing row buffer.""" - return self._state["data"].dtype + def num_cells(self) -> int: + """Return the number of fixed-grid cells in the current selection.""" + return int(self._selected_cell_indices().size) @property - def name(self) -> str: - """Human-readable Vector name.""" - return self._state["name"] - - @name.setter - def name(self, value: str) -> None: - self._state["name"] = str(value) + def total_rows(self) -> int: + """Return the total ragged-row count in the current selection.""" + return int(self._state["cell_lengths"][self._selected_cell_indices()].sum()) @property - def metadata(self) -> dict[str, Any]: - """Mutable metadata dictionary shared by all views.""" - return self._state["metadata"] + def dtype(self) -> np.dtype[Any]: + """Return the NumPy dtype of the backing row buffer.""" + return self._state["data"].dtype + + # ------------------------------------------------------------------ # + # Data access + # ------------------------------------------------------------------ # @property def array(self) -> NDArray[Any]: @@ -264,40 +290,6 @@ def array(self) -> NDArray[Any]: return cell[:, int(cols[0]) : int(cols[-1]) + 1] return cell[:, cols].copy() - def __len__(self) -> int: - """Return ``shape[0]`` for non-scalar selections.""" - if self.shape == (): - raise TypeError("len() of unsized 0D Vector") - return self.shape[0] - - def __repr__(self) -> str: - return "\n".join( - [ - f"quantem.Vector, shape={self.shape}, name={self.name}", - f" fields = {self.fields}", - f" units: {self.units}", - ] - ) - - __str__ = __repr__ - - def copy(self) -> "Vector": - """Return a deep copy of the current selection.""" - copied = self.__class__( - shape=self.shape, - fields=self.fields, - units=self.units, - name=self.name, - metadata=copy.deepcopy(self.metadata), - _token=self.__class__._token, - ) - target_cells = copied._selected_cell_indices() - source_arrays = [ - self._selected_cell_matrix(index).copy() for index in self._selected_cell_indices() - ] - copied._replace_cells(target_cells, source_arrays) - return copied - def flatten(self) -> NDArray[Any]: """Concatenate selected cells in row-major order. @@ -315,20 +307,14 @@ def flatten(self) -> NDArray[Any]: dtype = self._state["data"].dtype if self._state["data"].ndim == 2 else float return np.empty((0, self.num_fields), dtype=dtype) - @property - def total_rows(self) -> int: - """Return the total ragged-row count in the current selection.""" - return int(self._state["cell_lengths"][self._selected_cell_indices()].sum()) - - @property - def num_cells(self) -> int: - """Return the number of fixed-grid cells in the current selection.""" - return int(self._selected_cell_indices().size) - def row_counts(self) -> list[int]: """Return per-cell row counts in the current selection order.""" return [self._cell_row_count(int(index)) for index in self._selected_cell_indices()] + # ------------------------------------------------------------------ # + # Field management + # ------------------------------------------------------------------ # + def select_fields(self, *field_names: str | Sequence[str]) -> "Vector": """Return a view containing only the requested fields. @@ -363,82 +349,6 @@ def select_fields(self, *field_names: str | Sequence[str]) -> "Vector": selected_fields, ) - def set_flattened(self, values: Any) -> None: - """Write values back in flattened row-major order. - - This updates existing rows without changing per-cell row counts. It is - the rowwise companion to ``flatten()`` and is especially useful for - NumPy-based transforms that operate on all selected rows at once. - """ - field_indices = self._field_indices() - targets = self._selected_cell_indices() - row_counts = self.row_counts() - total_rows = sum(row_counts) - - if isinstance(values, Vector): - if values.num_fields != self.num_fields: - raise ValueError(f"Expected {self.num_fields} fields, got {values.num_fields}") - flat_values = values.flatten() - if flat_values.shape[0] != total_rows: - raise ValueError(f"Expected {total_rows} rows, got {flat_values.shape[0]}") - else: - flat_values = _broadcast_field_values(values, total_rows, self.num_fields) - - cursor = 0 - for target, rows in zip(targets, row_counts): - cell = self._cell_matrix(int(target)) - if rows > 0: - cell[:, field_indices] = flat_values[cursor : cursor + rows] - cursor += rows - - def compact(self) -> None: - """Repack the backing row buffer to remove dead rows. - - Whole-cell replacement appends new rows and leaves previous rows unused - until compaction. Calling ``compact()`` makes memory usage and save size - predictable at the cost of reallocating the backing buffer. - """ - data = self._state["data"] - used_rows = int(self._state["cell_lengths"].sum()) - if used_rows == 0: - self._state["data"] = np.empty((0, self._full_num_fields), dtype=data.dtype) - self._state["cell_starts"].fill(0) - return - - compacted = np.empty((used_rows, self._full_num_fields), dtype=data.dtype) - starts = np.zeros_like(self._state["cell_starts"]) - cursor = 0 - for linear_index in range(_cell_count(self._state["shape"])): - length = self._cell_row_count(linear_index) - starts[linear_index] = cursor - if length > 0: - cell = self._cell_matrix(linear_index) - compacted[cursor : cursor + length] = cell - cursor += length - self._state["data"] = compacted - self._state["cell_starts"] = starts - - def append_rows(self, idx: Any, rows: Any) -> None: - """Append one or more rows to a single selected cell. - - ``idx`` is interpreted with the same fixed-grid indexing rules as - ``__getitem__`` and must resolve to exactly one cell. Appending rows is a - full-cell operation, so all fields must be selected. - """ - target = self[idx] - if target.shape != (): - raise ValueError("append_rows requires an index that selects exactly one cell.") - target._require_full_field_view("append_rows") - - new_rows = _coerce_cell_array(rows, target.num_fields) - if new_rows.shape[0] == 0: - return - - cell_index = int(target._selected_cell_indices()[0]) - existing = target._cell_matrix(cell_index) - combined = np.vstack((existing, new_rows)) if existing.shape[0] > 0 else new_rows.copy() - target._replace_cells(np.array([cell_index], dtype=np.int64), [combined]) - def add_fields( self, names: str | Sequence[str], @@ -523,6 +433,124 @@ def remove_fields(self, names: str | Sequence[str]) -> None: if len(self._selected_fields) == len(self._state["fields"]): self._selected_fields = None + # ------------------------------------------------------------------ # + # Cell / row mutation + # ------------------------------------------------------------------ # + + def append_rows(self, idx: Any, rows: Any) -> None: + """Append one or more rows to a single selected cell. + + ``idx`` is interpreted with the same fixed-grid indexing rules as + ``__getitem__`` and must resolve to exactly one cell. Appending rows is a + full-cell operation, so all fields must be selected. + """ + target = self[idx] + if target.shape != (): + raise ValueError("append_rows requires an index that selects exactly one cell.") + target._require_full_field_view("append_rows") + + new_rows = _coerce_cell_array(rows, target.num_fields) + if new_rows.shape[0] == 0: + return + + cell_index = int(target._selected_cell_indices()[0]) + existing = target._cell_matrix(cell_index) + combined = np.vstack((existing, new_rows)) if existing.shape[0] > 0 else new_rows.copy() + target._replace_cells(np.array([cell_index], dtype=np.int64), [combined]) + + def set_flattened(self, values: Any) -> None: + """Write values back in flattened row-major order. + + This updates existing rows without changing per-cell row counts. It is + the rowwise companion to ``flatten()`` and is especially useful for + NumPy-based transforms that operate on all selected rows at once. + """ + field_indices = self._field_indices() + targets = self._selected_cell_indices() + row_counts = self.row_counts() + total_rows = sum(row_counts) + + if isinstance(values, Vector): + if values.num_fields != self.num_fields: + raise ValueError(f"Expected {self.num_fields} fields, got {values.num_fields}") + flat_values = values.flatten() + if flat_values.shape[0] != total_rows: + raise ValueError(f"Expected {total_rows} rows, got {flat_values.shape[0]}") + else: + flat_values = _broadcast_field_values(values, total_rows, self.num_fields) + + cursor = 0 + for target, rows in zip(targets, row_counts): + cell = self._cell_matrix(int(target)) + if rows > 0: + cell[:, field_indices] = flat_values[cursor : cursor + rows] + cursor += rows + + def compact(self) -> None: + """Repack the backing row buffer to remove dead rows. + + Whole-cell replacement appends new rows and leaves previous rows unused + until compaction. Calling ``compact()`` makes memory usage and save size + predictable at the cost of reallocating the backing buffer. + """ + data = self._state["data"] + used_rows = int(self._state["cell_lengths"].sum()) + if used_rows == 0: + self._state["data"] = np.empty((0, self._full_num_fields), dtype=data.dtype) + self._state["cell_starts"].fill(0) + return + + compacted = np.empty((used_rows, self._full_num_fields), dtype=data.dtype) + starts = np.zeros_like(self._state["cell_starts"]) + cursor = 0 + for linear_index in range(_cell_count(self._state["shape"])): + length = self._cell_row_count(linear_index) + starts[linear_index] = cursor + if length > 0: + cell = self._cell_matrix(linear_index) + compacted[cursor : cursor + length] = cell + cursor += length + self._state["data"] = compacted + self._state["cell_starts"] = starts + + # ------------------------------------------------------------------ # + # Python data model + # ------------------------------------------------------------------ # + + def __len__(self) -> int: + """Return ``shape[0]`` for non-scalar selections.""" + if self.shape == (): + raise TypeError("len() of unsized 0D Vector") + return self.shape[0] + + def __repr__(self) -> str: + return "\n".join( + [ + f"quantem.Vector, shape={self.shape}, name={self.name}", + f" fields = {self.fields}", + f" units: {self.units}", + ] + ) + + __str__ = __repr__ + + def copy(self) -> "Vector": + """Return a deep copy of the current selection.""" + copied = self.__class__( + shape=self.shape, + fields=self.fields, + units=self.units, + name=self.name, + metadata=copy.deepcopy(self.metadata), + _token=self.__class__._token, + ) + target_cells = copied._selected_cell_indices() + source_arrays = [ + self._selected_cell_matrix(index).copy() for index in self._selected_cell_indices() + ] + copied._replace_cells(target_cells, source_arrays) + return copied + def __getitem__(self, idx: Any) -> "Vector": """Return a fixed-grid selection as another Vector view.""" if _looks_like_field_selector(idx): @@ -550,6 +578,10 @@ def __setitem__(self, idx: Any, value: Any) -> None: target = self[idx] target._assign(value) + # ------------------------------------------------------------------ # + # Arithmetic operators + # ------------------------------------------------------------------ # + def __array_ufunc__(self, ufunc: Any, method: str, *inputs: Any, **kwargs: Any) -> Any: """Apply supported NumPy ufuncs elementwise. @@ -668,6 +700,54 @@ def __abs__(self) -> "Vector": result._inplace_unary(np.abs) return result + # ------------------------------------------------------------------ # + # I/O + # ------------------------------------------------------------------ # + + def save( + self, + path: str | Path, + mode: Literal["w", "o"] = "w", + store: Literal["auto", "zip", "dir"] = "auto", + skip: str | type | Sequence[str | type] = (), + compression_level: int | None = 4, + ) -> None: + """ + Save the Vector object to disk using Zarr serialization. self.compact() is called before + saving to reduce file size if possible. + + Parameters + ---------- + path : str or Path + Target file path. Use '.zip' extension for zip format, otherwise a directory. + mode : {'w', 'o'} + 'w' = write only if file doesn't exist, 'o' = overwrite if it does. + store : {'auto', 'zip', 'dir'} + Storage format. 'auto' infers from file extension. + skip : str, type, or list of (str or type) + Attribute names/types to skip (by name or type) during serialization. + compression_level : int or None + If set (0–9), applies Zstandard compression with Blosc backend at that level. + Level 0 disables compression. Raises ValueError if > 9. + + Notes + ----- + Skipped attribute names and types are also stored in the file metadata for correct + round-trip skipping during load(). + """ + self.compact() + super().save( + path, + mode=mode, + store=store, + skip=skip, + compression_level=compression_level, + ) + + # ------------------------------------------------------------------ # + # Private helpers — backing-store access + # ------------------------------------------------------------------ # + @property def _full_num_fields(self) -> int: return len(self._state["fields"]) @@ -772,6 +852,10 @@ def _maybe_compact_storage(self) -> None: return self.compact() + # ------------------------------------------------------------------ # + # Private helpers — assignment + # ------------------------------------------------------------------ # + def _assign(self, value: Any) -> None: """Dispatch assignment based on whether all fields or a subset are selected.""" if self._selected_fields is None: @@ -844,6 +928,10 @@ def _assign_selected_fields(self, value: Any) -> None: cell[:, field_indices] = chunk cursor += rows + # ------------------------------------------------------------------ # + # Private helpers — arithmetic + # ------------------------------------------------------------------ # + def _binary_op(self, other: Any, op: Any, reverse: bool = False) -> "Vector": """Return a new Vector produced by elementwise arithmetic.""" result = self.copy() @@ -901,46 +989,6 @@ def _inplace_op(self, other: Any, op: Any, reverse: bool = False) -> None: cell[:, field_indices] = op(chunk, lhs) if reverse else op(lhs, chunk) cursor += rows - def save( - self, - path: str | Path, - mode: Literal["w", "o"] = "w", - store: Literal["auto", "zip", "dir"] = "auto", - skip: str | type | Sequence[str | type] = (), - compression_level: int | None = 4, - ) -> None: - """ - Save the Vector object to disk using Zarr serialization. self.compact() is called before - saving to reduce file size if possible. - - Parameters - ---------- - path : str or Path - Target file path. Use '.zip' extension for zip format, otherwise a directory. - mode : {'w', 'o'} - 'w' = write only if file doesn't exist, 'o' = overwrite if it does. - store : {'auto', 'zip', 'dir'} - Storage format. 'auto' infers from file extension. - skip : str, type, or list of (str or type) - Attribute names/types to skip (by name or type) during serialization. - compression_level : int or None - If set (0–9), applies Zstandard compression with Blosc backend at that level. - Level 0 disables compression. Raises ValueError if > 9. - - Notes - ----- - Skipped attribute names and types are also stored in the file metadata for correct - round-trip skipping during load(). - """ - self.compact() - super().save( - path, - mode=mode, - store=store, - skip=skip, - compression_level=compression_level, - ) - def _resolve_fields( fields: Sequence[str] | None, From 9684e3eccc400625495d5f39c6744cad97d73cbb Mon Sep 17 00:00:00 2001 From: Art-MC Date: Thu, 5 Mar 2026 16:19:52 -0800 Subject: [PATCH 163/335] few simplifications to Vector --- src/quantem/core/datastructures/vector.py | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/quantem/core/datastructures/vector.py b/src/quantem/core/datastructures/vector.py index a956bd7e..aa2627d2 100644 --- a/src/quantem/core/datastructures/vector.py +++ b/src/quantem/core/datastructures/vector.py @@ -241,7 +241,7 @@ def fields(self) -> list[str]: @property def units(self) -> list[str]: """Return units for the selected fields.""" - lookup = {field: unit for field, unit in zip(self._state["fields"], self._state["units"])} + lookup = dict(zip(self._state["fields"], self._state["units"])) return [lookup[field] for field in self.fields] @property @@ -338,10 +338,7 @@ def select_fields(self, *field_names: str | Sequence[str]) -> "Vector": if missing: raise KeyError(f"Unknown field(s): {missing}") - if selected == tuple(self._state["fields"]): - selected_fields = None - else: - selected_fields = selected + selected_fields = None if selected == tuple(self._state["fields"]) else selected return self._from_view( self._state, self.shape, @@ -782,8 +779,6 @@ def _cell_matrix(self, linear_index: int) -> NDArray[Any]: """Return the full backing matrix for one cell.""" start = int(self._state["cell_starts"][linear_index]) length = int(self._state["cell_lengths"][linear_index]) - if length == 0: - return self._state["data"][0:0] return self._state["data"][start : start + length] def _selected_cell_matrix(self, linear_index: int) -> NDArray[Any]: @@ -817,11 +812,7 @@ def _replace_cells(self, targets: NDArray[np.int64], arrays: Sequence[NDArray[An payloads = [array for array in normalized if array.shape[0] > 0] if payloads: appended = np.vstack(payloads) - data = self._state["data"] - if data.shape[0] == 0: - self._state["data"] = appended.copy() - else: - self._state["data"] = np.concatenate((data, appended), axis=0) + self._state["data"] = np.concatenate((self._state["data"], appended), axis=0) cursor = self._state["data"].shape[0] - sum(array.shape[0] for array in normalized) for target, array in zip(targets, normalized): @@ -846,9 +837,7 @@ def _maybe_compact_storage(self) -> None: """Compact automatically once dead rows become materially larger than live rows.""" data = self._state["data"] used_rows = int(self._state["cell_lengths"].sum()) - if data.shape[0] <= used_rows + 1024: - return - if data.shape[0] <= 2 * used_rows: + if data.shape[0] <= used_rows + 1024 or data.shape[0] <= 2 * used_rows: return self.compact() From 758cf268001e19eed55b95df556aa097960b33ec Mon Sep 17 00:00:00 2001 From: darshan-mali Date: Fri, 6 Mar 2026 11:06:41 -0800 Subject: [PATCH 164/335] Created Lattice class. Added constructors, properties and define_lattice() --- src/quantem/imaging/__init__.py | 1 + src/quantem/imaging/lattice.py | 504 ++++++++++++++++++++++++++++++++ 2 files changed, 505 insertions(+) create mode 100644 src/quantem/imaging/lattice.py diff --git a/src/quantem/imaging/__init__.py b/src/quantem/imaging/__init__.py index 84b5d876..e2183514 100644 --- a/src/quantem/imaging/__init__.py +++ b/src/quantem/imaging/__init__.py @@ -1 +1,2 @@ from quantem.imaging.drift import DriftCorrection as DriftCorrection +from quantem.imaging.lattice import Lattice as Lattice diff --git a/src/quantem/imaging/lattice.py b/src/quantem/imaging/lattice.py new file mode 100644 index 00000000..5f9ec9fb --- /dev/null +++ b/src/quantem/imaging/lattice.py @@ -0,0 +1,504 @@ +import numpy as np +from numpy.typing import NDArray + +from quantem.core.datastructures.dataset2d import Dataset2d +from quantem.core.io.serialize import AutoSerialize +from quantem.core.visualization import show_2d + + +class Lattice(AutoSerialize): + """ + Atomic lattice fitting in 2D. + """ + + _token = object() + + def __init__( + self, + image: Dataset2d, + _token: object | None = None, + ): + if _token is not self._token: + raise RuntimeError("Use Lattice.from_data() to instantiate this class.") + self._image: Dataset2d = image + + # --- Constructors --- + @classmethod + def from_data( + cls, + image: Dataset2d | NDArray, + normalize_min: bool = True, + normalize_max: bool = True, + ) -> "Lattice": + """ + Create a Lattice instance from a 2D image-like input. + + Parameters: + - image: A 2D numpy array or a Dataset2d instance representing the image. + - normalize_min: If True, shift the image so its minimum becomes 0. + - normalize_max: If True, scale the image by its maximum after min-shift + so values are in [0, 1]. If the maximum is 0 or non-finite (NaN/Inf), + scaling is skipped to avoid invalid operations. + + Notes: + - Non-2D inputs and empty arrays raise a ValueError. + - Inputs with boolean dtype are safely converted to float before normalization. + - NaN values are ignored when computing min/max (using nanmin/nanmax). If the + data is all-NaN, normalization is skipped. + """ + if isinstance(image, Dataset2d): + ds2d = image + # Ensure numeric operations are valid (e.g., for bool dtype) + ds2d.array = np.asarray(ds2d.array, dtype=float) + # Validate shape + if ds2d.array.ndim != 2: + raise ValueError("Input image must be a 2D array.") + if ds2d.array.size == 0: + raise ValueError("Input image array must not be empty.") + else: + # Validate dimensionality and emptiness before any processing + arr = np.asarray(image) + if arr.ndim != 2: + raise ValueError("Input image must be a 2D array.") + if arr.size == 0: + raise ValueError("Input image array must not be empty.") + # Convert to float for safe arithmetic (handles bool arrays) + arr = arr.astype(float, copy=False) + if hasattr(Dataset2d, "from_array") and callable(getattr(Dataset2d, "from_array")): + ds2d = Dataset2d.from_array(arr) # type: ignore[attr-defined] + else: + ds2d = Dataset2d(arr) # type: ignore[call-arg] + + # Normalization (robust to constant, NaN, and bool inputs) + if normalize_min: + # Use nanmin to ignore NaNs; if all-NaN, skip + try: + min_val = np.nanmin(ds2d.array) + if np.isfinite(min_val): + ds2d.array = ds2d.array - min_val + except ValueError: + # Raised when all values are NaN; skip + pass + + if normalize_max: + # Use nanmax to ignore NaNs; skip division if max <= 0 or not finite + try: + max_val = np.nanmax(ds2d.array) + if np.isfinite(max_val) and max_val > 0.0: + ds2d.array = ds2d.array / max_val + except ValueError: + # Raised when all values are NaN; skip + pass + + return cls(image=ds2d, _token=cls._token) + + # --- Properties --- + @property + def image(self) -> Dataset2d: + return self._image + + @image.setter + def image(self, value: Dataset2d | NDArray): + if isinstance(value, Dataset2d): + # Ensure numeric dtype to avoid boolean arithmetic issues downstream + value.array = np.asarray(value.array, dtype=float) + # Validate shape + if value.array.ndim != 2: + raise ValueError("Input image must be a 2D array.") + if value.array.size == 0: + raise ValueError("Input image array must not be empty.") + self._image = value + else: + arr = np.asarray(value) + if arr.ndim != 2: + raise ValueError("Input image must be a 2D array.") + if arr.size == 0: + raise ValueError("Input image array must not be empty.") + arr = arr.astype(float, copy=False) + if hasattr(Dataset2d, "from_array") and callable(getattr(Dataset2d, "from_array")): + self._image = Dataset2d.from_array(arr) # type: ignore[attr-defined] + else: + self._image = Dataset2d(arr) # type: ignore[call-arg] + + # --- Functions --- + def define_lattice( + self, + origin, + u, + v, + refine_lattice: bool = True, + block_size: int | None = None, + plot_lattice: bool = True, + bound_num_vectors: int | None = None, + refine_maxiter: int = 200, + **kwargs, + ) -> "Lattice": + """ + Define the lattice for the image using the origin and the u and v vectors starting from the origin. + The lattice is defined as r = r0 + nu + mv. + + Parameters + ---------- + origin : NDArray[2] | Sequence[float] + Start point (r0) to define the lattice. + Enter as (row, col) as a numpy array, list, or tuple. + Ideally a lattice point. + u : NDArray[2] | Sequence[float] + Basis vector u to define the lattice. + Enter as (row, col) as a numpy array, list, or tuple. + v : NDArray[2] | Sequence[float] + Basis vector v to define the lattice. + Enter as (row, col) as a numpy array, list, or tuple. + refine_lattice : bool, default=True + If True, refines the values of r0, u, and v by maximizing the bilinear intensity sum. + block_size : int | None , default=None + Fit the lattice points in steps of block_size * lattice_vectors(u, v). + For example, if block_size = 5, then the lattice points will be fit in steps of + (-5, 5)u * (-5, 5)v -> (-10, 10)u * (-10, 10)v -> ... + block_size = None means the entire image will be fit at once. + plot_lattice : bool, default=True + If True, the lattice vectors and lines will be plotted overlaid on the image. + bound_num_vectors : int | None, default=None + The maximum number of lattice vectors to plot in each direction. + For example, if bound_num_vectors = 5, lattice lines between (-5, 5)u * (-5, 5)v will be plotted. + If None, the plotting bounds are set to the image edges. + refine_maxiter : int, default=200 + Maximum number of iterations for the lattice refinement optimizer (Powell method). + **kwargs + Additional keyword arguments forwarded to the plotting function (show_2d), e.g., cmap, title, etc. + + Returns + ------- + self : Lattice + Returns the same object, modified in-place. + The final values of r0, u, v are stored in self._lat. + """ + # Lattice + self._lat = np.vstack( + ( + np.array(origin), + np.array(u), + np.array(v), + ) + ) + if not self._lat.shape == (3, 2): + raise ValueError("origin, u, v must be in (row, col) format only.") + + # Refine lattice coordinates + # Note that we currently assume corners are local maxima + if refine_lattice: + from scipy.optimize import minimize + + assert block_size is None or block_size > 0, "block_size must be positive or None." + + H, W = self._image.shape + im = np.asarray(self._image.array, dtype=float) + r0, u, v = (np.asarray(x, dtype=float) for x in self._lat) + + corners = np.array( + [ + [0.0, 0.0], + [float(H), 0.0], + [0.0, float(W)], + [float(H), float(W)], + ], + dtype=float, + ) + + # a,b from corners; A = [u v] in columns (2x2), rhs = (corner - r0) + A = np.column_stack((u, v)) # (2,2) + ab = np.linalg.lstsq(A, (corners - r0[None, :]).T, rcond=None)[0] # (2,4) + + # Getting the min and max values for the indices a, b from the corners + a_min, a_max = int(np.floor(ab[0].min())), int(np.ceil(ab[0].max())) + b_min, b_max = int(np.floor(ab[1].min())), int(np.ceil(ab[1].max())) + + max_ind = max(abs(a_min), a_max, abs(b_min), b_max) + if not block_size: + steps = [max_ind] + else: + steps = ( + [*np.arange(0, max_ind + 1, block_size)[1:], max_ind] + if max_ind > 0 + else [max_ind] + ) + + PENALTY = 1e10 + H_CLIP = H - 2 + W_CLIP = W - 2 + + a_range = np.arange(max(a_min, -max_ind), min(a_max, max_ind) + 1, dtype=np.int32) + b_range = np.arange(max(b_min, -max_ind), min(b_max, max_ind) + 1, dtype=np.int32) + aa, bb = np.meshgrid(a_range, b_range, indexing="ij") + + # Pre-compute all masks and bases + all_masks = {} + all_bases = {} + for curr_block_size in steps: + a_min_blk = max(a_min, -curr_block_size) + a_max_blk = min(a_max, curr_block_size) + b_min_blk = max(b_min, -curr_block_size) + b_max_blk = min(b_max, curr_block_size) + + mask = ( + (aa >= a_min_blk) & (aa <= a_max_blk) & (bb >= b_min_blk) & (bb <= b_max_blk) + ) + + aa_masked = aa[mask] + bb_masked = bb[mask] + + all_masks[curr_block_size] = mask + all_bases[curr_block_size] = np.column_stack( + [np.ones(aa_masked.size), aa_masked.ravel(), bb_masked.ravel()] + ) + + # Pre-allocate cache + max_points = max(basis.shape[0] for basis in all_bases.values()) + x0_cache = np.empty(max_points, dtype=np.int32) + y0_cache = np.empty(max_points, dtype=np.int32) + dx_cache = np.empty(max_points, dtype=np.float64) + dy_cache = np.empty(max_points, dtype=np.float64) + + def bilinear_sum(im_: np.ndarray, xy: np.ndarray) -> float: + """Sum of bilinearly interpolated intensities at (x,y) points.""" + + n_points = xy.shape[0] + if n_points == 0: + return 0.0 + + x, y = xy[:, 0], xy[:, 1] + + # Filter points that are within valid bounds for bilinear interpolation + # Need x in [0, H-2] and y in [0, W-2] so that x+1 and y+1 are valid + valid_mask = ( + (x >= 0) + & (x <= H_CLIP) + & (y >= 0) + & (y <= W_CLIP) + & np.isfinite(x) + & np.isfinite(y) + ) + + n_valid = np.sum(valid_mask) + if n_valid == 0: + return -PENALTY + + x_valid = x[valid_mask] + y_valid = y[valid_mask] + + # Use pre-allocated arrays + x0, y0 = x0_cache[:n_valid], y0_cache[:n_valid] + dx, dy = dx_cache[:n_valid], dy_cache[:n_valid] + + np.floor(x_valid, out=dx) + x0[:] = dx.astype(np.int32) + np.floor(y_valid, out=dy) + y0[:] = dy.astype(np.int32) + + np.subtract(x_valid, x0, out=dx) + np.subtract(y_valid, y0, out=dy) + + Ia = im_[x0, y0] + Ib = im_[x0 + 1, y0] + Ic = im_[x0, y0 + 1] + Id = im_[x0 + 1, y0 + 1] + + return np.sum( + Ia * (1 - dx) * (1 - dy) + + Ib * dx * (1 - dy) + + Ic * (1 - dx) * dy + + Id * dx * dy + ) + + current_basis = None + + def objective(theta: np.ndarray) -> float: + """Function to be minimized""" + # theta is 6-vector -> (3,2) matrix [[r0],[u],[v]] + lat = theta.reshape(3, 2) + xy = current_basis @ lat # (N,2) with columns (x,y) + # Negative: maximize intensity sum by minimizing its negative + return -bilinear_sum(im, xy) + + minimize_options = { + "maxiter": int(refine_maxiter), + "xtol": 1e-3, + "ftol": 1e-3, + "disp": False, + } + + lat_flat = self._lat.astype(np.float32).reshape(-1) + + for curr_block_size in steps: + current_basis = all_bases[curr_block_size] + + res = minimize( + objective, + lat_flat, + method="Powell", + options=minimize_options, + ) + + # Update for next iteration + lat_flat = res.x + self._lat = res.x.reshape(3, 2) + + # plotting + if plot_lattice: + fig, ax = show_2d( + self._image.array, + returnfig=True, + **kwargs, + ) + + # Put the image at lowest zorder so overlays sit on top + if ax.images: + ax.images[-1].set_zorder(0) + + H, W = self._image.shape + r0, u, v = (np.asarray(x, dtype=float) for x in self._lat) + + # Origin marker (TOP of stack) + ax.scatter( + r0[1], + r0[0], # (y, x) + s=60, + edgecolor=(0, 0, 0), + facecolor=(0, 0.5, 0), + marker="s", + zorder=30, + ) + + # Lattice vectors as arrows + n_vec = int(bound_num_vectors) if bound_num_vectors is not None else 1 + + # draw n_vec arrows for u (red) + for k in range(1, n_vec + 1): + tip = r0 + k * u + ax.arrow( + r0[1], + r0[0], # base (y, x) + (tip - r0)[1], + (tip - r0)[0], # delta (y, x) + length_includes_head=True, + head_width=4.0, + head_length=6.0, + linewidth=2.0, + color="red", + zorder=20, + ) + + # draw n_vec arrows for v (cyan) + for k in range(1, n_vec + 1): + tip = r0 + k * v + ax.arrow( + r0[1], + r0[0], + (tip - r0)[1], + (tip - r0)[0], + length_includes_head=True, + head_width=4.0, + head_length=6.0, + linewidth=2.0, + color=(0.0, 0.7, 1.0), + zorder=20, + ) + + # Solve for a,b at plot corners (bounds) + if bound_num_vectors is None: + corners = np.array( + [ + [0.0, 0.0], + [float(H), 0.0], + [0.0, float(W)], + [float(H), float(W)], + ] + ) + else: + n = float(bound_num_vectors) + corners = np.array( + [ + r0 - n * u, + r0 - n * v, + r0 + n * u, + r0 + n * v, + ], + dtype=float, + ) + + # a,b from corners; A = [u v] in columns (2x2), rhs = (corner - r0) + A = np.column_stack((u, v)) + ab = np.linalg.lstsq(A, (corners - r0[None, :]).T, rcond=None)[0] + + a_min, a_max = int(np.floor(np.min(ab[0]))), int(np.ceil(np.max(ab[0]))) + b_min, b_max = int(np.floor(np.min(ab[1]))), int(np.ceil(np.max(ab[1]))) + + # Clipping rectangle (image or custom) + if bound_num_vectors is None: + x_lo, x_hi = 0.0, float(H) + y_lo, y_hi = 0.0, float(W) + else: + # Bounds are the min/max over the provided corners + x_lo, x_hi = float(np.min(corners[:, 0])), float(np.max(corners[:, 0])) + y_lo, y_hi = float(np.min(corners[:, 1])), float(np.max(corners[:, 1])) + + def clipped_segment(base: np.ndarray, direction: np.ndarray): + """Clip base + t*direction to rectangle [x_lo,x_hi] x [y_lo,y_hi].""" + x0, y0 = base + dx, dy = direction + t0, t1 = -np.inf, np.inf + eps = 1e-12 + + # x in [x_lo, x_hi] + if abs(dx) < eps: + if not (x_lo <= x0 <= x_hi): + return None + else: + tx0 = (x_lo - x0) / dx + tx1 = (x_hi - x0) / dx + t_enter, t_exit = (tx0, tx1) if tx0 <= tx1 else (tx1, tx0) + t0, t1 = max(t0, t_enter), min(t1, t_exit) + + # y in [y_lo, y_hi] + if abs(dy) < eps: + if not (y_lo <= y0 <= y_hi): + return None + else: + ty0 = (y_lo - y0) / dy + ty1 = (y_hi - y0) / dy + t_enter, t_exit = (ty0, ty1) if ty0 <= ty1 else (ty1, ty0) + t0, t1 = max(t0, t_enter), min(t1, t_exit) + + if t0 > t1: + return None + + p1 = base + t0 * direction # (x, y) + p2 = base + t1 * direction + return p1, p2 + + # Lattice lines (zorder above image) + # Using x=rows, y=cols: plot(y, x) + + # Lines parallel to v (vary a) + for a in range(a_min, a_max + 1): + base = r0 + a * u + seg = clipped_segment(base, v) + if seg is None: + continue + (x1, y1), (x2, y2) = seg + ax.plot([y1, y2], [x1, x2], color=(0.0, 0.7, 1.0), lw=1, clip_on=True, zorder=10) + + # Lines parallel to u (vary b) + for b in range(b_min, b_max + 1): + base = r0 + b * v + seg = clipped_segment(base, u) + if seg is None: + continue + (x1, y1), (x2, y2) = seg + ax.plot([y1, y2], [x1, x2], color="red", lw=1, clip_on=True, zorder=10) + + # Axes limits (x=rows vertical; y=cols horizontal) + ax.set_xlim(y_lo, y_hi) + ax.set_ylim(x_hi, x_lo) + + return self From bee6bb104dacd0b8f01efb0a3a68dfd430845f4d Mon Sep 17 00:00:00 2001 From: quantem-bot Date: Mon, 9 Mar 2026 10:00:08 +0000 Subject: [PATCH 165/335] chore: update lock file --- uv.lock | 578 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 308 insertions(+), 270 deletions(-) diff --git a/uv.lock b/uv.lock index 13d0ff9e..2838cec7 100644 --- a/uv.lock +++ b/uv.lock @@ -284,75 +284,75 @@ wheels = [ [[package]] name = "charset-normalizer" -version = "3.4.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, - { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, - { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, - { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, - { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, - { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, - { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, - { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, - { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, - { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, - { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, - { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, - { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, - { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, - { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, - { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, - { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, - { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, - { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, - { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, - { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, - { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, - { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, - { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, - { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, - { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, - { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, - { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, - { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, - { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, - { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, - { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, - { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, - { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, - { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, - { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, - { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, - { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, - { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, - { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, - { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, - { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, - { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, - { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, - { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, - { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, - { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, - { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, - { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, - { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, - { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, - { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, - { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, - { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, - { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, - { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, - { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, - { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, - { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, - { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, - { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, - { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, - { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, - { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +version = "3.4.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/35/02daf95b9cd686320bb622eb148792655c9412dbb9b67abb5694e5910a24/charset_normalizer-3.4.5.tar.gz", hash = "sha256:95adae7b6c42a6c5b5b559b1a99149f090a57128155daeea91732c8d970d8644", size = 134804, upload-time = "2026-03-06T06:03:19.46Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/9e/bcec3b22c64ecec47d39bf5167c2613efd41898c019dccd4183f6aa5d6a7/charset_normalizer-3.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:610f72c0ee565dfb8ae1241b666119582fdbfe7c0975c175be719f940e110694", size = 279531, upload-time = "2026-03-06T06:00:52.252Z" }, + { url = "https://files.pythonhosted.org/packages/58/12/81fd25f7e7078ab5d1eedbb0fac44be4904ae3370a3bf4533c8f2d159acd/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60d68e820af339df4ae8358c7a2e7596badeb61e544438e489035f9fbf3246a5", size = 188006, upload-time = "2026-03-06T06:00:53.8Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6e/f2d30e8c27c1b0736a6520311982cf5286cfc7f6cac77d7bc1325e3a23f2/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:10b473fc8dca1c3ad8559985794815f06ca3fc71942c969129070f2c3cdf7281", size = 205085, upload-time = "2026-03-06T06:00:55.311Z" }, + { url = "https://files.pythonhosted.org/packages/d0/90/d12cefcb53b5931e2cf792a33718d7126efb116a320eaa0742c7059a95e4/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d4eb8ac7469b2a5d64b5b8c04f84d8bf3ad340f4514b98523805cbf46e3b3923", size = 200545, upload-time = "2026-03-06T06:00:56.532Z" }, + { url = "https://files.pythonhosted.org/packages/03/f4/44d3b830a20e89ff82a3134912d9a1cf6084d64f3b95dcad40f74449a654/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bcb3227c3d9aaf73eaaab1db7ccd80a8995c509ee9941e2aae060ca6e4e5d81", size = 193863, upload-time = "2026-03-06T06:00:57.823Z" }, + { url = "https://files.pythonhosted.org/packages/25/4b/f212119c18a6320a9d4a730d1b4057875cdeabf21b3614f76549042ef8a8/charset_normalizer-3.4.5-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:75ee9c1cce2911581a70a3c0919d8bccf5b1cbc9b0e5171400ec736b4b569497", size = 181827, upload-time = "2026-03-06T06:00:59.323Z" }, + { url = "https://files.pythonhosted.org/packages/74/00/b26158e48b425a202a92965f8069e8a63d9af1481dfa206825d7f74d2a3c/charset_normalizer-3.4.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d1401945cb77787dbd3af2446ff2d75912327c4c3a1526ab7955ecf8600687c", size = 191085, upload-time = "2026-03-06T06:01:00.546Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c2/1c1737bf6fd40335fe53d28fe49afd99ee4143cc57a845e99635ce0b9b6d/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a45e504f5e1be0bd385935a8e1507c442349ca36f511a47057a71c9d1d6ea9e", size = 190688, upload-time = "2026-03-06T06:01:02.479Z" }, + { url = "https://files.pythonhosted.org/packages/5a/3d/abb5c22dc2ef493cd56522f811246a63c5427c08f3e3e50ab663de27fcf4/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e09f671a54ce70b79a1fc1dc6da3072b7ef7251fadb894ed92d9aa8218465a5f", size = 183077, upload-time = "2026-03-06T06:01:04.231Z" }, + { url = "https://files.pythonhosted.org/packages/44/33/5298ad4d419a58e25b3508e87f2758d1442ff00c2471f8e0403dab8edad5/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d01de5e768328646e6a3fa9e562706f8f6641708c115c62588aef2b941a4f88e", size = 206706, upload-time = "2026-03-06T06:01:05.773Z" }, + { url = "https://files.pythonhosted.org/packages/7b/17/51e7895ac0f87c3b91d276a449ef09f5532a7529818f59646d7a55089432/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:131716d6786ad5e3dc542f5cc6f397ba3339dc0fb87f87ac30e550e8987756af", size = 191665, upload-time = "2026-03-06T06:01:07.473Z" }, + { url = "https://files.pythonhosted.org/packages/90/8f/cce9adf1883e98906dbae380d769b4852bb0fa0004bc7d7a2243418d3ea8/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a374cc0b88aa710e8865dc1bd6edb3743c59f27830f0293ab101e4cf3ce9f85", size = 201950, upload-time = "2026-03-06T06:01:08.973Z" }, + { url = "https://files.pythonhosted.org/packages/08/ca/bce99cd5c397a52919e2769d126723f27a4c037130374c051c00470bcd38/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d31f0d1671e1534e395f9eb84a68e0fb670e1edb1fe819a9d7f564ae3bc4e53f", size = 195830, upload-time = "2026-03-06T06:01:10.155Z" }, + { url = "https://files.pythonhosted.org/packages/87/4f/2e3d023a06911f1281f97b8f036edc9872167036ca6f55cc874a0be6c12c/charset_normalizer-3.4.5-cp311-cp311-win32.whl", hash = "sha256:cace89841c0599d736d3d74a27bc5821288bb47c5441923277afc6059d7fbcb4", size = 132029, upload-time = "2026-03-06T06:01:11.706Z" }, + { url = "https://files.pythonhosted.org/packages/fe/1f/a853b73d386521fd44b7f67ded6b17b7b2367067d9106a5c4b44f9a34274/charset_normalizer-3.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:f8102ae93c0bc863b1d41ea0f4499c20a83229f52ed870850892df555187154a", size = 142404, upload-time = "2026-03-06T06:01:12.865Z" }, + { url = "https://files.pythonhosted.org/packages/b4/10/dba36f76b71c38e9d391abe0fd8a5b818790e053c431adecfc98c35cd2a9/charset_normalizer-3.4.5-cp311-cp311-win_arm64.whl", hash = "sha256:ed98364e1c262cf5f9363c3eca8c2df37024f52a8fa1180a3610014f26eac51c", size = 132796, upload-time = "2026-03-06T06:01:14.106Z" }, + { url = "https://files.pythonhosted.org/packages/9c/b6/9ee9c1a608916ca5feae81a344dffbaa53b26b90be58cc2159e3332d44ec/charset_normalizer-3.4.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed97c282ee4f994ef814042423a529df9497e3c666dca19be1d4cd1129dc7ade", size = 280976, upload-time = "2026-03-06T06:01:15.276Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d8/a54f7c0b96f1df3563e9190f04daf981e365a9b397eedfdfb5dbef7e5c6c/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0294916d6ccf2d069727d65973c3a1ca477d68708db25fd758dd28b0827cff54", size = 189356, upload-time = "2026-03-06T06:01:16.511Z" }, + { url = "https://files.pythonhosted.org/packages/42/69/2bf7f76ce1446759a5787cb87d38f6a61eb47dbbdf035cfebf6347292a65/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dc57a0baa3eeedd99fafaef7511b5a6ef4581494e8168ee086031744e2679467", size = 206369, upload-time = "2026-03-06T06:01:17.853Z" }, + { url = "https://files.pythonhosted.org/packages/10/9c/949d1a46dab56b959d9a87272482195f1840b515a3380e39986989a893ae/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ed1a9a204f317ef879b32f9af507d47e49cd5e7f8e8d5d96358c98373314fc60", size = 203285, upload-time = "2026-03-06T06:01:19.473Z" }, + { url = "https://files.pythonhosted.org/packages/67/5c/ae30362a88b4da237d71ea214a8c7eb915db3eec941adda511729ac25fa2/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ad83b8f9379176c841f8865884f3514d905bcd2a9a3b210eaa446e7d2223e4d", size = 196274, upload-time = "2026-03-06T06:01:20.728Z" }, + { url = "https://files.pythonhosted.org/packages/b2/07/c9f2cb0e46cb6d64fdcc4f95953747b843bb2181bda678dc4e699b8f0f9a/charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:a118e2e0b5ae6b0120d5efa5f866e58f2bb826067a646431da4d6a2bdae7950e", size = 184715, upload-time = "2026-03-06T06:01:22.194Z" }, + { url = "https://files.pythonhosted.org/packages/36/64/6b0ca95c44fddf692cd06d642b28f63009d0ce325fad6e9b2b4d0ef86a52/charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:754f96058e61a5e22e91483f823e07df16416ce76afa4ebf306f8e1d1296d43f", size = 193426, upload-time = "2026-03-06T06:01:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/50/bc/a730690d726403743795ca3f5bb2baf67838c5fea78236098f324b965e40/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0c300cefd9b0970381a46394902cd18eaf2aa00163f999590ace991989dcd0fc", size = 191780, upload-time = "2026-03-06T06:01:25.053Z" }, + { url = "https://files.pythonhosted.org/packages/97/4f/6c0bc9af68222b22951552d73df4532b5be6447cee32d58e7e8c74ecbb7b/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c108f8619e504140569ee7de3f97d234f0fbae338a7f9f360455071ef9855a95", size = 185805, upload-time = "2026-03-06T06:01:26.294Z" }, + { url = "https://files.pythonhosted.org/packages/dd/b9/a523fb9b0ee90814b503452b2600e4cbc118cd68714d57041564886e7325/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d1028de43596a315e2720a9849ee79007ab742c06ad8b45a50db8cdb7ed4a82a", size = 208342, upload-time = "2026-03-06T06:01:27.55Z" }, + { url = "https://files.pythonhosted.org/packages/4d/61/c59e761dee4464050713e50e27b58266cc8e209e518c0b378c1580c959ba/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:19092dde50335accf365cce21998a1c6dd8eafd42c7b226eb54b2747cdce2fac", size = 193661, upload-time = "2026-03-06T06:01:29.051Z" }, + { url = "https://files.pythonhosted.org/packages/1c/43/729fa30aad69783f755c5ad8649da17ee095311ca42024742701e202dc59/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4354e401eb6dab9aed3c7b4030514328a6c748d05e1c3e19175008ca7de84fb1", size = 204819, upload-time = "2026-03-06T06:01:30.298Z" }, + { url = "https://files.pythonhosted.org/packages/87/33/d9b442ce5a91b96fc0840455a9e49a611bbadae6122778d0a6a79683dd31/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a68766a3c58fde7f9aaa22b3786276f62ab2f594efb02d0a1421b6282e852e98", size = 198080, upload-time = "2026-03-06T06:01:31.478Z" }, + { url = "https://files.pythonhosted.org/packages/56/5a/b8b5a23134978ee9885cee2d6995f4c27cc41f9baded0a9685eabc5338f0/charset_normalizer-3.4.5-cp312-cp312-win32.whl", hash = "sha256:1827734a5b308b65ac54e86a618de66f935a4f63a8a462ff1e19a6788d6c2262", size = 132630, upload-time = "2026-03-06T06:01:33.056Z" }, + { url = "https://files.pythonhosted.org/packages/70/53/e44a4c07e8904500aec95865dc3f6464dc3586a039ef0df606eb3ac38e35/charset_normalizer-3.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:728c6a963dfab66ef865f49286e45239384249672cd598576765acc2a640a636", size = 142856, upload-time = "2026-03-06T06:01:34.489Z" }, + { url = "https://files.pythonhosted.org/packages/ea/aa/c5628f7cad591b1cf45790b7a61483c3e36cf41349c98af7813c483fd6e8/charset_normalizer-3.4.5-cp312-cp312-win_arm64.whl", hash = "sha256:75dfd1afe0b1647449e852f4fb428195a7ed0588947218f7ba929f6538487f02", size = 132982, upload-time = "2026-03-06T06:01:35.641Z" }, + { url = "https://files.pythonhosted.org/packages/f5/48/9f34ec4bb24aa3fdba1890c1bddb97c8a4be1bd84ef5c42ac2352563ad05/charset_normalizer-3.4.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ac59c15e3f1465f722607800c68713f9fbc2f672b9eb649fe831da4019ae9b23", size = 280788, upload-time = "2026-03-06T06:01:37.126Z" }, + { url = "https://files.pythonhosted.org/packages/0e/09/6003e7ffeb90cc0560da893e3208396a44c210c5ee42efff539639def59b/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:165c7b21d19365464e8f70e5ce5e12524c58b48c78c1f5a57524603c1ab003f8", size = 188890, upload-time = "2026-03-06T06:01:38.73Z" }, + { url = "https://files.pythonhosted.org/packages/42/1e/02706edf19e390680daa694d17e2b8eab4b5f7ac285e2a51168b4b22ee6b/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:28269983f25a4da0425743d0d257a2d6921ea7d9b83599d4039486ec5b9f911d", size = 206136, upload-time = "2026-03-06T06:01:40.016Z" }, + { url = "https://files.pythonhosted.org/packages/c7/87/942c3def1b37baf3cf786bad01249190f3ca3d5e63a84f831e704977de1f/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d27ce22ec453564770d29d03a9506d449efbb9fa13c00842262b2f6801c48cce", size = 202551, upload-time = "2026-03-06T06:01:41.522Z" }, + { url = "https://files.pythonhosted.org/packages/94/0a/af49691938dfe175d71b8a929bd7e4ace2809c0c5134e28bc535660d5262/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0625665e4ebdddb553ab185de5db7054393af8879fb0c87bd5690d14379d6819", size = 195572, upload-time = "2026-03-06T06:01:43.208Z" }, + { url = "https://files.pythonhosted.org/packages/20/ea/dfb1792a8050a8e694cfbde1570ff97ff74e48afd874152d38163d1df9ae/charset_normalizer-3.4.5-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:c23eb3263356d94858655b3e63f85ac5d50970c6e8febcdde7830209139cc37d", size = 184438, upload-time = "2026-03-06T06:01:44.755Z" }, + { url = "https://files.pythonhosted.org/packages/72/12/c281e2067466e3ddd0595bfaea58a6946765ace5c72dfa3edc2f5f118026/charset_normalizer-3.4.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e6302ca4ae283deb0af68d2fbf467474b8b6aedcd3dab4db187e07f94c109763", size = 193035, upload-time = "2026-03-06T06:01:46.051Z" }, + { url = "https://files.pythonhosted.org/packages/ba/4f/3792c056e7708e10464bad0438a44708886fb8f92e3c3d29ec5e2d964d42/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e51ae7d81c825761d941962450f50d041db028b7278e7b08930b4541b3e45cb9", size = 191340, upload-time = "2026-03-06T06:01:47.547Z" }, + { url = "https://files.pythonhosted.org/packages/e7/86/80ddba897127b5c7a9bccc481b0cd36c8fefa485d113262f0fe4332f0bf4/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:597d10dec876923e5c59e48dbd366e852eacb2b806029491d307daea6b917d7c", size = 185464, upload-time = "2026-03-06T06:01:48.764Z" }, + { url = "https://files.pythonhosted.org/packages/4d/00/b5eff85ba198faacab83e0e4b6f0648155f072278e3b392a82478f8b988b/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5cffde4032a197bd3b42fd0b9509ec60fb70918d6970e4cc773f20fc9180ca67", size = 208014, upload-time = "2026-03-06T06:01:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/c8/11/d36f70be01597fd30850dde8a1269ebc8efadd23ba5785808454f2389bde/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2da4eedcb6338e2321e831a0165759c0c620e37f8cd044a263ff67493be8ffb3", size = 193297, upload-time = "2026-03-06T06:01:51.933Z" }, + { url = "https://files.pythonhosted.org/packages/1a/1d/259eb0a53d4910536c7c2abb9cb25f4153548efb42800c6a9456764649c0/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:65a126fb4b070d05340a84fc709dd9e7c75d9b063b610ece8a60197a291d0adf", size = 204321, upload-time = "2026-03-06T06:01:53.887Z" }, + { url = "https://files.pythonhosted.org/packages/84/31/faa6c5b9d3688715e1ed1bb9d124c384fe2fc1633a409e503ffe1c6398c1/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7a80a9242963416bd81f99349d5f3fce1843c303bd404f204918b6d75a75fd6", size = 197509, upload-time = "2026-03-06T06:01:56.439Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a5/c7d9dd1503ffc08950b3260f5d39ec2366dd08254f0900ecbcf3a6197c7c/charset_normalizer-3.4.5-cp313-cp313-win32.whl", hash = "sha256:f1d725b754e967e648046f00c4facc42d414840f5ccc670c5670f59f83693e4f", size = 132284, upload-time = "2026-03-06T06:01:57.812Z" }, + { url = "https://files.pythonhosted.org/packages/b9/0f/57072b253af40c8aa6636e6de7d75985624c1eb392815b2f934199340a89/charset_normalizer-3.4.5-cp313-cp313-win_amd64.whl", hash = "sha256:e37bd100d2c5d3ba35db9c7c5ba5a9228cbcffe5c4778dc824b164e5257813d7", size = 142630, upload-time = "2026-03-06T06:01:59.062Z" }, + { url = "https://files.pythonhosted.org/packages/31/41/1c4b7cc9f13bd9d369ce3bc993e13d374ce25fa38a2663644283ecf422c1/charset_normalizer-3.4.5-cp313-cp313-win_arm64.whl", hash = "sha256:93b3b2cc5cf1b8743660ce77a4f45f3f6d1172068207c1defc779a36eea6bb36", size = 133254, upload-time = "2026-03-06T06:02:00.281Z" }, + { url = "https://files.pythonhosted.org/packages/43/be/0f0fd9bb4a7fa4fb5067fb7d9ac693d4e928d306f80a0d02bde43a7c4aee/charset_normalizer-3.4.5-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8197abe5ca1ffb7d91e78360f915eef5addff270f8a71c1fc5be24a56f3e4873", size = 280232, upload-time = "2026-03-06T06:02:01.508Z" }, + { url = "https://files.pythonhosted.org/packages/28/02/983b5445e4bef49cd8c9da73a8e029f0825f39b74a06d201bfaa2e55142a/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2aecdb364b8a1802afdc7f9327d55dad5366bc97d8502d0f5854e50712dbc5f", size = 189688, upload-time = "2026-03-06T06:02:02.857Z" }, + { url = "https://files.pythonhosted.org/packages/d0/88/152745c5166437687028027dc080e2daed6fe11cfa95a22f4602591c42db/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a66aa5022bf81ab4b1bebfb009db4fd68e0c6d4307a1ce5ef6a26e5878dfc9e4", size = 206833, upload-time = "2026-03-06T06:02:05.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0f/ebc15c8b02af2f19be9678d6eed115feeeccc45ce1f4b098d986c13e8769/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d77f97e515688bd615c1d1f795d540f32542d514242067adcb8ef532504cb9ee", size = 202879, upload-time = "2026-03-06T06:02:06.446Z" }, + { url = "https://files.pythonhosted.org/packages/38/9c/71336bff6934418dc8d1e8a1644176ac9088068bc571da612767619c97b3/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01a1ed54b953303ca7e310fafe0fe347aab348bd81834a0bcd602eb538f89d66", size = 195764, upload-time = "2026-03-06T06:02:08.763Z" }, + { url = "https://files.pythonhosted.org/packages/b7/95/ce92fde4f98615661871bc282a856cf9b8a15f686ba0af012984660d480b/charset_normalizer-3.4.5-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:b2d37d78297b39a9eb9eb92c0f6df98c706467282055419df141389b23f93362", size = 183728, upload-time = "2026-03-06T06:02:10.137Z" }, + { url = "https://files.pythonhosted.org/packages/1c/e7/f5b4588d94e747ce45ae680f0f242bc2d98dbd4eccfab73e6160b6893893/charset_normalizer-3.4.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e71bbb595973622b817c042bd943c3f3667e9c9983ce3d205f973f486fec98a7", size = 192937, upload-time = "2026-03-06T06:02:11.663Z" }, + { url = "https://files.pythonhosted.org/packages/f9/29/9d94ed6b929bf9f48bf6ede6e7474576499f07c4c5e878fb186083622716/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4cd966c2559f501c6fd69294d082c2934c8dd4719deb32c22961a5ac6db0df1d", size = 192040, upload-time = "2026-03-06T06:02:13.489Z" }, + { url = "https://files.pythonhosted.org/packages/15/d2/1a093a1cf827957f9445f2fe7298bcc16f8fc5e05c1ed2ad1af0b239035e/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d5e52d127045d6ae01a1e821acfad2f3a1866c54d0e837828538fabe8d9d1bd6", size = 184107, upload-time = "2026-03-06T06:02:14.83Z" }, + { url = "https://files.pythonhosted.org/packages/0f/7d/82068ce16bd36135df7b97f6333c5d808b94e01d4599a682e2337ed5fd14/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:30a2b1a48478c3428d047ed9690d57c23038dac838a87ad624c85c0a78ebeb39", size = 208310, upload-time = "2026-03-06T06:02:16.165Z" }, + { url = "https://files.pythonhosted.org/packages/84/4e/4dfb52307bb6af4a5c9e73e482d171b81d36f522b21ccd28a49656baa680/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d8ed79b8f6372ca4254955005830fd61c1ccdd8c0fac6603e2c145c61dd95db6", size = 192918, upload-time = "2026-03-06T06:02:18.144Z" }, + { url = "https://files.pythonhosted.org/packages/08/a4/159ff7da662cf7201502ca89980b8f06acf3e887b278956646a8aeb178ab/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:c5af897b45fa606b12464ccbe0014bbf8c09191e0a66aab6aa9d5cf6e77e0c94", size = 204615, upload-time = "2026-03-06T06:02:19.821Z" }, + { url = "https://files.pythonhosted.org/packages/d6/62/0dd6172203cb6b429ffffc9935001fde42e5250d57f07b0c28c6046deb6b/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1088345bcc93c58d8d8f3d783eca4a6e7a7752bbff26c3eee7e73c597c191c2e", size = 197784, upload-time = "2026-03-06T06:02:21.86Z" }, + { url = "https://files.pythonhosted.org/packages/c7/5e/1aab5cb737039b9c59e63627dc8bbc0d02562a14f831cc450e5f91d84ce1/charset_normalizer-3.4.5-cp314-cp314-win32.whl", hash = "sha256:ee57b926940ba00bca7ba7041e665cc956e55ef482f851b9b65acb20d867e7a2", size = 133009, upload-time = "2026-03-06T06:02:23.289Z" }, + { url = "https://files.pythonhosted.org/packages/40/65/e7c6c77d7aaa4c0d7974f2e403e17f0ed2cb0fc135f77d686b916bf1eead/charset_normalizer-3.4.5-cp314-cp314-win_amd64.whl", hash = "sha256:4481e6da1830c8a1cc0b746b47f603b653dadb690bcd851d039ffaefe70533aa", size = 143511, upload-time = "2026-03-06T06:02:26.195Z" }, + { url = "https://files.pythonhosted.org/packages/ba/91/52b0841c71f152f563b8e072896c14e3d83b195c188b338d3cc2e582d1d4/charset_normalizer-3.4.5-cp314-cp314-win_arm64.whl", hash = "sha256:97ab7787092eb9b50fb47fa04f24c75b768a606af1bcba1957f07f128a7219e4", size = 133775, upload-time = "2026-03-06T06:02:27.473Z" }, + { url = "https://files.pythonhosted.org/packages/c5/60/3a621758945513adfd4db86827a5bafcc615f913dbd0b4c2ed64a65731be/charset_normalizer-3.4.5-py3-none-any.whl", hash = "sha256:9db5e3fcdcee89a78c04dffb3fe33c79f77bd741a624946db2591c81b2fc85b0", size = 55455, upload-time = "2026-03-06T06:03:17.827Z" }, ] [[package]] @@ -636,10 +636,10 @@ wheels = [ [[package]] name = "cuda-pathfinder" -version = "1.4.0" +version = "1.4.1" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/60/d8f1dbfb7f06b94c662e98c95189e6f39b817da638bc8fcea0d003f89e5d/cuda_pathfinder-1.4.0-py3-none-any.whl", hash = "sha256:437079ca59e7b61ae439ecc501d69ed87b3accc34d58153ef1e54815e2c2e118", size = 38406, upload-time = "2026-02-25T22:13:00.807Z" }, + { url = "https://files.pythonhosted.org/packages/07/02/59a5bc738a09def0b49aea0e460bdf97f65206d0d041246147cf6207e69c/cuda_pathfinder-1.4.1-py3-none-any.whl", hash = "sha256:40793006082de88e0950753655e55558a446bed9a7d9d0bcb48b2506d50ed82a", size = 43903, upload-time = "2026-03-06T21:05:24.372Z" }, ] [[package]] @@ -1005,45 +1005,53 @@ wheels = [ [[package]] name = "h5py" -version = "3.15.1" +version = "3.16.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4d/6a/0d79de0b025aa85dc8864de8e97659c94cf3d23148394a954dc5ca52f8c8/h5py-3.15.1.tar.gz", hash = "sha256:c86e3ed45c4473564de55aa83b6fc9e5ead86578773dfbd93047380042e26b69", size = 426236, upload-time = "2025-10-16T10:35:27.404Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/41/fd/8349b48b15b47768042cff06ad6e1c229f0a4bd89225bf6b6894fea27e6d/h5py-3.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5aaa330bcbf2830150c50897ea5dcbed30b5b6d56897289846ac5b9e529ec243", size = 3434135, upload-time = "2025-10-16T10:33:47.954Z" }, - { url = "https://files.pythonhosted.org/packages/c1/b0/1c628e26a0b95858f54aba17e1599e7f6cd241727596cc2580b72cb0a9bf/h5py-3.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c970fb80001fffabb0109eaf95116c8e7c0d3ca2de854e0901e8a04c1f098509", size = 2870958, upload-time = "2025-10-16T10:33:50.907Z" }, - { url = "https://files.pythonhosted.org/packages/f9/e3/c255cafc9b85e6ea04e2ad1bba1416baa1d7f57fc98a214be1144087690c/h5py-3.15.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80e5bb5b9508d5d9da09f81fd00abbb3f85da8143e56b1585d59bc8ceb1dba8b", size = 4504770, upload-time = "2025-10-16T10:33:54.357Z" }, - { url = "https://files.pythonhosted.org/packages/8b/23/4ab1108e87851ccc69694b03b817d92e142966a6c4abd99e17db77f2c066/h5py-3.15.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b849ba619a066196169763c33f9f0f02e381156d61c03e000bb0100f9950faf", size = 4700329, upload-time = "2025-10-16T10:33:57.616Z" }, - { url = "https://files.pythonhosted.org/packages/a4/e4/932a3a8516e4e475b90969bf250b1924dbe3612a02b897e426613aed68f4/h5py-3.15.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e7f6c841efd4e6e5b7e82222eaf90819927b6d256ab0f3aca29675601f654f3c", size = 4152456, upload-time = "2025-10-16T10:34:00.843Z" }, - { url = "https://files.pythonhosted.org/packages/2a/0a/f74d589883b13737021b2049ac796328f188dbb60c2ed35b101f5b95a3fc/h5py-3.15.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ca8a3a22458956ee7b40d8e39c9a9dc01f82933e4c030c964f8b875592f4d831", size = 4617295, upload-time = "2025-10-16T10:34:04.154Z" }, - { url = "https://files.pythonhosted.org/packages/23/95/499b4e56452ef8b6c95a271af0dde08dac4ddb70515a75f346d4f400579b/h5py-3.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:550e51131376889656feec4aff2170efc054a7fe79eb1da3bb92e1625d1ac878", size = 2882129, upload-time = "2025-10-16T10:34:06.886Z" }, - { url = "https://files.pythonhosted.org/packages/ce/bb/cfcc70b8a42222ba3ad4478bcef1791181ea908e2adbd7d53c66395edad5/h5py-3.15.1-cp311-cp311-win_arm64.whl", hash = "sha256:b39239947cb36a819147fc19e86b618dcb0953d1cd969f5ed71fc0de60392427", size = 2477121, upload-time = "2025-10-16T10:34:09.579Z" }, - { url = "https://files.pythonhosted.org/packages/62/b8/c0d9aa013ecfa8b7057946c080c0c07f6fa41e231d2e9bd306a2f8110bdc/h5py-3.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:316dd0f119734f324ca7ed10b5627a2de4ea42cc4dfbcedbee026aaa361c238c", size = 3399089, upload-time = "2025-10-16T10:34:12.135Z" }, - { url = "https://files.pythonhosted.org/packages/a4/5e/3c6f6e0430813c7aefe784d00c6711166f46225f5d229546eb53032c3707/h5py-3.15.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b51469890e58e85d5242e43aab29f5e9c7e526b951caab354f3ded4ac88e7b76", size = 2847803, upload-time = "2025-10-16T10:34:14.564Z" }, - { url = "https://files.pythonhosted.org/packages/00/69/ba36273b888a4a48d78f9268d2aee05787e4438557450a8442946ab8f3ec/h5py-3.15.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a33bfd5dfcea037196f7778534b1ff7e36a7f40a89e648c8f2967292eb6898e", size = 4914884, upload-time = "2025-10-16T10:34:18.452Z" }, - { url = "https://files.pythonhosted.org/packages/3a/30/d1c94066343a98bb2cea40120873193a4fed68c4ad7f8935c11caf74c681/h5py-3.15.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25c8843fec43b2cc368aa15afa1cdf83fc5e17b1c4e10cd3771ef6c39b72e5ce", size = 5109965, upload-time = "2025-10-16T10:34:21.853Z" }, - { url = "https://files.pythonhosted.org/packages/81/3d/d28172116eafc3bc9f5991b3cb3fd2c8a95f5984f50880adfdf991de9087/h5py-3.15.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a308fd8681a864c04423c0324527237a0484e2611e3441f8089fd00ed56a8171", size = 4561870, upload-time = "2025-10-16T10:34:26.69Z" }, - { url = "https://files.pythonhosted.org/packages/a5/83/393a7226024238b0f51965a7156004eaae1fcf84aa4bfecf7e582676271b/h5py-3.15.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f4a016df3f4a8a14d573b496e4d1964deb380e26031fc85fb40e417e9131888a", size = 5037161, upload-time = "2025-10-16T10:34:30.383Z" }, - { url = "https://files.pythonhosted.org/packages/cf/51/329e7436bf87ca6b0fe06dd0a3795c34bebe4ed8d6c44450a20565d57832/h5py-3.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:59b25cf02411bf12e14f803fef0b80886444c7fe21a5ad17c6a28d3f08098a1e", size = 2874165, upload-time = "2025-10-16T10:34:33.461Z" }, - { url = "https://files.pythonhosted.org/packages/09/a8/2d02b10a66747c54446e932171dd89b8b4126c0111b440e6bc05a7c852ec/h5py-3.15.1-cp312-cp312-win_arm64.whl", hash = "sha256:61d5a58a9851e01ee61c932bbbb1c98fe20aba0a5674776600fb9a361c0aa652", size = 2458214, upload-time = "2025-10-16T10:34:35.733Z" }, - { url = "https://files.pythonhosted.org/packages/88/b3/40207e0192415cbff7ea1d37b9f24b33f6d38a5a2f5d18a678de78f967ae/h5py-3.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8440fd8bee9500c235ecb7aa1917a0389a2adb80c209fa1cc485bd70e0d94a5", size = 3376511, upload-time = "2025-10-16T10:34:38.596Z" }, - { url = "https://files.pythonhosted.org/packages/31/96/ba99a003c763998035b0de4c299598125df5fc6c9ccf834f152ddd60e0fb/h5py-3.15.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ab2219dbc6fcdb6932f76b548e2b16f34a1f52b7666e998157a4dfc02e2c4123", size = 2826143, upload-time = "2025-10-16T10:34:41.342Z" }, - { url = "https://files.pythonhosted.org/packages/6a/c2/fc6375d07ea3962df7afad7d863fe4bde18bb88530678c20d4c90c18de1d/h5py-3.15.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8cb02c3a96255149ed3ac811eeea25b655d959c6dd5ce702c9a95ff11859eb5", size = 4908316, upload-time = "2025-10-16T10:34:44.619Z" }, - { url = "https://files.pythonhosted.org/packages/d9/69/4402ea66272dacc10b298cca18ed73e1c0791ff2ae9ed218d3859f9698ac/h5py-3.15.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:121b2b7a4c1915d63737483b7bff14ef253020f617c2fb2811f67a4bed9ac5e8", size = 5103710, upload-time = "2025-10-16T10:34:48.639Z" }, - { url = "https://files.pythonhosted.org/packages/e0/f6/11f1e2432d57d71322c02a97a5567829a75f223a8c821764a0e71a65cde8/h5py-3.15.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59b0d63b318bf3cc06687def2b45afd75926bbc006f7b8cd2b1a231299fc8599", size = 4556042, upload-time = "2025-10-16T10:34:51.841Z" }, - { url = "https://files.pythonhosted.org/packages/18/88/3eda3ef16bfe7a7dbc3d8d6836bbaa7986feb5ff091395e140dc13927bcc/h5py-3.15.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e02fe77a03f652500d8bff288cbf3675f742fc0411f5a628fa37116507dc7cc0", size = 5030639, upload-time = "2025-10-16T10:34:55.257Z" }, - { url = "https://files.pythonhosted.org/packages/e5/ea/fbb258a98863f99befb10ed727152b4ae659f322e1d9c0576f8a62754e81/h5py-3.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:dea78b092fd80a083563ed79a3171258d4a4d307492e7cf8b2313d464c82ba52", size = 2864363, upload-time = "2025-10-16T10:34:58.099Z" }, - { url = "https://files.pythonhosted.org/packages/5d/c9/35021cc9cd2b2915a7da3026e3d77a05bed1144a414ff840953b33937fb9/h5py-3.15.1-cp313-cp313-win_arm64.whl", hash = "sha256:c256254a8a81e2bddc0d376e23e2a6d2dc8a1e8a2261835ed8c1281a0744cd97", size = 2449570, upload-time = "2025-10-16T10:35:00.473Z" }, - { url = "https://files.pythonhosted.org/packages/a0/2c/926eba1514e4d2e47d0e9eb16c784e717d8b066398ccfca9b283917b1bfb/h5py-3.15.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5f4fb0567eb8517c3ecd6b3c02c4f4e9da220c8932604960fd04e24ee1254763", size = 3380368, upload-time = "2025-10-16T10:35:03.117Z" }, - { url = "https://files.pythonhosted.org/packages/65/4b/d715ed454d3baa5f6ae1d30b7eca4c7a1c1084f6a2edead9e801a1541d62/h5py-3.15.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:954e480433e82d3872503104f9b285d369048c3a788b2b1a00e53d1c47c98dd2", size = 2833793, upload-time = "2025-10-16T10:35:05.623Z" }, - { url = "https://files.pythonhosted.org/packages/ef/d4/ef386c28e4579314610a8bffebbee3b69295b0237bc967340b7c653c6c10/h5py-3.15.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd125c131889ebbef0849f4a0e29cf363b48aba42f228d08b4079913b576bb3a", size = 4903199, upload-time = "2025-10-16T10:35:08.972Z" }, - { url = "https://files.pythonhosted.org/packages/33/5d/65c619e195e0b5e54ea5a95c1bb600c8ff8715e0d09676e4cce56d89f492/h5py-3.15.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28a20e1a4082a479b3d7db2169f3a5034af010b90842e75ebbf2e9e49eb4183e", size = 5097224, upload-time = "2025-10-16T10:35:12.808Z" }, - { url = "https://files.pythonhosted.org/packages/30/30/5273218400bf2da01609e1292f562c94b461fcb73c7a9e27fdadd43abc0a/h5py-3.15.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa8df5267f545b4946df8ca0d93d23382191018e4cda2deda4c2cedf9a010e13", size = 4551207, upload-time = "2025-10-16T10:35:16.24Z" }, - { url = "https://files.pythonhosted.org/packages/d3/39/a7ef948ddf4d1c556b0b2b9559534777bccc318543b3f5a1efdf6b556c9c/h5py-3.15.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99d374a21f7321a4c6ab327c4ab23bd925ad69821aeb53a1e75dd809d19f67fa", size = 5025426, upload-time = "2025-10-16T10:35:19.831Z" }, - { url = "https://files.pythonhosted.org/packages/b6/d8/7368679b8df6925b8415f9dcc9ab1dab01ddc384d2b2c24aac9191bd9ceb/h5py-3.15.1-cp314-cp314-win_amd64.whl", hash = "sha256:9c73d1d7cdb97d5b17ae385153472ce118bed607e43be11e9a9deefaa54e0734", size = 2865704, upload-time = "2025-10-16T10:35:22.658Z" }, - { url = "https://files.pythonhosted.org/packages/d3/b7/4a806f85d62c20157e62e58e03b27513dc9c55499768530acc4f4c5ce4be/h5py-3.15.1-cp314-cp314-win_arm64.whl", hash = "sha256:a6d8c5a05a76aca9a494b4c53ce8a9c29023b7f64f625c6ce1841e92a362ccdf", size = 2465544, upload-time = "2025-10-16T10:35:25.695Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/db/33/acd0ce6863b6c0d7735007df01815403f5589a21ff8c2e1ee2587a38f548/h5py-3.16.0.tar.gz", hash = "sha256:a0dbaad796840ccaa67a4c144a0d0c8080073c34c76d5a6941d6818678ef2738", size = 446526, upload-time = "2026-03-06T13:49:08.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/95/a825894f3e45cbac7554c4e97314ce886b233a20033787eda755ca8fecc7/h5py-3.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:719439d14b83f74eeb080e9650a6c7aa6d0d9ea0ca7f804347b05fac6fbf18af", size = 3721663, upload-time = "2026-03-06T13:47:49.599Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3b/38ff88b347c3e346cda1d3fc1b65a7aa75d40632228d8b8a5d7b58508c24/h5py-3.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c3f0a0e136f2e95dd0b67146abb6668af4f1a69c81ef8651a2d316e8e01de447", size = 3087630, upload-time = "2026-03-06T13:47:51.249Z" }, + { url = "https://files.pythonhosted.org/packages/98/a8/2594cef906aee761601eff842c7dc598bea2b394a3e1c00966832b8eeb7c/h5py-3.16.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:a6fbc5367d4046801f9b7db9191b31895f22f1c6df1f9987d667854cac493538", size = 4823472, upload-time = "2026-03-06T13:47:53.085Z" }, + { url = "https://files.pythonhosted.org/packages/52/a0/c1f604538ff6db22a0690be2dc44ab59178e115f63c917794e529356ab23/h5py-3.16.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:fb1720028d99040792bb2fb31facb8da44a6f29df7697e0b84f0d79aff2e9bd3", size = 5027150, upload-time = "2026-03-06T13:47:55.043Z" }, + { url = "https://files.pythonhosted.org/packages/2e/fd/301739083c2fc4fd89950f9bcfce75d6e14b40b0ca3d40e48a8993d1722c/h5py-3.16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:314b6054fe0b1051c2b0cb2df5cbdab15622fb05e80f202e3b6a5eee0d6fe365", size = 4814544, upload-time = "2026-03-06T13:47:56.893Z" }, + { url = "https://files.pythonhosted.org/packages/4c/42/2193ed41ccee78baba8fcc0cff2c925b8b9ee3793305b23e1f22c20bf4c7/h5py-3.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ffbab2fedd6581f6aa31cf1639ca2cb86e02779de525667892ebf4cc9fd26434", size = 5034013, upload-time = "2026-03-06T13:47:59.01Z" }, + { url = "https://files.pythonhosted.org/packages/f7/20/e6c0ff62ca2ad1a396a34f4380bafccaaf8791ff8fccf3d995a1fc12d417/h5py-3.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:17d1f1630f92ad74494a9a7392ab25982ce2b469fc62da6074c0ce48366a2999", size = 3191673, upload-time = "2026-03-06T13:48:00.626Z" }, + { url = "https://files.pythonhosted.org/packages/f2/48/239cbe352ac4f2b8243a8e620fa1a2034635f633731493a7ff1ed71e8658/h5py-3.16.0-cp311-cp311-win_arm64.whl", hash = "sha256:85b9c49dd58dc44cf70af944784e2c2038b6f799665d0dcbbc812a26e0faa859", size = 2673834, upload-time = "2026-03-06T13:48:02.579Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c0/5d4119dba94093bbafede500d3defd2f5eab7897732998c04b54021e530b/h5py-3.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c5313566f4643121a78503a473f0fb1e6dcc541d5115c44f05e037609c565c4d", size = 3685604, upload-time = "2026-03-06T13:48:04.198Z" }, + { url = "https://files.pythonhosted.org/packages/b0/42/c84efcc1d4caebafb1ecd8be4643f39c85c47a80fe254d92b8b43b1eadaf/h5py-3.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:42b012933a83e1a558c673176676a10ce2fd3759976a0fedee1e672d1e04fc9d", size = 3061940, upload-time = "2026-03-06T13:48:05.783Z" }, + { url = "https://files.pythonhosted.org/packages/89/84/06281c82d4d1686fde1ac6b0f307c50918f1c0151062445ab3b6fa5a921d/h5py-3.16.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:ff24039e2573297787c3063df64b60aab0591980ac898329a08b0320e0cf2527", size = 5198852, upload-time = "2026-03-06T13:48:07.482Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e9/1a19e42cd43cc1365e127db6aae85e1c671da1d9a5d746f4d34a50edb577/h5py-3.16.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:dfc21898ff025f1e8e67e194965a95a8d4754f452f83454538f98f8a3fcb207e", size = 5405250, upload-time = "2026-03-06T13:48:09.628Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8e/9790c1655eabeb85b92b1ecab7d7e62a2069e53baefd58c98f0909c7a948/h5py-3.16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:698dd69291272642ffda44a0ecd6cd3bda5faf9621452d255f57ce91487b9794", size = 5190108, upload-time = "2026-03-06T13:48:11.26Z" }, + { url = "https://files.pythonhosted.org/packages/51/d7/ab693274f1bd7e8c5f9fdd6c7003a88d59bedeaf8752716a55f532924fbb/h5py-3.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2b2c02b0a160faed5fb33f1ba8a264a37ee240b22e049ecc827345d0d9043074", size = 5419216, upload-time = "2026-03-06T13:48:13.322Z" }, + { url = "https://files.pythonhosted.org/packages/03/c1/0976b235cf29ead553e22f2fb6385a8252b533715e00d0ae52ed7b900582/h5py-3.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:96b422019a1c8975c2d5dadcf61d4ba6f01c31f92bbde6e4649607885fe502d6", size = 3182868, upload-time = "2026-03-06T13:48:15.759Z" }, + { url = "https://files.pythonhosted.org/packages/14/d9/866b7e570b39070f92d47b0ff1800f0f8239b6f9e45f02363d7112336c1f/h5py-3.16.0-cp312-cp312-win_arm64.whl", hash = "sha256:39c2838fb1e8d97bcf1755e60ad1f3dd76a7b2a475928dc321672752678b96db", size = 2653286, upload-time = "2026-03-06T13:48:17.279Z" }, + { url = "https://files.pythonhosted.org/packages/0f/9e/6142ebfda0cb6e9349c091eae73c2e01a770b7659255248d637bec54a88b/h5py-3.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:370a845f432c2c9619db8eed334d1e610c6015796122b0e57aa46312c22617d9", size = 3671808, upload-time = "2026-03-06T13:48:19.737Z" }, + { url = "https://files.pythonhosted.org/packages/b0/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42108e93326c50c2810025aade9eac9d6827524cdccc7d4b75a546e5ab308edb", size = 3045837, upload-time = "2026-03-06T13:48:21.854Z" }, + { url = "https://files.pythonhosted.org/packages/da/1e/6172269e18cc5a484e2913ced33339aad588e02ba407fafd00d369e22ef3/h5py-3.16.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:099f2525c9dcf28de366970a5fb34879aab20491589fa89ce2863a84218bb524", size = 5193860, upload-time = "2026-03-06T13:48:24.071Z" }, + { url = "https://files.pythonhosted.org/packages/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:9300ad32dea9dfc5171f94d5f6948e159ed93e4701280b0f508773b3f582f402", size = 5400417, upload-time = "2026-03-06T13:48:25.728Z" }, + { url = "https://files.pythonhosted.org/packages/bc/81/5b62d760039eed64348c98129d17061fdfc7839fc9c04eaaad6dee1004e4/h5py-3.16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:171038f23bccddfc23f344cadabdfc9917ff554db6a0d417180d2747fe4c75a7", size = 5185214, upload-time = "2026-03-06T13:48:27.436Z" }, + { url = "https://files.pythonhosted.org/packages/28/c4/532123bcd9080e250696779c927f2cb906c8bf3447df98f5ceb8dcded539/h5py-3.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7e420b539fb6023a259a1b14d4c9f6df8cf50d7268f48e161169987a57b737ff", size = 5414598, upload-time = "2026-03-06T13:48:29.49Z" }, + { url = "https://files.pythonhosted.org/packages/c3/d9/a27997f84341fc0dfcdd1fe4179b6ba6c32a7aa880fdb8c514d4dad6fba3/h5py-3.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:18f2bbcd545e6991412253b98727374c356d67caa920e68dc79eab36bf5fedad", size = 3175509, upload-time = "2026-03-06T13:48:31.131Z" }, + { url = "https://files.pythonhosted.org/packages/a5/23/bb8647521d4fd770c30a76cfc6cb6a2f5495868904054e92f2394c5a78ff/h5py-3.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:656f00e4d903199a1d58df06b711cf3ca632b874b4207b7dbec86185b5c8c7d4", size = 2647362, upload-time = "2026-03-06T13:48:33.411Z" }, + { url = "https://files.pythonhosted.org/packages/48/3c/7fcd9b4c9eed82e91fb15568992561019ae7a829d1f696b2c844355d95dd/h5py-3.16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9c9d307c0ef862d1cd5714f72ecfafe0a5d7529c44845afa8de9f46e5ba8bd65", size = 3678608, upload-time = "2026-03-06T13:48:35.183Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b7/9366ed44ced9b7ef357ab48c94205280276db9d7f064aa3012a97227e966/h5py-3.16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8c1eff849cdd53cbc73c214c30ebdb6f1bb8b64790b4b4fc36acdb5e43570210", size = 3054773, upload-time = "2026-03-06T13:48:37.139Z" }, + { url = "https://files.pythonhosted.org/packages/58/a5/4964bc0e91e86340c2bbda83420225b2f770dcf1eb8a39464871ad769436/h5py-3.16.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:e2c04d129f180019e216ee5f9c40b78a418634091c8782e1f723a6ca3658b965", size = 5198886, upload-time = "2026-03-06T13:48:38.879Z" }, + { url = "https://files.pythonhosted.org/packages/f1/16/d905e7f53e661ce2c24686c38048d8e2b750ffc4350009d41c4e6c6c9826/h5py-3.16.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:e4360f15875a532bc7b98196c7592ed4fc92672a57c0a621355961cafb17a6dd", size = 5404883, upload-time = "2026-03-06T13:48:41.324Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f2/58f34cb74af46d39f4cd18ea20909a8514960c5a3e5b92fd06a28161e0a8/h5py-3.16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3fae9197390c325e62e0a1aa977f2f62d994aa87aab182abbea85479b791197c", size = 5192039, upload-time = "2026-03-06T13:48:43.117Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ca/934a39c24ce2e2db017268c08da0537c20fa0be7e1549be3e977313fc8f5/h5py-3.16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:43259303989ac8adacc9986695b31e35dba6fd1e297ff9c6a04b7da5542139cc", size = 5421526, upload-time = "2026-03-06T13:48:44.838Z" }, + { url = "https://files.pythonhosted.org/packages/3e/14/615a450205e1b56d16c6783f5ccd116cde05550faad70ae077c955654a75/h5py-3.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:fa48993a0b799737ba7fd21e2350fa0a60701e58180fae9f2de834bc39a147ab", size = 3183263, upload-time = "2026-03-06T13:48:47.117Z" }, + { url = "https://files.pythonhosted.org/packages/7b/48/a6faef5ed632cae0c65ac6b214a6614a0b510c3183532c521bdb0055e117/h5py-3.16.0-cp314-cp314-win_arm64.whl", hash = "sha256:1897a771a7f40d05c262fc8f37376ec37873218544b70216872876c627640f63", size = 2663450, upload-time = "2026-03-06T13:48:48.707Z" }, + { url = "https://files.pythonhosted.org/packages/5d/32/0c8bb8aedb62c772cf7c1d427c7d1951477e8c2835f872bc0a13d1f85f86/h5py-3.16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:15922e485844f77c0b9d275396d435db3baa58292a9c2176a386e072e0cf2491", size = 3760693, upload-time = "2026-03-06T13:48:50.453Z" }, + { url = "https://files.pythonhosted.org/packages/1d/1f/fcc5977d32d6387c5c9a694afee716a5e20658ac08b3ff24fdec79fb05f2/h5py-3.16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:df02dd29bd247f98674634dfe41f89fd7c16ba3d7de8695ec958f58404a4e618", size = 3181305, upload-time = "2026-03-06T13:48:52.221Z" }, + { url = "https://files.pythonhosted.org/packages/f5/a1/af87f64b9f986889884243643621ebbd4ac72472ba8ec8cec891ac8e2ca1/h5py-3.16.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:0f456f556e4e2cebeebd9d66adf8dc321770a42593494a0b6f0af54a7567b242", size = 5074061, upload-time = "2026-03-06T13:48:54.089Z" }, + { url = "https://files.pythonhosted.org/packages/cc/d0/146f5eaff3dc246a9c7f6e5e4f42bd45cc613bce16693bcd4d1f7c958bf5/h5py-3.16.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:3e6cb3387c756de6a9492d601553dffea3fe11b5f22b443aac708c69f3f55e16", size = 5279216, upload-time = "2026-03-06T13:48:56.75Z" }, + { url = "https://files.pythonhosted.org/packages/a1/9d/12a13424f1e604fc7df9497b73c0356fb78c2fb206abd7465ce47226e8fd/h5py-3.16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8389e13a1fd745ad2856873e8187fd10268b2d9677877bb667b41aebd771d8b7", size = 5070068, upload-time = "2026-03-06T13:48:59.169Z" }, + { url = "https://files.pythonhosted.org/packages/41/8c/bbe98f813722b4873818a8db3e15aa3e625b59278566905ac439725e8070/h5py-3.16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:346df559a0f7dcb31cf8e44805319e2ab24b8957c45e7708ce503b2ec79ba725", size = 5300253, upload-time = "2026-03-06T13:49:02.033Z" }, + { url = "https://files.pythonhosted.org/packages/32/9e/87e6705b4d6890e7cecdf876e2a7d3e40654a2ae37482d79a6f1b87f7b92/h5py-3.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4c6ab014ab704b4feaa719ae783b86522ed0bf1f82184704ed3c9e4e3228796e", size = 3381671, upload-time = "2026-03-06T13:49:04.351Z" }, + { url = "https://files.pythonhosted.org/packages/96/91/9fad90cfc5f9b2489c7c26ad897157bce82f0e9534a986a221b99760b23b/h5py-3.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:faca8fb4e4319c09d83337adc80b2ca7d5c5a343c2d6f1b6388f32cfecca13c1", size = 2740706, upload-time = "2026-03-06T13:49:06.347Z" }, ] [[package]] @@ -1150,7 +1158,8 @@ dependencies = [ { name = "appnope", marker = "sys_platform == 'darwin'" }, { name = "comm" }, { name = "debugpy" }, - { name = "ipython" }, + { name = "ipython", version = "9.10.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "ipython", version = "9.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "jupyter-client" }, { name = "jupyter-core" }, { name = "matplotlib-inline" }, @@ -1170,17 +1179,20 @@ wheels = [ name = "ipython" version = "9.10.0" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.12'", +] dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "decorator" }, - { name = "ipython-pygments-lexers" }, - { name = "jedi" }, - { name = "matplotlib-inline" }, - { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit" }, - { name = "pygments" }, - { name = "stack-data" }, - { name = "traitlets" }, + { name = "colorama", marker = "python_full_version < '3.12' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version < '3.12'" }, + { name = "ipython-pygments-lexers", marker = "python_full_version < '3.12'" }, + { name = "jedi", marker = "python_full_version < '3.12'" }, + { name = "matplotlib-inline", marker = "python_full_version < '3.12'" }, + { name = "pexpect", marker = "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version < '3.12'" }, + { name = "pygments", marker = "python_full_version < '3.12'" }, + { name = "stack-data", marker = "python_full_version < '3.12'" }, + { name = "traitlets", marker = "python_full_version < '3.12'" }, { name = "typing-extensions", marker = "python_full_version < '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a6/60/2111715ea11f39b1535bed6024b7dec7918b71e5e5d30855a5b503056b50/ipython-9.10.0.tar.gz", hash = "sha256:cd9e656be97618a0676d058134cd44e6dc7012c0e5cb36a9ce96a8c904adaf77", size = 4426526, upload-time = "2026-02-02T10:00:33.594Z" } @@ -1188,6 +1200,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3d/aa/898dec789a05731cd5a9f50605b7b44a72bd198fd0d4528e11fc610177cc/ipython-9.10.0-py3-none-any.whl", hash = "sha256:c6ab68cc23bba8c7e18e9b932797014cc61ea7fd6f19de180ab9ba73e65ee58d", size = 622774, upload-time = "2026-02-02T10:00:31.503Z" }, ] +[[package]] +name = "ipython" +version = "9.11.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version >= '3.12' and python_full_version < '3.14'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version >= '3.12'" }, + { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.12'" }, + { name = "jedi", marker = "python_full_version >= '3.12'" }, + { name = "matplotlib-inline", marker = "python_full_version >= '3.12'" }, + { name = "pexpect", marker = "python_full_version >= '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version >= '3.12'" }, + { name = "pygments", marker = "python_full_version >= '3.12'" }, + { name = "stack-data", marker = "python_full_version >= '3.12'" }, + { name = "traitlets", marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/28/a4698eda5a8928a45d6b693578b135b753e14fa1c2b36ee9441e69a45576/ipython-9.11.0.tar.gz", hash = "sha256:2a94bc4406b22ecc7e4cb95b98450f3ea493a76bec8896cda11b78d7752a6667", size = 4427354, upload-time = "2026-03-05T08:57:30.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl", hash = "sha256:6922d5bcf944c6e525a76a0a304451b60a2b6f875e86656d8bc2dfda5d710e19", size = 624222, upload-time = "2026-03-05T08:57:28.94Z" }, +] + [[package]] name = "ipython-pygments-lexers" version = "1.1.1" @@ -1206,7 +1243,8 @@ version = "8.1.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "comm" }, - { name = "ipython" }, + { name = "ipython", version = "9.10.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "ipython", version = "9.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "jupyterlab-widgets" }, { name = "traitlets" }, { name = "widgetsnbextension" }, @@ -1574,14 +1612,14 @@ wheels = [ [[package]] name = "lazy-loader" -version = "0.4" +version = "0.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6f/6b/c875b30a1ba490860c93da4cabf479e03f584eba06fe5963f6f6644653d8/lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1", size = 15431, upload-time = "2024-04-05T13:03:12.261Z" } +sdist = { url = "https://files.pythonhosted.org/packages/49/ac/21a1f8aa3777f5658576777ea76bfb124b702c520bbe90edf4ae9915eafa/lazy_loader-0.5.tar.gz", hash = "sha256:717f9179a0dbed357012ddad50a5ad3d5e4d9a0b8712680d4e687f5e6e6ed9b3", size = 15294, upload-time = "2026-03-06T15:45:09.054Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc", size = 12097, upload-time = "2024-04-05T13:03:10.514Z" }, + { url = "https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl", hash = "sha256:ab0ea149e9c554d4ffeeb21105ac60bed7f3b4fd69b1d2360a4add51b170b005", size = 8044, upload-time = "2026-03-06T15:45:07.668Z" }, ] [[package]] @@ -1923,81 +1961,81 @@ wheels = [ [[package]] name = "numpy" -version = "2.4.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/44/71852273146957899753e69986246d6a176061ea183407e95418c2aa4d9a/numpy-2.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7e88598032542bd49af7c4747541422884219056c268823ef6e5e89851c8825", size = 16955478, upload-time = "2026-01-31T23:10:25.623Z" }, - { url = "https://files.pythonhosted.org/packages/74/41/5d17d4058bd0cd96bcbd4d9ff0fb2e21f52702aab9a72e4a594efa18692f/numpy-2.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7edc794af8b36ca37ef5fcb5e0d128c7e0595c7b96a2318d1badb6fcd8ee86b1", size = 14965467, upload-time = "2026-01-31T23:10:28.186Z" }, - { url = "https://files.pythonhosted.org/packages/49/48/fb1ce8136c19452ed15f033f8aee91d5defe515094e330ce368a0647846f/numpy-2.4.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6e9f61981ace1360e42737e2bae58b27bf28a1b27e781721047d84bd754d32e7", size = 5475172, upload-time = "2026-01-31T23:10:30.848Z" }, - { url = "https://files.pythonhosted.org/packages/40/a9/3feb49f17bbd1300dd2570432961f5c8a4ffeff1db6f02c7273bd020a4c9/numpy-2.4.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cb7bbb88aa74908950d979eeaa24dbdf1a865e3c7e45ff0121d8f70387b55f73", size = 6805145, upload-time = "2026-01-31T23:10:32.352Z" }, - { url = "https://files.pythonhosted.org/packages/3f/39/fdf35cbd6d6e2fcad42fcf85ac04a85a0d0fbfbf34b30721c98d602fd70a/numpy-2.4.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f069069931240b3fc703f1e23df63443dbd6390614c8c44a87d96cd0ec81eb1", size = 15966084, upload-time = "2026-01-31T23:10:34.502Z" }, - { url = "https://files.pythonhosted.org/packages/1b/46/6fa4ea94f1ddf969b2ee941290cca6f1bfac92b53c76ae5f44afe17ceb69/numpy-2.4.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c02ef4401a506fb60b411467ad501e1429a3487abca4664871d9ae0b46c8ba32", size = 16899477, upload-time = "2026-01-31T23:10:37.075Z" }, - { url = "https://files.pythonhosted.org/packages/09/a1/2a424e162b1a14a5bd860a464ab4e07513916a64ab1683fae262f735ccd2/numpy-2.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2653de5c24910e49c2b106499803124dde62a5a1fe0eedeaecf4309a5f639390", size = 17323429, upload-time = "2026-01-31T23:10:39.704Z" }, - { url = "https://files.pythonhosted.org/packages/ce/a2/73014149ff250628df72c58204822ac01d768697913881aacf839ff78680/numpy-2.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1ae241bbfc6ae276f94a170b14785e561cb5e7f626b6688cf076af4110887413", size = 18635109, upload-time = "2026-01-31T23:10:41.924Z" }, - { url = "https://files.pythonhosted.org/packages/6c/0c/73e8be2f1accd56df74abc1c5e18527822067dced5ec0861b5bb882c2ce0/numpy-2.4.2-cp311-cp311-win32.whl", hash = "sha256:df1b10187212b198dd45fa943d8985a3c8cf854aed4923796e0e019e113a1bda", size = 6237915, upload-time = "2026-01-31T23:10:45.26Z" }, - { url = "https://files.pythonhosted.org/packages/76/ae/e0265e0163cf127c24c3969d29f1c4c64551a1e375d95a13d32eab25d364/numpy-2.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:b9c618d56a29c9cb1c4da979e9899be7578d2e0b3c24d52079c166324c9e8695", size = 12607972, upload-time = "2026-01-31T23:10:47.021Z" }, - { url = "https://files.pythonhosted.org/packages/29/a5/c43029af9b8014d6ea157f192652c50042e8911f4300f8f6ed3336bf437f/numpy-2.4.2-cp311-cp311-win_arm64.whl", hash = "sha256:47c5a6ed21d9452b10227e5e8a0e1c22979811cad7dcc19d8e3e2fb8fa03f1a3", size = 10485763, upload-time = "2026-01-31T23:10:50.087Z" }, - { url = "https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a", size = 16667963, upload-time = "2026-01-31T23:10:52.147Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1", size = 14693571, upload-time = "2026-01-31T23:10:54.789Z" }, - { url = "https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e", size = 5203469, upload-time = "2026-01-31T23:10:57.343Z" }, - { url = "https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27", size = 6550820, upload-time = "2026-01-31T23:10:59.429Z" }, - { url = "https://files.pythonhosted.org/packages/b1/91/b97fdfd12dc75b02c44e26c6638241cc004d4079a0321a69c62f51470c4c/numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548", size = 15663067, upload-time = "2026-01-31T23:11:01.291Z" }, - { url = "https://files.pythonhosted.org/packages/f5/c6/a18e59f3f0b8071cc85cbc8d80cd02d68aa9710170b2553a117203d46936/numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f", size = 16619782, upload-time = "2026-01-31T23:11:03.669Z" }, - { url = "https://files.pythonhosted.org/packages/b7/83/9751502164601a79e18847309f5ceec0b1446d7b6aa12305759b72cf98b2/numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460", size = 17013128, upload-time = "2026-01-31T23:11:05.913Z" }, - { url = "https://files.pythonhosted.org/packages/61/c4/c4066322256ec740acc1c8923a10047818691d2f8aec254798f3dd90f5f2/numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba", size = 18345324, upload-time = "2026-01-31T23:11:08.248Z" }, - { url = "https://files.pythonhosted.org/packages/ab/af/6157aa6da728fa4525a755bfad486ae7e3f76d4c1864138003eb84328497/numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f", size = 5960282, upload-time = "2026-01-31T23:11:10.497Z" }, - { url = "https://files.pythonhosted.org/packages/92/0f/7ceaaeaacb40567071e94dbf2c9480c0ae453d5bb4f52bea3892c39dc83c/numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85", size = 12314210, upload-time = "2026-01-31T23:11:12.176Z" }, - { url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z" }, - { url = "https://files.pythonhosted.org/packages/a1/22/815b9fe25d1d7ae7d492152adbc7226d3eff731dffc38fe970589fcaaa38/numpy-2.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c", size = 16663696, upload-time = "2026-01-31T23:11:17.516Z" }, - { url = "https://files.pythonhosted.org/packages/09/f0/817d03a03f93ba9c6c8993de509277d84e69f9453601915e4a69554102a1/numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979", size = 14688322, upload-time = "2026-01-31T23:11:19.883Z" }, - { url = "https://files.pythonhosted.org/packages/da/b4/f805ab79293c728b9a99438775ce51885fd4f31b76178767cfc718701a39/numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98", size = 5198157, upload-time = "2026-01-31T23:11:22.375Z" }, - { url = "https://files.pythonhosted.org/packages/74/09/826e4289844eccdcd64aac27d13b0fd3f32039915dd5b9ba01baae1f436c/numpy-2.4.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef", size = 6546330, upload-time = "2026-01-31T23:11:23.958Z" }, - { url = "https://files.pythonhosted.org/packages/19/fb/cbfdbfa3057a10aea5422c558ac57538e6acc87ec1669e666d32ac198da7/numpy-2.4.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7", size = 15660968, upload-time = "2026-01-31T23:11:25.713Z" }, - { url = "https://files.pythonhosted.org/packages/04/dc/46066ce18d01645541f0186877377b9371b8fa8017fa8262002b4ef22612/numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499", size = 16607311, upload-time = "2026-01-31T23:11:28.117Z" }, - { url = "https://files.pythonhosted.org/packages/14/d9/4b5adfc39a43fa6bf918c6d544bc60c05236cc2f6339847fc5b35e6cb5b0/numpy-2.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb", size = 17012850, upload-time = "2026-01-31T23:11:30.888Z" }, - { url = "https://files.pythonhosted.org/packages/b7/20/adb6e6adde6d0130046e6fdfb7675cc62bc2f6b7b02239a09eb58435753d/numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7", size = 18334210, upload-time = "2026-01-31T23:11:33.214Z" }, - { url = "https://files.pythonhosted.org/packages/78/0e/0a73b3dff26803a8c02baa76398015ea2a5434d9b8265a7898a6028c1591/numpy-2.4.2-cp313-cp313-win32.whl", hash = "sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110", size = 5958199, upload-time = "2026-01-31T23:11:35.385Z" }, - { url = "https://files.pythonhosted.org/packages/43/bc/6352f343522fcb2c04dbaf94cb30cca6fd32c1a750c06ad6231b4293708c/numpy-2.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622", size = 12310848, upload-time = "2026-01-31T23:11:38.001Z" }, - { url = "https://files.pythonhosted.org/packages/6e/8d/6da186483e308da5da1cc6918ce913dcfe14ffde98e710bfeff2a6158d4e/numpy-2.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71", size = 10221082, upload-time = "2026-01-31T23:11:40.392Z" }, - { url = "https://files.pythonhosted.org/packages/25/a1/9510aa43555b44781968935c7548a8926274f815de42ad3997e9e83680dd/numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262", size = 14815866, upload-time = "2026-01-31T23:11:42.495Z" }, - { url = "https://files.pythonhosted.org/packages/36/30/6bbb5e76631a5ae46e7923dd16ca9d3f1c93cfa8d4ed79a129814a9d8db3/numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913", size = 5325631, upload-time = "2026-01-31T23:11:44.7Z" }, - { url = "https://files.pythonhosted.org/packages/46/00/3a490938800c1923b567b3a15cd17896e68052e2145d8662aaf3e1ffc58f/numpy-2.4.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab", size = 6646254, upload-time = "2026-01-31T23:11:46.341Z" }, - { url = "https://files.pythonhosted.org/packages/d3/e9/fac0890149898a9b609caa5af7455a948b544746e4b8fe7c212c8edd71f8/numpy-2.4.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82", size = 15720138, upload-time = "2026-01-31T23:11:48.082Z" }, - { url = "https://files.pythonhosted.org/packages/ea/5c/08887c54e68e1e28df53709f1893ce92932cc6f01f7c3d4dc952f61ffd4e/numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f", size = 16655398, upload-time = "2026-01-31T23:11:50.293Z" }, - { url = "https://files.pythonhosted.org/packages/4d/89/253db0fa0e66e9129c745e4ef25631dc37d5f1314dad2b53e907b8538e6d/numpy-2.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554", size = 17079064, upload-time = "2026-01-31T23:11:52.927Z" }, - { url = "https://files.pythonhosted.org/packages/2a/d5/cbade46ce97c59c6c3da525e8d95b7abe8a42974a1dc5c1d489c10433e88/numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257", size = 18379680, upload-time = "2026-01-31T23:11:55.22Z" }, - { url = "https://files.pythonhosted.org/packages/40/62/48f99ae172a4b63d981babe683685030e8a3df4f246c893ea5c6ef99f018/numpy-2.4.2-cp313-cp313t-win32.whl", hash = "sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657", size = 6082433, upload-time = "2026-01-31T23:11:58.096Z" }, - { url = "https://files.pythonhosted.org/packages/07/38/e054a61cfe48ad9f1ed0d188e78b7e26859d0b60ef21cd9de4897cdb5326/numpy-2.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b", size = 12451181, upload-time = "2026-01-31T23:11:59.782Z" }, - { url = "https://files.pythonhosted.org/packages/6e/a4/a05c3a6418575e185dd84d0b9680b6bb2e2dc3e4202f036b7b4e22d6e9dc/numpy-2.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1", size = 10290756, upload-time = "2026-01-31T23:12:02.438Z" }, - { url = "https://files.pythonhosted.org/packages/18/88/b7df6050bf18fdcfb7046286c6535cabbdd2064a3440fca3f069d319c16e/numpy-2.4.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:444be170853f1f9d528428eceb55f12918e4fda5d8805480f36a002f1415e09b", size = 16663092, upload-time = "2026-01-31T23:12:04.521Z" }, - { url = "https://files.pythonhosted.org/packages/25/7a/1fee4329abc705a469a4afe6e69b1ef7e915117747886327104a8493a955/numpy-2.4.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d1240d50adff70c2a88217698ca844723068533f3f5c5fa6ee2e3220e3bdb000", size = 14698770, upload-time = "2026-01-31T23:12:06.96Z" }, - { url = "https://files.pythonhosted.org/packages/fb/0b/f9e49ba6c923678ad5bc38181c08ac5e53b7a5754dbca8e581aa1a56b1ff/numpy-2.4.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:7cdde6de52fb6664b00b056341265441192d1291c130e99183ec0d4b110ff8b1", size = 5208562, upload-time = "2026-01-31T23:12:09.632Z" }, - { url = "https://files.pythonhosted.org/packages/7d/12/d7de8f6f53f9bb76997e5e4c069eda2051e3fe134e9181671c4391677bb2/numpy-2.4.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:cda077c2e5b780200b6b3e09d0b42205a3d1c68f30c6dceb90401c13bff8fe74", size = 6543710, upload-time = "2026-01-31T23:12:11.969Z" }, - { url = "https://files.pythonhosted.org/packages/09/63/c66418c2e0268a31a4cf8a8b512685748200f8e8e8ec6c507ce14e773529/numpy-2.4.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d30291931c915b2ab5717c2974bb95ee891a1cf22ebc16a8006bd59cd210d40a", size = 15677205, upload-time = "2026-01-31T23:12:14.33Z" }, - { url = "https://files.pythonhosted.org/packages/5d/6c/7f237821c9642fb2a04d2f1e88b4295677144ca93285fd76eff3bcba858d/numpy-2.4.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bba37bc29d4d85761deed3954a1bc62be7cf462b9510b51d367b769a8c8df325", size = 16611738, upload-time = "2026-01-31T23:12:16.525Z" }, - { url = "https://files.pythonhosted.org/packages/c2/a7/39c4cdda9f019b609b5c473899d87abff092fc908cfe4d1ecb2fcff453b0/numpy-2.4.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b2f0073ed0868db1dcd86e052d37279eef185b9c8db5bf61f30f46adac63c909", size = 17028888, upload-time = "2026-01-31T23:12:19.306Z" }, - { url = "https://files.pythonhosted.org/packages/da/b3/e84bb64bdfea967cc10950d71090ec2d84b49bc691df0025dddb7c26e8e3/numpy-2.4.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f54844851cdb630ceb623dcec4db3240d1ac13d4990532446761baede94996a", size = 18339556, upload-time = "2026-01-31T23:12:21.816Z" }, - { url = "https://files.pythonhosted.org/packages/88/f5/954a291bc1192a27081706862ac62bb5920fbecfbaa302f64682aa90beed/numpy-2.4.2-cp314-cp314-win32.whl", hash = "sha256:12e26134a0331d8dbd9351620f037ec470b7c75929cb8a1537f6bfe411152a1a", size = 6006899, upload-time = "2026-01-31T23:12:24.14Z" }, - { url = "https://files.pythonhosted.org/packages/05/cb/eff72a91b2efdd1bc98b3b8759f6a1654aa87612fc86e3d87d6fe4f948c4/numpy-2.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:068cdb2d0d644cdb45670810894f6a0600797a69c05f1ac478e8d31670b8ee75", size = 12443072, upload-time = "2026-01-31T23:12:26.33Z" }, - { url = "https://files.pythonhosted.org/packages/37/75/62726948db36a56428fce4ba80a115716dc4fad6a3a4352487f8bb950966/numpy-2.4.2-cp314-cp314-win_arm64.whl", hash = "sha256:6ed0be1ee58eef41231a5c943d7d1375f093142702d5723ca2eb07db9b934b05", size = 10494886, upload-time = "2026-01-31T23:12:28.488Z" }, - { url = "https://files.pythonhosted.org/packages/36/2f/ee93744f1e0661dc267e4b21940870cabfae187c092e1433b77b09b50ac4/numpy-2.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:98f16a80e917003a12c0580f97b5f875853ebc33e2eaa4bccfc8201ac6869308", size = 14818567, upload-time = "2026-01-31T23:12:30.709Z" }, - { url = "https://files.pythonhosted.org/packages/a7/24/6535212add7d76ff938d8bdc654f53f88d35cddedf807a599e180dcb8e66/numpy-2.4.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:20abd069b9cda45874498b245c8015b18ace6de8546bf50dfa8cea1696ed06ef", size = 5328372, upload-time = "2026-01-31T23:12:32.962Z" }, - { url = "https://files.pythonhosted.org/packages/5e/9d/c48f0a035725f925634bf6b8994253b43f2047f6778a54147d7e213bc5a7/numpy-2.4.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e98c97502435b53741540a5717a6749ac2ada901056c7db951d33e11c885cc7d", size = 6649306, upload-time = "2026-01-31T23:12:34.797Z" }, - { url = "https://files.pythonhosted.org/packages/81/05/7c73a9574cd4a53a25907bad38b59ac83919c0ddc8234ec157f344d57d9a/numpy-2.4.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da6cad4e82cb893db4b69105c604d805e0c3ce11501a55b5e9f9083b47d2ffe8", size = 15722394, upload-time = "2026-01-31T23:12:36.565Z" }, - { url = "https://files.pythonhosted.org/packages/35/fa/4de10089f21fc7d18442c4a767ab156b25c2a6eaf187c0db6d9ecdaeb43f/numpy-2.4.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4424677ce4b47fe73c8b5556d876571f7c6945d264201180db2dc34f676ab5", size = 16653343, upload-time = "2026-01-31T23:12:39.188Z" }, - { url = "https://files.pythonhosted.org/packages/b8/f9/d33e4ffc857f3763a57aa85650f2e82486832d7492280ac21ba9efda80da/numpy-2.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2b8f157c8a6f20eb657e240f8985cc135598b2b46985c5bccbde7616dc9c6b1e", size = 17078045, upload-time = "2026-01-31T23:12:42.041Z" }, - { url = "https://files.pythonhosted.org/packages/c8/b8/54bdb43b6225badbea6389fa038c4ef868c44f5890f95dd530a218706da3/numpy-2.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5daf6f3914a733336dab21a05cdec343144600e964d2fcdabaac0c0269874b2a", size = 18380024, upload-time = "2026-01-31T23:12:44.331Z" }, - { url = "https://files.pythonhosted.org/packages/a5/55/6e1a61ded7af8df04016d81b5b02daa59f2ea9252ee0397cb9f631efe9e5/numpy-2.4.2-cp314-cp314t-win32.whl", hash = "sha256:8c50dd1fc8826f5b26a5ee4d77ca55d88a895f4e4819c7ecc2a9f5905047a443", size = 6153937, upload-time = "2026-01-31T23:12:47.229Z" }, - { url = "https://files.pythonhosted.org/packages/45/aa/fa6118d1ed6d776b0983f3ceac9b1a5558e80df9365b1c3aa6d42bf9eee4/numpy-2.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fcf92bee92742edd401ba41135185866f7026c502617f422eb432cfeca4fe236", size = 12631844, upload-time = "2026-01-31T23:12:48.997Z" }, - { url = "https://files.pythonhosted.org/packages/32/0a/2ec5deea6dcd158f254a7b372fb09cfba5719419c8d66343bab35237b3fb/numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181", size = 10565379, upload-time = "2026-01-31T23:12:51.345Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f8/50e14d36d915ef64d8f8bc4a087fc8264d82c785eda6711f80ab7e620335/numpy-2.4.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:89f7268c009bc492f506abd6f5265defa7cb3f7487dc21d357c3d290add45082", size = 16833179, upload-time = "2026-01-31T23:12:53.5Z" }, - { url = "https://files.pythonhosted.org/packages/17/17/809b5cad63812058a8189e91a1e2d55a5a18fd04611dbad244e8aeae465c/numpy-2.4.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6dee3bb76aa4009d5a912180bf5b2de012532998d094acee25d9cb8dee3e44a", size = 14889755, upload-time = "2026-01-31T23:12:55.933Z" }, - { url = "https://files.pythonhosted.org/packages/3e/ea/181b9bcf7627fc8371720316c24db888dcb9829b1c0270abf3d288b2e29b/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:cd2bd2bbed13e213d6b55dc1d035a4f91748a7d3edc9480c13898b0353708920", size = 5399500, upload-time = "2026-01-31T23:12:58.671Z" }, - { url = "https://files.pythonhosted.org/packages/33/9f/413adf3fc955541ff5536b78fcf0754680b3c6d95103230252a2c9408d23/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:cf28c0c1d4c4bf00f509fa7eb02c58d7caf221b50b467bcb0d9bbf1584d5c821", size = 6714252, upload-time = "2026-01-31T23:13:00.518Z" }, - { url = "https://files.pythonhosted.org/packages/91/da/643aad274e29ccbdf42ecd94dafe524b81c87bcb56b83872d54827f10543/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e04ae107ac591763a47398bb45b568fc38f02dbc4aa44c063f67a131f99346cb", size = 15797142, upload-time = "2026-01-31T23:13:02.219Z" }, - { url = "https://files.pythonhosted.org/packages/66/27/965b8525e9cb5dc16481b30a1b3c21e50c7ebf6e9dbd48d0c4d0d5089c7e/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:602f65afdef699cda27ec0b9224ae5dc43e328f4c24c689deaf77133dbee74d0", size = 16727979, upload-time = "2026-01-31T23:13:04.62Z" }, - { url = "https://files.pythonhosted.org/packages/de/e5/b7d20451657664b07986c2f6e3be564433f5dcaf3482d68eaecd79afaf03/numpy-2.4.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be71bf1edb48ebbbf7f6337b5bfd2f895d1902f6335a5830b20141fc126ffba0", size = 12502577, upload-time = "2026-01-31T23:13:07.08Z" }, +version = "2.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/10/8b/c265f4823726ab832de836cdd184d0986dcf94480f81e8739692a7ac7af2/numpy-2.4.3.tar.gz", hash = "sha256:483a201202b73495f00dbc83796c6ae63137a9bdade074f7648b3e32613412dd", size = 20727743, upload-time = "2026-03-09T07:58:53.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/51/5093a2df15c4dc19da3f79d1021e891f5dcf1d9d1db6ba38891d5590f3fe/numpy-2.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:33b3bf58ee84b172c067f56aeadc7ee9ab6de69c5e800ab5b10295d54c581adb", size = 16957183, upload-time = "2026-03-09T07:55:57.774Z" }, + { url = "https://files.pythonhosted.org/packages/b5/7c/c061f3de0630941073d2598dc271ac2f6cbcf5c83c74a5870fea07488333/numpy-2.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8ba7b51e71c05aa1f9bc3641463cd82308eab40ce0d5c7e1fd4038cbf9938147", size = 14968734, upload-time = "2026-03-09T07:56:00.494Z" }, + { url = "https://files.pythonhosted.org/packages/ef/27/d26c85cbcd86b26e4f125b0668e7a7c0542d19dd7d23ee12e87b550e95b5/numpy-2.4.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a1988292870c7cb9d0ebb4cc96b4d447513a9644801de54606dc7aabf2b7d920", size = 5475288, upload-time = "2026-03-09T07:56:02.857Z" }, + { url = "https://files.pythonhosted.org/packages/2b/09/3c4abbc1dcd8010bf1a611d174c7aa689fc505585ec806111b4406f6f1b1/numpy-2.4.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:23b46bb6d8ecb68b58c09944483c135ae5f0e9b8d8858ece5e4ead783771d2a9", size = 6805253, upload-time = "2026-03-09T07:56:04.53Z" }, + { url = "https://files.pythonhosted.org/packages/21/bc/e7aa3f6817e40c3f517d407742337cbb8e6fc4b83ce0b55ab780c829243b/numpy-2.4.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a016db5c5dba78fa8fe9f5d80d6708f9c42ab087a739803c0ac83a43d686a470", size = 15969479, upload-time = "2026-03-09T07:56:06.638Z" }, + { url = "https://files.pythonhosted.org/packages/78/51/9f5d7a41f0b51649ddf2f2320595e15e122a40610b233d51928dd6c92353/numpy-2.4.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:715de7f82e192e8cae5a507a347d97ad17598f8e026152ca97233e3666daaa71", size = 16901035, upload-time = "2026-03-09T07:56:09.405Z" }, + { url = "https://files.pythonhosted.org/packages/64/6e/b221dd847d7181bc5ee4857bfb026182ef69499f9305eb1371cbb1aea626/numpy-2.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2ddb7919366ee468342b91dea2352824c25b55814a987847b6c52003a7c97f15", size = 17325657, upload-time = "2026-03-09T07:56:12.067Z" }, + { url = "https://files.pythonhosted.org/packages/eb/b8/8f3fd2da596e1063964b758b5e3c970aed1949a05200d7e3d46a9d46d643/numpy-2.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a315e5234d88067f2d97e1f2ef670a7569df445d55400f1e33d117418d008d52", size = 18635512, upload-time = "2026-03-09T07:56:14.629Z" }, + { url = "https://files.pythonhosted.org/packages/5c/24/2993b775c37e39d2f8ab4125b44337ab0b2ba106c100980b7c274a22bee7/numpy-2.4.3-cp311-cp311-win32.whl", hash = "sha256:2b3f8d2c4589b1a2028d2a770b0fc4d1f332fb5e01521f4de3199a896d158ddd", size = 6238100, upload-time = "2026-03-09T07:56:17.243Z" }, + { url = "https://files.pythonhosted.org/packages/76/1d/edccf27adedb754db7c4511d5eac8b83f004ae948fe2d3509e8b78097d4c/numpy-2.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:77e76d932c49a75617c6d13464e41203cd410956614d0a0e999b25e9e8d27eec", size = 12609816, upload-time = "2026-03-09T07:56:19.089Z" }, + { url = "https://files.pythonhosted.org/packages/92/82/190b99153480076c8dce85f4cfe7d53ea84444145ffa54cb58dcd460d66b/numpy-2.4.3-cp311-cp311-win_arm64.whl", hash = "sha256:eb610595dd91560905c132c709412b512135a60f1851ccbd2c959e136431ff67", size = 10485757, upload-time = "2026-03-09T07:56:21.753Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ed/6388632536f9788cea23a3a1b629f25b43eaacd7d7377e5d6bc7b9deb69b/numpy-2.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:61b0cbabbb6126c8df63b9a3a0c4b1f44ebca5e12ff6997b80fcf267fb3150ef", size = 16669628, upload-time = "2026-03-09T07:56:24.252Z" }, + { url = "https://files.pythonhosted.org/packages/74/1b/ee2abfc68e1ce728b2958b6ba831d65c62e1b13ce3017c13943f8f9b5b2e/numpy-2.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7395e69ff32526710748f92cd8c9849b361830968ea3e24a676f272653e8983e", size = 14696872, upload-time = "2026-03-09T07:56:26.991Z" }, + { url = "https://files.pythonhosted.org/packages/ba/d1/780400e915ff5638166f11ca9dc2c5815189f3d7cf6f8759a1685e586413/numpy-2.4.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:abdce0f71dcb4a00e4e77f3faf05e4616ceccfe72ccaa07f47ee79cda3b7b0f4", size = 5203489, upload-time = "2026-03-09T07:56:29.414Z" }, + { url = "https://files.pythonhosted.org/packages/0b/bb/baffa907e9da4cc34a6e556d6d90e032f6d7a75ea47968ea92b4858826c4/numpy-2.4.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:48da3a4ee1336454b07497ff7ec83903efa5505792c4e6d9bf83d99dc07a1e18", size = 6550814, upload-time = "2026-03-09T07:56:32.225Z" }, + { url = "https://files.pythonhosted.org/packages/7b/12/8c9f0c6c95f76aeb20fc4a699c33e9f827fa0d0f857747c73bb7b17af945/numpy-2.4.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32e3bef222ad6b052280311d1d60db8e259e4947052c3ae7dd6817451fc8a4c5", size = 15666601, upload-time = "2026-03-09T07:56:34.461Z" }, + { url = "https://files.pythonhosted.org/packages/bd/79/cc665495e4d57d0aa6fbcc0aa57aa82671dfc78fbf95fe733ed86d98f52a/numpy-2.4.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7dd01a46700b1967487141a66ac1a3cf0dd8ebf1f08db37d46389401512ca97", size = 16621358, upload-time = "2026-03-09T07:56:36.852Z" }, + { url = "https://files.pythonhosted.org/packages/a8/40/b4ecb7224af1065c3539f5ecfff879d090de09608ad1008f02c05c770cb3/numpy-2.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:76f0f283506c28b12bba319c0fab98217e9f9b54e6160e9c79e9f7348ba32e9c", size = 17016135, upload-time = "2026-03-09T07:56:39.337Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b1/6a88e888052eed951afed7a142dcdf3b149a030ca59b4c71eef085858e43/numpy-2.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:737f630a337364665aba3b5a77e56a68cc42d350edd010c345d65a3efa3addcc", size = 18345816, upload-time = "2026-03-09T07:56:42.31Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8f/103a60c5f8c3d7fc678c19cd7b2476110da689ccb80bc18050efbaeae183/numpy-2.4.3-cp312-cp312-win32.whl", hash = "sha256:26952e18d82a1dbbc2f008d402021baa8d6fc8e84347a2072a25e08b46d698b9", size = 5960132, upload-time = "2026-03-09T07:56:44.851Z" }, + { url = "https://files.pythonhosted.org/packages/d7/7c/f5ee1bf6ed888494978046a809df2882aad35d414b622893322df7286879/numpy-2.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:65f3c2455188f09678355f5cae1f959a06b778bc66d535da07bf2ef20cd319d5", size = 12316144, upload-time = "2026-03-09T07:56:47.057Z" }, + { url = "https://files.pythonhosted.org/packages/71/46/8d1cb3f7a00f2fb6394140e7e6623696e54c6318a9d9691bb4904672cf42/numpy-2.4.3-cp312-cp312-win_arm64.whl", hash = "sha256:2abad5c7fef172b3377502bde47892439bae394a71bc329f31df0fd829b41a9e", size = 10220364, upload-time = "2026-03-09T07:56:49.849Z" }, + { url = "https://files.pythonhosted.org/packages/b6/d0/1fe47a98ce0df229238b77611340aff92d52691bcbc10583303181abf7fc/numpy-2.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b346845443716c8e542d54112966383b448f4a3ba5c66409771b8c0889485dd3", size = 16665297, upload-time = "2026-03-09T07:56:52.296Z" }, + { url = "https://files.pythonhosted.org/packages/27/d9/4e7c3f0e68dfa91f21c6fb6cf839bc829ec920688b1ce7ec722b1a6202fb/numpy-2.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2629289168f4897a3c4e23dc98d6f1731f0fc0fe52fb9db19f974041e4cc12b9", size = 14691853, upload-time = "2026-03-09T07:56:54.992Z" }, + { url = "https://files.pythonhosted.org/packages/3a/66/bd096b13a87549683812b53ab211e6d413497f84e794fb3c39191948da97/numpy-2.4.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:bb2e3cf95854233799013779216c57e153c1ee67a0bf92138acca0e429aefaee", size = 5198435, upload-time = "2026-03-09T07:56:57.184Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2f/687722910b5a5601de2135c891108f51dfc873d8e43c8ed9f4ebb440b4a2/numpy-2.4.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:7f3408ff897f8ab07a07fbe2823d7aee6ff644c097cc1f90382511fe982f647f", size = 6546347, upload-time = "2026-03-09T07:56:59.531Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ec/7971c4e98d86c564750393fab8d7d83d0a9432a9d78bb8a163a6dc59967a/numpy-2.4.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:decb0eb8a53c3b009b0962378065589685d66b23467ef5dac16cbe818afde27f", size = 15664626, upload-time = "2026-03-09T07:57:01.385Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/7daecbea84ec935b7fc732e18f532073064a3816f0932a40a17f3349185f/numpy-2.4.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5f51900414fc9204a0e0da158ba2ac52b75656e7dce7e77fb9f84bfa343b4cc", size = 16608916, upload-time = "2026-03-09T07:57:04.008Z" }, + { url = "https://files.pythonhosted.org/packages/df/58/2a2b4a817ffd7472dca4421d9f0776898b364154e30c95f42195041dc03b/numpy-2.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6bd06731541f89cdc01b261ba2c9e037f1543df7472517836b78dfb15bd6e476", size = 17015824, upload-time = "2026-03-09T07:57:06.347Z" }, + { url = "https://files.pythonhosted.org/packages/4a/ca/627a828d44e78a418c55f82dd4caea8ea4a8ef24e5144d9e71016e52fb40/numpy-2.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22654fe6be0e5206f553a9250762c653d3698e46686eee53b399ab90da59bd92", size = 18334581, upload-time = "2026-03-09T07:57:09.114Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c0/76f93962fc79955fcba30a429b62304332345f22d4daec1cb33653425643/numpy-2.4.3-cp313-cp313-win32.whl", hash = "sha256:d71e379452a2f670ccb689ec801b1218cd3983e253105d6e83780967e899d687", size = 5958618, upload-time = "2026-03-09T07:57:11.432Z" }, + { url = "https://files.pythonhosted.org/packages/b1/3c/88af0040119209b9b5cb59485fa48b76f372c73068dbf9254784b975ac53/numpy-2.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:0a60e17a14d640f49146cb38e3f105f571318db7826d9b6fef7e4dce758faecd", size = 12312824, upload-time = "2026-03-09T07:57:13.586Z" }, + { url = "https://files.pythonhosted.org/packages/58/ce/3d07743aced3d173f877c3ef6a454c2174ba42b584ab0b7e6d99374f51ed/numpy-2.4.3-cp313-cp313-win_arm64.whl", hash = "sha256:c9619741e9da2059cd9c3f206110b97583c7152c1dc9f8aafd4beb450ac1c89d", size = 10221218, upload-time = "2026-03-09T07:57:16.183Z" }, + { url = "https://files.pythonhosted.org/packages/62/09/d96b02a91d09e9d97862f4fc8bfebf5400f567d8eb1fe4b0cc4795679c15/numpy-2.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7aa4e54f6469300ebca1d9eb80acd5253cdfa36f2c03d79a35883687da430875", size = 14819570, upload-time = "2026-03-09T07:57:18.564Z" }, + { url = "https://files.pythonhosted.org/packages/b5/ca/0b1aba3905fdfa3373d523b2b15b19029f4f3031c87f4066bd9d20ef6c6b/numpy-2.4.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d1b90d840b25874cf5cd20c219af10bac3667db3876d9a495609273ebe679070", size = 5326113, upload-time = "2026-03-09T07:57:21.052Z" }, + { url = "https://files.pythonhosted.org/packages/c0/63/406e0fd32fcaeb94180fd6a4c41e55736d676c54346b7efbce548b94a914/numpy-2.4.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a749547700de0a20a6718293396ec237bb38218049cfce788e08fcb716e8cf73", size = 6646370, upload-time = "2026-03-09T07:57:22.804Z" }, + { url = "https://files.pythonhosted.org/packages/b6/d0/10f7dc157d4b37af92720a196be6f54f889e90dcd30dce9dc657ed92c257/numpy-2.4.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f3c4a151a2e529adf49c1d54f0f57ff8f9b233ee4d44af623a81553ab86368", size = 15723499, upload-time = "2026-03-09T07:57:24.693Z" }, + { url = "https://files.pythonhosted.org/packages/66/f1/d1c2bf1161396629701bc284d958dc1efa3a5a542aab83cf11ee6eb4cba5/numpy-2.4.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22c31dc07025123aedf7f2db9e91783df13f1776dc52c6b22c620870dc0fab22", size = 16657164, upload-time = "2026-03-09T07:57:27.676Z" }, + { url = "https://files.pythonhosted.org/packages/1a/be/cca19230b740af199ac47331a21c71e7a3d0ba59661350483c1600d28c37/numpy-2.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:148d59127ac95979d6f07e4d460f934ebdd6eed641db9c0db6c73026f2b2101a", size = 17081544, upload-time = "2026-03-09T07:57:30.664Z" }, + { url = "https://files.pythonhosted.org/packages/b9/c5/9602b0cbb703a0936fb40f8a95407e8171935b15846de2f0776e08af04c7/numpy-2.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a97cbf7e905c435865c2d939af3d93f99d18eaaa3cabe4256f4304fb51604349", size = 18380290, upload-time = "2026-03-09T07:57:33.763Z" }, + { url = "https://files.pythonhosted.org/packages/ed/81/9f24708953cd30be9ee36ec4778f4b112b45165812f2ada4cc5ea1c1f254/numpy-2.4.3-cp313-cp313t-win32.whl", hash = "sha256:be3b8487d725a77acccc9924f65fd8bce9af7fac8c9820df1049424a2115af6c", size = 6082814, upload-time = "2026-03-09T07:57:36.491Z" }, + { url = "https://files.pythonhosted.org/packages/e2/9e/52f6eaa13e1a799f0ab79066c17f7016a4a8ae0c1aefa58c82b4dab690b4/numpy-2.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1ec84fd7c8e652b0f4aaaf2e6e9cc8eaa9b1b80a537e06b2e3a2fb176eedcb26", size = 12452673, upload-time = "2026-03-09T07:57:38.281Z" }, + { url = "https://files.pythonhosted.org/packages/c4/04/b8cece6ead0b30c9fbd99bb835ad7ea0112ac5f39f069788c5558e3b1ab2/numpy-2.4.3-cp313-cp313t-win_arm64.whl", hash = "sha256:120df8c0a81ebbf5b9020c91439fccd85f5e018a927a39f624845be194a2be02", size = 10290907, upload-time = "2026-03-09T07:57:40.747Z" }, + { url = "https://files.pythonhosted.org/packages/70/ae/3936f79adebf8caf81bd7a599b90a561334a658be4dcc7b6329ebf4ee8de/numpy-2.4.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5884ce5c7acfae1e4e1b6fde43797d10aa506074d25b531b4f54bde33c0c31d4", size = 16664563, upload-time = "2026-03-09T07:57:43.817Z" }, + { url = "https://files.pythonhosted.org/packages/9b/62/760f2b55866b496bb1fa7da2a6db076bef908110e568b02fcfc1422e2a3a/numpy-2.4.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:297837823f5bc572c5f9379b0c9f3a3365f08492cbdc33bcc3af174372ebb168", size = 14702161, upload-time = "2026-03-09T07:57:46.169Z" }, + { url = "https://files.pythonhosted.org/packages/32/af/a7a39464e2c0a21526fb4fb76e346fb172ebc92f6d1c7a07c2c139cc17b1/numpy-2.4.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:a111698b4a3f8dcbe54c64a7708f049355abd603e619013c346553c1fd4ca90b", size = 5208738, upload-time = "2026-03-09T07:57:48.506Z" }, + { url = "https://files.pythonhosted.org/packages/29/8c/2a0cf86a59558fa078d83805589c2de490f29ed4fb336c14313a161d358a/numpy-2.4.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:4bd4741a6a676770e0e97fe9ab2e51de01183df3dcbcec591d26d331a40de950", size = 6543618, upload-time = "2026-03-09T07:57:50.591Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b8/612ce010c0728b1c363fa4ea3aa4c22fe1c5da1de008486f8c2f5cb92fae/numpy-2.4.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:54f29b877279d51e210e0c80709ee14ccbbad647810e8f3d375561c45ef613dd", size = 15680676, upload-time = "2026-03-09T07:57:52.34Z" }, + { url = "https://files.pythonhosted.org/packages/a9/7e/4f120ecc54ba26ddf3dc348eeb9eb063f421de65c05fc961941798feea18/numpy-2.4.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:679f2a834bae9020f81534671c56fd0cc76dd7e5182f57131478e23d0dc59e24", size = 16613492, upload-time = "2026-03-09T07:57:54.91Z" }, + { url = "https://files.pythonhosted.org/packages/2c/86/1b6020db73be330c4b45d5c6ee4295d59cfeef0e3ea323959d053e5a6909/numpy-2.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d84f0f881cb2225c2dfd7f78a10a5645d487a496c6668d6cc39f0f114164f3d0", size = 17031789, upload-time = "2026-03-09T07:57:57.641Z" }, + { url = "https://files.pythonhosted.org/packages/07/3a/3b90463bf41ebc21d1b7e06079f03070334374208c0f9a1f05e4ae8455e7/numpy-2.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d213c7e6e8d211888cc359bab7199670a00f5b82c0978b9d1c75baf1eddbeac0", size = 18339941, upload-time = "2026-03-09T07:58:00.577Z" }, + { url = "https://files.pythonhosted.org/packages/a8/74/6d736c4cd962259fd8bae9be27363eb4883a2f9069763747347544c2a487/numpy-2.4.3-cp314-cp314-win32.whl", hash = "sha256:52077feedeff7c76ed7c9f1a0428558e50825347b7545bbb8523da2cd55c547a", size = 6007503, upload-time = "2026-03-09T07:58:03.331Z" }, + { url = "https://files.pythonhosted.org/packages/48/39/c56ef87af669364356bb011922ef0734fc49dad51964568634c72a009488/numpy-2.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:0448e7f9caefb34b4b7dd2b77f21e8906e5d6f0365ad525f9f4f530b13df2afc", size = 12444915, upload-time = "2026-03-09T07:58:06.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1f/ab8528e38d295fd349310807496fabb7cf9fe2e1f70b97bc20a483ea9d4a/numpy-2.4.3-cp314-cp314-win_arm64.whl", hash = "sha256:b44fd60341c4d9783039598efadd03617fa28d041fc37d22b62d08f2027fa0e7", size = 10494875, upload-time = "2026-03-09T07:58:08.734Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ef/b7c35e4d5ef141b836658ab21a66d1a573e15b335b1d111d31f26c8ef80f/numpy-2.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0a195f4216be9305a73c0e91c9b026a35f2161237cf1c6de9b681637772ea657", size = 14822225, upload-time = "2026-03-09T07:58:11.034Z" }, + { url = "https://files.pythonhosted.org/packages/cd/8d/7730fa9278cf6648639946cc816e7cc89f0d891602584697923375f801ed/numpy-2.4.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:cd32fbacb9fd1bf041bf8e89e4576b6f00b895f06d00914820ae06a616bdfef7", size = 5328769, upload-time = "2026-03-09T07:58:13.67Z" }, + { url = "https://files.pythonhosted.org/packages/47/01/d2a137317c958b074d338807c1b6a383406cdf8b8e53b075d804cc3d211d/numpy-2.4.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:2e03c05abaee1f672e9d67bc858f300b5ccba1c21397211e8d77d98350972093", size = 6649461, upload-time = "2026-03-09T07:58:15.912Z" }, + { url = "https://files.pythonhosted.org/packages/5c/34/812ce12bc0f00272a4b0ec0d713cd237cb390666eb6206323d1cc9cedbb2/numpy-2.4.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d1ce23cce91fcea443320a9d0ece9b9305d4368875bab09538f7a5b4131938a", size = 15725809, upload-time = "2026-03-09T07:58:17.787Z" }, + { url = "https://files.pythonhosted.org/packages/25/c0/2aed473a4823e905e765fee3dc2cbf504bd3e68ccb1150fbdabd5c39f527/numpy-2.4.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c59020932feb24ed49ffd03704fbab89f22aa9c0d4b180ff45542fe8918f5611", size = 16655242, upload-time = "2026-03-09T07:58:20.476Z" }, + { url = "https://files.pythonhosted.org/packages/f2/c8/7e052b2fc87aa0e86de23f20e2c42bd261c624748aa8efd2c78f7bb8d8c6/numpy-2.4.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9684823a78a6cd6ad7511fc5e25b07947d1d5b5e2812c93fe99d7d4195130720", size = 17080660, upload-time = "2026-03-09T07:58:23.067Z" }, + { url = "https://files.pythonhosted.org/packages/f3/3d/0876746044db2adcb11549f214d104f2e1be00f07a67edbb4e2812094847/numpy-2.4.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0200b25c687033316fb39f0ff4e3e690e8957a2c3c8d22499891ec58c37a3eb5", size = 18380384, upload-time = "2026-03-09T07:58:25.839Z" }, + { url = "https://files.pythonhosted.org/packages/07/12/8160bea39da3335737b10308df4f484235fd297f556745f13092aa039d3b/numpy-2.4.3-cp314-cp314t-win32.whl", hash = "sha256:5e10da9e93247e554bb1d22f8edc51847ddd7dde52d85ce31024c1b4312bfba0", size = 6154547, upload-time = "2026-03-09T07:58:28.289Z" }, + { url = "https://files.pythonhosted.org/packages/42/f3/76534f61f80d74cc9cdf2e570d3d4eeb92c2280a27c39b0aaf471eda7b48/numpy-2.4.3-cp314-cp314t-win_amd64.whl", hash = "sha256:45f003dbdffb997a03da2d1d0cb41fbd24a87507fb41605c0420a3db5bd4667b", size = 12633645, upload-time = "2026-03-09T07:58:30.384Z" }, + { url = "https://files.pythonhosted.org/packages/1f/b6/7c0d4334c15983cec7f92a69e8ce9b1e6f31857e5ee3a413ac424e6bd63d/numpy-2.4.3-cp314-cp314t-win_arm64.whl", hash = "sha256:4d382735cecd7bcf090172489a525cd7d4087bc331f7df9f60ddc9a296cf208e", size = 10565454, upload-time = "2026-03-09T07:58:33.031Z" }, + { url = "https://files.pythonhosted.org/packages/64/e4/4dab9fb43c83719c29241c535d9e07be73bea4bc0c6686c5816d8e1b6689/numpy-2.4.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c6b124bfcafb9e8d3ed09130dbee44848c20b3e758b6bbf006e641778927c028", size = 16834892, upload-time = "2026-03-09T07:58:35.334Z" }, + { url = "https://files.pythonhosted.org/packages/c9/29/f8b6d4af90fed3dfda84ebc0df06c9833d38880c79ce954e5b661758aa31/numpy-2.4.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:76dbb9d4e43c16cf9aa711fcd8de1e2eeb27539dcefb60a1d5e9f12fae1d1ed8", size = 14893070, upload-time = "2026-03-09T07:58:37.7Z" }, + { url = "https://files.pythonhosted.org/packages/9a/04/a19b3c91dbec0a49269407f15d5753673a09832daed40c45e8150e6fa558/numpy-2.4.3-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:29363fbfa6f8ee855d7569c96ce524845e3d726d6c19b29eceec7dd555dab152", size = 5399609, upload-time = "2026-03-09T07:58:39.853Z" }, + { url = "https://files.pythonhosted.org/packages/79/34/4d73603f5420eab89ea8a67097b31364bf7c30f811d4dd84b1659c7476d9/numpy-2.4.3-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:bc71942c789ef415a37f0d4eab90341425a00d538cd0642445d30b41023d3395", size = 6714355, upload-time = "2026-03-09T07:58:42.365Z" }, + { url = "https://files.pythonhosted.org/packages/58/ad/1100d7229bb248394939a12a8074d485b655e8ed44207d328fdd7fcebc7b/numpy-2.4.3-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e58765ad74dcebd3ef0208a5078fba32dc8ec3578fe84a604432950cd043d79", size = 15800434, upload-time = "2026-03-09T07:58:44.837Z" }, + { url = "https://files.pythonhosted.org/packages/0c/fd/16d710c085d28ba4feaf29ac60c936c9d662e390344f94a6beaa2ac9899b/numpy-2.4.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e236dbda4e1d319d681afcbb136c0c4a8e0f1a5c58ceec2adebb547357fe857", size = 16729409, upload-time = "2026-03-09T07:58:47.972Z" }, + { url = "https://files.pythonhosted.org/packages/57/a7/b35835e278c18b85206834b3aa3abe68e77a98769c59233d1f6300284781/numpy-2.4.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:4b42639cdde6d24e732ff823a3fa5b701d8acad89c4142bc1d0bd6dc85200ba5", size = 12504685, upload-time = "2026-03-09T07:58:50.525Z" }, ] [[package]] @@ -2317,11 +2355,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.9.2" +version = "4.9.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394, upload-time = "2026-02-16T03:56:10.574Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/56/8d4c30c8a1d07013911a8fdbd8f89440ef9f08d07a1b50ab8ca8be5a20f9/platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934", size = 28737, upload-time = "2026-03-05T18:34:13.271Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/31/05e764397056194206169869b50cf2fee4dbbbc71b344705b9c0d878d4d8/platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd", size = 21168, upload-time = "2026-02-16T03:56:08.891Z" }, + { url = "https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868", size = 21216, upload-time = "2026-03-05T18:34:12.172Z" }, ] [[package]] @@ -2551,15 +2589,15 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.1.0" +version = "1.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/82/bb/93a3e83bdf9322c7e21cafd092e56a4a17c4d8ef4277b6eb01af1a540a6f/python_discovery-1.1.0.tar.gz", hash = "sha256:447941ba1aed8cc2ab7ee3cb91be5fc137c5bdbb05b7e6ea62fbdcb66e50b268", size = 55674, upload-time = "2026-02-26T09:42:49.668Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/67/09765eacf4e44413c4f8943ba5a317fcb9c7b447c3b8b0b7fce7e3090b0b/python_discovery-1.1.1.tar.gz", hash = "sha256:584c08b141c5b7029f206b4e8b78b1a1764b22121e21519b89dec56936e95b0a", size = 56016, upload-time = "2026-03-07T00:00:56.354Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/54/82a6e2ef37f0f23dccac604b9585bdcbd0698604feb64807dcb72853693e/python_discovery-1.1.0-py3-none-any.whl", hash = "sha256:a162893b8809727f54594a99ad2179d2ede4bf953e12d4c7abc3cc9cdbd1437b", size = 30687, upload-time = "2026-02-26T09:42:48.548Z" }, + { url = "https://files.pythonhosted.org/packages/75/0f/2bf7e3b5a4a65f623cb820feb5793e243fad58ae561015ee15a6152f67a2/python_discovery-1.1.1-py3-none-any.whl", hash = "sha256:69f11073fa2392251e405d4e847d60ffffd25fd762a0dc4d1a7d6b9c3f79f1a3", size = 30732, upload-time = "2026-03-07T00:00:55.143Z" }, ] [[package]] @@ -3024,27 +3062,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/da/31/d6e536cdebb6568ae75a7f00e4b4819ae0ad2640c3604c305a0428680b0c/ruff-0.15.4.tar.gz", hash = "sha256:3412195319e42d634470cc97aa9803d07e9d5c9223b99bcb1518f0c725f26ae1", size = 4569550, upload-time = "2026-02-26T20:04:14.959Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/82/c11a03cfec3a4d26a0ea1e571f0f44be5993b923f905eeddfc397c13d360/ruff-0.15.4-py3-none-linux_armv6l.whl", hash = "sha256:a1810931c41606c686bae8b5b9a8072adac2f611bb433c0ba476acba17a332e0", size = 10453333, upload-time = "2026-02-26T20:04:20.093Z" }, - { url = "https://files.pythonhosted.org/packages/ce/5d/6a1f271f6e31dffb31855996493641edc3eef8077b883eaf007a2f1c2976/ruff-0.15.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5a1632c66672b8b4d3e1d1782859e98d6e0b4e70829530666644286600a33992", size = 10853356, upload-time = "2026-02-26T20:04:05.808Z" }, - { url = "https://files.pythonhosted.org/packages/b1/d8/0fab9f8842b83b1a9c2bf81b85063f65e93fb512e60effa95b0be49bfc54/ruff-0.15.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a4386ba2cd6c0f4ff75252845906acc7c7c8e1ac567b7bc3d373686ac8c222ba", size = 10187434, upload-time = "2026-02-26T20:03:54.656Z" }, - { url = "https://files.pythonhosted.org/packages/85/cc/cc220fd9394eff5db8d94dec199eec56dd6c9f3651d8869d024867a91030/ruff-0.15.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2496488bdfd3732747558b6f95ae427ff066d1fcd054daf75f5a50674411e75", size = 10535456, upload-time = "2026-02-26T20:03:52.738Z" }, - { url = "https://files.pythonhosted.org/packages/fa/0f/bced38fa5cf24373ec767713c8e4cadc90247f3863605fb030e597878661/ruff-0.15.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3f1c4893841ff2d54cbda1b2860fa3260173df5ddd7b95d370186f8a5e66a4ac", size = 10287772, upload-time = "2026-02-26T20:04:08.138Z" }, - { url = "https://files.pythonhosted.org/packages/2b/90/58a1802d84fed15f8f281925b21ab3cecd813bde52a8ca033a4de8ab0e7a/ruff-0.15.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:820b8766bd65503b6c30aaa6331e8ef3a6e564f7999c844e9a547c40179e440a", size = 11049051, upload-time = "2026-02-26T20:04:03.53Z" }, - { url = "https://files.pythonhosted.org/packages/d2/ac/b7ad36703c35f3866584564dc15f12f91cb1a26a897dc2fd13d7cb3ae1af/ruff-0.15.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9fb74bab47139c1751f900f857fa503987253c3ef89129b24ed375e72873e85", size = 11890494, upload-time = "2026-02-26T20:04:10.497Z" }, - { url = "https://files.pythonhosted.org/packages/93/3d/3eb2f47a39a8b0da99faf9c54d3eb24720add1e886a5309d4d1be73a6380/ruff-0.15.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f80c98765949c518142b3a50a5db89343aa90f2c2bf7799de9986498ae6176db", size = 11326221, upload-time = "2026-02-26T20:04:12.84Z" }, - { url = "https://files.pythonhosted.org/packages/ff/90/bf134f4c1e5243e62690e09d63c55df948a74084c8ac3e48a88468314da6/ruff-0.15.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:451a2e224151729b3b6c9ffb36aed9091b2996fe4bdbd11f47e27d8f2e8888ec", size = 11168459, upload-time = "2026-02-26T20:04:00.969Z" }, - { url = "https://files.pythonhosted.org/packages/b5/e5/a64d27688789b06b5d55162aafc32059bb8c989c61a5139a36e1368285eb/ruff-0.15.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a8f157f2e583c513c4f5f896163a93198297371f34c04220daf40d133fdd4f7f", size = 11104366, upload-time = "2026-02-26T20:03:48.099Z" }, - { url = "https://files.pythonhosted.org/packages/f1/f6/32d1dcb66a2559763fc3027bdd65836cad9eb09d90f2ed6a63d8e9252b02/ruff-0.15.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:917cc68503357021f541e69b35361c99387cdbbf99bd0ea4aa6f28ca99ff5338", size = 10510887, upload-time = "2026-02-26T20:03:45.771Z" }, - { url = "https://files.pythonhosted.org/packages/ff/92/22d1ced50971c5b6433aed166fcef8c9343f567a94cf2b9d9089f6aa80fe/ruff-0.15.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e9737c8161da79fd7cfec19f1e35620375bd8b2a50c3e77fa3d2c16f574105cc", size = 10285939, upload-time = "2026-02-26T20:04:22.42Z" }, - { url = "https://files.pythonhosted.org/packages/e6/f4/7c20aec3143837641a02509a4668fb146a642fd1211846634edc17eb5563/ruff-0.15.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:291258c917539e18f6ba40482fe31d6f5ac023994ee11d7bdafd716f2aab8a68", size = 10765471, upload-time = "2026-02-26T20:03:58.924Z" }, - { url = "https://files.pythonhosted.org/packages/d0/09/6d2f7586f09a16120aebdff8f64d962d7c4348313c77ebb29c566cefc357/ruff-0.15.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3f83c45911da6f2cd5936c436cf86b9f09f09165f033a99dcf7477e34041cbc3", size = 11263382, upload-time = "2026-02-26T20:04:24.424Z" }, - { url = "https://files.pythonhosted.org/packages/1b/fa/2ef715a1cd329ef47c1a050e10dee91a9054b7ce2fcfdd6a06d139afb7ec/ruff-0.15.4-py3-none-win32.whl", hash = "sha256:65594a2d557d4ee9f02834fcdf0a28daa8b3b9f6cb2cb93846025a36db47ef22", size = 10506664, upload-time = "2026-02-26T20:03:50.56Z" }, - { url = "https://files.pythonhosted.org/packages/d0/a8/c688ef7e29983976820d18710f955751d9f4d4eb69df658af3d006e2ba3e/ruff-0.15.4-py3-none-win_amd64.whl", hash = "sha256:04196ad44f0df220c2ece5b0e959c2f37c777375ec744397d21d15b50a75264f", size = 11651048, upload-time = "2026-02-26T20:04:17.191Z" }, - { url = "https://files.pythonhosted.org/packages/3e/0a/9e1be9035b37448ce2e68c978f0591da94389ade5a5abafa4cf99985d1b2/ruff-0.15.4-py3-none-win_arm64.whl", hash = "sha256:60d5177e8cfc70e51b9c5fad936c634872a74209f934c1e79107d11787ad5453", size = 10966776, upload-time = "2026-02-26T20:03:56.908Z" }, +version = "0.15.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/77/9b/840e0039e65fcf12758adf684d2289024d6140cde9268cc59887dc55189c/ruff-0.15.5.tar.gz", hash = "sha256:7c3601d3b6d76dce18c5c824fc8d06f4eef33d6df0c21ec7799510cde0f159a2", size = 4574214, upload-time = "2026-03-05T20:06:34.946Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/20/5369c3ce21588c708bcbe517a8fbe1a8dfdb5dfd5137e14790b1da71612c/ruff-0.15.5-py3-none-linux_armv6l.whl", hash = "sha256:4ae44c42281f42e3b06b988e442d344a5b9b72450ff3c892e30d11b29a96a57c", size = 10478185, upload-time = "2026-03-05T20:06:29.093Z" }, + { url = "https://files.pythonhosted.org/packages/44/ed/e81dd668547da281e5dce710cf0bc60193f8d3d43833e8241d006720e42b/ruff-0.15.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6edd3792d408ebcf61adabc01822da687579a1a023f297618ac27a5b51ef0080", size = 10859201, upload-time = "2026-03-05T20:06:32.632Z" }, + { url = "https://files.pythonhosted.org/packages/c4/8f/533075f00aaf19b07c5cd6aa6e5d89424b06b3b3f4583bfa9c640a079059/ruff-0.15.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:89f463f7c8205a9f8dea9d658d59eff49db05f88f89cc3047fb1a02d9f344010", size = 10184752, upload-time = "2026-03-05T20:06:40.312Z" }, + { url = "https://files.pythonhosted.org/packages/66/0e/ba49e2c3fa0395b3152bad634c7432f7edfc509c133b8f4529053ff024fb/ruff-0.15.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba786a8295c6574c1116704cf0b9e6563de3432ac888d8f83685654fe528fd65", size = 10534857, upload-time = "2026-03-05T20:06:19.581Z" }, + { url = "https://files.pythonhosted.org/packages/59/71/39234440f27a226475a0659561adb0d784b4d247dfe7f43ffc12dd02e288/ruff-0.15.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd4b801e57955fe9f02b31d20375ab3a5c4415f2e5105b79fb94cf2642c91440", size = 10309120, upload-time = "2026-03-05T20:06:00.435Z" }, + { url = "https://files.pythonhosted.org/packages/f5/87/4140aa86a93df032156982b726f4952aaec4a883bb98cb6ef73c347da253/ruff-0.15.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:391f7c73388f3d8c11b794dbbc2959a5b5afe66642c142a6effa90b45f6f5204", size = 11047428, upload-time = "2026-03-05T20:05:51.867Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f7/4953e7e3287676f78fbe85e3a0ca414c5ca81237b7575bdadc00229ac240/ruff-0.15.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dc18f30302e379fe1e998548b0f5e9f4dff907f52f73ad6da419ea9c19d66c8", size = 11914251, upload-time = "2026-03-05T20:06:22.887Z" }, + { url = "https://files.pythonhosted.org/packages/77/46/0f7c865c10cf896ccf5a939c3e84e1cfaeed608ff5249584799a74d33835/ruff-0.15.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc6e7f90087e2d27f98dc34ed1b3ab7c8f0d273cc5431415454e22c0bd2a681", size = 11333801, upload-time = "2026-03-05T20:05:57.168Z" }, + { url = "https://files.pythonhosted.org/packages/d3/01/a10fe54b653061585e655f5286c2662ebddb68831ed3eaebfb0eb08c0a16/ruff-0.15.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1cb7169f53c1ddb06e71a9aebd7e98fc0fea936b39afb36d8e86d36ecc2636a", size = 11206821, upload-time = "2026-03-05T20:06:03.441Z" }, + { url = "https://files.pythonhosted.org/packages/7a/0d/2132ceaf20c5e8699aa83da2706ecb5c5dcdf78b453f77edca7fb70f8a93/ruff-0.15.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9b037924500a31ee17389b5c8c4d88874cc6ea8e42f12e9c61a3d754ff72f1ca", size = 11133326, upload-time = "2026-03-05T20:06:25.655Z" }, + { url = "https://files.pythonhosted.org/packages/72/cb/2e5259a7eb2a0f87c08c0fe5bf5825a1e4b90883a52685524596bfc93072/ruff-0.15.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:65bb414e5b4eadd95a8c1e4804f6772bbe8995889f203a01f77ddf2d790929dd", size = 10510820, upload-time = "2026-03-05T20:06:37.79Z" }, + { url = "https://files.pythonhosted.org/packages/ff/20/b67ce78f9e6c59ffbdb5b4503d0090e749b5f2d31b599b554698a80d861c/ruff-0.15.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d20aa469ae3b57033519c559e9bc9cd9e782842e39be05b50e852c7c981fa01d", size = 10302395, upload-time = "2026-03-05T20:05:54.504Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e5/719f1acccd31b720d477751558ed74e9c88134adcc377e5e886af89d3072/ruff-0.15.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:15388dd28c9161cdb8eda68993533acc870aa4e646a0a277aa166de9ad5a8752", size = 10754069, upload-time = "2026-03-05T20:06:06.422Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9c/d1db14469e32d98f3ca27079dbd30b7b44dbb5317d06ab36718dee3baf03/ruff-0.15.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b30da330cbd03bed0c21420b6b953158f60c74c54c5f4c1dabbdf3a57bf355d2", size = 11304315, upload-time = "2026-03-05T20:06:10.867Z" }, + { url = "https://files.pythonhosted.org/packages/28/3a/950367aee7c69027f4f422059227b290ed780366b6aecee5de5039d50fa8/ruff-0.15.5-py3-none-win32.whl", hash = "sha256:732e5ee1f98ba5b3679029989a06ca39a950cced52143a0ea82a2102cb592b74", size = 10551676, upload-time = "2026-03-05T20:06:13.705Z" }, + { url = "https://files.pythonhosted.org/packages/b8/00/bf077a505b4e649bdd3c47ff8ec967735ce2544c8e4a43aba42ee9bf935d/ruff-0.15.5-py3-none-win_amd64.whl", hash = "sha256:821d41c5fa9e19117616c35eaa3f4b75046ec76c65e7ae20a333e9a8696bc7fe", size = 11678972, upload-time = "2026-03-05T20:06:45.379Z" }, + { url = "https://files.pythonhosted.org/packages/fe/4e/cd76eca6db6115604b7626668e891c9dd03330384082e33662fb0f113614/ruff-0.15.5-py3-none-win_arm64.whl", hash = "sha256:b498d1c60d2fe5c10c45ec3f698901065772730b411f164ae270bb6bfcc4740b", size = 10965572, upload-time = "2026-03-05T20:06:16.984Z" }, ] [[package]] @@ -3222,55 +3260,55 @@ wheels = [ [[package]] name = "sqlalchemy" -version = "2.0.47" +version = "2.0.48" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cd/4b/1e00561093fe2cd8eef09d406da003c8a118ff02d6548498c1ae677d68d9/sqlalchemy-2.0.47.tar.gz", hash = "sha256:e3e7feb57b267fe897e492b9721ae46d5c7de6f9e8dee58aacf105dc4e154f3d", size = 9886323, upload-time = "2026-02-24T16:34:27.947Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/13/886338d3e8ab5ddcfe84d54302c749b1793e16c4bba63d7004e3f7baa8ec/sqlalchemy-2.0.47-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a1dbf0913879c443617d6b64403cf2801c941651db8c60e96d204ed9388d6b0", size = 2157124, upload-time = "2026-02-24T16:43:54.706Z" }, - { url = "https://files.pythonhosted.org/packages/b6/bb/a897f6a66c9986aa9f27f5cf8550637d8a5ea368fd7fb42f6dac3105b4dc/sqlalchemy-2.0.47-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:775effbb97ea3b00c4dd3aeaf3ba8acba6e3e2b4b41d17d67a27e696843dbc95", size = 3313513, upload-time = "2026-02-24T17:29:00.527Z" }, - { url = "https://files.pythonhosted.org/packages/59/fb/69bfae022b681507565ab0d34f0c80aa1e9f954a5a7cbfb0ed054966ac8d/sqlalchemy-2.0.47-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56cc834a3ffac34270cc2a41875e0f40e97aa651f4f3ca1cfbbf421c044cb62b", size = 3313014, upload-time = "2026-02-24T17:27:11.679Z" }, - { url = "https://files.pythonhosted.org/packages/04/f3/0eba329f7c182d53205a228c4fd24651b95489b431ea2bd830887b4c13c4/sqlalchemy-2.0.47-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:49b5e0c7244262f39e767c018e4fdb5e5dbc23cd54c5ddac8eea8f0ba32ef890", size = 3265389, upload-time = "2026-02-24T17:29:02.497Z" }, - { url = "https://files.pythonhosted.org/packages/5c/06/654edc084b3b46ac79e04200d7c46467ae80c759c4ee41c897f9272b036f/sqlalchemy-2.0.47-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:15cd822a3f1f6f77b5b841a30c1a07a07f7dee3385f17e638e1722de9ab683be", size = 3287604, upload-time = "2026-02-24T17:27:13.295Z" }, - { url = "https://files.pythonhosted.org/packages/78/33/c18c8f63b61981219d3aa12321bb7ccee605034d195e868ed94f9727b27c/sqlalchemy-2.0.47-cp311-cp311-win32.whl", hash = "sha256:9847a19548cd283a65e1ce0afd54016598d55ff72682d6fd3e493af6fc044064", size = 2116916, upload-time = "2026-02-24T17:14:37.392Z" }, - { url = "https://files.pythonhosted.org/packages/f5/c6/a59e3f9796fff844e16afbd821db9abfd6e12698db9441a231a96193a100/sqlalchemy-2.0.47-cp311-cp311-win_amd64.whl", hash = "sha256:722abf1c82aeca46a1a0803711244a48a298279eeaec9e02f7bfee9e064182e5", size = 2141587, upload-time = "2026-02-24T17:14:39.746Z" }, - { url = "https://files.pythonhosted.org/packages/80/88/74eb470223ff88ea6572a132c0b8de8c1d8ed7b843d3b44a8a3c77f31d39/sqlalchemy-2.0.47-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4fa91b19d6b9821c04cc8f7aa2476429cc8887b9687c762815aa629f5c0edec1", size = 2155687, upload-time = "2026-02-24T17:05:46.451Z" }, - { url = "https://files.pythonhosted.org/packages/ef/ba/1447d3d558971b036cb93b557595cb5dcdfe728f1c7ac4dec16505ef5756/sqlalchemy-2.0.47-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c5bbbd14eff577c8c79cbfe39a0771eecd20f430f3678533476f0087138f356", size = 3336978, upload-time = "2026-02-24T17:18:04.597Z" }, - { url = "https://files.pythonhosted.org/packages/8a/07/b47472d2ffd0776826f17ccf0b4d01b224c99fbd1904aeb103dffbb4b1cc/sqlalchemy-2.0.47-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5a6c555da8d4280a3c4c78c5b7a3f990cee2b2884e5f934f87a226191682ff7", size = 3349939, upload-time = "2026-02-24T17:27:18.937Z" }, - { url = "https://files.pythonhosted.org/packages/bb/c6/95fa32b79b57769da3e16f054cf658d90940317b5ca0ec20eac84aa19c4f/sqlalchemy-2.0.47-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ed48a1701d24dff3bb49a5bce94d6bc84cbe33d98af2aa2d3cdcce3dea1709ec", size = 3279648, upload-time = "2026-02-24T17:18:07.038Z" }, - { url = "https://files.pythonhosted.org/packages/bb/c8/3d07e7c73928dc59a0bed40961ca4e313e797bce650b088e8d5fdd3ad939/sqlalchemy-2.0.47-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4f3178c920ad98158f0b6309382194df04b14808fa6052ae07099fdde29d5602", size = 3314695, upload-time = "2026-02-24T17:27:20.93Z" }, - { url = "https://files.pythonhosted.org/packages/6b/d2/ed32b1611c1e19fdb028eee1adc5a9aa138c2952d09ae11f1670170f80ae/sqlalchemy-2.0.47-cp312-cp312-win32.whl", hash = "sha256:b9c11ac9934dd59ece9619fe42780a08abe2faab7b0543bb00d5eabea4f421b9", size = 2115502, upload-time = "2026-02-24T17:22:52.546Z" }, - { url = "https://files.pythonhosted.org/packages/fd/52/9de590356a4dd8e9ef5a881dbba64b2bbc4cbc71bf02bc68e775fb9b1899/sqlalchemy-2.0.47-cp312-cp312-win_amd64.whl", hash = "sha256:db43b72cf8274a99e089755c9c1e0b947159b71adbc2c83c3de2e38d5d607acb", size = 2142435, upload-time = "2026-02-24T17:22:54.268Z" }, - { url = "https://files.pythonhosted.org/packages/4a/e5/0af64ce7d8f60ec5328c10084e2f449e7912a9b8bdbefdcfb44454a25f49/sqlalchemy-2.0.47-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:456a135b790da5d3c6b53d0ef71ac7b7d280b7f41eb0c438986352bf03ca7143", size = 2152551, upload-time = "2026-02-24T17:05:47.675Z" }, - { url = "https://files.pythonhosted.org/packages/63/79/746b8d15f6940e2ac469ce22d7aa5b1124b1ab820bad9b046eb3000c88a6/sqlalchemy-2.0.47-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09a2f7698e44b3135433387da5d8846cf7cc7c10e5425af7c05fee609df978b6", size = 3278782, upload-time = "2026-02-24T17:18:10.012Z" }, - { url = "https://files.pythonhosted.org/packages/91/b1/bd793ddb34345d1ed43b13ab2d88c95d7d4eb2e28f5b5a99128b9cc2bca2/sqlalchemy-2.0.47-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0bbc72e6a177c78d724f9106aaddc0d26a2ada89c6332b5935414eccf04cbd5", size = 3295155, upload-time = "2026-02-24T17:27:22.827Z" }, - { url = "https://files.pythonhosted.org/packages/97/84/7213def33f94e5ca6f5718d259bc9f29de0363134648425aa218d4356b23/sqlalchemy-2.0.47-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:75460456b043b78b6006e41bdf5b86747ee42eafaf7fffa3b24a6e9a456a2092", size = 3226834, upload-time = "2026-02-24T17:18:11.465Z" }, - { url = "https://files.pythonhosted.org/packages/ef/06/456810204f4dc29b5f025b1b0a03b4bd6b600ebf3c1040aebd90a257fa33/sqlalchemy-2.0.47-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5d9adaa616c3bc7d80f9ded57cd84b51d6617cad6a5456621d858c9f23aaee01", size = 3265001, upload-time = "2026-02-24T17:27:24.813Z" }, - { url = "https://files.pythonhosted.org/packages/fb/20/df3920a4b2217dbd7390a5bd277c1902e0393f42baaf49f49b3c935e7328/sqlalchemy-2.0.47-cp313-cp313-win32.whl", hash = "sha256:76e09f974382a496a5ed985db9343628b1cb1ac911f27342e4cc46a8bac10476", size = 2113647, upload-time = "2026-02-24T17:22:55.747Z" }, - { url = "https://files.pythonhosted.org/packages/46/06/7873ddf69918efbfabd7211829f4bd8019739d0a719253112d305d3ba51d/sqlalchemy-2.0.47-cp313-cp313-win_amd64.whl", hash = "sha256:0664089b0bf6724a0bfb49a0cf4d4da24868a0a5c8e937cd7db356d5dcdf2c66", size = 2139425, upload-time = "2026-02-24T17:22:57.033Z" }, - { url = "https://files.pythonhosted.org/packages/54/fa/61ad9731370c90ac7ea5bf8f5eaa12c48bb4beec41c0fa0360becf4ac10d/sqlalchemy-2.0.47-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed0c967c701ae13da98eb220f9ddab3044ab63504c1ba24ad6a59b26826ad003", size = 3558809, upload-time = "2026-02-24T17:12:15.232Z" }, - { url = "https://files.pythonhosted.org/packages/33/d5/221fac96f0529391fe374875633804c866f2b21a9c6d3a6ca57d9c12cfd7/sqlalchemy-2.0.47-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3537943a61fd25b241e976426a0c6814434b93cf9b09d39e8e78f3c9eb9a487", size = 3525480, upload-time = "2026-02-24T17:27:59.602Z" }, - { url = "https://files.pythonhosted.org/packages/ec/55/8247d53998c3673e4a8d1958eba75c6f5cc3b39082029d400bb1f2a911ae/sqlalchemy-2.0.47-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:57f7e336a64a0dba686c66392d46b9bc7af2c57d55ce6dc1697b4ef32b043ceb", size = 3466569, upload-time = "2026-02-24T17:12:16.94Z" }, - { url = "https://files.pythonhosted.org/packages/6b/b5/c1f0eea1bac6790845f71420a7fe2f2a0566203aa57543117d4af3b77d1c/sqlalchemy-2.0.47-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dff735a621858680217cb5142b779bad40ef7322ddbb7c12062190db6879772e", size = 3475770, upload-time = "2026-02-24T17:28:02.034Z" }, - { url = "https://files.pythonhosted.org/packages/c5/ed/2f43f92474ea0c43c204657dc47d9d002cd738b96ca2af8e6d29a9b5e42d/sqlalchemy-2.0.47-cp313-cp313t-win32.whl", hash = "sha256:3893dc096bb3cca9608ea3487372ffcea3ae9b162f40e4d3c51dd49db1d1b2dc", size = 2141300, upload-time = "2026-02-24T17:14:37.024Z" }, - { url = "https://files.pythonhosted.org/packages/cc/a9/8b73f9f1695b6e92f7aaf1711135a1e3bbeb78bca9eded35cb79180d3c6d/sqlalchemy-2.0.47-cp313-cp313t-win_amd64.whl", hash = "sha256:b5103427466f4b3e61f04833ae01f9a914b1280a2a8bcde3a9d7ab11f3755b42", size = 2173053, upload-time = "2026-02-24T17:14:38.688Z" }, - { url = "https://files.pythonhosted.org/packages/c1/30/98243209aae58ed80e090ea988d5182244ca7ab3ff59e6d850c3dfc7651e/sqlalchemy-2.0.47-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b03010a5a5dfe71676bc83f2473ebe082478e32d77e6f082c8fe15a31c3b42a6", size = 2154355, upload-time = "2026-02-24T17:05:48.959Z" }, - { url = "https://files.pythonhosted.org/packages/ab/62/12ca6ea92055fe486d6558a2a4efe93e194ff597463849c01f88e5adb99d/sqlalchemy-2.0.47-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8e3371aa9024520883a415a09cc20c33cfd3eeccf9e0f4f4c367f940b9cbd44", size = 3274486, upload-time = "2026-02-24T17:18:13.659Z" }, - { url = "https://files.pythonhosted.org/packages/97/88/7dfbdeaa8d42b1584e65d6cc713e9d33b6fa563e0d546d5cb87e545bb0e5/sqlalchemy-2.0.47-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9449f747e50d518c6e1b40cc379e48bfc796453c47b15e627ea901c201e48a6", size = 3279481, upload-time = "2026-02-24T17:27:26.491Z" }, - { url = "https://files.pythonhosted.org/packages/d0/b7/75e1c1970616a9dd64a8a6fd788248da2ddaf81c95f4875f2a1e8aee4128/sqlalchemy-2.0.47-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:21410f60d5cac1d6bfe360e05bd91b179be4fa0aa6eea6be46054971d277608f", size = 3224269, upload-time = "2026-02-24T17:18:15.078Z" }, - { url = "https://files.pythonhosted.org/packages/31/ac/eec1a13b891df9a8bc203334caf6e6aac60b02f61b018ef3b4124b8c4120/sqlalchemy-2.0.47-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:819841dd5bb4324c284c09e2874cf96fe6338bfb57a64548d9b81a4e39c9871f", size = 3246262, upload-time = "2026-02-24T17:27:27.986Z" }, - { url = "https://files.pythonhosted.org/packages/c9/b0/661b0245b06421058610da39f8ceb34abcc90b49f90f256380968d761dbe/sqlalchemy-2.0.47-cp314-cp314-win32.whl", hash = "sha256:e255ee44821a7ef45649c43064cf94e74f81f61b4df70547304b97a351e9b7db", size = 2116528, upload-time = "2026-02-24T17:22:59.363Z" }, - { url = "https://files.pythonhosted.org/packages/aa/ef/1035a90d899e61810791c052004958be622a2cf3eb3df71c3fe20778c5d0/sqlalchemy-2.0.47-cp314-cp314-win_amd64.whl", hash = "sha256:209467ff73ea1518fe1a5aaed9ba75bb9e33b2666e2553af9ccd13387bf192cb", size = 2142181, upload-time = "2026-02-24T17:23:01.001Z" }, - { url = "https://files.pythonhosted.org/packages/76/bb/17a1dd09cbba91258218ceb582225f14b5364d2683f9f5a274f72f2d764f/sqlalchemy-2.0.47-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e78fd9186946afaa287f8a1fe147ead06e5d566b08c0afcb601226e9c7322a64", size = 3563477, upload-time = "2026-02-24T17:12:18.46Z" }, - { url = "https://files.pythonhosted.org/packages/66/8f/1a03d24c40cc321ef2f2231f05420d140bb06a84f7047eaa7eaa21d230ba/sqlalchemy-2.0.47-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5740e2f31b5987ed9619d6912ae5b750c03637f2078850da3002934c9532f172", size = 3528568, upload-time = "2026-02-24T17:28:03.732Z" }, - { url = "https://files.pythonhosted.org/packages/fd/53/d56a213055d6b038a5384f0db5ece7343334aca230ff3f0fa1561106f22c/sqlalchemy-2.0.47-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb9ac00d03de93acb210e8ec7243fefe3e012515bf5fd2f0898c8dff38bc77a4", size = 3472284, upload-time = "2026-02-24T17:12:20.319Z" }, - { url = "https://files.pythonhosted.org/packages/ff/19/c235d81b9cfdd6130bf63143b7bade0dc4afa46c4b634d5d6b2a96bea233/sqlalchemy-2.0.47-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c72a0b9eb2672d70d112cb149fbaf172d466bc691014c496aaac594f1988e706", size = 3478410, upload-time = "2026-02-24T17:28:05.892Z" }, - { url = "https://files.pythonhosted.org/packages/0e/db/cafdeca5ecdaa3bb0811ba5449501da677ce0d83be8d05c5822da72d2e86/sqlalchemy-2.0.47-cp314-cp314t-win32.whl", hash = "sha256:c200db1128d72a71dc3c31c24b42eb9fd85b2b3e5a3c9ba1e751c11ac31250ff", size = 2147164, upload-time = "2026-02-24T17:14:40.783Z" }, - { url = "https://files.pythonhosted.org/packages/fc/5e/ff41a010e9e0f76418b02ad352060a4341bb15f0af66cedc924ab376c7c6/sqlalchemy-2.0.47-cp314-cp314t-win_amd64.whl", hash = "sha256:669837759b84e575407355dcff912835892058aea9b80bd1cb76d6a151cf37f7", size = 2182154, upload-time = "2026-02-24T17:14:43.205Z" }, - { url = "https://files.pythonhosted.org/packages/15/9f/7c378406b592fcf1fc157248607b495a40e3202ba4a6f1372a2ba6447717/sqlalchemy-2.0.47-py3-none-any.whl", hash = "sha256:e2647043599297a1ef10e720cf310846b7f31b6c841fee093d2b09d81215eb93", size = 1940159, upload-time = "2026-02-24T17:15:07.158Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/1f/73/b4a9737255583b5fa858e0bb8e116eb94b88c910164ed2ed719147bde3de/sqlalchemy-2.0.48.tar.gz", hash = "sha256:5ca74f37f3369b45e1f6b7b06afb182af1fd5dde009e4ffd831830d98cbe5fe7", size = 9886075, upload-time = "2026-03-02T15:28:51.474Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/6d/b8b78b5b80f3c3ab3f7fa90faa195ec3401f6d884b60221260fd4d51864c/sqlalchemy-2.0.48-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b4c575df7368b3b13e0cebf01d4679f9a28ed2ae6c1cd0b1d5beffb6b2007dc", size = 2157184, upload-time = "2026-03-02T15:38:28.161Z" }, + { url = "https://files.pythonhosted.org/packages/21/4b/4f3d4a43743ab58b95b9ddf5580a265b593d017693df9e08bd55780af5bb/sqlalchemy-2.0.48-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e83e3f959aaa1c9df95c22c528096d94848a1bc819f5d0ebf7ee3df0ca63db6c", size = 3313555, upload-time = "2026-03-02T15:58:57.21Z" }, + { url = "https://files.pythonhosted.org/packages/21/dd/3b7c53f1dbbf736fd27041aee68f8ac52226b610f914085b1652c2323442/sqlalchemy-2.0.48-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f7b7243850edd0b8b97043f04748f31de50cf426e939def5c16bedb540698f7", size = 3313057, upload-time = "2026-03-02T15:52:29.366Z" }, + { url = "https://files.pythonhosted.org/packages/d9/cc/3e600a90ae64047f33313d7d32e5ad025417f09d2ded487e8284b5e21a15/sqlalchemy-2.0.48-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:82745b03b4043e04600a6b665cb98697c4339b24e34d74b0a2ac0a2488b6f94d", size = 3265431, upload-time = "2026-03-02T15:58:59.096Z" }, + { url = "https://files.pythonhosted.org/packages/8b/19/780138dacfe3f5024f4cf96e4005e91edf6653d53d3673be4844578faf1d/sqlalchemy-2.0.48-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e5e088bf43f6ee6fec7dbf1ef7ff7774a616c236b5c0cb3e00662dd71a56b571", size = 3287646, upload-time = "2026-03-02T15:52:31.569Z" }, + { url = "https://files.pythonhosted.org/packages/40/fd/f32ced124f01a23151f4777e4c705f3a470adc7bd241d9f36a7c941a33bf/sqlalchemy-2.0.48-cp311-cp311-win32.whl", hash = "sha256:9c7d0a77e36b5f4b01ca398482230ab792061d243d715299b44a0b55c89fe617", size = 2116956, upload-time = "2026-03-02T15:46:54.535Z" }, + { url = "https://files.pythonhosted.org/packages/58/d5/dd767277f6feef12d05651538f280277e661698f617fa4d086cce6055416/sqlalchemy-2.0.48-cp311-cp311-win_amd64.whl", hash = "sha256:583849c743e0e3c9bb7446f5b5addeacedc168d657a69b418063dfdb2d90081c", size = 2141627, upload-time = "2026-03-02T15:46:55.849Z" }, + { url = "https://files.pythonhosted.org/packages/ef/91/a42ae716f8925e9659df2da21ba941f158686856107a61cc97a95e7647a3/sqlalchemy-2.0.48-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:348174f228b99f33ca1f773e85510e08927620caa59ffe7803b37170df30332b", size = 2155737, upload-time = "2026-03-02T15:49:13.207Z" }, + { url = "https://files.pythonhosted.org/packages/b9/52/f75f516a1f3888f027c1cfb5d22d4376f4b46236f2e8669dcb0cddc60275/sqlalchemy-2.0.48-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53667b5f668991e279d21f94ccfa6e45b4e3f4500e7591ae59a8012d0f010dcb", size = 3337020, upload-time = "2026-03-02T15:50:34.547Z" }, + { url = "https://files.pythonhosted.org/packages/37/9a/0c28b6371e0cdcb14f8f1930778cb3123acfcbd2c95bb9cf6b4a2ba0cce3/sqlalchemy-2.0.48-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34634e196f620c7a61d18d5cf7dc841ca6daa7961aed75d532b7e58b309ac894", size = 3349983, upload-time = "2026-03-02T15:53:25.542Z" }, + { url = "https://files.pythonhosted.org/packages/1c/46/0aee8f3ff20b1dcbceb46ca2d87fcc3d48b407925a383ff668218509d132/sqlalchemy-2.0.48-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:546572a1793cc35857a2ffa1fe0e58571af1779bcc1ffa7c9fb0839885ed69a9", size = 3279690, upload-time = "2026-03-02T15:50:36.277Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8c/a957bc91293b49181350bfd55e6dfc6e30b7f7d83dc6792d72043274a390/sqlalchemy-2.0.48-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:07edba08061bc277bfdc772dd2a1a43978f5a45994dd3ede26391b405c15221e", size = 3314738, upload-time = "2026-03-02T15:53:27.519Z" }, + { url = "https://files.pythonhosted.org/packages/4b/44/1d257d9f9556661e7bdc83667cc414ba210acfc110c82938cb3611eea58f/sqlalchemy-2.0.48-cp312-cp312-win32.whl", hash = "sha256:908a3fa6908716f803b86896a09a2c4dde5f5ce2bb07aacc71ffebb57986ce99", size = 2115546, upload-time = "2026-03-02T15:54:31.591Z" }, + { url = "https://files.pythonhosted.org/packages/f2/af/c3c7e1f3a2b383155a16454df62ae8c62a30dd238e42e68c24cebebbfae6/sqlalchemy-2.0.48-cp312-cp312-win_amd64.whl", hash = "sha256:68549c403f79a8e25984376480959975212a670405e3913830614432b5daa07a", size = 2142484, upload-time = "2026-03-02T15:54:34.072Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c6/569dc8bf3cd375abc5907e82235923e986799f301cd79a903f784b996fca/sqlalchemy-2.0.48-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e3070c03701037aa418b55d36532ecb8f8446ed0135acb71c678dbdf12f5b6e4", size = 2152599, upload-time = "2026-03-02T15:49:14.41Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ff/f4e04a4bd5a24304f38cb0d4aa2ad4c0fb34999f8b884c656535e1b2b74c/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2645b7d8a738763b664a12a1542c89c940daa55196e8d73e55b169cc5c99f65f", size = 3278825, upload-time = "2026-03-02T15:50:38.269Z" }, + { url = "https://files.pythonhosted.org/packages/fe/88/cb59509e4668d8001818d7355d9995be90c321313078c912420603a7cb95/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b19151e76620a412c2ac1c6f977ab1b9fa7ad43140178345136456d5265b32ed", size = 3295200, upload-time = "2026-03-02T15:53:29.366Z" }, + { url = "https://files.pythonhosted.org/packages/87/dc/1609a4442aefd750ea2f32629559394ec92e89ac1d621a7f462b70f736ff/sqlalchemy-2.0.48-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b193a7e29fd9fa56e502920dca47dffe60f97c863494946bd698c6058a55658", size = 3226876, upload-time = "2026-03-02T15:50:39.802Z" }, + { url = "https://files.pythonhosted.org/packages/37/c3/6ae2ab5ea2fa989fbac4e674de01224b7a9d744becaf59bb967d62e99bed/sqlalchemy-2.0.48-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:36ac4ddc3d33e852da9cb00ffb08cea62ca05c39711dc67062ca2bb1fae35fd8", size = 3265045, upload-time = "2026-03-02T15:53:31.421Z" }, + { url = "https://files.pythonhosted.org/packages/6f/82/ea4665d1bb98c50c19666e672f21b81356bd6077c4574e3d2bbb84541f53/sqlalchemy-2.0.48-cp313-cp313-win32.whl", hash = "sha256:389b984139278f97757ea9b08993e7b9d1142912e046ab7d82b3fbaeb0209131", size = 2113700, upload-time = "2026-03-02T15:54:35.825Z" }, + { url = "https://files.pythonhosted.org/packages/b7/2b/b9040bec58c58225f073f5b0c1870defe1940835549dafec680cbd58c3c3/sqlalchemy-2.0.48-cp313-cp313-win_amd64.whl", hash = "sha256:d612c976cbc2d17edfcc4c006874b764e85e990c29ce9bd411f926bbfb02b9a2", size = 2139487, upload-time = "2026-03-02T15:54:37.079Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/7b17bd50244b78a49d22cc63c969d71dc4de54567dc152a9b46f6fae40ce/sqlalchemy-2.0.48-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69f5bc24904d3bc3640961cddd2523e361257ef68585d6e364166dfbe8c78fae", size = 3558851, upload-time = "2026-03-02T15:57:48.607Z" }, + { url = "https://files.pythonhosted.org/packages/20/0d/213668e9aca61d370f7d2a6449ea4ec699747fac67d4bda1bb3d129025be/sqlalchemy-2.0.48-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd08b90d211c086181caed76931ecfa2bdfc83eea3cfccdb0f82abc6c4b876cb", size = 3525525, upload-time = "2026-03-02T16:04:38.058Z" }, + { url = "https://files.pythonhosted.org/packages/85/d7/a84edf412979e7d59c69b89a5871f90a49228360594680e667cb2c46a828/sqlalchemy-2.0.48-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1ccd42229aaac2df431562117ac7e667d702e8e44afdb6cf0e50fa3f18160f0b", size = 3466611, upload-time = "2026-03-02T15:57:50.759Z" }, + { url = "https://files.pythonhosted.org/packages/86/55/42404ce5770f6be26a2b0607e7866c31b9a4176c819e9a7a5e0a055770be/sqlalchemy-2.0.48-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0dcbc588cd5b725162c076eb9119342f6579c7f7f55057bb7e3c6ff27e13121", size = 3475812, upload-time = "2026-03-02T16:04:40.092Z" }, + { url = "https://files.pythonhosted.org/packages/ae/ae/29b87775fadc43e627cf582fe3bda4d02e300f6b8f2747c764950d13784c/sqlalchemy-2.0.48-cp313-cp313t-win32.whl", hash = "sha256:9764014ef5e58aab76220c5664abb5d47d5bc858d9debf821e55cfdd0f128485", size = 2141335, upload-time = "2026-03-02T15:52:51.518Z" }, + { url = "https://files.pythonhosted.org/packages/91/44/f39d063c90f2443e5b46ec4819abd3d8de653893aae92df42a5c4f5843de/sqlalchemy-2.0.48-cp313-cp313t-win_amd64.whl", hash = "sha256:e2f35b4cccd9ed286ad62e0a3c3ac21e06c02abc60e20aa51a3e305a30f5fa79", size = 2173095, upload-time = "2026-03-02T15:52:52.79Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b3/f437eaa1cf028bb3c927172c7272366393e73ccd104dcf5b6963f4ab5318/sqlalchemy-2.0.48-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e2d0d88686e3d35a76f3e15a34e8c12d73fc94c1dea1cd55782e695cc14086dd", size = 2154401, upload-time = "2026-03-02T15:49:17.24Z" }, + { url = "https://files.pythonhosted.org/packages/6c/1c/b3abdf0f402aa3f60f0df6ea53d92a162b458fca2321d8f1f00278506402/sqlalchemy-2.0.48-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49b7bddc1eebf011ea5ab722fdbe67a401caa34a350d278cc7733c0e88fecb1f", size = 3274528, upload-time = "2026-03-02T15:50:41.489Z" }, + { url = "https://files.pythonhosted.org/packages/f2/5e/327428a034407651a048f5e624361adf3f9fbac9d0fa98e981e9c6ff2f5e/sqlalchemy-2.0.48-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:426c5ca86415d9b8945c7073597e10de9644802e2ff502b8e1f11a7a2642856b", size = 3279523, upload-time = "2026-03-02T15:53:32.962Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ca/ece73c81a918add0965b76b868b7b5359e068380b90ef1656ee995940c02/sqlalchemy-2.0.48-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:288937433bd44e3990e7da2402fabc44a3c6c25d3704da066b85b89a85474ae0", size = 3224312, upload-time = "2026-03-02T15:50:42.996Z" }, + { url = "https://files.pythonhosted.org/packages/88/11/fbaf1ae91fa4ee43f4fe79661cead6358644824419c26adb004941bdce7c/sqlalchemy-2.0.48-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8183dc57ae7d9edc1346e007e840a9f3d6aa7b7f165203a99e16f447150140d2", size = 3246304, upload-time = "2026-03-02T15:53:34.937Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5fb0deb13930b4f2f698c5541ae076c18981173e27dd00376dbaea7a9c82/sqlalchemy-2.0.48-cp314-cp314-win32.whl", hash = "sha256:1182437cb2d97988cfea04cf6cdc0b0bb9c74f4d56ec3d08b81e23d621a28cc6", size = 2116565, upload-time = "2026-03-02T15:54:38.321Z" }, + { url = "https://files.pythonhosted.org/packages/95/7e/e83615cb63f80047f18e61e31e8e32257d39458426c23006deeaf48f463b/sqlalchemy-2.0.48-cp314-cp314-win_amd64.whl", hash = "sha256:144921da96c08feb9e2b052c5c5c1d0d151a292c6135623c6b2c041f2a45f9e0", size = 2142205, upload-time = "2026-03-02T15:54:39.831Z" }, + { url = "https://files.pythonhosted.org/packages/83/e3/69d8711b3f2c5135e9cde5f063bc1605860f0b2c53086d40c04017eb1f77/sqlalchemy-2.0.48-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5aee45fd2c6c0f2b9cdddf48c48535e7471e42d6fb81adfde801da0bd5b93241", size = 3563519, upload-time = "2026-03-02T15:57:52.387Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4f/a7cce98facca73c149ea4578981594aaa5fd841e956834931de503359336/sqlalchemy-2.0.48-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7cddca31edf8b0653090cbb54562ca027c421c58ddde2c0685f49ff56a1690e0", size = 3528611, upload-time = "2026-03-02T16:04:42.097Z" }, + { url = "https://files.pythonhosted.org/packages/cd/7d/5936c7a03a0b0cb0fa0cc425998821c6029756b0855a8f7ee70fba1de955/sqlalchemy-2.0.48-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7a936f1bb23d370b7c8cc079d5fce4c7d18da87a33c6744e51a93b0f9e97e9b3", size = 3472326, upload-time = "2026-03-02T15:57:54.423Z" }, + { url = "https://files.pythonhosted.org/packages/f4/33/cea7dfc31b52904efe3dcdc169eb4514078887dff1f5ae28a7f4c5d54b3c/sqlalchemy-2.0.48-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e004aa9248e8cb0a5f9b96d003ca7c1c0a5da8decd1066e7b53f59eb8ce7c62b", size = 3478453, upload-time = "2026-03-02T16:04:44.584Z" }, + { url = "https://files.pythonhosted.org/packages/c8/95/32107c4d13be077a9cae61e9ae49966a35dc4bf442a8852dd871db31f62e/sqlalchemy-2.0.48-cp314-cp314t-win32.whl", hash = "sha256:b8438ec5594980d405251451c5b7ea9aa58dda38eb7ac35fb7e4c696712ee24f", size = 2147209, upload-time = "2026-03-02T15:52:54.274Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d7/1e073da7a4bc645eb83c76067284a0374e643bc4be57f14cc6414656f92c/sqlalchemy-2.0.48-cp314-cp314t-win_amd64.whl", hash = "sha256:d854b3970067297f3a7fbd7a4683587134aa9b3877ee15aa29eea478dc68f933", size = 2182198, upload-time = "2026-03-02T15:52:55.606Z" }, + { url = "https://files.pythonhosted.org/packages/46/2c/9664130905f03db57961b8980b05cab624afd114bf2be2576628a9f22da4/sqlalchemy-2.0.48-py3-none-any.whl", hash = "sha256:a66fe406437dd65cacd96a72689a3aaaecaebbcd62d81c5ac1c0fdbeac835096", size = 1940202, upload-time = "2026-03-02T15:52:43.285Z" }, ] [[package]] @@ -3345,14 +3383,14 @@ wheels = [ [[package]] name = "tifffile" -version = "2026.2.24" +version = "2026.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6e/1c/19fc653e2b05ec0defae511b03b330ca60c95f2c47fcaaf21c52c6e84aa8/tifffile-2026.2.24.tar.gz", hash = "sha256:d73cfa6d7a8f5775a1e3c9f3bfca77c992946639fb41a5bbe888878cb6964dc6", size = 387373, upload-time = "2026-02-24T23:59:11.706Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/cb/2f6d79c7576e22c116352a801f4c3c8ace5957e9aced862012430b62e14f/tifffile-2026.3.3.tar.gz", hash = "sha256:d9a1266bed6f2ee1dd0abde2018a38b4f8b2935cb843df381d70ac4eac5458b7", size = 388745, upload-time = "2026-03-03T19:14:38.134Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/fe/80250dc06cd4a3a5afe7059875a8d53e97a78528c5dd9ea8c3f981fb897a/tifffile-2026.2.24-py3-none-any.whl", hash = "sha256:38ef6258c2bd8dd3551c7480c6d75a36c041616262e6cd55a50dd16046b71863", size = 243223, upload-time = "2026-02-24T23:59:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/1a/e4/e804505f87627cd8cdae9c010c47c4485fd8c1ce31a7dd0ab7fcc4707377/tifffile-2026.3.3-py3-none-any.whl", hash = "sha256:e8be15c94273113d31ecb7aa3a39822189dd11c4967e3cc88c178f1ad2fd1170", size = 243960, upload-time = "2026-03-03T19:14:35.808Z" }, ] [[package]] From 35e81b30f5ce6831ce355749d6789fa4fb6fc1ed Mon Sep 17 00:00:00 2001 From: darshan-mali Date: Mon, 9 Mar 2026 14:50:02 -0700 Subject: [PATCH 166/335] Added test_lattice.py. Contains basic pytests for Lattice Initialization, constructors, define_lattice() and AutoSerialize implementation. --- tests/imaging/test_lattice.py | 166 ++++++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 tests/imaging/test_lattice.py diff --git a/tests/imaging/test_lattice.py b/tests/imaging/test_lattice.py new file mode 100644 index 00000000..4a3a6bc4 --- /dev/null +++ b/tests/imaging/test_lattice.py @@ -0,0 +1,166 @@ +import numpy as np +import pytest +from numpy.testing import assert_array_almost_equal + +from quantem.core.datastructures.dataset2d import Dataset2d +from quantem.core.io.serialize import load +from quantem.imaging.lattice import Lattice + + +class TestLatticeInit: + """Test Lattice initialization and from_data.""" + + def test_init_and_constructor(self): + """Test that direct init is blocked and from_data works.""" + image = np.random.randn(100, 100) + ds2d = Dataset2d.from_array(image) + + with pytest.raises(RuntimeError, match="Use Lattice.from_data"): + Lattice(ds2d) + + lattice = Lattice.from_data(image) + assert isinstance(lattice, Lattice) + assert lattice.image is not None + + def test_normalization(self): + """Test min/max normalization.""" + image = np.random.randn(100, 100) * 1000.0 + image[0, 0] = 0.0 + image[99, 99] = 1000.0 + + # Both normalizations + lattice = Lattice.from_data(image) + assert lattice.image.array.min() == 0 + assert lattice.image.array.max() == 1 + + # No normalization + lattice = Lattice.from_data(image, normalize_min=False, normalize_max=False) + assert_array_almost_equal(lattice.image.array, image) + + def test_edge_cases(self): + """Test NaN handling.""" + nan_arr = np.array([[1, np.nan], [3, 4]], dtype=float) + lattice = Lattice.from_data(nan_arr) + assert isinstance(lattice, Lattice) + + def test_invalid_inputs(self): + """Test that invalid inputs raise errors.""" + with pytest.raises(ValueError, match="must be a 2D array"): + Lattice.from_data(np.array([1, 2, 3])) + + with pytest.raises(ValueError, match="must be a 2D array"): + Lattice.from_data(np.ones((2, 2, 2))) + + with pytest.raises(ValueError, match="must not be empty"): + Lattice.from_data(np.array([[]])) + + +class TestLatticeImage: + """Test image property getter and setter.""" + + def test_image_property(self): + """Test getting and setting image.""" + image = np.random.randn(100, 100) + lattice = Lattice.from_data(image) + + # Get + assert isinstance(lattice.image, Dataset2d) + + # Set with new array + new_image = np.random.randn(50, 50) + lattice.image = new_image + assert lattice.image.array.shape == (50, 50) + + # Invalid set + with pytest.raises(ValueError, match="must be a 2D array"): + lattice.image = np.array([1, 2, 3]) + + +class TestDefineLattice: + """Test define_lattice method.""" + + def test_basic_define(self): + """Test basic lattice definition.""" + image = np.random.randn(100, 100) + lattice = Lattice.from_data(image) + + result = lattice.define_lattice( + origin=[50, 50], u=[5, 0], v=[0, 5], refine_lattice=False, plot_lattice=False + ) + + assert result is lattice + assert hasattr(lattice, "_lat") + assert lattice._lat.shape == (3, 2) + + def test_refinement_options(self): + """Test lattice refinement and block_size.""" + image = np.random.randn(100, 100) + lattice = Lattice.from_data(image) + + # With refinement + lattice.define_lattice( + origin=[50, 50], + u=[5, 0], + v=[0, 5], + refine_lattice=True, + refine_maxiter=5, + plot_lattice=False, + ) + assert lattice._lat.shape == (3, 2) + + # With block_size + lattice.define_lattice( + origin=[50, 50], + u=[5, 0], + v=[0, 5], + refine_lattice=True, + refine_maxiter=5, + block_size=5, + plot_lattice=False, + ) + assert lattice._lat.shape == (3, 2) + + def test_invalid_lattice_params(self): + """Test invalid lattice parameters.""" + image = np.random.randn(100, 100) + lattice = Lattice.from_data(image) + + # Wrong shape + with pytest.raises(ValueError): + lattice.define_lattice(origin=[1, 2, 3], u=[5, 0], v=[0, 5], plot_lattice=False) + + # Negative block_size + with pytest.raises(AssertionError): + lattice.define_lattice( + origin=[50, 50], u=[5, 0], v=[0, 5], block_size=-1, plot_lattice=False + ) + + +class TestLatticeSerialize: + """Test Lattice Autoserialize implementation.""" + + @pytest.mark.parametrize("store", ["zip", "dir"]) + def test_lattice_save_load(self, tmp_path, store): + """Test save/load of lattice.""" + # Create lattice with image and defined lattice + image = np.random.randn(100, 100) + lattice = Lattice.from_data(image) + lattice.define_lattice( + origin=[50, 50], u=[5, 0], v=[0, 5], refine_lattice=False, plot_lattice=False + ) + + # Save + filepath = tmp_path / ("lattice.zip" if store == "zip" else "lattice_dir") + lattice.save(str(filepath), mode="w", store=store) + + # Load + loaded = load(str(filepath)) + + # Verify + assert isinstance(loaded, Lattice) + assert isinstance(loaded.image, Dataset2d) + assert loaded.image.array.shape == lattice.image.array.shape + assert np.allclose(loaded.image.array, lattice.image.array) + assert hasattr(loaded, "_lat") + assert loaded._lat.shape == (3, 2) + assert np.allclose(loaded._lat, lattice._lat) From 568fe350cb86c8a696e5178dc391286cecc8eba6 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 9 Mar 2026 18:10:29 -0700 Subject: [PATCH 167/335] in ptycho, visualize scan positions, get real-space sampling --- .../ptychography_visualizations.py | 85 ++++++++++++++----- 1 file changed, 62 insertions(+), 23 deletions(-) diff --git a/src/quantem/diffractive_imaging/ptychography_visualizations.py b/src/quantem/diffractive_imaging/ptychography_visualizations.py index d415dd4f..9ff3a549 100644 --- a/src/quantem/diffractive_imaging/ptychography_visualizations.py +++ b/src/quantem/diffractive_imaging/ptychography_visualizations.py @@ -829,50 +829,89 @@ def _show_object_and_probe_iters( **kwargs, ) - def show_scan_positions(self, plot_radii: bool = True): - """ - Show the scan positions and the probe radius. + def show_scan_positions( + self, + plot_radii: bool = True, + num_probes: int | None = None, + axsize: tuple[int, int] = (10, 10), + edgecolors: str = "red", + linewidths: float = 0.5, + **kwargs, + ): + r"""Show scan positions, probe radii, and overlap metadata + + Visualize probe coverage to verify sufficient overlap for + ptychographic reconstruction. The probe radius is estimated as + :math:`r = 0.61 \lambda / \alpha + |\Delta f| \cdot \alpha` + where :math:`\lambda` is the electron wavelength, :math:`\alpha` is + the convergence semi-angle, and :math:`\Delta f` is the defocus. + Overlap is :math:`(d - s)/d = 1 - s/d` where :math:`d = 2r` is + the probe diameter and :math:`s` is the scan step size. + Parameters ---------- - plot_radii: bool, optional - Whether to plot the probe radius, by default True + plot_radii : bool, optional + Whether to plot the probe radius. Default is True + num_probes : int | None, optional + Number of probe positions to display. Default is 3 rows + of scan positions + axsize : tuple[int, int], optional + Size of the figure axes. Default is (10, 10) + edgecolors : str, optional + Edge color for the probe circles. Default is "red" + linewidths : float, optional + Line width of the probe circles. Default is 0.5 + **kwargs + Additional keyword arguments passed to ``matplotlib.axes.Axes.scatter`` - Returns - ------- - None + Examples + -------- + >>> ptycho.show_scan_positions() + + >>> ptycho.show_scan_positions(axsize=(10, 10), num_probes=10, edgecolors="b", linewidths=0.5) """ # for each scan position, sum the intensity of self.probe at that position scan_positions = self.dset.scan_positions_px.cpu().detach().numpy() - probe_params = self.probe_model.probe_params probe_radius_px = None - conv_angle = probe_params.get("semiangle_cutoff") defocus = probe_params.get("defocus", 0) energy = probe_params.get("energy") + scan_gpts = self.dset.gpts + scan_sampling = self.dset.scan_sampling + fov = scan_sampling * (np.array(scan_gpts) - 1) + print(f"Scan grid: {scan_gpts[0]} x {scan_gpts[1]} positions") + print(f"FOV: {fov[0]:.2f} x {fov[1]:.2f} Å") if conv_angle is not None and energy is not None: from quantem.core.utils.utils import electron_wavelength_angstrom - wavelength = electron_wavelength_angstrom(energy) conv_angle_rad = conv_angle * 1e-3 - # For defocused probe: radius ≈ |defocus| * convergence_angle + diffraction_limit - diffraction_limit_angstrom = 0.61 * wavelength / conv_angle_rad - defocus_blur_angstrom = abs(defocus) * conv_angle_rad - probe_radius_angstrom = diffraction_limit_angstrom + defocus_blur_angstrom - probe_radius_px = probe_radius_angstrom / self.sampling[0] - - _fig, ax = show_2d(self._get_probe_overlap(), title="probe overlap") + diffraction_limit_A = 0.61 * wavelength / conv_angle_rad + defocus_blur_A = abs(defocus) * conv_angle_rad + probe_radius_A = diffraction_limit_A + defocus_blur_A + probe_radius_px = probe_radius_A / self.sampling[0] + probe_diameter_A = 2 * probe_radius_A + probe_step_size_A = scan_sampling[0] + overlap = max(0.0, 1 - probe_step_size_A / probe_diameter_A) + print(f"Probe diameter (rough estimate): {probe_diameter_A:.2f} Å") + print(f"Step size: {probe_step_size_A:.2f} Å") + print(f"Probe overlap (rough estimate): {overlap * 100:.1f}%") + + _fig, ax = show_2d(self._get_probe_overlap(), title="probe overlap", axsize=axsize) if probe_radius_px is not None and plot_radii: # plot a circle with the probe radius for each probe position + if num_probes is None: + num_probes = 3 * scan_gpts[1] # 3 rows worth ax.scatter( - scan_positions[:, 1], - scan_positions[:, 0], + scan_positions[:num_probes, 1], + scan_positions[:num_probes, 0], s=probe_radius_px**2, - edgecolors="red", - c="none", - linestyle="--", + facecolors="none", + edgecolors=edgecolors, + linewidths=linewidths, + **kwargs, ) plt.show() From 9ce62df9d6e34f3f5f66e014189f8a670ac1a24c Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 9 Mar 2026 18:18:56 -0700 Subject: [PATCH 168/335] add show_scan_position print output in the api doc --- .../diffractive_imaging/ptychography_visualizations.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/quantem/diffractive_imaging/ptychography_visualizations.py b/src/quantem/diffractive_imaging/ptychography_visualizations.py index 9ff3a549..05683abc 100644 --- a/src/quantem/diffractive_imaging/ptychography_visualizations.py +++ b/src/quantem/diffractive_imaging/ptychography_visualizations.py @@ -867,8 +867,13 @@ def show_scan_positions( Examples -------- >>> ptycho.show_scan_positions() + Scan grid: 192 x 192 positions + FOV: 133.46 x 133.46 Å + Probe diameter (rough estimate): 3.46 Å + Step size: 0.70 Å + Probe overlap (rough estimate): 79.8% - >>> ptycho.show_scan_positions(axsize=(10, 10), num_probes=10, edgecolors="b", linewidths=0.5) + >>> ptycho.show_scan_positions(num_probes=10, edgecolors="b", linewidths=0.5) """ # for each scan position, sum the intensity of self.probe at that position scan_positions = self.dset.scan_positions_px.cpu().detach().numpy() From 66ec915ade9d9a8369f91753d560292447da5d62 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 9 Mar 2026 22:29:31 -0700 Subject: [PATCH 169/335] bin, crop API doc improvement --- src/quantem/core/datastructures/dataset.py | 93 ++++++++++++++++++---- 1 file changed, 76 insertions(+), 17 deletions(-) diff --git a/src/quantem/core/datastructures/dataset.py b/src/quantem/core/datastructures/dataset.py index f01e68f6..c1648d2a 100644 --- a/src/quantem/core/datastructures/dataset.py +++ b/src/quantem/core/datastructures/dataset.py @@ -452,21 +452,56 @@ def crop( axes: tuple | None = None, modify_in_place: bool = False, ) -> Self | None: - """ - Crops Dataset + """Select a sub-region of the dataset along specified axes + + Unlike regular slicing (``dset[64:192, ...]``), which creates a + view that keeps the full original array in memory, + ``modify_in_place=True`` replaces the array and frees the original. + This is important for large 4D-STEM datasets where memory is + limited. + + Each ``crop_widths`` entry is a ``(start, stop)`` pair defining + which elements to keep. A ``stop`` of ``0`` keeps everything from + ``start`` to the end. Parameters ---------- - crop_widths:tuple - Min and max for cropping each axis specified as a tuple - axes: - Axes over which to crop. If None specified, all are cropped. - modify_in_place: bool - If True, modifies dataset + crop_widths : tuple[tuple[int, int], ...] + ``(start, stop)`` indices for each axis specified in ``axes``. + axes : tuple | None + Axes to crop. If None, all axes are cropped. + modify_in_place : bool + If True, modifies this dataset in-place and frees the original + array. If False, returns a new dataset. Returns + ------- + Dataset | None + Cropped dataset if ``modify_in_place`` is False, otherwise None. + + Examples -------- - Dataset (cropped) only if modify_in_place is False + Crop real-space to a 128x128 region: + + >>> dset_cropped = dset.crop( + ... crop_widths=((64, 192), (64, 192)), + ... axes=(0, 1), + ... ) + + Crop k-space to keep the first 180 pixels: + + >>> dset_preview = dset.crop( + ... crop_widths=((0, 180), (0, 180)), + ... axes=(2, 3), + ... ) + + Crop k-space in-place to free memory: + + >>> dset.crop( + ... crop_widths=((4, 92), (4, 92)), + ... axes=(2, 3), + ... modify_in_place=True, + ... ) """ if axes is None: if len(crop_widths) != self.ndim: @@ -526,27 +561,51 @@ def bin( modify_in_place: bool = False, reducer: str = "sum", ) -> Self | None: - """ - Bin the Dataset by integer factors along selected axes using block reduction. + """Reduce the dataset resolution by grouping pixels into blocks + + Useful for reducing diffraction pattern size to speed up + reconstruction or lower memory usage. Sampling metadata is + updated automatically. Parameters ---------- bin_factors : int | tuple[int, ...] - Bin factors per specified axis (positive integers). + A single integer bins all axes by the same factor. A tuple + specifies a different factor per axis, e.g. ``(1, 1, 2, 2)`` + to bin only the last two axes by 2x. axes : int | tuple[int, ...] | None Axes to bin. If None, all axes are binned. modify_in_place : bool - If True, modifies this dataset; otherwise returns a new Dataset. - reducer : {"sum","mean"} - Reduction applied within each block. "sum" (default) preserves counts; - "mean" averages over each block (block volume = product of factors). + If True, modifies this dataset in-place. If False, returns + a new dataset. + reducer : {"sum", "mean"} + ``"sum"`` (default) preserves total counts; ``"mean"`` + averages over each block. + + Returns + ------- + Dataset | None + Binned dataset if ``modify_in_place`` is False, otherwise None. Notes ----- - Any remainder (shape % factor) is dropped on each binned axis. - Sampling is multiplied by the factor on each binned axis. - Origin is shifted to the center of the first block: - origin_new = origin_old + 0.5 * (factor - 1) * sampling_old + ``origin_new = origin_old + 0.5 * (factor - 1) * sampling_old`` + + Examples + -------- + Bin diffraction space by 2x to reduce memory: + + >>> dset.bin( + ... bin_factors=(1, 1, 2, 2), + ... modify_in_place=True, + ... ) + + Bin all axes by 2x and return a new dataset: + + >>> dset_binned = dset.bin(bin_factors=2) """ reducer_norm = str(reducer).lower() if reducer_norm not in ("sum", "mean"): From 72ecdf7f58b5e9f04434adc76d0e2a22749e63df Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 9 Mar 2026 22:30:40 -0700 Subject: [PATCH 170/335] add bin tests for 4dstem --- tests/datastructures/test_dataset.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/datastructures/test_dataset.py b/tests/datastructures/test_dataset.py index 93449aa2..9a222620 100644 --- a/tests/datastructures/test_dataset.py +++ b/tests/datastructures/test_dataset.py @@ -252,6 +252,25 @@ def test_crop(self, sample_dataset_2d): sample_dataset_2d.crop(crop_widths=((1, 9), (1, 9)), modify_in_place=True) assert sample_dataset_2d.shape == (8, 8) + def test_crop_4dstem_kspace(self): + """Test cropping k-space axes of a 4D-STEM dataset.""" + dset = Dataset.from_array(np.random.rand(8, 8, 96, 96)) + cropped = dset.crop(crop_widths=((4, 92), (4, 92)), axes=(2, 3)) + assert cropped.shape == (8, 8, 88, 88) + assert dset.shape == (8, 8, 96, 96) + + def test_crop_4dstem_realspace_in_place(self): + """Test in-place real-space crop of a 4D-STEM dataset.""" + dset = Dataset.from_array(np.random.rand(16, 16, 32, 32)) + dset.crop(crop_widths=((4, 12), (4, 12)), axes=(0, 1), modify_in_place=True) + assert dset.shape == (8, 8, 32, 32) + + def test_crop_4dstem_stop_zero(self): + """Test that stop=0 keeps all remaining elements.""" + dset = Dataset.from_array(np.random.rand(8, 8, 96, 96)) + cropped = dset.crop(crop_widths=((10, 0), (10, 0)), axes=(2, 3)) + assert cropped.shape == (8, 8, 86, 86) + def test_bin(self, sample_dataset_2d): """Test bin method.""" # Bin by factor of 2 From b9b3ed3d190321ff510d7b8657f6f197e62fb7a3 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 9 Mar 2026 22:32:50 -0700 Subject: [PATCH 171/335] maintain original api doc for some --- src/quantem/core/datastructures/dataset.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/quantem/core/datastructures/dataset.py b/src/quantem/core/datastructures/dataset.py index c1648d2a..2bf6e667 100644 --- a/src/quantem/core/datastructures/dataset.py +++ b/src/quantem/core/datastructures/dataset.py @@ -579,8 +579,8 @@ def bin( If True, modifies this dataset in-place. If False, returns a new dataset. reducer : {"sum", "mean"} - ``"sum"`` (default) preserves total counts; ``"mean"`` - averages over each block. + Reduction applied within each block. "sum" (default) preserves + counts; "mean" averages over each block. Returns ------- @@ -592,7 +592,7 @@ def bin( - Any remainder (shape % factor) is dropped on each binned axis. - Sampling is multiplied by the factor on each binned axis. - Origin is shifted to the center of the first block: - ``origin_new = origin_old + 0.5 * (factor - 1) * sampling_old`` + origin_new = origin_old + 0.5 * (factor - 1) * sampling_old Examples -------- From cbb3e549eda6ec3305ba294803bd049f91e65c77 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 9 Mar 2026 22:48:42 -0700 Subject: [PATCH 172/335] remove free memory part --- tests/datastructures/test_dataset.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/datastructures/test_dataset.py b/tests/datastructures/test_dataset.py index 9a222620..fc5c108d 100644 --- a/tests/datastructures/test_dataset.py +++ b/tests/datastructures/test_dataset.py @@ -241,13 +241,10 @@ def test_crop(self, sample_dataset_2d): """Test crop method.""" # Crop 1 pixel from each side cropped_dataset = sample_dataset_2d.crop(crop_widths=((1, 9), (1, 9))) - # Check shape assert cropped_dataset.shape == (8, 8) # Original (10, 10) - 1 from each side - # Check that the original dataset is unchanged assert sample_dataset_2d.shape == (10, 10) - # Test modify_in_place sample_dataset_2d.crop(crop_widths=((1, 9), (1, 9)), modify_in_place=True) assert sample_dataset_2d.shape == (8, 8) @@ -275,13 +272,10 @@ def test_bin(self, sample_dataset_2d): """Test bin method.""" # Bin by factor of 2 binned_dataset = sample_dataset_2d.bin(bin_factors=2) - # Check shape assert binned_dataset.shape == (5, 5) # Original (10, 10) / 2 - # Check that the original dataset is unchanged assert sample_dataset_2d.shape == (10, 10) - # Test modify_in_place sample_dataset_2d.bin(bin_factors=2, modify_in_place=True) assert sample_dataset_2d.shape == (5, 5) From e57def2621b49db8c4c13d0a7ee8c0eada0bd34b Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 9 Mar 2026 22:50:01 -0700 Subject: [PATCH 173/335] do not free memory part in API --- src/quantem/core/datastructures/dataset.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/quantem/core/datastructures/dataset.py b/src/quantem/core/datastructures/dataset.py index 2bf6e667..3dc3b925 100644 --- a/src/quantem/core/datastructures/dataset.py +++ b/src/quantem/core/datastructures/dataset.py @@ -454,12 +454,6 @@ def crop( ) -> Self | None: """Select a sub-region of the dataset along specified axes - Unlike regular slicing (``dset[64:192, ...]``), which creates a - view that keeps the full original array in memory, - ``modify_in_place=True`` replaces the array and frees the original. - This is important for large 4D-STEM datasets where memory is - limited. - Each ``crop_widths`` entry is a ``(start, stop)`` pair defining which elements to keep. A ``stop`` of ``0`` keeps everything from ``start`` to the end. From 1590facf878e5c6b3dd2884c9f768256b0abc046 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 10 Mar 2026 16:04:13 -0700 Subject: [PATCH 174/335] add Object FOV instead of FOV per PR feedback --- src/quantem/diffractive_imaging/ptychography_visualizations.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/quantem/diffractive_imaging/ptychography_visualizations.py b/src/quantem/diffractive_imaging/ptychography_visualizations.py index 05683abc..fe8f84bf 100644 --- a/src/quantem/diffractive_imaging/ptychography_visualizations.py +++ b/src/quantem/diffractive_imaging/ptychography_visualizations.py @@ -1,3 +1,4 @@ + import warnings from typing import Any, Literal @@ -886,7 +887,7 @@ def show_scan_positions( scan_sampling = self.dset.scan_sampling fov = scan_sampling * (np.array(scan_gpts) - 1) print(f"Scan grid: {scan_gpts[0]} x {scan_gpts[1]} positions") - print(f"FOV: {fov[0]:.2f} x {fov[1]:.2f} Å") + print(f"Object FOV: {fov[0]:.2f} x {fov[1]:.2f} Å") if conv_angle is not None and energy is not None: from quantem.core.utils.utils import electron_wavelength_angstrom From 922dc1291105fafaae227c5ef3e6072f6621dd45 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 10 Mar 2026 16:57:27 -0700 Subject: [PATCH 175/335] Verbose, if false will just start printing --- src/quantem/tomography/tomography.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 8b1ebd9f..677d3ca3 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -302,6 +302,9 @@ def reconstruct( ) self.logger.flush() + if not self.verbose: + if self.global_rank == 0: + print(f"Reconstruction Epoch {self.num_epochs} | Loss: {total_loss:.5e}, Consistency Loss: {consistency_loss:.5e}, Soft Constraint Loss: {epoch_soft_constraint_loss:.5e}") # --- Helper Functions --- From bad43ced39f4d9266ef0ebe0b225d0a8e41ddf06 Mon Sep 17 00:00:00 2001 From: quantem-bot Date: Mon, 16 Mar 2026 10:10:11 +0000 Subject: [PATCH 176/335] chore: update lock file --- uv.lock | 562 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 299 insertions(+), 263 deletions(-) diff --git a/uv.lock b/uv.lock index 2838cec7..05052285 100644 --- a/uv.lock +++ b/uv.lock @@ -284,75 +284,91 @@ wheels = [ [[package]] name = "charset-normalizer" -version = "3.4.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/35/02daf95b9cd686320bb622eb148792655c9412dbb9b67abb5694e5910a24/charset_normalizer-3.4.5.tar.gz", hash = "sha256:95adae7b6c42a6c5b5b559b1a99149f090a57128155daeea91732c8d970d8644", size = 134804, upload-time = "2026-03-06T06:03:19.46Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/9e/bcec3b22c64ecec47d39bf5167c2613efd41898c019dccd4183f6aa5d6a7/charset_normalizer-3.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:610f72c0ee565dfb8ae1241b666119582fdbfe7c0975c175be719f940e110694", size = 279531, upload-time = "2026-03-06T06:00:52.252Z" }, - { url = "https://files.pythonhosted.org/packages/58/12/81fd25f7e7078ab5d1eedbb0fac44be4904ae3370a3bf4533c8f2d159acd/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60d68e820af339df4ae8358c7a2e7596badeb61e544438e489035f9fbf3246a5", size = 188006, upload-time = "2026-03-06T06:00:53.8Z" }, - { url = "https://files.pythonhosted.org/packages/ae/6e/f2d30e8c27c1b0736a6520311982cf5286cfc7f6cac77d7bc1325e3a23f2/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:10b473fc8dca1c3ad8559985794815f06ca3fc71942c969129070f2c3cdf7281", size = 205085, upload-time = "2026-03-06T06:00:55.311Z" }, - { url = "https://files.pythonhosted.org/packages/d0/90/d12cefcb53b5931e2cf792a33718d7126efb116a320eaa0742c7059a95e4/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d4eb8ac7469b2a5d64b5b8c04f84d8bf3ad340f4514b98523805cbf46e3b3923", size = 200545, upload-time = "2026-03-06T06:00:56.532Z" }, - { url = "https://files.pythonhosted.org/packages/03/f4/44d3b830a20e89ff82a3134912d9a1cf6084d64f3b95dcad40f74449a654/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bcb3227c3d9aaf73eaaab1db7ccd80a8995c509ee9941e2aae060ca6e4e5d81", size = 193863, upload-time = "2026-03-06T06:00:57.823Z" }, - { url = "https://files.pythonhosted.org/packages/25/4b/f212119c18a6320a9d4a730d1b4057875cdeabf21b3614f76549042ef8a8/charset_normalizer-3.4.5-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:75ee9c1cce2911581a70a3c0919d8bccf5b1cbc9b0e5171400ec736b4b569497", size = 181827, upload-time = "2026-03-06T06:00:59.323Z" }, - { url = "https://files.pythonhosted.org/packages/74/00/b26158e48b425a202a92965f8069e8a63d9af1481dfa206825d7f74d2a3c/charset_normalizer-3.4.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d1401945cb77787dbd3af2446ff2d75912327c4c3a1526ab7955ecf8600687c", size = 191085, upload-time = "2026-03-06T06:01:00.546Z" }, - { url = "https://files.pythonhosted.org/packages/c4/c2/1c1737bf6fd40335fe53d28fe49afd99ee4143cc57a845e99635ce0b9b6d/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a45e504f5e1be0bd385935a8e1507c442349ca36f511a47057a71c9d1d6ea9e", size = 190688, upload-time = "2026-03-06T06:01:02.479Z" }, - { url = "https://files.pythonhosted.org/packages/5a/3d/abb5c22dc2ef493cd56522f811246a63c5427c08f3e3e50ab663de27fcf4/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e09f671a54ce70b79a1fc1dc6da3072b7ef7251fadb894ed92d9aa8218465a5f", size = 183077, upload-time = "2026-03-06T06:01:04.231Z" }, - { url = "https://files.pythonhosted.org/packages/44/33/5298ad4d419a58e25b3508e87f2758d1442ff00c2471f8e0403dab8edad5/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d01de5e768328646e6a3fa9e562706f8f6641708c115c62588aef2b941a4f88e", size = 206706, upload-time = "2026-03-06T06:01:05.773Z" }, - { url = "https://files.pythonhosted.org/packages/7b/17/51e7895ac0f87c3b91d276a449ef09f5532a7529818f59646d7a55089432/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:131716d6786ad5e3dc542f5cc6f397ba3339dc0fb87f87ac30e550e8987756af", size = 191665, upload-time = "2026-03-06T06:01:07.473Z" }, - { url = "https://files.pythonhosted.org/packages/90/8f/cce9adf1883e98906dbae380d769b4852bb0fa0004bc7d7a2243418d3ea8/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a374cc0b88aa710e8865dc1bd6edb3743c59f27830f0293ab101e4cf3ce9f85", size = 201950, upload-time = "2026-03-06T06:01:08.973Z" }, - { url = "https://files.pythonhosted.org/packages/08/ca/bce99cd5c397a52919e2769d126723f27a4c037130374c051c00470bcd38/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d31f0d1671e1534e395f9eb84a68e0fb670e1edb1fe819a9d7f564ae3bc4e53f", size = 195830, upload-time = "2026-03-06T06:01:10.155Z" }, - { url = "https://files.pythonhosted.org/packages/87/4f/2e3d023a06911f1281f97b8f036edc9872167036ca6f55cc874a0be6c12c/charset_normalizer-3.4.5-cp311-cp311-win32.whl", hash = "sha256:cace89841c0599d736d3d74a27bc5821288bb47c5441923277afc6059d7fbcb4", size = 132029, upload-time = "2026-03-06T06:01:11.706Z" }, - { url = "https://files.pythonhosted.org/packages/fe/1f/a853b73d386521fd44b7f67ded6b17b7b2367067d9106a5c4b44f9a34274/charset_normalizer-3.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:f8102ae93c0bc863b1d41ea0f4499c20a83229f52ed870850892df555187154a", size = 142404, upload-time = "2026-03-06T06:01:12.865Z" }, - { url = "https://files.pythonhosted.org/packages/b4/10/dba36f76b71c38e9d391abe0fd8a5b818790e053c431adecfc98c35cd2a9/charset_normalizer-3.4.5-cp311-cp311-win_arm64.whl", hash = "sha256:ed98364e1c262cf5f9363c3eca8c2df37024f52a8fa1180a3610014f26eac51c", size = 132796, upload-time = "2026-03-06T06:01:14.106Z" }, - { url = "https://files.pythonhosted.org/packages/9c/b6/9ee9c1a608916ca5feae81a344dffbaa53b26b90be58cc2159e3332d44ec/charset_normalizer-3.4.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed97c282ee4f994ef814042423a529df9497e3c666dca19be1d4cd1129dc7ade", size = 280976, upload-time = "2026-03-06T06:01:15.276Z" }, - { url = "https://files.pythonhosted.org/packages/f8/d8/a54f7c0b96f1df3563e9190f04daf981e365a9b397eedfdfb5dbef7e5c6c/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0294916d6ccf2d069727d65973c3a1ca477d68708db25fd758dd28b0827cff54", size = 189356, upload-time = "2026-03-06T06:01:16.511Z" }, - { url = "https://files.pythonhosted.org/packages/42/69/2bf7f76ce1446759a5787cb87d38f6a61eb47dbbdf035cfebf6347292a65/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dc57a0baa3eeedd99fafaef7511b5a6ef4581494e8168ee086031744e2679467", size = 206369, upload-time = "2026-03-06T06:01:17.853Z" }, - { url = "https://files.pythonhosted.org/packages/10/9c/949d1a46dab56b959d9a87272482195f1840b515a3380e39986989a893ae/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ed1a9a204f317ef879b32f9af507d47e49cd5e7f8e8d5d96358c98373314fc60", size = 203285, upload-time = "2026-03-06T06:01:19.473Z" }, - { url = "https://files.pythonhosted.org/packages/67/5c/ae30362a88b4da237d71ea214a8c7eb915db3eec941adda511729ac25fa2/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ad83b8f9379176c841f8865884f3514d905bcd2a9a3b210eaa446e7d2223e4d", size = 196274, upload-time = "2026-03-06T06:01:20.728Z" }, - { url = "https://files.pythonhosted.org/packages/b2/07/c9f2cb0e46cb6d64fdcc4f95953747b843bb2181bda678dc4e699b8f0f9a/charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:a118e2e0b5ae6b0120d5efa5f866e58f2bb826067a646431da4d6a2bdae7950e", size = 184715, upload-time = "2026-03-06T06:01:22.194Z" }, - { url = "https://files.pythonhosted.org/packages/36/64/6b0ca95c44fddf692cd06d642b28f63009d0ce325fad6e9b2b4d0ef86a52/charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:754f96058e61a5e22e91483f823e07df16416ce76afa4ebf306f8e1d1296d43f", size = 193426, upload-time = "2026-03-06T06:01:23.795Z" }, - { url = "https://files.pythonhosted.org/packages/50/bc/a730690d726403743795ca3f5bb2baf67838c5fea78236098f324b965e40/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0c300cefd9b0970381a46394902cd18eaf2aa00163f999590ace991989dcd0fc", size = 191780, upload-time = "2026-03-06T06:01:25.053Z" }, - { url = "https://files.pythonhosted.org/packages/97/4f/6c0bc9af68222b22951552d73df4532b5be6447cee32d58e7e8c74ecbb7b/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c108f8619e504140569ee7de3f97d234f0fbae338a7f9f360455071ef9855a95", size = 185805, upload-time = "2026-03-06T06:01:26.294Z" }, - { url = "https://files.pythonhosted.org/packages/dd/b9/a523fb9b0ee90814b503452b2600e4cbc118cd68714d57041564886e7325/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d1028de43596a315e2720a9849ee79007ab742c06ad8b45a50db8cdb7ed4a82a", size = 208342, upload-time = "2026-03-06T06:01:27.55Z" }, - { url = "https://files.pythonhosted.org/packages/4d/61/c59e761dee4464050713e50e27b58266cc8e209e518c0b378c1580c959ba/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:19092dde50335accf365cce21998a1c6dd8eafd42c7b226eb54b2747cdce2fac", size = 193661, upload-time = "2026-03-06T06:01:29.051Z" }, - { url = "https://files.pythonhosted.org/packages/1c/43/729fa30aad69783f755c5ad8649da17ee095311ca42024742701e202dc59/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4354e401eb6dab9aed3c7b4030514328a6c748d05e1c3e19175008ca7de84fb1", size = 204819, upload-time = "2026-03-06T06:01:30.298Z" }, - { url = "https://files.pythonhosted.org/packages/87/33/d9b442ce5a91b96fc0840455a9e49a611bbadae6122778d0a6a79683dd31/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a68766a3c58fde7f9aaa22b3786276f62ab2f594efb02d0a1421b6282e852e98", size = 198080, upload-time = "2026-03-06T06:01:31.478Z" }, - { url = "https://files.pythonhosted.org/packages/56/5a/b8b5a23134978ee9885cee2d6995f4c27cc41f9baded0a9685eabc5338f0/charset_normalizer-3.4.5-cp312-cp312-win32.whl", hash = "sha256:1827734a5b308b65ac54e86a618de66f935a4f63a8a462ff1e19a6788d6c2262", size = 132630, upload-time = "2026-03-06T06:01:33.056Z" }, - { url = "https://files.pythonhosted.org/packages/70/53/e44a4c07e8904500aec95865dc3f6464dc3586a039ef0df606eb3ac38e35/charset_normalizer-3.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:728c6a963dfab66ef865f49286e45239384249672cd598576765acc2a640a636", size = 142856, upload-time = "2026-03-06T06:01:34.489Z" }, - { url = "https://files.pythonhosted.org/packages/ea/aa/c5628f7cad591b1cf45790b7a61483c3e36cf41349c98af7813c483fd6e8/charset_normalizer-3.4.5-cp312-cp312-win_arm64.whl", hash = "sha256:75dfd1afe0b1647449e852f4fb428195a7ed0588947218f7ba929f6538487f02", size = 132982, upload-time = "2026-03-06T06:01:35.641Z" }, - { url = "https://files.pythonhosted.org/packages/f5/48/9f34ec4bb24aa3fdba1890c1bddb97c8a4be1bd84ef5c42ac2352563ad05/charset_normalizer-3.4.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ac59c15e3f1465f722607800c68713f9fbc2f672b9eb649fe831da4019ae9b23", size = 280788, upload-time = "2026-03-06T06:01:37.126Z" }, - { url = "https://files.pythonhosted.org/packages/0e/09/6003e7ffeb90cc0560da893e3208396a44c210c5ee42efff539639def59b/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:165c7b21d19365464e8f70e5ce5e12524c58b48c78c1f5a57524603c1ab003f8", size = 188890, upload-time = "2026-03-06T06:01:38.73Z" }, - { url = "https://files.pythonhosted.org/packages/42/1e/02706edf19e390680daa694d17e2b8eab4b5f7ac285e2a51168b4b22ee6b/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:28269983f25a4da0425743d0d257a2d6921ea7d9b83599d4039486ec5b9f911d", size = 206136, upload-time = "2026-03-06T06:01:40.016Z" }, - { url = "https://files.pythonhosted.org/packages/c7/87/942c3def1b37baf3cf786bad01249190f3ca3d5e63a84f831e704977de1f/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d27ce22ec453564770d29d03a9506d449efbb9fa13c00842262b2f6801c48cce", size = 202551, upload-time = "2026-03-06T06:01:41.522Z" }, - { url = "https://files.pythonhosted.org/packages/94/0a/af49691938dfe175d71b8a929bd7e4ace2809c0c5134e28bc535660d5262/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0625665e4ebdddb553ab185de5db7054393af8879fb0c87bd5690d14379d6819", size = 195572, upload-time = "2026-03-06T06:01:43.208Z" }, - { url = "https://files.pythonhosted.org/packages/20/ea/dfb1792a8050a8e694cfbde1570ff97ff74e48afd874152d38163d1df9ae/charset_normalizer-3.4.5-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:c23eb3263356d94858655b3e63f85ac5d50970c6e8febcdde7830209139cc37d", size = 184438, upload-time = "2026-03-06T06:01:44.755Z" }, - { url = "https://files.pythonhosted.org/packages/72/12/c281e2067466e3ddd0595bfaea58a6946765ace5c72dfa3edc2f5f118026/charset_normalizer-3.4.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e6302ca4ae283deb0af68d2fbf467474b8b6aedcd3dab4db187e07f94c109763", size = 193035, upload-time = "2026-03-06T06:01:46.051Z" }, - { url = "https://files.pythonhosted.org/packages/ba/4f/3792c056e7708e10464bad0438a44708886fb8f92e3c3d29ec5e2d964d42/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e51ae7d81c825761d941962450f50d041db028b7278e7b08930b4541b3e45cb9", size = 191340, upload-time = "2026-03-06T06:01:47.547Z" }, - { url = "https://files.pythonhosted.org/packages/e7/86/80ddba897127b5c7a9bccc481b0cd36c8fefa485d113262f0fe4332f0bf4/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:597d10dec876923e5c59e48dbd366e852eacb2b806029491d307daea6b917d7c", size = 185464, upload-time = "2026-03-06T06:01:48.764Z" }, - { url = "https://files.pythonhosted.org/packages/4d/00/b5eff85ba198faacab83e0e4b6f0648155f072278e3b392a82478f8b988b/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5cffde4032a197bd3b42fd0b9509ec60fb70918d6970e4cc773f20fc9180ca67", size = 208014, upload-time = "2026-03-06T06:01:50.371Z" }, - { url = "https://files.pythonhosted.org/packages/c8/11/d36f70be01597fd30850dde8a1269ebc8efadd23ba5785808454f2389bde/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2da4eedcb6338e2321e831a0165759c0c620e37f8cd044a263ff67493be8ffb3", size = 193297, upload-time = "2026-03-06T06:01:51.933Z" }, - { url = "https://files.pythonhosted.org/packages/1a/1d/259eb0a53d4910536c7c2abb9cb25f4153548efb42800c6a9456764649c0/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:65a126fb4b070d05340a84fc709dd9e7c75d9b063b610ece8a60197a291d0adf", size = 204321, upload-time = "2026-03-06T06:01:53.887Z" }, - { url = "https://files.pythonhosted.org/packages/84/31/faa6c5b9d3688715e1ed1bb9d124c384fe2fc1633a409e503ffe1c6398c1/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7a80a9242963416bd81f99349d5f3fce1843c303bd404f204918b6d75a75fd6", size = 197509, upload-time = "2026-03-06T06:01:56.439Z" }, - { url = "https://files.pythonhosted.org/packages/fd/a5/c7d9dd1503ffc08950b3260f5d39ec2366dd08254f0900ecbcf3a6197c7c/charset_normalizer-3.4.5-cp313-cp313-win32.whl", hash = "sha256:f1d725b754e967e648046f00c4facc42d414840f5ccc670c5670f59f83693e4f", size = 132284, upload-time = "2026-03-06T06:01:57.812Z" }, - { url = "https://files.pythonhosted.org/packages/b9/0f/57072b253af40c8aa6636e6de7d75985624c1eb392815b2f934199340a89/charset_normalizer-3.4.5-cp313-cp313-win_amd64.whl", hash = "sha256:e37bd100d2c5d3ba35db9c7c5ba5a9228cbcffe5c4778dc824b164e5257813d7", size = 142630, upload-time = "2026-03-06T06:01:59.062Z" }, - { url = "https://files.pythonhosted.org/packages/31/41/1c4b7cc9f13bd9d369ce3bc993e13d374ce25fa38a2663644283ecf422c1/charset_normalizer-3.4.5-cp313-cp313-win_arm64.whl", hash = "sha256:93b3b2cc5cf1b8743660ce77a4f45f3f6d1172068207c1defc779a36eea6bb36", size = 133254, upload-time = "2026-03-06T06:02:00.281Z" }, - { url = "https://files.pythonhosted.org/packages/43/be/0f0fd9bb4a7fa4fb5067fb7d9ac693d4e928d306f80a0d02bde43a7c4aee/charset_normalizer-3.4.5-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8197abe5ca1ffb7d91e78360f915eef5addff270f8a71c1fc5be24a56f3e4873", size = 280232, upload-time = "2026-03-06T06:02:01.508Z" }, - { url = "https://files.pythonhosted.org/packages/28/02/983b5445e4bef49cd8c9da73a8e029f0825f39b74a06d201bfaa2e55142a/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2aecdb364b8a1802afdc7f9327d55dad5366bc97d8502d0f5854e50712dbc5f", size = 189688, upload-time = "2026-03-06T06:02:02.857Z" }, - { url = "https://files.pythonhosted.org/packages/d0/88/152745c5166437687028027dc080e2daed6fe11cfa95a22f4602591c42db/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a66aa5022bf81ab4b1bebfb009db4fd68e0c6d4307a1ce5ef6a26e5878dfc9e4", size = 206833, upload-time = "2026-03-06T06:02:05.127Z" }, - { url = "https://files.pythonhosted.org/packages/cb/0f/ebc15c8b02af2f19be9678d6eed115feeeccc45ce1f4b098d986c13e8769/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d77f97e515688bd615c1d1f795d540f32542d514242067adcb8ef532504cb9ee", size = 202879, upload-time = "2026-03-06T06:02:06.446Z" }, - { url = "https://files.pythonhosted.org/packages/38/9c/71336bff6934418dc8d1e8a1644176ac9088068bc571da612767619c97b3/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01a1ed54b953303ca7e310fafe0fe347aab348bd81834a0bcd602eb538f89d66", size = 195764, upload-time = "2026-03-06T06:02:08.763Z" }, - { url = "https://files.pythonhosted.org/packages/b7/95/ce92fde4f98615661871bc282a856cf9b8a15f686ba0af012984660d480b/charset_normalizer-3.4.5-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:b2d37d78297b39a9eb9eb92c0f6df98c706467282055419df141389b23f93362", size = 183728, upload-time = "2026-03-06T06:02:10.137Z" }, - { url = "https://files.pythonhosted.org/packages/1c/e7/f5b4588d94e747ce45ae680f0f242bc2d98dbd4eccfab73e6160b6893893/charset_normalizer-3.4.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e71bbb595973622b817c042bd943c3f3667e9c9983ce3d205f973f486fec98a7", size = 192937, upload-time = "2026-03-06T06:02:11.663Z" }, - { url = "https://files.pythonhosted.org/packages/f9/29/9d94ed6b929bf9f48bf6ede6e7474576499f07c4c5e878fb186083622716/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4cd966c2559f501c6fd69294d082c2934c8dd4719deb32c22961a5ac6db0df1d", size = 192040, upload-time = "2026-03-06T06:02:13.489Z" }, - { url = "https://files.pythonhosted.org/packages/15/d2/1a093a1cf827957f9445f2fe7298bcc16f8fc5e05c1ed2ad1af0b239035e/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d5e52d127045d6ae01a1e821acfad2f3a1866c54d0e837828538fabe8d9d1bd6", size = 184107, upload-time = "2026-03-06T06:02:14.83Z" }, - { url = "https://files.pythonhosted.org/packages/0f/7d/82068ce16bd36135df7b97f6333c5d808b94e01d4599a682e2337ed5fd14/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:30a2b1a48478c3428d047ed9690d57c23038dac838a87ad624c85c0a78ebeb39", size = 208310, upload-time = "2026-03-06T06:02:16.165Z" }, - { url = "https://files.pythonhosted.org/packages/84/4e/4dfb52307bb6af4a5c9e73e482d171b81d36f522b21ccd28a49656baa680/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d8ed79b8f6372ca4254955005830fd61c1ccdd8c0fac6603e2c145c61dd95db6", size = 192918, upload-time = "2026-03-06T06:02:18.144Z" }, - { url = "https://files.pythonhosted.org/packages/08/a4/159ff7da662cf7201502ca89980b8f06acf3e887b278956646a8aeb178ab/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:c5af897b45fa606b12464ccbe0014bbf8c09191e0a66aab6aa9d5cf6e77e0c94", size = 204615, upload-time = "2026-03-06T06:02:19.821Z" }, - { url = "https://files.pythonhosted.org/packages/d6/62/0dd6172203cb6b429ffffc9935001fde42e5250d57f07b0c28c6046deb6b/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1088345bcc93c58d8d8f3d783eca4a6e7a7752bbff26c3eee7e73c597c191c2e", size = 197784, upload-time = "2026-03-06T06:02:21.86Z" }, - { url = "https://files.pythonhosted.org/packages/c7/5e/1aab5cb737039b9c59e63627dc8bbc0d02562a14f831cc450e5f91d84ce1/charset_normalizer-3.4.5-cp314-cp314-win32.whl", hash = "sha256:ee57b926940ba00bca7ba7041e665cc956e55ef482f851b9b65acb20d867e7a2", size = 133009, upload-time = "2026-03-06T06:02:23.289Z" }, - { url = "https://files.pythonhosted.org/packages/40/65/e7c6c77d7aaa4c0d7974f2e403e17f0ed2cb0fc135f77d686b916bf1eead/charset_normalizer-3.4.5-cp314-cp314-win_amd64.whl", hash = "sha256:4481e6da1830c8a1cc0b746b47f603b653dadb690bcd851d039ffaefe70533aa", size = 143511, upload-time = "2026-03-06T06:02:26.195Z" }, - { url = "https://files.pythonhosted.org/packages/ba/91/52b0841c71f152f563b8e072896c14e3d83b195c188b338d3cc2e582d1d4/charset_normalizer-3.4.5-cp314-cp314-win_arm64.whl", hash = "sha256:97ab7787092eb9b50fb47fa04f24c75b768a606af1bcba1957f07f128a7219e4", size = 133775, upload-time = "2026-03-06T06:02:27.473Z" }, - { url = "https://files.pythonhosted.org/packages/c5/60/3a621758945513adfd4db86827a5bafcc615f913dbd0b4c2ed64a65731be/charset_normalizer-3.4.5-py3-none-any.whl", hash = "sha256:9db5e3fcdcee89a78c04dffb3fe33c79f77bd741a624946db2591c81b2fc85b0", size = 55455, upload-time = "2026-03-06T06:03:17.827Z" }, +version = "3.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/60/e3bec1881450851b087e301bedc3daa9377a4d45f1c26aa90b0b235e38aa/charset_normalizer-3.4.6.tar.gz", hash = "sha256:1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6", size = 143363, upload-time = "2026-03-15T18:53:25.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/28/ff6f234e628a2de61c458be2779cb182bc03f6eec12200d4a525bbfc9741/charset_normalizer-3.4.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:82060f995ab5003a2d6e0f4ad29065b7672b6593c8c63559beefe5b443242c3e", size = 293582, upload-time = "2026-03-15T18:50:25.454Z" }, + { url = "https://files.pythonhosted.org/packages/1c/b7/b1a117e5385cbdb3205f6055403c2a2a220c5ea80b8716c324eaf75c5c95/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60c74963d8350241a79cb8feea80e54d518f72c26db618862a8f53e5023deaf9", size = 197240, upload-time = "2026-03-15T18:50:27.196Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5f/2574f0f09f3c3bc1b2f992e20bce6546cb1f17e111c5be07308dc5427956/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6e4333fb15c83f7d1482a76d45a0818897b3d33f00efd215528ff7c51b8e35d", size = 217363, upload-time = "2026-03-15T18:50:28.601Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d1/0ae20ad77bc949ddd39b51bf383b6ca932f2916074c95cad34ae465ab71f/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bc72863f4d9aba2e8fd9085e63548a324ba706d2ea2c83b260da08a59b9482de", size = 212994, upload-time = "2026-03-15T18:50:30.102Z" }, + { url = "https://files.pythonhosted.org/packages/60/ac/3233d262a310c1b12633536a07cde5ddd16985e6e7e238e9f3f9423d8eb9/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9cc4fc6c196d6a8b76629a70ddfcd4635a6898756e2d9cac5565cf0654605d73", size = 204697, upload-time = "2026-03-15T18:50:31.654Z" }, + { url = "https://files.pythonhosted.org/packages/25/3c/8a18fc411f085b82303cfb7154eed5bd49c77035eb7608d049468b53f87c/charset_normalizer-3.4.6-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:0c173ce3a681f309f31b87125fecec7a5d1347261ea11ebbb856fa6006b23c8c", size = 191673, upload-time = "2026-03-15T18:50:33.433Z" }, + { url = "https://files.pythonhosted.org/packages/ff/a7/11cfe61d6c5c5c7438d6ba40919d0306ed83c9ab957f3d4da2277ff67836/charset_normalizer-3.4.6-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c907cdc8109f6c619e6254212e794d6548373cc40e1ec75e6e3823d9135d29cc", size = 201120, upload-time = "2026-03-15T18:50:35.105Z" }, + { url = "https://files.pythonhosted.org/packages/b5/10/cf491fa1abd47c02f69687046b896c950b92b6cd7337a27e6548adbec8e4/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:404a1e552cf5b675a87f0651f8b79f5f1e6fd100ee88dc612f89aa16abd4486f", size = 200911, upload-time = "2026-03-15T18:50:36.819Z" }, + { url = "https://files.pythonhosted.org/packages/28/70/039796160b48b18ed466fde0af84c1b090c4e288fae26cd674ad04a2d703/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e3c701e954abf6fc03a49f7c579cc80c2c6cc52525340ca3186c41d3f33482ef", size = 192516, upload-time = "2026-03-15T18:50:38.228Z" }, + { url = "https://files.pythonhosted.org/packages/ff/34/c56f3223393d6ff3124b9e78f7de738047c2d6bc40a4f16ac0c9d7a1cb3c/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7a6967aaf043bceabab5412ed6bd6bd26603dae84d5cb75bf8d9a74a4959d398", size = 218795, upload-time = "2026-03-15T18:50:39.664Z" }, + { url = "https://files.pythonhosted.org/packages/e8/3b/ce2d4f86c5282191a041fdc5a4ce18f1c6bd40a5bd1f74cf8625f08d51c1/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5feb91325bbceade6afab43eb3b508c63ee53579fe896c77137ded51c6b6958e", size = 201833, upload-time = "2026-03-15T18:50:41.552Z" }, + { url = "https://files.pythonhosted.org/packages/3b/9b/b6a9f76b0fd7c5b5ec58b228ff7e85095370282150f0bd50b3126f5506d6/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f820f24b09e3e779fe84c3c456cb4108a7aa639b0d1f02c28046e11bfcd088ed", size = 213920, upload-time = "2026-03-15T18:50:43.33Z" }, + { url = "https://files.pythonhosted.org/packages/ae/98/7bc23513a33d8172365ed30ee3a3b3fe1ece14a395e5fc94129541fc6003/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b35b200d6a71b9839a46b9b7fff66b6638bb52fc9658aa58796b0326595d3021", size = 206951, upload-time = "2026-03-15T18:50:44.789Z" }, + { url = "https://files.pythonhosted.org/packages/32/73/c0b86f3d1458468e11aec870e6b3feac931facbe105a894b552b0e518e79/charset_normalizer-3.4.6-cp311-cp311-win32.whl", hash = "sha256:9ca4c0b502ab399ef89248a2c84c54954f77a070f28e546a85e91da627d1301e", size = 143703, upload-time = "2026-03-15T18:50:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/c6/e3/76f2facfe8eddee0bbd38d2594e709033338eae44ebf1738bcefe0a06185/charset_normalizer-3.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:a9e68c9d88823b274cf1e72f28cb5dc89c990edf430b0bfd3e2fb0785bfeabf4", size = 153857, upload-time = "2026-03-15T18:50:47.563Z" }, + { url = "https://files.pythonhosted.org/packages/e2/dc/9abe19c9b27e6cd3636036b9d1b387b78c40dedbf0b47f9366737684b4b0/charset_normalizer-3.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:97d0235baafca5f2b09cf332cc275f021e694e8362c6bb9c96fc9a0eb74fc316", size = 142751, upload-time = "2026-03-15T18:50:49.234Z" }, + { url = "https://files.pythonhosted.org/packages/e5/62/c0815c992c9545347aeea7859b50dc9044d147e2e7278329c6e02ac9a616/charset_normalizer-3.4.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ef7fedc7a6ecbe99969cd09632516738a97eeb8bd7258bf8a0f23114c057dab", size = 295154, upload-time = "2026-03-15T18:50:50.88Z" }, + { url = "https://files.pythonhosted.org/packages/a8/37/bdca6613c2e3c58c7421891d80cc3efa1d32e882f7c4a7ee6039c3fc951a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4ea868bc28109052790eb2b52a9ab33f3aa7adc02f96673526ff47419490e21", size = 199191, upload-time = "2026-03-15T18:50:52.658Z" }, + { url = "https://files.pythonhosted.org/packages/6c/92/9934d1bbd69f7f398b38c5dae1cbf9cc672e7c34a4adf7b17c0a9c17d15d/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:836ab36280f21fc1a03c99cd05c6b7af70d2697e374c7af0b61ed271401a72a2", size = 218674, upload-time = "2026-03-15T18:50:54.102Z" }, + { url = "https://files.pythonhosted.org/packages/af/90/25f6ab406659286be929fd89ab0e78e38aa183fc374e03aa3c12d730af8a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f1ce721c8a7dfec21fcbdfe04e8f68174183cf4e8188e0645e92aa23985c57ff", size = 215259, upload-time = "2026-03-15T18:50:55.616Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ef/79a463eb0fff7f96afa04c1d4c51f8fc85426f918db467854bfb6a569ce3/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e28d62a8fc7a1fa411c43bd65e346f3bce9716dc51b897fbe930c5987b402d5", size = 207276, upload-time = "2026-03-15T18:50:57.054Z" }, + { url = "https://files.pythonhosted.org/packages/f7/72/d0426afec4b71dc159fa6b4e68f868cd5a3ecd918fec5813a15d292a7d10/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:530d548084c4a9f7a16ed4a294d459b4f229db50df689bfe92027452452943a0", size = 195161, upload-time = "2026-03-15T18:50:58.686Z" }, + { url = "https://files.pythonhosted.org/packages/bf/18/c82b06a68bfcb6ce55e508225d210c7e6a4ea122bfc0748892f3dc4e8e11/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30f445ae60aad5e1f8bdbb3108e39f6fbc09f4ea16c815c66578878325f8f15a", size = 203452, upload-time = "2026-03-15T18:51:00.196Z" }, + { url = "https://files.pythonhosted.org/packages/44/d6/0c25979b92f8adafdbb946160348d8d44aa60ce99afdc27df524379875cb/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ac2393c73378fea4e52aa56285a3d64be50f1a12395afef9cce47772f60334c2", size = 202272, upload-time = "2026-03-15T18:51:01.703Z" }, + { url = "https://files.pythonhosted.org/packages/2e/3d/7fea3e8fe84136bebbac715dd1221cc25c173c57a699c030ab9b8900cbb7/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:90ca27cd8da8118b18a52d5f547859cc1f8354a00cd1e8e5120df3e30d6279e5", size = 195622, upload-time = "2026-03-15T18:51:03.526Z" }, + { url = "https://files.pythonhosted.org/packages/57/8a/d6f7fd5cb96c58ef2f681424fbca01264461336d2a7fc875e4446b1f1346/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e5a94886bedca0f9b78fecd6afb6629142fd2605aa70a125d49f4edc6037ee6", size = 220056, upload-time = "2026-03-15T18:51:05.269Z" }, + { url = "https://files.pythonhosted.org/packages/16/50/478cdda782c8c9c3fb5da3cc72dd7f331f031e7f1363a893cdd6ca0f8de0/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:695f5c2823691a25f17bc5d5ffe79fa90972cc34b002ac6c843bb8a1720e950d", size = 203751, upload-time = "2026-03-15T18:51:06.858Z" }, + { url = "https://files.pythonhosted.org/packages/75/fc/cc2fcac943939c8e4d8791abfa139f685e5150cae9f94b60f12520feaa9b/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:231d4da14bcd9301310faf492051bee27df11f2bc7549bc0bb41fef11b82daa2", size = 216563, upload-time = "2026-03-15T18:51:08.564Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b7/a4add1d9a5f68f3d037261aecca83abdb0ab15960a3591d340e829b37298/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a056d1ad2633548ca18ffa2f85c202cfb48b68615129143915b8dc72a806a923", size = 209265, upload-time = "2026-03-15T18:51:10.312Z" }, + { url = "https://files.pythonhosted.org/packages/6c/18/c094561b5d64a24277707698e54b7f67bd17a4f857bbfbb1072bba07c8bf/charset_normalizer-3.4.6-cp312-cp312-win32.whl", hash = "sha256:c2274ca724536f173122f36c98ce188fd24ce3dad886ec2b7af859518ce008a4", size = 144229, upload-time = "2026-03-15T18:51:11.694Z" }, + { url = "https://files.pythonhosted.org/packages/ab/20/0567efb3a8fd481b8f34f739ebddc098ed062a59fed41a8d193a61939e8f/charset_normalizer-3.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:c8ae56368f8cc97c7e40a7ee18e1cedaf8e780cd8bc5ed5ac8b81f238614facb", size = 154277, upload-time = "2026-03-15T18:51:13.004Z" }, + { url = "https://files.pythonhosted.org/packages/15/57/28d79b44b51933119e21f65479d0864a8d5893e494cf5daab15df0247c17/charset_normalizer-3.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:899d28f422116b08be5118ef350c292b36fc15ec2daeb9ea987c89281c7bb5c4", size = 142817, upload-time = "2026-03-15T18:51:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/1e/1d/4fdabeef4e231153b6ed7567602f3b68265ec4e5b76d6024cf647d43d981/charset_normalizer-3.4.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:11afb56037cbc4b1555a34dd69151e8e069bee82e613a73bef6e714ce733585f", size = 294823, upload-time = "2026-03-15T18:51:15.755Z" }, + { url = "https://files.pythonhosted.org/packages/47/7b/20e809b89c69d37be748d98e84dce6820bf663cf19cf6b942c951a3e8f41/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:423fb7e748a08f854a08a222b983f4df1912b1daedce51a72bd24fe8f26a1843", size = 198527, upload-time = "2026-03-15T18:51:17.177Z" }, + { url = "https://files.pythonhosted.org/packages/37/a6/4f8d27527d59c039dce6f7622593cdcd3d70a8504d87d09eb11e9fdc6062/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d73beaac5e90173ac3deb9928a74763a6d230f494e4bfb422c217a0ad8e629bf", size = 218388, upload-time = "2026-03-15T18:51:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/f6/9b/4770ccb3e491a9bacf1c46cc8b812214fe367c86a96353ccc6daf87b01ec/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d60377dce4511655582e300dc1e5a5f24ba0cb229005a1d5c8d0cb72bb758ab8", size = 214563, upload-time = "2026-03-15T18:51:20.374Z" }, + { url = "https://files.pythonhosted.org/packages/2b/58/a199d245894b12db0b957d627516c78e055adc3a0d978bc7f65ddaf7c399/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:530e8cebeea0d76bdcf93357aa5e41336f48c3dc709ac52da2bb167c5b8271d9", size = 206587, upload-time = "2026-03-15T18:51:21.807Z" }, + { url = "https://files.pythonhosted.org/packages/7e/70/3def227f1ec56f5c69dfc8392b8bd63b11a18ca8178d9211d7cc5e5e4f27/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:a26611d9987b230566f24a0a125f17fe0de6a6aff9f25c9f564aaa2721a5fb88", size = 194724, upload-time = "2026-03-15T18:51:23.508Z" }, + { url = "https://files.pythonhosted.org/packages/58/ab/9318352e220c05efd31c2779a23b50969dc94b985a2efa643ed9077bfca5/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:34315ff4fc374b285ad7f4a0bf7dcbfe769e1b104230d40f49f700d4ab6bbd84", size = 202956, upload-time = "2026-03-15T18:51:25.239Z" }, + { url = "https://files.pythonhosted.org/packages/75/13/f3550a3ac25b70f87ac98c40d3199a8503676c2f1620efbf8d42095cfc40/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ddd609f9e1af8c7bd6e2aca279c931aefecd148a14402d4e368f3171769fd", size = 201923, upload-time = "2026-03-15T18:51:26.682Z" }, + { url = "https://files.pythonhosted.org/packages/1b/db/c5c643b912740b45e8eec21de1bbab8e7fc085944d37e1e709d3dcd9d72f/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:80d0a5615143c0b3225e5e3ef22c8d5d51f3f72ce0ea6fb84c943546c7b25b6c", size = 195366, upload-time = "2026-03-15T18:51:28.129Z" }, + { url = "https://files.pythonhosted.org/packages/5a/67/3b1c62744f9b2448443e0eb160d8b001c849ec3fef591e012eda6484787c/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:92734d4d8d187a354a556626c221cd1a892a4e0802ccb2af432a1d85ec012194", size = 219752, upload-time = "2026-03-15T18:51:29.556Z" }, + { url = "https://files.pythonhosted.org/packages/f6/98/32ffbaf7f0366ffb0445930b87d103f6b406bc2c271563644bde8a2b1093/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:613f19aa6e082cf96e17e3ffd89383343d0d589abda756b7764cf78361fd41dc", size = 203296, upload-time = "2026-03-15T18:51:30.921Z" }, + { url = "https://files.pythonhosted.org/packages/41/12/5d308c1bbe60cabb0c5ef511574a647067e2a1f631bc8634fcafaccd8293/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2b1a63e8224e401cafe7739f77efd3f9e7f5f2026bda4aead8e59afab537784f", size = 215956, upload-time = "2026-03-15T18:51:32.399Z" }, + { url = "https://files.pythonhosted.org/packages/53/e9/5f85f6c5e20669dbe56b165c67b0260547dea97dba7e187938833d791687/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6cceb5473417d28edd20c6c984ab6fee6c6267d38d906823ebfe20b03d607dc2", size = 208652, upload-time = "2026-03-15T18:51:34.214Z" }, + { url = "https://files.pythonhosted.org/packages/f1/11/897052ea6af56df3eef3ca94edafee410ca699ca0c7b87960ad19932c55e/charset_normalizer-3.4.6-cp313-cp313-win32.whl", hash = "sha256:d7de2637729c67d67cf87614b566626057e95c303bc0a55ffe391f5205e7003d", size = 143940, upload-time = "2026-03-15T18:51:36.15Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5c/724b6b363603e419829f561c854b87ed7c7e31231a7908708ac086cdf3e2/charset_normalizer-3.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:572d7c822caf521f0525ba1bce1a622a0b85cf47ffbdae6c9c19e3b5ac3c4389", size = 154101, upload-time = "2026-03-15T18:51:37.876Z" }, + { url = "https://files.pythonhosted.org/packages/01/a5/7abf15b4c0968e47020f9ca0935fb3274deb87cb288cd187cad92e8cdffd/charset_normalizer-3.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a4474d924a47185a06411e0064b803c68be044be2d60e50e8bddcc2649957c1f", size = 143109, upload-time = "2026-03-15T18:51:39.565Z" }, + { url = "https://files.pythonhosted.org/packages/25/6f/ffe1e1259f384594063ea1869bfb6be5cdb8bc81020fc36c3636bc8302a1/charset_normalizer-3.4.6-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9cc6e6d9e571d2f863fa77700701dae73ed5f78881efc8b3f9a4398772ff53e8", size = 294458, upload-time = "2026-03-15T18:51:41.134Z" }, + { url = "https://files.pythonhosted.org/packages/56/60/09bb6c13a8c1016c2ed5c6a6488e4ffef506461aa5161662bd7636936fb1/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5960d965e67165d75b7c7ffc60a83ec5abfc5c11b764ec13ea54fbef8b4421", size = 199277, upload-time = "2026-03-15T18:51:42.953Z" }, + { url = "https://files.pythonhosted.org/packages/00/50/dcfbb72a5138bbefdc3332e8d81a23494bf67998b4b100703fd15fa52d81/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b3694e3f87f8ac7ce279d4355645b3c878d24d1424581b46282f24b92f5a4ae2", size = 218758, upload-time = "2026-03-15T18:51:44.339Z" }, + { url = "https://files.pythonhosted.org/packages/03/b3/d79a9a191bb75f5aa81f3aaaa387ef29ce7cb7a9e5074ba8ea095cc073c2/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5d11595abf8dd942a77883a39d81433739b287b6aa71620f15164f8096221b30", size = 215299, upload-time = "2026-03-15T18:51:45.871Z" }, + { url = "https://files.pythonhosted.org/packages/76/7e/bc8911719f7084f72fd545f647601ea3532363927f807d296a8c88a62c0d/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7bda6eebafd42133efdca535b04ccb338ab29467b3f7bf79569883676fc628db", size = 206811, upload-time = "2026-03-15T18:51:47.308Z" }, + { url = "https://files.pythonhosted.org/packages/e2/40/c430b969d41dda0c465aa36cc7c2c068afb67177bef50905ac371b28ccc7/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:bbc8c8650c6e51041ad1be191742b8b421d05bbd3410f43fa2a00c8db87678e8", size = 193706, upload-time = "2026-03-15T18:51:48.849Z" }, + { url = "https://files.pythonhosted.org/packages/48/15/e35e0590af254f7df984de1323640ef375df5761f615b6225ba8deb9799a/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22c6f0c2fbc31e76c3b8a86fba1a56eda6166e238c29cdd3d14befdb4a4e4815", size = 202706, upload-time = "2026-03-15T18:51:50.257Z" }, + { url = "https://files.pythonhosted.org/packages/5e/bd/f736f7b9cc5e93a18b794a50346bb16fbfd6b37f99e8f306f7951d27c17c/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7edbed096e4a4798710ed6bc75dcaa2a21b68b6c356553ac4823c3658d53743a", size = 202497, upload-time = "2026-03-15T18:51:52.012Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ba/2cc9e3e7dfdf7760a6ed8da7446d22536f3d0ce114ac63dee2a5a3599e62/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7f9019c9cb613f084481bd6a100b12e1547cf2efe362d873c2e31e4035a6fa43", size = 193511, upload-time = "2026-03-15T18:51:53.723Z" }, + { url = "https://files.pythonhosted.org/packages/9e/cb/5be49b5f776e5613be07298c80e1b02a2d900f7a7de807230595c85a8b2e/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:58c948d0d086229efc484fe2f30c2d382c86720f55cd9bc33591774348ad44e0", size = 220133, upload-time = "2026-03-15T18:51:55.333Z" }, + { url = "https://files.pythonhosted.org/packages/83/43/99f1b5dad345accb322c80c7821071554f791a95ee50c1c90041c157ae99/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:419a9d91bd238052642a51938af8ac05da5b3343becde08d5cdeab9046df9ee1", size = 203035, upload-time = "2026-03-15T18:51:56.736Z" }, + { url = "https://files.pythonhosted.org/packages/87/9a/62c2cb6a531483b55dddff1a68b3d891a8b498f3ca555fbcf2978e804d9d/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5273b9f0b5835ff0350c0828faea623c68bfa65b792720c453e22b25cc72930f", size = 216321, upload-time = "2026-03-15T18:51:58.17Z" }, + { url = "https://files.pythonhosted.org/packages/6e/79/94a010ff81e3aec7c293eb82c28f930918e517bc144c9906a060844462eb/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0e901eb1049fdb80f5bd11ed5ea1e498ec423102f7a9b9e4645d5b8204ff2815", size = 208973, upload-time = "2026-03-15T18:51:59.998Z" }, + { url = "https://files.pythonhosted.org/packages/2a/57/4ecff6d4ec8585342f0c71bc03efaa99cb7468f7c91a57b105bcd561cea8/charset_normalizer-3.4.6-cp314-cp314-win32.whl", hash = "sha256:b4ff1d35e8c5bd078be89349b6f3a845128e685e751b6ea1169cf2160b344c4d", size = 144610, upload-time = "2026-03-15T18:52:02.213Z" }, + { url = "https://files.pythonhosted.org/packages/80/94/8434a02d9d7f168c25767c64671fead8d599744a05d6a6c877144c754246/charset_normalizer-3.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:74119174722c4349af9708993118581686f343adc1c8c9c007d59be90d077f3f", size = 154962, upload-time = "2026-03-15T18:52:03.658Z" }, + { url = "https://files.pythonhosted.org/packages/46/4c/48f2cdbfd923026503dfd67ccea45c94fd8fe988d9056b468579c66ed62b/charset_normalizer-3.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:e5bcc1a1ae744e0bb59641171ae53743760130600da8db48cbb6e4918e186e4e", size = 143595, upload-time = "2026-03-15T18:52:05.123Z" }, + { url = "https://files.pythonhosted.org/packages/31/93/8878be7569f87b14f1d52032946131bcb6ebbd8af3e20446bc04053dc3f1/charset_normalizer-3.4.6-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ad8faf8df23f0378c6d527d8b0b15ea4a2e23c89376877c598c4870d1b2c7866", size = 314828, upload-time = "2026-03-15T18:52:06.831Z" }, + { url = "https://files.pythonhosted.org/packages/06/b6/fae511ca98aac69ecc35cde828b0a3d146325dd03d99655ad38fc2cc3293/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5ea69428fa1b49573eef0cc44a1d43bebd45ad0c611eb7d7eac760c7ae771bc", size = 208138, upload-time = "2026-03-15T18:52:08.239Z" }, + { url = "https://files.pythonhosted.org/packages/54/57/64caf6e1bf07274a1e0b7c160a55ee9e8c9ec32c46846ce59b9c333f7008/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:06a7e86163334edfc5d20fe104db92fcd666e5a5df0977cb5680a506fe26cc8e", size = 224679, upload-time = "2026-03-15T18:52:10.043Z" }, + { url = "https://files.pythonhosted.org/packages/aa/cb/9ff5a25b9273ef160861b41f6937f86fae18b0792fe0a8e75e06acb08f1d/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e1f6e2f00a6b8edb562826e4632e26d063ac10307e80f7461f7de3ad8ef3f077", size = 223475, upload-time = "2026-03-15T18:52:11.854Z" }, + { url = "https://files.pythonhosted.org/packages/fc/97/440635fc093b8d7347502a377031f9605a1039c958f3cd18dcacffb37743/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95b52c68d64c1878818687a473a10547b3292e82b6f6fe483808fb1468e2f52f", size = 215230, upload-time = "2026-03-15T18:52:13.325Z" }, + { url = "https://files.pythonhosted.org/packages/cd/24/afff630feb571a13f07c8539fbb502d2ab494019492aaffc78ef41f1d1d0/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:7504e9b7dc05f99a9bbb4525c67a2c155073b44d720470a148b34166a69c054e", size = 199045, upload-time = "2026-03-15T18:52:14.752Z" }, + { url = "https://files.pythonhosted.org/packages/e5/17/d1399ecdaf7e0498c327433e7eefdd862b41236a7e484355b8e0e5ebd64b/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:172985e4ff804a7ad08eebec0a1640ece87ba5041d565fff23c8f99c1f389484", size = 211658, upload-time = "2026-03-15T18:52:16.278Z" }, + { url = "https://files.pythonhosted.org/packages/b5/38/16baa0affb957b3d880e5ac2144caf3f9d7de7bc4a91842e447fbb5e8b67/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4be9f4830ba8741527693848403e2c457c16e499100963ec711b1c6f2049b7c7", size = 210769, upload-time = "2026-03-15T18:52:17.782Z" }, + { url = "https://files.pythonhosted.org/packages/05/34/c531bc6ac4c21da9ddfddb3107be2287188b3ea4b53b70fc58f2a77ac8d8/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:79090741d842f564b1b2827c0b82d846405b744d31e84f18d7a7b41c20e473ff", size = 201328, upload-time = "2026-03-15T18:52:19.553Z" }, + { url = "https://files.pythonhosted.org/packages/fa/73/a5a1e9ca5f234519c1953608a03fe109c306b97fdfb25f09182babad51a7/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:87725cfb1a4f1f8c2fc9890ae2f42094120f4b44db9360be5d99a4c6b0e03a9e", size = 225302, upload-time = "2026-03-15T18:52:21.043Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f6/cd782923d112d296294dea4bcc7af5a7ae0f86ab79f8fefbda5526b6cfc0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:fcce033e4021347d80ed9c66dcf1e7b1546319834b74445f561d2e2221de5659", size = 211127, upload-time = "2026-03-15T18:52:22.491Z" }, + { url = "https://files.pythonhosted.org/packages/0e/c5/0b6898950627af7d6103a449b22320372c24c6feda91aa24e201a478d161/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:ca0276464d148c72defa8bb4390cce01b4a0e425f3b50d1435aa6d7a18107602", size = 222840, upload-time = "2026-03-15T18:52:24.113Z" }, + { url = "https://files.pythonhosted.org/packages/7d/25/c4bba773bef442cbdc06111d40daa3de5050a676fa26e85090fc54dd12f0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:197c1a244a274bb016dd8b79204850144ef77fe81c5b797dc389327adb552407", size = 216890, upload-time = "2026-03-15T18:52:25.541Z" }, + { url = "https://files.pythonhosted.org/packages/35/1a/05dacadb0978da72ee287b0143097db12f2e7e8d3ffc4647da07a383b0b7/charset_normalizer-3.4.6-cp314-cp314t-win32.whl", hash = "sha256:2a24157fa36980478dd1770b585c0f30d19e18f4fb0c47c13aa568f871718579", size = 155379, upload-time = "2026-03-15T18:52:27.05Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7a/d269d834cb3a76291651256f3b9a5945e81d0a49ab9f4a498964e83c0416/charset_normalizer-3.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:cd5e2801c89992ed8c0a3f0293ae83c159a60d9a5d685005383ef4caca77f2c4", size = 169043, upload-time = "2026-03-15T18:52:28.502Z" }, + { url = "https://files.pythonhosted.org/packages/23/06/28b29fba521a37a8932c6a84192175c34d49f84a6d4773fa63d05f9aff22/charset_normalizer-3.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:47955475ac79cc504ef2704b192364e51d0d473ad452caedd0002605f780101c", size = 148523, upload-time = "2026-03-15T18:52:29.956Z" }, + { url = "https://files.pythonhosted.org/packages/2a/68/687187c7e26cb24ccbd88e5069f5ef00eba804d36dde11d99aad0838ab45/charset_normalizer-3.4.6-py3-none-any.whl", hash = "sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69", size = 61455, upload-time = "2026-03-15T18:53:23.833Z" }, ] [[package]] @@ -636,10 +652,10 @@ wheels = [ [[package]] name = "cuda-pathfinder" -version = "1.4.1" +version = "1.4.2" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/02/59a5bc738a09def0b49aea0e460bdf97f65206d0d041246147cf6207e69c/cuda_pathfinder-1.4.1-py3-none-any.whl", hash = "sha256:40793006082de88e0950753655e55558a446bed9a7d9d0bcb48b2506d50ed82a", size = 43903, upload-time = "2026-03-06T21:05:24.372Z" }, + { url = "https://files.pythonhosted.org/packages/92/de/8ca2b613042550dcf9ef50c596c8b1f602afda92cf9032ac28a73f6ee410/cuda_pathfinder-1.4.2-py3-none-any.whl", hash = "sha256:eb354abc20278f8609dc5b666a24648655bef5613c6dfe78a238a6fd95566754", size = 44779, upload-time = "2026-03-10T21:57:30.974Z" }, ] [[package]] @@ -768,11 +784,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.25.0" +version = "3.25.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/77/18/a1fd2231c679dcb9726204645721b12498aeac28e1ad0601038f94b42556/filelock-3.25.0.tar.gz", hash = "sha256:8f00faf3abf9dc730a1ffe9c354ae5c04e079ab7d3a683b7c32da5dd05f26af3", size = 40158, upload-time = "2026-03-01T15:08:45.916Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/b8/00651a0f559862f3bb7d6f7477b192afe3f583cc5e26403b44e59a55ab34/filelock-3.25.2.tar.gz", hash = "sha256:b64ece2b38f4ca29dd3e810287aa8c48182bbecd1ae6e9ae126c9b35f1382694", size = 40480, upload-time = "2026-03-11T20:45:38.487Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/0b/de6f54d4a8bedfe8645c41497f3c18d749f0bd3218170c667bf4b81d0cdd/filelock-3.25.0-py3-none-any.whl", hash = "sha256:5ccf8069f7948f494968fc0713c10e5c182a9c9d9eef3a636307a20c2490f047", size = 26427, upload-time = "2026-03-01T15:08:44.593Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl", hash = "sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70", size = 26759, upload-time = "2026-03-11T20:45:37.437Z" }, ] [[package]] @@ -801,51 +817,51 @@ wheels = [ [[package]] name = "fonttools" -version = "4.61.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69", size = 3565756, upload-time = "2025-12-12T17:31:24.246Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/69/12/bf9f4eaa2fad039356cc627587e30ed008c03f1cebd3034376b5ee8d1d44/fonttools-4.61.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09", size = 2852213, upload-time = "2025-12-12T17:29:46.675Z" }, - { url = "https://files.pythonhosted.org/packages/ac/49/4138d1acb6261499bedde1c07f8c2605d1d8f9d77a151e5507fd3ef084b6/fonttools-4.61.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37", size = 2401689, upload-time = "2025-12-12T17:29:48.769Z" }, - { url = "https://files.pythonhosted.org/packages/e5/fe/e6ce0fe20a40e03aef906af60aa87668696f9e4802fa283627d0b5ed777f/fonttools-4.61.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77efb033d8d7ff233385f30c62c7c79271c8885d5c9657d967ede124671bbdfb", size = 5058809, upload-time = "2025-12-12T17:29:51.701Z" }, - { url = "https://files.pythonhosted.org/packages/79/61/1ca198af22f7dd22c17ab86e9024ed3c06299cfdb08170640e9996d501a0/fonttools-4.61.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9", size = 5036039, upload-time = "2025-12-12T17:29:53.659Z" }, - { url = "https://files.pythonhosted.org/packages/99/cc/fa1801e408586b5fce4da9f5455af8d770f4fc57391cd5da7256bb364d38/fonttools-4.61.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0de30bfe7745c0d1ffa2b0b7048fb7123ad0d71107e10ee090fa0b16b9452e87", size = 5034714, upload-time = "2025-12-12T17:29:55.592Z" }, - { url = "https://files.pythonhosted.org/packages/bf/aa/b7aeafe65adb1b0a925f8f25725e09f078c635bc22754f3fecb7456955b0/fonttools-4.61.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58b0ee0ab5b1fc9921eccfe11d1435added19d6494dde14e323f25ad2bc30c56", size = 5158648, upload-time = "2025-12-12T17:29:57.861Z" }, - { url = "https://files.pythonhosted.org/packages/99/f9/08ea7a38663328881384c6e7777bbefc46fd7d282adfd87a7d2b84ec9d50/fonttools-4.61.1-cp311-cp311-win32.whl", hash = "sha256:f79b168428351d11e10c5aeb61a74e1851ec221081299f4cf56036a95431c43a", size = 2280681, upload-time = "2025-12-12T17:29:59.943Z" }, - { url = "https://files.pythonhosted.org/packages/07/ad/37dd1ae5fa6e01612a1fbb954f0927681f282925a86e86198ccd7b15d515/fonttools-4.61.1-cp311-cp311-win_amd64.whl", hash = "sha256:fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7", size = 2331951, upload-time = "2025-12-12T17:30:02.254Z" }, - { url = "https://files.pythonhosted.org/packages/6f/16/7decaa24a1bd3a70c607b2e29f0adc6159f36a7e40eaba59846414765fd4/fonttools-4.61.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e", size = 2851593, upload-time = "2025-12-12T17:30:04.225Z" }, - { url = "https://files.pythonhosted.org/packages/94/98/3c4cb97c64713a8cf499b3245c3bf9a2b8fd16a3e375feff2aed78f96259/fonttools-4.61.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2", size = 2400231, upload-time = "2025-12-12T17:30:06.47Z" }, - { url = "https://files.pythonhosted.org/packages/b7/37/82dbef0f6342eb01f54bca073ac1498433d6ce71e50c3c3282b655733b31/fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796", size = 4954103, upload-time = "2025-12-12T17:30:08.432Z" }, - { url = "https://files.pythonhosted.org/packages/6c/44/f3aeac0fa98e7ad527f479e161aca6c3a1e47bb6996b053d45226fe37bf2/fonttools-4.61.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d", size = 5004295, upload-time = "2025-12-12T17:30:10.56Z" }, - { url = "https://files.pythonhosted.org/packages/14/e8/7424ced75473983b964d09f6747fa09f054a6d656f60e9ac9324cf40c743/fonttools-4.61.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8", size = 4944109, upload-time = "2025-12-12T17:30:12.874Z" }, - { url = "https://files.pythonhosted.org/packages/c8/8b/6391b257fa3d0b553d73e778f953a2f0154292a7a7a085e2374b111e5410/fonttools-4.61.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0", size = 5093598, upload-time = "2025-12-12T17:30:15.79Z" }, - { url = "https://files.pythonhosted.org/packages/d9/71/fd2ea96cdc512d92da5678a1c98c267ddd4d8c5130b76d0f7a80f9a9fde8/fonttools-4.61.1-cp312-cp312-win32.whl", hash = "sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261", size = 2269060, upload-time = "2025-12-12T17:30:18.058Z" }, - { url = "https://files.pythonhosted.org/packages/80/3b/a3e81b71aed5a688e89dfe0e2694b26b78c7d7f39a5ffd8a7d75f54a12a8/fonttools-4.61.1-cp312-cp312-win_amd64.whl", hash = "sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9", size = 2319078, upload-time = "2025-12-12T17:30:22.862Z" }, - { url = "https://files.pythonhosted.org/packages/4b/cf/00ba28b0990982530addb8dc3e9e6f2fa9cb5c20df2abdda7baa755e8fe1/fonttools-4.61.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c56c488ab471628ff3bfa80964372fc13504ece601e0d97a78ee74126b2045c", size = 2846454, upload-time = "2025-12-12T17:30:24.938Z" }, - { url = "https://files.pythonhosted.org/packages/5a/ca/468c9a8446a2103ae645d14fee3f610567b7042aba85031c1c65e3ef7471/fonttools-4.61.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc492779501fa723b04d0ab1f5be046797fee17d27700476edc7ee9ae535a61e", size = 2398191, upload-time = "2025-12-12T17:30:27.343Z" }, - { url = "https://files.pythonhosted.org/packages/a3/4b/d67eedaed19def5967fade3297fed8161b25ba94699efc124b14fb68cdbc/fonttools-4.61.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:64102ca87e84261419c3747a0d20f396eb024bdbeb04c2bfb37e2891f5fadcb5", size = 4928410, upload-time = "2025-12-12T17:30:29.771Z" }, - { url = "https://files.pythonhosted.org/packages/b0/8d/6fb3494dfe61a46258cd93d979cf4725ded4eb46c2a4ca35e4490d84daea/fonttools-4.61.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c1b526c8d3f615a7b1867f38a9410849c8f4aef078535742198e942fba0e9bd", size = 4984460, upload-time = "2025-12-12T17:30:32.073Z" }, - { url = "https://files.pythonhosted.org/packages/f7/f1/a47f1d30b3dc00d75e7af762652d4cbc3dff5c2697a0dbd5203c81afd9c3/fonttools-4.61.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:41ed4b5ec103bd306bb68f81dc166e77409e5209443e5773cb4ed837bcc9b0d3", size = 4925800, upload-time = "2025-12-12T17:30:34.339Z" }, - { url = "https://files.pythonhosted.org/packages/a7/01/e6ae64a0981076e8a66906fab01539799546181e32a37a0257b77e4aa88b/fonttools-4.61.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b501c862d4901792adaec7c25b1ecc749e2662543f68bb194c42ba18d6eec98d", size = 5067859, upload-time = "2025-12-12T17:30:36.593Z" }, - { url = "https://files.pythonhosted.org/packages/73/aa/28e40b8d6809a9b5075350a86779163f074d2b617c15d22343fce81918db/fonttools-4.61.1-cp313-cp313-win32.whl", hash = "sha256:4d7092bb38c53bbc78e9255a59158b150bcdc115a1e3b3ce0b5f267dc35dd63c", size = 2267821, upload-time = "2025-12-12T17:30:38.478Z" }, - { url = "https://files.pythonhosted.org/packages/1a/59/453c06d1d83dc0951b69ef692d6b9f1846680342927df54e9a1ca91c6f90/fonttools-4.61.1-cp313-cp313-win_amd64.whl", hash = "sha256:21e7c8d76f62ab13c9472ccf74515ca5b9a761d1bde3265152a6dc58700d895b", size = 2318169, upload-time = "2025-12-12T17:30:40.951Z" }, - { url = "https://files.pythonhosted.org/packages/32/8f/4e7bf82c0cbb738d3c2206c920ca34ca74ef9dabde779030145d28665104/fonttools-4.61.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fff4f534200a04b4a36e7ae3cb74493afe807b517a09e99cb4faa89a34ed6ecd", size = 2846094, upload-time = "2025-12-12T17:30:43.511Z" }, - { url = "https://files.pythonhosted.org/packages/71/09/d44e45d0a4f3a651f23a1e9d42de43bc643cce2971b19e784cc67d823676/fonttools-4.61.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d9203500f7c63545b4ce3799319fe4d9feb1a1b89b28d3cb5abd11b9dd64147e", size = 2396589, upload-time = "2025-12-12T17:30:45.681Z" }, - { url = "https://files.pythonhosted.org/packages/89/18/58c64cafcf8eb677a99ef593121f719e6dcbdb7d1c594ae5a10d4997ca8a/fonttools-4.61.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa646ecec9528bef693415c79a86e733c70a4965dd938e9a226b0fc64c9d2e6c", size = 4877892, upload-time = "2025-12-12T17:30:47.709Z" }, - { url = "https://files.pythonhosted.org/packages/8a/ec/9e6b38c7ba1e09eb51db849d5450f4c05b7e78481f662c3b79dbde6f3d04/fonttools-4.61.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f35ad7805edba3aac1a3710d104592df59f4b957e30108ae0ba6c10b11dd75", size = 4972884, upload-time = "2025-12-12T17:30:49.656Z" }, - { url = "https://files.pythonhosted.org/packages/5e/87/b5339da8e0256734ba0dbbf5b6cdebb1dd79b01dc8c270989b7bcd465541/fonttools-4.61.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b931ae8f62db78861b0ff1ac017851764602288575d65b8e8ff1963fed419063", size = 4924405, upload-time = "2025-12-12T17:30:51.735Z" }, - { url = "https://files.pythonhosted.org/packages/0b/47/e3409f1e1e69c073a3a6fd8cb886eb18c0bae0ee13db2c8d5e7f8495e8b7/fonttools-4.61.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b148b56f5de675ee16d45e769e69f87623a4944f7443850bf9a9376e628a89d2", size = 5035553, upload-time = "2025-12-12T17:30:54.823Z" }, - { url = "https://files.pythonhosted.org/packages/bf/b6/1f6600161b1073a984294c6c031e1a56ebf95b6164249eecf30012bb2e38/fonttools-4.61.1-cp314-cp314-win32.whl", hash = "sha256:9b666a475a65f4e839d3d10473fad6d47e0a9db14a2f4a224029c5bfde58ad2c", size = 2271915, upload-time = "2025-12-12T17:30:57.913Z" }, - { url = "https://files.pythonhosted.org/packages/52/7b/91e7b01e37cc8eb0e1f770d08305b3655e4f002fc160fb82b3390eabacf5/fonttools-4.61.1-cp314-cp314-win_amd64.whl", hash = "sha256:4f5686e1fe5fce75d82d93c47a438a25bf0d1319d2843a926f741140b2b16e0c", size = 2323487, upload-time = "2025-12-12T17:30:59.804Z" }, - { url = "https://files.pythonhosted.org/packages/39/5c/908ad78e46c61c3e3ed70c3b58ff82ab48437faf84ec84f109592cabbd9f/fonttools-4.61.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e76ce097e3c57c4bcb67c5aa24a0ecdbd9f74ea9219997a707a4061fbe2707aa", size = 2929571, upload-time = "2025-12-12T17:31:02.574Z" }, - { url = "https://files.pythonhosted.org/packages/bd/41/975804132c6dea64cdbfbaa59f3518a21c137a10cccf962805b301ac6ab2/fonttools-4.61.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9cfef3ab326780c04d6646f68d4b4742aae222e8b8ea1d627c74e38afcbc9d91", size = 2435317, upload-time = "2025-12-12T17:31:04.974Z" }, - { url = "https://files.pythonhosted.org/packages/b0/5a/aef2a0a8daf1ebaae4cfd83f84186d4a72ee08fd6a8451289fcd03ffa8a4/fonttools-4.61.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a75c301f96db737e1c5ed5fd7d77d9c34466de16095a266509e13da09751bd19", size = 4882124, upload-time = "2025-12-12T17:31:07.456Z" }, - { url = "https://files.pythonhosted.org/packages/80/33/d6db3485b645b81cea538c9d1c9219d5805f0877fda18777add4671c5240/fonttools-4.61.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91669ccac46bbc1d09e9273546181919064e8df73488ea087dcac3e2968df9ba", size = 5100391, upload-time = "2025-12-12T17:31:09.732Z" }, - { url = "https://files.pythonhosted.org/packages/6c/d6/675ba631454043c75fcf76f0ca5463eac8eb0666ea1d7badae5fea001155/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c33ab3ca9d3ccd581d58e989d67554e42d8d4ded94ab3ade3508455fe70e65f7", size = 4978800, upload-time = "2025-12-12T17:31:11.681Z" }, - { url = "https://files.pythonhosted.org/packages/7f/33/d3ec753d547a8d2bdaedd390d4a814e8d5b45a093d558f025c6b990b554c/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:664c5a68ec406f6b1547946683008576ef8b38275608e1cee6c061828171c118", size = 5006426, upload-time = "2025-12-12T17:31:13.764Z" }, - { url = "https://files.pythonhosted.org/packages/b4/40/cc11f378b561a67bea850ab50063366a0d1dd3f6d0a30ce0f874b0ad5664/fonttools-4.61.1-cp314-cp314t-win32.whl", hash = "sha256:aed04cabe26f30c1647ef0e8fbb207516fd40fe9472e9439695f5c6998e60ac5", size = 2335377, upload-time = "2025-12-12T17:31:16.49Z" }, - { url = "https://files.pythonhosted.org/packages/e4/ff/c9a2b66b39f8628531ea58b320d66d951267c98c6a38684daa8f50fb02f8/fonttools-4.61.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2180f14c141d2f0f3da43f3a81bc8aa4684860f6b0e6f9e165a4831f24e6a23b", size = 2400613, upload-time = "2025-12-12T17:31:18.769Z" }, - { url = "https://files.pythonhosted.org/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371", size = 1148996, upload-time = "2025-12-12T17:31:21.03Z" }, +version = "4.62.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/08/7012b00a9a5874311b639c3920270c36ee0c445b69d9989a85e5c92ebcb0/fonttools-4.62.1.tar.gz", hash = "sha256:e54c75fd6041f1122476776880f7c3c3295ffa31962dc6ebe2543c00dca58b5d", size = 3580737, upload-time = "2026-03-13T13:54:25.52Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/39/23ff32561ec8d45a4d48578b4d241369d9270dc50926c017570e60893701/fonttools-4.62.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:40975849bac44fb0b9253d77420c6d8b523ac4dcdcefeff6e4d706838a5b80f7", size = 2871039, upload-time = "2026-03-13T13:52:33.127Z" }, + { url = "https://files.pythonhosted.org/packages/24/7f/66d3f8a9338a9b67fe6e1739f47e1cd5cee78bd3bc1206ef9b0b982289a5/fonttools-4.62.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9dde91633f77fa576879a0c76b1d89de373cae751a98ddf0109d54e173b40f14", size = 2416346, upload-time = "2026-03-13T13:52:35.676Z" }, + { url = "https://files.pythonhosted.org/packages/aa/53/5276ceba7bff95da7793a07c5284e1da901cf00341ce5e2f3273056c0cca/fonttools-4.62.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6acb4109f8bee00fec985c8c7afb02299e35e9c94b57287f3ea542f28bd0b0a7", size = 5100897, upload-time = "2026-03-13T13:52:38.102Z" }, + { url = "https://files.pythonhosted.org/packages/cc/a1/40a5c4d8e28b0851d53a8eeeb46fbd73c325a2a9a165f290a5ed90e6c597/fonttools-4.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1c5c25671ce8805e0d080e2ffdeca7f1e86778c5cbfbeae86d7f866d8830517b", size = 5071078, upload-time = "2026-03-13T13:52:41.305Z" }, + { url = "https://files.pythonhosted.org/packages/e3/be/d378fca4c65ea1956fee6d90ace6e861776809cbbc5af22388a090c3c092/fonttools-4.62.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a5d8825e1140f04e6c99bb7d37a9e31c172f3bc208afbe02175339e699c710e1", size = 5076908, upload-time = "2026-03-13T13:52:44.122Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d9/ae6a1d0693a4185a84605679c8a1f719a55df87b9c6e8e817bfdd9ef5936/fonttools-4.62.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:268abb1cb221e66c014acc234e872b7870d8b5d4657a83a8f4205094c32d2416", size = 5202275, upload-time = "2026-03-13T13:52:46.591Z" }, + { url = "https://files.pythonhosted.org/packages/54/6c/af95d9c4efb15cabff22642b608342f2bd67137eea6107202d91b5b03184/fonttools-4.62.1-cp311-cp311-win32.whl", hash = "sha256:942b03094d7edbb99bdf1ae7e9090898cad7bf9030b3d21f33d7072dbcb51a53", size = 2293075, upload-time = "2026-03-13T13:52:48.711Z" }, + { url = "https://files.pythonhosted.org/packages/d3/97/bf54c5b3f2be34e1f143e6db838dfdc54f2ffa3e68c738934c82f3b2a08d/fonttools-4.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:e8514f4924375f77084e81467e63238b095abda5107620f49421c368a6017ed2", size = 2344593, upload-time = "2026-03-13T13:52:50.725Z" }, + { url = "https://files.pythonhosted.org/packages/47/d4/dbacced3953544b9a93088cc10ef2b596d348c983d5c67a404fa41ec51ba/fonttools-4.62.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:90365821debbd7db678809c7491ca4acd1e0779b9624cdc6ddaf1f31992bf974", size = 2870219, upload-time = "2026-03-13T13:52:53.664Z" }, + { url = "https://files.pythonhosted.org/packages/66/9e/a769c8e99b81e5a87ab7e5e7236684de4e96246aae17274e5347d11ebd78/fonttools-4.62.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12859ff0b47dd20f110804c3e0d0970f7b832f561630cd879969011541a464a9", size = 2414891, upload-time = "2026-03-13T13:52:56.493Z" }, + { url = "https://files.pythonhosted.org/packages/69/64/f19a9e3911968c37e1e620e14dfc5778299e1474f72f4e57c5ec771d9489/fonttools-4.62.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c125ffa00c3d9003cdaaf7f2c79e6e535628093e14b5de1dccb08859b680936", size = 5033197, upload-time = "2026-03-13T13:52:59.179Z" }, + { url = "https://files.pythonhosted.org/packages/9b/8a/99c8b3c3888c5c474c08dbfd7c8899786de9604b727fcefb055b42c84bba/fonttools-4.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:149f7d84afca659d1a97e39a4778794a2f83bf344c5ee5134e09995086cc2392", size = 4988768, upload-time = "2026-03-13T13:53:02.761Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c6/0f904540d3e6ab463c1243a0d803504826a11604c72dd58c2949796a1762/fonttools-4.62.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0aa72c43a601cfa9273bb1ae0518f1acadc01ee181a6fc60cd758d7fdadffc04", size = 4971512, upload-time = "2026-03-13T13:53:05.678Z" }, + { url = "https://files.pythonhosted.org/packages/29/0b/5cbef6588dc9bd6b5c9ad6a4d5a8ca384d0cea089da31711bbeb4f9654a6/fonttools-4.62.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:19177c8d96c7c36359266e571c5173bcee9157b59cfc8cb0153c5673dc5a3a7d", size = 5122723, upload-time = "2026-03-13T13:53:08.662Z" }, + { url = "https://files.pythonhosted.org/packages/4a/47/b3a5342d381595ef439adec67848bed561ab7fdb1019fa522e82101b7d9c/fonttools-4.62.1-cp312-cp312-win32.whl", hash = "sha256:a24decd24d60744ee8b4679d38e88b8303d86772053afc29b19d23bb8207803c", size = 2281278, upload-time = "2026-03-13T13:53:10.998Z" }, + { url = "https://files.pythonhosted.org/packages/28/b1/0c2ab56a16f409c6c8a68816e6af707827ad5d629634691ff60a52879792/fonttools-4.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:9e7863e10b3de72376280b515d35b14f5eeed639d1aa7824f4cf06779ec65e42", size = 2331414, upload-time = "2026-03-13T13:53:13.992Z" }, + { url = "https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c22b1014017111c401469e3acc5433e6acf6ebcc6aa9efb538a533c800971c79", size = 2865155, upload-time = "2026-03-13T13:53:16.132Z" }, + { url = "https://files.pythonhosted.org/packages/03/c5/0e3966edd5ec668d41dfe418787726752bc07e2f5fd8c8f208615e61fa89/fonttools-4.62.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68959f5fc58ed4599b44aad161c2837477d7f35f5f79402d97439974faebfebe", size = 2412802, upload-time = "2026-03-13T13:53:18.878Z" }, + { url = "https://files.pythonhosted.org/packages/52/94/e6ac4b44026de7786fe46e3bfa0c87e51d5d70a841054065d49cd62bb909/fonttools-4.62.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef46db46c9447103b8f3ff91e8ba009d5fe181b1920a83757a5762551e32bb68", size = 5013926, upload-time = "2026-03-13T13:53:21.379Z" }, + { url = "https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6706d1cb1d5e6251a97ad3c1b9347505c5615c112e66047abbef0f8545fa30d1", size = 4964575, upload-time = "2026-03-13T13:53:23.857Z" }, + { url = "https://files.pythonhosted.org/packages/46/76/7d051671e938b1881670528fec69cc4044315edd71a229c7fd712eaa5119/fonttools-4.62.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e7abd2b1e11736f58c1de27819e1955a53267c21732e78243fa2fa2e5c1e069", size = 4953693, upload-time = "2026-03-13T13:53:26.569Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ae/b41f8628ec0be3c1b934fc12b84f4576a5c646119db4d3bdd76a217c90b5/fonttools-4.62.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:403d28ce06ebfc547fbcb0cb8b7f7cc2f7a2d3e1a67ba9a34b14632df9e080f9", size = 5094920, upload-time = "2026-03-13T13:53:29.329Z" }, + { url = "https://files.pythonhosted.org/packages/f2/f6/53a1e9469331a23dcc400970a27a4caa3d9f6edbf5baab0260285238b884/fonttools-4.62.1-cp313-cp313-win32.whl", hash = "sha256:93c316e0f5301b2adbe6a5f658634307c096fd5aae60a5b3412e4f3e1728ab24", size = 2279928, upload-time = "2026-03-13T13:53:32.352Z" }, + { url = "https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl", hash = "sha256:7aa21ff53e28a9c2157acbc44e5b401149d3c9178107130e82d74ceb500e5056", size = 2330514, upload-time = "2026-03-13T13:53:34.991Z" }, + { url = "https://files.pythonhosted.org/packages/36/f0/2888cdac391807d68d90dcb16ef858ddc1b5309bfc6966195a459dd326e2/fonttools-4.62.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fa1d16210b6b10a826d71bed68dd9ec24a9e218d5a5e2797f37c573e7ec215ca", size = 2864442, upload-time = "2026-03-13T13:53:37.509Z" }, + { url = "https://files.pythonhosted.org/packages/4b/b2/e521803081f8dc35990816b82da6360fa668a21b44da4b53fc9e77efcd62/fonttools-4.62.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:aa69d10ed420d8121118e628ad47d86e4caa79ba37f968597b958f6cceab7eca", size = 2410901, upload-time = "2026-03-13T13:53:40.55Z" }, + { url = "https://files.pythonhosted.org/packages/00/a4/8c3511ff06e53110039358dbbdc1a65d72157a054638387aa2ada300a8b8/fonttools-4.62.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd13b7999d59c5eb1c2b442eb2d0c427cb517a0b7a1f5798fc5c9e003f5ff782", size = 4999608, upload-time = "2026-03-13T13:53:42.798Z" }, + { url = "https://files.pythonhosted.org/packages/28/63/cd0c3b26afe60995a5295f37c246a93d454023726c3261cfbb3559969bb9/fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8d337fdd49a79b0d51c4da87bc38169d21c3abbf0c1aa9367eff5c6656fb6dae", size = 4912726, upload-time = "2026-03-13T13:53:45.405Z" }, + { url = "https://files.pythonhosted.org/packages/70/b9/ac677cb07c24c685cf34f64e140617d58789d67a3dd524164b63648c6114/fonttools-4.62.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d241cdc4a67b5431c6d7f115fdf63335222414995e3a1df1a41e1182acd4bcc7", size = 4951422, upload-time = "2026-03-13T13:53:48.326Z" }, + { url = "https://files.pythonhosted.org/packages/e6/10/11c08419a14b85b7ca9a9faca321accccc8842dd9e0b1c8a72908de05945/fonttools-4.62.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c05557a78f8fa514da0f869556eeda40887a8abc77c76ee3f74cf241778afd5a", size = 5060979, upload-time = "2026-03-13T13:53:51.366Z" }, + { url = "https://files.pythonhosted.org/packages/4e/3c/12eea4a4cf054e7ab058ed5ceada43b46809fce2bf319017c4d63ae55bb4/fonttools-4.62.1-cp314-cp314-win32.whl", hash = "sha256:49a445d2f544ce4a69338694cad575ba97b9a75fff02720da0882d1a73f12800", size = 2283733, upload-time = "2026-03-13T13:53:53.606Z" }, + { url = "https://files.pythonhosted.org/packages/6b/67/74b070029043186b5dd13462c958cb7c7f811be0d2e634309d9a1ffb1505/fonttools-4.62.1-cp314-cp314-win_amd64.whl", hash = "sha256:1eecc128c86c552fb963fe846ca4e011b1be053728f798185a1687502f6d398e", size = 2335663, upload-time = "2026-03-13T13:53:56.23Z" }, + { url = "https://files.pythonhosted.org/packages/42/c5/4d2ed3ca6e33617fc5624467da353337f06e7f637707478903c785bd8e20/fonttools-4.62.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1596aeaddf7f78e21e68293c011316a25267b3effdaccaf4d59bc9159d681b82", size = 2947288, upload-time = "2026-03-13T13:53:59.397Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e9/7ab11ddfda48ed0f89b13380e5595ba572619c27077be0b2c447a63ff351/fonttools-4.62.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8f8fca95d3bb3208f59626a4b0ea6e526ee51f5a8ad5d91821c165903e8d9260", size = 2449023, upload-time = "2026-03-13T13:54:01.642Z" }, + { url = "https://files.pythonhosted.org/packages/b2/10/a800fa090b5e8819942e54e19b55fc7c21fe14a08757c3aa3ca8db358939/fonttools-4.62.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee91628c08e76f77b533d65feb3fbe6d9dad699f95be51cf0d022db94089cdc4", size = 5137599, upload-time = "2026-03-13T13:54:04.495Z" }, + { url = "https://files.pythonhosted.org/packages/37/dc/8ccd45033fffd74deb6912fa1ca524643f584b94c87a16036855b498a1ed/fonttools-4.62.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f37df1cac61d906e7b836abe356bc2f34c99d4477467755c216b72aa3dc748b", size = 4920933, upload-time = "2026-03-13T13:54:07.557Z" }, + { url = "https://files.pythonhosted.org/packages/99/eb/e618adefb839598d25ac8136cd577925d6c513dc0d931d93b8af956210f0/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:92bb00a947e666169c99b43753c4305fc95a890a60ef3aeb2a6963e07902cc87", size = 5016232, upload-time = "2026-03-13T13:54:10.611Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5f/9b5c9bfaa8ec82def8d8168c4f13615990d6ce5996fe52bd49bfb5e05134/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bdfe592802ef939a0e33106ea4a318eeb17822c7ee168c290273cbd5fabd746c", size = 5042987, upload-time = "2026-03-13T13:54:13.569Z" }, + { url = "https://files.pythonhosted.org/packages/90/aa/dfbbe24c6a6afc5c203d90cc0343e24bcbb09e76d67c4d6eef8c2558d7ba/fonttools-4.62.1-cp314-cp314t-win32.whl", hash = "sha256:b820fcb92d4655513d8402d5b219f94481c4443d825b4372c75a2072aa4b357a", size = 2348021, upload-time = "2026-03-13T13:54:16.98Z" }, + { url = "https://files.pythonhosted.org/packages/13/6f/ae9c4e4dd417948407b680855c2c7790efb52add6009aaecff1e3bc50e8e/fonttools-4.62.1-cp314-cp314t-win_amd64.whl", hash = "sha256:59b372b4f0e113d3746b88985f1c796e7bf830dd54b28374cd85c2b8acd7583e", size = 2414147, upload-time = "2026-03-13T13:54:19.416Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ba/56147c165442cc5ba7e82ecf301c9a68353cede498185869e6e02b4c264f/fonttools-4.62.1-py3-none-any.whl", hash = "sha256:7487782e2113861f4ddcc07c3436450659e3caa5e470b27dc2177cade2d8e7fd", size = 1152647, upload-time = "2026-03-13T13:54:22.735Z" }, ] [[package]] @@ -1100,11 +1116,11 @@ wheels = [ [[package]] name = "identify" -version = "2.6.17" +version = "2.6.18" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/84/376a3b96e5a8d33a7aa2c5b3b31a4b3c364117184bf0b17418055f6ace66/identify-2.6.17.tar.gz", hash = "sha256:f816b0b596b204c9fdf076ded172322f2723cf958d02f9c3587504834c8ff04d", size = 99579, upload-time = "2026-03-01T20:04:12.702Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/c4/7fb4db12296cdb11893d61c92048fe617ee853f8523b9b296ac03b43757e/identify-2.6.18.tar.gz", hash = "sha256:873ac56a5e3fd63e7438a7ecbc4d91aca692eb3fefa4534db2b7913f3fc352fd", size = 99580, upload-time = "2026-03-15T18:39:50.319Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl", hash = "sha256:be5f8412d5ed4b20f2bd41a65f920990bdccaa6a4a18a08f1eefdcd0bdd885f0", size = 99382, upload-time = "2026-03-01T20:04:11.439Z" }, + { url = "https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl", hash = "sha256:8db9d3c8ea9079db92cafb0ebf97abdc09d52e97f4dcf773a2e694048b7cd737", size = 99394, upload-time = "2026-03-15T18:39:48.915Z" }, ] [[package]] @@ -1118,15 +1134,15 @@ wheels = [ [[package]] name = "imageio" -version = "2.37.2" +version = "2.37.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, { name = "pillow" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/6f/606be632e37bf8d05b253e8626c2291d74c691ddc7bcdf7d6aaf33b32f6a/imageio-2.37.2.tar.gz", hash = "sha256:0212ef2727ac9caa5ca4b2c75ae89454312f440a756fcfc8ef1993e718f50f8a", size = 389600, upload-time = "2025-11-04T14:29:39.898Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/84/93bcd1300216ea50811cee96873b84a1bebf8d0489ffaf7f2a3756bab866/imageio-2.37.3.tar.gz", hash = "sha256:bbb37efbfc4c400fcd534b367b91fcd66d5da639aaa138034431a1c5e0a41451", size = 389673, upload-time = "2026-03-09T11:31:12.573Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/fe/301e0936b79bcab4cacc7548bf2853fc28dced0a578bab1f7ef53c9aa75b/imageio-2.37.2-py3-none-any.whl", hash = "sha256:ad9adfb20335d718c03de457358ed69f141021a333c40a53e57273d8a5bd0b9b", size = 317646, upload-time = "2025-11-04T14:29:37.948Z" }, + { url = "https://files.pythonhosted.org/packages/49/fa/391e437a34e55095173dca5f24070d89cbc233ff85bf1c29c93248c6588d/imageio-2.37.3-py3-none-any.whl", hash = "sha256:46f5bb8522cd421c0f5ae104d8268f569d856b29eb1a13b92829d1970f32c9f0", size = 317646, upload-time = "2026-03-09T11:31:10.771Z" }, ] [[package]] @@ -1453,7 +1469,7 @@ wheels = [ [[package]] name = "jupyterlab" -version = "4.5.5" +version = "4.5.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "async-lru" }, @@ -1470,9 +1486,9 @@ dependencies = [ { name = "tornado" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6e/2d/953a5612a34a3c799a62566a548e711d103f631672fd49650e0f2de80870/jupyterlab-4.5.5.tar.gz", hash = "sha256:eac620698c59eb810e1729909be418d9373d18137cac66637141abba613b3fda", size = 23968441, upload-time = "2026-02-23T18:57:34.339Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/d5/730628e03fff2e8a8e8ccdaedde1489ab1309f9a4fa2536248884e30b7c7/jupyterlab-4.5.6.tar.gz", hash = "sha256:642fe2cfe7f0f5922a8a558ba7a0d246c7bc133b708dfe43f7b3a826d163cf42", size = 23970670, upload-time = "2026-03-11T14:17:04.531Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl", hash = "sha256:a35694a40a8e7f2e82f387472af24e61b22adcce87b5a8ab97a5d9c486202a6d", size = 12446824, upload-time = "2026-02-23T18:57:30.398Z" }, + { url = "https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl", hash = "sha256:d6b3dac883aa4d9993348e0f8e95b24624f75099aed64eab6a4351a9cdd1e580", size = 12447124, upload-time = "2026-03-11T14:17:00.229Z" }, ] [[package]] @@ -1513,92 +1529,108 @@ wheels = [ [[package]] name = "kiwisolver" -version = "1.4.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/ab/c80b0d5a9d8a1a65f4f815f2afff9798b12c3b9f31f1d304dd233dd920e2/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16", size = 124167, upload-time = "2025-08-10T21:25:53.403Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c0/27fe1a68a39cf62472a300e2879ffc13c0538546c359b86f149cc19f6ac3/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089", size = 66579, upload-time = "2025-08-10T21:25:54.79Z" }, - { url = "https://files.pythonhosted.org/packages/31/a2/a12a503ac1fd4943c50f9822678e8015a790a13b5490354c68afb8489814/kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543", size = 65309, upload-time = "2025-08-10T21:25:55.76Z" }, - { url = "https://files.pythonhosted.org/packages/66/e1/e533435c0be77c3f64040d68d7a657771194a63c279f55573188161e81ca/kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61", size = 1435596, upload-time = "2025-08-10T21:25:56.861Z" }, - { url = "https://files.pythonhosted.org/packages/67/1e/51b73c7347f9aabdc7215aa79e8b15299097dc2f8e67dee2b095faca9cb0/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1", size = 1246548, upload-time = "2025-08-10T21:25:58.246Z" }, - { url = "https://files.pythonhosted.org/packages/21/aa/72a1c5d1e430294f2d32adb9542719cfb441b5da368d09d268c7757af46c/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872", size = 1263618, upload-time = "2025-08-10T21:25:59.857Z" }, - { url = "https://files.pythonhosted.org/packages/a3/af/db1509a9e79dbf4c260ce0cfa3903ea8945f6240e9e59d1e4deb731b1a40/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26", size = 1317437, upload-time = "2025-08-10T21:26:01.105Z" }, - { url = "https://files.pythonhosted.org/packages/e0/f2/3ea5ee5d52abacdd12013a94130436e19969fa183faa1e7c7fbc89e9a42f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028", size = 2195742, upload-time = "2025-08-10T21:26:02.675Z" }, - { url = "https://files.pythonhosted.org/packages/6f/9b/1efdd3013c2d9a2566aa6a337e9923a00590c516add9a1e89a768a3eb2fc/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771", size = 2290810, upload-time = "2025-08-10T21:26:04.009Z" }, - { url = "https://files.pythonhosted.org/packages/fb/e5/cfdc36109ae4e67361f9bc5b41323648cb24a01b9ade18784657e022e65f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a", size = 2461579, upload-time = "2025-08-10T21:26:05.317Z" }, - { url = "https://files.pythonhosted.org/packages/62/86/b589e5e86c7610842213994cdea5add00960076bef4ae290c5fa68589cac/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464", size = 2268071, upload-time = "2025-08-10T21:26:06.686Z" }, - { url = "https://files.pythonhosted.org/packages/3b/c6/f8df8509fd1eee6c622febe54384a96cfaf4d43bf2ccec7a0cc17e4715c9/kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2", size = 73840, upload-time = "2025-08-10T21:26:07.94Z" }, - { url = "https://files.pythonhosted.org/packages/e2/2d/16e0581daafd147bc11ac53f032a2b45eabac897f42a338d0a13c1e5c436/kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7", size = 65159, upload-time = "2025-08-10T21:26:09.048Z" }, - { url = "https://files.pythonhosted.org/packages/86/c9/13573a747838aeb1c76e3267620daa054f4152444d1f3d1a2324b78255b5/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", size = 123686, upload-time = "2025-08-10T21:26:10.034Z" }, - { url = "https://files.pythonhosted.org/packages/51/ea/2ecf727927f103ffd1739271ca19c424d0e65ea473fbaeea1c014aea93f6/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2", size = 66460, upload-time = "2025-08-10T21:26:11.083Z" }, - { url = "https://files.pythonhosted.org/packages/5b/5a/51f5464373ce2aeb5194508298a508b6f21d3867f499556263c64c621914/kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14", size = 64952, upload-time = "2025-08-10T21:26:12.058Z" }, - { url = "https://files.pythonhosted.org/packages/70/90/6d240beb0f24b74371762873e9b7f499f1e02166a2d9c5801f4dbf8fa12e/kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04", size = 1474756, upload-time = "2025-08-10T21:26:13.096Z" }, - { url = "https://files.pythonhosted.org/packages/12/42/f36816eaf465220f683fb711efdd1bbf7a7005a2473d0e4ed421389bd26c/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752", size = 1276404, upload-time = "2025-08-10T21:26:14.457Z" }, - { url = "https://files.pythonhosted.org/packages/2e/64/bc2de94800adc830c476dce44e9b40fd0809cddeef1fde9fcf0f73da301f/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77", size = 1294410, upload-time = "2025-08-10T21:26:15.73Z" }, - { url = "https://files.pythonhosted.org/packages/5f/42/2dc82330a70aa8e55b6d395b11018045e58d0bb00834502bf11509f79091/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198", size = 1343631, upload-time = "2025-08-10T21:26:17.045Z" }, - { url = "https://files.pythonhosted.org/packages/22/fd/f4c67a6ed1aab149ec5a8a401c323cee7a1cbe364381bb6c9c0d564e0e20/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d", size = 2224963, upload-time = "2025-08-10T21:26:18.737Z" }, - { url = "https://files.pythonhosted.org/packages/45/aa/76720bd4cb3713314677d9ec94dcc21ced3f1baf4830adde5bb9b2430a5f/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab", size = 2321295, upload-time = "2025-08-10T21:26:20.11Z" }, - { url = "https://files.pythonhosted.org/packages/80/19/d3ec0d9ab711242f56ae0dc2fc5d70e298bb4a1f9dfab44c027668c673a1/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2", size = 2487987, upload-time = "2025-08-10T21:26:21.49Z" }, - { url = "https://files.pythonhosted.org/packages/39/e9/61e4813b2c97e86b6fdbd4dd824bf72d28bcd8d4849b8084a357bc0dd64d/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145", size = 2291817, upload-time = "2025-08-10T21:26:22.812Z" }, - { url = "https://files.pythonhosted.org/packages/a0/41/85d82b0291db7504da3c2defe35c9a8a5c9803a730f297bd823d11d5fb77/kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54", size = 73895, upload-time = "2025-08-10T21:26:24.37Z" }, - { url = "https://files.pythonhosted.org/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992, upload-time = "2025-08-10T21:26:25.732Z" }, - { url = "https://files.pythonhosted.org/packages/31/c1/c2686cda909742ab66c7388e9a1a8521a59eb89f8bcfbee28fc980d07e24/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8", size = 123681, upload-time = "2025-08-10T21:26:26.725Z" }, - { url = "https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2", size = 66464, upload-time = "2025-08-10T21:26:27.733Z" }, - { url = "https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f", size = 64961, upload-time = "2025-08-10T21:26:28.729Z" }, - { url = "https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098", size = 1474607, upload-time = "2025-08-10T21:26:29.798Z" }, - { url = "https://files.pythonhosted.org/packages/d9/28/aac26d4c882f14de59041636292bc838db8961373825df23b8eeb807e198/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed", size = 1276546, upload-time = "2025-08-10T21:26:31.401Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ad/8bfc1c93d4cc565e5069162f610ba2f48ff39b7de4b5b8d93f69f30c4bed/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525", size = 1294482, upload-time = "2025-08-10T21:26:32.721Z" }, - { url = "https://files.pythonhosted.org/packages/da/f1/6aca55ff798901d8ce403206d00e033191f63d82dd708a186e0ed2067e9c/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78", size = 1343720, upload-time = "2025-08-10T21:26:34.032Z" }, - { url = "https://files.pythonhosted.org/packages/d1/91/eed031876c595c81d90d0f6fc681ece250e14bf6998c3d7c419466b523b7/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b", size = 2224907, upload-time = "2025-08-10T21:26:35.824Z" }, - { url = "https://files.pythonhosted.org/packages/e9/ec/4d1925f2e49617b9cca9c34bfa11adefad49d00db038e692a559454dfb2e/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799", size = 2321334, upload-time = "2025-08-10T21:26:37.534Z" }, - { url = "https://files.pythonhosted.org/packages/43/cb/450cd4499356f68802750c6ddc18647b8ea01ffa28f50d20598e0befe6e9/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3", size = 2488313, upload-time = "2025-08-10T21:26:39.191Z" }, - { url = "https://files.pythonhosted.org/packages/71/67/fc76242bd99f885651128a5d4fa6083e5524694b7c88b489b1b55fdc491d/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c", size = 2291970, upload-time = "2025-08-10T21:26:40.828Z" }, - { url = "https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d", size = 73894, upload-time = "2025-08-10T21:26:42.33Z" }, - { url = "https://files.pythonhosted.org/packages/95/38/dce480814d25b99a391abbddadc78f7c117c6da34be68ca8b02d5848b424/kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2", size = 64995, upload-time = "2025-08-10T21:26:43.889Z" }, - { url = "https://files.pythonhosted.org/packages/e2/37/7d218ce5d92dadc5ebdd9070d903e0c7cf7edfe03f179433ac4d13ce659c/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1", size = 126510, upload-time = "2025-08-10T21:26:44.915Z" }, - { url = "https://files.pythonhosted.org/packages/23/b0/e85a2b48233daef4b648fb657ebbb6f8367696a2d9548a00b4ee0eb67803/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1", size = 67903, upload-time = "2025-08-10T21:26:45.934Z" }, - { url = "https://files.pythonhosted.org/packages/44/98/f2425bc0113ad7de24da6bb4dae1343476e95e1d738be7c04d31a5d037fd/kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11", size = 66402, upload-time = "2025-08-10T21:26:47.101Z" }, - { url = "https://files.pythonhosted.org/packages/98/d8/594657886df9f34c4177cc353cc28ca7e6e5eb562d37ccc233bff43bbe2a/kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c", size = 1582135, upload-time = "2025-08-10T21:26:48.665Z" }, - { url = "https://files.pythonhosted.org/packages/5c/c6/38a115b7170f8b306fc929e166340c24958347308ea3012c2b44e7e295db/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197", size = 1389409, upload-time = "2025-08-10T21:26:50.335Z" }, - { url = "https://files.pythonhosted.org/packages/bf/3b/e04883dace81f24a568bcee6eb3001da4ba05114afa622ec9b6fafdc1f5e/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c", size = 1401763, upload-time = "2025-08-10T21:26:51.867Z" }, - { url = "https://files.pythonhosted.org/packages/9f/80/20ace48e33408947af49d7d15c341eaee69e4e0304aab4b7660e234d6288/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185", size = 1453643, upload-time = "2025-08-10T21:26:53.592Z" }, - { url = "https://files.pythonhosted.org/packages/64/31/6ce4380a4cd1f515bdda976a1e90e547ccd47b67a1546d63884463c92ca9/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748", size = 2330818, upload-time = "2025-08-10T21:26:55.051Z" }, - { url = "https://files.pythonhosted.org/packages/fa/e9/3f3fcba3bcc7432c795b82646306e822f3fd74df0ee81f0fa067a1f95668/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64", size = 2419963, upload-time = "2025-08-10T21:26:56.421Z" }, - { url = "https://files.pythonhosted.org/packages/99/43/7320c50e4133575c66e9f7dadead35ab22d7c012a3b09bb35647792b2a6d/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff", size = 2594639, upload-time = "2025-08-10T21:26:57.882Z" }, - { url = "https://files.pythonhosted.org/packages/65/d6/17ae4a270d4a987ef8a385b906d2bdfc9fce502d6dc0d3aea865b47f548c/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07", size = 2391741, upload-time = "2025-08-10T21:26:59.237Z" }, - { url = "https://files.pythonhosted.org/packages/2a/8f/8f6f491d595a9e5912971f3f863d81baddccc8a4d0c3749d6a0dd9ffc9df/kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c", size = 68646, upload-time = "2025-08-10T21:27:00.52Z" }, - { url = "https://files.pythonhosted.org/packages/6b/32/6cc0fbc9c54d06c2969faa9c1d29f5751a2e51809dd55c69055e62d9b426/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386", size = 123806, upload-time = "2025-08-10T21:27:01.537Z" }, - { url = "https://files.pythonhosted.org/packages/b2/dd/2bfb1d4a4823d92e8cbb420fe024b8d2167f72079b3bb941207c42570bdf/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552", size = 66605, upload-time = "2025-08-10T21:27:03.335Z" }, - { url = "https://files.pythonhosted.org/packages/f7/69/00aafdb4e4509c2ca6064646cba9cd4b37933898f426756adb2cb92ebbed/kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3", size = 64925, upload-time = "2025-08-10T21:27:04.339Z" }, - { url = "https://files.pythonhosted.org/packages/43/dc/51acc6791aa14e5cb6d8a2e28cefb0dc2886d8862795449d021334c0df20/kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58", size = 1472414, upload-time = "2025-08-10T21:27:05.437Z" }, - { url = "https://files.pythonhosted.org/packages/3d/bb/93fa64a81db304ac8a246f834d5094fae4b13baf53c839d6bb6e81177129/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4", size = 1281272, upload-time = "2025-08-10T21:27:07.063Z" }, - { url = "https://files.pythonhosted.org/packages/70/e6/6df102916960fb8d05069d4bd92d6d9a8202d5a3e2444494e7cd50f65b7a/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df", size = 1298578, upload-time = "2025-08-10T21:27:08.452Z" }, - { url = "https://files.pythonhosted.org/packages/7c/47/e142aaa612f5343736b087864dbaebc53ea8831453fb47e7521fa8658f30/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6", size = 1345607, upload-time = "2025-08-10T21:27:10.125Z" }, - { url = "https://files.pythonhosted.org/packages/54/89/d641a746194a0f4d1a3670fb900d0dbaa786fb98341056814bc3f058fa52/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5", size = 2230150, upload-time = "2025-08-10T21:27:11.484Z" }, - { url = "https://files.pythonhosted.org/packages/aa/6b/5ee1207198febdf16ac11f78c5ae40861b809cbe0e6d2a8d5b0b3044b199/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf", size = 2325979, upload-time = "2025-08-10T21:27:12.917Z" }, - { url = "https://files.pythonhosted.org/packages/fc/ff/b269eefd90f4ae14dcc74973d5a0f6d28d3b9bb1afd8c0340513afe6b39a/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5", size = 2491456, upload-time = "2025-08-10T21:27:14.353Z" }, - { url = "https://files.pythonhosted.org/packages/fc/d4/10303190bd4d30de547534601e259a4fbf014eed94aae3e5521129215086/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce", size = 2294621, upload-time = "2025-08-10T21:27:15.808Z" }, - { url = "https://files.pythonhosted.org/packages/28/e0/a9a90416fce5c0be25742729c2ea52105d62eda6c4be4d803c2a7be1fa50/kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7", size = 75417, upload-time = "2025-08-10T21:27:17.436Z" }, - { url = "https://files.pythonhosted.org/packages/1f/10/6949958215b7a9a264299a7db195564e87900f709db9245e4ebdd3c70779/kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c", size = 66582, upload-time = "2025-08-10T21:27:18.436Z" }, - { url = "https://files.pythonhosted.org/packages/ec/79/60e53067903d3bc5469b369fe0dfc6b3482e2133e85dae9daa9527535991/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548", size = 126514, upload-time = "2025-08-10T21:27:19.465Z" }, - { url = "https://files.pythonhosted.org/packages/25/d1/4843d3e8d46b072c12a38c97c57fab4608d36e13fe47d47ee96b4d61ba6f/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d", size = 67905, upload-time = "2025-08-10T21:27:20.51Z" }, - { url = "https://files.pythonhosted.org/packages/8c/ae/29ffcbd239aea8b93108de1278271ae764dfc0d803a5693914975f200596/kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c", size = 66399, upload-time = "2025-08-10T21:27:21.496Z" }, - { url = "https://files.pythonhosted.org/packages/a1/ae/d7ba902aa604152c2ceba5d352d7b62106bedbccc8e95c3934d94472bfa3/kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122", size = 1582197, upload-time = "2025-08-10T21:27:22.604Z" }, - { url = "https://files.pythonhosted.org/packages/f2/41/27c70d427eddb8bc7e4f16420a20fefc6f480312122a59a959fdfe0445ad/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64", size = 1390125, upload-time = "2025-08-10T21:27:24.036Z" }, - { url = "https://files.pythonhosted.org/packages/41/42/b3799a12bafc76d962ad69083f8b43b12bf4fe78b097b12e105d75c9b8f1/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134", size = 1402612, upload-time = "2025-08-10T21:27:25.773Z" }, - { url = "https://files.pythonhosted.org/packages/d2/b5/a210ea073ea1cfaca1bb5c55a62307d8252f531beb364e18aa1e0888b5a0/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370", size = 1453990, upload-time = "2025-08-10T21:27:27.089Z" }, - { url = "https://files.pythonhosted.org/packages/5f/ce/a829eb8c033e977d7ea03ed32fb3c1781b4fa0433fbadfff29e39c676f32/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21", size = 2331601, upload-time = "2025-08-10T21:27:29.343Z" }, - { url = "https://files.pythonhosted.org/packages/e0/4b/b5e97eb142eb9cd0072dacfcdcd31b1c66dc7352b0f7c7255d339c0edf00/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a", size = 2422041, upload-time = "2025-08-10T21:27:30.754Z" }, - { url = "https://files.pythonhosted.org/packages/40/be/8eb4cd53e1b85ba4edc3a9321666f12b83113a178845593307a3e7891f44/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f", size = 2594897, upload-time = "2025-08-10T21:27:32.803Z" }, - { url = "https://files.pythonhosted.org/packages/99/dd/841e9a66c4715477ea0abc78da039832fbb09dac5c35c58dc4c41a407b8a/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369", size = 2391835, upload-time = "2025-08-10T21:27:34.23Z" }, - { url = "https://files.pythonhosted.org/packages/0c/28/4b2e5c47a0da96896fdfdb006340ade064afa1e63675d01ea5ac222b6d52/kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891", size = 79988, upload-time = "2025-08-10T21:27:35.587Z" }, - { url = "https://files.pythonhosted.org/packages/80/be/3578e8afd18c88cdf9cb4cffde75a96d2be38c5a903f1ed0ceec061bd09e/kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32", size = 70260, upload-time = "2025-08-10T21:27:36.606Z" }, - { url = "https://files.pythonhosted.org/packages/a3/0f/36d89194b5a32c054ce93e586d4049b6c2c22887b0eb229c61c68afd3078/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5", size = 60104, upload-time = "2025-08-10T21:27:43.287Z" }, - { url = "https://files.pythonhosted.org/packages/52/ba/4ed75f59e4658fd21fe7dde1fee0ac397c678ec3befba3fe6482d987af87/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa", size = 58592, upload-time = "2025-08-10T21:27:44.314Z" }, - { url = "https://files.pythonhosted.org/packages/33/01/a8ea7c5ea32a9b45ceeaee051a04c8ed4320f5add3c51bfa20879b765b70/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2", size = 80281, upload-time = "2025-08-10T21:27:45.369Z" }, - { url = "https://files.pythonhosted.org/packages/da/e3/dbd2ecdce306f1d07a1aaf324817ee993aab7aee9db47ceac757deabafbe/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f", size = 78009, upload-time = "2025-08-10T21:27:46.376Z" }, - { url = "https://files.pythonhosted.org/packages/da/e9/0d4add7873a73e462aeb45c036a2dead2562b825aa46ba326727b3f31016/kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1", size = 73929, upload-time = "2025-08-10T21:27:48.236Z" }, +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/67/9c61eccb13f0bdca9307614e782fec49ffdde0f7a2314935d489fa93cd9c/kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a", size = 103482, upload-time = "2026-03-09T13:15:53.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/dd/a495a9c104be1c476f0386e714252caf2b7eca883915422a64c50b88c6f5/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9eed0f7edbb274413b6ee781cca50541c8c0facd3d6fd289779e494340a2b85c", size = 122798, upload-time = "2026-03-09T13:12:58.963Z" }, + { url = "https://files.pythonhosted.org/packages/11/60/37b4047a2af0cf5ef6d8b4b26e91829ae6fc6a2d1f74524bcb0e7cd28a32/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c4923e404d6bcd91b6779c009542e5647fef32e4a5d75e115e3bbac6f2335eb", size = 66216, upload-time = "2026-03-09T13:13:00.155Z" }, + { url = "https://files.pythonhosted.org/packages/0a/aa/510dc933d87767584abfe03efa445889996c70c2990f6f87c3ebaa0a18c5/kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0df54df7e686afa55e6f21fb86195224a6d9beb71d637e8d7920c95cf0f89aac", size = 63911, upload-time = "2026-03-09T13:13:01.671Z" }, + { url = "https://files.pythonhosted.org/packages/80/46/bddc13df6c2a40741e0cc7865bb1c9ed4796b6760bd04ce5fae3928ef917/kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2517e24d7315eb51c10664cdb865195df38ab74456c677df67bb47f12d088a27", size = 1438209, upload-time = "2026-03-09T13:13:03.385Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d6/76621246f5165e5372f02f5e6f3f48ea336a8f9e96e43997d45b240ed8cd/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff710414307fefa903e0d9bdf300972f892c23477829f49504e59834f4195398", size = 1248888, upload-time = "2026-03-09T13:13:05.231Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c1/31559ec6fb39a5b48035ce29bb63ade628f321785f38c384dee3e2c08bc1/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6176c1811d9d5a04fa391c490cc44f451e240697a16977f11c6f722efb9041db", size = 1266304, upload-time = "2026-03-09T13:13:06.743Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ef/1cb8276f2d29cc6a41e0a042f27946ca347d3a4a75acf85d0a16aa6dcc82/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50847dca5d197fcbd389c805aa1a1cf32f25d2e7273dc47ab181a517666b68cc", size = 1319650, upload-time = "2026-03-09T13:13:08.607Z" }, + { url = "https://files.pythonhosted.org/packages/4c/e4/5ba3cecd7ce6236ae4a80f67e5d5531287337d0e1f076ca87a5abe4cd5d0/kiwisolver-1.5.0-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:01808c6d15f4c3e8559595d6d1fe6411c68e4a3822b4b9972b44473b24f4e679", size = 970949, upload-time = "2026-03-09T13:13:10.299Z" }, + { url = "https://files.pythonhosted.org/packages/5a/69/dc61f7ae9a2f071f26004ced87f078235b5507ab6e5acd78f40365655034/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1f9f4121ec58628c96baa3de1a55a4e3a333c5102c8e94b64e23bf7b2083309", size = 2199125, upload-time = "2026-03-09T13:13:11.841Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7b/abbe0f1b5afa85f8d084b73e90e5f801c0939eba16ac2e49af7c61a6c28d/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b7d335370ae48a780c6e6a6bbfa97342f563744c39c35562f3f367665f5c1de2", size = 2293783, upload-time = "2026-03-09T13:13:14.399Z" }, + { url = "https://files.pythonhosted.org/packages/8a/80/5908ae149d96d81580d604c7f8aefd0e98f4fd728cf172f477e9f2a81744/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:800ee55980c18545af444d93fdd60c56b580db5cc54867d8cbf8a1dc0829938c", size = 1960726, upload-time = "2026-03-09T13:13:16.047Z" }, + { url = "https://files.pythonhosted.org/packages/84/08/a78cb776f8c085b7143142ce479859cfec086bd09ee638a317040b6ef420/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c438f6ca858697c9ab67eb28246c92508af972e114cac34e57a6d4ba17a3ac08", size = 2464738, upload-time = "2026-03-09T13:13:17.897Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e1/65584da5356ed6cb12c63791a10b208860ac40a83de165cb6a6751a686e3/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8c63c91f95173f9c2a67c7c526b2cea976828a0e7fced9cdcead2802dc10f8a4", size = 2270718, upload-time = "2026-03-09T13:13:19.421Z" }, + { url = "https://files.pythonhosted.org/packages/be/6c/28f17390b62b8f2f520e2915095b3c94d88681ecf0041e75389d9667f202/kiwisolver-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:beb7f344487cdcb9e1efe4b7a29681b74d34c08f0043a327a74da852a6749e7b", size = 73480, upload-time = "2026-03-09T13:13:20.818Z" }, + { url = "https://files.pythonhosted.org/packages/d8/0e/2ee5debc4f77a625778fec5501ff3e8036fe361b7ee28ae402a485bb9694/kiwisolver-1.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:ad4ae4ffd1ee9cd11357b4c66b612da9888f4f4daf2f36995eda64bd45370cac", size = 64930, upload-time = "2026-03-09T13:13:21.997Z" }, + { url = "https://files.pythonhosted.org/packages/4d/b2/818b74ebea34dabe6d0c51cb1c572e046730e64844da6ed646d5298c40ce/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9", size = 123158, upload-time = "2026-03-09T13:13:23.127Z" }, + { url = "https://files.pythonhosted.org/packages/bf/d9/405320f8077e8e1c5c4bd6adc45e1e6edf6d727b6da7f2e2533cf58bff71/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588", size = 66388, upload-time = "2026-03-09T13:13:24.765Z" }, + { url = "https://files.pythonhosted.org/packages/99/9f/795fedf35634f746151ca8839d05681ceb6287fbed6cc1c9bf235f7887c2/kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819", size = 64068, upload-time = "2026-03-09T13:13:25.878Z" }, + { url = "https://files.pythonhosted.org/packages/c4/13/680c54afe3e65767bed7ec1a15571e1a2f1257128733851ade24abcefbcc/kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f", size = 1477934, upload-time = "2026-03-09T13:13:27.166Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2f/cebfcdb60fd6a9b0f6b47a9337198bcbad6fbe15e68189b7011fd914911f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf", size = 1278537, upload-time = "2026-03-09T13:13:28.707Z" }, + { url = "https://files.pythonhosted.org/packages/f2/0d/9b782923aada3fafb1d6b84e13121954515c669b18af0c26e7d21f579855/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d", size = 1296685, upload-time = "2026-03-09T13:13:30.528Z" }, + { url = "https://files.pythonhosted.org/packages/27/70/83241b6634b04fe44e892688d5208332bde130f38e610c0418f9ede47ded/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083", size = 1346024, upload-time = "2026-03-09T13:13:32.818Z" }, + { url = "https://files.pythonhosted.org/packages/e4/db/30ed226fb271ae1a6431fc0fe0edffb2efe23cadb01e798caeb9f2ceae8f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6", size = 987241, upload-time = "2026-03-09T13:13:34.435Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bd/c314595208e4c9587652d50959ead9e461995389664e490f4dce7ff0f782/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1", size = 2227742, upload-time = "2026-03-09T13:13:36.4Z" }, + { url = "https://files.pythonhosted.org/packages/c1/43/0499cec932d935229b5543d073c2b87c9c22846aab48881e9d8d6e742a2d/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0", size = 2323966, upload-time = "2026-03-09T13:13:38.204Z" }, + { url = "https://files.pythonhosted.org/packages/3d/6f/79b0d760907965acfd9d61826a3d41f8f093c538f55cd2633d3f0db269f6/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15", size = 1977417, upload-time = "2026-03-09T13:13:39.966Z" }, + { url = "https://files.pythonhosted.org/packages/ab/31/01d0537c41cb75a551a438c3c7a80d0c60d60b81f694dac83dd436aec0d0/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314", size = 2491238, upload-time = "2026-03-09T13:13:41.698Z" }, + { url = "https://files.pythonhosted.org/packages/e4/34/8aefdd0be9cfd00a44509251ba864f5caf2991e36772e61c408007e7f417/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9", size = 2294947, upload-time = "2026-03-09T13:13:43.343Z" }, + { url = "https://files.pythonhosted.org/packages/ad/cf/0348374369ca588f8fe9c338fae49fa4e16eeb10ffb3d012f23a54578a9e/kiwisolver-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384", size = 73569, upload-time = "2026-03-09T13:13:45.792Z" }, + { url = "https://files.pythonhosted.org/packages/28/26/192b26196e2316e2bd29deef67e37cdf9870d9af8e085e521afff0fed526/kiwisolver-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7", size = 64997, upload-time = "2026-03-09T13:13:46.878Z" }, + { url = "https://files.pythonhosted.org/packages/9d/69/024d6711d5ba575aa65d5538042e99964104e97fa153a9f10bc369182bc2/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fd40bb9cd0891c4c3cb1ddf83f8bbfa15731a248fdc8162669405451e2724b09", size = 123166, upload-time = "2026-03-09T13:13:48.032Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/adbb40df306f587054a348831220812b9b1d787aff714cfbc8556e38fccd/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0e1403fd7c26d77c1f03e096dc58a5c726503fa0db0456678b8668f76f521e3", size = 66395, upload-time = "2026-03-09T13:13:49.365Z" }, + { url = "https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd", size = 64065, upload-time = "2026-03-09T13:13:50.562Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3", size = 1477903, upload-time = "2026-03-09T13:13:52.084Z" }, + { url = "https://files.pythonhosted.org/packages/18/d8/55638d89ffd27799d5cc3d8aa28e12f4ce7a64d67b285114dbedc8ea4136/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c50b89ffd3e1a911c69a1dd3de7173c0cd10b130f56222e57898683841e4f96", size = 1278751, upload-time = "2026-03-09T13:13:54.673Z" }, + { url = "https://files.pythonhosted.org/packages/b8/97/b4c8d0d18421ecceba20ad8701358453b88e32414e6f6950b5a4bad54e65/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4db576bb8c3ef9365f8b40fe0f671644de6736ae2c27a2c62d7d8a1b4329f099", size = 1296793, upload-time = "2026-03-09T13:13:56.287Z" }, + { url = "https://files.pythonhosted.org/packages/c4/10/f862f94b6389d8957448ec9df59450b81bec4abb318805375c401a1e6892/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b85aad90cea8ac6797a53b5d5f2e967334fa4d1149f031c4537569972596cb8", size = 1346041, upload-time = "2026-03-09T13:13:58.269Z" }, + { url = "https://files.pythonhosted.org/packages/a3/6a/f1650af35821eaf09de398ec0bc2aefc8f211f0cda50204c9f1673741ba9/kiwisolver-1.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:d36ca54cb4c6c4686f7cbb7b817f66f5911c12ddb519450bbe86707155028f87", size = 987292, upload-time = "2026-03-09T13:13:59.871Z" }, + { url = "https://files.pythonhosted.org/packages/de/19/d7fb82984b9238115fe629c915007be608ebd23dc8629703d917dbfaffd4/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:38f4a703656f493b0ad185211ccfca7f0386120f022066b018eb5296d8613e23", size = 2227865, upload-time = "2026-03-09T13:14:01.401Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b9/46b7f386589fd222dac9e9de9c956ce5bcefe2ee73b4e79891381dda8654/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ac2360e93cb41be81121755c6462cff3beaa9967188c866e5fce5cf13170859", size = 2324369, upload-time = "2026-03-09T13:14:02.972Z" }, + { url = "https://files.pythonhosted.org/packages/92/8b/95e237cf3d9c642960153c769ddcbe278f182c8affb20cecc1cc983e7cc5/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c95cab08d1965db3d84a121f1c7ce7479bdd4072c9b3dafd8fecce48a2e6b902", size = 1977989, upload-time = "2026-03-09T13:14:04.503Z" }, + { url = "https://files.pythonhosted.org/packages/1b/95/980c9df53501892784997820136c01f62bc1865e31b82b9560f980c0e649/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc20894c3d21194d8041a28b65622d5b86db786da6e3cfe73f0c762951a61167", size = 2491645, upload-time = "2026-03-09T13:14:06.106Z" }, + { url = "https://files.pythonhosted.org/packages/cb/32/900647fd0840abebe1561792c6b31e6a7c0e278fc3973d30572a965ca14c/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a32f72973f0f950c1920475d5c5ea3d971b81b6f0ec53b8d0a956cc965f22e0", size = 2295237, upload-time = "2026-03-09T13:14:08.891Z" }, + { url = "https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276", size = 73573, upload-time = "2026-03-09T13:14:12.327Z" }, + { url = "https://files.pythonhosted.org/packages/4d/d2/64be2e429eb4fca7f7e1c52a91b12663aeaf25de3895e5cca0f47ef2a8d0/kiwisolver-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa8eb9ecdb7efb0b226acec134e0d709e87a909fa4971a54c0c4f6e88635484c", size = 64998, upload-time = "2026-03-09T13:14:13.469Z" }, + { url = "https://files.pythonhosted.org/packages/b0/69/ce68dd0c85755ae2de490bf015b62f2cea5f6b14ff00a463f9d0774449ff/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db485b3847d182b908b483b2ed133c66d88d49cacf98fd278fadafe11b4478d1", size = 125700, upload-time = "2026-03-09T13:14:14.636Z" }, + { url = "https://files.pythonhosted.org/packages/74/aa/937aac021cf9d4349990d47eb319309a51355ed1dbdc9c077cdc9224cb11/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:be12f931839a3bdfe28b584db0e640a65a8bcbc24560ae3fdb025a449b3d754e", size = 67537, upload-time = "2026-03-09T13:14:15.808Z" }, + { url = "https://files.pythonhosted.org/packages/ee/20/3a87fbece2c40ad0f6f0aefa93542559159c5f99831d596050e8afae7a9f/kiwisolver-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:16b85d37c2cbb3253226d26e64663f755d88a03439a9c47df6246b35defbdfb7", size = 65514, upload-time = "2026-03-09T13:14:18.035Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7f/f943879cda9007c45e1f7dba216d705c3a18d6b35830e488b6c6a4e7cdf0/kiwisolver-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4432b835675f0ea7414aab3d37d119f7226d24869b7a829caeab49ebda407b0c", size = 1584848, upload-time = "2026-03-09T13:14:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/37/f8/4d4f85cc1870c127c88d950913370dd76138482161cd07eabbc450deff01/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b0feb50971481a2cc44d94e88bdb02cdd497618252ae226b8eb1201b957e368", size = 1391542, upload-time = "2026-03-09T13:14:21.54Z" }, + { url = "https://files.pythonhosted.org/packages/04/0b/65dd2916c84d252b244bd405303220f729e7c17c9d7d33dca6feeff9ffc4/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56fa888f10d0f367155e76ce849fa1166fc9730d13bd2d65a2aa13b6f5424489", size = 1404447, upload-time = "2026-03-09T13:14:23.205Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/2606a373247babce9b1d056c03a04b65f3cf5290a8eac5d7bdead0a17e21/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:940dda65d5e764406b9fb92761cbf462e4e63f712ab60ed98f70552e496f3bf1", size = 1455918, upload-time = "2026-03-09T13:14:24.74Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d1/c6078b5756670658e9192a2ef11e939c92918833d2745f85cd14a6004bdf/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:89fc958c702ee9a745e4700378f5d23fddbc46ff89e8fdbf5395c24d5c1452a3", size = 1072856, upload-time = "2026-03-09T13:14:26.597Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c8/7def6ddf16eb2b3741d8b172bdaa9af882b03c78e9b0772975408801fa63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9027d773c4ff81487181a925945743413f6069634d0b122d0b37684ccf4f1e18", size = 2333580, upload-time = "2026-03-09T13:14:28.237Z" }, + { url = "https://files.pythonhosted.org/packages/9e/87/2ac1fce0eb1e616fcd3c35caa23e665e9b1948bb984f4764790924594128/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5b233ea3e165e43e35dba1d2b8ecc21cf070b45b65ae17dd2747d2713d942021", size = 2423018, upload-time = "2026-03-09T13:14:30.018Z" }, + { url = "https://files.pythonhosted.org/packages/67/13/c6700ccc6cc218716bfcda4935e4b2997039869b4ad8a94f364c5a3b8e63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ce9bf03dad3b46408c08649c6fbd6ca28a9fce0eb32fdfffa6775a13103b5310", size = 2062804, upload-time = "2026-03-09T13:14:32.888Z" }, + { url = "https://files.pythonhosted.org/packages/1b/bd/877056304626943ff0f1f44c08f584300c199b887cb3176cd7e34f1515f1/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:fc4d3f1fb9ca0ae9f97b095963bc6326f1dbfd3779d6679a1e016b9baaa153d3", size = 2597482, upload-time = "2026-03-09T13:14:34.971Z" }, + { url = "https://files.pythonhosted.org/packages/75/19/c60626c47bf0f8ac5dcf72c6c98e266d714f2fbbfd50cf6dab5ede3aaa50/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f443b4825c50a51ee68585522ab4a1d1257fac65896f282b4c6763337ac9f5d2", size = 2394328, upload-time = "2026-03-09T13:14:36.816Z" }, + { url = "https://files.pythonhosted.org/packages/47/84/6a6d5e5bb8273756c27b7d810d47f7ef2f1f9b9fd23c9ee9a3f8c75c9cef/kiwisolver-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:893ff3a711d1b515ba9da14ee090519bad4610ed1962fbe298a434e8c5f8db53", size = 68410, upload-time = "2026-03-09T13:14:38.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/060f45052f2a01ad5762c8fdecd6d7a752b43400dc29ff75cd47225a40fd/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8df31fe574b8b3993cc61764f40941111b25c2d9fea13d3ce24a49907cd2d615", size = 123231, upload-time = "2026-03-09T13:14:41.323Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a7/78da680eadd06ff35edef6ef68a1ad273bad3e2a0936c9a885103230aece/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1d49a49ac4cbfb7c1375301cd1ec90169dfeae55ff84710d782260ce77a75a02", size = 66489, upload-time = "2026-03-09T13:14:42.534Z" }, + { url = "https://files.pythonhosted.org/packages/49/b2/97980f3ad4fae37dd7fe31626e2bf75fbf8bdf5d303950ec1fab39a12da8/kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0cbe94b69b819209a62cb27bdfa5dc2a8977d8de2f89dfd97ba4f53ed3af754e", size = 64063, upload-time = "2026-03-09T13:14:44.759Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f9/b06c934a6aa8bc91f566bd2a214fd04c30506c2d9e2b6b171953216a65b6/kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80aa065ffd378ff784822a6d7c3212f2d5f5e9c3589614b5c228b311fd3063ac", size = 1475913, upload-time = "2026-03-09T13:14:46.247Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f0/f768ae564a710135630672981231320bc403cf9152b5596ec5289de0f106/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e7f886f47ab881692f278ae901039a234e4025a68e6dfab514263a0b1c4ae05", size = 1282782, upload-time = "2026-03-09T13:14:48.458Z" }, + { url = "https://files.pythonhosted.org/packages/e2/9f/1de7aad00697325f05238a5f2eafbd487fb637cc27a558b5367a5f37fb7f/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5060731cc3ed12ca3a8b57acd4aeca5bbc2f49216dd0bec1650a1acd89486bcd", size = 1300815, upload-time = "2026-03-09T13:14:50.721Z" }, + { url = "https://files.pythonhosted.org/packages/5a/c2/297f25141d2e468e0ce7f7a7b92e0cf8918143a0cbd3422c1ad627e85a06/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a4aa69609f40fce3cbc3f87b2061f042eee32f94b8f11db707b66a26461591a", size = 1347925, upload-time = "2026-03-09T13:14:52.304Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d3/f4c73a02eb41520c47610207b21afa8cdd18fdbf64ffd94674ae21c4812d/kiwisolver-1.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:d168fda2dbff7b9b5f38e693182d792a938c31db4dac3a80a4888de603c99554", size = 991322, upload-time = "2026-03-09T13:14:54.637Z" }, + { url = "https://files.pythonhosted.org/packages/7b/46/d3f2efef7732fcda98d22bf4ad5d3d71d545167a852ca710a494f4c15343/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:413b820229730d358efd838ecbab79902fe97094565fdc80ddb6b0a18c18a581", size = 2232857, upload-time = "2026-03-09T13:14:56.471Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ec/2d9756bf2b6d26ae4349b8d3662fb3993f16d80c1f971c179ce862b9dbae/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5124d1ea754509b09e53738ec185584cc609aae4a3b510aaf4ed6aa047ef9303", size = 2329376, upload-time = "2026-03-09T13:14:58.072Z" }, + { url = "https://files.pythonhosted.org/packages/8f/9f/876a0a0f2260f1bde92e002b3019a5fabc35e0939c7d945e0fa66185eb20/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e4415a8db000bf49a6dd1c478bf70062eaacff0f462b92b0ba68791a905861f9", size = 1982549, upload-time = "2026-03-09T13:14:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4f/ba3624dfac23a64d54ac4179832860cb537c1b0af06024936e82ca4154a0/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d618fd27420381a4f6044faa71f46d8bfd911bd077c555f7138ed88729bfbe79", size = 2494680, upload-time = "2026-03-09T13:15:01.364Z" }, + { url = "https://files.pythonhosted.org/packages/39/b7/97716b190ab98911b20d10bf92eca469121ec483b8ce0edd314f51bc85af/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5092eb5b1172947f57d6ea7d89b2f29650414e4293c47707eb499ec07a0ac796", size = 2297905, upload-time = "2026-03-09T13:15:03.925Z" }, + { url = "https://files.pythonhosted.org/packages/a3/36/4e551e8aa55c9188bca9abb5096805edbf7431072b76e2298e34fd3a3008/kiwisolver-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e", size = 75086, upload-time = "2026-03-09T13:15:07.775Z" }, + { url = "https://files.pythonhosted.org/packages/70/15/9b90f7df0e31a003c71649cf66ef61c3c1b862f48c81007fa2383c8bd8d7/kiwisolver-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df", size = 66577, upload-time = "2026-03-09T13:15:09.139Z" }, + { url = "https://files.pythonhosted.org/packages/17/01/7dc8c5443ff42b38e72731643ed7cf1ed9bf01691ae5cdca98501999ed83/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d1ffeb80b5676463d7a7d56acbe8e37a20ce725570e09549fe738e02ca6b7e1e", size = 125794, upload-time = "2026-03-09T13:15:10.525Z" }, + { url = "https://files.pythonhosted.org/packages/46/8a/b4ebe46ebaac6a303417fab10c2e165c557ddaff558f9699d302b256bc53/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc4d8e252f532ab46a1de9349e2d27b91fce46736a9eedaa37beaca66f574ed4", size = 67646, upload-time = "2026-03-09T13:15:12.016Z" }, + { url = "https://files.pythonhosted.org/packages/60/35/10a844afc5f19d6f567359bf4789e26661755a2f36200d5d1ed8ad0126e5/kiwisolver-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6783e069732715ad0c3ce96dbf21dbc2235ab0593f2baf6338101f70371f4028", size = 65511, upload-time = "2026-03-09T13:15:13.311Z" }, + { url = "https://files.pythonhosted.org/packages/f8/8a/685b297052dd041dcebce8e8787b58923b6e78acc6115a0dc9189011c44b/kiwisolver-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7c4c09a490dc4d4a7f8cbee56c606a320f9dc28cf92a7157a39d1ce7676a657", size = 1584858, upload-time = "2026-03-09T13:15:15.103Z" }, + { url = "https://files.pythonhosted.org/packages/9e/80/04865e3d4638ac5bddec28908916df4a3075b8c6cc101786a96803188b96/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a075bd7bd19c70cf67c8badfa36cf7c5d8de3c9ddb8420c51e10d9c50e94920", size = 1392539, upload-time = "2026-03-09T13:15:16.661Z" }, + { url = "https://files.pythonhosted.org/packages/ba/01/77a19cacc0893fa13fafa46d1bba06fb4dc2360b3292baf4b56d8e067b24/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bdd3e53429ff02aa319ba59dfe4ceeec345bf46cf180ec2cf6fd5b942e7975e9", size = 1405310, upload-time = "2026-03-09T13:15:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/53/39/bcaf5d0cca50e604cfa9b4e3ae1d64b50ca1ae5b754122396084599ef903/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cdcb35dc9d807259c981a85531048ede628eabcffb3239adf3d17463518992d", size = 1456244, upload-time = "2026-03-09T13:15:20.444Z" }, + { url = "https://files.pythonhosted.org/packages/d0/7a/72c187abc6975f6978c3e39b7cf67aeb8b3c0a8f9790aa7fd412855e9e1f/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:70d593af6a6ca332d1df73d519fddb5148edb15cd90d5f0155e3746a6d4fcc65", size = 1073154, upload-time = "2026-03-09T13:15:22.039Z" }, + { url = "https://files.pythonhosted.org/packages/c7/ca/cf5b25783ebbd59143b4371ed0c8428a278abe68d6d0104b01865b1bbd0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:377815a8616074cabbf3f53354e1d040c35815a134e01d7614b7692e4bf8acfa", size = 2334377, upload-time = "2026-03-09T13:15:23.741Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e5/b1f492adc516796e88751282276745340e2a72dcd0d36cf7173e0daf3210/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0255a027391d52944eae1dbb5d4cc5903f57092f3674e8e544cdd2622826b3f0", size = 2425288, upload-time = "2026-03-09T13:15:25.789Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e5/9b21fbe91a61b8f409d74a26498706e97a48008bfcd1864373d32a6ba31c/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9", size = 2063158, upload-time = "2026-03-09T13:15:27.63Z" }, + { url = "https://files.pythonhosted.org/packages/b1/02/83f47986138310f95ea95531f851b2a62227c11cbc3e690ae1374fe49f0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e3aafb33aed7479377e5e9a82e9d4bf87063741fc99fc7ae48b0f16e32bdd6f", size = 2597260, upload-time = "2026-03-09T13:15:29.421Z" }, + { url = "https://files.pythonhosted.org/packages/07/18/43a5f24608d8c313dd189cf838c8e68d75b115567c6279de7796197cfb6a/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7a116ae737f0000343218c4edf5bd45893bfeaff0993c0b215d7124c9f77646", size = 2394403, upload-time = "2026-03-09T13:15:31.517Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b5/98222136d839b8afabcaa943b09bd05888c2d36355b7e448550211d1fca4/kiwisolver-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681", size = 79687, upload-time = "2026-03-09T13:15:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/99/a2/ca7dc962848040befed12732dff6acae7fb3c4f6fc4272b3f6c9a30b8713/kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57", size = 70032, upload-time = "2026-03-09T13:15:34.411Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fa/2910df836372d8761bb6eff7d8bdcb1613b5c2e03f260efe7abe34d388a7/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797", size = 130262, upload-time = "2026-03-09T13:15:35.629Z" }, + { url = "https://files.pythonhosted.org/packages/0f/41/c5f71f9f00aabcc71fee8b7475e3f64747282580c2fe748961ba29b18385/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f6764a4ccab3078db14a632420930f6186058750df066b8ea2a7106df91d3203", size = 138036, upload-time = "2026-03-09T13:15:36.894Z" }, + { url = "https://files.pythonhosted.org/packages/fa/06/7399a607f434119c6e1fdc8ec89a8d51ccccadf3341dee4ead6bd14caaf5/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31c13da98624f957b0fb1b5bae5383b2333c2c3f6793d9825dd5ce79b525cb7", size = 194295, upload-time = "2026-03-09T13:15:38.22Z" }, + { url = "https://files.pythonhosted.org/packages/b5/91/53255615acd2a1eaca307ede3c90eb550bae9c94581f8c00081b6b1c8f44/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:1f1489f769582498610e015a8ef2d36f28f505ab3096d0e16b4858a9ec214f57", size = 75987, upload-time = "2026-03-09T13:15:39.65Z" }, + { url = "https://files.pythonhosted.org/packages/e9/eb/5fcbbbf9a0e2c3a35effb88831a483345326bbc3a030a3b5b69aee647f84/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ec4c85dc4b687c7f7f15f553ff26a98bfe8c58f5f7f0ac8905f0ba4c7be60232", size = 59532, upload-time = "2026-03-09T13:15:47.047Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9b/e17104555bb4db148fd52327feea1e96be4b88e8e008b029002c281a21ab/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:12e91c215a96e39f57989c8912ae761286ac5a9584d04030ceb3368a357f017a", size = 57420, upload-time = "2026-03-09T13:15:48.199Z" }, + { url = "https://files.pythonhosted.org/packages/48/44/2b5b95b7aa39fb2d8d9d956e0f3d5d45aef2ae1d942d4c3ffac2f9cfed1a/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be4a51a55833dc29ab5d7503e7bcb3b3af3402d266018137127450005cdfe737", size = 79892, upload-time = "2026-03-09T13:15:49.694Z" }, + { url = "https://files.pythonhosted.org/packages/52/7d/7157f9bba6b455cfb4632ed411e199fc8b8977642c2b12082e1bd9e6d173/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daae526907e262de627d8f70058a0f64acc9e2641c164c99c8f594b34a799a16", size = 77603, upload-time = "2026-03-09T13:15:50.945Z" }, + { url = "https://files.pythonhosted.org/packages/0a/dd/8050c947d435c8d4bc94e3252f4d8bb8a76cfb424f043a8680be637a57f1/kiwisolver-1.5.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:59cd8683f575d96df5bb48f6add94afc055012c29e28124fcae2b63661b9efb1", size = 73558, upload-time = "2026-03-09T13:15:52.112Z" }, ] [[package]] @@ -2174,7 +2206,7 @@ wheels = [ [[package]] name = "optuna" -version = "4.7.0" +version = "4.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alembic" }, @@ -2185,9 +2217,9 @@ dependencies = [ { name = "sqlalchemy" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/b2/b5e12de7b4486556fe2257611b55dbabf30d0300bdb031831aa943ad20e4/optuna-4.7.0.tar.gz", hash = "sha256:d91817e2079825557bd2e97de2e8c9ae260bfc99b32712502aef8a5095b2d2c0", size = 479740, upload-time = "2026-01-19T05:45:52.604Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/9b/62f120fb2ecbc4338bee70c5a3671c8e561714f3aa1a046b897ff142050e/optuna-4.8.0.tar.gz", hash = "sha256:6f7043e9f8ecb5e607af86a7eb00fb5ec2be26c3b08c201209a73d36aff37a38", size = 482603, upload-time = "2026-03-16T04:59:58.659Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/75/d1/6c8a4fbb38a9e3565f5c36b871262a85ecab3da48120af036b1e4937a15c/optuna-4.7.0-py3-none-any.whl", hash = "sha256:e41ec84018cecc10eabf28143573b1f0bde0ba56dba8151631a590ecbebc1186", size = 413894, upload-time = "2026-01-19T05:45:50.815Z" }, + { url = "https://files.pythonhosted.org/packages/ac/24/7c731839566d30dc70556d9824ef17692d896c15e3df627bce8c16f753e1/optuna-4.8.0-py3-none-any.whl", hash = "sha256:c57a7682679c36bfc9bca0da430698179e513874074b71bebedb0334964ab930", size = 419456, upload-time = "2026-03-16T04:59:56.977Z" }, ] [[package]] @@ -2589,15 +2621,15 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.1.1" +version = "1.1.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ec/67/09765eacf4e44413c4f8943ba5a317fcb9c7b447c3b8b0b7fce7e3090b0b/python_discovery-1.1.1.tar.gz", hash = "sha256:584c08b141c5b7029f206b4e8b78b1a1764b22121e21519b89dec56936e95b0a", size = 56016, upload-time = "2026-03-07T00:00:56.354Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/7e/9f3b0dd3a074a6c3e1e79f35e465b1f2ee4b262d619de00cfce523cc9b24/python_discovery-1.1.3.tar.gz", hash = "sha256:7acca36e818cd88e9b2ba03e045ad7e93e1713e29c6bbfba5d90202310b7baa5", size = 56945, upload-time = "2026-03-10T15:08:15.038Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/75/0f/2bf7e3b5a4a65f623cb820feb5793e243fad58ae561015ee15a6152f67a2/python_discovery-1.1.1-py3-none-any.whl", hash = "sha256:69f11073fa2392251e405d4e847d60ffffd25fd762a0dc4d1a7d6b9c3f79f1a3", size = 30732, upload-time = "2026-03-07T00:00:55.143Z" }, + { url = "https://files.pythonhosted.org/packages/e7/80/73211fc5bfbfc562369b4aa61dc1e4bf07dc7b34df7b317e4539316b809c/python_discovery-1.1.3-py3-none-any.whl", hash = "sha256:90e795f0121bc84572e737c9aa9966311b9fde44ffb88a5953b3ec9b31c6945e", size = 31485, upload-time = "2026-03-10T15:08:13.06Z" }, ] [[package]] @@ -3062,27 +3094,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/77/9b/840e0039e65fcf12758adf684d2289024d6140cde9268cc59887dc55189c/ruff-0.15.5.tar.gz", hash = "sha256:7c3601d3b6d76dce18c5c824fc8d06f4eef33d6df0c21ec7799510cde0f159a2", size = 4574214, upload-time = "2026-03-05T20:06:34.946Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/47/20/5369c3ce21588c708bcbe517a8fbe1a8dfdb5dfd5137e14790b1da71612c/ruff-0.15.5-py3-none-linux_armv6l.whl", hash = "sha256:4ae44c42281f42e3b06b988e442d344a5b9b72450ff3c892e30d11b29a96a57c", size = 10478185, upload-time = "2026-03-05T20:06:29.093Z" }, - { url = "https://files.pythonhosted.org/packages/44/ed/e81dd668547da281e5dce710cf0bc60193f8d3d43833e8241d006720e42b/ruff-0.15.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6edd3792d408ebcf61adabc01822da687579a1a023f297618ac27a5b51ef0080", size = 10859201, upload-time = "2026-03-05T20:06:32.632Z" }, - { url = "https://files.pythonhosted.org/packages/c4/8f/533075f00aaf19b07c5cd6aa6e5d89424b06b3b3f4583bfa9c640a079059/ruff-0.15.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:89f463f7c8205a9f8dea9d658d59eff49db05f88f89cc3047fb1a02d9f344010", size = 10184752, upload-time = "2026-03-05T20:06:40.312Z" }, - { url = "https://files.pythonhosted.org/packages/66/0e/ba49e2c3fa0395b3152bad634c7432f7edfc509c133b8f4529053ff024fb/ruff-0.15.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba786a8295c6574c1116704cf0b9e6563de3432ac888d8f83685654fe528fd65", size = 10534857, upload-time = "2026-03-05T20:06:19.581Z" }, - { url = "https://files.pythonhosted.org/packages/59/71/39234440f27a226475a0659561adb0d784b4d247dfe7f43ffc12dd02e288/ruff-0.15.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd4b801e57955fe9f02b31d20375ab3a5c4415f2e5105b79fb94cf2642c91440", size = 10309120, upload-time = "2026-03-05T20:06:00.435Z" }, - { url = "https://files.pythonhosted.org/packages/f5/87/4140aa86a93df032156982b726f4952aaec4a883bb98cb6ef73c347da253/ruff-0.15.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:391f7c73388f3d8c11b794dbbc2959a5b5afe66642c142a6effa90b45f6f5204", size = 11047428, upload-time = "2026-03-05T20:05:51.867Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f7/4953e7e3287676f78fbe85e3a0ca414c5ca81237b7575bdadc00229ac240/ruff-0.15.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dc18f30302e379fe1e998548b0f5e9f4dff907f52f73ad6da419ea9c19d66c8", size = 11914251, upload-time = "2026-03-05T20:06:22.887Z" }, - { url = "https://files.pythonhosted.org/packages/77/46/0f7c865c10cf896ccf5a939c3e84e1cfaeed608ff5249584799a74d33835/ruff-0.15.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc6e7f90087e2d27f98dc34ed1b3ab7c8f0d273cc5431415454e22c0bd2a681", size = 11333801, upload-time = "2026-03-05T20:05:57.168Z" }, - { url = "https://files.pythonhosted.org/packages/d3/01/a10fe54b653061585e655f5286c2662ebddb68831ed3eaebfb0eb08c0a16/ruff-0.15.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1cb7169f53c1ddb06e71a9aebd7e98fc0fea936b39afb36d8e86d36ecc2636a", size = 11206821, upload-time = "2026-03-05T20:06:03.441Z" }, - { url = "https://files.pythonhosted.org/packages/7a/0d/2132ceaf20c5e8699aa83da2706ecb5c5dcdf78b453f77edca7fb70f8a93/ruff-0.15.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9b037924500a31ee17389b5c8c4d88874cc6ea8e42f12e9c61a3d754ff72f1ca", size = 11133326, upload-time = "2026-03-05T20:06:25.655Z" }, - { url = "https://files.pythonhosted.org/packages/72/cb/2e5259a7eb2a0f87c08c0fe5bf5825a1e4b90883a52685524596bfc93072/ruff-0.15.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:65bb414e5b4eadd95a8c1e4804f6772bbe8995889f203a01f77ddf2d790929dd", size = 10510820, upload-time = "2026-03-05T20:06:37.79Z" }, - { url = "https://files.pythonhosted.org/packages/ff/20/b67ce78f9e6c59ffbdb5b4503d0090e749b5f2d31b599b554698a80d861c/ruff-0.15.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d20aa469ae3b57033519c559e9bc9cd9e782842e39be05b50e852c7c981fa01d", size = 10302395, upload-time = "2026-03-05T20:05:54.504Z" }, - { url = "https://files.pythonhosted.org/packages/5f/e5/719f1acccd31b720d477751558ed74e9c88134adcc377e5e886af89d3072/ruff-0.15.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:15388dd28c9161cdb8eda68993533acc870aa4e646a0a277aa166de9ad5a8752", size = 10754069, upload-time = "2026-03-05T20:06:06.422Z" }, - { url = "https://files.pythonhosted.org/packages/c3/9c/d1db14469e32d98f3ca27079dbd30b7b44dbb5317d06ab36718dee3baf03/ruff-0.15.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b30da330cbd03bed0c21420b6b953158f60c74c54c5f4c1dabbdf3a57bf355d2", size = 11304315, upload-time = "2026-03-05T20:06:10.867Z" }, - { url = "https://files.pythonhosted.org/packages/28/3a/950367aee7c69027f4f422059227b290ed780366b6aecee5de5039d50fa8/ruff-0.15.5-py3-none-win32.whl", hash = "sha256:732e5ee1f98ba5b3679029989a06ca39a950cced52143a0ea82a2102cb592b74", size = 10551676, upload-time = "2026-03-05T20:06:13.705Z" }, - { url = "https://files.pythonhosted.org/packages/b8/00/bf077a505b4e649bdd3c47ff8ec967735ce2544c8e4a43aba42ee9bf935d/ruff-0.15.5-py3-none-win_amd64.whl", hash = "sha256:821d41c5fa9e19117616c35eaa3f4b75046ec76c65e7ae20a333e9a8696bc7fe", size = 11678972, upload-time = "2026-03-05T20:06:45.379Z" }, - { url = "https://files.pythonhosted.org/packages/fe/4e/cd76eca6db6115604b7626668e891c9dd03330384082e33662fb0f113614/ruff-0.15.5-py3-none-win_arm64.whl", hash = "sha256:b498d1c60d2fe5c10c45ec3f698901065772730b411f164ae270bb6bfcc4740b", size = 10965572, upload-time = "2026-03-05T20:06:16.984Z" }, +version = "0.15.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/df/f8629c19c5318601d3121e230f74cbee7a3732339c52b21daa2b82ef9c7d/ruff-0.15.6.tar.gz", hash = "sha256:8394c7bb153a4e3811a4ecdacd4a8e6a4fa8097028119160dffecdcdf9b56ae4", size = 4597916, upload-time = "2026-03-12T23:05:47.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/2f/4e03a7e5ce99b517e98d3b4951f411de2b0fa8348d39cf446671adcce9a2/ruff-0.15.6-py3-none-linux_armv6l.whl", hash = "sha256:7c98c3b16407b2cf3d0f2b80c80187384bc92c6774d85fefa913ecd941256fff", size = 10508953, upload-time = "2026-03-12T23:05:17.246Z" }, + { url = "https://files.pythonhosted.org/packages/70/60/55bcdc3e9f80bcf39edf0cd272da6fa511a3d94d5a0dd9e0adf76ceebdb4/ruff-0.15.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ee7dcfaad8b282a284df4aa6ddc2741b3f4a18b0555d626805555a820ea181c3", size = 10942257, upload-time = "2026-03-12T23:05:23.076Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f9/005c29bd1726c0f492bfa215e95154cf480574140cb5f867c797c18c790b/ruff-0.15.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3bd9967851a25f038fc8b9ae88a7fbd1b609f30349231dffaa37b6804923c4bb", size = 10322683, upload-time = "2026-03-12T23:05:33.738Z" }, + { url = "https://files.pythonhosted.org/packages/5f/74/2f861f5fd7cbb2146bddb5501450300ce41562da36d21868c69b7a828169/ruff-0.15.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13f4594b04e42cd24a41da653886b04d2ff87adbf57497ed4f728b0e8a4866f8", size = 10660986, upload-time = "2026-03-12T23:05:53.245Z" }, + { url = "https://files.pythonhosted.org/packages/c1/a1/309f2364a424eccb763cdafc49df843c282609f47fe53aa83f38272389e0/ruff-0.15.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e2ed8aea2f3fe57886d3f00ea5b8aae5bf68d5e195f487f037a955ff9fbaac9e", size = 10332177, upload-time = "2026-03-12T23:05:56.145Z" }, + { url = "https://files.pythonhosted.org/packages/30/41/7ebf1d32658b4bab20f8ac80972fb19cd4e2c6b78552be263a680edc55ac/ruff-0.15.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70789d3e7830b848b548aae96766431c0dc01a6c78c13381f423bf7076c66d15", size = 11170783, upload-time = "2026-03-12T23:06:01.742Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/6d488f6adca047df82cd62c304638bcb00821c36bd4881cfca221561fdfc/ruff-0.15.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:542aaf1de3154cea088ced5a819ce872611256ffe2498e750bbae5247a8114e9", size = 12044201, upload-time = "2026-03-12T23:05:28.697Z" }, + { url = "https://files.pythonhosted.org/packages/71/68/e6f125df4af7e6d0b498f8d373274794bc5156b324e8ab4bf5c1b4fc0ec7/ruff-0.15.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c22e6f02c16cfac3888aa636e9eba857254d15bbacc9906c9689fdecb1953ab", size = 11421561, upload-time = "2026-03-12T23:05:31.236Z" }, + { url = "https://files.pythonhosted.org/packages/f1/9f/f85ef5fd01a52e0b472b26dc1b4bd228b8f6f0435975442ffa4741278703/ruff-0.15.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98893c4c0aadc8e448cfa315bd0cc343a5323d740fe5f28ef8a3f9e21b381f7e", size = 11310928, upload-time = "2026-03-12T23:05:45.288Z" }, + { url = "https://files.pythonhosted.org/packages/8c/26/b75f8c421f5654304b89471ed384ae8c7f42b4dff58fa6ce1626d7f2b59a/ruff-0.15.6-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:70d263770d234912374493e8cc1e7385c5d49376e41dfa51c5c3453169dc581c", size = 11235186, upload-time = "2026-03-12T23:05:50.677Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d4/d5a6d065962ff7a68a86c9b4f5500f7d101a0792078de636526c0edd40da/ruff-0.15.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:55a1ad63c5a6e54b1f21b7514dfadc0c7fb40093fa22e95143cf3f64ebdcd512", size = 10635231, upload-time = "2026-03-12T23:05:37.044Z" }, + { url = "https://files.pythonhosted.org/packages/d6/56/7c3acf3d50910375349016cf33de24be021532042afbed87942858992491/ruff-0.15.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8dc473ba093c5ec238bb1e7429ee676dca24643c471e11fbaa8a857925b061c0", size = 10340357, upload-time = "2026-03-12T23:06:04.748Z" }, + { url = "https://files.pythonhosted.org/packages/06/54/6faa39e9c1033ff6a3b6e76b5df536931cd30caf64988e112bbf91ef5ce5/ruff-0.15.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:85b042377c2a5561131767974617006f99f7e13c63c111b998f29fc1e58a4cfb", size = 10860583, upload-time = "2026-03-12T23:05:58.978Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/509a201b843b4dfb0b32acdedf68d951d3377988cae43949ba4c4133a96a/ruff-0.15.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cef49e30bc5a86a6a92098a7fbf6e467a234d90b63305d6f3ec01225a9d092e0", size = 11410976, upload-time = "2026-03-12T23:05:39.955Z" }, + { url = "https://files.pythonhosted.org/packages/6c/25/3fc9114abf979a41673ce877c08016f8e660ad6cf508c3957f537d2e9fa9/ruff-0.15.6-py3-none-win32.whl", hash = "sha256:bbf67d39832404812a2d23020dda68fee7f18ce15654e96fb1d3ad21a5fe436c", size = 10616872, upload-time = "2026-03-12T23:05:42.451Z" }, + { url = "https://files.pythonhosted.org/packages/89/7a/09ece68445ceac348df06e08bf75db72d0e8427765b96c9c0ffabc1be1d9/ruff-0.15.6-py3-none-win_amd64.whl", hash = "sha256:aee25bc84c2f1007ecb5037dff75cef00414fdf17c23f07dc13e577883dca406", size = 11787271, upload-time = "2026-03-12T23:05:20.168Z" }, + { url = "https://files.pythonhosted.org/packages/7f/d0/578c47dd68152ddddddf31cd7fc67dc30b7cdf639a86275fda821b0d9d98/ruff-0.15.6-py3-none-win_arm64.whl", hash = "sha256:c34de3dd0b0ba203be50ae70f5910b17188556630e2178fd7d79fc030eb0d837", size = 11060497, upload-time = "2026-03-12T23:05:25.968Z" }, ] [[package]] @@ -3233,11 +3265,11 @@ wheels = [ [[package]] name = "setuptools" -version = "82.0.0" +version = "82.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/f3/748f4d6f65d1756b9ae577f329c951cda23fb900e4de9f70900ced962085/setuptools-82.0.0.tar.gz", hash = "sha256:22e0a2d69474c6ae4feb01951cb69d515ed23728cf96d05513d36e42b62b37cb", size = 1144893, upload-time = "2026-02-08T15:08:40.206Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl", hash = "sha256:70b18734b607bd1da571d097d236cfcfacaf01de45717d59e6e04b96877532e0", size = 1003468, upload-time = "2026-02-08T15:08:38.723Z" }, + { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" }, ] [[package]] @@ -3502,6 +3534,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0f/8b/4b61d6e13f7108f36910df9ab4b58fd389cc2520d54d81b88660804aad99/torch-2.10.0-2-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:418997cb02d0a0f1497cf6a09f63166f9f5df9f3e16c8a716ab76a72127c714f", size = 79423467, upload-time = "2026-02-10T21:44:48.711Z" }, { url = "https://files.pythonhosted.org/packages/d3/54/a2ba279afcca44bbd320d4e73675b282fcee3d81400ea1b53934efca6462/torch-2.10.0-2-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:13ec4add8c3faaed8d13e0574f5cd4a323c11655546f91fbe6afa77b57423574", size = 79498202, upload-time = "2026-02-10T21:44:52.603Z" }, { url = "https://files.pythonhosted.org/packages/ec/23/2c9fe0c9c27f7f6cb865abcea8a4568f29f00acaeadfc6a37f6801f84cb4/torch-2.10.0-2-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:e521c9f030a3774ed770a9c011751fb47c4d12029a3d6522116e48431f2ff89e", size = 79498254, upload-time = "2026-02-10T21:44:44.095Z" }, + { url = "https://files.pythonhosted.org/packages/36/ab/7b562f1808d3f65414cd80a4f7d4bb00979d9355616c034c171249e1a303/torch-2.10.0-3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:ac5bdcbb074384c66fa160c15b1ead77839e3fe7ed117d667249afce0acabfac", size = 915518691, upload-time = "2026-03-11T14:15:43.147Z" }, + { url = "https://files.pythonhosted.org/packages/b3/7a/abada41517ce0011775f0f4eacc79659bc9bc6c361e6bfe6f7052a6b9363/torch-2.10.0-3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:98c01b8bb5e3240426dcde1446eed6f40c778091c8544767ef1168fc663a05a6", size = 915622781, upload-time = "2026-03-11T14:17:11.354Z" }, + { url = "https://files.pythonhosted.org/packages/ab/c6/4dfe238342ffdcec5aef1c96c457548762d33c40b45a1ab7033bb26d2ff2/torch-2.10.0-3-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:80b1b5bfe38eb0e9f5ff09f206dcac0a87aadd084230d4a36eea5ec5232c115b", size = 915627275, upload-time = "2026-03-11T14:16:11.325Z" }, + { url = "https://files.pythonhosted.org/packages/d8/f0/72bf18847f58f877a6a8acf60614b14935e2f156d942483af1ffc081aea0/torch-2.10.0-3-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:46b3574d93a2a8134b3f5475cfb98e2eb46771794c57015f6ad1fb795ec25e49", size = 915523474, upload-time = "2026-03-11T14:17:44.422Z" }, + { url = "https://files.pythonhosted.org/packages/f4/39/590742415c3030551944edc2ddc273ea1fdfe8ffb2780992e824f1ebee98/torch-2.10.0-3-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:b1d5e2aba4eb7f8e87fbe04f86442887f9167a35f092afe4c237dfcaaef6e328", size = 915632474, upload-time = "2026-03-11T14:15:13.666Z" }, + { url = "https://files.pythonhosted.org/packages/b6/8e/34949484f764dde5b222b7fe3fede43e4a6f0da9d7f8c370bb617d629ee2/torch-2.10.0-3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:0228d20b06701c05a8f978357f657817a4a63984b0c90745def81c18aedfa591", size = 915523882, upload-time = "2026-03-11T14:14:46.311Z" }, { url = "https://files.pythonhosted.org/packages/78/89/f5554b13ebd71e05c0b002f95148033e730d3f7067f67423026cc9c69410/torch-2.10.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:3282d9febd1e4e476630a099692b44fdc214ee9bf8ee5377732d9d9dfe5712e4", size = 145992610, upload-time = "2026-01-21T16:25:26.327Z" }, { url = "https://files.pythonhosted.org/packages/ae/30/a3a2120621bf9c17779b169fc17e3dc29b230c29d0f8222f499f5e159aa8/torch-2.10.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a2f9edd8dbc99f62bc4dfb78af7bf89499bca3d753423ac1b4e06592e467b763", size = 915607863, upload-time = "2026-01-21T16:25:06.696Z" }, { url = "https://files.pythonhosted.org/packages/6f/3d/c87b33c5f260a2a8ad68da7147e105f05868c281c63d65ed85aa4da98c66/torch-2.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:29b7009dba4b7a1c960260fc8ac85022c784250af43af9fb0ebafc9883782ebd", size = 113723116, upload-time = "2026-01-21T16:25:21.916Z" }, @@ -3539,7 +3577,7 @@ wheels = [ [[package]] name = "torchmetrics" -version = "1.8.2" +version = "1.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "lightning-utilities" }, @@ -3547,9 +3585,9 @@ dependencies = [ { name = "packaging" }, { name = "torch" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/85/2e/48a887a59ecc4a10ce9e8b35b3e3c5cef29d902c4eac143378526e7485cb/torchmetrics-1.8.2.tar.gz", hash = "sha256:cf64a901036bf107f17a524009eea7781c9c5315d130713aeca5747a686fe7a5", size = 580679, upload-time = "2025-09-03T14:00:54.077Z" } +sdist = { url = "https://files.pythonhosted.org/packages/81/34/39b8b749333db56c0585d7a11fa62a283c087bb1dfc897d69fb8cedbefb1/torchmetrics-1.9.0.tar.gz", hash = "sha256:a488609948600df52d3db4fcdab02e62aab2a85ef34da67037dc3e65b8512faa", size = 581765, upload-time = "2026-03-09T17:41:22.443Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/21/aa0f434434c48490f91b65962b1ce863fdcce63febc166ca9fe9d706c2b6/torchmetrics-1.8.2-py3-none-any.whl", hash = "sha256:08382fd96b923e39e904c4d570f3d49e2cc71ccabd2a94e0f895d1f0dac86242", size = 983161, upload-time = "2025-09-03T14:00:51.921Z" }, + { url = "https://files.pythonhosted.org/packages/c3/a2/c7f6ebf546f8f644edf0f999aa98ece106986a77a7b922316bf6414ff825/torchmetrics-1.9.0-py3-none-any.whl", hash = "sha256:bfdcbff3dd1d96b3374bb2496eb39f23c4b28b8a845b6a18c313688e0d2d9ca1", size = 983384, upload-time = "2026-03-09T17:41:19.756Z" }, ] [[package]] @@ -3590,21 +3628,19 @@ wheels = [ [[package]] name = "tornado" -version = "6.5.4" +version = "6.5.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/37/1d/0a336abf618272d53f62ebe274f712e213f5a03c0b2339575430b8362ef2/tornado-6.5.4.tar.gz", hash = "sha256:a22fa9047405d03260b483980635f0b041989d8bcc9a313f8fe18b411d84b1d7", size = 513632, upload-time = "2025-12-15T19:21:03.836Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/f1/3173dfa4a18db4a9b03e5d55325559dab51ee653763bb8745a75af491286/tornado-6.5.5.tar.gz", hash = "sha256:192b8f3ea91bd7f1f50c06955416ed76c6b72f96779b962f07f911b91e8d30e9", size = 516006, upload-time = "2026-03-10T21:31:02.067Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d6241c1a16b1c9e4cc28148b1cda97dd1c6cb4fb7068ac1bedc610768dff0ba9", size = 443909, upload-time = "2025-12-15T19:20:48.382Z" }, - { url = "https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2d50f63dda1d2cac3ae1fa23d254e16b5e38153758470e9956cbc3d813d40843", size = 442163, upload-time = "2025-12-15T19:20:49.791Z" }, - { url = "https://files.pythonhosted.org/packages/ba/b5/206f82d51e1bfa940ba366a8d2f83904b15942c45a78dd978b599870ab44/tornado-6.5.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1cf66105dc6acb5af613c054955b8137e34a03698aa53272dbda4afe252be17", size = 445746, upload-time = "2025-12-15T19:20:51.491Z" }, - { url = "https://files.pythonhosted.org/packages/8e/9d/1a3338e0bd30ada6ad4356c13a0a6c35fbc859063fa7eddb309183364ac1/tornado-6.5.4-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50ff0a58b0dc97939d29da29cd624da010e7f804746621c78d14b80238669335", size = 445083, upload-time = "2025-12-15T19:20:52.778Z" }, - { url = "https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fb5e04efa54cf0baabdd10061eb4148e0be137166146fff835745f59ab9f7f", size = 445315, upload-time = "2025-12-15T19:20:53.996Z" }, - { url = "https://files.pythonhosted.org/packages/27/07/2273972f69ca63dbc139694a3fc4684edec3ea3f9efabf77ed32483b875c/tornado-6.5.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9c86b1643b33a4cd415f8d0fe53045f913bf07b4a3ef646b735a6a86047dda84", size = 446003, upload-time = "2025-12-15T19:20:56.101Z" }, - { url = "https://files.pythonhosted.org/packages/d1/83/41c52e47502bf7260044413b6770d1a48dda2f0246f95ee1384a3cd9c44a/tornado-6.5.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:6eb82872335a53dd063a4f10917b3efd28270b56a33db69009606a0312660a6f", size = 445412, upload-time = "2025-12-15T19:20:57.398Z" }, - { url = "https://files.pythonhosted.org/packages/10/c7/bc96917f06cbee182d44735d4ecde9c432e25b84f4c2086143013e7b9e52/tornado-6.5.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6076d5dda368c9328ff41ab5d9dd3608e695e8225d1cd0fd1e006f05da3635a8", size = 445392, upload-time = "2025-12-15T19:20:58.692Z" }, - { url = "https://files.pythonhosted.org/packages/0c/1a/d7592328d037d36f2d2462f4bc1fbb383eec9278bc786c1b111cbbd44cfa/tornado-6.5.4-cp39-abi3-win32.whl", hash = "sha256:1768110f2411d5cd281bac0a090f707223ce77fd110424361092859e089b38d1", size = 446481, upload-time = "2025-12-15T19:21:00.008Z" }, - { url = "https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl", hash = "sha256:fa07d31e0cd85c60713f2b995da613588aa03e1303d75705dca6af8babc18ddc", size = 446886, upload-time = "2025-12-15T19:21:01.287Z" }, - { url = "https://files.pythonhosted.org/packages/50/49/8dc3fd90902f70084bd2cd059d576ddb4f8bb44c2c7c0e33a11422acb17e/tornado-6.5.4-cp39-abi3-win_arm64.whl", hash = "sha256:053e6e16701eb6cbe641f308f4c1a9541f91b6261991160391bfc342e8a551a1", size = 445910, upload-time = "2025-12-15T19:21:02.571Z" }, + { url = "https://files.pythonhosted.org/packages/59/8c/77f5097695f4dd8255ecbd08b2a1ed8ba8b953d337804dd7080f199e12bf/tornado-6.5.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:487dc9cc380e29f58c7ab88f9e27cdeef04b2140862e5076a66fb6bb68bb1bfa", size = 445983, upload-time = "2026-03-10T21:30:44.28Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5e/7625b76cd10f98f1516c36ce0346de62061156352353ef2da44e5c21523c/tornado-6.5.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:65a7f1d46d4bb41df1ac99f5fcb685fb25c7e61613742d5108b010975a9a6521", size = 444246, upload-time = "2026-03-10T21:30:46.571Z" }, + { url = "https://files.pythonhosted.org/packages/b2/04/7b5705d5b3c0fab088f434f9c83edac1573830ca49ccf29fb83bf7178eec/tornado-6.5.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e74c92e8e65086b338fd56333fb9a68b9f6f2fe7ad532645a290a464bcf46be5", size = 447229, upload-time = "2026-03-10T21:30:48.273Z" }, + { url = "https://files.pythonhosted.org/packages/34/01/74e034a30ef59afb4097ef8659515e96a39d910b712a89af76f5e4e1f93c/tornado-6.5.5-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:435319e9e340276428bbdb4e7fa732c2d399386d1de5686cb331ec8eee754f07", size = 448192, upload-time = "2026-03-10T21:30:51.22Z" }, + { url = "https://files.pythonhosted.org/packages/be/00/fe9e02c5a96429fce1a1d15a517f5d8444f9c412e0bb9eadfbe3b0fc55bf/tornado-6.5.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3f54aa540bdbfee7b9eb268ead60e7d199de5021facd276819c193c0fb28ea4e", size = 448039, upload-time = "2026-03-10T21:30:53.52Z" }, + { url = "https://files.pythonhosted.org/packages/82/9e/656ee4cec0398b1d18d0f1eb6372c41c6b889722641d84948351ae19556d/tornado-6.5.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:36abed1754faeb80fbd6e64db2758091e1320f6bba74a4cf8c09cd18ccce8aca", size = 447445, upload-time = "2026-03-10T21:30:55.541Z" }, + { url = "https://files.pythonhosted.org/packages/5a/76/4921c00511f88af86a33de770d64141170f1cfd9c00311aea689949e274e/tornado-6.5.5-cp39-abi3-win32.whl", hash = "sha256:dd3eafaaeec1c7f2f8fdcd5f964e8907ad788fe8a5a32c4426fbbdda621223b7", size = 448582, upload-time = "2026-03-10T21:30:57.142Z" }, + { url = "https://files.pythonhosted.org/packages/2c/23/f6c6112a04d28eed765e374435fb1a9198f73e1ec4b4024184f21faeb1ad/tornado-6.5.5-cp39-abi3-win_amd64.whl", hash = "sha256:6443a794ba961a9f619b1ae926a2e900ac20c34483eea67be4ed8f1e58d3ef7b", size = 448990, upload-time = "2026-03-10T21:30:58.857Z" }, + { url = "https://files.pythonhosted.org/packages/b7/c8/876602cbc96469911f0939f703453c1157b0c826ecb05bdd32e023397d4e/tornado-6.5.5-cp39-abi3-win_arm64.whl", hash = "sha256:2c9a876e094109333f888539ddb2de4361743e5d21eece20688e3e351e4990a6", size = 448016, upload-time = "2026-03-10T21:31:00.43Z" }, ] [[package]] @@ -3679,7 +3715,7 @@ wheels = [ [[package]] name = "virtualenv" -version = "21.1.0" +version = "21.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -3687,9 +3723,9 @@ dependencies = [ { name = "platformdirs" }, { name = "python-discovery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2f/c9/18d4b36606d6091844daa3bd93cf7dc78e6f5da21d9f21d06c221104b684/virtualenv-21.1.0.tar.gz", hash = "sha256:1990a0188c8f16b6b9cf65c9183049007375b26aad415514d377ccacf1e4fb44", size = 5840471, upload-time = "2026-02-27T08:49:29.702Z" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/92/58199fe10049f9703c2666e809c4f686c54ef0a68b0f6afccf518c0b1eb9/virtualenv-21.2.0.tar.gz", hash = "sha256:1720dc3a62ef5b443092e3f499228599045d7fea4c79199770499df8becf9098", size = 5840618, upload-time = "2026-03-09T17:24:38.013Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/55/896b06bf93a49bec0f4ae2a6f1ed12bd05c8860744ac3a70eda041064e4d/virtualenv-21.1.0-py3-none-any.whl", hash = "sha256:164f5e14c5587d170cf98e60378eb91ea35bf037be313811905d3a24ea33cc07", size = 5825072, upload-time = "2026-02-27T08:49:27.516Z" }, + { url = "https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-py3-none-any.whl", hash = "sha256:1bd755b504931164a5a496d217c014d098426cddc79363ad66ac78125f9d908f", size = 5825084, upload-time = "2026-03-09T17:24:35.378Z" }, ] [[package]] From 8472dfb9c572f346c17a33818ac1fd82fb568ec3 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 16 Mar 2026 11:45:10 -0700 Subject: [PATCH 177/335] Added relaxation term to SIRT reconstruction, stable gradients --- src/quantem/core/ml/optimizer_mixin.py | 2 +- src/quantem/tomography/object_models.py | 7 +++++++ src/quantem/tomography/tomography.py | 5 ++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index c07fefc9..4ea263c0 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -273,7 +273,7 @@ class Plateau: min_lr : float or None Absolute lower bound on the learning rate. Overrides ``min_lr_factor`` when set. Default: None. - factor : float + factor : float Factor by which the LR is reduced: ``new_lr = lr * factor``. Default: 0.5. patience : int Number of epochs with no improvement before reducing LR. Default: 10. diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 6ef9eb59..77530d08 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -517,6 +517,13 @@ def apply_soft_constraints( grad_norm = torch.norm(grad_outputs, dim=1) # Shape: [num_samples] soft_loss += self.constraints.tv_vol * grad_norm.mean() + # grad_outputs: [num, 3] + # for us: grad_outputs: [num, 4] # 4 is t, xyz + # grad_norm_volf: norm(grad_outpus[:, 1:]) + # grad_time: grad_outputs[:, 0] + # grad_norm = torch.norm(grad_outputs, dim=1) # Shape: [num_samples] + # soft_loss += self.constraints.tv_vol * grad_norm.mean() + if ( isinstance(self.constraints, ObjConstraintParams.ObjINRConstraints) and self.constraints.sparsity > 0 diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 8b1ebd9f..4ca1fea1 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -402,6 +402,7 @@ def reconstruct( num_iter: int = 10, obj_constraints: dict | ObjConstraintsType | None = None, mode: Literal["sirt", "fbp"] = "sirt", + relaxation: float = 0.25, reset: bool = False, inline_alignment: bool = False, smoothing_sigma: float | None = None, @@ -433,6 +434,7 @@ def reconstruct( mode=mode, proj_forward=proj_forward, gaussian_kernel=gaussian_kernel, + relaxation=relaxation, ) pbar.set_description(f"{mode} Reconstruction | Loss: {loss.item():.4f}") @@ -449,6 +451,7 @@ def _reconstruction_epoch( inline_alignment: bool, mode: Literal["sirt", "fbp"], proj_forward: torch.Tensor, + relaxation: float, gaussian_kernel: torch.Tensor | None = None, ): loss = 0 @@ -506,7 +509,7 @@ def _reconstruction_epoch( correction /= normalization - self.obj_model.obj += correction + self.obj_model.obj += correction * relaxation if gaussian_kernel is not None: self.obj_model.obj = gaussian_filter_2d_stack(self.obj_model.obj, gaussian_kernel) From d2a94342a5f3b7a2204909b0ebea76aa4efa106f Mon Sep 17 00:00:00 2001 From: Georgios Varnavides Date: Tue, 17 Mar 2026 09:04:53 +0100 Subject: [PATCH 178/335] arthurs typing changes --- src/quantem/core/io/file_readers.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/quantem/core/io/file_readers.py b/src/quantem/core/io/file_readers.py index d537e125..04ff0d50 100644 --- a/src/quantem/core/io/file_readers.py +++ b/src/quantem/core/io/file_readers.py @@ -1,6 +1,7 @@ import importlib from os import PathLike from pathlib import Path +from typing import Any import h5py @@ -160,7 +161,7 @@ def read_emdfile_to_4dstem( data_keys = ["datacube_root", "datacube", "data"] if data_keys is None else data_keys print("keys: ", data_keys) try: - data = file + data: Any = file for key in data_keys: data = data[key] except KeyError: @@ -206,7 +207,7 @@ def _open_zarr(url): import zarr if url.endswith(".zip"): - store = zarr.storage.ZipStore(url, mode="r") # ty:ignore[possibly-missing-attribute] + store = zarr.storage.ZipStore(url, mode="r") # type: ignore return zarr.open(store=store, mode="r") return zarr.open(url, mode="r") @@ -231,7 +232,7 @@ def _iter_metadata_indices(root): yield i i += 1 - def _decode_types(obj): + def _decode_types(obj) -> Any: if isinstance(obj, dict): if obj.get("_type") == "tuple": return tuple(_decode_types(v) for v in obj["_value"]) From 36215685d386cbd2b0813f80f0e3e878b6ec501b Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 17 Mar 2026 11:58:45 -0700 Subject: [PATCH 179/335] Relaxation parameters for SIRT reconstructions --- src/quantem/tomography/radon/radon.py | 159 +++++++++++--------------- src/quantem/tomography/tomography.py | 22 +++- 2 files changed, 87 insertions(+), 94 deletions(-) diff --git a/src/quantem/tomography/radon/radon.py b/src/quantem/tomography/radon/radon.py index d6d408e3..75c66b6f 100644 --- a/src/quantem/tomography/radon/radon.py +++ b/src/quantem/tomography/radon/radon.py @@ -9,37 +9,25 @@ def radon_torch(images, theta=None, device=None): - """ - Batched Radon transform implemented in PyTorch. - images: torch.Tensor of shape [B, H, W] - Returns: torch.Tensor of shape [B, N_angles, N_pixels] - """ if images.ndim == 2: - images = images.unsqueeze(0) # [1, H, W] + images = images.unsqueeze(0) B, H, W = images.shape + device = device or images.device + theta = theta if theta is not None else torch.arange(180, device=device) + A = len(theta) - if device is None: - device = images.device - - if theta is None: - theta = torch.arange(180, device=device) - - N_angles = len(theta) shape_min = min(H, W) radius = shape_min // 2 - center = torch.tensor([H // 2, W // 2], device=device) + center_h, center_w = H // 2, W // 2 Y, X = torch.meshgrid( torch.arange(H, device=device), torch.arange(W, device=device), indexing="ij", ) - dist2 = (X - center[1]) ** 2 + (Y - center[0]) ** 2 - mask = dist2 <= radius**2 - images = images.clone() - images *= mask # broadcasting over batch + mask = (X - center_w) ** 2 + (Y - center_h) ** 2 <= radius**2 + images = images.clone() * mask - # Crop to square excess = torch.tensor([H, W], device=device) - shape_min slices = tuple( slice(int((e.item() + 1) // 2), int((e.item() + 1) // 2 + shape_min)) @@ -51,112 +39,99 @@ def radon_torch(images, theta=None, device=None): N = images.shape[-1] center = N // 2 - radon_images = torch.zeros((B, N_angles, N), dtype=images.dtype, device=device) + # Build all rotation matrices at once: [A, 2, 2] + angles_rad = torch.deg2rad(theta.float()) + cos_a = torch.cos(angles_rad) + sin_a = torch.sin(angles_rad) + rot = torch.stack( + [ + torch.stack([cos_a, -sin_a], dim=1), + torch.stack([-sin_a, -cos_a], dim=1), + ], + dim=1, + ) # [A, 2, 2] + # Pixel coords: [N*N, 2] grid_y, grid_x = torch.meshgrid( torch.arange(N, dtype=torch.float32, device=device), torch.arange(N, dtype=torch.float32, device=device), indexing="ij", ) - coords = torch.stack((grid_x - center, grid_y - center), dim=-1) # (N, N, 2) - coords = coords.view(1, N, N, 2).expand(B, -1, -1, -1) # [B, N, N, 2] - - for i, angle in enumerate(theta): - angle_rad = torch.deg2rad(angle) - rot = torch.tensor( - [ - [torch.cos(angle_rad), -torch.sin(angle_rad)], - [-torch.sin(angle_rad), -torch.cos(angle_rad)], - ], - device=device, - dtype=torch.float32, - ) + coords = torch.stack((grid_x - center, grid_y - center), dim=-1).view(-1, 2) # [N*N, 2] - rot = rot.unsqueeze(0).expand(B, -1, -1) # [B, 2, 2] - coords_rot = torch.matmul(coords.view(B, -1, 2), rot.transpose(1, 2)).view(B, N, N, 2) - coords_rot += center + # Rotate all angles at once: [A, N*N, 2] + coords_rot = (coords @ rot.transpose(1, 2)) + center # [A, N*N, 2] - # Normalize to [-1, 1] - grid = 2 * coords_rot / (N - 1) - 1 # [B, N, N, 2] + # Normalize to [-1, 1] for grid_sample + grid = (2 * coords_rot / (N - 1) - 1).view(A, N, N, 2) # [A, N, N, 2] - # grid = grid.unsqueeze(1) # [B, 1, N, N, 2] - imgs = images.unsqueeze(1) # [B, 1, N, N] + # Expand images: [B, 1, N, N] -> sample with [B*A, 1, N, N] style + # Tile images over angles, tile grid over batch + images_exp = images.unsqueeze(1).expand(-1, A, -1, -1).reshape(B * A, 1, N, N) + grid_exp = grid.unsqueeze(0).expand(B, -1, -1, -1, -1).reshape(B * A, N, N, 2) - sampled = F.grid_sample( - imgs, grid, mode="bilinear", padding_mode="zeros", align_corners=True - ) - projection = sampled.squeeze(1).sum(dim=1) # [B, N] - radon_images[:, i, :] = projection + sampled = F.grid_sample( + images_exp, grid_exp, mode="bilinear", padding_mode="zeros", align_corners=True + ) + # sampled: [B*A, 1, N, N] -> sum over rows -> [B, A, N] + radon_images = sampled.squeeze(1).sum(dim=1).view(B, A, N) - return radon_images.squeeze(0) if radon_images.shape[0] == 1 else radon_images + return radon_images.squeeze(0) if B == 1 else radon_images def iradon_torch( - sinograms, - theta=None, - output_size=None, - filter_name="ramp", - circle=True, - device=None, + sinograms, theta=None, output_size=None, filter_name="ramp", circle=True, device=None ): - """ - Batched inverse Radon transform (filtered backprojection). - sinograms: [B, N_angles, N_pixels] or [N_angles, N_pixels] (automatically batched) - Returns: [B, output_size, output_size] or [output_size, output_size] - """ if sinograms.ndim == 2: - sinograms = sinograms.unsqueeze(0) # [1, A, P] + sinograms = sinograms.unsqueeze(0) B, A, N = sinograms.shape - - device = sinograms.device if device is None else device + device = device or sinograms.device theta = theta if theta is not None else torch.linspace(0, 180, steps=A, device=device) - if theta.shape[0] != A: - raise ValueError("theta does not match number of projections") - if output_size is None: - output_size = N if circle else int(torch.floor(torch.sqrt(torch.tensor(N**2 / 2.0)))) + output_size = N if circle else int((N**2 / 2) ** 0.5) - # Padding for FFT - padded_size = max( - 64, int(2 ** torch.ceil(torch.log2(torch.tensor(2 * N, dtype=torch.float32)))) - ) + # Filter + padded_size = max(64, int(2 ** torch.ceil(torch.log2(torch.tensor(2.0 * N))))) pad_y = padded_size - N - sinograms_padded = F.pad(sinograms, (0, pad_y)) # [B, A, padded] - - f_filter = get_fourier_filter_torch(padded_size, filter_name, device=device) # [1, padded] - spectrum = torch.fft.fft(sinograms_padded, dim=2) - filtered = torch.real(torch.fft.ifft(spectrum * f_filter, dim=2))[:, :, :N] + sinograms_padded = F.pad(sinograms, (0, pad_y)) + f_filter = get_fourier_filter_torch(padded_size, filter_name, device=device) + filtered = torch.real( + torch.fft.ifft(torch.fft.fft(sinograms_padded, dim=2) * f_filter, dim=2) + )[:, :, :N] # [B, A, N] - # Backprojection - recon = torch.zeros((B, output_size, output_size), device=device) radius = output_size // 2 - y, x = torch.meshgrid( torch.arange(output_size, device=device) - radius, torch.arange(output_size, device=device) - radius, indexing="ij", ) - x = x.flatten() - y = y.flatten() + x = x.float().flatten() # [H*W] + y = y.float().flatten() - for i, angle in enumerate(torch.deg2rad(theta)): - t = (x * torch.cos(angle) - y * torch.sin(angle)).reshape(1, output_size, output_size) - t_idx = t + (N // 2) + # Compute t for all angles at once: [A, H*W] + angles_rad = torch.deg2rad(theta.float()) + t = x.unsqueeze(0) * torch.cos(angles_rad).unsqueeze(1) - y.unsqueeze(0) * torch.sin( + angles_rad + ).unsqueeze(1) # [A, H*W] + t_idx = t + (N // 2) # [A, H*W] - t0 = torch.floor(t_idx).long().clamp(0, N - 2) # [1, H, W] - t1 = t0 + 1 - w = t_idx - t0.float() + t0 = t_idx.long().clamp(0, N - 2) # [A, H*W] + t1 = t0 + 1 + w = t_idx - t0.float() # [A, H*W] - t0 = t0.expand(B, -1, -1) # [B, H, W] - t1 = t1.expand(B, -1, -1) + # Gather from filtered: [B, A, N] -> gather at [A, H*W] indices + # Expand: [B, A, H*W] + # HW = output_size * output_size + t0_exp = t0.unsqueeze(0).expand(B, -1, -1) # [B, A, H*W] + t1_exp = t1.unsqueeze(0).expand(B, -1, -1) + w_exp = w.unsqueeze(0).expand(B, -1, -1) - filtered_i = filtered[:, i, :] # [B, N] - val0 = torch.gather(filtered_i, 1, t0.view(B, -1)).view(B, output_size, output_size) - val1 = torch.gather(filtered_i, 1, t1.view(B, -1)).view(B, output_size, output_size) + val0 = torch.gather(filtered, 2, t0_exp) # [B, A, H*W] + val1 = torch.gather(filtered, 2, t1_exp) + proj = ((1 - w_exp) * val0 + w_exp * val1).sum(dim=1) # [B, H*W] - proj = (1 - w) * val0 + w * val1 - recon += proj + recon = proj.view(B, output_size, output_size) if circle: mask = ( @@ -166,7 +141,7 @@ def iradon_torch( recon[:, mask] = 0.0 recon *= torch.pi / (2 * A) - return recon.squeeze(0) if recon.shape[0] == 1 else recon + return recon.squeeze(0) if B == 1 else recon def get_fourier_filter_torch(size, filter_name="ramp", device=None, dtype=torch.float32): diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index aaf335d2..fe854b4a 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -304,7 +304,9 @@ def reconstruct( self.logger.flush() if not self.verbose: if self.global_rank == 0: - print(f"Reconstruction Epoch {self.num_epochs} | Loss: {total_loss:.5e}, Consistency Loss: {consistency_loss:.5e}, Soft Constraint Loss: {epoch_soft_constraint_loss:.5e}") + print( + f"Reconstruction Epoch {self.num_epochs} | Loss: {total_loss:.5e}, Consistency Loss: {consistency_loss:.5e}, Soft Constraint Loss: {epoch_soft_constraint_loss:.5e}" + ) # --- Helper Functions --- @@ -431,6 +433,7 @@ def reconstruct( else: gaussian_kernel = None + patience = self.dset.tilt_angles.max() // 10 for iter in pbar: proj_forward, loss = self._reconstruction_epoch( inline_alignment=inline_alignment, @@ -444,10 +447,23 @@ def reconstruct( self._epoch_losses.append(loss.item()) + # Change relaxation parameter if loss greater than last epoch + if len(self._epoch_losses) > 1 and self._epoch_losses[-1] > self._epoch_losses[-2]: + if patience == 0: + relaxation *= 0.85 + print(f"Relaxation parameter changed to: {relaxation}") + patience = 10 + else: + patience -= 1 + if mode == "fbp": break # --- Conventional reconstruction method --- + def _adaptive_relaxation(self, n_power_iter: int = 10) -> float: + raise NotImplementedError( + "Adaptive relaxation hasn't been implemented, please input a valid relaxation parameter." + ) def _reconstruction_epoch( self, @@ -458,7 +474,9 @@ def _reconstruction_epoch( gaussian_kernel: torch.Tensor | None = None, ): loss = 0 - + if relaxation == 0.0: + relaxation = self._adaptive_relaxation() + print(f"Adaptive relaxation: {relaxation}") if inline_alignment: for ind in range(len(self.dset.tilt_angles)): im_proj = proj_forward[:, ind, :] From d332b4b6fbad186216a1deadce7cd27a5a0b1b04 Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Tue, 17 Mar 2026 15:08:50 -0700 Subject: [PATCH 180/335] fixing bug of arg name changing in dataset pad --- src/quantem/diffractive_imaging/dataset_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quantem/diffractive_imaging/dataset_models.py b/src/quantem/diffractive_imaging/dataset_models.py index 2fbfa119..4a4b9d5b 100644 --- a/src/quantem/diffractive_imaging/dataset_models.py +++ b/src/quantem/diffractive_imaging/dataset_models.py @@ -981,7 +981,7 @@ def preprocess( self.num_gpts, *padded_diffraction_intensities_shape, ), - in_place=True, + modify_in_place=True, ) self.intensities_4d = self.dset.array.reshape( (*self.gpts, *padded_diffraction_intensities_shape) From b089e553ccf1e2e8fa462b1dd0c18aa72e7a73fd Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Wed, 18 Mar 2026 14:31:24 -0700 Subject: [PATCH 181/335] updating example to best practices for INR make equispaced grid --- src/quantem/core/ml/inr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quantem/core/ml/inr.py b/src/quantem/core/ml/inr.py index 276e5680..5ae5b298 100644 --- a/src/quantem/core/ml/inr.py +++ b/src/quantem/core/ml/inr.py @@ -151,7 +151,7 @@ def make_equispaced_grid( Examples -------- For a model with in_features=2: - >>> bounds = ((0, 1), (0, 1)) + >>> bounds = ((-1, 1), (-1, 1)) >>> sampling = (0.1, 0.1) >>> coords = siren.make_equispaced_grid(bounds, sampling) """ From e860c03e0640ecd33b2c8b365c1da9ddbacbc32f Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 18 Mar 2026 21:06:48 -0700 Subject: [PATCH 182/335] show_2d, add api description and examples for color, norm, scale bar --- .../core/visualization/visualization.py | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/src/quantem/core/visualization/visualization.py b/src/quantem/core/visualization/visualization.py index f35dab76..7c82525a 100644 --- a/src/quantem/core/visualization/visualization.py +++ b/src/quantem/core/visualization/visualization.py @@ -454,12 +454,16 @@ def show_2d( norm : NormalizationConfig or dict or str, optional Configuration for normalizing the data before visualization. This can be a string, dictionary, or a NormalizationConfig object. Strings for preset normalization types - include: "linear_auto" (quantile), "linear_minmax", "linear_centered", "log_auto", - "log_minmax", "power_squared", "power_sqrt", "asinh_centered" + include: ``"linear_auto"`` (quantile), ``"linear_minmax"``, ``"linear_centered"``, + ``"log_auto"``, ``"log_minmax"``, ``"power_squared"``, ``"power_sqrt"``, + ``"asinh_centered"``. Dict form example: ``{"power": 0.5}``. scalebar : ScalebarConfig or dict or bool, optional Configuration for adding a scale bar to the plot. + Dict form example: ``{"sampling": 0.5, "units": "Å"}`` or + ``{"sampling": 0.02, "units": "1/Å"}``. cmap : str or Colormap, default="gray" Colormap to use for real data or amplitude of complex data. + Common choices: ``"gray"``, ``"viridis"``, ``"turbo"``, ``"inferno"``, ``"magma"``. cbar : bool, default=False Whether to add a colorbar to the plot. title : str, optional @@ -507,6 +511,38 @@ def show_2d( ValueError If combine_images is True but arrays contains multiple rows, or if figax is provided but the axes shape doesn't match the grid shape. + + Examples + -------- + Display a single image: + + >>> import numpy as np + >>> from quantem.core.visualization import show_2d + >>> image = np.random.rand(256, 256) + >>> fig, ax = show_2d(image, axsize=(3, 3)) + + Display a 2-row grid with titles and a scale bar: + + >>> image_rows = [[np.random.rand(128, 128) for _ in range(3)] for _ in range(2)] + >>> label_rows = [["BF", "ADF", "ABF"], ["HAADF", "DPC", "SSB"]] + >>> fig, axs = show_2d( + ... image_rows, + ... title=label_rows, + ... cmap="gray", + ... scalebar={"sampling": 0.5, "units": "Å"}, + ... ) + + Display diffraction patterns with log normalization, colorbar, and scale bar: + + >>> dps = [np.random.rand(256, 256) ** 3 for _ in range(3)] + >>> fig, axs = show_2d( + ... dps, + ... title=["Zone axis", "Off-axis", "CBED"], + ... norm="log_auto", + ... cbar=True, + ... cmap="turbo", + ... scalebar={"sampling": 0.02, "units": "1/Å"}, + ... ) """ arrays = to_cpu(arrays) grid = _normalize_show_input_to_grid(arrays) From 7b5eaf77c80e6b7ad4e5704e6004c4481d62e910 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 18 Mar 2026 21:22:47 -0700 Subject: [PATCH 183/335] use the most common api --- src/quantem/core/visualization/visualization.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/quantem/core/visualization/visualization.py b/src/quantem/core/visualization/visualization.py index 7c82525a..2eb2418f 100644 --- a/src/quantem/core/visualization/visualization.py +++ b/src/quantem/core/visualization/visualization.py @@ -454,9 +454,8 @@ def show_2d( norm : NormalizationConfig or dict or str, optional Configuration for normalizing the data before visualization. This can be a string, dictionary, or a NormalizationConfig object. Strings for preset normalization types - include: ``"linear_auto"`` (quantile), ``"linear_minmax"``, ``"linear_centered"``, - ``"log_auto"``, ``"log_minmax"``, ``"power_squared"``, ``"power_sqrt"``, - ``"asinh_centered"``. Dict form example: ``{"power": 0.5}``. + include: ``"linear_auto"`` (quantile), ``"log_auto"``, ``"power_sqrt"``. + Dict form example: ``{"power": 0.5}``. scalebar : ScalebarConfig or dict or bool, optional Configuration for adding a scale bar to the plot. Dict form example: ``{"sampling": 0.5, "units": "Å"}`` or From eda4b9da8bc5be11093fd42f68f07aeb3e921fdb Mon Sep 17 00:00:00 2001 From: darshan-mali Date: Thu, 19 Mar 2026 10:29:20 -0700 Subject: [PATCH 184/335] Minor formatting changes --- src/quantem/imaging/lattice.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/quantem/imaging/lattice.py b/src/quantem/imaging/lattice.py index 5f9ec9fb..58227dc1 100644 --- a/src/quantem/imaging/lattice.py +++ b/src/quantem/imaging/lattice.py @@ -22,7 +22,7 @@ def __init__( raise RuntimeError("Use Lattice.from_data() to instantiate this class.") self._image: Dataset2d = image - # --- Constructors --- + ### --- Constructors --- @classmethod def from_data( cls, @@ -92,7 +92,7 @@ def from_data( return cls(image=ds2d, _token=cls._token) - # --- Properties --- + ### --- Properties --- @property def image(self) -> Dataset2d: return self._image @@ -120,7 +120,7 @@ def image(self, value: Dataset2d | NDArray): else: self._image = Dataset2d(arr) # type: ignore[call-arg] - # --- Functions --- + ### --- Functions --- def define_lattice( self, origin, @@ -226,7 +226,6 @@ def define_lattice( PENALTY = 1e10 H_CLIP = H - 2 W_CLIP = W - 2 - a_range = np.arange(max(a_min, -max_ind), min(a_max, max_ind) + 1, dtype=np.int32) b_range = np.arange(max(b_min, -max_ind), min(b_max, max_ind) + 1, dtype=np.int32) aa, bb = np.meshgrid(a_range, b_range, indexing="ij") @@ -239,7 +238,6 @@ def define_lattice( a_max_blk = min(a_max, curr_block_size) b_min_blk = max(b_min, -curr_block_size) b_max_blk = min(b_max, curr_block_size) - mask = ( (aa >= a_min_blk) & (aa <= a_max_blk) & (bb >= b_min_blk) & (bb <= b_max_blk) ) From ec87cbe6ec156f478c7c9bc796d8ee2b9d6e986b Mon Sep 17 00:00:00 2001 From: darshan-mali Date: Thu, 19 Mar 2026 12:15:25 -0700 Subject: [PATCH 185/335] Updated Errors. Removed assert from src and replaced with raise. --- src/quantem/imaging/lattice.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/quantem/imaging/lattice.py b/src/quantem/imaging/lattice.py index 58227dc1..303ff26f 100644 --- a/src/quantem/imaging/lattice.py +++ b/src/quantem/imaging/lattice.py @@ -189,7 +189,8 @@ def define_lattice( if refine_lattice: from scipy.optimize import minimize - assert block_size is None or block_size > 0, "block_size must be positive or None." + if block_size is not None and block_size < 0: + raise ValueError("block_size must be positive or None.") H, W = self._image.shape im = np.asarray(self._image.array, dtype=float) From 023b6cf2770b0ecc7ac7eb828c1336cbe8f87e11 Mon Sep 17 00:00:00 2001 From: darshan-mali Date: Thu, 19 Mar 2026 12:19:40 -0700 Subject: [PATCH 186/335] Updated pytest --- tests/imaging/test_lattice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/imaging/test_lattice.py b/tests/imaging/test_lattice.py index 4a3a6bc4..0d11e9c5 100644 --- a/tests/imaging/test_lattice.py +++ b/tests/imaging/test_lattice.py @@ -130,7 +130,7 @@ def test_invalid_lattice_params(self): lattice.define_lattice(origin=[1, 2, 3], u=[5, 0], v=[0, 5], plot_lattice=False) # Negative block_size - with pytest.raises(AssertionError): + with pytest.raises(ValueError): lattice.define_lattice( origin=[50, 50], u=[5, 0], v=[0, 5], block_size=-1, plot_lattice=False ) From 614ffa166a443e3d32ce06ea5e50f61cad2bb052 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Thu, 19 Mar 2026 15:07:15 -0700 Subject: [PATCH 187/335] DDP dist.all_reduce for scheduler stepping --- src/quantem/tomography/tomography.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index fe854b4a..0335424f 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -212,13 +212,19 @@ def reconstruct( total_loss += batch_loss.detach() consistency_loss += batch_consistency_loss.detach() - self.step_schedulers(loss=total_loss) - # TODO: Maybe reorganize the losses so that the order makes sense lol. + if self.world_size > 1: + dist.all_reduce(total_loss, dist.ReduceOp.AVG) + dist.all_reduce(consistency_loss, dist.ReduceOp.AVG) + dist.all_reduce(epoch_soft_constraint_loss, dist.ReduceOp.AVG) + total_loss = total_loss.item() / len(self.dataloader) consistency_loss = consistency_loss.item() / len(self.dataloader) epoch_soft_constraint_loss = epoch_soft_constraint_loss.item() / len(self.dataloader) + self.step_schedulers(loss=total_loss) + # TODO: Maybe reorganize the losses so that the order makes sense lol. + if self.val_dataloader is not None: print("Validating...") self.obj_model.model.eval() From 8772d89842e448b8e32459a2a7c4960dd4717d06 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Thu, 19 Mar 2026 22:06:28 -0700 Subject: [PATCH 188/335] .to() for object_models fixed; Note should check if world_size > 1 and only do distribute_model if model is not already a DDP --- src/quantem/tomography/object_models.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 77530d08..4a2562f1 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -836,7 +836,10 @@ def to(self, device: str | torch.device): # pyright: ignore[reportIncompatibleM if isinstance(device, str): device = torch.device(device) self._device = device - self._model = self._model.to(device) + if self.world_size == 1: + self._model = self._model.to(device) + elif not isinstance(self._model, torch.nn.parallel.DistributedDataParallel): + self.distribute_model(self._model) self.reconnect_optimizer_to_parameters() From 196b9e9fa65d18571b997debe43dfb6edf08f49b Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Sat, 21 Mar 2026 19:16:49 -0700 Subject: [PATCH 189/335] @arthurmccray cuBLAS error on the first iteration is due to torch.autograd in computing the tv_loss. Temporary/Permanent fix is to just apply soft constraints after the first iteration. --- src/quantem/tomography/object_models.py | 7 ------- src/quantem/tomography/tomography.py | 6 +++++- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 4a2562f1..dfedd0a1 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -517,13 +517,6 @@ def apply_soft_constraints( grad_norm = torch.norm(grad_outputs, dim=1) # Shape: [num_samples] soft_loss += self.constraints.tv_vol * grad_norm.mean() - # grad_outputs: [num, 3] - # for us: grad_outputs: [num, 4] # 4 is t, xyz - # grad_norm_volf: norm(grad_outpus[:, 1:]) - # grad_time: grad_outputs[:, 0] - # grad_norm = torch.norm(grad_outputs, dim=1) # Shape: [num_samples] - # soft_loss += self.constraints.tv_vol * grad_norm.mean() - if ( isinstance(self.constraints, ObjConstraintParams.ObjINRConstraints) and self.constraints.sparsity > 0 diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 0335424f..3abb988a 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -192,13 +192,17 @@ def reconstruct( len(batch["target_value"]), ) + pred = integrated_densities.float() + if a0 > 0: + soft_constraints_loss = self.obj_model.apply_soft_constraints(all_coords, pred) + else: + soft_constraints_loss = 0.0 target = batch["target_value"].to(self.device, non_blocking=True).float() batch_consistency_loss = loss_func(pred, target) - soft_constraints_loss = self.obj_model.apply_soft_constraints(all_coords, pred) soft_constraints_loss += self.dset.apply_soft_constraints() epoch_soft_constraint_loss += soft_constraints_loss.detach() From 3e196023edb46c2d919cf131f7c334a30cc6150e Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Sun, 22 Mar 2026 13:06:02 -0700 Subject: [PATCH 190/335] @arthurmccray all Python 3.14 conflcits have been resolved. --- src/quantem/core/ml/ddp.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/quantem/core/ml/ddp.py b/src/quantem/core/ml/ddp.py index d081f757..d3a02b1a 100644 --- a/src/quantem/core/ml/ddp.py +++ b/src/quantem/core/ml/ddp.py @@ -98,6 +98,7 @@ def setup_dataloader( pin_memory=pin_mem, drop_last=True, persistent_workers=persist, + multiprocessing_context="spawn" ) if val_dataset: @@ -110,6 +111,7 @@ def setup_dataloader( pin_memory=pin_mem, drop_last=False, persistent_workers=persist, + multiprocessing_context="spawn" ) val_dataloader = val_dataloader else: From c7be96972ee72564bc3758ed0a269ff9140d4d06 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Sun, 22 Mar 2026 18:08:00 -0700 Subject: [PATCH 191/335] TomographyLite updated to match ObjConstraints and DatasetConstraints --- src/quantem/core/ml/ddp.py | 10 ++++- src/quantem/tomography/tomography_lite.py | 51 ++++++++++++----------- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/src/quantem/core/ml/ddp.py b/src/quantem/core/ml/ddp.py index d3a02b1a..2253b4a5 100644 --- a/src/quantem/core/ml/ddp.py +++ b/src/quantem/core/ml/ddp.py @@ -8,6 +8,10 @@ from quantem.tomography.dataset_models import DatasetModelType +def worker_init_fn(worker_id): + os.environ["CUDA_VISIBLE_DEVICES"] = "" + + class DDPMixin: """ Class for setting up all distributed training. @@ -98,7 +102,8 @@ def setup_dataloader( pin_memory=pin_mem, drop_last=True, persistent_workers=persist, - multiprocessing_context="spawn" + multiprocessing_context="spawn", + worker_init_fn=worker_init_fn, ) if val_dataset: @@ -111,7 +116,8 @@ def setup_dataloader( pin_memory=pin_mem, drop_last=False, persistent_workers=persist, - multiprocessing_context="spawn" + multiprocessing_context="spawn", + worker_init_fn=worker_init_fn, ) val_dataloader = val_dataloader else: diff --git a/src/quantem/tomography/tomography_lite.py b/src/quantem/tomography/tomography_lite.py index 4a6d4c86..eb5c9940 100644 --- a/src/quantem/tomography/tomography_lite.py +++ b/src/quantem/tomography/tomography_lite.py @@ -7,12 +7,17 @@ from quantem.core.datastructures.dataset3d import Dataset3d from quantem.core.ml.inr import HSiren +from quantem.core.ml.optimizer_mixin import ( + OptimizerParams, + SchedulerParams, +) from quantem.tomography.dataset_models import ( + DatasetConstraintsType, TomographyINRDataset, TomographyPixDataset, ) from quantem.tomography.logger_tomography import LoggerTomography -from quantem.tomography.object_models import ObjectINR, ObjectPixelated +from quantem.tomography.object_models import ObjConstraintsType, ObjectINR, ObjectPixelated from quantem.tomography.tomography import Tomography, TomographyConventional @@ -53,7 +58,7 @@ def from_dataset( if log_dir is not None: logger = LoggerTomography( - log_dir=log_dir, + log_dir=str(log_dir), run_prefix="tomography_lite_inr", run_suffix="", log_images_every=log_images_every, @@ -84,38 +89,36 @@ def reconstruct( scheduler_type: Literal[ "exp", "cyclic", "plateau", "cosine_annealing", "linear", "full_warmup", "none" ] = "none", - scheduler_factor: float = 0.5, + scheduler_params: dict = {}, new_optimizers: bool = False, - obj_constraints: dict = {}, - dset_constraints: dict = {}, + obj_constraints: ObjConstraintsType | dict = {}, + dset_constraints: DatasetConstraintsType | dict = {}, ): if self.num_epochs == 0: opt_params = { - "object": { - "type": "adam", - "lr": obj_lr, - }, + "object": OptimizerParams.Adam(lr=obj_lr), } - scheduler_params = { - "object": { - "type": scheduler_type, - "factor": scheduler_factor, - }, + all_scheduler_params = { + "object": SchedulerParams.parse_dict( + { + "name": scheduler_type, + **scheduler_params, + } + ), } if learn_pose: - opt_params["pose"] = { - "type": "adam", - "lr": pose_lr, - } - scheduler_params["pose"] = { - "type": scheduler_type, - } - + opt_params["pose"] = OptimizerParams.Adam(lr=pose_lr) + all_scheduler_params["pose"] = SchedulerParams.parse_dict( + { + "name": scheduler_type, + **scheduler_params, + } + ) else: opt_params = None - scheduler_params = None + all_scheduler_params = {} num_samples_per_ray = int(max(self.dset.tilt_stack.shape)) return super().reconstruct( @@ -125,7 +128,7 @@ def reconstruct( reset=reset, num_samples_per_ray=num_samples_per_ray, optimizer_params=opt_params, - scheduler_params=scheduler_params, + scheduler_params=all_scheduler_params, obj_constraints=obj_constraints, dset_constraints=dset_constraints, ) From df9d6f8ee82e6b7b8eecd204f9766a9e10e8420c Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Sun, 22 Mar 2026 20:47:12 -0700 Subject: [PATCH 192/335] show_2d upstream --- src/quantem/core/visualization/visualization.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/quantem/core/visualization/visualization.py b/src/quantem/core/visualization/visualization.py index 2eb2418f..ae3dd508 100644 --- a/src/quantem/core/visualization/visualization.py +++ b/src/quantem/core/visualization/visualization.py @@ -427,7 +427,11 @@ def _normalize_show_args_to_grid( def show_2d( arrays: Union[NDArray, Sequence[NDArray], Sequence[Sequence[NDArray]]], *, - norm: NormalizationConfig | dict | str | Sequence[dict | str] | None = None, + norm: NormalizationConfig + | dict + | str + | Sequence[dict | str] + | None = None, # TODO: Revamp NormalizationConfig, ScalebarConfig with Dataclasses scalebar: ScalebarConfig | dict | bool | Sequence[bool | dict | None] | None = None, cmap: str | colors.Colormap | Sequence[str] | Sequence[Sequence[str]] = "gray", cbar: bool | Sequence[bool] | Sequence[Sequence[bool]] = False, From bcf6ee4150ece9258f12894320d51b92dd2857bd Mon Sep 17 00:00:00 2001 From: quantem-bot Date: Mon, 23 Mar 2026 10:08:16 +0000 Subject: [PATCH 193/335] chore: update lock file --- uv.lock | 302 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 151 insertions(+), 151 deletions(-) diff --git a/uv.lock b/uv.lock index 05052285..d899a96e 100644 --- a/uv.lock +++ b/uv.lock @@ -139,20 +139,20 @@ wheels = [ [[package]] name = "async-lru" -version = "2.2.0" +version = "2.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/8a/ca724066c32a53fa75f59e0f21aa822fdaa8a0dffa112d223634e3caabf9/async_lru-2.2.0.tar.gz", hash = "sha256:80abae2a237dbc6c60861d621619af39f0d920aea306de34cb992c879e01370c", size = 14654, upload-time = "2026-02-20T19:11:43.848Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/1f/989ecfef8e64109a489fff357450cb73fa73a865a92bd8c272170a6922c2/async_lru-2.3.0.tar.gz", hash = "sha256:89bdb258a0140d7313cf8f4031d816a042202faa61d0ab310a0a538baa1c24b6", size = 16332, upload-time = "2026-03-19T01:04:32.413Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl", hash = "sha256:e2c1cf731eba202b59c5feedaef14ffd9d02ad0037fcda64938699f2c380eafe", size = 7890, upload-time = "2026-02-20T19:11:42.273Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl", hash = "sha256:eea27b01841909316f2cc739807acea1c623df2be8c5cfad7583286397bb8315", size = 8403, upload-time = "2026-03-19T01:04:30.883Z" }, ] [[package]] name = "attrs" -version = "25.4.0" +version = "26.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, ] [[package]] @@ -532,101 +532,101 @@ wheels = [ [[package]] name = "coverage" -version = "7.13.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/ad/b59e5b451cf7172b8d1043dc0fa718f23aab379bc1521ee13d4bd9bfa960/coverage-7.13.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d490ba50c3f35dd7c17953c68f3270e7ccd1c6642e2d2afe2d8e720b98f5a053", size = 219278, upload-time = "2026-02-09T12:56:31.673Z" }, - { url = "https://files.pythonhosted.org/packages/f1/17/0cb7ca3de72e5f4ef2ec2fa0089beafbcaaaead1844e8b8a63d35173d77d/coverage-7.13.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:19bc3c88078789f8ef36acb014d7241961dbf883fd2533d18cb1e7a5b4e28b11", size = 219783, upload-time = "2026-02-09T12:56:33.104Z" }, - { url = "https://files.pythonhosted.org/packages/ab/63/325d8e5b11e0eaf6d0f6a44fad444ae58820929a9b0de943fa377fe73e85/coverage-7.13.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3998e5a32e62fdf410c0dbd3115df86297995d6e3429af80b8798aad894ca7aa", size = 250200, upload-time = "2026-02-09T12:56:34.474Z" }, - { url = "https://files.pythonhosted.org/packages/76/53/c16972708cbb79f2942922571a687c52bd109a7bd51175aeb7558dff2236/coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7", size = 252114, upload-time = "2026-02-09T12:56:35.749Z" }, - { url = "https://files.pythonhosted.org/packages/eb/c2/7ab36d8b8cc412bec9ea2d07c83c48930eb4ba649634ba00cb7e4e0f9017/coverage-7.13.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3aa4e7b9e416774b21797365b358a6e827ffadaaca81b69ee02946852449f00", size = 254220, upload-time = "2026-02-09T12:56:37.796Z" }, - { url = "https://files.pythonhosted.org/packages/d6/4d/cf52c9a3322c89a0e6febdfbc83bb45c0ed3c64ad14081b9503adee702e7/coverage-7.13.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:71ca20079dd8f27fcf808817e281e90220475cd75115162218d0e27549f95fef", size = 256164, upload-time = "2026-02-09T12:56:39.016Z" }, - { url = "https://files.pythonhosted.org/packages/78/e9/eb1dd17bd6de8289df3580e967e78294f352a5df8a57ff4671ee5fc3dcd0/coverage-7.13.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e2f25215f1a359ab17320b47bcdaca3e6e6356652e8256f2441e4ef972052903", size = 250325, upload-time = "2026-02-09T12:56:40.668Z" }, - { url = "https://files.pythonhosted.org/packages/71/07/8c1542aa873728f72267c07278c5cc0ec91356daf974df21335ccdb46368/coverage-7.13.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d65b2d373032411e86960604dc4edac91fdfb5dca539461cf2cbe78327d1e64f", size = 251913, upload-time = "2026-02-09T12:56:41.97Z" }, - { url = "https://files.pythonhosted.org/packages/74/d7/c62e2c5e4483a748e27868e4c32ad3daa9bdddbba58e1bc7a15e252baa74/coverage-7.13.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94eb63f9b363180aff17de3e7c8760c3ba94664ea2695c52f10111244d16a299", size = 249974, upload-time = "2026-02-09T12:56:43.323Z" }, - { url = "https://files.pythonhosted.org/packages/98/9f/4c5c015a6e98ced54efd0f5cf8d31b88e5504ecb6857585fc0161bb1e600/coverage-7.13.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e856bf6616714c3a9fbc270ab54103f4e685ba236fa98c054e8f87f266c93505", size = 253741, upload-time = "2026-02-09T12:56:45.155Z" }, - { url = "https://files.pythonhosted.org/packages/bd/59/0f4eef89b9f0fcd9633b5d350016f54126ab49426a70ff4c4e87446cabdc/coverage-7.13.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:65dfcbe305c3dfe658492df2d85259e0d79ead4177f9ae724b6fb245198f55d6", size = 249695, upload-time = "2026-02-09T12:56:46.636Z" }, - { url = "https://files.pythonhosted.org/packages/b5/2c/b7476f938deb07166f3eb281a385c262675d688ff4659ad56c6c6b8e2e70/coverage-7.13.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b507778ae8a4c915436ed5c2e05b4a6cecfa70f734e19c22a005152a11c7b6a9", size = 250599, upload-time = "2026-02-09T12:56:48.13Z" }, - { url = "https://files.pythonhosted.org/packages/b8/34/c3420709d9846ee3785b9f2831b4d94f276f38884032dca1457fa83f7476/coverage-7.13.4-cp311-cp311-win32.whl", hash = "sha256:784fc3cf8be001197b652d51d3fd259b1e2262888693a4636e18879f613a62a9", size = 221780, upload-time = "2026-02-09T12:56:50.479Z" }, - { url = "https://files.pythonhosted.org/packages/61/08/3d9c8613079d2b11c185b865de9a4c1a68850cfda2b357fae365cf609f29/coverage-7.13.4-cp311-cp311-win_amd64.whl", hash = "sha256:2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f", size = 222715, upload-time = "2026-02-09T12:56:51.815Z" }, - { url = "https://files.pythonhosted.org/packages/18/1a/54c3c80b2f056164cc0a6cdcb040733760c7c4be9d780fe655f356f433e4/coverage-7.13.4-cp311-cp311-win_arm64.whl", hash = "sha256:79e73a76b854d9c6088fe5d8b2ebe745f8681c55f7397c3c0a016192d681045f", size = 221385, upload-time = "2026-02-09T12:56:53.194Z" }, - { url = "https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459", size = 219449, upload-time = "2026-02-09T12:56:54.889Z" }, - { url = "https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3", size = 219810, upload-time = "2026-02-09T12:56:56.33Z" }, - { url = "https://files.pythonhosted.org/packages/78/72/2f372b726d433c9c35e56377cf1d513b4c16fe51841060d826b95caacec1/coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634", size = 251308, upload-time = "2026-02-09T12:56:57.858Z" }, - { url = "https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3", size = 254052, upload-time = "2026-02-09T12:56:59.754Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ac/45dc2e19a1939098d783c846e130b8f862fbb50d09e0af663988f2f21973/coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa", size = 255165, upload-time = "2026-02-09T12:57:01.287Z" }, - { url = "https://files.pythonhosted.org/packages/2d/4d/26d236ff35abc3b5e63540d3386e4c3b192168c1d96da5cb2f43c640970f/coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3", size = 257432, upload-time = "2026-02-09T12:57:02.637Z" }, - { url = "https://files.pythonhosted.org/packages/ec/55/14a966c757d1348b2e19caf699415a2a4c4f7feaa4bbc6326a51f5c7dd1b/coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a", size = 251716, upload-time = "2026-02-09T12:57:04.056Z" }, - { url = "https://files.pythonhosted.org/packages/77/33/50116647905837c66d28b2af1321b845d5f5d19be9655cb84d4a0ea806b4/coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7", size = 253089, upload-time = "2026-02-09T12:57:05.503Z" }, - { url = "https://files.pythonhosted.org/packages/c2/b4/8efb11a46e3665d92635a56e4f2d4529de6d33f2cb38afd47d779d15fc99/coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc", size = 251232, upload-time = "2026-02-09T12:57:06.879Z" }, - { url = "https://files.pythonhosted.org/packages/51/24/8cd73dd399b812cc76bb0ac260e671c4163093441847ffe058ac9fda1e32/coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47", size = 255299, upload-time = "2026-02-09T12:57:08.245Z" }, - { url = "https://files.pythonhosted.org/packages/03/94/0a4b12f1d0e029ce1ccc1c800944a9984cbe7d678e470bb6d3c6bc38a0da/coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985", size = 250796, upload-time = "2026-02-09T12:57:10.142Z" }, - { url = "https://files.pythonhosted.org/packages/73/44/6002fbf88f6698ca034360ce474c406be6d5a985b3fdb3401128031eef6b/coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0", size = 252673, upload-time = "2026-02-09T12:57:12.197Z" }, - { url = "https://files.pythonhosted.org/packages/de/c6/a0279f7c00e786be75a749a5674e6fa267bcbd8209cd10c9a450c655dfa7/coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246", size = 221990, upload-time = "2026-02-09T12:57:14.085Z" }, - { url = "https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126", size = 222800, upload-time = "2026-02-09T12:57:15.944Z" }, - { url = "https://files.pythonhosted.org/packages/47/ac/92da44ad9a6f4e3a7debd178949d6f3769bedca33830ce9b1dcdab589a37/coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d", size = 221415, upload-time = "2026-02-09T12:57:17.497Z" }, - { url = "https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9", size = 219474, upload-time = "2026-02-09T12:57:19.332Z" }, - { url = "https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac", size = 219844, upload-time = "2026-02-09T12:57:20.66Z" }, - { url = "https://files.pythonhosted.org/packages/97/fd/7e859f8fab324cef6c4ad7cff156ca7c489fef9179d5749b0c8d321281c2/coverage-7.13.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea", size = 250832, upload-time = "2026-02-09T12:57:22.007Z" }, - { url = "https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b", size = 253434, upload-time = "2026-02-09T12:57:23.339Z" }, - { url = "https://files.pythonhosted.org/packages/5a/88/6728a7ad17428b18d836540630487231f5470fb82454871149502f5e5aa2/coverage-7.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525", size = 254676, upload-time = "2026-02-09T12:57:24.774Z" }, - { url = "https://files.pythonhosted.org/packages/7c/bc/21244b1b8cedf0dff0a2b53b208015fe798d5f2a8d5348dbfece04224fff/coverage-7.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242", size = 256807, upload-time = "2026-02-09T12:57:26.125Z" }, - { url = "https://files.pythonhosted.org/packages/97/a0/ddba7ed3251cff51006737a727d84e05b61517d1784a9988a846ba508877/coverage-7.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148", size = 251058, upload-time = "2026-02-09T12:57:27.614Z" }, - { url = "https://files.pythonhosted.org/packages/9b/55/e289addf7ff54d3a540526f33751951bf0878f3809b47f6dfb3def69c6f7/coverage-7.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a", size = 252805, upload-time = "2026-02-09T12:57:29.066Z" }, - { url = "https://files.pythonhosted.org/packages/13/4e/cc276b1fa4a59be56d96f1dabddbdc30f4ba22e3b1cd42504c37b3313255/coverage-7.13.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23", size = 250766, upload-time = "2026-02-09T12:57:30.522Z" }, - { url = "https://files.pythonhosted.org/packages/94/44/1093b8f93018f8b41a8cf29636c9292502f05e4a113d4d107d14a3acd044/coverage-7.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80", size = 254923, upload-time = "2026-02-09T12:57:31.946Z" }, - { url = "https://files.pythonhosted.org/packages/8b/55/ea2796da2d42257f37dbea1aab239ba9263b31bd91d5527cdd6db5efe174/coverage-7.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea", size = 250591, upload-time = "2026-02-09T12:57:33.842Z" }, - { url = "https://files.pythonhosted.org/packages/d4/fa/7c4bb72aacf8af5020675aa633e59c1fbe296d22aed191b6a5b711eb2bc7/coverage-7.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a", size = 252364, upload-time = "2026-02-09T12:57:35.743Z" }, - { url = "https://files.pythonhosted.org/packages/5c/38/a8d2ec0146479c20bbaa7181b5b455a0c41101eed57f10dd19a78ab44c80/coverage-7.13.4-cp313-cp313-win32.whl", hash = "sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d", size = 222010, upload-time = "2026-02-09T12:57:37.25Z" }, - { url = "https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd", size = 222818, upload-time = "2026-02-09T12:57:38.734Z" }, - { url = "https://files.pythonhosted.org/packages/04/d1/934918a138c932c90d78301f45f677fb05c39a3112b96fd2c8e60503cdc7/coverage-7.13.4-cp313-cp313-win_arm64.whl", hash = "sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af", size = 221438, upload-time = "2026-02-09T12:57:40.223Z" }, - { url = "https://files.pythonhosted.org/packages/52/57/ee93ced533bcb3e6df961c0c6e42da2fc6addae53fb95b94a89b1e33ebd7/coverage-7.13.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d", size = 220165, upload-time = "2026-02-09T12:57:41.639Z" }, - { url = "https://files.pythonhosted.org/packages/c5/e0/969fc285a6fbdda49d91af278488d904dcd7651b2693872f0ff94e40e84a/coverage-7.13.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12", size = 220516, upload-time = "2026-02-09T12:57:44.215Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b8/9531944e16267e2735a30a9641ff49671f07e8138ecf1ca13db9fd2560c7/coverage-7.13.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b", size = 261804, upload-time = "2026-02-09T12:57:45.989Z" }, - { url = "https://files.pythonhosted.org/packages/8a/f3/e63df6d500314a2a60390d1989240d5f27318a7a68fa30ad3806e2a9323e/coverage-7.13.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9", size = 263885, upload-time = "2026-02-09T12:57:47.42Z" }, - { url = "https://files.pythonhosted.org/packages/f3/67/7654810de580e14b37670b60a09c599fa348e48312db5b216d730857ffe6/coverage-7.13.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092", size = 266308, upload-time = "2026-02-09T12:57:49.345Z" }, - { url = "https://files.pythonhosted.org/packages/37/6f/39d41eca0eab3cc82115953ad41c4e77935286c930e8fad15eaed1389d83/coverage-7.13.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9", size = 267452, upload-time = "2026-02-09T12:57:50.811Z" }, - { url = "https://files.pythonhosted.org/packages/50/6d/39c0fbb8fc5cd4d2090811e553c2108cf5112e882f82505ee7495349a6bf/coverage-7.13.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26", size = 261057, upload-time = "2026-02-09T12:57:52.447Z" }, - { url = "https://files.pythonhosted.org/packages/a4/a2/60010c669df5fa603bb5a97fb75407e191a846510da70ac657eb696b7fce/coverage-7.13.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2", size = 263875, upload-time = "2026-02-09T12:57:53.938Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d9/63b22a6bdbd17f1f96e9ed58604c2a6b0e72a9133e37d663bef185877cf6/coverage-7.13.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940", size = 261500, upload-time = "2026-02-09T12:57:56.012Z" }, - { url = "https://files.pythonhosted.org/packages/70/bf/69f86ba1ad85bc3ad240e4c0e57a2e620fbc0e1645a47b5c62f0e941ad7f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c", size = 265212, upload-time = "2026-02-09T12:57:57.5Z" }, - { url = "https://files.pythonhosted.org/packages/ae/f2/5f65a278a8c2148731831574c73e42f57204243d33bedaaf18fa79c5958f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0", size = 260398, upload-time = "2026-02-09T12:57:59.027Z" }, - { url = "https://files.pythonhosted.org/packages/ef/80/6e8280a350ee9fea92f14b8357448a242dcaa243cb2c72ab0ca591f66c8c/coverage-7.13.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b", size = 262584, upload-time = "2026-02-09T12:58:01.129Z" }, - { url = "https://files.pythonhosted.org/packages/22/63/01ff182fc95f260b539590fb12c11ad3e21332c15f9799cb5e2386f71d9f/coverage-7.13.4-cp313-cp313t-win32.whl", hash = "sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9", size = 222688, upload-time = "2026-02-09T12:58:02.736Z" }, - { url = "https://files.pythonhosted.org/packages/a9/43/89de4ef5d3cd53b886afa114065f7e9d3707bdb3e5efae13535b46ae483d/coverage-7.13.4-cp313-cp313t-win_amd64.whl", hash = "sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd", size = 223746, upload-time = "2026-02-09T12:58:05.362Z" }, - { url = "https://files.pythonhosted.org/packages/35/39/7cf0aa9a10d470a5309b38b289b9bb07ddeac5d61af9b664fe9775a4cb3e/coverage-7.13.4-cp313-cp313t-win_arm64.whl", hash = "sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997", size = 222003, upload-time = "2026-02-09T12:58:06.952Z" }, - { url = "https://files.pythonhosted.org/packages/92/11/a9cf762bb83386467737d32187756a42094927150c3e107df4cb078e8590/coverage-7.13.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:300deaee342f90696ed186e3a00c71b5b3d27bffe9e827677954f4ee56969601", size = 219522, upload-time = "2026-02-09T12:58:08.623Z" }, - { url = "https://files.pythonhosted.org/packages/d3/28/56e6d892b7b052236d67c95f1936b6a7cf7c3e2634bf27610b8cbd7f9c60/coverage-7.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29e3220258d682b6226a9b0925bc563ed9a1ebcff3cad30f043eceea7eaf2689", size = 219855, upload-time = "2026-02-09T12:58:10.176Z" }, - { url = "https://files.pythonhosted.org/packages/e5/69/233459ee9eb0c0d10fcc2fe425a029b3fa5ce0f040c966ebce851d030c70/coverage-7.13.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:391ee8f19bef69210978363ca930f7328081c6a0152f1166c91f0b5fdd2a773c", size = 250887, upload-time = "2026-02-09T12:58:12.503Z" }, - { url = "https://files.pythonhosted.org/packages/06/90/2cdab0974b9b5bbc1623f7876b73603aecac11b8d95b85b5b86b32de5eab/coverage-7.13.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0dd7ab8278f0d58a0128ba2fca25824321f05d059c1441800e934ff2efa52129", size = 253396, upload-time = "2026-02-09T12:58:14.615Z" }, - { url = "https://files.pythonhosted.org/packages/ac/15/ea4da0f85bf7d7b27635039e649e99deb8173fe551096ea15017f7053537/coverage-7.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78cdf0d578b15148b009ccf18c686aa4f719d887e76e6b40c38ffb61d264a552", size = 254745, upload-time = "2026-02-09T12:58:16.162Z" }, - { url = "https://files.pythonhosted.org/packages/99/11/bb356e86920c655ca4d61daee4e2bbc7258f0a37de0be32d233b561134ff/coverage-7.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:48685fee12c2eb3b27c62f2658e7ea21e9c3239cba5a8a242801a0a3f6a8c62a", size = 257055, upload-time = "2026-02-09T12:58:17.892Z" }, - { url = "https://files.pythonhosted.org/packages/c9/0f/9ae1f8cb17029e09da06ca4e28c9e1d5c1c0a511c7074592e37e0836c915/coverage-7.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4e83efc079eb39480e6346a15a1bcb3e9b04759c5202d157e1dd4303cd619356", size = 250911, upload-time = "2026-02-09T12:58:19.495Z" }, - { url = "https://files.pythonhosted.org/packages/89/3a/adfb68558fa815cbc29747b553bc833d2150228f251b127f1ce97e48547c/coverage-7.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecae9737b72408d6a950f7e525f30aca12d4bd8dd95e37342e5beb3a2a8c4f71", size = 252754, upload-time = "2026-02-09T12:58:21.064Z" }, - { url = "https://files.pythonhosted.org/packages/32/b1/540d0c27c4e748bd3cd0bd001076ee416eda993c2bae47a73b7cc9357931/coverage-7.13.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ae4578f8528569d3cf303fef2ea569c7f4c4059a38c8667ccef15c6e1f118aa5", size = 250720, upload-time = "2026-02-09T12:58:22.622Z" }, - { url = "https://files.pythonhosted.org/packages/c7/95/383609462b3ffb1fe133014a7c84fc0dd01ed55ac6140fa1093b5af7ebb1/coverage-7.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6fdef321fdfbb30a197efa02d48fcd9981f0d8ad2ae8903ac318adc653f5df98", size = 254994, upload-time = "2026-02-09T12:58:24.548Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ba/1761138e86c81680bfc3c49579d66312865457f9fe405b033184e5793cb3/coverage-7.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b0f6ccf3dbe577170bebfce1318707d0e8c3650003cb4b3a9dd744575daa8b5", size = 250531, upload-time = "2026-02-09T12:58:26.271Z" }, - { url = "https://files.pythonhosted.org/packages/f8/8e/05900df797a9c11837ab59c4d6fe94094e029582aab75c3309a93e6fb4e3/coverage-7.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75fcd519f2a5765db3f0e391eb3b7d150cce1a771bf4c9f861aeab86c767a3c0", size = 252189, upload-time = "2026-02-09T12:58:27.807Z" }, - { url = "https://files.pythonhosted.org/packages/00/bd/29c9f2db9ea4ed2738b8a9508c35626eb205d51af4ab7bf56a21a2e49926/coverage-7.13.4-cp314-cp314-win32.whl", hash = "sha256:8e798c266c378da2bd819b0677df41ab46d78065fb2a399558f3f6cae78b2fbb", size = 222258, upload-time = "2026-02-09T12:58:29.441Z" }, - { url = "https://files.pythonhosted.org/packages/a7/4d/1f8e723f6829977410efeb88f73673d794075091c8c7c18848d273dc9d73/coverage-7.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505", size = 223073, upload-time = "2026-02-09T12:58:31.026Z" }, - { url = "https://files.pythonhosted.org/packages/51/5b/84100025be913b44e082ea32abcf1afbf4e872f5120b7a1cab1d331b1e13/coverage-7.13.4-cp314-cp314-win_arm64.whl", hash = "sha256:ad27098a189e5838900ce4c2a99f2fe42a0bf0c2093c17c69b45a71579e8d4a2", size = 221638, upload-time = "2026-02-09T12:58:32.599Z" }, - { url = "https://files.pythonhosted.org/packages/a7/e4/c884a405d6ead1370433dad1e3720216b4f9fd8ef5b64bfd984a2a60a11a/coverage-7.13.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:85480adfb35ffc32d40918aad81b89c69c9cc5661a9b8a81476d3e645321a056", size = 220246, upload-time = "2026-02-09T12:58:34.181Z" }, - { url = "https://files.pythonhosted.org/packages/81/5c/4d7ed8b23b233b0fffbc9dfec53c232be2e695468523242ea9fd30f97ad2/coverage-7.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:79be69cf7f3bf9b0deeeb062eab7ac7f36cd4cc4c4dd694bd28921ba4d8596cc", size = 220514, upload-time = "2026-02-09T12:58:35.704Z" }, - { url = "https://files.pythonhosted.org/packages/2f/6f/3284d4203fd2f28edd73034968398cd2d4cb04ab192abc8cff007ea35679/coverage-7.13.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:caa421e2684e382c5d8973ac55e4f36bed6821a9bad5c953494de960c74595c9", size = 261877, upload-time = "2026-02-09T12:58:37.864Z" }, - { url = "https://files.pythonhosted.org/packages/09/aa/b672a647bbe1556a85337dc95bfd40d146e9965ead9cc2fe81bde1e5cbce/coverage-7.13.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14375934243ee05f56c45393fe2ce81fe5cc503c07cee2bdf1725fb8bef3ffaf", size = 264004, upload-time = "2026-02-09T12:58:39.492Z" }, - { url = "https://files.pythonhosted.org/packages/79/a1/aa384dbe9181f98bba87dd23dda436f0c6cf2e148aecbb4e50fc51c1a656/coverage-7.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25a41c3104d08edb094d9db0d905ca54d0cd41c928bb6be3c4c799a54753af55", size = 266408, upload-time = "2026-02-09T12:58:41.852Z" }, - { url = "https://files.pythonhosted.org/packages/53/5e/5150bf17b4019bc600799f376bb9606941e55bd5a775dc1e096b6ffea952/coverage-7.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f01afcff62bf9a08fb32b2c1d6e924236c0383c02c790732b6537269e466a72", size = 267544, upload-time = "2026-02-09T12:58:44.093Z" }, - { url = "https://files.pythonhosted.org/packages/e0/ed/f1de5c675987a4a7a672250d2c5c9d73d289dbf13410f00ed7181d8017dd/coverage-7.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eb9078108fbf0bcdde37c3f4779303673c2fa1fe8f7956e68d447d0dd426d38a", size = 260980, upload-time = "2026-02-09T12:58:45.721Z" }, - { url = "https://files.pythonhosted.org/packages/b3/e3/fe758d01850aa172419a6743fe76ba8b92c29d181d4f676ffe2dae2ba631/coverage-7.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e086334e8537ddd17e5f16a344777c1ab8194986ec533711cbe6c41cde841b6", size = 263871, upload-time = "2026-02-09T12:58:47.334Z" }, - { url = "https://files.pythonhosted.org/packages/b6/76/b829869d464115e22499541def9796b25312b8cf235d3bb00b39f1675395/coverage-7.13.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:725d985c5ab621268b2edb8e50dfe57633dc69bda071abc470fed55a14935fd3", size = 261472, upload-time = "2026-02-09T12:58:48.995Z" }, - { url = "https://files.pythonhosted.org/packages/14/9e/caedb1679e73e2f6ad240173f55218488bfe043e38da577c4ec977489915/coverage-7.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3c06f0f1337c667b971ca2f975523347e63ec5e500b9aa5882d91931cd3ef750", size = 265210, upload-time = "2026-02-09T12:58:51.178Z" }, - { url = "https://files.pythonhosted.org/packages/3a/10/0dd02cb009b16ede425b49ec344aba13a6ae1dc39600840ea6abcb085ac4/coverage-7.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:590c0ed4bf8e85f745e6b805b2e1c457b2e33d5255dd9729743165253bc9ad39", size = 260319, upload-time = "2026-02-09T12:58:53.081Z" }, - { url = "https://files.pythonhosted.org/packages/92/8e/234d2c927af27c6d7a5ffad5bd2cf31634c46a477b4c7adfbfa66baf7ebb/coverage-7.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eb30bf180de3f632cd043322dad5751390e5385108b2807368997d1a92a509d0", size = 262638, upload-time = "2026-02-09T12:58:55.258Z" }, - { url = "https://files.pythonhosted.org/packages/2f/64/e5547c8ff6964e5965c35a480855911b61509cce544f4d442caa759a0702/coverage-7.13.4-cp314-cp314t-win32.whl", hash = "sha256:c4240e7eded42d131a2d2c4dec70374b781b043ddc79a9de4d55ca71f8e98aea", size = 223040, upload-time = "2026-02-09T12:58:56.936Z" }, - { url = "https://files.pythonhosted.org/packages/c7/96/38086d58a181aac86d503dfa9c47eb20715a79c3e3acbdf786e92e5c09a8/coverage-7.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4c7d3cc01e7350f2f0f6f7036caaf5673fb56b6998889ccfe9e1c1fe75a9c932", size = 224148, upload-time = "2026-02-09T12:58:58.645Z" }, - { url = "https://files.pythonhosted.org/packages/ce/72/8d10abd3740a0beb98c305e0c3faf454366221c0f37a8bcf8f60020bb65a/coverage-7.13.4-cp314-cp314t-win_arm64.whl", hash = "sha256:23e3f687cf945070d1c90f85db66d11e3025665d8dafa831301a0e0038f3db9b", size = 222172, upload-time = "2026-02-09T12:59:00.396Z" }, - { url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" }, +version = "7.13.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967, upload-time = "2026-03-17T10:33:18.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/37/d24c8f8220ff07b839b2c043ea4903a33b0f455abe673ae3c03bbdb7f212/coverage-7.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66a80c616f80181f4d643b0f9e709d97bcea413ecd9631e1dedc7401c8e6695d", size = 219381, upload-time = "2026-03-17T10:30:14.68Z" }, + { url = "https://files.pythonhosted.org/packages/35/8b/cd129b0ca4afe886a6ce9d183c44d8301acbd4ef248622e7c49a23145605/coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:145ede53ccbafb297c1c9287f788d1bc3efd6c900da23bf6931b09eafc931587", size = 219880, upload-time = "2026-03-17T10:30:16.231Z" }, + { url = "https://files.pythonhosted.org/packages/55/2f/e0e5b237bffdb5d6c530ce87cc1d413a5b7d7dfd60fb067ad6d254c35c76/coverage-7.13.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0672854dc733c342fa3e957e0605256d2bf5934feeac328da9e0b5449634a642", size = 250303, upload-time = "2026-03-17T10:30:17.748Z" }, + { url = "https://files.pythonhosted.org/packages/92/be/b1afb692be85b947f3401375851484496134c5554e67e822c35f28bf2fbc/coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ec10e2a42b41c923c2209b846126c6582db5e43a33157e9870ba9fb70dc7854b", size = 252218, upload-time = "2026-03-17T10:30:19.804Z" }, + { url = "https://files.pythonhosted.org/packages/da/69/2f47bb6fa1b8d1e3e5d0c4be8ccb4313c63d742476a619418f85740d597b/coverage-7.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be3d4bbad9d4b037791794ddeedd7d64a56f5933a2c1373e18e9e568b9141686", size = 254326, upload-time = "2026-03-17T10:30:21.321Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d0/79db81da58965bd29dabc8f4ad2a2af70611a57cba9d1ec006f072f30a54/coverage-7.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d2afbc5cc54d286bfb54541aa50b64cdb07a718227168c87b9e2fb8f25e1743", size = 256267, upload-time = "2026-03-17T10:30:23.094Z" }, + { url = "https://files.pythonhosted.org/packages/e5/32/d0d7cc8168f91ddab44c0ce4806b969df5f5fdfdbb568eaca2dbc2a04936/coverage-7.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3ad050321264c49c2fa67bb599100456fc51d004b82534f379d16445da40fb75", size = 250430, upload-time = "2026-03-17T10:30:25.311Z" }, + { url = "https://files.pythonhosted.org/packages/4d/06/a055311d891ddbe231cd69fdd20ea4be6e3603ffebddf8704b8ca8e10a3c/coverage-7.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7300c8a6d13335b29bb76d7651c66af6bd8658517c43499f110ddc6717bfc209", size = 252017, upload-time = "2026-03-17T10:30:27.284Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f6/d0fd2d21e29a657b5f77a2fe7082e1568158340dceb941954f776dce1b7b/coverage-7.13.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:eb07647a5738b89baab047f14edd18ded523de60f3b30e75c2acc826f79c839a", size = 250080, upload-time = "2026-03-17T10:30:29.481Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ab/0d7fb2efc2e9a5eb7ddcc6e722f834a69b454b7e6e5888c3a8567ecffb31/coverage-7.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9adb6688e3b53adffefd4a52d72cbd8b02602bfb8f74dcd862337182fd4d1a4e", size = 253843, upload-time = "2026-03-17T10:30:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/ba/6f/7467b917bbf5408610178f62a49c0ed4377bb16c1657f689cc61470da8ce/coverage-7.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7c8d4bc913dd70b93488d6c496c77f3aff5ea99a07e36a18f865bca55adef8bd", size = 249802, upload-time = "2026-03-17T10:30:33.358Z" }, + { url = "https://files.pythonhosted.org/packages/75/2c/1172fb689df92135f5bfbbd69fc83017a76d24ea2e2f3a1154007e2fb9f8/coverage-7.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e3c426ffc4cd952f54ee9ffbdd10345709ecc78a3ecfd796a57236bfad0b9b8", size = 250707, upload-time = "2026-03-17T10:30:35.2Z" }, + { url = "https://files.pythonhosted.org/packages/67/21/9ac389377380a07884e3b48ba7a620fcd9dbfaf1d40565facdc6b36ec9ef/coverage-7.13.5-cp311-cp311-win32.whl", hash = "sha256:259b69bb83ad9894c4b25be2528139eecba9a82646ebdda2d9db1ba28424a6bf", size = 221880, upload-time = "2026-03-17T10:30:36.775Z" }, + { url = "https://files.pythonhosted.org/packages/af/7f/4cd8a92531253f9d7c1bbecd9fa1b472907fb54446ca768c59b531248dc5/coverage-7.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:258354455f4e86e3e9d0d17571d522e13b4e1e19bf0f8596bcf9476d61e7d8a9", size = 222816, upload-time = "2026-03-17T10:30:38.891Z" }, + { url = "https://files.pythonhosted.org/packages/12/a6/1d3f6155fb0010ca68eba7fe48ca6c9da7385058b77a95848710ecf189b1/coverage-7.13.5-cp311-cp311-win_arm64.whl", hash = "sha256:bff95879c33ec8da99fc9b6fe345ddb5be6414b41d6d1ad1c8f188d26f36e028", size = 221483, upload-time = "2026-03-17T10:30:40.463Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c3/a396306ba7db865bf96fc1fb3b7fd29bcbf3d829df642e77b13555163cd6/coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01", size = 219554, upload-time = "2026-03-17T10:30:42.208Z" }, + { url = "https://files.pythonhosted.org/packages/a6/16/a68a19e5384e93f811dccc51034b1fd0b865841c390e3c931dcc4699e035/coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422", size = 219908, upload-time = "2026-03-17T10:30:43.906Z" }, + { url = "https://files.pythonhosted.org/packages/29/72/20b917c6793af3a5ceb7fb9c50033f3ec7865f2911a1416b34a7cfa0813b/coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f", size = 251419, upload-time = "2026-03-17T10:30:45.545Z" }, + { url = "https://files.pythonhosted.org/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5", size = 254159, upload-time = "2026-03-17T10:30:47.204Z" }, + { url = "https://files.pythonhosted.org/packages/9d/00/7b0edcfe64e2ed4c0340dac14a52ad0f4c9bd0b8b5e531af7d55b703db7c/coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376", size = 255270, upload-time = "2026-03-17T10:30:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/7ffc4ba0f5d0a55c1e84ea7cee39c9fc06af7b170513d83fbf3bbefce280/coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256", size = 257538, upload-time = "2026-03-17T10:30:50.77Z" }, + { url = "https://files.pythonhosted.org/packages/81/bd/73ddf85f93f7e6fa83e77ccecb6162d9415c79007b4bc124008a4995e4a7/coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c", size = 251821, upload-time = "2026-03-17T10:30:52.5Z" }, + { url = "https://files.pythonhosted.org/packages/a0/81/278aff4e8dec4926a0bcb9486320752811f543a3ce5b602cc7a29978d073/coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5", size = 253191, upload-time = "2026-03-17T10:30:54.543Z" }, + { url = "https://files.pythonhosted.org/packages/70/ee/fe1621488e2e0a58d7e94c4800f0d96f79671553488d401a612bebae324b/coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09", size = 251337, upload-time = "2026-03-17T10:30:56.663Z" }, + { url = "https://files.pythonhosted.org/packages/37/a6/f79fb37aa104b562207cc23cb5711ab6793608e246cae1e93f26b2236ed9/coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9", size = 255404, upload-time = "2026-03-17T10:30:58.427Z" }, + { url = "https://files.pythonhosted.org/packages/75/f0/ed15262a58ec81ce457ceb717b7f78752a1713556b19081b76e90896e8d4/coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf", size = 250903, upload-time = "2026-03-17T10:31:00.093Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e9/9129958f20e7e9d4d56d51d42ccf708d15cac355ff4ac6e736e97a9393d2/coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c", size = 252780, upload-time = "2026-03-17T10:31:01.916Z" }, + { url = "https://files.pythonhosted.org/packages/a4/d7/0ad9b15812d81272db94379fe4c6df8fd17781cc7671fdfa30c76ba5ff7b/coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf", size = 222093, upload-time = "2026-03-17T10:31:03.642Z" }, + { url = "https://files.pythonhosted.org/packages/29/3d/821a9a5799fac2556bcf0bd37a70d1d11fa9e49784b6d22e92e8b2f85f18/coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810", size = 222900, upload-time = "2026-03-17T10:31:05.651Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/2238c2ad08e35cf4f020ea721f717e09ec3152aea75d191a7faf3ef009a8/coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de", size = 221515, upload-time = "2026-03-17T10:31:07.293Z" }, + { url = "https://files.pythonhosted.org/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1", size = 219576, upload-time = "2026-03-17T10:31:09.045Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3", size = 219942, upload-time = "2026-03-17T10:31:10.708Z" }, + { url = "https://files.pythonhosted.org/packages/5f/13/93419671cee82b780bab7ea96b67c8ef448f5f295f36bf5031154ec9a790/coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26", size = 250935, upload-time = "2026-03-17T10:31:12.392Z" }, + { url = "https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3", size = 253541, upload-time = "2026-03-17T10:31:14.247Z" }, + { url = "https://files.pythonhosted.org/packages/4e/5e/3ee3b835647be646dcf3c65a7c6c18f87c27326a858f72ab22c12730773d/coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b", size = 254780, upload-time = "2026-03-17T10:31:16.193Z" }, + { url = "https://files.pythonhosted.org/packages/44/b3/cb5bd1a04cfcc49ede6cd8409d80bee17661167686741e041abc7ee1b9a9/coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a", size = 256912, upload-time = "2026-03-17T10:31:17.89Z" }, + { url = "https://files.pythonhosted.org/packages/1b/66/c1dceb7b9714473800b075f5c8a84f4588f887a90eb8645282031676e242/coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969", size = 251165, upload-time = "2026-03-17T10:31:19.605Z" }, + { url = "https://files.pythonhosted.org/packages/b7/62/5502b73b97aa2e53ea22a39cf8649ff44827bef76d90bf638777daa27a9d/coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161", size = 252908, upload-time = "2026-03-17T10:31:21.312Z" }, + { url = "https://files.pythonhosted.org/packages/7d/37/7792c2d69854397ca77a55c4646e5897c467928b0e27f2d235d83b5d08c6/coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15", size = 250873, upload-time = "2026-03-17T10:31:23.565Z" }, + { url = "https://files.pythonhosted.org/packages/a3/23/bc866fb6163be52a8a9e5d708ba0d3b1283c12158cefca0a8bbb6e247a43/coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1", size = 255030, upload-time = "2026-03-17T10:31:25.58Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8b/ef67e1c222ef49860701d346b8bbb70881bef283bd5f6cbba68a39a086c7/coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6", size = 250694, upload-time = "2026-03-17T10:31:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/46/0d/866d1f74f0acddbb906db212e096dee77a8e2158ca5e6bb44729f9d93298/coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17", size = 252469, upload-time = "2026-03-17T10:31:29.472Z" }, + { url = "https://files.pythonhosted.org/packages/7a/f5/be742fec31118f02ce42b21c6af187ad6a344fed546b56ca60caacc6a9a0/coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85", size = 222112, upload-time = "2026-03-17T10:31:31.526Z" }, + { url = "https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b", size = 222923, upload-time = "2026-03-17T10:31:33.633Z" }, + { url = "https://files.pythonhosted.org/packages/48/af/fea819c12a095781f6ccd504890aaddaf88b8fab263c4940e82c7b770124/coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664", size = 221540, upload-time = "2026-03-17T10:31:35.445Z" }, + { url = "https://files.pythonhosted.org/packages/23/d2/17879af479df7fbbd44bd528a31692a48f6b25055d16482fdf5cdb633805/coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d", size = 220262, upload-time = "2026-03-17T10:31:37.184Z" }, + { url = "https://files.pythonhosted.org/packages/5b/4c/d20e554f988c8f91d6a02c5118f9abbbf73a8768a3048cb4962230d5743f/coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0", size = 220617, upload-time = "2026-03-17T10:31:39.245Z" }, + { url = "https://files.pythonhosted.org/packages/29/9c/f9f5277b95184f764b24e7231e166dfdb5780a46d408a2ac665969416d61/coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806", size = 261912, upload-time = "2026-03-17T10:31:41.324Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f6/7f1ab39393eeb50cfe4747ae8ef0e4fc564b989225aa1152e13a180d74f8/coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3", size = 263987, upload-time = "2026-03-17T10:31:43.724Z" }, + { url = "https://files.pythonhosted.org/packages/a0/d7/62c084fb489ed9c6fbdf57e006752e7c516ea46fd690e5ed8b8617c7d52e/coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9", size = 266416, upload-time = "2026-03-17T10:31:45.769Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f6/df63d8660e1a0bff6125947afda112a0502736f470d62ca68b288ea762d8/coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd", size = 267558, upload-time = "2026-03-17T10:31:48.293Z" }, + { url = "https://files.pythonhosted.org/packages/5b/02/353ca81d36779bd108f6d384425f7139ac3c58c750dcfaafe5d0bee6436b/coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606", size = 261163, upload-time = "2026-03-17T10:31:50.125Z" }, + { url = "https://files.pythonhosted.org/packages/2c/16/2e79106d5749bcaf3aee6d309123548e3276517cd7851faa8da213bc61bf/coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e", size = 263981, upload-time = "2026-03-17T10:31:51.961Z" }, + { url = "https://files.pythonhosted.org/packages/29/c7/c29e0c59ffa6942030ae6f50b88ae49988e7e8da06de7ecdbf49c6d4feae/coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0", size = 261604, upload-time = "2026-03-17T10:31:53.872Z" }, + { url = "https://files.pythonhosted.org/packages/40/48/097cdc3db342f34006a308ab41c3a7c11c3f0d84750d340f45d88a782e00/coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87", size = 265321, upload-time = "2026-03-17T10:31:55.997Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1f/4994af354689e14fd03a75f8ec85a9a68d94e0188bbdab3fc1516b55e512/coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479", size = 260502, upload-time = "2026-03-17T10:31:58.308Z" }, + { url = "https://files.pythonhosted.org/packages/22/c6/9bb9ef55903e628033560885f5c31aa227e46878118b63ab15dc7ba87797/coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2", size = 262688, upload-time = "2026-03-17T10:32:00.141Z" }, + { url = "https://files.pythonhosted.org/packages/14/4f/f5df9007e50b15e53e01edea486814783a7f019893733d9e4d6caad75557/coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a", size = 222788, upload-time = "2026-03-17T10:32:02.246Z" }, + { url = "https://files.pythonhosted.org/packages/e1/98/aa7fccaa97d0f3192bec013c4e6fd6d294a6ed44b640e6bb61f479e00ed5/coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819", size = 223851, upload-time = "2026-03-17T10:32:04.416Z" }, + { url = "https://files.pythonhosted.org/packages/3d/8b/e5c469f7352651e5f013198e9e21f97510b23de957dd06a84071683b4b60/coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911", size = 222104, upload-time = "2026-03-17T10:32:06.65Z" }, + { url = "https://files.pythonhosted.org/packages/8e/77/39703f0d1d4b478bfd30191d3c14f53caf596fac00efb3f8f6ee23646439/coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f", size = 219621, upload-time = "2026-03-17T10:32:08.589Z" }, + { url = "https://files.pythonhosted.org/packages/e2/3e/51dff36d99ae14639a133d9b164d63e628532e2974d8b1edb99dd1ebc733/coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e", size = 219953, upload-time = "2026-03-17T10:32:10.507Z" }, + { url = "https://files.pythonhosted.org/packages/6a/6c/1f1917b01eb647c2f2adc9962bd66c79eb978951cab61bdc1acab3290c07/coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a", size = 250992, upload-time = "2026-03-17T10:32:12.41Z" }, + { url = "https://files.pythonhosted.org/packages/22/e5/06b1f88f42a5a99df42ce61208bdec3bddb3d261412874280a19796fc09c/coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510", size = 253503, upload-time = "2026-03-17T10:32:14.449Z" }, + { url = "https://files.pythonhosted.org/packages/80/28/2a148a51e5907e504fa7b85490277734e6771d8844ebcc48764a15e28155/coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247", size = 254852, upload-time = "2026-03-17T10:32:16.56Z" }, + { url = "https://files.pythonhosted.org/packages/61/77/50e8d3d85cc0b7ebe09f30f151d670e302c7ff4a1bf6243f71dd8b0981fa/coverage-7.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6", size = 257161, upload-time = "2026-03-17T10:32:19.004Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c4/b5fd1d4b7bf8d0e75d997afd3925c59ba629fc8616f1b3aae7605132e256/coverage-7.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0", size = 251021, upload-time = "2026-03-17T10:32:21.344Z" }, + { url = "https://files.pythonhosted.org/packages/f8/66/6ea21f910e92d69ef0b1c3346ea5922a51bad4446c9126db2ae96ee24c4c/coverage-7.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882", size = 252858, upload-time = "2026-03-17T10:32:23.506Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ea/879c83cb5d61aa2a35fb80e72715e92672daef8191b84911a643f533840c/coverage-7.13.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740", size = 250823, upload-time = "2026-03-17T10:32:25.516Z" }, + { url = "https://files.pythonhosted.org/packages/8a/fb/616d95d3adb88b9803b275580bdeee8bd1b69a886d057652521f83d7322f/coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16", size = 255099, upload-time = "2026-03-17T10:32:27.944Z" }, + { url = "https://files.pythonhosted.org/packages/1c/93/25e6917c90ec1c9a56b0b26f6cad6408e5f13bb6b35d484a0d75c9cf000d/coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0", size = 250638, upload-time = "2026-03-17T10:32:29.914Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7b/dc1776b0464145a929deed214aef9fb1493f159b59ff3c7eeeedf91eddd0/coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0", size = 252295, upload-time = "2026-03-17T10:32:31.981Z" }, + { url = "https://files.pythonhosted.org/packages/ea/fb/99cbbc56a26e07762a2740713f3c8f9f3f3106e3a3dd8cc4474954bccd34/coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc", size = 222360, upload-time = "2026-03-17T10:32:34.233Z" }, + { url = "https://files.pythonhosted.org/packages/8d/b7/4758d4f73fb536347cc5e4ad63662f9d60ba9118cb6785e9616b2ce5d7fa/coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633", size = 223174, upload-time = "2026-03-17T10:32:36.369Z" }, + { url = "https://files.pythonhosted.org/packages/2c/f2/24d84e1dfe70f8ac9fdf30d338239860d0d1d5da0bda528959d0ebc9da28/coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8", size = 221739, upload-time = "2026-03-17T10:32:38.736Z" }, + { url = "https://files.pythonhosted.org/packages/60/5b/4a168591057b3668c2428bff25dd3ebc21b629d666d90bcdfa0217940e84/coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b", size = 220351, upload-time = "2026-03-17T10:32:41.196Z" }, + { url = "https://files.pythonhosted.org/packages/f5/21/1fd5c4dbfe4a58b6b99649125635df46decdfd4a784c3cd6d410d303e370/coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c", size = 220612, upload-time = "2026-03-17T10:32:43.204Z" }, + { url = "https://files.pythonhosted.org/packages/d6/fe/2a924b3055a5e7e4512655a9d4609781b0d62334fa0140c3e742926834e2/coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9", size = 261985, upload-time = "2026-03-17T10:32:45.514Z" }, + { url = "https://files.pythonhosted.org/packages/d7/0d/c8928f2bd518c45990fe1a2ab8db42e914ef9b726c975facc4282578c3eb/coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29", size = 264107, upload-time = "2026-03-17T10:32:47.971Z" }, + { url = "https://files.pythonhosted.org/packages/ef/ae/4ae35bbd9a0af9d820362751f0766582833c211224b38665c0f8de3d487f/coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607", size = 266513, upload-time = "2026-03-17T10:32:50.1Z" }, + { url = "https://files.pythonhosted.org/packages/9c/20/d326174c55af36f74eac6ae781612d9492f060ce8244b570bb9d50d9d609/coverage-7.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90", size = 267650, upload-time = "2026-03-17T10:32:52.391Z" }, + { url = "https://files.pythonhosted.org/packages/7a/5e/31484d62cbd0eabd3412e30d74386ece4a0837d4f6c3040a653878bfc019/coverage-7.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3", size = 261089, upload-time = "2026-03-17T10:32:54.544Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d8/49a72d6de146eebb0b7e48cc0f4bc2c0dd858e3d4790ab2b39a2872b62bd/coverage-7.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab", size = 263982, upload-time = "2026-03-17T10:32:56.803Z" }, + { url = "https://files.pythonhosted.org/packages/06/3b/0351f1bd566e6e4dd39e978efe7958bde1d32f879e85589de147654f57bb/coverage-7.13.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562", size = 261579, upload-time = "2026-03-17T10:32:59.466Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ce/796a2a2f4017f554d7810f5c573449b35b1e46788424a548d4d19201b222/coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2", size = 265316, upload-time = "2026-03-17T10:33:01.847Z" }, + { url = "https://files.pythonhosted.org/packages/3d/16/d5ae91455541d1a78bc90abf495be600588aff8f6db5c8b0dae739fa39c9/coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea", size = 260427, upload-time = "2026-03-17T10:33:03.945Z" }, + { url = "https://files.pythonhosted.org/packages/48/11/07f413dba62db21fb3fad5d0de013a50e073cc4e2dc4306e770360f6dfc8/coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a", size = 262745, upload-time = "2026-03-17T10:33:06.285Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/d792371332eb4663115becf4bad47e047d16234b1aff687b1b18c58d60ae/coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215", size = 223146, upload-time = "2026-03-17T10:33:08.756Z" }, + { url = "https://files.pythonhosted.org/packages/db/51/37221f59a111dca5e85be7dbf09696323b5b9f13ff65e0641d535ed06ea8/coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43", size = 224254, upload-time = "2026-03-17T10:33:11.174Z" }, + { url = "https://files.pythonhosted.org/packages/54/83/6acacc889de8987441aa7d5adfbdbf33d288dad28704a67e574f1df9bcbb/coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45", size = 222276, upload-time = "2026-03-17T10:33:13.466Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346, upload-time = "2026-03-17T10:33:15.691Z" }, ] [package.optional-dependencies] @@ -652,10 +652,10 @@ wheels = [ [[package]] name = "cuda-pathfinder" -version = "1.4.2" +version = "1.4.3" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/92/de/8ca2b613042550dcf9ef50c596c8b1f602afda92cf9032ac28a73f6ee410/cuda_pathfinder-1.4.2-py3-none-any.whl", hash = "sha256:eb354abc20278f8609dc5b666a24648655bef5613c6dfe78a238a6fd95566754", size = 44779, upload-time = "2026-03-10T21:57:30.974Z" }, + { url = "https://files.pythonhosted.org/packages/c0/59/911a1a597264f1fb7ac176995a0f0b6062e37f8c1b6e0f23071a76838507/cuda_pathfinder-1.4.3-py3-none-any.whl", hash = "sha256:4345d8ead1f701c4fb8a99be6bc1843a7348b6ba0ef3b031f5a2d66fb128ae4c", size = 47951, upload-time = "2026-03-16T21:31:25.526Z" }, ] [[package]] @@ -669,7 +669,7 @@ wheels = [ [[package]] name = "dask" -version = "2026.1.2" +version = "2026.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -681,9 +681,9 @@ dependencies = [ { name = "pyyaml" }, { name = "toolz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bd/52/b0f9172b22778def907db1ff173249e4eb41f054b46a9c83b1528aaf811f/dask-2026.1.2.tar.gz", hash = "sha256:1136683de2750d98ea792670f7434e6c1cfce90cab2cc2f2495a9e60fd25a4fc", size = 10997838, upload-time = "2026-01-30T21:04:20.54Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/2a/5d8cc1579590af86576dde890254440e478c7174b93a02095ecfc2e6ba38/dask-2026.3.0.tar.gz", hash = "sha256:f7d96c8274e8a900d217c1ff6ea8d1bbf0b4c2c21e74a409644498d925eb8f85", size = 11000710, upload-time = "2026-03-18T07:10:14.945Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl", hash = "sha256:46a0cf3b8d87f78a3d2e6b145aea4418a6d6d606fe6a16c79bd8ca2bb862bc91", size = 1482084, upload-time = "2026-01-30T21:04:18.363Z" }, + { url = "https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl", hash = "sha256:be614b9242b0b38288060fb2d7696125946469c98a1c30e174883fd199e0428d", size = 1485630, upload-time = "2026-03-18T07:10:12.832Z" }, ] [package.optional-dependencies] @@ -1147,14 +1147,14 @@ wheels = [ [[package]] name = "importlib-metadata" -version = "8.7.1" +version = "9.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "zipp", marker = "python_full_version < '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/01/15bb152d77b21318514a96f43af312635eb2500c96b55398d020c93d86ea/importlib_metadata-9.0.0.tar.gz", hash = "sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc", size = 56405, upload-time = "2026-03-20T06:42:56.999Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, + { url = "https://files.pythonhosted.org/packages/38/3d/2d244233ac4f76e38533cfcb2991c9eb4c7bf688ae0a036d30725b8faafe/importlib_metadata-9.0.0-py3-none-any.whl", hash = "sha256:2d21d1cc5a017bd0559e36150c21c830ab1dc304dedd1b7ea85d20f45ef3edd7", size = 27789, upload-time = "2026-03-20T06:42:55.665Z" }, ] [[package]] @@ -1317,11 +1317,11 @@ wheels = [ [[package]] name = "jsonpointer" -version = "3.0.0" +version = "3.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114, upload-time = "2024-06-10T19:24:42.462Z" } +sdist = { url = "https://files.pythonhosted.org/packages/48/bf/9ecc036fbc15cf4153ea6ed4dbeed31ef043f762cccc9d44a534be8319b0/jsonpointer-3.1.0.tar.gz", hash = "sha256:f9b39abd59ba8c1de8a4ff16141605d2a8dacc4dd6cf399672cf237bfe47c211", size = 9000, upload-time = "2026-03-20T21:47:09.982Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/cebb241a435cbf4626b5ea096d8385c04416d7ca3082a15299b746e248fa/jsonpointer-3.1.0-py3-none-any.whl", hash = "sha256:f82aa0f745001f169b96473348370b43c3f581446889c41c807bab1db11c8e7b", size = 7651, upload-time = "2026-03-20T21:47:08.792Z" }, ] [[package]] @@ -2372,7 +2372,7 @@ wheels = [ [[package]] name = "pint" -version = "0.25.2" +version = "0.25.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "flexcache" }, @@ -2380,9 +2380,9 @@ dependencies = [ { name = "platformdirs" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5f/74/bc3f671997158aef171194c3c4041e549946f4784b8690baa0626a0a164b/pint-0.25.2.tar.gz", hash = "sha256:85a45d1da8fe9c9f7477fed8aef59ad2b939af3d6611507e1a9cbdacdcd3450a", size = 254467, upload-time = "2025-11-06T22:08:09.184Z" } +sdist = { url = "https://files.pythonhosted.org/packages/52/9d/b1379cdbd33a49d17d627bc24e2b63cca06a1c5343b38072d2889499e82e/pint-0.25.3.tar.gz", hash = "sha256:f8f5df6cf65314d74da1ade1bf96f8e3e4d0c41b51577ac53c49e7d44ca5acee", size = 255106, upload-time = "2026-03-19T21:57:08.72Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/88/550d41e81e6d43335603a960cd9c75c1d88f9cf01bc9d4ee8e86290aba7d/pint-0.25.2-py3-none-any.whl", hash = "sha256:ca35ab1d8eeeb6f7d9942b3cb5f34ca42b61cdd5fb3eae79531553dcca04dda7", size = 306762, upload-time = "2025-11-06T22:08:07.745Z" }, + { url = "https://files.pythonhosted.org/packages/1b/dd/a9fe6a0a09512da23951c68bf36466aeecd89def3183dc095edbc807ddc5/pint-0.25.3-py3-none-any.whl", hash = "sha256:27eb25143bd5de9fcc4d5a4b484f16faf6b4615aa93ece6b3373a8c1a3c1b97d", size = 307488, upload-time = "2026-03-19T21:57:07.022Z" }, ] [[package]] @@ -2442,17 +2442,17 @@ wheels = [ [[package]] name = "protobuf" -version = "7.34.0" +version = "7.34.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/00/04a2ab36b70a52d0356852979e08b44edde0435f2115dc66e25f2100f3ab/protobuf-7.34.0.tar.gz", hash = "sha256:3871a3df67c710aaf7bb8d214cc997342e63ceebd940c8c7fc65c9b3d697591a", size = 454726, upload-time = "2026-02-27T00:30:25.421Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/6b/a0e95cad1ad7cc3f2c6821fcab91671bd5b78bd42afb357bb4765f29bc41/protobuf-7.34.1.tar.gz", hash = "sha256:9ce42245e704cc5027be797c1db1eb93184d44d1cdd71811fb2d9b25ad541280", size = 454708, upload-time = "2026-03-20T17:34:47.036Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/c4/6322ab5c8f279c4c358bc14eb8aefc0550b97222a39f04eb3c1af7a830fa/protobuf-7.34.0-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e329966799f2c271d5e05e236459fe1cbfdb8755aaa3b0914fa60947ddea408", size = 429248, upload-time = "2026-02-27T00:30:14.924Z" }, - { url = "https://files.pythonhosted.org/packages/45/99/b029bbbc61e8937545da5b79aa405ab2d9cf307a728f8c9459ad60d7a481/protobuf-7.34.0-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:9d7a5005fb96f3c1e64f397f91500b0eb371b28da81296ae73a6b08a5b76cdd6", size = 325753, upload-time = "2026-02-27T00:30:17.247Z" }, - { url = "https://files.pythonhosted.org/packages/cc/79/09f02671eb75b251c5550a1c48e7b3d4b0623efd7c95a15a50f6f9fc1e2e/protobuf-7.34.0-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:4a72a8ec94e7a9f7ef7fe818ed26d073305f347f8b3b5ba31e22f81fd85fca02", size = 340200, upload-time = "2026-02-27T00:30:18.672Z" }, - { url = "https://files.pythonhosted.org/packages/b5/57/89727baef7578897af5ed166735ceb315819f1c184da8c3441271dbcfde7/protobuf-7.34.0-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:964cf977e07f479c0697964e83deda72bcbc75c3badab506fb061b352d991b01", size = 324268, upload-time = "2026-02-27T00:30:20.088Z" }, - { url = "https://files.pythonhosted.org/packages/1f/3e/38ff2ddee5cc946f575c9d8cc822e34bde205cf61acf8099ad88ef19d7d2/protobuf-7.34.0-cp310-abi3-win32.whl", hash = "sha256:f791ec509707a1d91bd02e07df157e75e4fb9fbdad12a81b7396201ec244e2e3", size = 426628, upload-time = "2026-02-27T00:30:21.555Z" }, - { url = "https://files.pythonhosted.org/packages/cb/71/7c32eaf34a61a1bae1b62a2ac4ffe09b8d1bb0cf93ad505f42040023db89/protobuf-7.34.0-cp310-abi3-win_amd64.whl", hash = "sha256:9f9079f1dde4e32342ecbd1c118d76367090d4aaa19da78230c38101c5b3dd40", size = 437901, upload-time = "2026-02-27T00:30:22.836Z" }, - { url = "https://files.pythonhosted.org/packages/a4/e7/14dc9366696dcb53a413449881743426ed289d687bcf3d5aee4726c32ebb/protobuf-7.34.0-py3-none-any.whl", hash = "sha256:e3b914dd77fa33fa06ab2baa97937746ab25695f389869afdf03e81f34e45dc7", size = 170716, upload-time = "2026-02-27T00:30:23.994Z" }, + { url = "https://files.pythonhosted.org/packages/ec/11/3325d41e6ee15bf1125654301211247b042563bcc898784351252549a8ad/protobuf-7.34.1-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:d8b2cc79c4d8f62b293ad9b11ec3aebce9af481fa73e64556969f7345ebf9fc7", size = 429247, upload-time = "2026-03-20T17:34:37.024Z" }, + { url = "https://files.pythonhosted.org/packages/eb/9d/aa69df2724ff63efa6f72307b483ce0827f4347cc6d6df24b59e26659fef/protobuf-7.34.1-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:5185e0e948d07abe94bb76ec9b8416b604cfe5da6f871d67aad30cbf24c3110b", size = 325753, upload-time = "2026-03-20T17:34:38.751Z" }, + { url = "https://files.pythonhosted.org/packages/92/e8/d174c91fd48e50101943f042b09af9029064810b734e4160bbe282fa1caa/protobuf-7.34.1-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:403b093a6e28a960372b44e5eb081775c9b056e816a8029c61231743d63f881a", size = 340198, upload-time = "2026-03-20T17:34:39.871Z" }, + { url = "https://files.pythonhosted.org/packages/53/1b/3b431694a4dc6d37b9f653f0c64b0a0d9ec074ee810710c0c3da21d67ba7/protobuf-7.34.1-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:8ff40ce8cd688f7265326b38d5a1bed9bfdf5e6723d49961432f83e21d5713e4", size = 324267, upload-time = "2026-03-20T17:34:41.1Z" }, + { url = "https://files.pythonhosted.org/packages/85/29/64de04a0ac142fb685fd09999bc3d337943fb386f3a0ec57f92fd8203f97/protobuf-7.34.1-cp310-abi3-win32.whl", hash = "sha256:34b84ce27680df7cca9f231043ada0daa55d0c44a2ddfaa58ec1d0d89d8bf60a", size = 426628, upload-time = "2026-03-20T17:34:42.536Z" }, + { url = "https://files.pythonhosted.org/packages/4d/87/cb5e585192a22b8bd457df5a2c16a75ea0db9674c3a0a39fc9347d84e075/protobuf-7.34.1-cp310-abi3-win_amd64.whl", hash = "sha256:e97b55646e6ce5cbb0954a8c28cd39a5869b59090dfaa7df4598a7fba869468c", size = 437901, upload-time = "2026-03-20T17:34:44.112Z" }, + { url = "https://files.pythonhosted.org/packages/88/95/608f665226bca68b736b79e457fded9a2a38c4f4379a4a7614303d9db3bc/protobuf-7.34.1-py3-none-any.whl", hash = "sha256:bb3812cd53aefea2b028ef42bd780f5b96407247f20c6ef7c679807e9d188f11", size = 170715, upload-time = "2026-03-20T17:34:45.384Z" }, ] [[package]] @@ -2575,16 +2575,16 @@ wheels = [ [[package]] name = "pytest-cov" -version = "7.0.0" +version = "7.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage", extra = ["toml"] }, { name = "pluggy" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, ] [[package]] @@ -2621,15 +2621,15 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.1.3" +version = "1.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d7/7e/9f3b0dd3a074a6c3e1e79f35e465b1f2ee4b262d619de00cfce523cc9b24/python_discovery-1.1.3.tar.gz", hash = "sha256:7acca36e818cd88e9b2ba03e045ad7e93e1713e29c6bbfba5d90202310b7baa5", size = 56945, upload-time = "2026-03-10T15:08:15.038Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/90/bcce6b46823c9bec1757c964dc37ed332579be512e17a30e9698095dcae4/python_discovery-1.2.0.tar.gz", hash = "sha256:7d33e350704818b09e3da2bd419d37e21e7c30db6e0977bb438916e06b41b5b1", size = 58055, upload-time = "2026-03-19T01:43:08.248Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/80/73211fc5bfbfc562369b4aa61dc1e4bf07dc7b34df7b317e4539316b809c/python_discovery-1.1.3-py3-none-any.whl", hash = "sha256:90e795f0121bc84572e737c9aa9966311b9fde44ffb88a5953b3ec9b31c6945e", size = 31485, upload-time = "2026-03-10T15:08:13.06Z" }, + { url = "https://files.pythonhosted.org/packages/c2/3c/2005227cb951df502412de2fa781f800663cccbef8d90ec6f1b371ac2c0d/python_discovery-1.2.0-py3-none-any.whl", hash = "sha256:1e108f1bbe2ed0ef089823d28805d5ad32be8e734b86a5f212bf89b71c266e4a", size = 31524, upload-time = "2026-03-19T01:43:07.045Z" }, ] [[package]] @@ -3094,27 +3094,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/51/df/f8629c19c5318601d3121e230f74cbee7a3732339c52b21daa2b82ef9c7d/ruff-0.15.6.tar.gz", hash = "sha256:8394c7bb153a4e3811a4ecdacd4a8e6a4fa8097028119160dffecdcdf9b56ae4", size = 4597916, upload-time = "2026-03-12T23:05:47.51Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/2f/4e03a7e5ce99b517e98d3b4951f411de2b0fa8348d39cf446671adcce9a2/ruff-0.15.6-py3-none-linux_armv6l.whl", hash = "sha256:7c98c3b16407b2cf3d0f2b80c80187384bc92c6774d85fefa913ecd941256fff", size = 10508953, upload-time = "2026-03-12T23:05:17.246Z" }, - { url = "https://files.pythonhosted.org/packages/70/60/55bcdc3e9f80bcf39edf0cd272da6fa511a3d94d5a0dd9e0adf76ceebdb4/ruff-0.15.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ee7dcfaad8b282a284df4aa6ddc2741b3f4a18b0555d626805555a820ea181c3", size = 10942257, upload-time = "2026-03-12T23:05:23.076Z" }, - { url = "https://files.pythonhosted.org/packages/e7/f9/005c29bd1726c0f492bfa215e95154cf480574140cb5f867c797c18c790b/ruff-0.15.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3bd9967851a25f038fc8b9ae88a7fbd1b609f30349231dffaa37b6804923c4bb", size = 10322683, upload-time = "2026-03-12T23:05:33.738Z" }, - { url = "https://files.pythonhosted.org/packages/5f/74/2f861f5fd7cbb2146bddb5501450300ce41562da36d21868c69b7a828169/ruff-0.15.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13f4594b04e42cd24a41da653886b04d2ff87adbf57497ed4f728b0e8a4866f8", size = 10660986, upload-time = "2026-03-12T23:05:53.245Z" }, - { url = "https://files.pythonhosted.org/packages/c1/a1/309f2364a424eccb763cdafc49df843c282609f47fe53aa83f38272389e0/ruff-0.15.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e2ed8aea2f3fe57886d3f00ea5b8aae5bf68d5e195f487f037a955ff9fbaac9e", size = 10332177, upload-time = "2026-03-12T23:05:56.145Z" }, - { url = "https://files.pythonhosted.org/packages/30/41/7ebf1d32658b4bab20f8ac80972fb19cd4e2c6b78552be263a680edc55ac/ruff-0.15.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70789d3e7830b848b548aae96766431c0dc01a6c78c13381f423bf7076c66d15", size = 11170783, upload-time = "2026-03-12T23:06:01.742Z" }, - { url = "https://files.pythonhosted.org/packages/76/be/6d488f6adca047df82cd62c304638bcb00821c36bd4881cfca221561fdfc/ruff-0.15.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:542aaf1de3154cea088ced5a819ce872611256ffe2498e750bbae5247a8114e9", size = 12044201, upload-time = "2026-03-12T23:05:28.697Z" }, - { url = "https://files.pythonhosted.org/packages/71/68/e6f125df4af7e6d0b498f8d373274794bc5156b324e8ab4bf5c1b4fc0ec7/ruff-0.15.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c22e6f02c16cfac3888aa636e9eba857254d15bbacc9906c9689fdecb1953ab", size = 11421561, upload-time = "2026-03-12T23:05:31.236Z" }, - { url = "https://files.pythonhosted.org/packages/f1/9f/f85ef5fd01a52e0b472b26dc1b4bd228b8f6f0435975442ffa4741278703/ruff-0.15.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98893c4c0aadc8e448cfa315bd0cc343a5323d740fe5f28ef8a3f9e21b381f7e", size = 11310928, upload-time = "2026-03-12T23:05:45.288Z" }, - { url = "https://files.pythonhosted.org/packages/8c/26/b75f8c421f5654304b89471ed384ae8c7f42b4dff58fa6ce1626d7f2b59a/ruff-0.15.6-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:70d263770d234912374493e8cc1e7385c5d49376e41dfa51c5c3453169dc581c", size = 11235186, upload-time = "2026-03-12T23:05:50.677Z" }, - { url = "https://files.pythonhosted.org/packages/fc/d4/d5a6d065962ff7a68a86c9b4f5500f7d101a0792078de636526c0edd40da/ruff-0.15.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:55a1ad63c5a6e54b1f21b7514dfadc0c7fb40093fa22e95143cf3f64ebdcd512", size = 10635231, upload-time = "2026-03-12T23:05:37.044Z" }, - { url = "https://files.pythonhosted.org/packages/d6/56/7c3acf3d50910375349016cf33de24be021532042afbed87942858992491/ruff-0.15.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8dc473ba093c5ec238bb1e7429ee676dca24643c471e11fbaa8a857925b061c0", size = 10340357, upload-time = "2026-03-12T23:06:04.748Z" }, - { url = "https://files.pythonhosted.org/packages/06/54/6faa39e9c1033ff6a3b6e76b5df536931cd30caf64988e112bbf91ef5ce5/ruff-0.15.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:85b042377c2a5561131767974617006f99f7e13c63c111b998f29fc1e58a4cfb", size = 10860583, upload-time = "2026-03-12T23:05:58.978Z" }, - { url = "https://files.pythonhosted.org/packages/cb/1e/509a201b843b4dfb0b32acdedf68d951d3377988cae43949ba4c4133a96a/ruff-0.15.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cef49e30bc5a86a6a92098a7fbf6e467a234d90b63305d6f3ec01225a9d092e0", size = 11410976, upload-time = "2026-03-12T23:05:39.955Z" }, - { url = "https://files.pythonhosted.org/packages/6c/25/3fc9114abf979a41673ce877c08016f8e660ad6cf508c3957f537d2e9fa9/ruff-0.15.6-py3-none-win32.whl", hash = "sha256:bbf67d39832404812a2d23020dda68fee7f18ce15654e96fb1d3ad21a5fe436c", size = 10616872, upload-time = "2026-03-12T23:05:42.451Z" }, - { url = "https://files.pythonhosted.org/packages/89/7a/09ece68445ceac348df06e08bf75db72d0e8427765b96c9c0ffabc1be1d9/ruff-0.15.6-py3-none-win_amd64.whl", hash = "sha256:aee25bc84c2f1007ecb5037dff75cef00414fdf17c23f07dc13e577883dca406", size = 11787271, upload-time = "2026-03-12T23:05:20.168Z" }, - { url = "https://files.pythonhosted.org/packages/7f/d0/578c47dd68152ddddddf31cd7fc67dc30b7cdf639a86275fda821b0d9d98/ruff-0.15.6-py3-none-win_arm64.whl", hash = "sha256:c34de3dd0b0ba203be50ae70f5910b17188556630e2178fd7d79fc030eb0d837", size = 11060497, upload-time = "2026-03-12T23:05:25.968Z" }, +version = "0.15.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/22/9e4f66ee588588dc6c9af6a994e12d26e19efbe874d1a909d09a6dac7a59/ruff-0.15.7.tar.gz", hash = "sha256:04f1ae61fc20fe0b148617c324d9d009b5f63412c0b16474f3d5f1a1a665f7ac", size = 4601277, upload-time = "2026-03-19T16:26:22.605Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/2f/0b08ced94412af091807b6119ca03755d651d3d93a242682bf020189db94/ruff-0.15.7-py3-none-linux_armv6l.whl", hash = "sha256:a81cc5b6910fb7dfc7c32d20652e50fa05963f6e13ead3c5915c41ac5d16668e", size = 10489037, upload-time = "2026-03-19T16:26:32.47Z" }, + { url = "https://files.pythonhosted.org/packages/91/4a/82e0fa632e5c8b1eba5ee86ecd929e8ff327bbdbfb3c6ac5d81631bef605/ruff-0.15.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:722d165bd52403f3bdabc0ce9e41fc47070ac56d7a91b4e0d097b516a53a3477", size = 10955433, upload-time = "2026-03-19T16:27:00.205Z" }, + { url = "https://files.pythonhosted.org/packages/ab/10/12586735d0ff42526ad78c049bf51d7428618c8b5c467e72508c694119df/ruff-0.15.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7fbc2448094262552146cbe1b9643a92f66559d3761f1ad0656d4991491af49e", size = 10269302, upload-time = "2026-03-19T16:26:26.183Z" }, + { url = "https://files.pythonhosted.org/packages/eb/5d/32b5c44ccf149a26623671df49cbfbd0a0ae511ff3df9d9d2426966a8d57/ruff-0.15.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b39329b60eba44156d138275323cc726bbfbddcec3063da57caa8a8b1d50adf", size = 10607625, upload-time = "2026-03-19T16:27:03.263Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f1/f0001cabe86173aaacb6eb9bb734aa0605f9a6aa6fa7d43cb49cbc4af9c9/ruff-0.15.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87768c151808505f2bfc93ae44e5f9e7c8518943e5074f76ac21558ef5627c85", size = 10324743, upload-time = "2026-03-19T16:27:09.791Z" }, + { url = "https://files.pythonhosted.org/packages/7a/87/b8a8f3d56b8d848008559e7c9d8bf367934d5367f6d932ba779456e2f73b/ruff-0.15.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb0511670002c6c529ec66c0e30641c976c8963de26a113f3a30456b702468b0", size = 11138536, upload-time = "2026-03-19T16:27:06.101Z" }, + { url = "https://files.pythonhosted.org/packages/e4/f2/4fd0d05aab0c5934b2e1464784f85ba2eab9d54bffc53fb5430d1ed8b829/ruff-0.15.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0d19644f801849229db8345180a71bee5407b429dd217f853ec515e968a6912", size = 11994292, upload-time = "2026-03-19T16:26:48.718Z" }, + { url = "https://files.pythonhosted.org/packages/64/22/fc4483871e767e5e95d1622ad83dad5ebb830f762ed0420fde7dfa9d9b08/ruff-0.15.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4806d8e09ef5e84eb19ba833d0442f7e300b23fe3f0981cae159a248a10f0036", size = 11398981, upload-time = "2026-03-19T16:26:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/b0/99/66f0343176d5eab02c3f7fcd2de7a8e0dd7a41f0d982bee56cd1c24db62b/ruff-0.15.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dce0896488562f09a27b9c91b1f58a097457143931f3c4d519690dea54e624c5", size = 11242422, upload-time = "2026-03-19T16:26:29.277Z" }, + { url = "https://files.pythonhosted.org/packages/5d/3a/a7060f145bfdcce4c987ea27788b30c60e2c81d6e9a65157ca8afe646328/ruff-0.15.7-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:1852ce241d2bc89e5dc823e03cff4ce73d816b5c6cdadd27dbfe7b03217d2a12", size = 11232158, upload-time = "2026-03-19T16:26:42.321Z" }, + { url = "https://files.pythonhosted.org/packages/a7/53/90fbb9e08b29c048c403558d3cdd0adf2668b02ce9d50602452e187cd4af/ruff-0.15.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5f3e4b221fb4bd293f79912fc5e93a9063ebd6d0dcbd528f91b89172a9b8436c", size = 10577861, upload-time = "2026-03-19T16:26:57.459Z" }, + { url = "https://files.pythonhosted.org/packages/2f/aa/5f486226538fe4d0f0439e2da1716e1acf895e2a232b26f2459c55f8ddad/ruff-0.15.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b15e48602c9c1d9bdc504b472e90b90c97dc7d46c7028011ae67f3861ceba7b4", size = 10327310, upload-time = "2026-03-19T16:26:35.909Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/271afdffb81fe7bfc8c43ba079e9d96238f674380099457a74ccb3863857/ruff-0.15.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b4705e0e85cedc74b0a23cf6a179dbb3df184cb227761979cc76c0440b5ab0d", size = 10840752, upload-time = "2026-03-19T16:26:45.723Z" }, + { url = "https://files.pythonhosted.org/packages/bf/29/a4ae78394f76c7759953c47884eb44de271b03a66634148d9f7d11e721bd/ruff-0.15.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:112c1fa316a558bb34319282c1200a8bf0495f1b735aeb78bfcb2991e6087580", size = 11336961, upload-time = "2026-03-19T16:26:39.076Z" }, + { url = "https://files.pythonhosted.org/packages/26/6b/8786ba5736562220d588a2f6653e6c17e90c59ced34a2d7b512ef8956103/ruff-0.15.7-py3-none-win32.whl", hash = "sha256:6d39e2d3505b082323352f733599f28169d12e891f7dd407f2d4f54b4c2886de", size = 10582538, upload-time = "2026-03-19T16:26:15.992Z" }, + { url = "https://files.pythonhosted.org/packages/2b/e9/346d4d3fffc6871125e877dae8d9a1966b254fbd92a50f8561078b88b099/ruff-0.15.7-py3-none-win_amd64.whl", hash = "sha256:4d53d712ddebcd7dace1bc395367aec12c057aacfe9adbb6d832302575f4d3a1", size = 11755839, upload-time = "2026-03-19T16:26:19.897Z" }, + { url = "https://files.pythonhosted.org/packages/8f/e8/726643a3ea68c727da31570bde48c7a10f1aa60eddd628d94078fec586ff/ruff-0.15.7-py3-none-win_arm64.whl", hash = "sha256:18e8d73f1c3fdf27931497972250340f92e8c861722161a9caeb89a58ead6ed2", size = 11023304, upload-time = "2026-03-19T16:26:51.669Z" }, ] [[package]] From a7f9b5dd26cd4c61929894e8d7072abceea60ec9 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Mar 2026 11:42:54 -0700 Subject: [PATCH 194/335] Fix for TomographyLite when reloading --- src/quantem/core/ml/ddp.py | 3 ++- src/quantem/tomography/tomography.py | 4 +--- src/quantem/tomography/tomography_lite.py | 8 +++++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/quantem/core/ml/ddp.py b/src/quantem/core/ml/ddp.py index 2253b4a5..dedd90dd 100644 --- a/src/quantem/core/ml/ddp.py +++ b/src/quantem/core/ml/ddp.py @@ -146,6 +146,7 @@ def distribute_model( Returns the model. """ model = model.to(self.device) + if self.world_size > 1: model = torch.nn.parallel.DistributedDataParallel( model, @@ -158,7 +159,7 @@ def distribute_model( ) if self.global_rank == 0: - print("Model wrapped with DDP") + print("Model wrapped with DDP and compiled") if self.world_size > 1: if self.global_rank == 0: diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 3abb988a..b9a4344d 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -192,9 +192,8 @@ def reconstruct( len(batch["target_value"]), ) - pred = integrated_densities.float() - if a0 > 0: + if self.num_epochs > 0: soft_constraints_loss = self.obj_model.apply_soft_constraints(all_coords, pred) else: soft_constraints_loss = 0.0 @@ -221,7 +220,6 @@ def reconstruct( dist.all_reduce(consistency_loss, dist.ReduceOp.AVG) dist.all_reduce(epoch_soft_constraint_loss, dist.ReduceOp.AVG) - total_loss = total_loss.item() / len(self.dataloader) consistency_loss = consistency_loss.item() / len(self.dataloader) epoch_soft_constraint_loss = epoch_soft_constraint_loss.item() / len(self.dataloader) diff --git a/src/quantem/tomography/tomography_lite.py b/src/quantem/tomography/tomography_lite.py index eb5c9940..711434ec 100644 --- a/src/quantem/tomography/tomography_lite.py +++ b/src/quantem/tomography/tomography_lite.py @@ -91,8 +91,8 @@ def reconstruct( ] = "none", scheduler_params: dict = {}, new_optimizers: bool = False, - obj_constraints: ObjConstraintsType | dict = {}, - dset_constraints: DatasetConstraintsType | dict = {}, + obj_constraints: ObjConstraintsType | dict | None = None, + dset_constraints: DatasetConstraintsType | dict | None = None, ): if self.num_epochs == 0: opt_params = { @@ -118,7 +118,9 @@ def reconstruct( ) else: opt_params = None - all_scheduler_params = {} + all_scheduler_params = None + obj_constraints = None + dset_constraints = None num_samples_per_ray = int(max(self.dset.tilt_stack.shape)) return super().reconstruct( From 73c282ffbe49c0fd4ed57bb55146f3d4b5556fc1 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Mar 2026 17:18:33 -0700 Subject: [PATCH 195/335] Show metrics total loss + lrs from different optimizers after finished training --- pyproject.toml | 2 +- src/quantem/tomography/tomography.py | 24 + src/quantem/tomography/tomography_base.py | 17 + src/quantem/tomography/tomography_lite.py | 2 + uv.lock | 3774 +++++++++++---------- 5 files changed, 1961 insertions(+), 1858 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5ca6787b..19882bf9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,4 +82,4 @@ dev = [ "pre-commit>=4.2.0", "ruff>=0.11.5", "tomli>=2.2.1", -] +] \ No newline at end of file diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index b9a4344d..a5fcccfa 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -2,6 +2,7 @@ from pathlib import Path from typing import Literal, Self, Sequence +import matplotlib.pyplot as plt import numpy as np import torch import torch.distributed as dist @@ -77,6 +78,7 @@ def reconstruct( ] = "l2", loss_func_kwargs: dict = {}, reset_dset: DatasetModelType | None = None, + show_metrics: bool = False, ): """ This function should be able to handle both AD and INR-based tomography reconstruction methods. @@ -277,6 +279,7 @@ def reconstruct( self._epoch_losses.append(total_loss) self._consistency_losses.append(consistency_loss) + self.append_learning_rates(self.get_current_lrs()) self.obj_model._soft_constraint_losses.append(epoch_soft_constraint_loss) if self.val_dataloader is not None: self._val_losses.append(avg_val_loss) @@ -315,6 +318,27 @@ def reconstruct( print( f"Reconstruction Epoch {self.num_epochs} | Loss: {total_loss:.5e}, Consistency Loss: {consistency_loss:.5e}, Soft Constraint Loss: {epoch_soft_constraint_loss:.5e}" ) + if show_metrics and self.world_size == 1: + fig, ax = plt.subplots(figsize=(10, 4), ncols=2) + + ax[0].plot(self._epoch_losses, label="Total Training Loss") + if len(self._val_losses) > 0: + ax[0].plot(self._val_losses, label="Validation Loss") + ax[0].legend() + + for key, value in self._lrs.items(): + ax[1].plot(value, label=key) + + ax[1].legend() + ax[0].legend() + ax[0].set_yscale("log") + ax[1].set_yscale("log") + ax[0].set_xlabel("Epoch") + ax[1].set_xlabel("Epoch") + ax[0].set_ylabel("Loss") + ax[1].set_ylabel("Learning Rate") + + fig.tight_layout() # --- Helper Functions --- diff --git a/src/quantem/tomography/tomography_base.py b/src/quantem/tomography/tomography_base.py index 50a16024..8c747c63 100644 --- a/src/quantem/tomography/tomography_base.py +++ b/src/quantem/tomography/tomography_base.py @@ -49,6 +49,7 @@ def __init__( self._epoch_losses: list[float] = [] self._consistency_losses: list[float] = [] self._val_losses: list[float] = [] + self._lrs: dict[str, float] = {} # DDP Initialization if isinstance(obj_model, ObjectINR): print("Setting up DDP for obj_model") @@ -125,6 +126,22 @@ def consistency_losses(self) -> NDArray: """ return np.array(self._consistency_losses) + @property + def learning_rates(self) -> dict[str, float]: + """ + Returns the learning rates for each epoch ran. + """ + return self._lrs + + def append_learning_rates(self, learning_rates: dict[str, float]): + """ + Appends the learning rates for each epoch ran. + """ + for key, value in learning_rates.items(): + if key not in self._lrs: + self._lrs[key] = [] + self._lrs[key].append(float(value)) + @property def num_epochs(self) -> int: return len(self._epoch_losses) diff --git a/src/quantem/tomography/tomography_lite.py b/src/quantem/tomography/tomography_lite.py index 711434ec..dc3d918c 100644 --- a/src/quantem/tomography/tomography_lite.py +++ b/src/quantem/tomography/tomography_lite.py @@ -93,6 +93,7 @@ def reconstruct( new_optimizers: bool = False, obj_constraints: ObjConstraintsType | dict | None = None, dset_constraints: DatasetConstraintsType | dict | None = None, + show_metrics: bool = False, ): if self.num_epochs == 0: opt_params = { @@ -133,6 +134,7 @@ def reconstruct( scheduler_params=all_scheduler_params, obj_constraints=obj_constraints, dset_constraints=dset_constraints, + show_metrics=show_metrics, ) diff --git a/uv.lock b/uv.lock index 2d2559a0..40dc186c 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 1 requires-python = ">=3.11" resolution-markers = [ "python_full_version >= '3.14'", @@ -17,9 +17,9 @@ members = [ name = "absl-py" version = "2.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/64/c7/8de93764ad66968d19329a7e0c147a2bb3c7054c554d4a119111b8f9440f/absl_py-2.4.0.tar.gz", hash = "sha256:8c6af82722b35cf71e0f4d1d47dcaebfff286e27110a99fc359349b247dfb5d4", size = 116543, upload-time = "2026-01-28T10:17:05.322Z" } +sdist = { url = "https://files.pythonhosted.org/packages/64/c7/8de93764ad66968d19329a7e0c147a2bb3c7054c554d4a119111b8f9440f/absl_py-2.4.0.tar.gz", hash = "sha256:8c6af82722b35cf71e0f4d1d47dcaebfff286e27110a99fc359349b247dfb5d4", size = 116543 } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/a6/907a406bb7d359e6a63f99c313846d9eec4f7e6f7437809e03aa00fa3074/absl_py-2.4.0-py3-none-any.whl", hash = "sha256:88476fd881ca8aab94ffa78b7b6c632a782ab3ba1cd19c9bd423abc4fb4cd28d", size = 135750, upload-time = "2026-01-28T10:17:04.19Z" }, + { url = "https://files.pythonhosted.org/packages/18/a6/907a406bb7d359e6a63f99c313846d9eec4f7e6f7437809e03aa00fa3074/absl_py-2.4.0-py3-none-any.whl", hash = "sha256:88476fd881ca8aab94ffa78b7b6c632a782ab3ba1cd19c9bd423abc4fb4cd28d", size = 135750 }, ] [[package]] @@ -31,9 +31,9 @@ dependencies = [ { name = "sqlalchemy" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/94/13/8b084e0f2efb0275a1d534838844926f798bd766566b1375174e2448cd31/alembic-1.18.4.tar.gz", hash = "sha256:cb6e1fd84b6174ab8dbb2329f86d631ba9559dd78df550b57804d607672cedbc", size = 2056725, upload-time = "2026-02-10T16:00:47.195Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/13/8b084e0f2efb0275a1d534838844926f798bd766566b1375174e2448cd31/alembic-1.18.4.tar.gz", hash = "sha256:cb6e1fd84b6174ab8dbb2329f86d631ba9559dd78df550b57804d607672cedbc", size = 2056725 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/29/6533c317b74f707ea28f8d633734dbda2119bbadfc61b2f3640ba835d0f7/alembic-1.18.4-py3-none-any.whl", hash = "sha256:a5ed4adcf6d8a4cb575f3d759f071b03cd6e5c7618eb796cb52497be25bfe19a", size = 263893, upload-time = "2026-02-10T16:00:49.997Z" }, + { url = "https://files.pythonhosted.org/packages/d2/29/6533c317b74f707ea28f8d633734dbda2119bbadfc61b2f3640ba835d0f7/alembic-1.18.4-py3-none-any.whl", hash = "sha256:a5ed4adcf6d8a4cb575f3d759f071b03cd6e5c7618eb796cb52497be25bfe19a", size = 263893 }, ] [[package]] @@ -44,9 +44,9 @@ dependencies = [ { name = "idna" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" } +sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685 } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, + { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592 }, ] [[package]] @@ -58,18 +58,18 @@ dependencies = [ { name = "psygnal" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/be/5e/cbea445bf062b81e4d366ca29dae4f0aedc7a64f384afc24670e07bec560/anywidget-0.9.21.tar.gz", hash = "sha256:b8d0172029ac426573053c416c6a587838661612208bb390fa0607862e594b27", size = 390517, upload-time = "2025-11-12T17:06:03.035Z" } +sdist = { url = "https://files.pythonhosted.org/packages/be/5e/cbea445bf062b81e4d366ca29dae4f0aedc7a64f384afc24670e07bec560/anywidget-0.9.21.tar.gz", hash = "sha256:b8d0172029ac426573053c416c6a587838661612208bb390fa0607862e594b27", size = 390517 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/03/c17464bbf682ea87e7e3de2ddc63395e359a78ae9c01f55fc78759ecbd79/anywidget-0.9.21-py3-none-any.whl", hash = "sha256:78c268e0fbdb1dfd15da37fb578f9cf0a0df58a430e68d9156942b7a9391a761", size = 231797, upload-time = "2025-11-12T17:06:01.564Z" }, + { url = "https://files.pythonhosted.org/packages/5b/03/c17464bbf682ea87e7e3de2ddc63395e359a78ae9c01f55fc78759ecbd79/anywidget-0.9.21-py3-none-any.whl", hash = "sha256:78c268e0fbdb1dfd15da37fb578f9cf0a0df58a430e68d9156942b7a9391a761", size = 231797 }, ] [[package]] name = "appnope" version = "0.1.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170, upload-time = "2024-02-06T09:43:11.258Z" } +sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170 } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321, upload-time = "2024-02-06T09:43:09.663Z" }, + { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321 }, ] [[package]] @@ -79,9 +79,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "argon2-cffi-bindings" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657, upload-time = "2025-06-03T06:55:30.804Z" }, + { url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657 }, ] [[package]] @@ -91,28 +91,28 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441, upload-time = "2025-07-30T10:02:05.147Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/60/97/3c0a35f46e52108d4707c44b95cfe2afcafc50800b5450c197454569b776/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3d3f05610594151994ca9ccb3c771115bdb4daef161976a266f0dd8aa9996b8f", size = 54393, upload-time = "2025-07-30T10:01:40.97Z" }, - { url = "https://files.pythonhosted.org/packages/9d/f4/98bbd6ee89febd4f212696f13c03ca302b8552e7dbf9c8efa11ea4a388c3/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8b8efee945193e667a396cbc7b4fb7d357297d6234d30a489905d96caabde56b", size = 29328, upload-time = "2025-07-30T10:01:41.916Z" }, - { url = "https://files.pythonhosted.org/packages/43/24/90a01c0ef12ac91a6be05969f29944643bc1e5e461155ae6559befa8f00b/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3c6702abc36bf3ccba3f802b799505def420a1b7039862014a65db3205967f5a", size = 31269, upload-time = "2025-07-30T10:01:42.716Z" }, - { url = "https://files.pythonhosted.org/packages/d4/d3/942aa10782b2697eee7af5e12eeff5ebb325ccfb86dd8abda54174e377e4/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1c70058c6ab1e352304ac7e3b52554daadacd8d453c1752e547c76e9c99ac44", size = 86558, upload-time = "2025-07-30T10:01:43.943Z" }, - { url = "https://files.pythonhosted.org/packages/0d/82/b484f702fec5536e71836fc2dbc8c5267b3f6e78d2d539b4eaa6f0db8bf8/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2fd3bfbff3c5d74fef31a722f729bf93500910db650c925c2d6ef879a7e51cb", size = 92364, upload-time = "2025-07-30T10:01:44.887Z" }, - { url = "https://files.pythonhosted.org/packages/c9/c1/a606ff83b3f1735f3759ad0f2cd9e038a0ad11a3de3b6c673aa41c24bb7b/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4f9665de60b1b0e99bcd6be4f17d90339698ce954cfd8d9cf4f91c995165a92", size = 85637, upload-time = "2025-07-30T10:01:46.225Z" }, - { url = "https://files.pythonhosted.org/packages/44/b4/678503f12aceb0262f84fa201f6027ed77d71c5019ae03b399b97caa2f19/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ba92837e4a9aa6a508c8d2d7883ed5a8f6c308c89a4790e1e447a220deb79a85", size = 91934, upload-time = "2025-07-30T10:01:47.203Z" }, - { url = "https://files.pythonhosted.org/packages/f0/c7/f36bd08ef9bd9f0a9cff9428406651f5937ce27b6c5b07b92d41f91ae541/argon2_cffi_bindings-25.1.0-cp314-cp314t-win32.whl", hash = "sha256:84a461d4d84ae1295871329b346a97f68eade8c53b6ed9a7ca2d7467f3c8ff6f", size = 28158, upload-time = "2025-07-30T10:01:48.341Z" }, - { url = "https://files.pythonhosted.org/packages/b3/80/0106a7448abb24a2c467bf7d527fe5413b7fdfa4ad6d6a96a43a62ef3988/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b55aec3565b65f56455eebc9b9f34130440404f27fe21c3b375bf1ea4d8fbae6", size = 32597, upload-time = "2025-07-30T10:01:49.112Z" }, - { url = "https://files.pythonhosted.org/packages/05/b8/d663c9caea07e9180b2cb662772865230715cbd573ba3b5e81793d580316/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:87c33a52407e4c41f3b70a9c2d3f6056d88b10dad7695be708c5021673f55623", size = 28231, upload-time = "2025-07-30T10:01:49.92Z" }, - { url = "https://files.pythonhosted.org/packages/1d/57/96b8b9f93166147826da5f90376e784a10582dd39a393c99bb62cfcf52f0/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500", size = 54121, upload-time = "2025-07-30T10:01:50.815Z" }, - { url = "https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44", size = 29177, upload-time = "2025-07-30T10:01:51.681Z" }, - { url = "https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0", size = 31090, upload-time = "2025-07-30T10:01:53.184Z" }, - { url = "https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6", size = 81246, upload-time = "2025-07-30T10:01:54.145Z" }, - { url = "https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a", size = 87126, upload-time = "2025-07-30T10:01:55.074Z" }, - { url = "https://files.pythonhosted.org/packages/72/70/7a2993a12b0ffa2a9271259b79cc616e2389ed1a4d93842fac5a1f923ffd/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d", size = 80343, upload-time = "2025-07-30T10:01:56.007Z" }, - { url = "https://files.pythonhosted.org/packages/78/9a/4e5157d893ffc712b74dbd868c7f62365618266982b64accab26bab01edc/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99", size = 86777, upload-time = "2025-07-30T10:01:56.943Z" }, - { url = "https://files.pythonhosted.org/packages/74/cd/15777dfde1c29d96de7f18edf4cc94c385646852e7c7b0320aa91ccca583/argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2", size = 27180, upload-time = "2025-07-30T10:01:57.759Z" }, - { url = "https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98", size = 31715, upload-time = "2025-07-30T10:01:58.56Z" }, - { url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149, upload-time = "2025-07-30T10:01:59.329Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/97/3c0a35f46e52108d4707c44b95cfe2afcafc50800b5450c197454569b776/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3d3f05610594151994ca9ccb3c771115bdb4daef161976a266f0dd8aa9996b8f", size = 54393 }, + { url = "https://files.pythonhosted.org/packages/9d/f4/98bbd6ee89febd4f212696f13c03ca302b8552e7dbf9c8efa11ea4a388c3/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8b8efee945193e667a396cbc7b4fb7d357297d6234d30a489905d96caabde56b", size = 29328 }, + { url = "https://files.pythonhosted.org/packages/43/24/90a01c0ef12ac91a6be05969f29944643bc1e5e461155ae6559befa8f00b/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3c6702abc36bf3ccba3f802b799505def420a1b7039862014a65db3205967f5a", size = 31269 }, + { url = "https://files.pythonhosted.org/packages/d4/d3/942aa10782b2697eee7af5e12eeff5ebb325ccfb86dd8abda54174e377e4/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1c70058c6ab1e352304ac7e3b52554daadacd8d453c1752e547c76e9c99ac44", size = 86558 }, + { url = "https://files.pythonhosted.org/packages/0d/82/b484f702fec5536e71836fc2dbc8c5267b3f6e78d2d539b4eaa6f0db8bf8/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2fd3bfbff3c5d74fef31a722f729bf93500910db650c925c2d6ef879a7e51cb", size = 92364 }, + { url = "https://files.pythonhosted.org/packages/c9/c1/a606ff83b3f1735f3759ad0f2cd9e038a0ad11a3de3b6c673aa41c24bb7b/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4f9665de60b1b0e99bcd6be4f17d90339698ce954cfd8d9cf4f91c995165a92", size = 85637 }, + { url = "https://files.pythonhosted.org/packages/44/b4/678503f12aceb0262f84fa201f6027ed77d71c5019ae03b399b97caa2f19/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ba92837e4a9aa6a508c8d2d7883ed5a8f6c308c89a4790e1e447a220deb79a85", size = 91934 }, + { url = "https://files.pythonhosted.org/packages/f0/c7/f36bd08ef9bd9f0a9cff9428406651f5937ce27b6c5b07b92d41f91ae541/argon2_cffi_bindings-25.1.0-cp314-cp314t-win32.whl", hash = "sha256:84a461d4d84ae1295871329b346a97f68eade8c53b6ed9a7ca2d7467f3c8ff6f", size = 28158 }, + { url = "https://files.pythonhosted.org/packages/b3/80/0106a7448abb24a2c467bf7d527fe5413b7fdfa4ad6d6a96a43a62ef3988/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b55aec3565b65f56455eebc9b9f34130440404f27fe21c3b375bf1ea4d8fbae6", size = 32597 }, + { url = "https://files.pythonhosted.org/packages/05/b8/d663c9caea07e9180b2cb662772865230715cbd573ba3b5e81793d580316/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:87c33a52407e4c41f3b70a9c2d3f6056d88b10dad7695be708c5021673f55623", size = 28231 }, + { url = "https://files.pythonhosted.org/packages/1d/57/96b8b9f93166147826da5f90376e784a10582dd39a393c99bb62cfcf52f0/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500", size = 54121 }, + { url = "https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44", size = 29177 }, + { url = "https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0", size = 31090 }, + { url = "https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6", size = 81246 }, + { url = "https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a", size = 87126 }, + { url = "https://files.pythonhosted.org/packages/72/70/7a2993a12b0ffa2a9271259b79cc616e2389ed1a4d93842fac5a1f923ffd/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d", size = 80343 }, + { url = "https://files.pythonhosted.org/packages/78/9a/4e5157d893ffc712b74dbd868c7f62365618266982b64accab26bab01edc/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99", size = 86777 }, + { url = "https://files.pythonhosted.org/packages/74/cd/15777dfde1c29d96de7f18edf4cc94c385646852e7c7b0320aa91ccca583/argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2", size = 27180 }, + { url = "https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98", size = 31715 }, + { url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149 }, ] [[package]] @@ -123,45 +123,45 @@ dependencies = [ { name = "python-dateutil" }, { name = "tzdata" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/33/032cdc44182491aa708d06a68b62434140d8c50820a087fac7af37703357/arrow-1.4.0.tar.gz", hash = "sha256:ed0cc050e98001b8779e84d461b0098c4ac597e88704a655582b21d116e526d7", size = 152931, upload-time = "2025-10-18T17:46:46.761Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/33/032cdc44182491aa708d06a68b62434140d8c50820a087fac7af37703357/arrow-1.4.0.tar.gz", hash = "sha256:ed0cc050e98001b8779e84d461b0098c4ac597e88704a655582b21d116e526d7", size = 152931 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl", hash = "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205", size = 68797, upload-time = "2025-10-18T17:46:45.663Z" }, + { url = "https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl", hash = "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205", size = 68797 }, ] [[package]] name = "asttokens" version = "3.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" } +sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" }, + { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047 }, ] [[package]] name = "async-lru" version = "2.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/8a/ca724066c32a53fa75f59e0f21aa822fdaa8a0dffa112d223634e3caabf9/async_lru-2.2.0.tar.gz", hash = "sha256:80abae2a237dbc6c60861d621619af39f0d920aea306de34cb992c879e01370c", size = 14654, upload-time = "2026-02-20T19:11:43.848Z" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8a/ca724066c32a53fa75f59e0f21aa822fdaa8a0dffa112d223634e3caabf9/async_lru-2.2.0.tar.gz", hash = "sha256:80abae2a237dbc6c60861d621619af39f0d920aea306de34cb992c879e01370c", size = 14654 } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl", hash = "sha256:e2c1cf731eba202b59c5feedaef14ffd9d02ad0037fcda64938699f2c380eafe", size = 7890, upload-time = "2026-02-20T19:11:42.273Z" }, + { url = "https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl", hash = "sha256:e2c1cf731eba202b59c5feedaef14ffd9d02ad0037fcda64938699f2c380eafe", size = 7890 }, ] [[package]] name = "attrs" version = "25.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615 }, ] [[package]] name = "babel" version = "2.18.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554 } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845 }, ] [[package]] @@ -172,9 +172,9 @@ dependencies = [ { name = "soupsieve" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, + { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721 }, ] [[package]] @@ -184,9 +184,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "webencodings" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/07/18/3c8523962314be6bf4c8989c79ad9531c825210dd13a8669f6b84336e8bd/bleach-6.3.0.tar.gz", hash = "sha256:6f3b91b1c0a02bb9a78b5a454c92506aa0fdf197e1d5e114d2e00c6f64306d22", size = 203533, upload-time = "2025-10-27T17:57:39.211Z" } +sdist = { url = "https://files.pythonhosted.org/packages/07/18/3c8523962314be6bf4c8989c79ad9531c825210dd13a8669f6b84336e8bd/bleach-6.3.0.tar.gz", hash = "sha256:6f3b91b1c0a02bb9a78b5a454c92506aa0fdf197e1d5e114d2e00c6f64306d22", size = 203533 } wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl", hash = "sha256:fe10ec77c93ddf3d13a73b035abaac7a9f5e436513864ccdad516693213c65d6", size = 164437, upload-time = "2025-10-27T17:57:37.538Z" }, + { url = "https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl", hash = "sha256:fe10ec77c93ddf3d13a73b035abaac7a9f5e436513864ccdad516693213c65d6", size = 164437 }, ] [package.optional-dependencies] @@ -198,9 +198,9 @@ css = [ name = "certifi" version = "2026.1.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900 }, ] [[package]] @@ -210,149 +210,149 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pycparser", marker = "implementation_name != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, - { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, - { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, - { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, - { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, - { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, - { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, - { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, - { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, - { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, - { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, - { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, - { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, - { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, - { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, - { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, - { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, - { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, - { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, - { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, - { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, - { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, - { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, - { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, - { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, - { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, - { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, - { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, - { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, - { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, - { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, - { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, - { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, - { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, - { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, - { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, - { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, - { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, - { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, - { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, - { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, - { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, - { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, - { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, - { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, - { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, - { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, - { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, - { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, - { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, - { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, - { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, - { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, - { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, - { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344 }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560 }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613 }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476 }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374 }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597 }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574 }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971 }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972 }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078 }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076 }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820 }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635 }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271 }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048 }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529 }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097 }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983 }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519 }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572 }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963 }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361 }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932 }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557 }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762 }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230 }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043 }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446 }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101 }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948 }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422 }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499 }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928 }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302 }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909 }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402 }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780 }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320 }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487 }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049 }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793 }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300 }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244 }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828 }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926 }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328 }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650 }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687 }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773 }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013 }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593 }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354 }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480 }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584 }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443 }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437 }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487 }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726 }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195 }, ] [[package]] name = "cfgv" version = "3.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334 } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445 }, ] [[package]] name = "charset-normalizer" version = "3.4.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, - { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, - { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, - { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, - { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, - { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, - { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, - { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, - { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, - { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, - { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, - { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, - { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, - { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, - { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, - { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, - { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, - { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, - { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, - { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, - { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, - { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, - { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, - { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, - { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, - { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, - { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, - { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, - { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, - { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, - { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, - { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, - { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, - { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, - { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, - { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, - { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, - { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, - { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, - { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, - { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, - { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, - { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, - { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, - { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, - { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, - { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, - { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, - { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, - { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, - { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, - { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, - { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, - { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, - { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, - { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, - { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, - { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, - { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, - { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, - { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, - { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, - { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, - { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988 }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324 }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742 }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863 }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837 }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550 }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162 }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019 }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310 }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022 }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383 }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098 }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991 }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456 }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978 }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969 }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425 }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162 }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558 }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497 }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240 }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471 }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864 }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647 }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110 }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839 }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667 }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535 }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816 }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694 }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131 }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390 }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091 }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936 }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180 }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346 }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874 }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076 }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601 }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376 }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825 }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583 }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366 }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300 }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465 }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404 }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092 }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408 }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746 }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889 }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641 }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779 }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035 }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542 }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524 }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395 }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680 }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045 }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687 }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014 }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044 }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940 }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104 }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743 }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402 }, ] [[package]] @@ -362,18 +362,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065 } wheels = [ - { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274 }, ] [[package]] name = "cloudpickle" version = "3.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" } +sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330 } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" }, + { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228 }, ] [[package]] @@ -385,18 +385,18 @@ dependencies = [ { name = "matplotlib" }, { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2f/ca/39152b506133881be74bd46cced27ec10d9bbc85db5bdfc22c8f8e4011f9/cmasher-1.9.2.tar.gz", hash = "sha256:6c90c2cec87146e3210d3e7f2321fceee38ac7d87c136cd0de25160a14f57ff5", size = 520860, upload-time = "2024-11-19T11:17:02.084Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/ca/39152b506133881be74bd46cced27ec10d9bbc85db5bdfc22c8f8e4011f9/cmasher-1.9.2.tar.gz", hash = "sha256:6c90c2cec87146e3210d3e7f2321fceee38ac7d87c136cd0de25160a14f57ff5", size = 520860 } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/d2/6fcb93a60777fef7662d84ba60846d2dee9b6b70b4a62472515110f79cee/cmasher-1.9.2-py3-none-any.whl", hash = "sha256:2fe45fde06051050dda5c023a527ba9066ca21f161c793f22839a6ebe6e4bbbb", size = 506496, upload-time = "2024-11-19T11:16:58.549Z" }, + { url = "https://files.pythonhosted.org/packages/91/d2/6fcb93a60777fef7662d84ba60846d2dee9b6b70b4a62472515110f79cee/cmasher-1.9.2-py3-none-any.whl", hash = "sha256:2fe45fde06051050dda5c023a527ba9066ca21f161c793f22839a6ebe6e4bbbb", size = 506496 }, ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, ] [[package]] @@ -406,9 +406,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a2/61/f083b5ac52e505dfc1c624eafbf8c7589a0d7f32daa398d2e7590efa5fda/colorlog-6.10.1.tar.gz", hash = "sha256:eb4ae5cb65fe7fec7773c2306061a8e63e02efc2c72eba9d27b0fa23c94f1321", size = 17162, upload-time = "2025-10-16T16:14:11.978Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/61/f083b5ac52e505dfc1c624eafbf8c7589a0d7f32daa398d2e7590efa5fda/colorlog-6.10.1.tar.gz", hash = "sha256:eb4ae5cb65fe7fec7773c2306061a8e63e02efc2c72eba9d27b0fa23c94f1321", size = 17162 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/c1/e419ef3723a074172b68aaa89c9f3de486ed4c2399e2dbd8113a4fdcaf9e/colorlog-6.10.1-py3-none-any.whl", hash = "sha256:2d7e8348291948af66122cff006c9f8da6255d224e7cf8e37d8de2df3bad8c9c", size = 11743, upload-time = "2025-10-16T16:14:10.512Z" }, + { url = "https://files.pythonhosted.org/packages/6d/c1/e419ef3723a074172b68aaa89c9f3de486ed4c2399e2dbd8113a4fdcaf9e/colorlog-6.10.1-py3-none-any.whl", hash = "sha256:2d7e8348291948af66122cff006c9f8da6255d224e7cf8e37d8de2df3bad8c9c", size = 11743 }, ] [[package]] @@ -418,18 +418,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/75/e4/aa41ae14c5c061205715006c8834496d86ec7500f1edda5981f0f0190cc6/colorspacious-1.1.2.tar.gz", hash = "sha256:5e9072e8cdca889dac445c35c9362a22ccf758e97b00b79ff0d5a7ba3e11b618", size = 688573, upload-time = "2018-04-08T04:27:30.83Z" } +sdist = { url = "https://files.pythonhosted.org/packages/75/e4/aa41ae14c5c061205715006c8834496d86ec7500f1edda5981f0f0190cc6/colorspacious-1.1.2.tar.gz", hash = "sha256:5e9072e8cdca889dac445c35c9362a22ccf758e97b00b79ff0d5a7ba3e11b618", size = 688573 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/a1/318b9aeca7b9856410ededa4f52d6f82174d1a41e64bdd70d951e532675a/colorspacious-1.1.2-py2.py3-none-any.whl", hash = "sha256:c78befa603cea5dccb332464e7dd29e96469eebf6cd5133029153d1e69e3fd6f", size = 37735, upload-time = "2018-04-08T04:27:22.143Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a1/318b9aeca7b9856410ededa4f52d6f82174d1a41e64bdd70d951e532675a/colorspacious-1.1.2-py2.py3-none-any.whl", hash = "sha256:c78befa603cea5dccb332464e7dd29e96469eebf6cd5133029153d1e69e3fd6f", size = 37735 }, ] [[package]] name = "comm" version = "0.2.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/13/7d740c5849255756bc17888787313b61fd38a0a8304fc4f073dfc46122aa/comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971", size = 6319, upload-time = "2025-07-25T14:02:04.452Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/13/7d740c5849255756bc17888787313b61fd38a0a8304fc4f073dfc46122aa/comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971", size = 6319 } wheels = [ - { url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294, upload-time = "2025-07-25T14:02:02.896Z" }, + { url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294 }, ] [[package]] @@ -439,178 +439,178 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" }, - { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" }, - { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" }, - { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" }, - { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" }, - { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" }, - { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" }, - { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" }, - { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" }, - { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" }, - { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" }, - { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, - { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, - { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, - { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, - { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, - { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, - { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, - { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, - { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, - { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, - { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, - { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, - { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, - { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, - { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, - { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, - { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, - { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, - { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, - { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, - { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, - { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, - { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, - { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, - { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, - { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, - { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, - { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, - { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, - { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, - { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, - { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, - { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, - { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, - { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, - { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, - { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, - { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, - { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, - { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, - { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, - { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, - { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, - { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, - { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, - { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, - { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, - { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, - { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, - { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, - { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, - { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, - { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, - { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, - { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" }, - { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" }, - { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" }, - { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" }, - { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773 }, + { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149 }, + { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222 }, + { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234 }, + { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555 }, + { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238 }, + { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218 }, + { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867 }, + { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677 }, + { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234 }, + { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123 }, + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419 }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979 }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653 }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536 }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397 }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601 }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288 }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386 }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018 }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567 }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655 }, + { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257 }, + { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034 }, + { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672 }, + { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234 }, + { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169 }, + { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859 }, + { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062 }, + { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932 }, + { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024 }, + { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578 }, + { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524 }, + { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730 }, + { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897 }, + { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751 }, + { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486 }, + { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106 }, + { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548 }, + { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297 }, + { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023 }, + { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157 }, + { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570 }, + { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713 }, + { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189 }, + { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251 }, + { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810 }, + { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871 }, + { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264 }, + { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819 }, + { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650 }, + { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833 }, + { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692 }, + { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424 }, + { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300 }, + { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769 }, + { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892 }, + { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748 }, + { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554 }, + { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118 }, + { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555 }, + { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295 }, + { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027 }, + { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428 }, + { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331 }, + { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831 }, + { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809 }, + { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593 }, + { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202 }, + { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207 }, + { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315 }, ] [[package]] name = "coverage" version = "7.13.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/ad/b59e5b451cf7172b8d1043dc0fa718f23aab379bc1521ee13d4bd9bfa960/coverage-7.13.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d490ba50c3f35dd7c17953c68f3270e7ccd1c6642e2d2afe2d8e720b98f5a053", size = 219278, upload-time = "2026-02-09T12:56:31.673Z" }, - { url = "https://files.pythonhosted.org/packages/f1/17/0cb7ca3de72e5f4ef2ec2fa0089beafbcaaaead1844e8b8a63d35173d77d/coverage-7.13.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:19bc3c88078789f8ef36acb014d7241961dbf883fd2533d18cb1e7a5b4e28b11", size = 219783, upload-time = "2026-02-09T12:56:33.104Z" }, - { url = "https://files.pythonhosted.org/packages/ab/63/325d8e5b11e0eaf6d0f6a44fad444ae58820929a9b0de943fa377fe73e85/coverage-7.13.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3998e5a32e62fdf410c0dbd3115df86297995d6e3429af80b8798aad894ca7aa", size = 250200, upload-time = "2026-02-09T12:56:34.474Z" }, - { url = "https://files.pythonhosted.org/packages/76/53/c16972708cbb79f2942922571a687c52bd109a7bd51175aeb7558dff2236/coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7", size = 252114, upload-time = "2026-02-09T12:56:35.749Z" }, - { url = "https://files.pythonhosted.org/packages/eb/c2/7ab36d8b8cc412bec9ea2d07c83c48930eb4ba649634ba00cb7e4e0f9017/coverage-7.13.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3aa4e7b9e416774b21797365b358a6e827ffadaaca81b69ee02946852449f00", size = 254220, upload-time = "2026-02-09T12:56:37.796Z" }, - { url = "https://files.pythonhosted.org/packages/d6/4d/cf52c9a3322c89a0e6febdfbc83bb45c0ed3c64ad14081b9503adee702e7/coverage-7.13.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:71ca20079dd8f27fcf808817e281e90220475cd75115162218d0e27549f95fef", size = 256164, upload-time = "2026-02-09T12:56:39.016Z" }, - { url = "https://files.pythonhosted.org/packages/78/e9/eb1dd17bd6de8289df3580e967e78294f352a5df8a57ff4671ee5fc3dcd0/coverage-7.13.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e2f25215f1a359ab17320b47bcdaca3e6e6356652e8256f2441e4ef972052903", size = 250325, upload-time = "2026-02-09T12:56:40.668Z" }, - { url = "https://files.pythonhosted.org/packages/71/07/8c1542aa873728f72267c07278c5cc0ec91356daf974df21335ccdb46368/coverage-7.13.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d65b2d373032411e86960604dc4edac91fdfb5dca539461cf2cbe78327d1e64f", size = 251913, upload-time = "2026-02-09T12:56:41.97Z" }, - { url = "https://files.pythonhosted.org/packages/74/d7/c62e2c5e4483a748e27868e4c32ad3daa9bdddbba58e1bc7a15e252baa74/coverage-7.13.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94eb63f9b363180aff17de3e7c8760c3ba94664ea2695c52f10111244d16a299", size = 249974, upload-time = "2026-02-09T12:56:43.323Z" }, - { url = "https://files.pythonhosted.org/packages/98/9f/4c5c015a6e98ced54efd0f5cf8d31b88e5504ecb6857585fc0161bb1e600/coverage-7.13.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e856bf6616714c3a9fbc270ab54103f4e685ba236fa98c054e8f87f266c93505", size = 253741, upload-time = "2026-02-09T12:56:45.155Z" }, - { url = "https://files.pythonhosted.org/packages/bd/59/0f4eef89b9f0fcd9633b5d350016f54126ab49426a70ff4c4e87446cabdc/coverage-7.13.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:65dfcbe305c3dfe658492df2d85259e0d79ead4177f9ae724b6fb245198f55d6", size = 249695, upload-time = "2026-02-09T12:56:46.636Z" }, - { url = "https://files.pythonhosted.org/packages/b5/2c/b7476f938deb07166f3eb281a385c262675d688ff4659ad56c6c6b8e2e70/coverage-7.13.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b507778ae8a4c915436ed5c2e05b4a6cecfa70f734e19c22a005152a11c7b6a9", size = 250599, upload-time = "2026-02-09T12:56:48.13Z" }, - { url = "https://files.pythonhosted.org/packages/b8/34/c3420709d9846ee3785b9f2831b4d94f276f38884032dca1457fa83f7476/coverage-7.13.4-cp311-cp311-win32.whl", hash = "sha256:784fc3cf8be001197b652d51d3fd259b1e2262888693a4636e18879f613a62a9", size = 221780, upload-time = "2026-02-09T12:56:50.479Z" }, - { url = "https://files.pythonhosted.org/packages/61/08/3d9c8613079d2b11c185b865de9a4c1a68850cfda2b357fae365cf609f29/coverage-7.13.4-cp311-cp311-win_amd64.whl", hash = "sha256:2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f", size = 222715, upload-time = "2026-02-09T12:56:51.815Z" }, - { url = "https://files.pythonhosted.org/packages/18/1a/54c3c80b2f056164cc0a6cdcb040733760c7c4be9d780fe655f356f433e4/coverage-7.13.4-cp311-cp311-win_arm64.whl", hash = "sha256:79e73a76b854d9c6088fe5d8b2ebe745f8681c55f7397c3c0a016192d681045f", size = 221385, upload-time = "2026-02-09T12:56:53.194Z" }, - { url = "https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459", size = 219449, upload-time = "2026-02-09T12:56:54.889Z" }, - { url = "https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3", size = 219810, upload-time = "2026-02-09T12:56:56.33Z" }, - { url = "https://files.pythonhosted.org/packages/78/72/2f372b726d433c9c35e56377cf1d513b4c16fe51841060d826b95caacec1/coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634", size = 251308, upload-time = "2026-02-09T12:56:57.858Z" }, - { url = "https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3", size = 254052, upload-time = "2026-02-09T12:56:59.754Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ac/45dc2e19a1939098d783c846e130b8f862fbb50d09e0af663988f2f21973/coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa", size = 255165, upload-time = "2026-02-09T12:57:01.287Z" }, - { url = "https://files.pythonhosted.org/packages/2d/4d/26d236ff35abc3b5e63540d3386e4c3b192168c1d96da5cb2f43c640970f/coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3", size = 257432, upload-time = "2026-02-09T12:57:02.637Z" }, - { url = "https://files.pythonhosted.org/packages/ec/55/14a966c757d1348b2e19caf699415a2a4c4f7feaa4bbc6326a51f5c7dd1b/coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a", size = 251716, upload-time = "2026-02-09T12:57:04.056Z" }, - { url = "https://files.pythonhosted.org/packages/77/33/50116647905837c66d28b2af1321b845d5f5d19be9655cb84d4a0ea806b4/coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7", size = 253089, upload-time = "2026-02-09T12:57:05.503Z" }, - { url = "https://files.pythonhosted.org/packages/c2/b4/8efb11a46e3665d92635a56e4f2d4529de6d33f2cb38afd47d779d15fc99/coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc", size = 251232, upload-time = "2026-02-09T12:57:06.879Z" }, - { url = "https://files.pythonhosted.org/packages/51/24/8cd73dd399b812cc76bb0ac260e671c4163093441847ffe058ac9fda1e32/coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47", size = 255299, upload-time = "2026-02-09T12:57:08.245Z" }, - { url = "https://files.pythonhosted.org/packages/03/94/0a4b12f1d0e029ce1ccc1c800944a9984cbe7d678e470bb6d3c6bc38a0da/coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985", size = 250796, upload-time = "2026-02-09T12:57:10.142Z" }, - { url = "https://files.pythonhosted.org/packages/73/44/6002fbf88f6698ca034360ce474c406be6d5a985b3fdb3401128031eef6b/coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0", size = 252673, upload-time = "2026-02-09T12:57:12.197Z" }, - { url = "https://files.pythonhosted.org/packages/de/c6/a0279f7c00e786be75a749a5674e6fa267bcbd8209cd10c9a450c655dfa7/coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246", size = 221990, upload-time = "2026-02-09T12:57:14.085Z" }, - { url = "https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126", size = 222800, upload-time = "2026-02-09T12:57:15.944Z" }, - { url = "https://files.pythonhosted.org/packages/47/ac/92da44ad9a6f4e3a7debd178949d6f3769bedca33830ce9b1dcdab589a37/coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d", size = 221415, upload-time = "2026-02-09T12:57:17.497Z" }, - { url = "https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9", size = 219474, upload-time = "2026-02-09T12:57:19.332Z" }, - { url = "https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac", size = 219844, upload-time = "2026-02-09T12:57:20.66Z" }, - { url = "https://files.pythonhosted.org/packages/97/fd/7e859f8fab324cef6c4ad7cff156ca7c489fef9179d5749b0c8d321281c2/coverage-7.13.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea", size = 250832, upload-time = "2026-02-09T12:57:22.007Z" }, - { url = "https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b", size = 253434, upload-time = "2026-02-09T12:57:23.339Z" }, - { url = "https://files.pythonhosted.org/packages/5a/88/6728a7ad17428b18d836540630487231f5470fb82454871149502f5e5aa2/coverage-7.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525", size = 254676, upload-time = "2026-02-09T12:57:24.774Z" }, - { url = "https://files.pythonhosted.org/packages/7c/bc/21244b1b8cedf0dff0a2b53b208015fe798d5f2a8d5348dbfece04224fff/coverage-7.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242", size = 256807, upload-time = "2026-02-09T12:57:26.125Z" }, - { url = "https://files.pythonhosted.org/packages/97/a0/ddba7ed3251cff51006737a727d84e05b61517d1784a9988a846ba508877/coverage-7.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148", size = 251058, upload-time = "2026-02-09T12:57:27.614Z" }, - { url = "https://files.pythonhosted.org/packages/9b/55/e289addf7ff54d3a540526f33751951bf0878f3809b47f6dfb3def69c6f7/coverage-7.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a", size = 252805, upload-time = "2026-02-09T12:57:29.066Z" }, - { url = "https://files.pythonhosted.org/packages/13/4e/cc276b1fa4a59be56d96f1dabddbdc30f4ba22e3b1cd42504c37b3313255/coverage-7.13.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23", size = 250766, upload-time = "2026-02-09T12:57:30.522Z" }, - { url = "https://files.pythonhosted.org/packages/94/44/1093b8f93018f8b41a8cf29636c9292502f05e4a113d4d107d14a3acd044/coverage-7.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80", size = 254923, upload-time = "2026-02-09T12:57:31.946Z" }, - { url = "https://files.pythonhosted.org/packages/8b/55/ea2796da2d42257f37dbea1aab239ba9263b31bd91d5527cdd6db5efe174/coverage-7.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea", size = 250591, upload-time = "2026-02-09T12:57:33.842Z" }, - { url = "https://files.pythonhosted.org/packages/d4/fa/7c4bb72aacf8af5020675aa633e59c1fbe296d22aed191b6a5b711eb2bc7/coverage-7.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a", size = 252364, upload-time = "2026-02-09T12:57:35.743Z" }, - { url = "https://files.pythonhosted.org/packages/5c/38/a8d2ec0146479c20bbaa7181b5b455a0c41101eed57f10dd19a78ab44c80/coverage-7.13.4-cp313-cp313-win32.whl", hash = "sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d", size = 222010, upload-time = "2026-02-09T12:57:37.25Z" }, - { url = "https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd", size = 222818, upload-time = "2026-02-09T12:57:38.734Z" }, - { url = "https://files.pythonhosted.org/packages/04/d1/934918a138c932c90d78301f45f677fb05c39a3112b96fd2c8e60503cdc7/coverage-7.13.4-cp313-cp313-win_arm64.whl", hash = "sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af", size = 221438, upload-time = "2026-02-09T12:57:40.223Z" }, - { url = "https://files.pythonhosted.org/packages/52/57/ee93ced533bcb3e6df961c0c6e42da2fc6addae53fb95b94a89b1e33ebd7/coverage-7.13.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d", size = 220165, upload-time = "2026-02-09T12:57:41.639Z" }, - { url = "https://files.pythonhosted.org/packages/c5/e0/969fc285a6fbdda49d91af278488d904dcd7651b2693872f0ff94e40e84a/coverage-7.13.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12", size = 220516, upload-time = "2026-02-09T12:57:44.215Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b8/9531944e16267e2735a30a9641ff49671f07e8138ecf1ca13db9fd2560c7/coverage-7.13.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b", size = 261804, upload-time = "2026-02-09T12:57:45.989Z" }, - { url = "https://files.pythonhosted.org/packages/8a/f3/e63df6d500314a2a60390d1989240d5f27318a7a68fa30ad3806e2a9323e/coverage-7.13.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9", size = 263885, upload-time = "2026-02-09T12:57:47.42Z" }, - { url = "https://files.pythonhosted.org/packages/f3/67/7654810de580e14b37670b60a09c599fa348e48312db5b216d730857ffe6/coverage-7.13.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092", size = 266308, upload-time = "2026-02-09T12:57:49.345Z" }, - { url = "https://files.pythonhosted.org/packages/37/6f/39d41eca0eab3cc82115953ad41c4e77935286c930e8fad15eaed1389d83/coverage-7.13.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9", size = 267452, upload-time = "2026-02-09T12:57:50.811Z" }, - { url = "https://files.pythonhosted.org/packages/50/6d/39c0fbb8fc5cd4d2090811e553c2108cf5112e882f82505ee7495349a6bf/coverage-7.13.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26", size = 261057, upload-time = "2026-02-09T12:57:52.447Z" }, - { url = "https://files.pythonhosted.org/packages/a4/a2/60010c669df5fa603bb5a97fb75407e191a846510da70ac657eb696b7fce/coverage-7.13.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2", size = 263875, upload-time = "2026-02-09T12:57:53.938Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d9/63b22a6bdbd17f1f96e9ed58604c2a6b0e72a9133e37d663bef185877cf6/coverage-7.13.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940", size = 261500, upload-time = "2026-02-09T12:57:56.012Z" }, - { url = "https://files.pythonhosted.org/packages/70/bf/69f86ba1ad85bc3ad240e4c0e57a2e620fbc0e1645a47b5c62f0e941ad7f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c", size = 265212, upload-time = "2026-02-09T12:57:57.5Z" }, - { url = "https://files.pythonhosted.org/packages/ae/f2/5f65a278a8c2148731831574c73e42f57204243d33bedaaf18fa79c5958f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0", size = 260398, upload-time = "2026-02-09T12:57:59.027Z" }, - { url = "https://files.pythonhosted.org/packages/ef/80/6e8280a350ee9fea92f14b8357448a242dcaa243cb2c72ab0ca591f66c8c/coverage-7.13.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b", size = 262584, upload-time = "2026-02-09T12:58:01.129Z" }, - { url = "https://files.pythonhosted.org/packages/22/63/01ff182fc95f260b539590fb12c11ad3e21332c15f9799cb5e2386f71d9f/coverage-7.13.4-cp313-cp313t-win32.whl", hash = "sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9", size = 222688, upload-time = "2026-02-09T12:58:02.736Z" }, - { url = "https://files.pythonhosted.org/packages/a9/43/89de4ef5d3cd53b886afa114065f7e9d3707bdb3e5efae13535b46ae483d/coverage-7.13.4-cp313-cp313t-win_amd64.whl", hash = "sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd", size = 223746, upload-time = "2026-02-09T12:58:05.362Z" }, - { url = "https://files.pythonhosted.org/packages/35/39/7cf0aa9a10d470a5309b38b289b9bb07ddeac5d61af9b664fe9775a4cb3e/coverage-7.13.4-cp313-cp313t-win_arm64.whl", hash = "sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997", size = 222003, upload-time = "2026-02-09T12:58:06.952Z" }, - { url = "https://files.pythonhosted.org/packages/92/11/a9cf762bb83386467737d32187756a42094927150c3e107df4cb078e8590/coverage-7.13.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:300deaee342f90696ed186e3a00c71b5b3d27bffe9e827677954f4ee56969601", size = 219522, upload-time = "2026-02-09T12:58:08.623Z" }, - { url = "https://files.pythonhosted.org/packages/d3/28/56e6d892b7b052236d67c95f1936b6a7cf7c3e2634bf27610b8cbd7f9c60/coverage-7.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29e3220258d682b6226a9b0925bc563ed9a1ebcff3cad30f043eceea7eaf2689", size = 219855, upload-time = "2026-02-09T12:58:10.176Z" }, - { url = "https://files.pythonhosted.org/packages/e5/69/233459ee9eb0c0d10fcc2fe425a029b3fa5ce0f040c966ebce851d030c70/coverage-7.13.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:391ee8f19bef69210978363ca930f7328081c6a0152f1166c91f0b5fdd2a773c", size = 250887, upload-time = "2026-02-09T12:58:12.503Z" }, - { url = "https://files.pythonhosted.org/packages/06/90/2cdab0974b9b5bbc1623f7876b73603aecac11b8d95b85b5b86b32de5eab/coverage-7.13.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0dd7ab8278f0d58a0128ba2fca25824321f05d059c1441800e934ff2efa52129", size = 253396, upload-time = "2026-02-09T12:58:14.615Z" }, - { url = "https://files.pythonhosted.org/packages/ac/15/ea4da0f85bf7d7b27635039e649e99deb8173fe551096ea15017f7053537/coverage-7.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78cdf0d578b15148b009ccf18c686aa4f719d887e76e6b40c38ffb61d264a552", size = 254745, upload-time = "2026-02-09T12:58:16.162Z" }, - { url = "https://files.pythonhosted.org/packages/99/11/bb356e86920c655ca4d61daee4e2bbc7258f0a37de0be32d233b561134ff/coverage-7.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:48685fee12c2eb3b27c62f2658e7ea21e9c3239cba5a8a242801a0a3f6a8c62a", size = 257055, upload-time = "2026-02-09T12:58:17.892Z" }, - { url = "https://files.pythonhosted.org/packages/c9/0f/9ae1f8cb17029e09da06ca4e28c9e1d5c1c0a511c7074592e37e0836c915/coverage-7.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4e83efc079eb39480e6346a15a1bcb3e9b04759c5202d157e1dd4303cd619356", size = 250911, upload-time = "2026-02-09T12:58:19.495Z" }, - { url = "https://files.pythonhosted.org/packages/89/3a/adfb68558fa815cbc29747b553bc833d2150228f251b127f1ce97e48547c/coverage-7.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecae9737b72408d6a950f7e525f30aca12d4bd8dd95e37342e5beb3a2a8c4f71", size = 252754, upload-time = "2026-02-09T12:58:21.064Z" }, - { url = "https://files.pythonhosted.org/packages/32/b1/540d0c27c4e748bd3cd0bd001076ee416eda993c2bae47a73b7cc9357931/coverage-7.13.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ae4578f8528569d3cf303fef2ea569c7f4c4059a38c8667ccef15c6e1f118aa5", size = 250720, upload-time = "2026-02-09T12:58:22.622Z" }, - { url = "https://files.pythonhosted.org/packages/c7/95/383609462b3ffb1fe133014a7c84fc0dd01ed55ac6140fa1093b5af7ebb1/coverage-7.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6fdef321fdfbb30a197efa02d48fcd9981f0d8ad2ae8903ac318adc653f5df98", size = 254994, upload-time = "2026-02-09T12:58:24.548Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ba/1761138e86c81680bfc3c49579d66312865457f9fe405b033184e5793cb3/coverage-7.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b0f6ccf3dbe577170bebfce1318707d0e8c3650003cb4b3a9dd744575daa8b5", size = 250531, upload-time = "2026-02-09T12:58:26.271Z" }, - { url = "https://files.pythonhosted.org/packages/f8/8e/05900df797a9c11837ab59c4d6fe94094e029582aab75c3309a93e6fb4e3/coverage-7.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75fcd519f2a5765db3f0e391eb3b7d150cce1a771bf4c9f861aeab86c767a3c0", size = 252189, upload-time = "2026-02-09T12:58:27.807Z" }, - { url = "https://files.pythonhosted.org/packages/00/bd/29c9f2db9ea4ed2738b8a9508c35626eb205d51af4ab7bf56a21a2e49926/coverage-7.13.4-cp314-cp314-win32.whl", hash = "sha256:8e798c266c378da2bd819b0677df41ab46d78065fb2a399558f3f6cae78b2fbb", size = 222258, upload-time = "2026-02-09T12:58:29.441Z" }, - { url = "https://files.pythonhosted.org/packages/a7/4d/1f8e723f6829977410efeb88f73673d794075091c8c7c18848d273dc9d73/coverage-7.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505", size = 223073, upload-time = "2026-02-09T12:58:31.026Z" }, - { url = "https://files.pythonhosted.org/packages/51/5b/84100025be913b44e082ea32abcf1afbf4e872f5120b7a1cab1d331b1e13/coverage-7.13.4-cp314-cp314-win_arm64.whl", hash = "sha256:ad27098a189e5838900ce4c2a99f2fe42a0bf0c2093c17c69b45a71579e8d4a2", size = 221638, upload-time = "2026-02-09T12:58:32.599Z" }, - { url = "https://files.pythonhosted.org/packages/a7/e4/c884a405d6ead1370433dad1e3720216b4f9fd8ef5b64bfd984a2a60a11a/coverage-7.13.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:85480adfb35ffc32d40918aad81b89c69c9cc5661a9b8a81476d3e645321a056", size = 220246, upload-time = "2026-02-09T12:58:34.181Z" }, - { url = "https://files.pythonhosted.org/packages/81/5c/4d7ed8b23b233b0fffbc9dfec53c232be2e695468523242ea9fd30f97ad2/coverage-7.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:79be69cf7f3bf9b0deeeb062eab7ac7f36cd4cc4c4dd694bd28921ba4d8596cc", size = 220514, upload-time = "2026-02-09T12:58:35.704Z" }, - { url = "https://files.pythonhosted.org/packages/2f/6f/3284d4203fd2f28edd73034968398cd2d4cb04ab192abc8cff007ea35679/coverage-7.13.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:caa421e2684e382c5d8973ac55e4f36bed6821a9bad5c953494de960c74595c9", size = 261877, upload-time = "2026-02-09T12:58:37.864Z" }, - { url = "https://files.pythonhosted.org/packages/09/aa/b672a647bbe1556a85337dc95bfd40d146e9965ead9cc2fe81bde1e5cbce/coverage-7.13.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14375934243ee05f56c45393fe2ce81fe5cc503c07cee2bdf1725fb8bef3ffaf", size = 264004, upload-time = "2026-02-09T12:58:39.492Z" }, - { url = "https://files.pythonhosted.org/packages/79/a1/aa384dbe9181f98bba87dd23dda436f0c6cf2e148aecbb4e50fc51c1a656/coverage-7.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25a41c3104d08edb094d9db0d905ca54d0cd41c928bb6be3c4c799a54753af55", size = 266408, upload-time = "2026-02-09T12:58:41.852Z" }, - { url = "https://files.pythonhosted.org/packages/53/5e/5150bf17b4019bc600799f376bb9606941e55bd5a775dc1e096b6ffea952/coverage-7.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f01afcff62bf9a08fb32b2c1d6e924236c0383c02c790732b6537269e466a72", size = 267544, upload-time = "2026-02-09T12:58:44.093Z" }, - { url = "https://files.pythonhosted.org/packages/e0/ed/f1de5c675987a4a7a672250d2c5c9d73d289dbf13410f00ed7181d8017dd/coverage-7.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eb9078108fbf0bcdde37c3f4779303673c2fa1fe8f7956e68d447d0dd426d38a", size = 260980, upload-time = "2026-02-09T12:58:45.721Z" }, - { url = "https://files.pythonhosted.org/packages/b3/e3/fe758d01850aa172419a6743fe76ba8b92c29d181d4f676ffe2dae2ba631/coverage-7.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e086334e8537ddd17e5f16a344777c1ab8194986ec533711cbe6c41cde841b6", size = 263871, upload-time = "2026-02-09T12:58:47.334Z" }, - { url = "https://files.pythonhosted.org/packages/b6/76/b829869d464115e22499541def9796b25312b8cf235d3bb00b39f1675395/coverage-7.13.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:725d985c5ab621268b2edb8e50dfe57633dc69bda071abc470fed55a14935fd3", size = 261472, upload-time = "2026-02-09T12:58:48.995Z" }, - { url = "https://files.pythonhosted.org/packages/14/9e/caedb1679e73e2f6ad240173f55218488bfe043e38da577c4ec977489915/coverage-7.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3c06f0f1337c667b971ca2f975523347e63ec5e500b9aa5882d91931cd3ef750", size = 265210, upload-time = "2026-02-09T12:58:51.178Z" }, - { url = "https://files.pythonhosted.org/packages/3a/10/0dd02cb009b16ede425b49ec344aba13a6ae1dc39600840ea6abcb085ac4/coverage-7.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:590c0ed4bf8e85f745e6b805b2e1c457b2e33d5255dd9729743165253bc9ad39", size = 260319, upload-time = "2026-02-09T12:58:53.081Z" }, - { url = "https://files.pythonhosted.org/packages/92/8e/234d2c927af27c6d7a5ffad5bd2cf31634c46a477b4c7adfbfa66baf7ebb/coverage-7.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eb30bf180de3f632cd043322dad5751390e5385108b2807368997d1a92a509d0", size = 262638, upload-time = "2026-02-09T12:58:55.258Z" }, - { url = "https://files.pythonhosted.org/packages/2f/64/e5547c8ff6964e5965c35a480855911b61509cce544f4d442caa759a0702/coverage-7.13.4-cp314-cp314t-win32.whl", hash = "sha256:c4240e7eded42d131a2d2c4dec70374b781b043ddc79a9de4d55ca71f8e98aea", size = 223040, upload-time = "2026-02-09T12:58:56.936Z" }, - { url = "https://files.pythonhosted.org/packages/c7/96/38086d58a181aac86d503dfa9c47eb20715a79c3e3acbdf786e92e5c09a8/coverage-7.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4c7d3cc01e7350f2f0f6f7036caaf5673fb56b6998889ccfe9e1c1fe75a9c932", size = 224148, upload-time = "2026-02-09T12:58:58.645Z" }, - { url = "https://files.pythonhosted.org/packages/ce/72/8d10abd3740a0beb98c305e0c3faf454366221c0f37a8bcf8f60020bb65a/coverage-7.13.4-cp314-cp314t-win_arm64.whl", hash = "sha256:23e3f687cf945070d1c90f85db66d11e3025665d8dafa831301a0e0038f3db9b", size = 222172, upload-time = "2026-02-09T12:59:00.396Z" }, - { url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/ad/b59e5b451cf7172b8d1043dc0fa718f23aab379bc1521ee13d4bd9bfa960/coverage-7.13.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d490ba50c3f35dd7c17953c68f3270e7ccd1c6642e2d2afe2d8e720b98f5a053", size = 219278 }, + { url = "https://files.pythonhosted.org/packages/f1/17/0cb7ca3de72e5f4ef2ec2fa0089beafbcaaaead1844e8b8a63d35173d77d/coverage-7.13.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:19bc3c88078789f8ef36acb014d7241961dbf883fd2533d18cb1e7a5b4e28b11", size = 219783 }, + { url = "https://files.pythonhosted.org/packages/ab/63/325d8e5b11e0eaf6d0f6a44fad444ae58820929a9b0de943fa377fe73e85/coverage-7.13.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3998e5a32e62fdf410c0dbd3115df86297995d6e3429af80b8798aad894ca7aa", size = 250200 }, + { url = "https://files.pythonhosted.org/packages/76/53/c16972708cbb79f2942922571a687c52bd109a7bd51175aeb7558dff2236/coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7", size = 252114 }, + { url = "https://files.pythonhosted.org/packages/eb/c2/7ab36d8b8cc412bec9ea2d07c83c48930eb4ba649634ba00cb7e4e0f9017/coverage-7.13.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3aa4e7b9e416774b21797365b358a6e827ffadaaca81b69ee02946852449f00", size = 254220 }, + { url = "https://files.pythonhosted.org/packages/d6/4d/cf52c9a3322c89a0e6febdfbc83bb45c0ed3c64ad14081b9503adee702e7/coverage-7.13.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:71ca20079dd8f27fcf808817e281e90220475cd75115162218d0e27549f95fef", size = 256164 }, + { url = "https://files.pythonhosted.org/packages/78/e9/eb1dd17bd6de8289df3580e967e78294f352a5df8a57ff4671ee5fc3dcd0/coverage-7.13.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e2f25215f1a359ab17320b47bcdaca3e6e6356652e8256f2441e4ef972052903", size = 250325 }, + { url = "https://files.pythonhosted.org/packages/71/07/8c1542aa873728f72267c07278c5cc0ec91356daf974df21335ccdb46368/coverage-7.13.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d65b2d373032411e86960604dc4edac91fdfb5dca539461cf2cbe78327d1e64f", size = 251913 }, + { url = "https://files.pythonhosted.org/packages/74/d7/c62e2c5e4483a748e27868e4c32ad3daa9bdddbba58e1bc7a15e252baa74/coverage-7.13.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94eb63f9b363180aff17de3e7c8760c3ba94664ea2695c52f10111244d16a299", size = 249974 }, + { url = "https://files.pythonhosted.org/packages/98/9f/4c5c015a6e98ced54efd0f5cf8d31b88e5504ecb6857585fc0161bb1e600/coverage-7.13.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e856bf6616714c3a9fbc270ab54103f4e685ba236fa98c054e8f87f266c93505", size = 253741 }, + { url = "https://files.pythonhosted.org/packages/bd/59/0f4eef89b9f0fcd9633b5d350016f54126ab49426a70ff4c4e87446cabdc/coverage-7.13.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:65dfcbe305c3dfe658492df2d85259e0d79ead4177f9ae724b6fb245198f55d6", size = 249695 }, + { url = "https://files.pythonhosted.org/packages/b5/2c/b7476f938deb07166f3eb281a385c262675d688ff4659ad56c6c6b8e2e70/coverage-7.13.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b507778ae8a4c915436ed5c2e05b4a6cecfa70f734e19c22a005152a11c7b6a9", size = 250599 }, + { url = "https://files.pythonhosted.org/packages/b8/34/c3420709d9846ee3785b9f2831b4d94f276f38884032dca1457fa83f7476/coverage-7.13.4-cp311-cp311-win32.whl", hash = "sha256:784fc3cf8be001197b652d51d3fd259b1e2262888693a4636e18879f613a62a9", size = 221780 }, + { url = "https://files.pythonhosted.org/packages/61/08/3d9c8613079d2b11c185b865de9a4c1a68850cfda2b357fae365cf609f29/coverage-7.13.4-cp311-cp311-win_amd64.whl", hash = "sha256:2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f", size = 222715 }, + { url = "https://files.pythonhosted.org/packages/18/1a/54c3c80b2f056164cc0a6cdcb040733760c7c4be9d780fe655f356f433e4/coverage-7.13.4-cp311-cp311-win_arm64.whl", hash = "sha256:79e73a76b854d9c6088fe5d8b2ebe745f8681c55f7397c3c0a016192d681045f", size = 221385 }, + { url = "https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459", size = 219449 }, + { url = "https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3", size = 219810 }, + { url = "https://files.pythonhosted.org/packages/78/72/2f372b726d433c9c35e56377cf1d513b4c16fe51841060d826b95caacec1/coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634", size = 251308 }, + { url = "https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3", size = 254052 }, + { url = "https://files.pythonhosted.org/packages/e8/ac/45dc2e19a1939098d783c846e130b8f862fbb50d09e0af663988f2f21973/coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa", size = 255165 }, + { url = "https://files.pythonhosted.org/packages/2d/4d/26d236ff35abc3b5e63540d3386e4c3b192168c1d96da5cb2f43c640970f/coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3", size = 257432 }, + { url = "https://files.pythonhosted.org/packages/ec/55/14a966c757d1348b2e19caf699415a2a4c4f7feaa4bbc6326a51f5c7dd1b/coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a", size = 251716 }, + { url = "https://files.pythonhosted.org/packages/77/33/50116647905837c66d28b2af1321b845d5f5d19be9655cb84d4a0ea806b4/coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7", size = 253089 }, + { url = "https://files.pythonhosted.org/packages/c2/b4/8efb11a46e3665d92635a56e4f2d4529de6d33f2cb38afd47d779d15fc99/coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc", size = 251232 }, + { url = "https://files.pythonhosted.org/packages/51/24/8cd73dd399b812cc76bb0ac260e671c4163093441847ffe058ac9fda1e32/coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47", size = 255299 }, + { url = "https://files.pythonhosted.org/packages/03/94/0a4b12f1d0e029ce1ccc1c800944a9984cbe7d678e470bb6d3c6bc38a0da/coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985", size = 250796 }, + { url = "https://files.pythonhosted.org/packages/73/44/6002fbf88f6698ca034360ce474c406be6d5a985b3fdb3401128031eef6b/coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0", size = 252673 }, + { url = "https://files.pythonhosted.org/packages/de/c6/a0279f7c00e786be75a749a5674e6fa267bcbd8209cd10c9a450c655dfa7/coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246", size = 221990 }, + { url = "https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126", size = 222800 }, + { url = "https://files.pythonhosted.org/packages/47/ac/92da44ad9a6f4e3a7debd178949d6f3769bedca33830ce9b1dcdab589a37/coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d", size = 221415 }, + { url = "https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9", size = 219474 }, + { url = "https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac", size = 219844 }, + { url = "https://files.pythonhosted.org/packages/97/fd/7e859f8fab324cef6c4ad7cff156ca7c489fef9179d5749b0c8d321281c2/coverage-7.13.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea", size = 250832 }, + { url = "https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b", size = 253434 }, + { url = "https://files.pythonhosted.org/packages/5a/88/6728a7ad17428b18d836540630487231f5470fb82454871149502f5e5aa2/coverage-7.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525", size = 254676 }, + { url = "https://files.pythonhosted.org/packages/7c/bc/21244b1b8cedf0dff0a2b53b208015fe798d5f2a8d5348dbfece04224fff/coverage-7.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242", size = 256807 }, + { url = "https://files.pythonhosted.org/packages/97/a0/ddba7ed3251cff51006737a727d84e05b61517d1784a9988a846ba508877/coverage-7.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148", size = 251058 }, + { url = "https://files.pythonhosted.org/packages/9b/55/e289addf7ff54d3a540526f33751951bf0878f3809b47f6dfb3def69c6f7/coverage-7.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a", size = 252805 }, + { url = "https://files.pythonhosted.org/packages/13/4e/cc276b1fa4a59be56d96f1dabddbdc30f4ba22e3b1cd42504c37b3313255/coverage-7.13.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23", size = 250766 }, + { url = "https://files.pythonhosted.org/packages/94/44/1093b8f93018f8b41a8cf29636c9292502f05e4a113d4d107d14a3acd044/coverage-7.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80", size = 254923 }, + { url = "https://files.pythonhosted.org/packages/8b/55/ea2796da2d42257f37dbea1aab239ba9263b31bd91d5527cdd6db5efe174/coverage-7.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea", size = 250591 }, + { url = "https://files.pythonhosted.org/packages/d4/fa/7c4bb72aacf8af5020675aa633e59c1fbe296d22aed191b6a5b711eb2bc7/coverage-7.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a", size = 252364 }, + { url = "https://files.pythonhosted.org/packages/5c/38/a8d2ec0146479c20bbaa7181b5b455a0c41101eed57f10dd19a78ab44c80/coverage-7.13.4-cp313-cp313-win32.whl", hash = "sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d", size = 222010 }, + { url = "https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd", size = 222818 }, + { url = "https://files.pythonhosted.org/packages/04/d1/934918a138c932c90d78301f45f677fb05c39a3112b96fd2c8e60503cdc7/coverage-7.13.4-cp313-cp313-win_arm64.whl", hash = "sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af", size = 221438 }, + { url = "https://files.pythonhosted.org/packages/52/57/ee93ced533bcb3e6df961c0c6e42da2fc6addae53fb95b94a89b1e33ebd7/coverage-7.13.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d", size = 220165 }, + { url = "https://files.pythonhosted.org/packages/c5/e0/969fc285a6fbdda49d91af278488d904dcd7651b2693872f0ff94e40e84a/coverage-7.13.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12", size = 220516 }, + { url = "https://files.pythonhosted.org/packages/b1/b8/9531944e16267e2735a30a9641ff49671f07e8138ecf1ca13db9fd2560c7/coverage-7.13.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b", size = 261804 }, + { url = "https://files.pythonhosted.org/packages/8a/f3/e63df6d500314a2a60390d1989240d5f27318a7a68fa30ad3806e2a9323e/coverage-7.13.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9", size = 263885 }, + { url = "https://files.pythonhosted.org/packages/f3/67/7654810de580e14b37670b60a09c599fa348e48312db5b216d730857ffe6/coverage-7.13.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092", size = 266308 }, + { url = "https://files.pythonhosted.org/packages/37/6f/39d41eca0eab3cc82115953ad41c4e77935286c930e8fad15eaed1389d83/coverage-7.13.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9", size = 267452 }, + { url = "https://files.pythonhosted.org/packages/50/6d/39c0fbb8fc5cd4d2090811e553c2108cf5112e882f82505ee7495349a6bf/coverage-7.13.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26", size = 261057 }, + { url = "https://files.pythonhosted.org/packages/a4/a2/60010c669df5fa603bb5a97fb75407e191a846510da70ac657eb696b7fce/coverage-7.13.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2", size = 263875 }, + { url = "https://files.pythonhosted.org/packages/3e/d9/63b22a6bdbd17f1f96e9ed58604c2a6b0e72a9133e37d663bef185877cf6/coverage-7.13.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940", size = 261500 }, + { url = "https://files.pythonhosted.org/packages/70/bf/69f86ba1ad85bc3ad240e4c0e57a2e620fbc0e1645a47b5c62f0e941ad7f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c", size = 265212 }, + { url = "https://files.pythonhosted.org/packages/ae/f2/5f65a278a8c2148731831574c73e42f57204243d33bedaaf18fa79c5958f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0", size = 260398 }, + { url = "https://files.pythonhosted.org/packages/ef/80/6e8280a350ee9fea92f14b8357448a242dcaa243cb2c72ab0ca591f66c8c/coverage-7.13.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b", size = 262584 }, + { url = "https://files.pythonhosted.org/packages/22/63/01ff182fc95f260b539590fb12c11ad3e21332c15f9799cb5e2386f71d9f/coverage-7.13.4-cp313-cp313t-win32.whl", hash = "sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9", size = 222688 }, + { url = "https://files.pythonhosted.org/packages/a9/43/89de4ef5d3cd53b886afa114065f7e9d3707bdb3e5efae13535b46ae483d/coverage-7.13.4-cp313-cp313t-win_amd64.whl", hash = "sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd", size = 223746 }, + { url = "https://files.pythonhosted.org/packages/35/39/7cf0aa9a10d470a5309b38b289b9bb07ddeac5d61af9b664fe9775a4cb3e/coverage-7.13.4-cp313-cp313t-win_arm64.whl", hash = "sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997", size = 222003 }, + { url = "https://files.pythonhosted.org/packages/92/11/a9cf762bb83386467737d32187756a42094927150c3e107df4cb078e8590/coverage-7.13.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:300deaee342f90696ed186e3a00c71b5b3d27bffe9e827677954f4ee56969601", size = 219522 }, + { url = "https://files.pythonhosted.org/packages/d3/28/56e6d892b7b052236d67c95f1936b6a7cf7c3e2634bf27610b8cbd7f9c60/coverage-7.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29e3220258d682b6226a9b0925bc563ed9a1ebcff3cad30f043eceea7eaf2689", size = 219855 }, + { url = "https://files.pythonhosted.org/packages/e5/69/233459ee9eb0c0d10fcc2fe425a029b3fa5ce0f040c966ebce851d030c70/coverage-7.13.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:391ee8f19bef69210978363ca930f7328081c6a0152f1166c91f0b5fdd2a773c", size = 250887 }, + { url = "https://files.pythonhosted.org/packages/06/90/2cdab0974b9b5bbc1623f7876b73603aecac11b8d95b85b5b86b32de5eab/coverage-7.13.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0dd7ab8278f0d58a0128ba2fca25824321f05d059c1441800e934ff2efa52129", size = 253396 }, + { url = "https://files.pythonhosted.org/packages/ac/15/ea4da0f85bf7d7b27635039e649e99deb8173fe551096ea15017f7053537/coverage-7.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78cdf0d578b15148b009ccf18c686aa4f719d887e76e6b40c38ffb61d264a552", size = 254745 }, + { url = "https://files.pythonhosted.org/packages/99/11/bb356e86920c655ca4d61daee4e2bbc7258f0a37de0be32d233b561134ff/coverage-7.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:48685fee12c2eb3b27c62f2658e7ea21e9c3239cba5a8a242801a0a3f6a8c62a", size = 257055 }, + { url = "https://files.pythonhosted.org/packages/c9/0f/9ae1f8cb17029e09da06ca4e28c9e1d5c1c0a511c7074592e37e0836c915/coverage-7.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4e83efc079eb39480e6346a15a1bcb3e9b04759c5202d157e1dd4303cd619356", size = 250911 }, + { url = "https://files.pythonhosted.org/packages/89/3a/adfb68558fa815cbc29747b553bc833d2150228f251b127f1ce97e48547c/coverage-7.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecae9737b72408d6a950f7e525f30aca12d4bd8dd95e37342e5beb3a2a8c4f71", size = 252754 }, + { url = "https://files.pythonhosted.org/packages/32/b1/540d0c27c4e748bd3cd0bd001076ee416eda993c2bae47a73b7cc9357931/coverage-7.13.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ae4578f8528569d3cf303fef2ea569c7f4c4059a38c8667ccef15c6e1f118aa5", size = 250720 }, + { url = "https://files.pythonhosted.org/packages/c7/95/383609462b3ffb1fe133014a7c84fc0dd01ed55ac6140fa1093b5af7ebb1/coverage-7.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6fdef321fdfbb30a197efa02d48fcd9981f0d8ad2ae8903ac318adc653f5df98", size = 254994 }, + { url = "https://files.pythonhosted.org/packages/f7/ba/1761138e86c81680bfc3c49579d66312865457f9fe405b033184e5793cb3/coverage-7.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b0f6ccf3dbe577170bebfce1318707d0e8c3650003cb4b3a9dd744575daa8b5", size = 250531 }, + { url = "https://files.pythonhosted.org/packages/f8/8e/05900df797a9c11837ab59c4d6fe94094e029582aab75c3309a93e6fb4e3/coverage-7.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75fcd519f2a5765db3f0e391eb3b7d150cce1a771bf4c9f861aeab86c767a3c0", size = 252189 }, + { url = "https://files.pythonhosted.org/packages/00/bd/29c9f2db9ea4ed2738b8a9508c35626eb205d51af4ab7bf56a21a2e49926/coverage-7.13.4-cp314-cp314-win32.whl", hash = "sha256:8e798c266c378da2bd819b0677df41ab46d78065fb2a399558f3f6cae78b2fbb", size = 222258 }, + { url = "https://files.pythonhosted.org/packages/a7/4d/1f8e723f6829977410efeb88f73673d794075091c8c7c18848d273dc9d73/coverage-7.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505", size = 223073 }, + { url = "https://files.pythonhosted.org/packages/51/5b/84100025be913b44e082ea32abcf1afbf4e872f5120b7a1cab1d331b1e13/coverage-7.13.4-cp314-cp314-win_arm64.whl", hash = "sha256:ad27098a189e5838900ce4c2a99f2fe42a0bf0c2093c17c69b45a71579e8d4a2", size = 221638 }, + { url = "https://files.pythonhosted.org/packages/a7/e4/c884a405d6ead1370433dad1e3720216b4f9fd8ef5b64bfd984a2a60a11a/coverage-7.13.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:85480adfb35ffc32d40918aad81b89c69c9cc5661a9b8a81476d3e645321a056", size = 220246 }, + { url = "https://files.pythonhosted.org/packages/81/5c/4d7ed8b23b233b0fffbc9dfec53c232be2e695468523242ea9fd30f97ad2/coverage-7.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:79be69cf7f3bf9b0deeeb062eab7ac7f36cd4cc4c4dd694bd28921ba4d8596cc", size = 220514 }, + { url = "https://files.pythonhosted.org/packages/2f/6f/3284d4203fd2f28edd73034968398cd2d4cb04ab192abc8cff007ea35679/coverage-7.13.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:caa421e2684e382c5d8973ac55e4f36bed6821a9bad5c953494de960c74595c9", size = 261877 }, + { url = "https://files.pythonhosted.org/packages/09/aa/b672a647bbe1556a85337dc95bfd40d146e9965ead9cc2fe81bde1e5cbce/coverage-7.13.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14375934243ee05f56c45393fe2ce81fe5cc503c07cee2bdf1725fb8bef3ffaf", size = 264004 }, + { url = "https://files.pythonhosted.org/packages/79/a1/aa384dbe9181f98bba87dd23dda436f0c6cf2e148aecbb4e50fc51c1a656/coverage-7.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25a41c3104d08edb094d9db0d905ca54d0cd41c928bb6be3c4c799a54753af55", size = 266408 }, + { url = "https://files.pythonhosted.org/packages/53/5e/5150bf17b4019bc600799f376bb9606941e55bd5a775dc1e096b6ffea952/coverage-7.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f01afcff62bf9a08fb32b2c1d6e924236c0383c02c790732b6537269e466a72", size = 267544 }, + { url = "https://files.pythonhosted.org/packages/e0/ed/f1de5c675987a4a7a672250d2c5c9d73d289dbf13410f00ed7181d8017dd/coverage-7.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eb9078108fbf0bcdde37c3f4779303673c2fa1fe8f7956e68d447d0dd426d38a", size = 260980 }, + { url = "https://files.pythonhosted.org/packages/b3/e3/fe758d01850aa172419a6743fe76ba8b92c29d181d4f676ffe2dae2ba631/coverage-7.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e086334e8537ddd17e5f16a344777c1ab8194986ec533711cbe6c41cde841b6", size = 263871 }, + { url = "https://files.pythonhosted.org/packages/b6/76/b829869d464115e22499541def9796b25312b8cf235d3bb00b39f1675395/coverage-7.13.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:725d985c5ab621268b2edb8e50dfe57633dc69bda071abc470fed55a14935fd3", size = 261472 }, + { url = "https://files.pythonhosted.org/packages/14/9e/caedb1679e73e2f6ad240173f55218488bfe043e38da577c4ec977489915/coverage-7.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3c06f0f1337c667b971ca2f975523347e63ec5e500b9aa5882d91931cd3ef750", size = 265210 }, + { url = "https://files.pythonhosted.org/packages/3a/10/0dd02cb009b16ede425b49ec344aba13a6ae1dc39600840ea6abcb085ac4/coverage-7.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:590c0ed4bf8e85f745e6b805b2e1c457b2e33d5255dd9729743165253bc9ad39", size = 260319 }, + { url = "https://files.pythonhosted.org/packages/92/8e/234d2c927af27c6d7a5ffad5bd2cf31634c46a477b4c7adfbfa66baf7ebb/coverage-7.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eb30bf180de3f632cd043322dad5751390e5385108b2807368997d1a92a509d0", size = 262638 }, + { url = "https://files.pythonhosted.org/packages/2f/64/e5547c8ff6964e5965c35a480855911b61509cce544f4d442caa759a0702/coverage-7.13.4-cp314-cp314t-win32.whl", hash = "sha256:c4240e7eded42d131a2d2c4dec70374b781b043ddc79a9de4d55ca71f8e98aea", size = 223040 }, + { url = "https://files.pythonhosted.org/packages/c7/96/38086d58a181aac86d503dfa9c47eb20715a79c3e3acbdf786e92e5c09a8/coverage-7.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4c7d3cc01e7350f2f0f6f7036caaf5673fb56b6998889ccfe9e1c1fe75a9c932", size = 224148 }, + { url = "https://files.pythonhosted.org/packages/ce/72/8d10abd3740a0beb98c305e0c3faf454366221c0f37a8bcf8f60020bb65a/coverage-7.13.4-cp314-cp314t-win_arm64.whl", hash = "sha256:23e3f687cf945070d1c90f85db66d11e3025665d8dafa831301a0e0038f3db9b", size = 222172 }, + { url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242 }, ] [package.optional-dependencies] @@ -620,18 +620,22 @@ toml = [ [[package]] name = "cuda-bindings" -version = "12.9.4" +version = "13.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cuda-pathfinder" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/45/e7/b47792cc2d01c7e1d37c32402182524774dadd2d26339bd224e0e913832e/cuda_bindings-12.9.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c912a3d9e6b6651853eed8eed96d6800d69c08e94052c292fec3f282c5a817c9", size = 12210593, upload-time = "2025-10-21T14:51:36.574Z" }, - { url = "https://files.pythonhosted.org/packages/a9/c1/dabe88f52c3e3760d861401bb994df08f672ec893b8f7592dc91626adcf3/cuda_bindings-12.9.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fda147a344e8eaeca0c6ff113d2851ffca8f7dfc0a6c932374ee5c47caa649c8", size = 12151019, upload-time = "2025-10-21T14:51:43.167Z" }, - { url = "https://files.pythonhosted.org/packages/63/56/e465c31dc9111be3441a9ba7df1941fe98f4aa6e71e8788a3fb4534ce24d/cuda_bindings-12.9.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:32bdc5a76906be4c61eb98f546a6786c5773a881f3b166486449b5d141e4a39f", size = 11906628, upload-time = "2025-10-21T14:51:49.905Z" }, - { url = "https://files.pythonhosted.org/packages/a3/84/1e6be415e37478070aeeee5884c2022713c1ecc735e6d82d744de0252eee/cuda_bindings-12.9.4-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56e0043c457a99ac473ddc926fe0dc4046694d99caef633e92601ab52cbe17eb", size = 11925991, upload-time = "2025-10-21T14:51:56.535Z" }, - { url = "https://files.pythonhosted.org/packages/d1/af/6dfd8f2ed90b1d4719bc053ff8940e494640fe4212dc3dd72f383e4992da/cuda_bindings-12.9.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8b72ee72a9cc1b531db31eebaaee5c69a8ec3500e32c6933f2d3b15297b53686", size = 11922703, upload-time = "2025-10-21T14:52:03.585Z" }, - { url = "https://files.pythonhosted.org/packages/6c/19/90ac264acc00f6df8a49378eedec9fd2db3061bf9263bf9f39fd3d8377c3/cuda_bindings-12.9.4-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d80bffc357df9988dca279734bc9674c3934a654cab10cadeed27ce17d8635ee", size = 11924658, upload-time = "2025-10-21T14:52:10.411Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a9/3a8241c6e19483ac1f1dcf5c10238205dcb8a6e9d0d4d4709240dff28ff4/cuda_bindings-13.2.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:721104c603f059780d287969be3d194a18d0cc3b713ed9049065a1107706759d", size = 5730273 }, + { url = "https://files.pythonhosted.org/packages/e9/94/2748597f47bb1600cd466b20cab4159f1530a3a33fe7f70fee199b3abb9e/cuda_bindings-13.2.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1eba9504ac70667dd48313395fe05157518fd6371b532790e96fbb31bbb5a5e1", size = 6313924 }, + { url = "https://files.pythonhosted.org/packages/52/c8/b2589d68acf7e3d63e2be330b84bc25712e97ed799affbca7edd7eae25d6/cuda_bindings-13.2.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e865447abfb83d6a98ad5130ed3c70b1fc295ae3eeee39fd07b4ddb0671b6788", size = 5722404 }, + { url = "https://files.pythonhosted.org/packages/1f/92/f899f7bbb5617bb65ec52a6eac1e9a1447a86b916c4194f8a5001b8cde0c/cuda_bindings-13.2.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46d8776a55d6d5da9dd6e9858fba2efcda2abe6743871dee47dd06eb8cb6d955", size = 6320619 }, + { url = "https://files.pythonhosted.org/packages/df/93/eef988860a3ca985f82c4f3174fc0cdd94e07331ba9a92e8e064c260337f/cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6629ca2df6f795b784752409bcaedbd22a7a651b74b56a165ebc0c9dcbd504d0", size = 5614610 }, + { url = "https://files.pythonhosted.org/packages/18/23/6db3aba46864aee357ab2415135b3fe3da7e9f1fa0221fa2a86a5968099c/cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7dca0da053d3b4cc4869eff49c61c03f3c5dbaa0bcd712317a358d5b8f3f385d", size = 6149914 }, + { url = "https://files.pythonhosted.org/packages/c0/87/87a014f045b77c6de5c8527b0757fe644417b184e5367db977236a141602/cuda_bindings-13.2.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6464b30f46692d6c7f65d4a0e0450d81dd29de3afc1bb515653973d01c2cd6e", size = 5685673 }, + { url = "https://files.pythonhosted.org/packages/ee/5e/c0fe77a73aaefd3fff25ffaccaac69c5a63eafdf8b9a4c476626ef0ac703/cuda_bindings-13.2.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4af9f3e1be603fa12d5ad6cfca7844c9d230befa9792b5abdf7dd79979c3626", size = 6191386 }, + { url = "https://files.pythonhosted.org/packages/5f/58/ed2c3b39c8dd5f96aa7a4abef0d47a73932c7a988e30f5fa428f00ed0da1/cuda_bindings-13.2.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df850a1ff8ce1b3385257b08e47b70e959932f5f432d0a4e46a355962b4e4771", size = 5507469 }, + { url = "https://files.pythonhosted.org/packages/1f/01/0c941b112ceeb21439b05895eace78ca1aa2eaaf695c8521a068fd9b4c00/cuda_bindings-13.2.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8a16384c6494e5485f39314b0b4afb04bee48d49edb16d5d8593fd35bbd231b", size = 6059693 }, ] [[package]] @@ -639,16 +643,59 @@ name = "cuda-pathfinder" version = "1.3.4" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/5e/db279a3bfbd18d59d0598922a3b3c1454908d0969e8372260afec9736376/cuda_pathfinder-1.3.4-py3-none-any.whl", hash = "sha256:fb983f6e0d43af27ef486e14d5989b5f904ef45cedf40538bfdcbffa6bb01fb2", size = 30878, upload-time = "2026-02-11T18:50:31.008Z" }, + { url = "https://files.pythonhosted.org/packages/b8/5e/db279a3bfbd18d59d0598922a3b3c1454908d0969e8372260afec9736376/cuda_pathfinder-1.3.4-py3-none-any.whl", hash = "sha256:fb983f6e0d43af27ef486e14d5989b5f904ef45cedf40538bfdcbffa6bb01fb2", size = 30878 }, +] + +[[package]] +name = "cuda-toolkit" +version = "13.0.2" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/b2/453099f5f3b698d7d0eab38916aac44c7f76229f451709e2eb9db6615dcd/cuda_toolkit-13.0.2-py2.py3-none-any.whl", hash = "sha256:b198824cf2f54003f50d64ada3a0f184b42ca0846c1c94192fa269ecd97a66eb", size = 2364 }, +] + +[package.optional-dependencies] +cublas = [ + { name = "nvidia-cublas", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +cudart = [ + { name = "nvidia-cuda-runtime", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +cufft = [ + { name = "nvidia-cufft", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +cufile = [ + { name = "nvidia-cufile", marker = "sys_platform == 'linux'" }, +] +cupti = [ + { name = "nvidia-cuda-cupti", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +curand = [ + { name = "nvidia-curand", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +cusolver = [ + { name = "nvidia-cusolver", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +cusparse = [ + { name = "nvidia-cusparse", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +nvjitlink = [ + { name = "nvidia-nvjitlink", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +nvrtc = [ + { name = "nvidia-cuda-nvrtc", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +nvtx = [ + { name = "nvidia-nvtx", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, ] [[package]] name = "cycler" version = "0.12.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +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, upload-time = "2023-10-07T05:32:16.783Z" }, + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321 }, ] [[package]] @@ -665,9 +712,9 @@ dependencies = [ { name = "pyyaml" }, { name = "toolz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bd/52/b0f9172b22778def907db1ff173249e4eb41f054b46a9c83b1528aaf811f/dask-2026.1.2.tar.gz", hash = "sha256:1136683de2750d98ea792670f7434e6c1cfce90cab2cc2f2495a9e60fd25a4fc", size = 10997838, upload-time = "2026-01-30T21:04:20.54Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/52/b0f9172b22778def907db1ff173249e4eb41f054b46a9c83b1528aaf811f/dask-2026.1.2.tar.gz", hash = "sha256:1136683de2750d98ea792670f7434e6c1cfce90cab2cc2f2495a9e60fd25a4fc", size = 10997838 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl", hash = "sha256:46a0cf3b8d87f78a3d2e6b145aea4418a6d6d606fe6a16c79bd8ca2bb862bc91", size = 1482084, upload-time = "2026-01-30T21:04:18.363Z" }, + { url = "https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl", hash = "sha256:46a0cf3b8d87f78a3d2e6b145aea4418a6d6d606fe6a16c79bd8ca2bb862bc91", size = 1482084 }, ] [package.optional-dependencies] @@ -679,61 +726,61 @@ array = [ name = "debugpy" version = "1.8.20" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/b7/cd8080344452e4874aae67c40d8940e2b4d47b01601a8fd9f44786c757c7/debugpy-1.8.20.tar.gz", hash = "sha256:55bc8701714969f1ab89a6d5f2f3d40c36f91b2cbe2f65d98bf8196f6a6a2c33", size = 1645207, upload-time = "2026-01-29T23:03:28.199Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/b7/cd8080344452e4874aae67c40d8940e2b4d47b01601a8fd9f44786c757c7/debugpy-1.8.20.tar.gz", hash = "sha256:55bc8701714969f1ab89a6d5f2f3d40c36f91b2cbe2f65d98bf8196f6a6a2c33", size = 1645207 } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/56/c3baf5cbe4dd77427fd9aef99fcdade259ad128feeb8a786c246adb838e5/debugpy-1.8.20-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:eada6042ad88fa1571b74bd5402ee8b86eded7a8f7b827849761700aff171f1b", size = 2208318, upload-time = "2026-01-29T23:03:36.481Z" }, - { url = "https://files.pythonhosted.org/packages/9a/7d/4fa79a57a8e69fe0d9763e98d1110320f9ecd7f1f362572e3aafd7417c9d/debugpy-1.8.20-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:7de0b7dfeedc504421032afba845ae2a7bcc32ddfb07dae2c3ca5442f821c344", size = 3171493, upload-time = "2026-01-29T23:03:37.775Z" }, - { url = "https://files.pythonhosted.org/packages/7d/f2/1e8f8affe51e12a26f3a8a8a4277d6e60aa89d0a66512f63b1e799d424a4/debugpy-1.8.20-cp311-cp311-win32.whl", hash = "sha256:773e839380cf459caf73cc533ea45ec2737a5cc184cf1b3b796cd4fd98504fec", size = 5209240, upload-time = "2026-01-29T23:03:39.109Z" }, - { url = "https://files.pythonhosted.org/packages/d5/92/1cb532e88560cbee973396254b21bece8c5d7c2ece958a67afa08c9f10dc/debugpy-1.8.20-cp311-cp311-win_amd64.whl", hash = "sha256:1f7650546e0eded1902d0f6af28f787fa1f1dbdbc97ddabaf1cd963a405930cb", size = 5233481, upload-time = "2026-01-29T23:03:40.659Z" }, - { url = "https://files.pythonhosted.org/packages/14/57/7f34f4736bfb6e00f2e4c96351b07805d83c9a7b33d28580ae01374430f7/debugpy-1.8.20-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:4ae3135e2089905a916909ef31922b2d733d756f66d87345b3e5e52b7a55f13d", size = 2550686, upload-time = "2026-01-29T23:03:42.023Z" }, - { url = "https://files.pythonhosted.org/packages/ab/78/b193a3975ca34458f6f0e24aaf5c3e3da72f5401f6054c0dfd004b41726f/debugpy-1.8.20-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:88f47850a4284b88bd2bfee1f26132147d5d504e4e86c22485dfa44b97e19b4b", size = 4310588, upload-time = "2026-01-29T23:03:43.314Z" }, - { url = "https://files.pythonhosted.org/packages/c1/55/f14deb95eaf4f30f07ef4b90a8590fc05d9e04df85ee379712f6fb6736d7/debugpy-1.8.20-cp312-cp312-win32.whl", hash = "sha256:4057ac68f892064e5f98209ab582abfee3b543fb55d2e87610ddc133a954d390", size = 5331372, upload-time = "2026-01-29T23:03:45.526Z" }, - { url = "https://files.pythonhosted.org/packages/a1/39/2bef246368bd42f9bd7cba99844542b74b84dacbdbea0833e610f384fee8/debugpy-1.8.20-cp312-cp312-win_amd64.whl", hash = "sha256:a1a8f851e7cf171330679ef6997e9c579ef6dd33c9098458bd9986a0f4ca52e3", size = 5372835, upload-time = "2026-01-29T23:03:47.245Z" }, - { url = "https://files.pythonhosted.org/packages/15/e2/fc500524cc6f104a9d049abc85a0a8b3f0d14c0a39b9c140511c61e5b40b/debugpy-1.8.20-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:5dff4bb27027821fdfcc9e8f87309a28988231165147c31730128b1c983e282a", size = 2539560, upload-time = "2026-01-29T23:03:48.738Z" }, - { url = "https://files.pythonhosted.org/packages/90/83/fb33dcea789ed6018f8da20c5a9bc9d82adc65c0c990faed43f7c955da46/debugpy-1.8.20-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:84562982dd7cf5ebebfdea667ca20a064e096099997b175fe204e86817f64eaf", size = 4293272, upload-time = "2026-01-29T23:03:50.169Z" }, - { url = "https://files.pythonhosted.org/packages/a6/25/b1e4a01bfb824d79a6af24b99ef291e24189080c93576dfd9b1a2815cd0f/debugpy-1.8.20-cp313-cp313-win32.whl", hash = "sha256:da11dea6447b2cadbf8ce2bec59ecea87cc18d2c574980f643f2d2dfe4862393", size = 5331208, upload-time = "2026-01-29T23:03:51.547Z" }, - { url = "https://files.pythonhosted.org/packages/13/f7/a0b368ce54ffff9e9028c098bd2d28cfc5b54f9f6c186929083d4c60ba58/debugpy-1.8.20-cp313-cp313-win_amd64.whl", hash = "sha256:eb506e45943cab2efb7c6eafdd65b842f3ae779f020c82221f55aca9de135ed7", size = 5372930, upload-time = "2026-01-29T23:03:53.585Z" }, - { url = "https://files.pythonhosted.org/packages/33/2e/f6cb9a8a13f5058f0a20fe09711a7b726232cd5a78c6a7c05b2ec726cff9/debugpy-1.8.20-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:9c74df62fc064cd5e5eaca1353a3ef5a5d50da5eb8058fcef63106f7bebe6173", size = 2538066, upload-time = "2026-01-29T23:03:54.999Z" }, - { url = "https://files.pythonhosted.org/packages/c5/56/6ddca50b53624e1ca3ce1d1e49ff22db46c47ea5fb4c0cc5c9b90a616364/debugpy-1.8.20-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:077a7447589ee9bc1ff0cdf443566d0ecf540ac8aa7333b775ebcb8ce9f4ecad", size = 4269425, upload-time = "2026-01-29T23:03:56.518Z" }, - { url = "https://files.pythonhosted.org/packages/c5/d9/d64199c14a0d4c476df46c82470a3ce45c8d183a6796cfb5e66533b3663c/debugpy-1.8.20-cp314-cp314-win32.whl", hash = "sha256:352036a99dd35053b37b7803f748efc456076f929c6a895556932eaf2d23b07f", size = 5331407, upload-time = "2026-01-29T23:03:58.481Z" }, - { url = "https://files.pythonhosted.org/packages/e0/d9/1f07395b54413432624d61524dfd98c1a7c7827d2abfdb8829ac92638205/debugpy-1.8.20-cp314-cp314-win_amd64.whl", hash = "sha256:a98eec61135465b062846112e5ecf2eebb855305acc1dfbae43b72903b8ab5be", size = 5372521, upload-time = "2026-01-29T23:03:59.864Z" }, - { url = "https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl", hash = "sha256:5be9bed9ae3be00665a06acaa48f8329d2b9632f15fd09f6a9a8c8d9907e54d7", size = 5337658, upload-time = "2026-01-29T23:04:17.404Z" }, + { url = "https://files.pythonhosted.org/packages/51/56/c3baf5cbe4dd77427fd9aef99fcdade259ad128feeb8a786c246adb838e5/debugpy-1.8.20-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:eada6042ad88fa1571b74bd5402ee8b86eded7a8f7b827849761700aff171f1b", size = 2208318 }, + { url = "https://files.pythonhosted.org/packages/9a/7d/4fa79a57a8e69fe0d9763e98d1110320f9ecd7f1f362572e3aafd7417c9d/debugpy-1.8.20-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:7de0b7dfeedc504421032afba845ae2a7bcc32ddfb07dae2c3ca5442f821c344", size = 3171493 }, + { url = "https://files.pythonhosted.org/packages/7d/f2/1e8f8affe51e12a26f3a8a8a4277d6e60aa89d0a66512f63b1e799d424a4/debugpy-1.8.20-cp311-cp311-win32.whl", hash = "sha256:773e839380cf459caf73cc533ea45ec2737a5cc184cf1b3b796cd4fd98504fec", size = 5209240 }, + { url = "https://files.pythonhosted.org/packages/d5/92/1cb532e88560cbee973396254b21bece8c5d7c2ece958a67afa08c9f10dc/debugpy-1.8.20-cp311-cp311-win_amd64.whl", hash = "sha256:1f7650546e0eded1902d0f6af28f787fa1f1dbdbc97ddabaf1cd963a405930cb", size = 5233481 }, + { url = "https://files.pythonhosted.org/packages/14/57/7f34f4736bfb6e00f2e4c96351b07805d83c9a7b33d28580ae01374430f7/debugpy-1.8.20-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:4ae3135e2089905a916909ef31922b2d733d756f66d87345b3e5e52b7a55f13d", size = 2550686 }, + { url = "https://files.pythonhosted.org/packages/ab/78/b193a3975ca34458f6f0e24aaf5c3e3da72f5401f6054c0dfd004b41726f/debugpy-1.8.20-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:88f47850a4284b88bd2bfee1f26132147d5d504e4e86c22485dfa44b97e19b4b", size = 4310588 }, + { url = "https://files.pythonhosted.org/packages/c1/55/f14deb95eaf4f30f07ef4b90a8590fc05d9e04df85ee379712f6fb6736d7/debugpy-1.8.20-cp312-cp312-win32.whl", hash = "sha256:4057ac68f892064e5f98209ab582abfee3b543fb55d2e87610ddc133a954d390", size = 5331372 }, + { url = "https://files.pythonhosted.org/packages/a1/39/2bef246368bd42f9bd7cba99844542b74b84dacbdbea0833e610f384fee8/debugpy-1.8.20-cp312-cp312-win_amd64.whl", hash = "sha256:a1a8f851e7cf171330679ef6997e9c579ef6dd33c9098458bd9986a0f4ca52e3", size = 5372835 }, + { url = "https://files.pythonhosted.org/packages/15/e2/fc500524cc6f104a9d049abc85a0a8b3f0d14c0a39b9c140511c61e5b40b/debugpy-1.8.20-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:5dff4bb27027821fdfcc9e8f87309a28988231165147c31730128b1c983e282a", size = 2539560 }, + { url = "https://files.pythonhosted.org/packages/90/83/fb33dcea789ed6018f8da20c5a9bc9d82adc65c0c990faed43f7c955da46/debugpy-1.8.20-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:84562982dd7cf5ebebfdea667ca20a064e096099997b175fe204e86817f64eaf", size = 4293272 }, + { url = "https://files.pythonhosted.org/packages/a6/25/b1e4a01bfb824d79a6af24b99ef291e24189080c93576dfd9b1a2815cd0f/debugpy-1.8.20-cp313-cp313-win32.whl", hash = "sha256:da11dea6447b2cadbf8ce2bec59ecea87cc18d2c574980f643f2d2dfe4862393", size = 5331208 }, + { url = "https://files.pythonhosted.org/packages/13/f7/a0b368ce54ffff9e9028c098bd2d28cfc5b54f9f6c186929083d4c60ba58/debugpy-1.8.20-cp313-cp313-win_amd64.whl", hash = "sha256:eb506e45943cab2efb7c6eafdd65b842f3ae779f020c82221f55aca9de135ed7", size = 5372930 }, + { url = "https://files.pythonhosted.org/packages/33/2e/f6cb9a8a13f5058f0a20fe09711a7b726232cd5a78c6a7c05b2ec726cff9/debugpy-1.8.20-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:9c74df62fc064cd5e5eaca1353a3ef5a5d50da5eb8058fcef63106f7bebe6173", size = 2538066 }, + { url = "https://files.pythonhosted.org/packages/c5/56/6ddca50b53624e1ca3ce1d1e49ff22db46c47ea5fb4c0cc5c9b90a616364/debugpy-1.8.20-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:077a7447589ee9bc1ff0cdf443566d0ecf540ac8aa7333b775ebcb8ce9f4ecad", size = 4269425 }, + { url = "https://files.pythonhosted.org/packages/c5/d9/d64199c14a0d4c476df46c82470a3ce45c8d183a6796cfb5e66533b3663c/debugpy-1.8.20-cp314-cp314-win32.whl", hash = "sha256:352036a99dd35053b37b7803f748efc456076f929c6a895556932eaf2d23b07f", size = 5331407 }, + { url = "https://files.pythonhosted.org/packages/e0/d9/1f07395b54413432624d61524dfd98c1a7c7827d2abfdb8829ac92638205/debugpy-1.8.20-cp314-cp314-win_amd64.whl", hash = "sha256:a98eec61135465b062846112e5ecf2eebb855305acc1dfbae43b72903b8ab5be", size = 5372521 }, + { url = "https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl", hash = "sha256:5be9bed9ae3be00665a06acaa48f8329d2b9632f15fd09f6a9a8c8d9907e54d7", size = 5337658 }, ] [[package]] name = "decorator" version = "5.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190 }, ] [[package]] name = "defusedxml" version = "0.7.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520 } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604 }, ] [[package]] name = "dill" version = "0.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/81/e1/56027a71e31b02ddc53c7d65b01e68edf64dea2932122fe7746a516f75d5/dill-0.4.1.tar.gz", hash = "sha256:423092df4182177d4d8ba8290c8a5b640c66ab35ec7da59ccfa00f6fa3eea5fa", size = 187315, upload-time = "2026-01-19T02:36:56.85Z" } +sdist = { url = "https://files.pythonhosted.org/packages/81/e1/56027a71e31b02ddc53c7d65b01e68edf64dea2932122fe7746a516f75d5/dill-0.4.1.tar.gz", hash = "sha256:423092df4182177d4d8ba8290c8a5b640c66ab35ec7da59ccfa00f6fa3eea5fa", size = 187315 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d", size = 120019, upload-time = "2026-01-19T02:36:55.663Z" }, + { url = "https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d", size = 120019 }, ] [[package]] name = "distlib" version = "0.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605 } wheels = [ - { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047 }, ] [[package]] @@ -743,36 +790,36 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/25/71/80cc718ff6d7abfbabacb1f57aaa42e9c1552bfdd01e64ddd704e4a03638/donfig-0.8.1.post1.tar.gz", hash = "sha256:3bef3413a4c1c601b585e8d297256d0c1470ea012afa6e8461dc28bfb7c23f52", size = 19506, upload-time = "2024-05-23T14:14:31.513Z" } +sdist = { url = "https://files.pythonhosted.org/packages/25/71/80cc718ff6d7abfbabacb1f57aaa42e9c1552bfdd01e64ddd704e4a03638/donfig-0.8.1.post1.tar.gz", hash = "sha256:3bef3413a4c1c601b585e8d297256d0c1470ea012afa6e8461dc28bfb7c23f52", size = 19506 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/d5/c5db1ea3394c6e1732fb3286b3bd878b59507a8f77d32a2cebda7d7b7cd4/donfig-0.8.1.post1-py3-none-any.whl", hash = "sha256:2a3175ce74a06109ff9307d90a230f81215cbac9a751f4d1c6194644b8204f9d", size = 21592, upload-time = "2024-05-23T14:13:55.283Z" }, + { url = "https://files.pythonhosted.org/packages/0c/d5/c5db1ea3394c6e1732fb3286b3bd878b59507a8f77d32a2cebda7d7b7cd4/donfig-0.8.1.post1-py3-none-any.whl", hash = "sha256:2a3175ce74a06109ff9307d90a230f81215cbac9a751f4d1c6194644b8204f9d", size = 21592 }, ] [[package]] name = "executing" version = "2.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317 }, ] [[package]] name = "fastjsonschema" version = "2.21.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/b5/23b216d9d985a956623b6bd12d4086b60f0059b27799f23016af04a74ea1/fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de", size = 374130, upload-time = "2025-08-14T18:49:36.666Z" } +sdist = { url = "https://files.pythonhosted.org/packages/20/b5/23b216d9d985a956623b6bd12d4086b60f0059b27799f23016af04a74ea1/fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de", size = 374130 } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463", size = 24024, upload-time = "2025-08-14T18:49:34.776Z" }, + { url = "https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463", size = 24024 }, ] [[package]] name = "filelock" version = "3.24.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/73/92/a8e2479937ff39185d20dd6a851c1a63e55849e447a55e798cc2e1f49c65/filelock-3.24.3.tar.gz", hash = "sha256:011a5644dc937c22699943ebbfc46e969cdde3e171470a6e40b9533e5a72affa", size = 37935, upload-time = "2026-02-19T00:48:20.543Z" } +sdist = { url = "https://files.pythonhosted.org/packages/73/92/a8e2479937ff39185d20dd6a851c1a63e55849e447a55e798cc2e1f49c65/filelock-3.24.3.tar.gz", hash = "sha256:011a5644dc937c22699943ebbfc46e969cdde3e171470a6e40b9533e5a72affa", size = 37935 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/0f/5d0c71a1aefeb08efff26272149e07ab922b64f46c63363756224bd6872e/filelock-3.24.3-py3-none-any.whl", hash = "sha256:426e9a4660391f7f8a810d71b0555bce9008b0a1cc342ab1f6947d37639e002d", size = 24331, upload-time = "2026-02-19T00:48:18.465Z" }, + { url = "https://files.pythonhosted.org/packages/9c/0f/5d0c71a1aefeb08efff26272149e07ab922b64f46c63363756224bd6872e/filelock-3.24.3-py3-none-any.whl", hash = "sha256:426e9a4660391f7f8a810d71b0555bce9008b0a1cc342ab1f6947d37639e002d", size = 24331 }, ] [[package]] @@ -782,9 +829,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/55/b0/8a21e330561c65653d010ef112bf38f60890051d244ede197ddaa08e50c1/flexcache-0.3.tar.gz", hash = "sha256:18743bd5a0621bfe2cf8d519e4c3bfdf57a269c15d1ced3fb4b64e0ff4600656", size = 15816, upload-time = "2024-03-09T03:21:07.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/b0/8a21e330561c65653d010ef112bf38f60890051d244ede197ddaa08e50c1/flexcache-0.3.tar.gz", hash = "sha256:18743bd5a0621bfe2cf8d519e4c3bfdf57a269c15d1ced3fb4b64e0ff4600656", size = 15816 } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/cd/c883e1a7c447479d6e13985565080e3fea88ab5a107c21684c813dba1875/flexcache-0.3-py3-none-any.whl", hash = "sha256:d43c9fea82336af6e0115e308d9d33a185390b8346a017564611f1466dcd2e32", size = 13263, upload-time = "2024-03-09T03:21:05.635Z" }, + { url = "https://files.pythonhosted.org/packages/27/cd/c883e1a7c447479d6e13985565080e3fea88ab5a107c21684c813dba1875/flexcache-0.3-py3-none-any.whl", hash = "sha256:d43c9fea82336af6e0115e308d9d33a185390b8346a017564611f1466dcd2e32", size = 13263 }, ] [[package]] @@ -794,153 +841,158 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/82/99/b4de7e39e8eaf8207ba1a8fa2241dd98b2ba72ae6e16960d8351736d8702/flexparser-0.4.tar.gz", hash = "sha256:266d98905595be2ccc5da964fe0a2c3526fbbffdc45b65b3146d75db992ef6b2", size = 31799, upload-time = "2024-11-07T02:00:56.249Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/99/b4de7e39e8eaf8207ba1a8fa2241dd98b2ba72ae6e16960d8351736d8702/flexparser-0.4.tar.gz", hash = "sha256:266d98905595be2ccc5da964fe0a2c3526fbbffdc45b65b3146d75db992ef6b2", size = 31799 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/5e/3be305568fe5f34448807976dc82fc151d76c3e0e03958f34770286278c1/flexparser-0.4-py3-none-any.whl", hash = "sha256:3738b456192dcb3e15620f324c447721023c0293f6af9955b481e91d00179846", size = 27625, upload-time = "2024-11-07T02:00:54.523Z" }, + { url = "https://files.pythonhosted.org/packages/fe/5e/3be305568fe5f34448807976dc82fc151d76c3e0e03958f34770286278c1/flexparser-0.4-py3-none-any.whl", hash = "sha256:3738b456192dcb3e15620f324c447721023c0293f6af9955b481e91d00179846", size = 27625 }, ] [[package]] name = "fonttools" version = "4.61.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69", size = 3565756, upload-time = "2025-12-12T17:31:24.246Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/69/12/bf9f4eaa2fad039356cc627587e30ed008c03f1cebd3034376b5ee8d1d44/fonttools-4.61.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09", size = 2852213, upload-time = "2025-12-12T17:29:46.675Z" }, - { url = "https://files.pythonhosted.org/packages/ac/49/4138d1acb6261499bedde1c07f8c2605d1d8f9d77a151e5507fd3ef084b6/fonttools-4.61.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37", size = 2401689, upload-time = "2025-12-12T17:29:48.769Z" }, - { url = "https://files.pythonhosted.org/packages/e5/fe/e6ce0fe20a40e03aef906af60aa87668696f9e4802fa283627d0b5ed777f/fonttools-4.61.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77efb033d8d7ff233385f30c62c7c79271c8885d5c9657d967ede124671bbdfb", size = 5058809, upload-time = "2025-12-12T17:29:51.701Z" }, - { url = "https://files.pythonhosted.org/packages/79/61/1ca198af22f7dd22c17ab86e9024ed3c06299cfdb08170640e9996d501a0/fonttools-4.61.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9", size = 5036039, upload-time = "2025-12-12T17:29:53.659Z" }, - { url = "https://files.pythonhosted.org/packages/99/cc/fa1801e408586b5fce4da9f5455af8d770f4fc57391cd5da7256bb364d38/fonttools-4.61.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0de30bfe7745c0d1ffa2b0b7048fb7123ad0d71107e10ee090fa0b16b9452e87", size = 5034714, upload-time = "2025-12-12T17:29:55.592Z" }, - { url = "https://files.pythonhosted.org/packages/bf/aa/b7aeafe65adb1b0a925f8f25725e09f078c635bc22754f3fecb7456955b0/fonttools-4.61.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58b0ee0ab5b1fc9921eccfe11d1435added19d6494dde14e323f25ad2bc30c56", size = 5158648, upload-time = "2025-12-12T17:29:57.861Z" }, - { url = "https://files.pythonhosted.org/packages/99/f9/08ea7a38663328881384c6e7777bbefc46fd7d282adfd87a7d2b84ec9d50/fonttools-4.61.1-cp311-cp311-win32.whl", hash = "sha256:f79b168428351d11e10c5aeb61a74e1851ec221081299f4cf56036a95431c43a", size = 2280681, upload-time = "2025-12-12T17:29:59.943Z" }, - { url = "https://files.pythonhosted.org/packages/07/ad/37dd1ae5fa6e01612a1fbb954f0927681f282925a86e86198ccd7b15d515/fonttools-4.61.1-cp311-cp311-win_amd64.whl", hash = "sha256:fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7", size = 2331951, upload-time = "2025-12-12T17:30:02.254Z" }, - { url = "https://files.pythonhosted.org/packages/6f/16/7decaa24a1bd3a70c607b2e29f0adc6159f36a7e40eaba59846414765fd4/fonttools-4.61.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e", size = 2851593, upload-time = "2025-12-12T17:30:04.225Z" }, - { url = "https://files.pythonhosted.org/packages/94/98/3c4cb97c64713a8cf499b3245c3bf9a2b8fd16a3e375feff2aed78f96259/fonttools-4.61.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2", size = 2400231, upload-time = "2025-12-12T17:30:06.47Z" }, - { url = "https://files.pythonhosted.org/packages/b7/37/82dbef0f6342eb01f54bca073ac1498433d6ce71e50c3c3282b655733b31/fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796", size = 4954103, upload-time = "2025-12-12T17:30:08.432Z" }, - { url = "https://files.pythonhosted.org/packages/6c/44/f3aeac0fa98e7ad527f479e161aca6c3a1e47bb6996b053d45226fe37bf2/fonttools-4.61.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d", size = 5004295, upload-time = "2025-12-12T17:30:10.56Z" }, - { url = "https://files.pythonhosted.org/packages/14/e8/7424ced75473983b964d09f6747fa09f054a6d656f60e9ac9324cf40c743/fonttools-4.61.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8", size = 4944109, upload-time = "2025-12-12T17:30:12.874Z" }, - { url = "https://files.pythonhosted.org/packages/c8/8b/6391b257fa3d0b553d73e778f953a2f0154292a7a7a085e2374b111e5410/fonttools-4.61.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0", size = 5093598, upload-time = "2025-12-12T17:30:15.79Z" }, - { url = "https://files.pythonhosted.org/packages/d9/71/fd2ea96cdc512d92da5678a1c98c267ddd4d8c5130b76d0f7a80f9a9fde8/fonttools-4.61.1-cp312-cp312-win32.whl", hash = "sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261", size = 2269060, upload-time = "2025-12-12T17:30:18.058Z" }, - { url = "https://files.pythonhosted.org/packages/80/3b/a3e81b71aed5a688e89dfe0e2694b26b78c7d7f39a5ffd8a7d75f54a12a8/fonttools-4.61.1-cp312-cp312-win_amd64.whl", hash = "sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9", size = 2319078, upload-time = "2025-12-12T17:30:22.862Z" }, - { url = "https://files.pythonhosted.org/packages/4b/cf/00ba28b0990982530addb8dc3e9e6f2fa9cb5c20df2abdda7baa755e8fe1/fonttools-4.61.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c56c488ab471628ff3bfa80964372fc13504ece601e0d97a78ee74126b2045c", size = 2846454, upload-time = "2025-12-12T17:30:24.938Z" }, - { url = "https://files.pythonhosted.org/packages/5a/ca/468c9a8446a2103ae645d14fee3f610567b7042aba85031c1c65e3ef7471/fonttools-4.61.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc492779501fa723b04d0ab1f5be046797fee17d27700476edc7ee9ae535a61e", size = 2398191, upload-time = "2025-12-12T17:30:27.343Z" }, - { url = "https://files.pythonhosted.org/packages/a3/4b/d67eedaed19def5967fade3297fed8161b25ba94699efc124b14fb68cdbc/fonttools-4.61.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:64102ca87e84261419c3747a0d20f396eb024bdbeb04c2bfb37e2891f5fadcb5", size = 4928410, upload-time = "2025-12-12T17:30:29.771Z" }, - { url = "https://files.pythonhosted.org/packages/b0/8d/6fb3494dfe61a46258cd93d979cf4725ded4eb46c2a4ca35e4490d84daea/fonttools-4.61.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c1b526c8d3f615a7b1867f38a9410849c8f4aef078535742198e942fba0e9bd", size = 4984460, upload-time = "2025-12-12T17:30:32.073Z" }, - { url = "https://files.pythonhosted.org/packages/f7/f1/a47f1d30b3dc00d75e7af762652d4cbc3dff5c2697a0dbd5203c81afd9c3/fonttools-4.61.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:41ed4b5ec103bd306bb68f81dc166e77409e5209443e5773cb4ed837bcc9b0d3", size = 4925800, upload-time = "2025-12-12T17:30:34.339Z" }, - { url = "https://files.pythonhosted.org/packages/a7/01/e6ae64a0981076e8a66906fab01539799546181e32a37a0257b77e4aa88b/fonttools-4.61.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b501c862d4901792adaec7c25b1ecc749e2662543f68bb194c42ba18d6eec98d", size = 5067859, upload-time = "2025-12-12T17:30:36.593Z" }, - { url = "https://files.pythonhosted.org/packages/73/aa/28e40b8d6809a9b5075350a86779163f074d2b617c15d22343fce81918db/fonttools-4.61.1-cp313-cp313-win32.whl", hash = "sha256:4d7092bb38c53bbc78e9255a59158b150bcdc115a1e3b3ce0b5f267dc35dd63c", size = 2267821, upload-time = "2025-12-12T17:30:38.478Z" }, - { url = "https://files.pythonhosted.org/packages/1a/59/453c06d1d83dc0951b69ef692d6b9f1846680342927df54e9a1ca91c6f90/fonttools-4.61.1-cp313-cp313-win_amd64.whl", hash = "sha256:21e7c8d76f62ab13c9472ccf74515ca5b9a761d1bde3265152a6dc58700d895b", size = 2318169, upload-time = "2025-12-12T17:30:40.951Z" }, - { url = "https://files.pythonhosted.org/packages/32/8f/4e7bf82c0cbb738d3c2206c920ca34ca74ef9dabde779030145d28665104/fonttools-4.61.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fff4f534200a04b4a36e7ae3cb74493afe807b517a09e99cb4faa89a34ed6ecd", size = 2846094, upload-time = "2025-12-12T17:30:43.511Z" }, - { url = "https://files.pythonhosted.org/packages/71/09/d44e45d0a4f3a651f23a1e9d42de43bc643cce2971b19e784cc67d823676/fonttools-4.61.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d9203500f7c63545b4ce3799319fe4d9feb1a1b89b28d3cb5abd11b9dd64147e", size = 2396589, upload-time = "2025-12-12T17:30:45.681Z" }, - { url = "https://files.pythonhosted.org/packages/89/18/58c64cafcf8eb677a99ef593121f719e6dcbdb7d1c594ae5a10d4997ca8a/fonttools-4.61.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa646ecec9528bef693415c79a86e733c70a4965dd938e9a226b0fc64c9d2e6c", size = 4877892, upload-time = "2025-12-12T17:30:47.709Z" }, - { url = "https://files.pythonhosted.org/packages/8a/ec/9e6b38c7ba1e09eb51db849d5450f4c05b7e78481f662c3b79dbde6f3d04/fonttools-4.61.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f35ad7805edba3aac1a3710d104592df59f4b957e30108ae0ba6c10b11dd75", size = 4972884, upload-time = "2025-12-12T17:30:49.656Z" }, - { url = "https://files.pythonhosted.org/packages/5e/87/b5339da8e0256734ba0dbbf5b6cdebb1dd79b01dc8c270989b7bcd465541/fonttools-4.61.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b931ae8f62db78861b0ff1ac017851764602288575d65b8e8ff1963fed419063", size = 4924405, upload-time = "2025-12-12T17:30:51.735Z" }, - { url = "https://files.pythonhosted.org/packages/0b/47/e3409f1e1e69c073a3a6fd8cb886eb18c0bae0ee13db2c8d5e7f8495e8b7/fonttools-4.61.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b148b56f5de675ee16d45e769e69f87623a4944f7443850bf9a9376e628a89d2", size = 5035553, upload-time = "2025-12-12T17:30:54.823Z" }, - { url = "https://files.pythonhosted.org/packages/bf/b6/1f6600161b1073a984294c6c031e1a56ebf95b6164249eecf30012bb2e38/fonttools-4.61.1-cp314-cp314-win32.whl", hash = "sha256:9b666a475a65f4e839d3d10473fad6d47e0a9db14a2f4a224029c5bfde58ad2c", size = 2271915, upload-time = "2025-12-12T17:30:57.913Z" }, - { url = "https://files.pythonhosted.org/packages/52/7b/91e7b01e37cc8eb0e1f770d08305b3655e4f002fc160fb82b3390eabacf5/fonttools-4.61.1-cp314-cp314-win_amd64.whl", hash = "sha256:4f5686e1fe5fce75d82d93c47a438a25bf0d1319d2843a926f741140b2b16e0c", size = 2323487, upload-time = "2025-12-12T17:30:59.804Z" }, - { url = "https://files.pythonhosted.org/packages/39/5c/908ad78e46c61c3e3ed70c3b58ff82ab48437faf84ec84f109592cabbd9f/fonttools-4.61.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e76ce097e3c57c4bcb67c5aa24a0ecdbd9f74ea9219997a707a4061fbe2707aa", size = 2929571, upload-time = "2025-12-12T17:31:02.574Z" }, - { url = "https://files.pythonhosted.org/packages/bd/41/975804132c6dea64cdbfbaa59f3518a21c137a10cccf962805b301ac6ab2/fonttools-4.61.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9cfef3ab326780c04d6646f68d4b4742aae222e8b8ea1d627c74e38afcbc9d91", size = 2435317, upload-time = "2025-12-12T17:31:04.974Z" }, - { url = "https://files.pythonhosted.org/packages/b0/5a/aef2a0a8daf1ebaae4cfd83f84186d4a72ee08fd6a8451289fcd03ffa8a4/fonttools-4.61.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a75c301f96db737e1c5ed5fd7d77d9c34466de16095a266509e13da09751bd19", size = 4882124, upload-time = "2025-12-12T17:31:07.456Z" }, - { url = "https://files.pythonhosted.org/packages/80/33/d6db3485b645b81cea538c9d1c9219d5805f0877fda18777add4671c5240/fonttools-4.61.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91669ccac46bbc1d09e9273546181919064e8df73488ea087dcac3e2968df9ba", size = 5100391, upload-time = "2025-12-12T17:31:09.732Z" }, - { url = "https://files.pythonhosted.org/packages/6c/d6/675ba631454043c75fcf76f0ca5463eac8eb0666ea1d7badae5fea001155/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c33ab3ca9d3ccd581d58e989d67554e42d8d4ded94ab3ade3508455fe70e65f7", size = 4978800, upload-time = "2025-12-12T17:31:11.681Z" }, - { url = "https://files.pythonhosted.org/packages/7f/33/d3ec753d547a8d2bdaedd390d4a814e8d5b45a093d558f025c6b990b554c/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:664c5a68ec406f6b1547946683008576ef8b38275608e1cee6c061828171c118", size = 5006426, upload-time = "2025-12-12T17:31:13.764Z" }, - { url = "https://files.pythonhosted.org/packages/b4/40/cc11f378b561a67bea850ab50063366a0d1dd3f6d0a30ce0f874b0ad5664/fonttools-4.61.1-cp314-cp314t-win32.whl", hash = "sha256:aed04cabe26f30c1647ef0e8fbb207516fd40fe9472e9439695f5c6998e60ac5", size = 2335377, upload-time = "2025-12-12T17:31:16.49Z" }, - { url = "https://files.pythonhosted.org/packages/e4/ff/c9a2b66b39f8628531ea58b320d66d951267c98c6a38684daa8f50fb02f8/fonttools-4.61.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2180f14c141d2f0f3da43f3a81bc8aa4684860f6b0e6f9e165a4831f24e6a23b", size = 2400613, upload-time = "2025-12-12T17:31:18.769Z" }, - { url = "https://files.pythonhosted.org/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371", size = 1148996, upload-time = "2025-12-12T17:31:21.03Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69", size = 3565756 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/12/bf9f4eaa2fad039356cc627587e30ed008c03f1cebd3034376b5ee8d1d44/fonttools-4.61.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09", size = 2852213 }, + { url = "https://files.pythonhosted.org/packages/ac/49/4138d1acb6261499bedde1c07f8c2605d1d8f9d77a151e5507fd3ef084b6/fonttools-4.61.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37", size = 2401689 }, + { url = "https://files.pythonhosted.org/packages/e5/fe/e6ce0fe20a40e03aef906af60aa87668696f9e4802fa283627d0b5ed777f/fonttools-4.61.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77efb033d8d7ff233385f30c62c7c79271c8885d5c9657d967ede124671bbdfb", size = 5058809 }, + { url = "https://files.pythonhosted.org/packages/79/61/1ca198af22f7dd22c17ab86e9024ed3c06299cfdb08170640e9996d501a0/fonttools-4.61.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9", size = 5036039 }, + { url = "https://files.pythonhosted.org/packages/99/cc/fa1801e408586b5fce4da9f5455af8d770f4fc57391cd5da7256bb364d38/fonttools-4.61.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0de30bfe7745c0d1ffa2b0b7048fb7123ad0d71107e10ee090fa0b16b9452e87", size = 5034714 }, + { url = "https://files.pythonhosted.org/packages/bf/aa/b7aeafe65adb1b0a925f8f25725e09f078c635bc22754f3fecb7456955b0/fonttools-4.61.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58b0ee0ab5b1fc9921eccfe11d1435added19d6494dde14e323f25ad2bc30c56", size = 5158648 }, + { url = "https://files.pythonhosted.org/packages/99/f9/08ea7a38663328881384c6e7777bbefc46fd7d282adfd87a7d2b84ec9d50/fonttools-4.61.1-cp311-cp311-win32.whl", hash = "sha256:f79b168428351d11e10c5aeb61a74e1851ec221081299f4cf56036a95431c43a", size = 2280681 }, + { url = "https://files.pythonhosted.org/packages/07/ad/37dd1ae5fa6e01612a1fbb954f0927681f282925a86e86198ccd7b15d515/fonttools-4.61.1-cp311-cp311-win_amd64.whl", hash = "sha256:fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7", size = 2331951 }, + { url = "https://files.pythonhosted.org/packages/6f/16/7decaa24a1bd3a70c607b2e29f0adc6159f36a7e40eaba59846414765fd4/fonttools-4.61.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e", size = 2851593 }, + { url = "https://files.pythonhosted.org/packages/94/98/3c4cb97c64713a8cf499b3245c3bf9a2b8fd16a3e375feff2aed78f96259/fonttools-4.61.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2", size = 2400231 }, + { url = "https://files.pythonhosted.org/packages/b7/37/82dbef0f6342eb01f54bca073ac1498433d6ce71e50c3c3282b655733b31/fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796", size = 4954103 }, + { url = "https://files.pythonhosted.org/packages/6c/44/f3aeac0fa98e7ad527f479e161aca6c3a1e47bb6996b053d45226fe37bf2/fonttools-4.61.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d", size = 5004295 }, + { url = "https://files.pythonhosted.org/packages/14/e8/7424ced75473983b964d09f6747fa09f054a6d656f60e9ac9324cf40c743/fonttools-4.61.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8", size = 4944109 }, + { url = "https://files.pythonhosted.org/packages/c8/8b/6391b257fa3d0b553d73e778f953a2f0154292a7a7a085e2374b111e5410/fonttools-4.61.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0", size = 5093598 }, + { url = "https://files.pythonhosted.org/packages/d9/71/fd2ea96cdc512d92da5678a1c98c267ddd4d8c5130b76d0f7a80f9a9fde8/fonttools-4.61.1-cp312-cp312-win32.whl", hash = "sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261", size = 2269060 }, + { url = "https://files.pythonhosted.org/packages/80/3b/a3e81b71aed5a688e89dfe0e2694b26b78c7d7f39a5ffd8a7d75f54a12a8/fonttools-4.61.1-cp312-cp312-win_amd64.whl", hash = "sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9", size = 2319078 }, + { url = "https://files.pythonhosted.org/packages/4b/cf/00ba28b0990982530addb8dc3e9e6f2fa9cb5c20df2abdda7baa755e8fe1/fonttools-4.61.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c56c488ab471628ff3bfa80964372fc13504ece601e0d97a78ee74126b2045c", size = 2846454 }, + { url = "https://files.pythonhosted.org/packages/5a/ca/468c9a8446a2103ae645d14fee3f610567b7042aba85031c1c65e3ef7471/fonttools-4.61.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc492779501fa723b04d0ab1f5be046797fee17d27700476edc7ee9ae535a61e", size = 2398191 }, + { url = "https://files.pythonhosted.org/packages/a3/4b/d67eedaed19def5967fade3297fed8161b25ba94699efc124b14fb68cdbc/fonttools-4.61.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:64102ca87e84261419c3747a0d20f396eb024bdbeb04c2bfb37e2891f5fadcb5", size = 4928410 }, + { url = "https://files.pythonhosted.org/packages/b0/8d/6fb3494dfe61a46258cd93d979cf4725ded4eb46c2a4ca35e4490d84daea/fonttools-4.61.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c1b526c8d3f615a7b1867f38a9410849c8f4aef078535742198e942fba0e9bd", size = 4984460 }, + { url = "https://files.pythonhosted.org/packages/f7/f1/a47f1d30b3dc00d75e7af762652d4cbc3dff5c2697a0dbd5203c81afd9c3/fonttools-4.61.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:41ed4b5ec103bd306bb68f81dc166e77409e5209443e5773cb4ed837bcc9b0d3", size = 4925800 }, + { url = "https://files.pythonhosted.org/packages/a7/01/e6ae64a0981076e8a66906fab01539799546181e32a37a0257b77e4aa88b/fonttools-4.61.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b501c862d4901792adaec7c25b1ecc749e2662543f68bb194c42ba18d6eec98d", size = 5067859 }, + { url = "https://files.pythonhosted.org/packages/73/aa/28e40b8d6809a9b5075350a86779163f074d2b617c15d22343fce81918db/fonttools-4.61.1-cp313-cp313-win32.whl", hash = "sha256:4d7092bb38c53bbc78e9255a59158b150bcdc115a1e3b3ce0b5f267dc35dd63c", size = 2267821 }, + { url = "https://files.pythonhosted.org/packages/1a/59/453c06d1d83dc0951b69ef692d6b9f1846680342927df54e9a1ca91c6f90/fonttools-4.61.1-cp313-cp313-win_amd64.whl", hash = "sha256:21e7c8d76f62ab13c9472ccf74515ca5b9a761d1bde3265152a6dc58700d895b", size = 2318169 }, + { url = "https://files.pythonhosted.org/packages/32/8f/4e7bf82c0cbb738d3c2206c920ca34ca74ef9dabde779030145d28665104/fonttools-4.61.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fff4f534200a04b4a36e7ae3cb74493afe807b517a09e99cb4faa89a34ed6ecd", size = 2846094 }, + { url = "https://files.pythonhosted.org/packages/71/09/d44e45d0a4f3a651f23a1e9d42de43bc643cce2971b19e784cc67d823676/fonttools-4.61.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d9203500f7c63545b4ce3799319fe4d9feb1a1b89b28d3cb5abd11b9dd64147e", size = 2396589 }, + { url = "https://files.pythonhosted.org/packages/89/18/58c64cafcf8eb677a99ef593121f719e6dcbdb7d1c594ae5a10d4997ca8a/fonttools-4.61.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa646ecec9528bef693415c79a86e733c70a4965dd938e9a226b0fc64c9d2e6c", size = 4877892 }, + { url = "https://files.pythonhosted.org/packages/8a/ec/9e6b38c7ba1e09eb51db849d5450f4c05b7e78481f662c3b79dbde6f3d04/fonttools-4.61.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f35ad7805edba3aac1a3710d104592df59f4b957e30108ae0ba6c10b11dd75", size = 4972884 }, + { url = "https://files.pythonhosted.org/packages/5e/87/b5339da8e0256734ba0dbbf5b6cdebb1dd79b01dc8c270989b7bcd465541/fonttools-4.61.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b931ae8f62db78861b0ff1ac017851764602288575d65b8e8ff1963fed419063", size = 4924405 }, + { url = "https://files.pythonhosted.org/packages/0b/47/e3409f1e1e69c073a3a6fd8cb886eb18c0bae0ee13db2c8d5e7f8495e8b7/fonttools-4.61.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b148b56f5de675ee16d45e769e69f87623a4944f7443850bf9a9376e628a89d2", size = 5035553 }, + { url = "https://files.pythonhosted.org/packages/bf/b6/1f6600161b1073a984294c6c031e1a56ebf95b6164249eecf30012bb2e38/fonttools-4.61.1-cp314-cp314-win32.whl", hash = "sha256:9b666a475a65f4e839d3d10473fad6d47e0a9db14a2f4a224029c5bfde58ad2c", size = 2271915 }, + { url = "https://files.pythonhosted.org/packages/52/7b/91e7b01e37cc8eb0e1f770d08305b3655e4f002fc160fb82b3390eabacf5/fonttools-4.61.1-cp314-cp314-win_amd64.whl", hash = "sha256:4f5686e1fe5fce75d82d93c47a438a25bf0d1319d2843a926f741140b2b16e0c", size = 2323487 }, + { url = "https://files.pythonhosted.org/packages/39/5c/908ad78e46c61c3e3ed70c3b58ff82ab48437faf84ec84f109592cabbd9f/fonttools-4.61.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e76ce097e3c57c4bcb67c5aa24a0ecdbd9f74ea9219997a707a4061fbe2707aa", size = 2929571 }, + { url = "https://files.pythonhosted.org/packages/bd/41/975804132c6dea64cdbfbaa59f3518a21c137a10cccf962805b301ac6ab2/fonttools-4.61.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9cfef3ab326780c04d6646f68d4b4742aae222e8b8ea1d627c74e38afcbc9d91", size = 2435317 }, + { url = "https://files.pythonhosted.org/packages/b0/5a/aef2a0a8daf1ebaae4cfd83f84186d4a72ee08fd6a8451289fcd03ffa8a4/fonttools-4.61.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a75c301f96db737e1c5ed5fd7d77d9c34466de16095a266509e13da09751bd19", size = 4882124 }, + { url = "https://files.pythonhosted.org/packages/80/33/d6db3485b645b81cea538c9d1c9219d5805f0877fda18777add4671c5240/fonttools-4.61.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91669ccac46bbc1d09e9273546181919064e8df73488ea087dcac3e2968df9ba", size = 5100391 }, + { url = "https://files.pythonhosted.org/packages/6c/d6/675ba631454043c75fcf76f0ca5463eac8eb0666ea1d7badae5fea001155/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c33ab3ca9d3ccd581d58e989d67554e42d8d4ded94ab3ade3508455fe70e65f7", size = 4978800 }, + { url = "https://files.pythonhosted.org/packages/7f/33/d3ec753d547a8d2bdaedd390d4a814e8d5b45a093d558f025c6b990b554c/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:664c5a68ec406f6b1547946683008576ef8b38275608e1cee6c061828171c118", size = 5006426 }, + { url = "https://files.pythonhosted.org/packages/b4/40/cc11f378b561a67bea850ab50063366a0d1dd3f6d0a30ce0f874b0ad5664/fonttools-4.61.1-cp314-cp314t-win32.whl", hash = "sha256:aed04cabe26f30c1647ef0e8fbb207516fd40fe9472e9439695f5c6998e60ac5", size = 2335377 }, + { url = "https://files.pythonhosted.org/packages/e4/ff/c9a2b66b39f8628531ea58b320d66d951267c98c6a38684daa8f50fb02f8/fonttools-4.61.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2180f14c141d2f0f3da43f3a81bc8aa4684860f6b0e6f9e165a4831f24e6a23b", size = 2400613 }, + { url = "https://files.pythonhosted.org/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371", size = 1148996 }, ] [[package]] name = "fqdn" version = "1.5.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015, upload-time = "2021-03-11T07:16:29.08Z" } +sdist = { url = "https://files.pythonhosted.org/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015 } wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121, upload-time = "2021-03-11T07:16:28.351Z" }, + { url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121 }, ] [[package]] name = "fsspec" version = "2026.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/51/7c/f60c259dcbf4f0c47cc4ddb8f7720d2dcdc8888c8e5ad84c73ea4531cc5b/fsspec-2026.2.0.tar.gz", hash = "sha256:6544e34b16869f5aacd5b90bdf1a71acb37792ea3ddf6125ee69a22a53fb8bff", size = 313441, upload-time = "2026-02-05T21:50:53.743Z" } +sdist = { url = "https://files.pythonhosted.org/packages/51/7c/f60c259dcbf4f0c47cc4ddb8f7720d2dcdc8888c8e5ad84c73ea4531cc5b/fsspec-2026.2.0.tar.gz", hash = "sha256:6544e34b16869f5aacd5b90bdf1a71acb37792ea3ddf6125ee69a22a53fb8bff", size = 313441 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl", hash = "sha256:98de475b5cb3bd66bedd5c4679e87b4fdfe1a3bf4d707b151b3c07e58c9a2437", size = 202505, upload-time = "2026-02-05T21:50:51.819Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl", hash = "sha256:98de475b5cb3bd66bedd5c4679e87b4fdfe1a3bf4d707b151b3c07e58c9a2437", size = 202505 }, ] [[package]] name = "google-crc32c" version = "1.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/03/41/4b9c02f99e4c5fb477122cd5437403b552873f014616ac1d19ac8221a58d/google_crc32c-1.8.0.tar.gz", hash = "sha256:a428e25fb7691024de47fecfbff7ff957214da51eddded0da0ae0e0f03a2cf79", size = 14192, upload-time = "2025-12-16T00:35:25.142Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/ef/21ccfaab3d5078d41efe8612e0ed0bfc9ce22475de074162a91a25f7980d/google_crc32c-1.8.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:014a7e68d623e9a4222d663931febc3033c5c7c9730785727de2a81f87d5bab8", size = 31298, upload-time = "2025-12-16T00:20:32.241Z" }, - { url = "https://files.pythonhosted.org/packages/c5/b8/f8413d3f4b676136e965e764ceedec904fe38ae8de0cdc52a12d8eb1096e/google_crc32c-1.8.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:86cfc00fe45a0ac7359e5214a1704e51a99e757d0272554874f419f79838c5f7", size = 30872, upload-time = "2025-12-16T00:33:58.785Z" }, - { url = "https://files.pythonhosted.org/packages/f6/fd/33aa4ec62b290477181c55bb1c9302c9698c58c0ce9a6ab4874abc8b0d60/google_crc32c-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:19b40d637a54cb71e0829179f6cb41835f0fbd9e8eb60552152a8b52c36cbe15", size = 33243, upload-time = "2025-12-16T00:40:21.46Z" }, - { url = "https://files.pythonhosted.org/packages/71/03/4820b3bd99c9653d1a5210cb32f9ba4da9681619b4d35b6a052432df4773/google_crc32c-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:17446feb05abddc187e5441a45971b8394ea4c1b6efd88ab0af393fd9e0a156a", size = 33608, upload-time = "2025-12-16T00:40:22.204Z" }, - { url = "https://files.pythonhosted.org/packages/7c/43/acf61476a11437bf9733fb2f70599b1ced11ec7ed9ea760fdd9a77d0c619/google_crc32c-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:71734788a88f551fbd6a97be9668a0020698e07b2bf5b3aa26a36c10cdfb27b2", size = 34439, upload-time = "2025-12-16T00:35:20.458Z" }, - { url = "https://files.pythonhosted.org/packages/e9/5f/7307325b1198b59324c0fa9807cafb551afb65e831699f2ce211ad5c8240/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:4b8286b659c1335172e39563ab0a768b8015e88e08329fa5321f774275fc3113", size = 31300, upload-time = "2025-12-16T00:21:56.723Z" }, - { url = "https://files.pythonhosted.org/packages/21/8e/58c0d5d86e2220e6a37befe7e6a94dd2f6006044b1a33edf1ff6d9f7e319/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:2a3dc3318507de089c5384cc74d54318401410f82aa65b2d9cdde9d297aca7cb", size = 30867, upload-time = "2025-12-16T00:38:31.302Z" }, - { url = "https://files.pythonhosted.org/packages/ce/a9/a780cc66f86335a6019f557a8aaca8fbb970728f0efd2430d15ff1beae0e/google_crc32c-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14f87e04d613dfa218d6135e81b78272c3b904e2a7053b841481b38a7d901411", size = 33364, upload-time = "2025-12-16T00:40:22.96Z" }, - { url = "https://files.pythonhosted.org/packages/21/3f/3457ea803db0198c9aaca2dd373750972ce28a26f00544b6b85088811939/google_crc32c-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb5c869c2923d56cb0c8e6bcdd73c009c36ae39b652dbe46a05eb4ef0ad01454", size = 33740, upload-time = "2025-12-16T00:40:23.96Z" }, - { url = "https://files.pythonhosted.org/packages/df/c0/87c2073e0c72515bb8733d4eef7b21548e8d189f094b5dad20b0ecaf64f6/google_crc32c-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:3cc0c8912038065eafa603b238abf252e204accab2a704c63b9e14837a854962", size = 34437, upload-time = "2025-12-16T00:35:21.395Z" }, - { url = "https://files.pythonhosted.org/packages/d1/db/000f15b41724589b0e7bc24bc7a8967898d8d3bc8caf64c513d91ef1f6c0/google_crc32c-1.8.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:3ebb04528e83b2634857f43f9bb8ef5b2bbe7f10f140daeb01b58f972d04736b", size = 31297, upload-time = "2025-12-16T00:23:20.709Z" }, - { url = "https://files.pythonhosted.org/packages/d7/0d/8ebed0c39c53a7e838e2a486da8abb0e52de135f1b376ae2f0b160eb4c1a/google_crc32c-1.8.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:450dc98429d3e33ed2926fc99ee81001928d63460f8538f21a5d6060912a8e27", size = 30867, upload-time = "2025-12-16T00:43:14.628Z" }, - { url = "https://files.pythonhosted.org/packages/ce/42/b468aec74a0354b34c8cbf748db20d6e350a68a2b0912e128cabee49806c/google_crc32c-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3b9776774b24ba76831609ffbabce8cdf6fa2bd5e9df37b594221c7e333a81fa", size = 33344, upload-time = "2025-12-16T00:40:24.742Z" }, - { url = "https://files.pythonhosted.org/packages/1c/e8/b33784d6fc77fb5062a8a7854e43e1e618b87d5ddf610a88025e4de6226e/google_crc32c-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:89c17d53d75562edfff86679244830599ee0a48efc216200691de8b02ab6b2b8", size = 33694, upload-time = "2025-12-16T00:40:25.505Z" }, - { url = "https://files.pythonhosted.org/packages/92/b1/d3cbd4d988afb3d8e4db94ca953df429ed6db7282ed0e700d25e6c7bfc8d/google_crc32c-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:57a50a9035b75643996fbf224d6661e386c7162d1dfdab9bc4ca790947d1007f", size = 34435, upload-time = "2025-12-16T00:35:22.107Z" }, - { url = "https://files.pythonhosted.org/packages/21/88/8ecf3c2b864a490b9e7010c84fd203ec8cf3b280651106a3a74dd1b0ca72/google_crc32c-1.8.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:e6584b12cb06796d285d09e33f63309a09368b9d806a551d8036a4207ea43697", size = 31301, upload-time = "2025-12-16T00:24:48.527Z" }, - { url = "https://files.pythonhosted.org/packages/36/c6/f7ff6c11f5ca215d9f43d3629163727a272eabc356e5c9b2853df2bfe965/google_crc32c-1.8.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:f4b51844ef67d6cf2e9425983274da75f18b1597bb2c998e1c0a0e8d46f8f651", size = 30868, upload-time = "2025-12-16T00:48:12.163Z" }, - { url = "https://files.pythonhosted.org/packages/56/15/c25671c7aad70f8179d858c55a6ae8404902abe0cdcf32a29d581792b491/google_crc32c-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b0d1a7afc6e8e4635564ba8aa5c0548e3173e41b6384d7711a9123165f582de2", size = 33381, upload-time = "2025-12-16T00:40:26.268Z" }, - { url = "https://files.pythonhosted.org/packages/42/fa/f50f51260d7b0ef5d4898af122d8a7ec5a84e2984f676f746445f783705f/google_crc32c-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8b3f68782f3cbd1bce027e48768293072813469af6a61a86f6bb4977a4380f21", size = 33734, upload-time = "2025-12-16T00:40:27.028Z" }, - { url = "https://files.pythonhosted.org/packages/08/a5/7b059810934a09fb3ccb657e0843813c1fee1183d3bc2c8041800374aa2c/google_crc32c-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:d511b3153e7011a27ab6ee6bb3a5404a55b994dc1a7322c0b87b29606d9790e2", size = 34878, upload-time = "2025-12-16T00:35:23.142Z" }, - { url = "https://files.pythonhosted.org/packages/52/c5/c171e4d8c44fec1422d801a6d2e5d7ddabd733eeda505c79730ee9607f07/google_crc32c-1.8.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:87fa445064e7db928226b2e6f0d5304ab4cd0339e664a4e9a25029f384d9bb93", size = 28615, upload-time = "2025-12-16T00:40:29.298Z" }, - { url = "https://files.pythonhosted.org/packages/9c/97/7d75fe37a7a6ed171a2cf17117177e7aab7e6e0d115858741b41e9dd4254/google_crc32c-1.8.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f639065ea2042d5c034bf258a9f085eaa7af0cd250667c0635a3118e8f92c69c", size = 28800, upload-time = "2025-12-16T00:40:30.322Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/03/41/4b9c02f99e4c5fb477122cd5437403b552873f014616ac1d19ac8221a58d/google_crc32c-1.8.0.tar.gz", hash = "sha256:a428e25fb7691024de47fecfbff7ff957214da51eddded0da0ae0e0f03a2cf79", size = 14192 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/ef/21ccfaab3d5078d41efe8612e0ed0bfc9ce22475de074162a91a25f7980d/google_crc32c-1.8.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:014a7e68d623e9a4222d663931febc3033c5c7c9730785727de2a81f87d5bab8", size = 31298 }, + { url = "https://files.pythonhosted.org/packages/c5/b8/f8413d3f4b676136e965e764ceedec904fe38ae8de0cdc52a12d8eb1096e/google_crc32c-1.8.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:86cfc00fe45a0ac7359e5214a1704e51a99e757d0272554874f419f79838c5f7", size = 30872 }, + { url = "https://files.pythonhosted.org/packages/f6/fd/33aa4ec62b290477181c55bb1c9302c9698c58c0ce9a6ab4874abc8b0d60/google_crc32c-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:19b40d637a54cb71e0829179f6cb41835f0fbd9e8eb60552152a8b52c36cbe15", size = 33243 }, + { url = "https://files.pythonhosted.org/packages/71/03/4820b3bd99c9653d1a5210cb32f9ba4da9681619b4d35b6a052432df4773/google_crc32c-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:17446feb05abddc187e5441a45971b8394ea4c1b6efd88ab0af393fd9e0a156a", size = 33608 }, + { url = "https://files.pythonhosted.org/packages/7c/43/acf61476a11437bf9733fb2f70599b1ced11ec7ed9ea760fdd9a77d0c619/google_crc32c-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:71734788a88f551fbd6a97be9668a0020698e07b2bf5b3aa26a36c10cdfb27b2", size = 34439 }, + { url = "https://files.pythonhosted.org/packages/e9/5f/7307325b1198b59324c0fa9807cafb551afb65e831699f2ce211ad5c8240/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:4b8286b659c1335172e39563ab0a768b8015e88e08329fa5321f774275fc3113", size = 31300 }, + { url = "https://files.pythonhosted.org/packages/21/8e/58c0d5d86e2220e6a37befe7e6a94dd2f6006044b1a33edf1ff6d9f7e319/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:2a3dc3318507de089c5384cc74d54318401410f82aa65b2d9cdde9d297aca7cb", size = 30867 }, + { url = "https://files.pythonhosted.org/packages/ce/a9/a780cc66f86335a6019f557a8aaca8fbb970728f0efd2430d15ff1beae0e/google_crc32c-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14f87e04d613dfa218d6135e81b78272c3b904e2a7053b841481b38a7d901411", size = 33364 }, + { url = "https://files.pythonhosted.org/packages/21/3f/3457ea803db0198c9aaca2dd373750972ce28a26f00544b6b85088811939/google_crc32c-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb5c869c2923d56cb0c8e6bcdd73c009c36ae39b652dbe46a05eb4ef0ad01454", size = 33740 }, + { url = "https://files.pythonhosted.org/packages/df/c0/87c2073e0c72515bb8733d4eef7b21548e8d189f094b5dad20b0ecaf64f6/google_crc32c-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:3cc0c8912038065eafa603b238abf252e204accab2a704c63b9e14837a854962", size = 34437 }, + { url = "https://files.pythonhosted.org/packages/d1/db/000f15b41724589b0e7bc24bc7a8967898d8d3bc8caf64c513d91ef1f6c0/google_crc32c-1.8.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:3ebb04528e83b2634857f43f9bb8ef5b2bbe7f10f140daeb01b58f972d04736b", size = 31297 }, + { url = "https://files.pythonhosted.org/packages/d7/0d/8ebed0c39c53a7e838e2a486da8abb0e52de135f1b376ae2f0b160eb4c1a/google_crc32c-1.8.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:450dc98429d3e33ed2926fc99ee81001928d63460f8538f21a5d6060912a8e27", size = 30867 }, + { url = "https://files.pythonhosted.org/packages/ce/42/b468aec74a0354b34c8cbf748db20d6e350a68a2b0912e128cabee49806c/google_crc32c-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3b9776774b24ba76831609ffbabce8cdf6fa2bd5e9df37b594221c7e333a81fa", size = 33344 }, + { url = "https://files.pythonhosted.org/packages/1c/e8/b33784d6fc77fb5062a8a7854e43e1e618b87d5ddf610a88025e4de6226e/google_crc32c-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:89c17d53d75562edfff86679244830599ee0a48efc216200691de8b02ab6b2b8", size = 33694 }, + { url = "https://files.pythonhosted.org/packages/92/b1/d3cbd4d988afb3d8e4db94ca953df429ed6db7282ed0e700d25e6c7bfc8d/google_crc32c-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:57a50a9035b75643996fbf224d6661e386c7162d1dfdab9bc4ca790947d1007f", size = 34435 }, + { url = "https://files.pythonhosted.org/packages/21/88/8ecf3c2b864a490b9e7010c84fd203ec8cf3b280651106a3a74dd1b0ca72/google_crc32c-1.8.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:e6584b12cb06796d285d09e33f63309a09368b9d806a551d8036a4207ea43697", size = 31301 }, + { url = "https://files.pythonhosted.org/packages/36/c6/f7ff6c11f5ca215d9f43d3629163727a272eabc356e5c9b2853df2bfe965/google_crc32c-1.8.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:f4b51844ef67d6cf2e9425983274da75f18b1597bb2c998e1c0a0e8d46f8f651", size = 30868 }, + { url = "https://files.pythonhosted.org/packages/56/15/c25671c7aad70f8179d858c55a6ae8404902abe0cdcf32a29d581792b491/google_crc32c-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b0d1a7afc6e8e4635564ba8aa5c0548e3173e41b6384d7711a9123165f582de2", size = 33381 }, + { url = "https://files.pythonhosted.org/packages/42/fa/f50f51260d7b0ef5d4898af122d8a7ec5a84e2984f676f746445f783705f/google_crc32c-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8b3f68782f3cbd1bce027e48768293072813469af6a61a86f6bb4977a4380f21", size = 33734 }, + { url = "https://files.pythonhosted.org/packages/08/a5/7b059810934a09fb3ccb657e0843813c1fee1183d3bc2c8041800374aa2c/google_crc32c-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:d511b3153e7011a27ab6ee6bb3a5404a55b994dc1a7322c0b87b29606d9790e2", size = 34878 }, + { url = "https://files.pythonhosted.org/packages/52/c5/c171e4d8c44fec1422d801a6d2e5d7ddabd733eeda505c79730ee9607f07/google_crc32c-1.8.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:87fa445064e7db928226b2e6f0d5304ab4cd0339e664a4e9a25029f384d9bb93", size = 28615 }, + { url = "https://files.pythonhosted.org/packages/9c/97/7d75fe37a7a6ed171a2cf17117177e7aab7e6e0d115858741b41e9dd4254/google_crc32c-1.8.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f639065ea2042d5c034bf258a9f085eaa7af0cd250667c0635a3118e8f92c69c", size = 28800 }, ] [[package]] name = "greenlet" version = "3.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a3/51/1664f6b78fc6ebbd98019a1fd730e83fa78f2db7058f72b1463d3612b8db/greenlet-3.3.2.tar.gz", hash = "sha256:2eaf067fc6d886931c7962e8c6bede15d2f01965560f3359b27c80bde2d151f2", size = 188267, upload-time = "2026-02-20T20:54:15.531Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/47/16400cb42d18d7a6bb46f0626852c1718612e35dcb0dffa16bbaffdf5dd2/greenlet-3.3.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:c56692189a7d1c7606cb794be0a8381470d95c57ce5be03fb3d0ef57c7853b86", size = 278890, upload-time = "2026-02-20T20:19:39.263Z" }, - { url = "https://files.pythonhosted.org/packages/a3/90/42762b77a5b6aa96cd8c0e80612663d39211e8ae8a6cd47c7f1249a66262/greenlet-3.3.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ebd458fa8285960f382841da585e02201b53a5ec2bac6b156fc623b5ce4499f", size = 581120, upload-time = "2026-02-20T20:47:30.161Z" }, - { url = "https://files.pythonhosted.org/packages/bf/6f/f3d64f4fa0a9c7b5c5b3c810ff1df614540d5aa7d519261b53fba55d4df9/greenlet-3.3.2-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a443358b33c4ec7b05b79a7c8b466f5d275025e750298be7340f8fc63dff2a55", size = 594363, upload-time = "2026-02-20T20:55:56.965Z" }, - { url = "https://files.pythonhosted.org/packages/72/83/3e06a52aca8128bdd4dcd67e932b809e76a96ab8c232a8b025b2850264c5/greenlet-3.3.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e2cd90d413acbf5e77ae41e5d3c9b3ac1d011a756d7284d7f3f2b806bbd6358", size = 594156, upload-time = "2026-02-20T20:20:59.955Z" }, - { url = "https://files.pythonhosted.org/packages/70/79/0de5e62b873e08fe3cef7dbe84e5c4bc0e8ed0c7ff131bccb8405cd107c8/greenlet-3.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:442b6057453c8cb29b4fb36a2ac689382fc71112273726e2423f7f17dc73bf99", size = 1554649, upload-time = "2026-02-20T20:49:32.293Z" }, - { url = "https://files.pythonhosted.org/packages/5a/00/32d30dee8389dc36d42170a9c66217757289e2afb0de59a3565260f38373/greenlet-3.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:45abe8eb6339518180d5a7fa47fa01945414d7cca5ecb745346fc6a87d2750be", size = 1619472, upload-time = "2026-02-20T20:21:07.966Z" }, - { url = "https://files.pythonhosted.org/packages/f1/3a/efb2cf697fbccdf75b24e2c18025e7dfa54c4f31fab75c51d0fe79942cef/greenlet-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e692b2dae4cc7077cbb11b47d258533b48c8fde69a33d0d8a82e2fe8d8531d5", size = 230389, upload-time = "2026-02-20T20:17:18.772Z" }, - { url = "https://files.pythonhosted.org/packages/e1/a1/65bbc059a43a7e2143ec4fc1f9e3f673e04f9c7b371a494a101422ac4fd5/greenlet-3.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:02b0a8682aecd4d3c6c18edf52bc8e51eacdd75c8eac52a790a210b06aa295fd", size = 229645, upload-time = "2026-02-20T20:18:18.695Z" }, - { url = "https://files.pythonhosted.org/packages/ea/ab/1608e5a7578e62113506740b88066bf09888322a311cff602105e619bd87/greenlet-3.3.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ac8d61d4343b799d1e526db579833d72f23759c71e07181c2d2944e429eb09cd", size = 280358, upload-time = "2026-02-20T20:17:43.971Z" }, - { url = "https://files.pythonhosted.org/packages/a5/23/0eae412a4ade4e6623ff7626e38998cb9b11e9ff1ebacaa021e4e108ec15/greenlet-3.3.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ceec72030dae6ac0c8ed7591b96b70410a8be370b6a477b1dbc072856ad02bd", size = 601217, upload-time = "2026-02-20T20:47:31.462Z" }, - { url = "https://files.pythonhosted.org/packages/f8/16/5b1678a9c07098ecb9ab2dd159fafaf12e963293e61ee8d10ecb55273e5e/greenlet-3.3.2-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2a5be83a45ce6188c045bcc44b0ee037d6a518978de9a5d97438548b953a1ac", size = 611792, upload-time = "2026-02-20T20:55:58.423Z" }, - { url = "https://files.pythonhosted.org/packages/50/1f/5155f55bd71cabd03765a4aac9ac446be129895271f73872c36ebd4b04b6/greenlet-3.3.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e99d1749147ac21dde49b99c9abffcbc1e2d55c67501465ef0930d6e78e070", size = 613875, upload-time = "2026-02-20T20:21:01.102Z" }, - { url = "https://files.pythonhosted.org/packages/fc/dd/845f249c3fcd69e32df80cdab059b4be8b766ef5830a3d0aa9d6cad55beb/greenlet-3.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c956a19350e2c37f2c48b336a3afb4bff120b36076d9d7fb68cb44e05d95b79", size = 1571467, upload-time = "2026-02-20T20:49:33.495Z" }, - { url = "https://files.pythonhosted.org/packages/2a/50/2649fe21fcc2b56659a452868e695634722a6655ba245d9f77f5656010bf/greenlet-3.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c6f8ba97d17a1e7d664151284cb3315fc5f8353e75221ed4324f84eb162b395", size = 1640001, upload-time = "2026-02-20T20:21:09.154Z" }, - { url = "https://files.pythonhosted.org/packages/9b/40/cc802e067d02af8b60b6771cea7d57e21ef5e6659912814babb42b864713/greenlet-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:34308836d8370bddadb41f5a7ce96879b72e2fdfb4e87729330c6ab52376409f", size = 231081, upload-time = "2026-02-20T20:17:28.121Z" }, - { url = "https://files.pythonhosted.org/packages/58/2e/fe7f36ff1982d6b10a60d5e0740c759259a7d6d2e1dc41da6d96de32fff6/greenlet-3.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:d3a62fa76a32b462a97198e4c9e99afb9ab375115e74e9a83ce180e7a496f643", size = 230331, upload-time = "2026-02-20T20:17:23.34Z" }, - { url = "https://files.pythonhosted.org/packages/ac/48/f8b875fa7dea7dd9b33245e37f065af59df6a25af2f9561efa8d822fde51/greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:aa6ac98bdfd716a749b84d4034486863fd81c3abde9aa3cf8eff9127981a4ae4", size = 279120, upload-time = "2026-02-20T20:19:01.9Z" }, - { url = "https://files.pythonhosted.org/packages/49/8d/9771d03e7a8b1ee456511961e1b97a6d77ae1dea4a34a5b98eee706689d3/greenlet-3.3.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab0c7e7901a00bc0a7284907273dc165b32e0d109a6713babd04471327ff7986", size = 603238, upload-time = "2026-02-20T20:47:32.873Z" }, - { url = "https://files.pythonhosted.org/packages/59/0e/4223c2bbb63cd5c97f28ffb2a8aee71bdfb30b323c35d409450f51b91e3e/greenlet-3.3.2-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d248d8c23c67d2291ffd47af766e2a3aa9fa1c6703155c099feb11f526c63a92", size = 614219, upload-time = "2026-02-20T20:55:59.817Z" }, - { url = "https://files.pythonhosted.org/packages/7a/34/259b28ea7a2a0c904b11cd36c79b8cef8019b26ee5dbe24e73b469dea347/greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6997d360a4e6a4e936c0f9625b1c20416b8a0ea18a8e19cabbefc712e7397ab", size = 616774, upload-time = "2026-02-20T20:21:02.454Z" }, - { url = "https://files.pythonhosted.org/packages/0a/03/996c2d1689d486a6e199cb0f1cf9e4aa940c500e01bdf201299d7d61fa69/greenlet-3.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64970c33a50551c7c50491671265d8954046cb6e8e2999aacdd60e439b70418a", size = 1571277, upload-time = "2026-02-20T20:49:34.795Z" }, - { url = "https://files.pythonhosted.org/packages/d9/c4/2570fc07f34a39f2caf0bf9f24b0a1a0a47bc2e8e465b2c2424821389dfc/greenlet-3.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1a9172f5bf6bd88e6ba5a84e0a68afeac9dc7b6b412b245dd64f52d83c81e55b", size = 1640455, upload-time = "2026-02-20T20:21:10.261Z" }, - { url = "https://files.pythonhosted.org/packages/91/39/5ef5aa23bc545aa0d31e1b9b55822b32c8da93ba657295840b6b34124009/greenlet-3.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:a7945dd0eab63ded0a48e4dcade82939783c172290a7903ebde9e184333ca124", size = 230961, upload-time = "2026-02-20T20:16:58.461Z" }, - { url = "https://files.pythonhosted.org/packages/62/6b/a89f8456dcb06becff288f563618e9f20deed8dd29beea14f9a168aef64b/greenlet-3.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:394ead29063ee3515b4e775216cb756b2e3b4a7e55ae8fd884f17fa579e6b327", size = 230221, upload-time = "2026-02-20T20:17:37.152Z" }, - { url = "https://files.pythonhosted.org/packages/3f/ae/8bffcbd373b57a5992cd077cbe8858fff39110480a9d50697091faea6f39/greenlet-3.3.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8d1658d7291f9859beed69a776c10822a0a799bc4bfe1bd4272bb60e62507dab", size = 279650, upload-time = "2026-02-20T20:18:00.783Z" }, - { url = "https://files.pythonhosted.org/packages/d1/c0/45f93f348fa49abf32ac8439938726c480bd96b2a3c6f4d949ec0124b69f/greenlet-3.3.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18cb1b7337bca281915b3c5d5ae19f4e76d35e1df80f4ad3c1a7be91fadf1082", size = 650295, upload-time = "2026-02-20T20:47:34.036Z" }, - { url = "https://files.pythonhosted.org/packages/b3/de/dd7589b3f2b8372069ab3e4763ea5329940fc7ad9dcd3e272a37516d7c9b/greenlet-3.3.2-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2e47408e8ce1c6f1ceea0dffcdf6ebb85cc09e55c7af407c99f1112016e45e9", size = 662163, upload-time = "2026-02-20T20:56:01.295Z" }, - { url = "https://files.pythonhosted.org/packages/d2/d8/09bfa816572a4d83bccd6750df1926f79158b1c36c5f73786e26dbe4ee38/greenlet-3.3.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63d10328839d1973e5ba35e98cccbca71b232b14051fd957b6f8b6e8e80d0506", size = 664160, upload-time = "2026-02-20T20:21:04.015Z" }, - { url = "https://files.pythonhosted.org/packages/48/cf/56832f0c8255d27f6c35d41b5ec91168d74ec721d85f01a12131eec6b93c/greenlet-3.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e4ab3cfb02993c8cc248ea73d7dae6cec0253e9afa311c9b37e603ca9fad2ce", size = 1619181, upload-time = "2026-02-20T20:49:36.052Z" }, - { url = "https://files.pythonhosted.org/packages/0a/23/b90b60a4aabb4cec0796e55f25ffbfb579a907c3898cd2905c8918acaa16/greenlet-3.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94ad81f0fd3c0c0681a018a976e5c2bd2ca2d9d94895f23e7bb1af4e8af4e2d5", size = 1687713, upload-time = "2026-02-20T20:21:11.684Z" }, - { url = "https://files.pythonhosted.org/packages/f3/ca/2101ca3d9223a1dc125140dbc063644dca76df6ff356531eb27bc267b446/greenlet-3.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:8c4dd0f3997cf2512f7601563cc90dfb8957c0cff1e3a1b23991d4ea1776c492", size = 232034, upload-time = "2026-02-20T20:20:08.186Z" }, - { url = "https://files.pythonhosted.org/packages/f6/4a/ecf894e962a59dea60f04877eea0fd5724618da89f1867b28ee8b91e811f/greenlet-3.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:cd6f9e2bbd46321ba3bbb4c8a15794d32960e3b0ae2cc4d49a1a53d314805d71", size = 231437, upload-time = "2026-02-20T20:18:59.722Z" }, - { url = "https://files.pythonhosted.org/packages/98/6d/8f2ef704e614bcf58ed43cfb8d87afa1c285e98194ab2cfad351bf04f81e/greenlet-3.3.2-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:e26e72bec7ab387ac80caa7496e0f908ff954f31065b0ffc1f8ecb1338b11b54", size = 286617, upload-time = "2026-02-20T20:19:29.856Z" }, - { url = "https://files.pythonhosted.org/packages/5e/0d/93894161d307c6ea237a43988f27eba0947b360b99ac5239ad3fe09f0b47/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b466dff7a4ffda6ca975979bab80bdadde979e29fc947ac3be4451428d8b0e4", size = 655189, upload-time = "2026-02-20T20:47:35.742Z" }, - { url = "https://files.pythonhosted.org/packages/f5/2c/d2d506ebd8abcb57386ec4f7ba20f4030cbe56eae541bc6fd6ef399c0b41/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b8bddc5b73c9720bea487b3bffdb1840fe4e3656fba3bd40aa1489e9f37877ff", size = 658225, upload-time = "2026-02-20T20:56:02.527Z" }, - { url = "https://files.pythonhosted.org/packages/8e/30/3a09155fbf728673a1dea713572d2d31159f824a37c22da82127056c44e4/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b26b0f4428b871a751968285a1ac9648944cea09807177ac639b030bddebcea4", size = 657907, upload-time = "2026-02-20T20:21:05.259Z" }, - { url = "https://files.pythonhosted.org/packages/f3/fd/d05a4b7acd0154ed758797f0a43b4c0962a843bedfe980115e842c5b2d08/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1fb39a11ee2e4d94be9a76671482be9398560955c9e568550de0224e41104727", size = 1618857, upload-time = "2026-02-20T20:49:37.309Z" }, - { url = "https://files.pythonhosted.org/packages/6f/e1/50ee92a5db521de8f35075b5eff060dd43d39ebd46c2181a2042f7070385/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:20154044d9085151bc309e7689d6f7ba10027f8f5a8c0676ad398b951913d89e", size = 1680010, upload-time = "2026-02-20T20:21:13.427Z" }, - { url = "https://files.pythonhosted.org/packages/29/4b/45d90626aef8e65336bed690106d1382f7a43665e2249017e9527df8823b/greenlet-3.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c04c5e06ec3e022cbfe2cd4a846e1d4e50087444f875ff6d2c2ad8445495cf1a", size = 237086, upload-time = "2026-02-20T20:20:45.786Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/a3/51/1664f6b78fc6ebbd98019a1fd730e83fa78f2db7058f72b1463d3612b8db/greenlet-3.3.2.tar.gz", hash = "sha256:2eaf067fc6d886931c7962e8c6bede15d2f01965560f3359b27c80bde2d151f2", size = 188267 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/47/16400cb42d18d7a6bb46f0626852c1718612e35dcb0dffa16bbaffdf5dd2/greenlet-3.3.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:c56692189a7d1c7606cb794be0a8381470d95c57ce5be03fb3d0ef57c7853b86", size = 278890 }, + { url = "https://files.pythonhosted.org/packages/a3/90/42762b77a5b6aa96cd8c0e80612663d39211e8ae8a6cd47c7f1249a66262/greenlet-3.3.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ebd458fa8285960f382841da585e02201b53a5ec2bac6b156fc623b5ce4499f", size = 581120 }, + { url = "https://files.pythonhosted.org/packages/bf/6f/f3d64f4fa0a9c7b5c5b3c810ff1df614540d5aa7d519261b53fba55d4df9/greenlet-3.3.2-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a443358b33c4ec7b05b79a7c8b466f5d275025e750298be7340f8fc63dff2a55", size = 594363 }, + { url = "https://files.pythonhosted.org/packages/9c/8b/1430a04657735a3f23116c2e0d5eb10220928846e4537a938a41b350bed6/greenlet-3.3.2-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4375a58e49522698d3e70cc0b801c19433021b5c37686f7ce9c65b0d5c8677d2", size = 605046 }, + { url = "https://files.pythonhosted.org/packages/72/83/3e06a52aca8128bdd4dcd67e932b809e76a96ab8c232a8b025b2850264c5/greenlet-3.3.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e2cd90d413acbf5e77ae41e5d3c9b3ac1d011a756d7284d7f3f2b806bbd6358", size = 594156 }, + { url = "https://files.pythonhosted.org/packages/70/79/0de5e62b873e08fe3cef7dbe84e5c4bc0e8ed0c7ff131bccb8405cd107c8/greenlet-3.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:442b6057453c8cb29b4fb36a2ac689382fc71112273726e2423f7f17dc73bf99", size = 1554649 }, + { url = "https://files.pythonhosted.org/packages/5a/00/32d30dee8389dc36d42170a9c66217757289e2afb0de59a3565260f38373/greenlet-3.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:45abe8eb6339518180d5a7fa47fa01945414d7cca5ecb745346fc6a87d2750be", size = 1619472 }, + { url = "https://files.pythonhosted.org/packages/f1/3a/efb2cf697fbccdf75b24e2c18025e7dfa54c4f31fab75c51d0fe79942cef/greenlet-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e692b2dae4cc7077cbb11b47d258533b48c8fde69a33d0d8a82e2fe8d8531d5", size = 230389 }, + { url = "https://files.pythonhosted.org/packages/e1/a1/65bbc059a43a7e2143ec4fc1f9e3f673e04f9c7b371a494a101422ac4fd5/greenlet-3.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:02b0a8682aecd4d3c6c18edf52bc8e51eacdd75c8eac52a790a210b06aa295fd", size = 229645 }, + { url = "https://files.pythonhosted.org/packages/ea/ab/1608e5a7578e62113506740b88066bf09888322a311cff602105e619bd87/greenlet-3.3.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ac8d61d4343b799d1e526db579833d72f23759c71e07181c2d2944e429eb09cd", size = 280358 }, + { url = "https://files.pythonhosted.org/packages/a5/23/0eae412a4ade4e6623ff7626e38998cb9b11e9ff1ebacaa021e4e108ec15/greenlet-3.3.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ceec72030dae6ac0c8ed7591b96b70410a8be370b6a477b1dbc072856ad02bd", size = 601217 }, + { url = "https://files.pythonhosted.org/packages/f8/16/5b1678a9c07098ecb9ab2dd159fafaf12e963293e61ee8d10ecb55273e5e/greenlet-3.3.2-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2a5be83a45ce6188c045bcc44b0ee037d6a518978de9a5d97438548b953a1ac", size = 611792 }, + { url = "https://files.pythonhosted.org/packages/5c/c5/cc09412a29e43406eba18d61c70baa936e299bc27e074e2be3806ed29098/greenlet-3.3.2-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae9e21c84035c490506c17002f5c8ab25f980205c3e61ddb3a2a2a2e6c411fcb", size = 626250 }, + { url = "https://files.pythonhosted.org/packages/50/1f/5155f55bd71cabd03765a4aac9ac446be129895271f73872c36ebd4b04b6/greenlet-3.3.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e99d1749147ac21dde49b99c9abffcbc1e2d55c67501465ef0930d6e78e070", size = 613875 }, + { url = "https://files.pythonhosted.org/packages/fc/dd/845f249c3fcd69e32df80cdab059b4be8b766ef5830a3d0aa9d6cad55beb/greenlet-3.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c956a19350e2c37f2c48b336a3afb4bff120b36076d9d7fb68cb44e05d95b79", size = 1571467 }, + { url = "https://files.pythonhosted.org/packages/2a/50/2649fe21fcc2b56659a452868e695634722a6655ba245d9f77f5656010bf/greenlet-3.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c6f8ba97d17a1e7d664151284cb3315fc5f8353e75221ed4324f84eb162b395", size = 1640001 }, + { url = "https://files.pythonhosted.org/packages/9b/40/cc802e067d02af8b60b6771cea7d57e21ef5e6659912814babb42b864713/greenlet-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:34308836d8370bddadb41f5a7ce96879b72e2fdfb4e87729330c6ab52376409f", size = 231081 }, + { url = "https://files.pythonhosted.org/packages/58/2e/fe7f36ff1982d6b10a60d5e0740c759259a7d6d2e1dc41da6d96de32fff6/greenlet-3.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:d3a62fa76a32b462a97198e4c9e99afb9ab375115e74e9a83ce180e7a496f643", size = 230331 }, + { url = "https://files.pythonhosted.org/packages/ac/48/f8b875fa7dea7dd9b33245e37f065af59df6a25af2f9561efa8d822fde51/greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:aa6ac98bdfd716a749b84d4034486863fd81c3abde9aa3cf8eff9127981a4ae4", size = 279120 }, + { url = "https://files.pythonhosted.org/packages/49/8d/9771d03e7a8b1ee456511961e1b97a6d77ae1dea4a34a5b98eee706689d3/greenlet-3.3.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab0c7e7901a00bc0a7284907273dc165b32e0d109a6713babd04471327ff7986", size = 603238 }, + { url = "https://files.pythonhosted.org/packages/59/0e/4223c2bbb63cd5c97f28ffb2a8aee71bdfb30b323c35d409450f51b91e3e/greenlet-3.3.2-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d248d8c23c67d2291ffd47af766e2a3aa9fa1c6703155c099feb11f526c63a92", size = 614219 }, + { url = "https://files.pythonhosted.org/packages/94/2b/4d012a69759ac9d77210b8bfb128bc621125f5b20fc398bce3940d036b1c/greenlet-3.3.2-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ccd21bb86944ca9be6d967cf7691e658e43417782bce90b5d2faeda0ff78a7dd", size = 628268 }, + { url = "https://files.pythonhosted.org/packages/7a/34/259b28ea7a2a0c904b11cd36c79b8cef8019b26ee5dbe24e73b469dea347/greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6997d360a4e6a4e936c0f9625b1c20416b8a0ea18a8e19cabbefc712e7397ab", size = 616774 }, + { url = "https://files.pythonhosted.org/packages/0a/03/996c2d1689d486a6e199cb0f1cf9e4aa940c500e01bdf201299d7d61fa69/greenlet-3.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64970c33a50551c7c50491671265d8954046cb6e8e2999aacdd60e439b70418a", size = 1571277 }, + { url = "https://files.pythonhosted.org/packages/d9/c4/2570fc07f34a39f2caf0bf9f24b0a1a0a47bc2e8e465b2c2424821389dfc/greenlet-3.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1a9172f5bf6bd88e6ba5a84e0a68afeac9dc7b6b412b245dd64f52d83c81e55b", size = 1640455 }, + { url = "https://files.pythonhosted.org/packages/91/39/5ef5aa23bc545aa0d31e1b9b55822b32c8da93ba657295840b6b34124009/greenlet-3.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:a7945dd0eab63ded0a48e4dcade82939783c172290a7903ebde9e184333ca124", size = 230961 }, + { url = "https://files.pythonhosted.org/packages/62/6b/a89f8456dcb06becff288f563618e9f20deed8dd29beea14f9a168aef64b/greenlet-3.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:394ead29063ee3515b4e775216cb756b2e3b4a7e55ae8fd884f17fa579e6b327", size = 230221 }, + { url = "https://files.pythonhosted.org/packages/3f/ae/8bffcbd373b57a5992cd077cbe8858fff39110480a9d50697091faea6f39/greenlet-3.3.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8d1658d7291f9859beed69a776c10822a0a799bc4bfe1bd4272bb60e62507dab", size = 279650 }, + { url = "https://files.pythonhosted.org/packages/d1/c0/45f93f348fa49abf32ac8439938726c480bd96b2a3c6f4d949ec0124b69f/greenlet-3.3.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18cb1b7337bca281915b3c5d5ae19f4e76d35e1df80f4ad3c1a7be91fadf1082", size = 650295 }, + { url = "https://files.pythonhosted.org/packages/b3/de/dd7589b3f2b8372069ab3e4763ea5329940fc7ad9dcd3e272a37516d7c9b/greenlet-3.3.2-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2e47408e8ce1c6f1ceea0dffcdf6ebb85cc09e55c7af407c99f1112016e45e9", size = 662163 }, + { url = "https://files.pythonhosted.org/packages/cd/ac/85804f74f1ccea31ba518dcc8ee6f14c79f73fe36fa1beba38930806df09/greenlet-3.3.2-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e3cb43ce200f59483eb82949bf1835a99cf43d7571e900d7c8d5c62cdf25d2f9", size = 675371 }, + { url = "https://files.pythonhosted.org/packages/d2/d8/09bfa816572a4d83bccd6750df1926f79158b1c36c5f73786e26dbe4ee38/greenlet-3.3.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63d10328839d1973e5ba35e98cccbca71b232b14051fd957b6f8b6e8e80d0506", size = 664160 }, + { url = "https://files.pythonhosted.org/packages/48/cf/56832f0c8255d27f6c35d41b5ec91168d74ec721d85f01a12131eec6b93c/greenlet-3.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e4ab3cfb02993c8cc248ea73d7dae6cec0253e9afa311c9b37e603ca9fad2ce", size = 1619181 }, + { url = "https://files.pythonhosted.org/packages/0a/23/b90b60a4aabb4cec0796e55f25ffbfb579a907c3898cd2905c8918acaa16/greenlet-3.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94ad81f0fd3c0c0681a018a976e5c2bd2ca2d9d94895f23e7bb1af4e8af4e2d5", size = 1687713 }, + { url = "https://files.pythonhosted.org/packages/f3/ca/2101ca3d9223a1dc125140dbc063644dca76df6ff356531eb27bc267b446/greenlet-3.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:8c4dd0f3997cf2512f7601563cc90dfb8957c0cff1e3a1b23991d4ea1776c492", size = 232034 }, + { url = "https://files.pythonhosted.org/packages/f6/4a/ecf894e962a59dea60f04877eea0fd5724618da89f1867b28ee8b91e811f/greenlet-3.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:cd6f9e2bbd46321ba3bbb4c8a15794d32960e3b0ae2cc4d49a1a53d314805d71", size = 231437 }, + { url = "https://files.pythonhosted.org/packages/98/6d/8f2ef704e614bcf58ed43cfb8d87afa1c285e98194ab2cfad351bf04f81e/greenlet-3.3.2-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:e26e72bec7ab387ac80caa7496e0f908ff954f31065b0ffc1f8ecb1338b11b54", size = 286617 }, + { url = "https://files.pythonhosted.org/packages/5e/0d/93894161d307c6ea237a43988f27eba0947b360b99ac5239ad3fe09f0b47/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b466dff7a4ffda6ca975979bab80bdadde979e29fc947ac3be4451428d8b0e4", size = 655189 }, + { url = "https://files.pythonhosted.org/packages/f5/2c/d2d506ebd8abcb57386ec4f7ba20f4030cbe56eae541bc6fd6ef399c0b41/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b8bddc5b73c9720bea487b3bffdb1840fe4e3656fba3bd40aa1489e9f37877ff", size = 658225 }, + { url = "https://files.pythonhosted.org/packages/d1/67/8197b7e7e602150938049d8e7f30de1660cfb87e4c8ee349b42b67bdb2e1/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:59b3e2c40f6706b05a9cd299c836c6aa2378cabe25d021acd80f13abf81181cf", size = 666581 }, + { url = "https://files.pythonhosted.org/packages/8e/30/3a09155fbf728673a1dea713572d2d31159f824a37c22da82127056c44e4/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b26b0f4428b871a751968285a1ac9648944cea09807177ac639b030bddebcea4", size = 657907 }, + { url = "https://files.pythonhosted.org/packages/f3/fd/d05a4b7acd0154ed758797f0a43b4c0962a843bedfe980115e842c5b2d08/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1fb39a11ee2e4d94be9a76671482be9398560955c9e568550de0224e41104727", size = 1618857 }, + { url = "https://files.pythonhosted.org/packages/6f/e1/50ee92a5db521de8f35075b5eff060dd43d39ebd46c2181a2042f7070385/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:20154044d9085151bc309e7689d6f7ba10027f8f5a8c0676ad398b951913d89e", size = 1680010 }, + { url = "https://files.pythonhosted.org/packages/29/4b/45d90626aef8e65336bed690106d1382f7a43665e2249017e9527df8823b/greenlet-3.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c04c5e06ec3e022cbfe2cd4a846e1d4e50087444f875ff6d2c2ad8445495cf1a", size = 237086 }, ] [[package]] @@ -950,57 +1002,57 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1f/de/de568532d9907552700f80dcec38219d8d298ad9e71f5e0a095abaf2761e/grpcio-1.78.1.tar.gz", hash = "sha256:27c625532d33ace45d57e775edf1982e183ff8641c72e4e91ef7ba667a149d72", size = 12835760, upload-time = "2026-02-20T01:16:10.869Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/1e/ad774af3b2c84f49c6d8c4a7bea4c40f02268ea8380630c28777edda463b/grpcio-1.78.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:3a8aa79bc6e004394c0abefd4b034c14affda7b66480085d87f5fbadf43b593b", size = 5951132, upload-time = "2026-02-20T01:13:05.942Z" }, - { url = "https://files.pythonhosted.org/packages/48/9d/ad3c284bedd88c545e20675d98ae904114d8517a71b0efc0901e9166628f/grpcio-1.78.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:8e1fcb419da5811deb47b7749b8049f7c62b993ba17822e3c7231e3e0ba65b79", size = 11831052, upload-time = "2026-02-20T01:13:09.604Z" }, - { url = "https://files.pythonhosted.org/packages/6d/08/20d12865e47242d03c3ade9bb2127f5b4aded964f373284cfb357d47c5ac/grpcio-1.78.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b071dccac245c32cd6b1dd96b722283b855881ca0bf1c685cf843185f5d5d51e", size = 6524749, upload-time = "2026-02-20T01:13:21.692Z" }, - { url = "https://files.pythonhosted.org/packages/c6/53/a8b72f52b253ec0cfdf88a13e9236a9d717c332b8aa5f0ba9e4699e94b55/grpcio-1.78.1-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:d6fb962947e4fe321eeef3be1ba5ba49d32dea9233c825fcbade8e858c14aaf4", size = 7198995, upload-time = "2026-02-20T01:13:24.275Z" }, - { url = "https://files.pythonhosted.org/packages/13/3c/ac769c8ded1bcb26bb119fb472d3374b481b3cf059a0875db9fc77139c17/grpcio-1.78.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6afd191551fd72e632367dfb083e33cd185bf9ead565f2476bba8ab864ae496", size = 6730770, upload-time = "2026-02-20T01:13:26.522Z" }, - { url = "https://files.pythonhosted.org/packages/dc/c3/2275ef4cc5b942314321f77d66179be4097ff484e82ca34bf7baa5b1ddbc/grpcio-1.78.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b2acd83186305c0802dbc4d81ed0ec2f3e8658d7fde97cfba2f78d7372f05b89", size = 7305036, upload-time = "2026-02-20T01:13:30.923Z" }, - { url = "https://files.pythonhosted.org/packages/91/cb/3c2aa99e12cbbfc72c2ed8aa328e6041709d607d668860380e6cd00ba17d/grpcio-1.78.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5380268ab8513445740f1f77bd966d13043d07e2793487e61fd5b5d0935071eb", size = 8288641, upload-time = "2026-02-20T01:13:39.42Z" }, - { url = "https://files.pythonhosted.org/packages/0d/b2/21b89f492260ac645775d9973752ca873acfd0609d6998e9d3065a21ea2f/grpcio-1.78.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:389b77484959bdaad6a2b7dda44d7d1228381dd669a03f5660392aa0e9385b22", size = 7730967, upload-time = "2026-02-20T01:13:41.697Z" }, - { url = "https://files.pythonhosted.org/packages/24/03/6b89eddf87fdffb8fa9d37375d44d3a798f4b8116ac363a5f7ca84caa327/grpcio-1.78.1-cp311-cp311-win32.whl", hash = "sha256:9dee66d142f4a8cca36b5b98a38f006419138c3c89e72071747f8fca415a6d8f", size = 4076680, upload-time = "2026-02-20T01:13:43.781Z" }, - { url = "https://files.pythonhosted.org/packages/a7/a8/204460b1bc1dff9862e98f56a2d14be3c4171f929f8eaf8c4517174b4270/grpcio-1.78.1-cp311-cp311-win_amd64.whl", hash = "sha256:43b930cf4f9c4a2262bb3e5d5bc40df426a72538b4f98e46f158b7eb112d2d70", size = 4801074, upload-time = "2026-02-20T01:13:46.315Z" }, - { url = "https://files.pythonhosted.org/packages/ab/ed/d2eb9d27fded1a76b2a80eb9aa8b12101da7e41ce2bac0ad3651e88a14ae/grpcio-1.78.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:41e4605c923e0e9a84a2718e4948a53a530172bfaf1a6d1ded16ef9c5849fca2", size = 5913389, upload-time = "2026-02-20T01:13:49.005Z" }, - { url = "https://files.pythonhosted.org/packages/69/1b/40034e9ab010eeb3fa41ec61d8398c6dbf7062f3872c866b8f72700e2522/grpcio-1.78.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:39da1680d260c0c619c3b5fa2dc47480ca24d5704c7a548098bca7de7f5dd17f", size = 11811839, upload-time = "2026-02-20T01:13:51.839Z" }, - { url = "https://files.pythonhosted.org/packages/b4/69/fe16ef2979ea62b8aceb3a3f1e7a8bbb8b717ae2a44b5899d5d426073273/grpcio-1.78.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b5d5881d72a09b8336a8f874784a8eeffacde44a7bc1a148bce5a0243a265ef0", size = 6475805, upload-time = "2026-02-20T01:13:55.423Z" }, - { url = "https://files.pythonhosted.org/packages/5b/1e/069e0a9062167db18446917d7c00ae2e91029f96078a072bedc30aaaa8c3/grpcio-1.78.1-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:888ceb7821acd925b1c90f0cdceaed1386e69cfe25e496e0771f6c35a156132f", size = 7169955, upload-time = "2026-02-20T01:13:59.553Z" }, - { url = "https://files.pythonhosted.org/packages/38/fc/44a57e2bb4a755e309ee4e9ed2b85c9af93450b6d3118de7e69410ee05fa/grpcio-1.78.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8942bdfc143b467c264b048862090c4ba9a0223c52ae28c9ae97754361372e42", size = 6690767, upload-time = "2026-02-20T01:14:02.31Z" }, - { url = "https://files.pythonhosted.org/packages/b8/87/21e16345d4c75046d453916166bc72a3309a382c8e97381ec4b8c1a54729/grpcio-1.78.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:716a544969660ed609164aff27b2effd3ff84e54ac81aa4ce77b1607ca917d22", size = 7266846, upload-time = "2026-02-20T01:14:12.974Z" }, - { url = "https://files.pythonhosted.org/packages/11/df/d6261983f9ca9ef4d69893765007a9a3211b91d9faf85a2591063df381c7/grpcio-1.78.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4d50329b081c223d444751076bb5b389d4f06c2b32d51b31a1e98172e6cecfb9", size = 8253522, upload-time = "2026-02-20T01:14:17.407Z" }, - { url = "https://files.pythonhosted.org/packages/de/7c/4f96a0ff113c5d853a27084d7590cd53fdb05169b596ea9f5f27f17e021e/grpcio-1.78.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7e836778c13ff70edada16567e8da0c431e8818eaae85b80d11c1ba5782eccbb", size = 7698070, upload-time = "2026-02-20T01:14:20.032Z" }, - { url = "https://files.pythonhosted.org/packages/17/3c/7b55c0b5af88fbeb3d0c13e25492d3ace41ac9dbd0f5f8f6c0fb613b6706/grpcio-1.78.1-cp312-cp312-win32.whl", hash = "sha256:07eb016ea7444a22bef465cce045512756956433f54450aeaa0b443b8563b9ca", size = 4066474, upload-time = "2026-02-20T01:14:22.602Z" }, - { url = "https://files.pythonhosted.org/packages/5d/17/388c12d298901b0acf10b612b650692bfed60e541672b1d8965acbf2d722/grpcio-1.78.1-cp312-cp312-win_amd64.whl", hash = "sha256:02b82dcd2fa580f5e82b4cf62ecde1b3c7cc9ba27b946421200706a6e5acaf85", size = 4797537, upload-time = "2026-02-20T01:14:25.444Z" }, - { url = "https://files.pythonhosted.org/packages/df/72/754754639cfd16ad04619e1435a518124b2d858e5752225376f9285d4c51/grpcio-1.78.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:2b7ad2981550ce999e25ce3f10c8863f718a352a2fd655068d29ea3fd37b4907", size = 5919437, upload-time = "2026-02-20T01:14:29.403Z" }, - { url = "https://files.pythonhosted.org/packages/5c/84/6267d1266f8bc335d3a8b7ccf981be7de41e3ed8bd3a49e57e588212b437/grpcio-1.78.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:409bfe22220889b9906739910a0ee4c197a967c21b8dd14b4b06dd477f8819ce", size = 11803701, upload-time = "2026-02-20T01:14:32.624Z" }, - { url = "https://files.pythonhosted.org/packages/f3/56/c9098e8b920a54261cd605bbb040de0cde1ca4406102db0aa2c0b11d1fb4/grpcio-1.78.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:34b6cb16f4b67eeb5206250dc5b4d5e8e3db939535e58efc330e4c61341554bd", size = 6479416, upload-time = "2026-02-20T01:14:35.926Z" }, - { url = "https://files.pythonhosted.org/packages/86/cf/5d52024371ee62658b7ed72480200524087528844ec1b65265bbcd31c974/grpcio-1.78.1-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:39d21fd30d38a5afb93f0e2e71e2ec2bd894605fb75d41d5a40060c2f98f8d11", size = 7174087, upload-time = "2026-02-20T01:14:39.98Z" }, - { url = "https://files.pythonhosted.org/packages/31/e6/5e59551afad4279e27335a6d60813b8aa3ae7b14fb62cea1d329a459c118/grpcio-1.78.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:09fbd4bcaadb6d8604ed1504b0bdf7ac18e48467e83a9d930a70a7fefa27e862", size = 6692881, upload-time = "2026-02-20T01:14:42.466Z" }, - { url = "https://files.pythonhosted.org/packages/db/8f/940062de2d14013c02f51b079eb717964d67d46f5d44f22038975c9d9576/grpcio-1.78.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:db681513a1bdd879c0b24a5a6a70398da5eaaba0e077a306410dc6008426847a", size = 7269092, upload-time = "2026-02-20T01:14:45.826Z" }, - { url = "https://files.pythonhosted.org/packages/09/87/9db657a4b5f3b15560ec591db950bc75a1a2f9e07832578d7e2b23d1a7bd/grpcio-1.78.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f81816faa426da461e9a597a178832a351d6f1078102590a4b32c77d251b71eb", size = 8252037, upload-time = "2026-02-20T01:14:48.57Z" }, - { url = "https://files.pythonhosted.org/packages/e2/37/b980e0265479ec65e26b6e300a39ceac33ecb3f762c2861d4bac990317cf/grpcio-1.78.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffbb760df1cd49e0989f9826b2fd48930700db6846ac171eaff404f3cfbe5c28", size = 7695243, upload-time = "2026-02-20T01:14:51.376Z" }, - { url = "https://files.pythonhosted.org/packages/98/46/5fc42c100ab702fa1ea41a75c890c563c3f96432b4a287d5a6369654f323/grpcio-1.78.1-cp313-cp313-win32.whl", hash = "sha256:1a56bf3ee99af5cf32d469de91bf5de79bdac2e18082b495fc1063ea33f4f2d0", size = 4065329, upload-time = "2026-02-20T01:14:53.952Z" }, - { url = "https://files.pythonhosted.org/packages/b0/da/806d60bb6611dfc16cf463d982bd92bd8b6bd5f87dfac66b0a44dfe20995/grpcio-1.78.1-cp313-cp313-win_amd64.whl", hash = "sha256:8991c2add0d8505178ff6c3ae54bd9386279e712be82fa3733c54067aae9eda1", size = 4797637, upload-time = "2026-02-20T01:14:57.276Z" }, - { url = "https://files.pythonhosted.org/packages/96/3a/2d2ec4d2ce2eb9d6a2b862630a0d9d4ff4239ecf1474ecff21442a78612a/grpcio-1.78.1-cp314-cp314-linux_armv7l.whl", hash = "sha256:d101fe49b1e0fb4a7aa36ed0c3821a0f67a5956ef572745452d2cd790d723a3f", size = 5920256, upload-time = "2026-02-20T01:15:00.23Z" }, - { url = "https://files.pythonhosted.org/packages/9c/92/dccb7d087a1220ed358753945230c1ddeeed13684b954cb09db6758f1271/grpcio-1.78.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:5ce1855e8cfc217cdf6bcfe0cf046d7cf81ddcc3e6894d6cfd075f87a2d8f460", size = 11813749, upload-time = "2026-02-20T01:15:03.312Z" }, - { url = "https://files.pythonhosted.org/packages/ef/47/c20e87f87986da9998f30f14776ce27e61f02482a3a030ffe265089342c6/grpcio-1.78.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd26048d066b51f39fe9206e2bcc2cea869a5e5b2d13c8d523f4179193047ebd", size = 6488739, upload-time = "2026-02-20T01:15:14.349Z" }, - { url = "https://files.pythonhosted.org/packages/a6/c2/088bd96e255133d7d87c3eed0d598350d16cde1041bdbe2bb065967aaf91/grpcio-1.78.1-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4b8d7fda614cf2af0f73bbb042f3b7fee2ecd4aea69ec98dbd903590a1083529", size = 7173096, upload-time = "2026-02-20T01:15:17.687Z" }, - { url = "https://files.pythonhosted.org/packages/60/ce/168db121073a03355ce3552b3b1f790b5ded62deffd7d98c5f642b9d3d81/grpcio-1.78.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:656a5bd142caeb8b1efe1fe0b4434ecc7781f44c97cfc7927f6608627cf178c0", size = 6693861, upload-time = "2026-02-20T01:15:20.911Z" }, - { url = "https://files.pythonhosted.org/packages/ae/d0/90b30ec2d9425215dd56922d85a90babbe6ee7e8256ba77d866b9c0d3aba/grpcio-1.78.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:99550e344482e3c21950c034f74668fccf8a546d50c1ecb4f717543bbdc071ba", size = 7278083, upload-time = "2026-02-20T01:15:23.698Z" }, - { url = "https://files.pythonhosted.org/packages/c1/fb/73f9ba0b082bcd385d46205095fd9c917754685885b28fce3741e9f54529/grpcio-1.78.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:8f27683ca68359bd3f0eb4925824d71e538f84338b3ae337ead2ae43977d7541", size = 8252546, upload-time = "2026-02-20T01:15:26.517Z" }, - { url = "https://files.pythonhosted.org/packages/85/c5/6a89ea3cb5db6c3d9ed029b0396c49f64328c0cf5d2630ffeed25711920a/grpcio-1.78.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a40515b69ac50792f9b8ead260f194ba2bb3285375b6c40c7ff938f14c3df17d", size = 7696289, upload-time = "2026-02-20T01:15:29.718Z" }, - { url = "https://files.pythonhosted.org/packages/3d/05/63a7495048499ef437b4933d32e59b7f737bd5368ad6fb2479e2bd83bf2c/grpcio-1.78.1-cp314-cp314-win32.whl", hash = "sha256:2c473b54ef1618f4fb85e82ff4994de18143b74efc088b91b5a935a3a45042ba", size = 4142186, upload-time = "2026-02-20T01:15:32.786Z" }, - { url = "https://files.pythonhosted.org/packages/1c/ce/adfe7e5f701d503be7778291757452e3fab6b19acf51917c79f5d1cf7f8a/grpcio-1.78.1-cp314-cp314-win_amd64.whl", hash = "sha256:e2a6b33d1050dce2c6f563c5caf7f7cbeebf7fba8cde37ffe3803d50526900d1", size = 4932000, upload-time = "2026-02-20T01:15:36.127Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/1f/de/de568532d9907552700f80dcec38219d8d298ad9e71f5e0a095abaf2761e/grpcio-1.78.1.tar.gz", hash = "sha256:27c625532d33ace45d57e775edf1982e183ff8641c72e4e91ef7ba667a149d72", size = 12835760 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/1e/ad774af3b2c84f49c6d8c4a7bea4c40f02268ea8380630c28777edda463b/grpcio-1.78.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:3a8aa79bc6e004394c0abefd4b034c14affda7b66480085d87f5fbadf43b593b", size = 5951132 }, + { url = "https://files.pythonhosted.org/packages/48/9d/ad3c284bedd88c545e20675d98ae904114d8517a71b0efc0901e9166628f/grpcio-1.78.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:8e1fcb419da5811deb47b7749b8049f7c62b993ba17822e3c7231e3e0ba65b79", size = 11831052 }, + { url = "https://files.pythonhosted.org/packages/6d/08/20d12865e47242d03c3ade9bb2127f5b4aded964f373284cfb357d47c5ac/grpcio-1.78.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b071dccac245c32cd6b1dd96b722283b855881ca0bf1c685cf843185f5d5d51e", size = 6524749 }, + { url = "https://files.pythonhosted.org/packages/c6/53/a8b72f52b253ec0cfdf88a13e9236a9d717c332b8aa5f0ba9e4699e94b55/grpcio-1.78.1-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:d6fb962947e4fe321eeef3be1ba5ba49d32dea9233c825fcbade8e858c14aaf4", size = 7198995 }, + { url = "https://files.pythonhosted.org/packages/13/3c/ac769c8ded1bcb26bb119fb472d3374b481b3cf059a0875db9fc77139c17/grpcio-1.78.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6afd191551fd72e632367dfb083e33cd185bf9ead565f2476bba8ab864ae496", size = 6730770 }, + { url = "https://files.pythonhosted.org/packages/dc/c3/2275ef4cc5b942314321f77d66179be4097ff484e82ca34bf7baa5b1ddbc/grpcio-1.78.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b2acd83186305c0802dbc4d81ed0ec2f3e8658d7fde97cfba2f78d7372f05b89", size = 7305036 }, + { url = "https://files.pythonhosted.org/packages/91/cb/3c2aa99e12cbbfc72c2ed8aa328e6041709d607d668860380e6cd00ba17d/grpcio-1.78.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5380268ab8513445740f1f77bd966d13043d07e2793487e61fd5b5d0935071eb", size = 8288641 }, + { url = "https://files.pythonhosted.org/packages/0d/b2/21b89f492260ac645775d9973752ca873acfd0609d6998e9d3065a21ea2f/grpcio-1.78.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:389b77484959bdaad6a2b7dda44d7d1228381dd669a03f5660392aa0e9385b22", size = 7730967 }, + { url = "https://files.pythonhosted.org/packages/24/03/6b89eddf87fdffb8fa9d37375d44d3a798f4b8116ac363a5f7ca84caa327/grpcio-1.78.1-cp311-cp311-win32.whl", hash = "sha256:9dee66d142f4a8cca36b5b98a38f006419138c3c89e72071747f8fca415a6d8f", size = 4076680 }, + { url = "https://files.pythonhosted.org/packages/a7/a8/204460b1bc1dff9862e98f56a2d14be3c4171f929f8eaf8c4517174b4270/grpcio-1.78.1-cp311-cp311-win_amd64.whl", hash = "sha256:43b930cf4f9c4a2262bb3e5d5bc40df426a72538b4f98e46f158b7eb112d2d70", size = 4801074 }, + { url = "https://files.pythonhosted.org/packages/ab/ed/d2eb9d27fded1a76b2a80eb9aa8b12101da7e41ce2bac0ad3651e88a14ae/grpcio-1.78.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:41e4605c923e0e9a84a2718e4948a53a530172bfaf1a6d1ded16ef9c5849fca2", size = 5913389 }, + { url = "https://files.pythonhosted.org/packages/69/1b/40034e9ab010eeb3fa41ec61d8398c6dbf7062f3872c866b8f72700e2522/grpcio-1.78.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:39da1680d260c0c619c3b5fa2dc47480ca24d5704c7a548098bca7de7f5dd17f", size = 11811839 }, + { url = "https://files.pythonhosted.org/packages/b4/69/fe16ef2979ea62b8aceb3a3f1e7a8bbb8b717ae2a44b5899d5d426073273/grpcio-1.78.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b5d5881d72a09b8336a8f874784a8eeffacde44a7bc1a148bce5a0243a265ef0", size = 6475805 }, + { url = "https://files.pythonhosted.org/packages/5b/1e/069e0a9062167db18446917d7c00ae2e91029f96078a072bedc30aaaa8c3/grpcio-1.78.1-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:888ceb7821acd925b1c90f0cdceaed1386e69cfe25e496e0771f6c35a156132f", size = 7169955 }, + { url = "https://files.pythonhosted.org/packages/38/fc/44a57e2bb4a755e309ee4e9ed2b85c9af93450b6d3118de7e69410ee05fa/grpcio-1.78.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8942bdfc143b467c264b048862090c4ba9a0223c52ae28c9ae97754361372e42", size = 6690767 }, + { url = "https://files.pythonhosted.org/packages/b8/87/21e16345d4c75046d453916166bc72a3309a382c8e97381ec4b8c1a54729/grpcio-1.78.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:716a544969660ed609164aff27b2effd3ff84e54ac81aa4ce77b1607ca917d22", size = 7266846 }, + { url = "https://files.pythonhosted.org/packages/11/df/d6261983f9ca9ef4d69893765007a9a3211b91d9faf85a2591063df381c7/grpcio-1.78.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4d50329b081c223d444751076bb5b389d4f06c2b32d51b31a1e98172e6cecfb9", size = 8253522 }, + { url = "https://files.pythonhosted.org/packages/de/7c/4f96a0ff113c5d853a27084d7590cd53fdb05169b596ea9f5f27f17e021e/grpcio-1.78.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7e836778c13ff70edada16567e8da0c431e8818eaae85b80d11c1ba5782eccbb", size = 7698070 }, + { url = "https://files.pythonhosted.org/packages/17/3c/7b55c0b5af88fbeb3d0c13e25492d3ace41ac9dbd0f5f8f6c0fb613b6706/grpcio-1.78.1-cp312-cp312-win32.whl", hash = "sha256:07eb016ea7444a22bef465cce045512756956433f54450aeaa0b443b8563b9ca", size = 4066474 }, + { url = "https://files.pythonhosted.org/packages/5d/17/388c12d298901b0acf10b612b650692bfed60e541672b1d8965acbf2d722/grpcio-1.78.1-cp312-cp312-win_amd64.whl", hash = "sha256:02b82dcd2fa580f5e82b4cf62ecde1b3c7cc9ba27b946421200706a6e5acaf85", size = 4797537 }, + { url = "https://files.pythonhosted.org/packages/df/72/754754639cfd16ad04619e1435a518124b2d858e5752225376f9285d4c51/grpcio-1.78.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:2b7ad2981550ce999e25ce3f10c8863f718a352a2fd655068d29ea3fd37b4907", size = 5919437 }, + { url = "https://files.pythonhosted.org/packages/5c/84/6267d1266f8bc335d3a8b7ccf981be7de41e3ed8bd3a49e57e588212b437/grpcio-1.78.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:409bfe22220889b9906739910a0ee4c197a967c21b8dd14b4b06dd477f8819ce", size = 11803701 }, + { url = "https://files.pythonhosted.org/packages/f3/56/c9098e8b920a54261cd605bbb040de0cde1ca4406102db0aa2c0b11d1fb4/grpcio-1.78.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:34b6cb16f4b67eeb5206250dc5b4d5e8e3db939535e58efc330e4c61341554bd", size = 6479416 }, + { url = "https://files.pythonhosted.org/packages/86/cf/5d52024371ee62658b7ed72480200524087528844ec1b65265bbcd31c974/grpcio-1.78.1-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:39d21fd30d38a5afb93f0e2e71e2ec2bd894605fb75d41d5a40060c2f98f8d11", size = 7174087 }, + { url = "https://files.pythonhosted.org/packages/31/e6/5e59551afad4279e27335a6d60813b8aa3ae7b14fb62cea1d329a459c118/grpcio-1.78.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:09fbd4bcaadb6d8604ed1504b0bdf7ac18e48467e83a9d930a70a7fefa27e862", size = 6692881 }, + { url = "https://files.pythonhosted.org/packages/db/8f/940062de2d14013c02f51b079eb717964d67d46f5d44f22038975c9d9576/grpcio-1.78.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:db681513a1bdd879c0b24a5a6a70398da5eaaba0e077a306410dc6008426847a", size = 7269092 }, + { url = "https://files.pythonhosted.org/packages/09/87/9db657a4b5f3b15560ec591db950bc75a1a2f9e07832578d7e2b23d1a7bd/grpcio-1.78.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f81816faa426da461e9a597a178832a351d6f1078102590a4b32c77d251b71eb", size = 8252037 }, + { url = "https://files.pythonhosted.org/packages/e2/37/b980e0265479ec65e26b6e300a39ceac33ecb3f762c2861d4bac990317cf/grpcio-1.78.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffbb760df1cd49e0989f9826b2fd48930700db6846ac171eaff404f3cfbe5c28", size = 7695243 }, + { url = "https://files.pythonhosted.org/packages/98/46/5fc42c100ab702fa1ea41a75c890c563c3f96432b4a287d5a6369654f323/grpcio-1.78.1-cp313-cp313-win32.whl", hash = "sha256:1a56bf3ee99af5cf32d469de91bf5de79bdac2e18082b495fc1063ea33f4f2d0", size = 4065329 }, + { url = "https://files.pythonhosted.org/packages/b0/da/806d60bb6611dfc16cf463d982bd92bd8b6bd5f87dfac66b0a44dfe20995/grpcio-1.78.1-cp313-cp313-win_amd64.whl", hash = "sha256:8991c2add0d8505178ff6c3ae54bd9386279e712be82fa3733c54067aae9eda1", size = 4797637 }, + { url = "https://files.pythonhosted.org/packages/96/3a/2d2ec4d2ce2eb9d6a2b862630a0d9d4ff4239ecf1474ecff21442a78612a/grpcio-1.78.1-cp314-cp314-linux_armv7l.whl", hash = "sha256:d101fe49b1e0fb4a7aa36ed0c3821a0f67a5956ef572745452d2cd790d723a3f", size = 5920256 }, + { url = "https://files.pythonhosted.org/packages/9c/92/dccb7d087a1220ed358753945230c1ddeeed13684b954cb09db6758f1271/grpcio-1.78.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:5ce1855e8cfc217cdf6bcfe0cf046d7cf81ddcc3e6894d6cfd075f87a2d8f460", size = 11813749 }, + { url = "https://files.pythonhosted.org/packages/ef/47/c20e87f87986da9998f30f14776ce27e61f02482a3a030ffe265089342c6/grpcio-1.78.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd26048d066b51f39fe9206e2bcc2cea869a5e5b2d13c8d523f4179193047ebd", size = 6488739 }, + { url = "https://files.pythonhosted.org/packages/a6/c2/088bd96e255133d7d87c3eed0d598350d16cde1041bdbe2bb065967aaf91/grpcio-1.78.1-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4b8d7fda614cf2af0f73bbb042f3b7fee2ecd4aea69ec98dbd903590a1083529", size = 7173096 }, + { url = "https://files.pythonhosted.org/packages/60/ce/168db121073a03355ce3552b3b1f790b5ded62deffd7d98c5f642b9d3d81/grpcio-1.78.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:656a5bd142caeb8b1efe1fe0b4434ecc7781f44c97cfc7927f6608627cf178c0", size = 6693861 }, + { url = "https://files.pythonhosted.org/packages/ae/d0/90b30ec2d9425215dd56922d85a90babbe6ee7e8256ba77d866b9c0d3aba/grpcio-1.78.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:99550e344482e3c21950c034f74668fccf8a546d50c1ecb4f717543bbdc071ba", size = 7278083 }, + { url = "https://files.pythonhosted.org/packages/c1/fb/73f9ba0b082bcd385d46205095fd9c917754685885b28fce3741e9f54529/grpcio-1.78.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:8f27683ca68359bd3f0eb4925824d71e538f84338b3ae337ead2ae43977d7541", size = 8252546 }, + { url = "https://files.pythonhosted.org/packages/85/c5/6a89ea3cb5db6c3d9ed029b0396c49f64328c0cf5d2630ffeed25711920a/grpcio-1.78.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a40515b69ac50792f9b8ead260f194ba2bb3285375b6c40c7ff938f14c3df17d", size = 7696289 }, + { url = "https://files.pythonhosted.org/packages/3d/05/63a7495048499ef437b4933d32e59b7f737bd5368ad6fb2479e2bd83bf2c/grpcio-1.78.1-cp314-cp314-win32.whl", hash = "sha256:2c473b54ef1618f4fb85e82ff4994de18143b74efc088b91b5a935a3a45042ba", size = 4142186 }, + { url = "https://files.pythonhosted.org/packages/1c/ce/adfe7e5f701d503be7778291757452e3fab6b19acf51917c79f5d1cf7f8a/grpcio-1.78.1-cp314-cp314-win_amd64.whl", hash = "sha256:e2a6b33d1050dce2c6f563c5caf7f7cbeebf7fba8cde37ffe3803d50526900d1", size = 4932000 }, ] [[package]] name = "h11" version = "0.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250 } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 }, ] [[package]] @@ -1010,40 +1062,40 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4d/6a/0d79de0b025aa85dc8864de8e97659c94cf3d23148394a954dc5ca52f8c8/h5py-3.15.1.tar.gz", hash = "sha256:c86e3ed45c4473564de55aa83b6fc9e5ead86578773dfbd93047380042e26b69", size = 426236, upload-time = "2025-10-16T10:35:27.404Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/41/fd/8349b48b15b47768042cff06ad6e1c229f0a4bd89225bf6b6894fea27e6d/h5py-3.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5aaa330bcbf2830150c50897ea5dcbed30b5b6d56897289846ac5b9e529ec243", size = 3434135, upload-time = "2025-10-16T10:33:47.954Z" }, - { url = "https://files.pythonhosted.org/packages/c1/b0/1c628e26a0b95858f54aba17e1599e7f6cd241727596cc2580b72cb0a9bf/h5py-3.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c970fb80001fffabb0109eaf95116c8e7c0d3ca2de854e0901e8a04c1f098509", size = 2870958, upload-time = "2025-10-16T10:33:50.907Z" }, - { url = "https://files.pythonhosted.org/packages/f9/e3/c255cafc9b85e6ea04e2ad1bba1416baa1d7f57fc98a214be1144087690c/h5py-3.15.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80e5bb5b9508d5d9da09f81fd00abbb3f85da8143e56b1585d59bc8ceb1dba8b", size = 4504770, upload-time = "2025-10-16T10:33:54.357Z" }, - { url = "https://files.pythonhosted.org/packages/8b/23/4ab1108e87851ccc69694b03b817d92e142966a6c4abd99e17db77f2c066/h5py-3.15.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b849ba619a066196169763c33f9f0f02e381156d61c03e000bb0100f9950faf", size = 4700329, upload-time = "2025-10-16T10:33:57.616Z" }, - { url = "https://files.pythonhosted.org/packages/a4/e4/932a3a8516e4e475b90969bf250b1924dbe3612a02b897e426613aed68f4/h5py-3.15.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e7f6c841efd4e6e5b7e82222eaf90819927b6d256ab0f3aca29675601f654f3c", size = 4152456, upload-time = "2025-10-16T10:34:00.843Z" }, - { url = "https://files.pythonhosted.org/packages/2a/0a/f74d589883b13737021b2049ac796328f188dbb60c2ed35b101f5b95a3fc/h5py-3.15.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ca8a3a22458956ee7b40d8e39c9a9dc01f82933e4c030c964f8b875592f4d831", size = 4617295, upload-time = "2025-10-16T10:34:04.154Z" }, - { url = "https://files.pythonhosted.org/packages/23/95/499b4e56452ef8b6c95a271af0dde08dac4ddb70515a75f346d4f400579b/h5py-3.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:550e51131376889656feec4aff2170efc054a7fe79eb1da3bb92e1625d1ac878", size = 2882129, upload-time = "2025-10-16T10:34:06.886Z" }, - { url = "https://files.pythonhosted.org/packages/ce/bb/cfcc70b8a42222ba3ad4478bcef1791181ea908e2adbd7d53c66395edad5/h5py-3.15.1-cp311-cp311-win_arm64.whl", hash = "sha256:b39239947cb36a819147fc19e86b618dcb0953d1cd969f5ed71fc0de60392427", size = 2477121, upload-time = "2025-10-16T10:34:09.579Z" }, - { url = "https://files.pythonhosted.org/packages/62/b8/c0d9aa013ecfa8b7057946c080c0c07f6fa41e231d2e9bd306a2f8110bdc/h5py-3.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:316dd0f119734f324ca7ed10b5627a2de4ea42cc4dfbcedbee026aaa361c238c", size = 3399089, upload-time = "2025-10-16T10:34:12.135Z" }, - { url = "https://files.pythonhosted.org/packages/a4/5e/3c6f6e0430813c7aefe784d00c6711166f46225f5d229546eb53032c3707/h5py-3.15.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b51469890e58e85d5242e43aab29f5e9c7e526b951caab354f3ded4ac88e7b76", size = 2847803, upload-time = "2025-10-16T10:34:14.564Z" }, - { url = "https://files.pythonhosted.org/packages/00/69/ba36273b888a4a48d78f9268d2aee05787e4438557450a8442946ab8f3ec/h5py-3.15.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a33bfd5dfcea037196f7778534b1ff7e36a7f40a89e648c8f2967292eb6898e", size = 4914884, upload-time = "2025-10-16T10:34:18.452Z" }, - { url = "https://files.pythonhosted.org/packages/3a/30/d1c94066343a98bb2cea40120873193a4fed68c4ad7f8935c11caf74c681/h5py-3.15.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25c8843fec43b2cc368aa15afa1cdf83fc5e17b1c4e10cd3771ef6c39b72e5ce", size = 5109965, upload-time = "2025-10-16T10:34:21.853Z" }, - { url = "https://files.pythonhosted.org/packages/81/3d/d28172116eafc3bc9f5991b3cb3fd2c8a95f5984f50880adfdf991de9087/h5py-3.15.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a308fd8681a864c04423c0324527237a0484e2611e3441f8089fd00ed56a8171", size = 4561870, upload-time = "2025-10-16T10:34:26.69Z" }, - { url = "https://files.pythonhosted.org/packages/a5/83/393a7226024238b0f51965a7156004eaae1fcf84aa4bfecf7e582676271b/h5py-3.15.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f4a016df3f4a8a14d573b496e4d1964deb380e26031fc85fb40e417e9131888a", size = 5037161, upload-time = "2025-10-16T10:34:30.383Z" }, - { url = "https://files.pythonhosted.org/packages/cf/51/329e7436bf87ca6b0fe06dd0a3795c34bebe4ed8d6c44450a20565d57832/h5py-3.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:59b25cf02411bf12e14f803fef0b80886444c7fe21a5ad17c6a28d3f08098a1e", size = 2874165, upload-time = "2025-10-16T10:34:33.461Z" }, - { url = "https://files.pythonhosted.org/packages/09/a8/2d02b10a66747c54446e932171dd89b8b4126c0111b440e6bc05a7c852ec/h5py-3.15.1-cp312-cp312-win_arm64.whl", hash = "sha256:61d5a58a9851e01ee61c932bbbb1c98fe20aba0a5674776600fb9a361c0aa652", size = 2458214, upload-time = "2025-10-16T10:34:35.733Z" }, - { url = "https://files.pythonhosted.org/packages/88/b3/40207e0192415cbff7ea1d37b9f24b33f6d38a5a2f5d18a678de78f967ae/h5py-3.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8440fd8bee9500c235ecb7aa1917a0389a2adb80c209fa1cc485bd70e0d94a5", size = 3376511, upload-time = "2025-10-16T10:34:38.596Z" }, - { url = "https://files.pythonhosted.org/packages/31/96/ba99a003c763998035b0de4c299598125df5fc6c9ccf834f152ddd60e0fb/h5py-3.15.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ab2219dbc6fcdb6932f76b548e2b16f34a1f52b7666e998157a4dfc02e2c4123", size = 2826143, upload-time = "2025-10-16T10:34:41.342Z" }, - { url = "https://files.pythonhosted.org/packages/6a/c2/fc6375d07ea3962df7afad7d863fe4bde18bb88530678c20d4c90c18de1d/h5py-3.15.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8cb02c3a96255149ed3ac811eeea25b655d959c6dd5ce702c9a95ff11859eb5", size = 4908316, upload-time = "2025-10-16T10:34:44.619Z" }, - { url = "https://files.pythonhosted.org/packages/d9/69/4402ea66272dacc10b298cca18ed73e1c0791ff2ae9ed218d3859f9698ac/h5py-3.15.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:121b2b7a4c1915d63737483b7bff14ef253020f617c2fb2811f67a4bed9ac5e8", size = 5103710, upload-time = "2025-10-16T10:34:48.639Z" }, - { url = "https://files.pythonhosted.org/packages/e0/f6/11f1e2432d57d71322c02a97a5567829a75f223a8c821764a0e71a65cde8/h5py-3.15.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59b0d63b318bf3cc06687def2b45afd75926bbc006f7b8cd2b1a231299fc8599", size = 4556042, upload-time = "2025-10-16T10:34:51.841Z" }, - { url = "https://files.pythonhosted.org/packages/18/88/3eda3ef16bfe7a7dbc3d8d6836bbaa7986feb5ff091395e140dc13927bcc/h5py-3.15.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e02fe77a03f652500d8bff288cbf3675f742fc0411f5a628fa37116507dc7cc0", size = 5030639, upload-time = "2025-10-16T10:34:55.257Z" }, - { url = "https://files.pythonhosted.org/packages/e5/ea/fbb258a98863f99befb10ed727152b4ae659f322e1d9c0576f8a62754e81/h5py-3.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:dea78b092fd80a083563ed79a3171258d4a4d307492e7cf8b2313d464c82ba52", size = 2864363, upload-time = "2025-10-16T10:34:58.099Z" }, - { url = "https://files.pythonhosted.org/packages/5d/c9/35021cc9cd2b2915a7da3026e3d77a05bed1144a414ff840953b33937fb9/h5py-3.15.1-cp313-cp313-win_arm64.whl", hash = "sha256:c256254a8a81e2bddc0d376e23e2a6d2dc8a1e8a2261835ed8c1281a0744cd97", size = 2449570, upload-time = "2025-10-16T10:35:00.473Z" }, - { url = "https://files.pythonhosted.org/packages/a0/2c/926eba1514e4d2e47d0e9eb16c784e717d8b066398ccfca9b283917b1bfb/h5py-3.15.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5f4fb0567eb8517c3ecd6b3c02c4f4e9da220c8932604960fd04e24ee1254763", size = 3380368, upload-time = "2025-10-16T10:35:03.117Z" }, - { url = "https://files.pythonhosted.org/packages/65/4b/d715ed454d3baa5f6ae1d30b7eca4c7a1c1084f6a2edead9e801a1541d62/h5py-3.15.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:954e480433e82d3872503104f9b285d369048c3a788b2b1a00e53d1c47c98dd2", size = 2833793, upload-time = "2025-10-16T10:35:05.623Z" }, - { url = "https://files.pythonhosted.org/packages/ef/d4/ef386c28e4579314610a8bffebbee3b69295b0237bc967340b7c653c6c10/h5py-3.15.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd125c131889ebbef0849f4a0e29cf363b48aba42f228d08b4079913b576bb3a", size = 4903199, upload-time = "2025-10-16T10:35:08.972Z" }, - { url = "https://files.pythonhosted.org/packages/33/5d/65c619e195e0b5e54ea5a95c1bb600c8ff8715e0d09676e4cce56d89f492/h5py-3.15.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28a20e1a4082a479b3d7db2169f3a5034af010b90842e75ebbf2e9e49eb4183e", size = 5097224, upload-time = "2025-10-16T10:35:12.808Z" }, - { url = "https://files.pythonhosted.org/packages/30/30/5273218400bf2da01609e1292f562c94b461fcb73c7a9e27fdadd43abc0a/h5py-3.15.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa8df5267f545b4946df8ca0d93d23382191018e4cda2deda4c2cedf9a010e13", size = 4551207, upload-time = "2025-10-16T10:35:16.24Z" }, - { url = "https://files.pythonhosted.org/packages/d3/39/a7ef948ddf4d1c556b0b2b9559534777bccc318543b3f5a1efdf6b556c9c/h5py-3.15.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99d374a21f7321a4c6ab327c4ab23bd925ad69821aeb53a1e75dd809d19f67fa", size = 5025426, upload-time = "2025-10-16T10:35:19.831Z" }, - { url = "https://files.pythonhosted.org/packages/b6/d8/7368679b8df6925b8415f9dcc9ab1dab01ddc384d2b2c24aac9191bd9ceb/h5py-3.15.1-cp314-cp314-win_amd64.whl", hash = "sha256:9c73d1d7cdb97d5b17ae385153472ce118bed607e43be11e9a9deefaa54e0734", size = 2865704, upload-time = "2025-10-16T10:35:22.658Z" }, - { url = "https://files.pythonhosted.org/packages/d3/b7/4a806f85d62c20157e62e58e03b27513dc9c55499768530acc4f4c5ce4be/h5py-3.15.1-cp314-cp314-win_arm64.whl", hash = "sha256:a6d8c5a05a76aca9a494b4c53ce8a9c29023b7f64f625c6ce1841e92a362ccdf", size = 2465544, upload-time = "2025-10-16T10:35:25.695Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/4d/6a/0d79de0b025aa85dc8864de8e97659c94cf3d23148394a954dc5ca52f8c8/h5py-3.15.1.tar.gz", hash = "sha256:c86e3ed45c4473564de55aa83b6fc9e5ead86578773dfbd93047380042e26b69", size = 426236 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/fd/8349b48b15b47768042cff06ad6e1c229f0a4bd89225bf6b6894fea27e6d/h5py-3.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5aaa330bcbf2830150c50897ea5dcbed30b5b6d56897289846ac5b9e529ec243", size = 3434135 }, + { url = "https://files.pythonhosted.org/packages/c1/b0/1c628e26a0b95858f54aba17e1599e7f6cd241727596cc2580b72cb0a9bf/h5py-3.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c970fb80001fffabb0109eaf95116c8e7c0d3ca2de854e0901e8a04c1f098509", size = 2870958 }, + { url = "https://files.pythonhosted.org/packages/f9/e3/c255cafc9b85e6ea04e2ad1bba1416baa1d7f57fc98a214be1144087690c/h5py-3.15.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80e5bb5b9508d5d9da09f81fd00abbb3f85da8143e56b1585d59bc8ceb1dba8b", size = 4504770 }, + { url = "https://files.pythonhosted.org/packages/8b/23/4ab1108e87851ccc69694b03b817d92e142966a6c4abd99e17db77f2c066/h5py-3.15.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b849ba619a066196169763c33f9f0f02e381156d61c03e000bb0100f9950faf", size = 4700329 }, + { url = "https://files.pythonhosted.org/packages/a4/e4/932a3a8516e4e475b90969bf250b1924dbe3612a02b897e426613aed68f4/h5py-3.15.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e7f6c841efd4e6e5b7e82222eaf90819927b6d256ab0f3aca29675601f654f3c", size = 4152456 }, + { url = "https://files.pythonhosted.org/packages/2a/0a/f74d589883b13737021b2049ac796328f188dbb60c2ed35b101f5b95a3fc/h5py-3.15.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ca8a3a22458956ee7b40d8e39c9a9dc01f82933e4c030c964f8b875592f4d831", size = 4617295 }, + { url = "https://files.pythonhosted.org/packages/23/95/499b4e56452ef8b6c95a271af0dde08dac4ddb70515a75f346d4f400579b/h5py-3.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:550e51131376889656feec4aff2170efc054a7fe79eb1da3bb92e1625d1ac878", size = 2882129 }, + { url = "https://files.pythonhosted.org/packages/ce/bb/cfcc70b8a42222ba3ad4478bcef1791181ea908e2adbd7d53c66395edad5/h5py-3.15.1-cp311-cp311-win_arm64.whl", hash = "sha256:b39239947cb36a819147fc19e86b618dcb0953d1cd969f5ed71fc0de60392427", size = 2477121 }, + { url = "https://files.pythonhosted.org/packages/62/b8/c0d9aa013ecfa8b7057946c080c0c07f6fa41e231d2e9bd306a2f8110bdc/h5py-3.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:316dd0f119734f324ca7ed10b5627a2de4ea42cc4dfbcedbee026aaa361c238c", size = 3399089 }, + { url = "https://files.pythonhosted.org/packages/a4/5e/3c6f6e0430813c7aefe784d00c6711166f46225f5d229546eb53032c3707/h5py-3.15.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b51469890e58e85d5242e43aab29f5e9c7e526b951caab354f3ded4ac88e7b76", size = 2847803 }, + { url = "https://files.pythonhosted.org/packages/00/69/ba36273b888a4a48d78f9268d2aee05787e4438557450a8442946ab8f3ec/h5py-3.15.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a33bfd5dfcea037196f7778534b1ff7e36a7f40a89e648c8f2967292eb6898e", size = 4914884 }, + { url = "https://files.pythonhosted.org/packages/3a/30/d1c94066343a98bb2cea40120873193a4fed68c4ad7f8935c11caf74c681/h5py-3.15.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25c8843fec43b2cc368aa15afa1cdf83fc5e17b1c4e10cd3771ef6c39b72e5ce", size = 5109965 }, + { url = "https://files.pythonhosted.org/packages/81/3d/d28172116eafc3bc9f5991b3cb3fd2c8a95f5984f50880adfdf991de9087/h5py-3.15.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a308fd8681a864c04423c0324527237a0484e2611e3441f8089fd00ed56a8171", size = 4561870 }, + { url = "https://files.pythonhosted.org/packages/a5/83/393a7226024238b0f51965a7156004eaae1fcf84aa4bfecf7e582676271b/h5py-3.15.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f4a016df3f4a8a14d573b496e4d1964deb380e26031fc85fb40e417e9131888a", size = 5037161 }, + { url = "https://files.pythonhosted.org/packages/cf/51/329e7436bf87ca6b0fe06dd0a3795c34bebe4ed8d6c44450a20565d57832/h5py-3.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:59b25cf02411bf12e14f803fef0b80886444c7fe21a5ad17c6a28d3f08098a1e", size = 2874165 }, + { url = "https://files.pythonhosted.org/packages/09/a8/2d02b10a66747c54446e932171dd89b8b4126c0111b440e6bc05a7c852ec/h5py-3.15.1-cp312-cp312-win_arm64.whl", hash = "sha256:61d5a58a9851e01ee61c932bbbb1c98fe20aba0a5674776600fb9a361c0aa652", size = 2458214 }, + { url = "https://files.pythonhosted.org/packages/88/b3/40207e0192415cbff7ea1d37b9f24b33f6d38a5a2f5d18a678de78f967ae/h5py-3.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8440fd8bee9500c235ecb7aa1917a0389a2adb80c209fa1cc485bd70e0d94a5", size = 3376511 }, + { url = "https://files.pythonhosted.org/packages/31/96/ba99a003c763998035b0de4c299598125df5fc6c9ccf834f152ddd60e0fb/h5py-3.15.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ab2219dbc6fcdb6932f76b548e2b16f34a1f52b7666e998157a4dfc02e2c4123", size = 2826143 }, + { url = "https://files.pythonhosted.org/packages/6a/c2/fc6375d07ea3962df7afad7d863fe4bde18bb88530678c20d4c90c18de1d/h5py-3.15.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8cb02c3a96255149ed3ac811eeea25b655d959c6dd5ce702c9a95ff11859eb5", size = 4908316 }, + { url = "https://files.pythonhosted.org/packages/d9/69/4402ea66272dacc10b298cca18ed73e1c0791ff2ae9ed218d3859f9698ac/h5py-3.15.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:121b2b7a4c1915d63737483b7bff14ef253020f617c2fb2811f67a4bed9ac5e8", size = 5103710 }, + { url = "https://files.pythonhosted.org/packages/e0/f6/11f1e2432d57d71322c02a97a5567829a75f223a8c821764a0e71a65cde8/h5py-3.15.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59b0d63b318bf3cc06687def2b45afd75926bbc006f7b8cd2b1a231299fc8599", size = 4556042 }, + { url = "https://files.pythonhosted.org/packages/18/88/3eda3ef16bfe7a7dbc3d8d6836bbaa7986feb5ff091395e140dc13927bcc/h5py-3.15.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e02fe77a03f652500d8bff288cbf3675f742fc0411f5a628fa37116507dc7cc0", size = 5030639 }, + { url = "https://files.pythonhosted.org/packages/e5/ea/fbb258a98863f99befb10ed727152b4ae659f322e1d9c0576f8a62754e81/h5py-3.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:dea78b092fd80a083563ed79a3171258d4a4d307492e7cf8b2313d464c82ba52", size = 2864363 }, + { url = "https://files.pythonhosted.org/packages/5d/c9/35021cc9cd2b2915a7da3026e3d77a05bed1144a414ff840953b33937fb9/h5py-3.15.1-cp313-cp313-win_arm64.whl", hash = "sha256:c256254a8a81e2bddc0d376e23e2a6d2dc8a1e8a2261835ed8c1281a0744cd97", size = 2449570 }, + { url = "https://files.pythonhosted.org/packages/a0/2c/926eba1514e4d2e47d0e9eb16c784e717d8b066398ccfca9b283917b1bfb/h5py-3.15.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5f4fb0567eb8517c3ecd6b3c02c4f4e9da220c8932604960fd04e24ee1254763", size = 3380368 }, + { url = "https://files.pythonhosted.org/packages/65/4b/d715ed454d3baa5f6ae1d30b7eca4c7a1c1084f6a2edead9e801a1541d62/h5py-3.15.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:954e480433e82d3872503104f9b285d369048c3a788b2b1a00e53d1c47c98dd2", size = 2833793 }, + { url = "https://files.pythonhosted.org/packages/ef/d4/ef386c28e4579314610a8bffebbee3b69295b0237bc967340b7c653c6c10/h5py-3.15.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd125c131889ebbef0849f4a0e29cf363b48aba42f228d08b4079913b576bb3a", size = 4903199 }, + { url = "https://files.pythonhosted.org/packages/33/5d/65c619e195e0b5e54ea5a95c1bb600c8ff8715e0d09676e4cce56d89f492/h5py-3.15.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28a20e1a4082a479b3d7db2169f3a5034af010b90842e75ebbf2e9e49eb4183e", size = 5097224 }, + { url = "https://files.pythonhosted.org/packages/30/30/5273218400bf2da01609e1292f562c94b461fcb73c7a9e27fdadd43abc0a/h5py-3.15.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa8df5267f545b4946df8ca0d93d23382191018e4cda2deda4c2cedf9a010e13", size = 4551207 }, + { url = "https://files.pythonhosted.org/packages/d3/39/a7ef948ddf4d1c556b0b2b9559534777bccc318543b3f5a1efdf6b556c9c/h5py-3.15.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99d374a21f7321a4c6ab327c4ab23bd925ad69821aeb53a1e75dd809d19f67fa", size = 5025426 }, + { url = "https://files.pythonhosted.org/packages/b6/d8/7368679b8df6925b8415f9dcc9ab1dab01ddc384d2b2c24aac9191bd9ceb/h5py-3.15.1-cp314-cp314-win_amd64.whl", hash = "sha256:9c73d1d7cdb97d5b17ae385153472ce118bed607e43be11e9a9deefaa54e0734", size = 2865704 }, + { url = "https://files.pythonhosted.org/packages/d3/b7/4a806f85d62c20157e62e58e03b27513dc9c55499768530acc4f4c5ce4be/h5py-3.15.1-cp314-cp314-win_arm64.whl", hash = "sha256:a6d8c5a05a76aca9a494b4c53ce8a9c29023b7f64f625c6ce1841e92a362ccdf", size = 2465544 }, ] [[package]] @@ -1053,13 +1105,13 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "h5py" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/4f/9130151e3aa475b3e4e9a611bf608107fe5c72d277d74c4cf36f164b7c81/hdf5plugin-6.0.0.tar.gz", hash = "sha256:847ed9e96b451367a110f0ba64a3b260d38d64bbf3f25751858d3b56e094cfe0", size = 66372085, upload-time = "2025-10-08T18:16:28.423Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/4f/9130151e3aa475b3e4e9a611bf608107fe5c72d277d74c4cf36f164b7c81/hdf5plugin-6.0.0.tar.gz", hash = "sha256:847ed9e96b451367a110f0ba64a3b260d38d64bbf3f25751858d3b56e094cfe0", size = 66372085 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/13/15017f6210bfea843316d62f0f121e364e17bb129444ed803a256a213036/hdf5plugin-6.0.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:a59fbd5d4290a8a5334d82ccb4c6b9bfc7aaf586de7fedb88762e8601bc05fd4", size = 13339413, upload-time = "2025-10-08T18:16:10.656Z" }, - { url = "https://files.pythonhosted.org/packages/40/bf/d1f3765fb879820d7331e30e860b684f5b78d3ec17324e8f54130cbe560b/hdf5plugin-6.0.0-py3-none-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d301f4b9295872bacf277c70628d4c5e965ee47db762d8fde2d4849f201b9897", size = 42858563, upload-time = "2025-10-08T18:16:14.106Z" }, - { url = "https://files.pythonhosted.org/packages/0a/67/37d0b84fbbf26bf0d6a99a8f98bcd82bb6d437dc8cabee259fb3d7506ec7/hdf5plugin-6.0.0-py3-none-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:78b082ea355fe46bf5b396024de1fb662a1aaf9a5e11861ad61a5a2a6316d59d", size = 45126124, upload-time = "2025-10-08T18:16:17.992Z" }, - { url = "https://files.pythonhosted.org/packages/ed/2f/1046d464ad1db29a4f6c70ba4e19b39baa8a6542c719eaa4e765108f07f1/hdf5plugin-6.0.0-py3-none-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:79e0524d18ddc41c0cf2e1bb2e529d4e154c286f6a1bd85f3d44019d2a17574a", size = 44857273, upload-time = "2025-10-08T18:16:22.007Z" }, - { url = "https://files.pythonhosted.org/packages/61/b3/75478bdfee85533777de4204373f563aa7a1074355300743c3aedc33cac5/hdf5plugin-6.0.0-py3-none-win_amd64.whl", hash = "sha256:99866f90be1ceac5519e6e038669564be326c233618d59ba1f38c9dd8c32099e", size = 3379316, upload-time = "2025-10-08T18:16:25.007Z" }, + { url = "https://files.pythonhosted.org/packages/9c/13/15017f6210bfea843316d62f0f121e364e17bb129444ed803a256a213036/hdf5plugin-6.0.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:a59fbd5d4290a8a5334d82ccb4c6b9bfc7aaf586de7fedb88762e8601bc05fd4", size = 13339413 }, + { url = "https://files.pythonhosted.org/packages/40/bf/d1f3765fb879820d7331e30e860b684f5b78d3ec17324e8f54130cbe560b/hdf5plugin-6.0.0-py3-none-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d301f4b9295872bacf277c70628d4c5e965ee47db762d8fde2d4849f201b9897", size = 42858563 }, + { url = "https://files.pythonhosted.org/packages/0a/67/37d0b84fbbf26bf0d6a99a8f98bcd82bb6d437dc8cabee259fb3d7506ec7/hdf5plugin-6.0.0-py3-none-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:78b082ea355fe46bf5b396024de1fb662a1aaf9a5e11861ad61a5a2a6316d59d", size = 45126124 }, + { url = "https://files.pythonhosted.org/packages/ed/2f/1046d464ad1db29a4f6c70ba4e19b39baa8a6542c719eaa4e765108f07f1/hdf5plugin-6.0.0-py3-none-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:79e0524d18ddc41c0cf2e1bb2e529d4e154c286f6a1bd85f3d44019d2a17574a", size = 44857273 }, + { url = "https://files.pythonhosted.org/packages/61/b3/75478bdfee85533777de4204373f563aa7a1074355300743c3aedc33cac5/hdf5plugin-6.0.0-py3-none-win_amd64.whl", hash = "sha256:99866f90be1ceac5519e6e038669564be326c233618d59ba1f38c9dd8c32099e", size = 3379316 }, ] [[package]] @@ -1070,9 +1122,9 @@ dependencies = [ { name = "certifi" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784 }, ] [[package]] @@ -1085,27 +1137,27 @@ dependencies = [ { name = "httpcore" }, { name = "idna" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, ] [[package]] name = "identify" version = "2.6.16" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5b/8d/e8b97e6bd3fb6fb271346f7981362f1e04d6a7463abd0de79e1fda17c067/identify-2.6.16.tar.gz", hash = "sha256:846857203b5511bbe94d5a352a48ef2359532bc8f6727b5544077a0dcfb24980", size = 99360, upload-time = "2026-01-12T18:58:58.201Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/8d/e8b97e6bd3fb6fb271346f7981362f1e04d6a7463abd0de79e1fda17c067/identify-2.6.16.tar.gz", hash = "sha256:846857203b5511bbe94d5a352a48ef2359532bc8f6727b5544077a0dcfb24980", size = 99360 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/58/40fbbcefeda82364720eba5cf2270f98496bdfa19ea75b4cccae79c698e6/identify-2.6.16-py2.py3-none-any.whl", hash = "sha256:391ee4d77741d994189522896270b787aed8670389bfd60f326d677d64a6dfb0", size = 99202, upload-time = "2026-01-12T18:58:56.627Z" }, + { url = "https://files.pythonhosted.org/packages/b8/58/40fbbcefeda82364720eba5cf2270f98496bdfa19ea75b4cccae79c698e6/identify-2.6.16-py2.py3-none-any.whl", hash = "sha256:391ee4d77741d994189522896270b787aed8670389bfd60f326d677d64a6dfb0", size = 99202 }, ] [[package]] name = "idna" version = "3.11" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008 }, ] [[package]] @@ -1116,9 +1168,9 @@ dependencies = [ { name = "numpy" }, { name = "pillow" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/6f/606be632e37bf8d05b253e8626c2291d74c691ddc7bcdf7d6aaf33b32f6a/imageio-2.37.2.tar.gz", hash = "sha256:0212ef2727ac9caa5ca4b2c75ae89454312f440a756fcfc8ef1993e718f50f8a", size = 389600, upload-time = "2025-11-04T14:29:39.898Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/6f/606be632e37bf8d05b253e8626c2291d74c691ddc7bcdf7d6aaf33b32f6a/imageio-2.37.2.tar.gz", hash = "sha256:0212ef2727ac9caa5ca4b2c75ae89454312f440a756fcfc8ef1993e718f50f8a", size = 389600 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/fe/301e0936b79bcab4cacc7548bf2853fc28dced0a578bab1f7ef53c9aa75b/imageio-2.37.2-py3-none-any.whl", hash = "sha256:ad9adfb20335d718c03de457358ed69f141021a333c40a53e57273d8a5bd0b9b", size = 317646, upload-time = "2025-11-04T14:29:37.948Z" }, + { url = "https://files.pythonhosted.org/packages/fb/fe/301e0936b79bcab4cacc7548bf2853fc28dced0a578bab1f7ef53c9aa75b/imageio-2.37.2-py3-none-any.whl", hash = "sha256:ad9adfb20335d718c03de457358ed69f141021a333c40a53e57273d8a5bd0b9b", size = 317646 }, ] [[package]] @@ -1128,18 +1180,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "zipp", marker = "python_full_version < '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865 }, ] [[package]] name = "iniconfig" version = "2.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503 } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484 }, ] [[package]] @@ -1161,9 +1213,9 @@ dependencies = [ { name = "tornado" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ca/8d/b68b728e2d06b9e0051019640a40a9eb7a88fcd82c2e1b5ce70bef5ff044/ipykernel-7.2.0.tar.gz", hash = "sha256:18ed160b6dee2cbb16e5f3575858bc19d8f1fe6046a9a680c708494ce31d909e", size = 176046, upload-time = "2026-02-06T16:43:27.403Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/8d/b68b728e2d06b9e0051019640a40a9eb7a88fcd82c2e1b5ce70bef5ff044/ipykernel-7.2.0.tar.gz", hash = "sha256:18ed160b6dee2cbb16e5f3575858bc19d8f1fe6046a9a680c708494ce31d909e", size = 176046 } wheels = [ - { url = "https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl", hash = "sha256:3bbd4420d2b3cc105cbdf3756bfc04500b1e52f090a90716851f3916c62e1661", size = 118788, upload-time = "2026-02-06T16:43:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl", hash = "sha256:3bbd4420d2b3cc105cbdf3756bfc04500b1e52f090a90716851f3916c62e1661", size = 118788 }, ] [[package]] @@ -1183,9 +1235,9 @@ dependencies = [ { name = "traitlets" }, { name = "typing-extensions", marker = "python_full_version < '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a6/60/2111715ea11f39b1535bed6024b7dec7918b71e5e5d30855a5b503056b50/ipython-9.10.0.tar.gz", hash = "sha256:cd9e656be97618a0676d058134cd44e6dc7012c0e5cb36a9ce96a8c904adaf77", size = 4426526, upload-time = "2026-02-02T10:00:33.594Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/60/2111715ea11f39b1535bed6024b7dec7918b71e5e5d30855a5b503056b50/ipython-9.10.0.tar.gz", hash = "sha256:cd9e656be97618a0676d058134cd44e6dc7012c0e5cb36a9ce96a8c904adaf77", size = 4426526 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/aa/898dec789a05731cd5a9f50605b7b44a72bd198fd0d4528e11fc610177cc/ipython-9.10.0-py3-none-any.whl", hash = "sha256:c6ab68cc23bba8c7e18e9b932797014cc61ea7fd6f19de180ab9ba73e65ee58d", size = 622774, upload-time = "2026-02-02T10:00:31.503Z" }, + { url = "https://files.pythonhosted.org/packages/3d/aa/898dec789a05731cd5a9f50605b7b44a72bd198fd0d4528e11fc610177cc/ipython-9.10.0-py3-none-any.whl", hash = "sha256:c6ab68cc23bba8c7e18e9b932797014cc61ea7fd6f19de180ab9ba73e65ee58d", size = 622774 }, ] [[package]] @@ -1195,9 +1247,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074 }, ] [[package]] @@ -1211,9 +1263,9 @@ dependencies = [ { name = "traitlets" }, { name = "widgetsnbextension" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/ae/c5ce1edc1afe042eadb445e95b0671b03cee61895264357956e61c0d2ac0/ipywidgets-8.1.8.tar.gz", hash = "sha256:61f969306b95f85fba6b6986b7fe45d73124d1d9e3023a8068710d47a22ea668", size = 116739, upload-time = "2025-11-01T21:18:12.393Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/ae/c5ce1edc1afe042eadb445e95b0671b03cee61895264357956e61c0d2ac0/ipywidgets-8.1.8.tar.gz", hash = "sha256:61f969306b95f85fba6b6986b7fe45d73124d1d9e3023a8068710d47a22ea668", size = 116739 } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl", hash = "sha256:ecaca67aed704a338f88f67b1181b58f821ab5dc89c1f0f5ef99db43c1c2921e", size = 139808, upload-time = "2025-11-01T21:18:10.956Z" }, + { url = "https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl", hash = "sha256:ecaca67aed704a338f88f67b1181b58f821ab5dc89c1f0f5ef99db43c1c2921e", size = 139808 }, ] [[package]] @@ -1223,9 +1275,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "arrow" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7c/1a/3c8edc664e06e6bd06cce40c6b22da5f1429aa4224d0c590f3be21c91ead/isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", size = 11649, upload-time = "2020-11-01T11:00:00.312Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/1a/3c8edc664e06e6bd06cce40c6b22da5f1429aa4224d0c590f3be21c91ead/isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", size = 11649 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321, upload-time = "2020-11-01T10:59:58.02Z" }, + { url = "https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321 }, ] [[package]] @@ -1235,9 +1287,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "parso" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278 }, ] [[package]] @@ -1247,27 +1299,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, ] [[package]] name = "json5" version = "0.13.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/77/e8/a3f261a66e4663f22700bc8a17c08cb83e91fbf086726e7a228398968981/json5-0.13.0.tar.gz", hash = "sha256:b1edf8d487721c0bf64d83c28e91280781f6e21f4a797d3261c7c828d4c165bf", size = 52441, upload-time = "2026-01-01T19:42:14.99Z" } +sdist = { url = "https://files.pythonhosted.org/packages/77/e8/a3f261a66e4663f22700bc8a17c08cb83e91fbf086726e7a228398968981/json5-0.13.0.tar.gz", hash = "sha256:b1edf8d487721c0bf64d83c28e91280781f6e21f4a797d3261c7c828d4c165bf", size = 52441 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl", hash = "sha256:9a08e1dd65f6a4d4c6fa82d216cf2477349ec2346a38fd70cc11d2557499fbcc", size = 36163, upload-time = "2026-01-01T19:42:13.962Z" }, + { url = "https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl", hash = "sha256:9a08e1dd65f6a4d4c6fa82d216cf2477349ec2346a38fd70cc11d2557499fbcc", size = 36163 }, ] [[package]] name = "jsonpointer" version = "3.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114, upload-time = "2024-06-10T19:24:42.462Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114 } wheels = [ - { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" }, + { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595 }, ] [[package]] @@ -1280,9 +1332,9 @@ dependencies = [ { name = "referencing" }, { name = "rpds-py" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583 } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630 }, ] [package.optional-dependencies] @@ -1305,9 +1357,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "referencing" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855 } wheels = [ - { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437 }, ] [[package]] @@ -1321,9 +1373,9 @@ dependencies = [ { name = "tornado" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/05/e4/ba649102a3bc3fbca54e7239fb924fd434c766f855693d86de0b1f2bec81/jupyter_client-8.8.0.tar.gz", hash = "sha256:d556811419a4f2d96c869af34e854e3f059b7cc2d6d01a9cd9c85c267691be3e", size = 348020, upload-time = "2026-01-08T13:55:47.938Z" } +sdist = { url = "https://files.pythonhosted.org/packages/05/e4/ba649102a3bc3fbca54e7239fb924fd434c766f855693d86de0b1f2bec81/jupyter_client-8.8.0.tar.gz", hash = "sha256:d556811419a4f2d96c869af34e854e3f059b7cc2d6d01a9cd9c85c267691be3e", size = 348020 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl", hash = "sha256:f93a5b99c5e23a507b773d3a1136bd6e16c67883ccdbd9a829b0bbdb98cd7d7a", size = 107371, upload-time = "2026-01-08T13:55:45.562Z" }, + { url = "https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl", hash = "sha256:f93a5b99c5e23a507b773d3a1136bd6e16c67883ccdbd9a829b0bbdb98cd7d7a", size = 107371 }, ] [[package]] @@ -1334,9 +1386,9 @@ dependencies = [ { name = "platformdirs" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/02/49/9d1284d0dc65e2c757b74c6687b6d319b02f822ad039e5c512df9194d9dd/jupyter_core-5.9.1.tar.gz", hash = "sha256:4d09aaff303b9566c3ce657f580bd089ff5c91f5f89cf7d8846c3cdf465b5508", size = 89814, upload-time = "2025-10-16T19:19:18.444Z" } +sdist = { url = "https://files.pythonhosted.org/packages/02/49/9d1284d0dc65e2c757b74c6687b6d319b02f822ad039e5c512df9194d9dd/jupyter_core-5.9.1.tar.gz", hash = "sha256:4d09aaff303b9566c3ce657f580bd089ff5c91f5f89cf7d8846c3cdf465b5508", size = 89814 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl", hash = "sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407", size = 29032, upload-time = "2025-10-16T19:19:16.783Z" }, + { url = "https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl", hash = "sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407", size = 29032 }, ] [[package]] @@ -1353,9 +1405,9 @@ dependencies = [ { name = "rfc3986-validator" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9d/c3/306d090461e4cf3cd91eceaff84bede12a8e52cd821c2d20c9a4fd728385/jupyter_events-0.12.0.tar.gz", hash = "sha256:fc3fce98865f6784c9cd0a56a20644fc6098f21c8c33834a8d9fe383c17e554b", size = 62196, upload-time = "2025-02-03T17:23:41.485Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/c3/306d090461e4cf3cd91eceaff84bede12a8e52cd821c2d20c9a4fd728385/jupyter_events-0.12.0.tar.gz", hash = "sha256:fc3fce98865f6784c9cd0a56a20644fc6098f21c8c33834a8d9fe383c17e554b", size = 62196 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl", hash = "sha256:6464b2fa5ad10451c3d35fabc75eab39556ae1e2853ad0c0cc31b656731a97fb", size = 19430, upload-time = "2025-02-03T17:23:38.643Z" }, + { url = "https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl", hash = "sha256:6464b2fa5ad10451c3d35fabc75eab39556ae1e2853ad0c0cc31b656731a97fb", size = 19430 }, ] [[package]] @@ -1365,9 +1417,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jupyter-server" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/eb/5a/9066c9f8e94ee517133cd98dba393459a16cd48bba71a82f16a65415206c/jupyter_lsp-2.3.0.tar.gz", hash = "sha256:458aa59339dc868fb784d73364f17dbce8836e906cd75fd471a325cba02e0245", size = 54823, upload-time = "2025-08-27T17:47:34.671Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/5a/9066c9f8e94ee517133cd98dba393459a16cd48bba71a82f16a65415206c/jupyter_lsp-2.3.0.tar.gz", hash = "sha256:458aa59339dc868fb784d73364f17dbce8836e906cd75fd471a325cba02e0245", size = 54823 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl", hash = "sha256:e914a3cb2addf48b1c7710914771aaf1819d46b2e5a79b0f917b5478ec93f34f", size = 76687, upload-time = "2025-08-27T17:47:33.15Z" }, + { url = "https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl", hash = "sha256:e914a3cb2addf48b1c7710914771aaf1819d46b2e5a79b0f917b5478ec93f34f", size = 76687 }, ] [[package]] @@ -1395,9 +1447,9 @@ dependencies = [ { name = "traitlets" }, { name = "websocket-client" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5b/ac/e040ec363d7b6b1f11304cc9f209dac4517ece5d5e01821366b924a64a50/jupyter_server-2.17.0.tar.gz", hash = "sha256:c38ea898566964c888b4772ae1ed58eca84592e88251d2cfc4d171f81f7e99d5", size = 731949, upload-time = "2025-08-21T14:42:54.042Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/ac/e040ec363d7b6b1f11304cc9f209dac4517ece5d5e01821366b924a64a50/jupyter_server-2.17.0.tar.gz", hash = "sha256:c38ea898566964c888b4772ae1ed58eca84592e88251d2cfc4d171f81f7e99d5", size = 731949 } wheels = [ - { url = "https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl", hash = "sha256:e8cb9c7db4251f51ed307e329b81b72ccf2056ff82d50524debde1ee1870e13f", size = 388221, upload-time = "2025-08-21T14:42:52.034Z" }, + { url = "https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl", hash = "sha256:e8cb9c7db4251f51ed307e329b81b72ccf2056ff82d50524debde1ee1870e13f", size = 388221 }, ] [[package]] @@ -1408,9 +1460,9 @@ dependencies = [ { name = "pywinpty", marker = "os_name == 'nt'" }, { name = "terminado" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f4/a7/bcd0a9b0cbba88986fe944aaaf91bfda603e5a50bda8ed15123f381a3b2f/jupyter_server_terminals-0.5.4.tar.gz", hash = "sha256:bbda128ed41d0be9020349f9f1f2a4ab9952a73ed5f5ac9f1419794761fb87f5", size = 31770, upload-time = "2026-01-14T16:53:20.213Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/a7/bcd0a9b0cbba88986fe944aaaf91bfda603e5a50bda8ed15123f381a3b2f/jupyter_server_terminals-0.5.4.tar.gz", hash = "sha256:bbda128ed41d0be9020349f9f1f2a4ab9952a73ed5f5ac9f1419794761fb87f5", size = 31770 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl", hash = "sha256:55be353fc74a80bc7f3b20e6be50a55a61cd525626f578dcb66a5708e2007d14", size = 13704, upload-time = "2026-01-14T16:53:18.738Z" }, + { url = "https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl", hash = "sha256:55be353fc74a80bc7f3b20e6be50a55a61cd525626f578dcb66a5708e2007d14", size = 13704 }, ] [[package]] @@ -1432,18 +1484,18 @@ dependencies = [ { name = "tornado" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7c/6b/21af7c0512bdf67e0c54c121779a1f2a97a164a7657e13fced79db8fa5a0/jupyterlab-4.5.4.tar.gz", hash = "sha256:c215f48d8e4582bd2920ad61cc6a40d8ebfef7e5a517ae56b8a9413c9789fdfb", size = 23943597, upload-time = "2026-02-11T00:26:55.308Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/6b/21af7c0512bdf67e0c54c121779a1f2a97a164a7657e13fced79db8fa5a0/jupyterlab-4.5.4.tar.gz", hash = "sha256:c215f48d8e4582bd2920ad61cc6a40d8ebfef7e5a517ae56b8a9413c9789fdfb", size = 23943597 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/9f/a70972ece62ead2d81acc6223188f6d18a92f665ccce17796a0cdea4fcf5/jupyterlab-4.5.4-py3-none-any.whl", hash = "sha256:cc233f70539728534669fb0015331f2a3a87656207b3bb2d07916e9289192f12", size = 12391867, upload-time = "2026-02-11T00:26:51.23Z" }, + { url = "https://files.pythonhosted.org/packages/f5/9f/a70972ece62ead2d81acc6223188f6d18a92f665ccce17796a0cdea4fcf5/jupyterlab-4.5.4-py3-none-any.whl", hash = "sha256:cc233f70539728534669fb0015331f2a3a87656207b3bb2d07916e9289192f12", size = 12391867 }, ] [[package]] name = "jupyterlab-pygments" version = "0.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/90/51/9187be60d989df97f5f0aba133fa54e7300f17616e065d1ada7d7646b6d6/jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d", size = 512900, upload-time = "2023-11-23T09:26:37.44Z" } +sdist = { url = "https://files.pythonhosted.org/packages/90/51/9187be60d989df97f5f0aba133fa54e7300f17616e065d1ada7d7646b6d6/jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d", size = 512900 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884, upload-time = "2023-11-23T09:26:34.325Z" }, + { url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884 }, ] [[package]] @@ -1459,117 +1511,117 @@ dependencies = [ { name = "packaging" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d6/2c/90153f189e421e93c4bb4f9e3f59802a1f01abd2ac5cf40b152d7f735232/jupyterlab_server-2.28.0.tar.gz", hash = "sha256:35baa81898b15f93573e2deca50d11ac0ae407ebb688299d3a5213265033712c", size = 76996, upload-time = "2025-10-22T13:59:18.37Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/2c/90153f189e421e93c4bb4f9e3f59802a1f01abd2ac5cf40b152d7f735232/jupyterlab_server-2.28.0.tar.gz", hash = "sha256:35baa81898b15f93573e2deca50d11ac0ae407ebb688299d3a5213265033712c", size = 76996 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl", hash = "sha256:e4355b148fdcf34d312bbbc80f22467d6d20460e8b8736bf235577dd18506968", size = 59830, upload-time = "2025-10-22T13:59:16.767Z" }, + { url = "https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl", hash = "sha256:e4355b148fdcf34d312bbbc80f22467d6d20460e8b8736bf235577dd18506968", size = 59830 }, ] [[package]] name = "jupyterlab-widgets" version = "3.0.16" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/26/2d/ef58fed122b268c69c0aa099da20bc67657cdfb2e222688d5731bd5b971d/jupyterlab_widgets-3.0.16.tar.gz", hash = "sha256:423da05071d55cf27a9e602216d35a3a65a3e41cdf9c5d3b643b814ce38c19e0", size = 897423, upload-time = "2025-11-01T21:11:29.724Z" } +sdist = { url = "https://files.pythonhosted.org/packages/26/2d/ef58fed122b268c69c0aa099da20bc67657cdfb2e222688d5731bd5b971d/jupyterlab_widgets-3.0.16.tar.gz", hash = "sha256:423da05071d55cf27a9e602216d35a3a65a3e41cdf9c5d3b643b814ce38c19e0", size = 897423 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl", hash = "sha256:45fa36d9c6422cf2559198e4db481aa243c7a32d9926b500781c830c80f7ecf8", size = 914926, upload-time = "2025-11-01T21:11:28.008Z" }, + { url = "https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl", hash = "sha256:45fa36d9c6422cf2559198e4db481aa243c7a32d9926b500781c830c80f7ecf8", size = 914926 }, ] [[package]] name = "kiwisolver" version = "1.4.9" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/ab/c80b0d5a9d8a1a65f4f815f2afff9798b12c3b9f31f1d304dd233dd920e2/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16", size = 124167, upload-time = "2025-08-10T21:25:53.403Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c0/27fe1a68a39cf62472a300e2879ffc13c0538546c359b86f149cc19f6ac3/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089", size = 66579, upload-time = "2025-08-10T21:25:54.79Z" }, - { url = "https://files.pythonhosted.org/packages/31/a2/a12a503ac1fd4943c50f9822678e8015a790a13b5490354c68afb8489814/kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543", size = 65309, upload-time = "2025-08-10T21:25:55.76Z" }, - { url = "https://files.pythonhosted.org/packages/66/e1/e533435c0be77c3f64040d68d7a657771194a63c279f55573188161e81ca/kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61", size = 1435596, upload-time = "2025-08-10T21:25:56.861Z" }, - { url = "https://files.pythonhosted.org/packages/67/1e/51b73c7347f9aabdc7215aa79e8b15299097dc2f8e67dee2b095faca9cb0/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1", size = 1246548, upload-time = "2025-08-10T21:25:58.246Z" }, - { url = "https://files.pythonhosted.org/packages/21/aa/72a1c5d1e430294f2d32adb9542719cfb441b5da368d09d268c7757af46c/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872", size = 1263618, upload-time = "2025-08-10T21:25:59.857Z" }, - { url = "https://files.pythonhosted.org/packages/a3/af/db1509a9e79dbf4c260ce0cfa3903ea8945f6240e9e59d1e4deb731b1a40/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26", size = 1317437, upload-time = "2025-08-10T21:26:01.105Z" }, - { url = "https://files.pythonhosted.org/packages/e0/f2/3ea5ee5d52abacdd12013a94130436e19969fa183faa1e7c7fbc89e9a42f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028", size = 2195742, upload-time = "2025-08-10T21:26:02.675Z" }, - { url = "https://files.pythonhosted.org/packages/6f/9b/1efdd3013c2d9a2566aa6a337e9923a00590c516add9a1e89a768a3eb2fc/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771", size = 2290810, upload-time = "2025-08-10T21:26:04.009Z" }, - { url = "https://files.pythonhosted.org/packages/fb/e5/cfdc36109ae4e67361f9bc5b41323648cb24a01b9ade18784657e022e65f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a", size = 2461579, upload-time = "2025-08-10T21:26:05.317Z" }, - { url = "https://files.pythonhosted.org/packages/62/86/b589e5e86c7610842213994cdea5add00960076bef4ae290c5fa68589cac/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464", size = 2268071, upload-time = "2025-08-10T21:26:06.686Z" }, - { url = "https://files.pythonhosted.org/packages/3b/c6/f8df8509fd1eee6c622febe54384a96cfaf4d43bf2ccec7a0cc17e4715c9/kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2", size = 73840, upload-time = "2025-08-10T21:26:07.94Z" }, - { url = "https://files.pythonhosted.org/packages/e2/2d/16e0581daafd147bc11ac53f032a2b45eabac897f42a338d0a13c1e5c436/kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7", size = 65159, upload-time = "2025-08-10T21:26:09.048Z" }, - { url = "https://files.pythonhosted.org/packages/86/c9/13573a747838aeb1c76e3267620daa054f4152444d1f3d1a2324b78255b5/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", size = 123686, upload-time = "2025-08-10T21:26:10.034Z" }, - { url = "https://files.pythonhosted.org/packages/51/ea/2ecf727927f103ffd1739271ca19c424d0e65ea473fbaeea1c014aea93f6/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2", size = 66460, upload-time = "2025-08-10T21:26:11.083Z" }, - { url = "https://files.pythonhosted.org/packages/5b/5a/51f5464373ce2aeb5194508298a508b6f21d3867f499556263c64c621914/kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14", size = 64952, upload-time = "2025-08-10T21:26:12.058Z" }, - { url = "https://files.pythonhosted.org/packages/70/90/6d240beb0f24b74371762873e9b7f499f1e02166a2d9c5801f4dbf8fa12e/kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04", size = 1474756, upload-time = "2025-08-10T21:26:13.096Z" }, - { url = "https://files.pythonhosted.org/packages/12/42/f36816eaf465220f683fb711efdd1bbf7a7005a2473d0e4ed421389bd26c/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752", size = 1276404, upload-time = "2025-08-10T21:26:14.457Z" }, - { url = "https://files.pythonhosted.org/packages/2e/64/bc2de94800adc830c476dce44e9b40fd0809cddeef1fde9fcf0f73da301f/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77", size = 1294410, upload-time = "2025-08-10T21:26:15.73Z" }, - { url = "https://files.pythonhosted.org/packages/5f/42/2dc82330a70aa8e55b6d395b11018045e58d0bb00834502bf11509f79091/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198", size = 1343631, upload-time = "2025-08-10T21:26:17.045Z" }, - { url = "https://files.pythonhosted.org/packages/22/fd/f4c67a6ed1aab149ec5a8a401c323cee7a1cbe364381bb6c9c0d564e0e20/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d", size = 2224963, upload-time = "2025-08-10T21:26:18.737Z" }, - { url = "https://files.pythonhosted.org/packages/45/aa/76720bd4cb3713314677d9ec94dcc21ced3f1baf4830adde5bb9b2430a5f/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab", size = 2321295, upload-time = "2025-08-10T21:26:20.11Z" }, - { url = "https://files.pythonhosted.org/packages/80/19/d3ec0d9ab711242f56ae0dc2fc5d70e298bb4a1f9dfab44c027668c673a1/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2", size = 2487987, upload-time = "2025-08-10T21:26:21.49Z" }, - { url = "https://files.pythonhosted.org/packages/39/e9/61e4813b2c97e86b6fdbd4dd824bf72d28bcd8d4849b8084a357bc0dd64d/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145", size = 2291817, upload-time = "2025-08-10T21:26:22.812Z" }, - { url = "https://files.pythonhosted.org/packages/a0/41/85d82b0291db7504da3c2defe35c9a8a5c9803a730f297bd823d11d5fb77/kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54", size = 73895, upload-time = "2025-08-10T21:26:24.37Z" }, - { url = "https://files.pythonhosted.org/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992, upload-time = "2025-08-10T21:26:25.732Z" }, - { url = "https://files.pythonhosted.org/packages/31/c1/c2686cda909742ab66c7388e9a1a8521a59eb89f8bcfbee28fc980d07e24/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8", size = 123681, upload-time = "2025-08-10T21:26:26.725Z" }, - { url = "https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2", size = 66464, upload-time = "2025-08-10T21:26:27.733Z" }, - { url = "https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f", size = 64961, upload-time = "2025-08-10T21:26:28.729Z" }, - { url = "https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098", size = 1474607, upload-time = "2025-08-10T21:26:29.798Z" }, - { url = "https://files.pythonhosted.org/packages/d9/28/aac26d4c882f14de59041636292bc838db8961373825df23b8eeb807e198/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed", size = 1276546, upload-time = "2025-08-10T21:26:31.401Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ad/8bfc1c93d4cc565e5069162f610ba2f48ff39b7de4b5b8d93f69f30c4bed/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525", size = 1294482, upload-time = "2025-08-10T21:26:32.721Z" }, - { url = "https://files.pythonhosted.org/packages/da/f1/6aca55ff798901d8ce403206d00e033191f63d82dd708a186e0ed2067e9c/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78", size = 1343720, upload-time = "2025-08-10T21:26:34.032Z" }, - { url = "https://files.pythonhosted.org/packages/d1/91/eed031876c595c81d90d0f6fc681ece250e14bf6998c3d7c419466b523b7/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b", size = 2224907, upload-time = "2025-08-10T21:26:35.824Z" }, - { url = "https://files.pythonhosted.org/packages/e9/ec/4d1925f2e49617b9cca9c34bfa11adefad49d00db038e692a559454dfb2e/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799", size = 2321334, upload-time = "2025-08-10T21:26:37.534Z" }, - { url = "https://files.pythonhosted.org/packages/43/cb/450cd4499356f68802750c6ddc18647b8ea01ffa28f50d20598e0befe6e9/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3", size = 2488313, upload-time = "2025-08-10T21:26:39.191Z" }, - { url = "https://files.pythonhosted.org/packages/71/67/fc76242bd99f885651128a5d4fa6083e5524694b7c88b489b1b55fdc491d/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c", size = 2291970, upload-time = "2025-08-10T21:26:40.828Z" }, - { url = "https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d", size = 73894, upload-time = "2025-08-10T21:26:42.33Z" }, - { url = "https://files.pythonhosted.org/packages/95/38/dce480814d25b99a391abbddadc78f7c117c6da34be68ca8b02d5848b424/kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2", size = 64995, upload-time = "2025-08-10T21:26:43.889Z" }, - { url = "https://files.pythonhosted.org/packages/e2/37/7d218ce5d92dadc5ebdd9070d903e0c7cf7edfe03f179433ac4d13ce659c/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1", size = 126510, upload-time = "2025-08-10T21:26:44.915Z" }, - { url = "https://files.pythonhosted.org/packages/23/b0/e85a2b48233daef4b648fb657ebbb6f8367696a2d9548a00b4ee0eb67803/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1", size = 67903, upload-time = "2025-08-10T21:26:45.934Z" }, - { url = "https://files.pythonhosted.org/packages/44/98/f2425bc0113ad7de24da6bb4dae1343476e95e1d738be7c04d31a5d037fd/kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11", size = 66402, upload-time = "2025-08-10T21:26:47.101Z" }, - { url = "https://files.pythonhosted.org/packages/98/d8/594657886df9f34c4177cc353cc28ca7e6e5eb562d37ccc233bff43bbe2a/kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c", size = 1582135, upload-time = "2025-08-10T21:26:48.665Z" }, - { url = "https://files.pythonhosted.org/packages/5c/c6/38a115b7170f8b306fc929e166340c24958347308ea3012c2b44e7e295db/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197", size = 1389409, upload-time = "2025-08-10T21:26:50.335Z" }, - { url = "https://files.pythonhosted.org/packages/bf/3b/e04883dace81f24a568bcee6eb3001da4ba05114afa622ec9b6fafdc1f5e/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c", size = 1401763, upload-time = "2025-08-10T21:26:51.867Z" }, - { url = "https://files.pythonhosted.org/packages/9f/80/20ace48e33408947af49d7d15c341eaee69e4e0304aab4b7660e234d6288/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185", size = 1453643, upload-time = "2025-08-10T21:26:53.592Z" }, - { url = "https://files.pythonhosted.org/packages/64/31/6ce4380a4cd1f515bdda976a1e90e547ccd47b67a1546d63884463c92ca9/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748", size = 2330818, upload-time = "2025-08-10T21:26:55.051Z" }, - { url = "https://files.pythonhosted.org/packages/fa/e9/3f3fcba3bcc7432c795b82646306e822f3fd74df0ee81f0fa067a1f95668/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64", size = 2419963, upload-time = "2025-08-10T21:26:56.421Z" }, - { url = "https://files.pythonhosted.org/packages/99/43/7320c50e4133575c66e9f7dadead35ab22d7c012a3b09bb35647792b2a6d/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff", size = 2594639, upload-time = "2025-08-10T21:26:57.882Z" }, - { url = "https://files.pythonhosted.org/packages/65/d6/17ae4a270d4a987ef8a385b906d2bdfc9fce502d6dc0d3aea865b47f548c/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07", size = 2391741, upload-time = "2025-08-10T21:26:59.237Z" }, - { url = "https://files.pythonhosted.org/packages/2a/8f/8f6f491d595a9e5912971f3f863d81baddccc8a4d0c3749d6a0dd9ffc9df/kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c", size = 68646, upload-time = "2025-08-10T21:27:00.52Z" }, - { url = "https://files.pythonhosted.org/packages/6b/32/6cc0fbc9c54d06c2969faa9c1d29f5751a2e51809dd55c69055e62d9b426/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386", size = 123806, upload-time = "2025-08-10T21:27:01.537Z" }, - { url = "https://files.pythonhosted.org/packages/b2/dd/2bfb1d4a4823d92e8cbb420fe024b8d2167f72079b3bb941207c42570bdf/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552", size = 66605, upload-time = "2025-08-10T21:27:03.335Z" }, - { url = "https://files.pythonhosted.org/packages/f7/69/00aafdb4e4509c2ca6064646cba9cd4b37933898f426756adb2cb92ebbed/kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3", size = 64925, upload-time = "2025-08-10T21:27:04.339Z" }, - { url = "https://files.pythonhosted.org/packages/43/dc/51acc6791aa14e5cb6d8a2e28cefb0dc2886d8862795449d021334c0df20/kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58", size = 1472414, upload-time = "2025-08-10T21:27:05.437Z" }, - { url = "https://files.pythonhosted.org/packages/3d/bb/93fa64a81db304ac8a246f834d5094fae4b13baf53c839d6bb6e81177129/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4", size = 1281272, upload-time = "2025-08-10T21:27:07.063Z" }, - { url = "https://files.pythonhosted.org/packages/70/e6/6df102916960fb8d05069d4bd92d6d9a8202d5a3e2444494e7cd50f65b7a/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df", size = 1298578, upload-time = "2025-08-10T21:27:08.452Z" }, - { url = "https://files.pythonhosted.org/packages/7c/47/e142aaa612f5343736b087864dbaebc53ea8831453fb47e7521fa8658f30/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6", size = 1345607, upload-time = "2025-08-10T21:27:10.125Z" }, - { url = "https://files.pythonhosted.org/packages/54/89/d641a746194a0f4d1a3670fb900d0dbaa786fb98341056814bc3f058fa52/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5", size = 2230150, upload-time = "2025-08-10T21:27:11.484Z" }, - { url = "https://files.pythonhosted.org/packages/aa/6b/5ee1207198febdf16ac11f78c5ae40861b809cbe0e6d2a8d5b0b3044b199/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf", size = 2325979, upload-time = "2025-08-10T21:27:12.917Z" }, - { url = "https://files.pythonhosted.org/packages/fc/ff/b269eefd90f4ae14dcc74973d5a0f6d28d3b9bb1afd8c0340513afe6b39a/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5", size = 2491456, upload-time = "2025-08-10T21:27:14.353Z" }, - { url = "https://files.pythonhosted.org/packages/fc/d4/10303190bd4d30de547534601e259a4fbf014eed94aae3e5521129215086/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce", size = 2294621, upload-time = "2025-08-10T21:27:15.808Z" }, - { url = "https://files.pythonhosted.org/packages/28/e0/a9a90416fce5c0be25742729c2ea52105d62eda6c4be4d803c2a7be1fa50/kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7", size = 75417, upload-time = "2025-08-10T21:27:17.436Z" }, - { url = "https://files.pythonhosted.org/packages/1f/10/6949958215b7a9a264299a7db195564e87900f709db9245e4ebdd3c70779/kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c", size = 66582, upload-time = "2025-08-10T21:27:18.436Z" }, - { url = "https://files.pythonhosted.org/packages/ec/79/60e53067903d3bc5469b369fe0dfc6b3482e2133e85dae9daa9527535991/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548", size = 126514, upload-time = "2025-08-10T21:27:19.465Z" }, - { url = "https://files.pythonhosted.org/packages/25/d1/4843d3e8d46b072c12a38c97c57fab4608d36e13fe47d47ee96b4d61ba6f/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d", size = 67905, upload-time = "2025-08-10T21:27:20.51Z" }, - { url = "https://files.pythonhosted.org/packages/8c/ae/29ffcbd239aea8b93108de1278271ae764dfc0d803a5693914975f200596/kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c", size = 66399, upload-time = "2025-08-10T21:27:21.496Z" }, - { url = "https://files.pythonhosted.org/packages/a1/ae/d7ba902aa604152c2ceba5d352d7b62106bedbccc8e95c3934d94472bfa3/kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122", size = 1582197, upload-time = "2025-08-10T21:27:22.604Z" }, - { url = "https://files.pythonhosted.org/packages/f2/41/27c70d427eddb8bc7e4f16420a20fefc6f480312122a59a959fdfe0445ad/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64", size = 1390125, upload-time = "2025-08-10T21:27:24.036Z" }, - { url = "https://files.pythonhosted.org/packages/41/42/b3799a12bafc76d962ad69083f8b43b12bf4fe78b097b12e105d75c9b8f1/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134", size = 1402612, upload-time = "2025-08-10T21:27:25.773Z" }, - { url = "https://files.pythonhosted.org/packages/d2/b5/a210ea073ea1cfaca1bb5c55a62307d8252f531beb364e18aa1e0888b5a0/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370", size = 1453990, upload-time = "2025-08-10T21:27:27.089Z" }, - { url = "https://files.pythonhosted.org/packages/5f/ce/a829eb8c033e977d7ea03ed32fb3c1781b4fa0433fbadfff29e39c676f32/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21", size = 2331601, upload-time = "2025-08-10T21:27:29.343Z" }, - { url = "https://files.pythonhosted.org/packages/e0/4b/b5e97eb142eb9cd0072dacfcdcd31b1c66dc7352b0f7c7255d339c0edf00/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a", size = 2422041, upload-time = "2025-08-10T21:27:30.754Z" }, - { url = "https://files.pythonhosted.org/packages/40/be/8eb4cd53e1b85ba4edc3a9321666f12b83113a178845593307a3e7891f44/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f", size = 2594897, upload-time = "2025-08-10T21:27:32.803Z" }, - { url = "https://files.pythonhosted.org/packages/99/dd/841e9a66c4715477ea0abc78da039832fbb09dac5c35c58dc4c41a407b8a/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369", size = 2391835, upload-time = "2025-08-10T21:27:34.23Z" }, - { url = "https://files.pythonhosted.org/packages/0c/28/4b2e5c47a0da96896fdfdb006340ade064afa1e63675d01ea5ac222b6d52/kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891", size = 79988, upload-time = "2025-08-10T21:27:35.587Z" }, - { url = "https://files.pythonhosted.org/packages/80/be/3578e8afd18c88cdf9cb4cffde75a96d2be38c5a903f1ed0ceec061bd09e/kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32", size = 70260, upload-time = "2025-08-10T21:27:36.606Z" }, - { url = "https://files.pythonhosted.org/packages/a3/0f/36d89194b5a32c054ce93e586d4049b6c2c22887b0eb229c61c68afd3078/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5", size = 60104, upload-time = "2025-08-10T21:27:43.287Z" }, - { url = "https://files.pythonhosted.org/packages/52/ba/4ed75f59e4658fd21fe7dde1fee0ac397c678ec3befba3fe6482d987af87/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa", size = 58592, upload-time = "2025-08-10T21:27:44.314Z" }, - { url = "https://files.pythonhosted.org/packages/33/01/a8ea7c5ea32a9b45ceeaee051a04c8ed4320f5add3c51bfa20879b765b70/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2", size = 80281, upload-time = "2025-08-10T21:27:45.369Z" }, - { url = "https://files.pythonhosted.org/packages/da/e3/dbd2ecdce306f1d07a1aaf324817ee993aab7aee9db47ceac757deabafbe/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f", size = 78009, upload-time = "2025-08-10T21:27:46.376Z" }, - { url = "https://files.pythonhosted.org/packages/da/e9/0d4add7873a73e462aeb45c036a2dead2562b825aa46ba326727b3f31016/kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1", size = 73929, upload-time = "2025-08-10T21:27:48.236Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/ab/c80b0d5a9d8a1a65f4f815f2afff9798b12c3b9f31f1d304dd233dd920e2/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16", size = 124167 }, + { url = "https://files.pythonhosted.org/packages/a0/c0/27fe1a68a39cf62472a300e2879ffc13c0538546c359b86f149cc19f6ac3/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089", size = 66579 }, + { url = "https://files.pythonhosted.org/packages/31/a2/a12a503ac1fd4943c50f9822678e8015a790a13b5490354c68afb8489814/kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543", size = 65309 }, + { url = "https://files.pythonhosted.org/packages/66/e1/e533435c0be77c3f64040d68d7a657771194a63c279f55573188161e81ca/kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61", size = 1435596 }, + { url = "https://files.pythonhosted.org/packages/67/1e/51b73c7347f9aabdc7215aa79e8b15299097dc2f8e67dee2b095faca9cb0/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1", size = 1246548 }, + { url = "https://files.pythonhosted.org/packages/21/aa/72a1c5d1e430294f2d32adb9542719cfb441b5da368d09d268c7757af46c/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872", size = 1263618 }, + { url = "https://files.pythonhosted.org/packages/a3/af/db1509a9e79dbf4c260ce0cfa3903ea8945f6240e9e59d1e4deb731b1a40/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26", size = 1317437 }, + { url = "https://files.pythonhosted.org/packages/e0/f2/3ea5ee5d52abacdd12013a94130436e19969fa183faa1e7c7fbc89e9a42f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028", size = 2195742 }, + { url = "https://files.pythonhosted.org/packages/6f/9b/1efdd3013c2d9a2566aa6a337e9923a00590c516add9a1e89a768a3eb2fc/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771", size = 2290810 }, + { url = "https://files.pythonhosted.org/packages/fb/e5/cfdc36109ae4e67361f9bc5b41323648cb24a01b9ade18784657e022e65f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a", size = 2461579 }, + { url = "https://files.pythonhosted.org/packages/62/86/b589e5e86c7610842213994cdea5add00960076bef4ae290c5fa68589cac/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464", size = 2268071 }, + { url = "https://files.pythonhosted.org/packages/3b/c6/f8df8509fd1eee6c622febe54384a96cfaf4d43bf2ccec7a0cc17e4715c9/kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2", size = 73840 }, + { url = "https://files.pythonhosted.org/packages/e2/2d/16e0581daafd147bc11ac53f032a2b45eabac897f42a338d0a13c1e5c436/kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7", size = 65159 }, + { url = "https://files.pythonhosted.org/packages/86/c9/13573a747838aeb1c76e3267620daa054f4152444d1f3d1a2324b78255b5/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", size = 123686 }, + { url = "https://files.pythonhosted.org/packages/51/ea/2ecf727927f103ffd1739271ca19c424d0e65ea473fbaeea1c014aea93f6/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2", size = 66460 }, + { url = "https://files.pythonhosted.org/packages/5b/5a/51f5464373ce2aeb5194508298a508b6f21d3867f499556263c64c621914/kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14", size = 64952 }, + { url = "https://files.pythonhosted.org/packages/70/90/6d240beb0f24b74371762873e9b7f499f1e02166a2d9c5801f4dbf8fa12e/kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04", size = 1474756 }, + { url = "https://files.pythonhosted.org/packages/12/42/f36816eaf465220f683fb711efdd1bbf7a7005a2473d0e4ed421389bd26c/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752", size = 1276404 }, + { url = "https://files.pythonhosted.org/packages/2e/64/bc2de94800adc830c476dce44e9b40fd0809cddeef1fde9fcf0f73da301f/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77", size = 1294410 }, + { url = "https://files.pythonhosted.org/packages/5f/42/2dc82330a70aa8e55b6d395b11018045e58d0bb00834502bf11509f79091/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198", size = 1343631 }, + { url = "https://files.pythonhosted.org/packages/22/fd/f4c67a6ed1aab149ec5a8a401c323cee7a1cbe364381bb6c9c0d564e0e20/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d", size = 2224963 }, + { url = "https://files.pythonhosted.org/packages/45/aa/76720bd4cb3713314677d9ec94dcc21ced3f1baf4830adde5bb9b2430a5f/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab", size = 2321295 }, + { url = "https://files.pythonhosted.org/packages/80/19/d3ec0d9ab711242f56ae0dc2fc5d70e298bb4a1f9dfab44c027668c673a1/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2", size = 2487987 }, + { url = "https://files.pythonhosted.org/packages/39/e9/61e4813b2c97e86b6fdbd4dd824bf72d28bcd8d4849b8084a357bc0dd64d/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145", size = 2291817 }, + { url = "https://files.pythonhosted.org/packages/a0/41/85d82b0291db7504da3c2defe35c9a8a5c9803a730f297bd823d11d5fb77/kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54", size = 73895 }, + { url = "https://files.pythonhosted.org/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992 }, + { url = "https://files.pythonhosted.org/packages/31/c1/c2686cda909742ab66c7388e9a1a8521a59eb89f8bcfbee28fc980d07e24/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8", size = 123681 }, + { url = "https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2", size = 66464 }, + { url = "https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f", size = 64961 }, + { url = "https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098", size = 1474607 }, + { url = "https://files.pythonhosted.org/packages/d9/28/aac26d4c882f14de59041636292bc838db8961373825df23b8eeb807e198/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed", size = 1276546 }, + { url = "https://files.pythonhosted.org/packages/8b/ad/8bfc1c93d4cc565e5069162f610ba2f48ff39b7de4b5b8d93f69f30c4bed/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525", size = 1294482 }, + { url = "https://files.pythonhosted.org/packages/da/f1/6aca55ff798901d8ce403206d00e033191f63d82dd708a186e0ed2067e9c/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78", size = 1343720 }, + { url = "https://files.pythonhosted.org/packages/d1/91/eed031876c595c81d90d0f6fc681ece250e14bf6998c3d7c419466b523b7/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b", size = 2224907 }, + { url = "https://files.pythonhosted.org/packages/e9/ec/4d1925f2e49617b9cca9c34bfa11adefad49d00db038e692a559454dfb2e/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799", size = 2321334 }, + { url = "https://files.pythonhosted.org/packages/43/cb/450cd4499356f68802750c6ddc18647b8ea01ffa28f50d20598e0befe6e9/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3", size = 2488313 }, + { url = "https://files.pythonhosted.org/packages/71/67/fc76242bd99f885651128a5d4fa6083e5524694b7c88b489b1b55fdc491d/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c", size = 2291970 }, + { url = "https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d", size = 73894 }, + { url = "https://files.pythonhosted.org/packages/95/38/dce480814d25b99a391abbddadc78f7c117c6da34be68ca8b02d5848b424/kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2", size = 64995 }, + { url = "https://files.pythonhosted.org/packages/e2/37/7d218ce5d92dadc5ebdd9070d903e0c7cf7edfe03f179433ac4d13ce659c/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1", size = 126510 }, + { url = "https://files.pythonhosted.org/packages/23/b0/e85a2b48233daef4b648fb657ebbb6f8367696a2d9548a00b4ee0eb67803/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1", size = 67903 }, + { url = "https://files.pythonhosted.org/packages/44/98/f2425bc0113ad7de24da6bb4dae1343476e95e1d738be7c04d31a5d037fd/kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11", size = 66402 }, + { url = "https://files.pythonhosted.org/packages/98/d8/594657886df9f34c4177cc353cc28ca7e6e5eb562d37ccc233bff43bbe2a/kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c", size = 1582135 }, + { url = "https://files.pythonhosted.org/packages/5c/c6/38a115b7170f8b306fc929e166340c24958347308ea3012c2b44e7e295db/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197", size = 1389409 }, + { url = "https://files.pythonhosted.org/packages/bf/3b/e04883dace81f24a568bcee6eb3001da4ba05114afa622ec9b6fafdc1f5e/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c", size = 1401763 }, + { url = "https://files.pythonhosted.org/packages/9f/80/20ace48e33408947af49d7d15c341eaee69e4e0304aab4b7660e234d6288/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185", size = 1453643 }, + { url = "https://files.pythonhosted.org/packages/64/31/6ce4380a4cd1f515bdda976a1e90e547ccd47b67a1546d63884463c92ca9/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748", size = 2330818 }, + { url = "https://files.pythonhosted.org/packages/fa/e9/3f3fcba3bcc7432c795b82646306e822f3fd74df0ee81f0fa067a1f95668/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64", size = 2419963 }, + { url = "https://files.pythonhosted.org/packages/99/43/7320c50e4133575c66e9f7dadead35ab22d7c012a3b09bb35647792b2a6d/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff", size = 2594639 }, + { url = "https://files.pythonhosted.org/packages/65/d6/17ae4a270d4a987ef8a385b906d2bdfc9fce502d6dc0d3aea865b47f548c/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07", size = 2391741 }, + { url = "https://files.pythonhosted.org/packages/2a/8f/8f6f491d595a9e5912971f3f863d81baddccc8a4d0c3749d6a0dd9ffc9df/kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c", size = 68646 }, + { url = "https://files.pythonhosted.org/packages/6b/32/6cc0fbc9c54d06c2969faa9c1d29f5751a2e51809dd55c69055e62d9b426/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386", size = 123806 }, + { url = "https://files.pythonhosted.org/packages/b2/dd/2bfb1d4a4823d92e8cbb420fe024b8d2167f72079b3bb941207c42570bdf/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552", size = 66605 }, + { url = "https://files.pythonhosted.org/packages/f7/69/00aafdb4e4509c2ca6064646cba9cd4b37933898f426756adb2cb92ebbed/kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3", size = 64925 }, + { url = "https://files.pythonhosted.org/packages/43/dc/51acc6791aa14e5cb6d8a2e28cefb0dc2886d8862795449d021334c0df20/kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58", size = 1472414 }, + { url = "https://files.pythonhosted.org/packages/3d/bb/93fa64a81db304ac8a246f834d5094fae4b13baf53c839d6bb6e81177129/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4", size = 1281272 }, + { url = "https://files.pythonhosted.org/packages/70/e6/6df102916960fb8d05069d4bd92d6d9a8202d5a3e2444494e7cd50f65b7a/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df", size = 1298578 }, + { url = "https://files.pythonhosted.org/packages/7c/47/e142aaa612f5343736b087864dbaebc53ea8831453fb47e7521fa8658f30/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6", size = 1345607 }, + { url = "https://files.pythonhosted.org/packages/54/89/d641a746194a0f4d1a3670fb900d0dbaa786fb98341056814bc3f058fa52/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5", size = 2230150 }, + { url = "https://files.pythonhosted.org/packages/aa/6b/5ee1207198febdf16ac11f78c5ae40861b809cbe0e6d2a8d5b0b3044b199/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf", size = 2325979 }, + { url = "https://files.pythonhosted.org/packages/fc/ff/b269eefd90f4ae14dcc74973d5a0f6d28d3b9bb1afd8c0340513afe6b39a/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5", size = 2491456 }, + { url = "https://files.pythonhosted.org/packages/fc/d4/10303190bd4d30de547534601e259a4fbf014eed94aae3e5521129215086/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce", size = 2294621 }, + { url = "https://files.pythonhosted.org/packages/28/e0/a9a90416fce5c0be25742729c2ea52105d62eda6c4be4d803c2a7be1fa50/kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7", size = 75417 }, + { url = "https://files.pythonhosted.org/packages/1f/10/6949958215b7a9a264299a7db195564e87900f709db9245e4ebdd3c70779/kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c", size = 66582 }, + { url = "https://files.pythonhosted.org/packages/ec/79/60e53067903d3bc5469b369fe0dfc6b3482e2133e85dae9daa9527535991/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548", size = 126514 }, + { url = "https://files.pythonhosted.org/packages/25/d1/4843d3e8d46b072c12a38c97c57fab4608d36e13fe47d47ee96b4d61ba6f/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d", size = 67905 }, + { url = "https://files.pythonhosted.org/packages/8c/ae/29ffcbd239aea8b93108de1278271ae764dfc0d803a5693914975f200596/kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c", size = 66399 }, + { url = "https://files.pythonhosted.org/packages/a1/ae/d7ba902aa604152c2ceba5d352d7b62106bedbccc8e95c3934d94472bfa3/kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122", size = 1582197 }, + { url = "https://files.pythonhosted.org/packages/f2/41/27c70d427eddb8bc7e4f16420a20fefc6f480312122a59a959fdfe0445ad/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64", size = 1390125 }, + { url = "https://files.pythonhosted.org/packages/41/42/b3799a12bafc76d962ad69083f8b43b12bf4fe78b097b12e105d75c9b8f1/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134", size = 1402612 }, + { url = "https://files.pythonhosted.org/packages/d2/b5/a210ea073ea1cfaca1bb5c55a62307d8252f531beb364e18aa1e0888b5a0/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370", size = 1453990 }, + { url = "https://files.pythonhosted.org/packages/5f/ce/a829eb8c033e977d7ea03ed32fb3c1781b4fa0433fbadfff29e39c676f32/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21", size = 2331601 }, + { url = "https://files.pythonhosted.org/packages/e0/4b/b5e97eb142eb9cd0072dacfcdcd31b1c66dc7352b0f7c7255d339c0edf00/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a", size = 2422041 }, + { url = "https://files.pythonhosted.org/packages/40/be/8eb4cd53e1b85ba4edc3a9321666f12b83113a178845593307a3e7891f44/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f", size = 2594897 }, + { url = "https://files.pythonhosted.org/packages/99/dd/841e9a66c4715477ea0abc78da039832fbb09dac5c35c58dc4c41a407b8a/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369", size = 2391835 }, + { url = "https://files.pythonhosted.org/packages/0c/28/4b2e5c47a0da96896fdfdb006340ade064afa1e63675d01ea5ac222b6d52/kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891", size = 79988 }, + { url = "https://files.pythonhosted.org/packages/80/be/3578e8afd18c88cdf9cb4cffde75a96d2be38c5a903f1ed0ceec061bd09e/kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32", size = 70260 }, + { url = "https://files.pythonhosted.org/packages/a3/0f/36d89194b5a32c054ce93e586d4049b6c2c22887b0eb229c61c68afd3078/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5", size = 60104 }, + { url = "https://files.pythonhosted.org/packages/52/ba/4ed75f59e4658fd21fe7dde1fee0ac397c678ec3befba3fe6482d987af87/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa", size = 58592 }, + { url = "https://files.pythonhosted.org/packages/33/01/a8ea7c5ea32a9b45ceeaee051a04c8ed4320f5add3c51bfa20879b765b70/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2", size = 80281 }, + { url = "https://files.pythonhosted.org/packages/da/e3/dbd2ecdce306f1d07a1aaf324817ee993aab7aee9db47ceac757deabafbe/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f", size = 78009 }, + { url = "https://files.pythonhosted.org/packages/da/e9/0d4add7873a73e462aeb45c036a2dead2562b825aa46ba326727b3f31016/kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1", size = 73929 }, ] [[package]] name = "lark" version = "1.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/da/34/28fff3ab31ccff1fd4f6c7c7b0ceb2b6968d8ea4950663eadcb5720591a0/lark-1.3.1.tar.gz", hash = "sha256:b426a7a6d6d53189d318f2b6236ab5d6429eaf09259f1ca33eb716eed10d2905", size = 382732, upload-time = "2025-10-27T18:25:56.653Z" } +sdist = { url = "https://files.pythonhosted.org/packages/da/34/28fff3ab31ccff1fd4f6c7c7b0ceb2b6968d8ea4950663eadcb5720591a0/lark-1.3.1.tar.gz", hash = "sha256:b426a7a6d6d53189d318f2b6236ab5d6429eaf09259f1ca33eb716eed10d2905", size = 382732 } wheels = [ - { url = "https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl", hash = "sha256:c629b661023a014c37da873b4ff58a817398d12635d3bbb2c5a03be7fe5d1e12", size = 113151, upload-time = "2025-10-27T18:25:54.882Z" }, + { url = "https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl", hash = "sha256:c629b661023a014c37da873b4ff58a817398d12635d3bbb2c5a03be7fe5d1e12", size = 113151 }, ] [[package]] @@ -1579,9 +1631,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6f/6b/c875b30a1ba490860c93da4cabf479e03f584eba06fe5963f6f6644653d8/lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1", size = 15431, upload-time = "2024-04-05T13:03:12.261Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6b/c875b30a1ba490860c93da4cabf479e03f584eba06fe5963f6f6644653d8/lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1", size = 15431 } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc", size = 12097, upload-time = "2024-04-05T13:03:10.514Z" }, + { url = "https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc", size = 12097 }, ] [[package]] @@ -1592,18 +1644,18 @@ dependencies = [ { name = "packaging" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f1/45/7fa8f56b17dc0f0a41ec70dd307ecd6787254483549843bef4c30ab5adce/lightning_utilities-0.15.3.tar.gz", hash = "sha256:792ae0204c79f6859721ac7f386c237a33b0ed06ba775009cb894e010a842033", size = 33553, upload-time = "2026-02-22T14:48:53.348Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/45/7fa8f56b17dc0f0a41ec70dd307ecd6787254483549843bef4c30ab5adce/lightning_utilities-0.15.3.tar.gz", hash = "sha256:792ae0204c79f6859721ac7f386c237a33b0ed06ba775009cb894e010a842033", size = 33553 } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/f4/ead6e0e37209b07c9baa3e984ccdb0348ca370b77cea3aaea8ddbb097e00/lightning_utilities-0.15.3-py3-none-any.whl", hash = "sha256:6c55f1bee70084a1cbeaa41ada96e4b3a0fea5909e844dd335bd80f5a73c5f91", size = 31906, upload-time = "2026-02-22T14:48:52.488Z" }, + { url = "https://files.pythonhosted.org/packages/25/f4/ead6e0e37209b07c9baa3e984ccdb0348ca370b77cea3aaea8ddbb097e00/lightning_utilities-0.15.3-py3-none-any.whl", hash = "sha256:6c55f1bee70084a1cbeaa41ada96e4b3a0fea5909e844dd335bd80f5a73c5f91", size = 31906 }, ] [[package]] name = "locket" version = "1.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2f/83/97b29fe05cb6ae28d2dbd30b81e2e402a3eed5f460c26e9eaa5895ceacf5/locket-1.0.0.tar.gz", hash = "sha256:5c0d4c052a8bbbf750e056a8e65ccd309086f4f0f18a2eac306a8dfa4112a632", size = 4350, upload-time = "2022-04-20T22:04:44.312Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/83/97b29fe05cb6ae28d2dbd30b81e2e402a3eed5f460c26e9eaa5895ceacf5/locket-1.0.0.tar.gz", hash = "sha256:5c0d4c052a8bbbf750e056a8e65ccd309086f4f0f18a2eac306a8dfa4112a632", size = 4350 } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl", hash = "sha256:b6c819a722f7b6bd955b80781788e4a66a55628b858d347536b7e81325a3a5e3", size = 4398, upload-time = "2022-04-20T22:04:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl", hash = "sha256:b6c819a722f7b6bd955b80781788e4a66a55628b858d347536b7e81325a3a5e3", size = 4398 }, ] [[package]] @@ -1613,92 +1665,92 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474, upload-time = "2025-04-10T12:44:31.16Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474 } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" }, + { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509 }, ] [[package]] name = "markdown" version = "3.10.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805 } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, + { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180 }, ] [[package]] name = "markupsafe" version = "3.0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, - { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, - { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, - { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, - { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, - { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, - { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, - { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, - { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, - { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, - { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, - { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, - { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, - { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, - { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, - { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, - { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, - { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, - { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, - { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, - { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, - { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, - { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, - { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, - { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, - { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, - { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, - { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, - { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, - { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, - { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, - { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, - { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, - { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, - { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, - { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, - { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, - { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, - { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, - { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, - { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, - { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, - { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, - { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, - { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, - { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, - { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, - { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, - { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, - { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, - { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, - { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, - { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, - { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, - { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, - { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, - { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, - { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, - { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, - { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, - { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, - { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, - { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, - { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, - { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631 }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058 }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287 }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940 }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887 }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692 }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471 }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923 }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572 }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077 }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876 }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615 }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020 }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332 }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947 }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962 }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760 }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529 }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015 }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540 }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105 }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906 }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622 }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029 }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374 }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980 }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990 }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784 }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588 }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041 }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543 }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113 }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911 }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658 }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066 }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639 }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569 }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284 }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801 }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769 }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642 }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612 }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200 }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973 }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619 }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029 }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408 }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005 }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048 }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821 }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606 }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043 }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747 }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341 }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073 }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661 }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069 }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670 }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598 }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261 }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835 }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733 }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672 }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819 }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426 }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146 }, ] [[package]] @@ -1716,53 +1768,53 @@ dependencies = [ { name = "pyparsing" }, { name = "python-dateutil" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8a/76/d3c6e3a13fe484ebe7718d14e269c9569c4eb0020a968a327acb3b9a8fe6/matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3", size = 34806269, upload-time = "2025-12-10T22:56:51.155Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/86/de7e3a1cdcfc941483af70609edc06b83e7c8a0e0dc9ac325200a3f4d220/matplotlib-3.10.8-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6be43b667360fef5c754dda5d25a32e6307a03c204f3c0fc5468b78fa87b4160", size = 8251215, upload-time = "2025-12-10T22:55:16.175Z" }, - { url = "https://files.pythonhosted.org/packages/fd/14/baad3222f424b19ce6ad243c71de1ad9ec6b2e4eb1e458a48fdc6d120401/matplotlib-3.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2b336e2d91a3d7006864e0990c83b216fcdca64b5a6484912902cef87313d78", size = 8139625, upload-time = "2025-12-10T22:55:17.712Z" }, - { url = "https://files.pythonhosted.org/packages/8f/a0/7024215e95d456de5883e6732e708d8187d9753a21d32f8ddb3befc0c445/matplotlib-3.10.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efb30e3baaea72ce5928e32bab719ab4770099079d66726a62b11b1ef7273be4", size = 8712614, upload-time = "2025-12-10T22:55:20.8Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f4/b8347351da9a5b3f41e26cf547252d861f685c6867d179a7c9d60ad50189/matplotlib-3.10.8-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d56a1efd5bfd61486c8bc968fa18734464556f0fb8e51690f4ac25d85cbbbbc2", size = 9540997, upload-time = "2025-12-10T22:55:23.258Z" }, - { url = "https://files.pythonhosted.org/packages/9e/c0/c7b914e297efe0bc36917bf216b2acb91044b91e930e878ae12981e461e5/matplotlib-3.10.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238b7ce5717600615c895050239ec955d91f321c209dd110db988500558e70d6", size = 9596825, upload-time = "2025-12-10T22:55:25.217Z" }, - { url = "https://files.pythonhosted.org/packages/6f/d3/a4bbc01c237ab710a1f22b4da72f4ff6d77eb4c7735ea9811a94ae239067/matplotlib-3.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:18821ace09c763ec93aef5eeff087ee493a24051936d7b9ebcad9662f66501f9", size = 8135090, upload-time = "2025-12-10T22:55:27.162Z" }, - { url = "https://files.pythonhosted.org/packages/89/dd/a0b6588f102beab33ca6f5218b31725216577b2a24172f327eaf6417d5c9/matplotlib-3.10.8-cp311-cp311-win_arm64.whl", hash = "sha256:bab485bcf8b1c7d2060b4fcb6fc368a9e6f4cd754c9c2fea281f4be21df394a2", size = 8012377, upload-time = "2025-12-10T22:55:29.185Z" }, - { url = "https://files.pythonhosted.org/packages/9e/67/f997cdcbb514012eb0d10cd2b4b332667997fb5ebe26b8d41d04962fa0e6/matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a", size = 8260453, upload-time = "2025-12-10T22:55:30.709Z" }, - { url = "https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58", size = 8148321, upload-time = "2025-12-10T22:55:33.265Z" }, - { url = "https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04", size = 8716944, upload-time = "2025-12-10T22:55:34.922Z" }, - { url = "https://files.pythonhosted.org/packages/00/f9/7638f5cc82ec8a7aa005de48622eecc3ed7c9854b96ba15bd76b7fd27574/matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f", size = 9550099, upload-time = "2025-12-10T22:55:36.789Z" }, - { url = "https://files.pythonhosted.org/packages/57/61/78cd5920d35b29fd2a0fe894de8adf672ff52939d2e9b43cb83cd5ce1bc7/matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466", size = 9613040, upload-time = "2025-12-10T22:55:38.715Z" }, - { url = "https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf", size = 8142717, upload-time = "2025-12-10T22:55:41.103Z" }, - { url = "https://files.pythonhosted.org/packages/f1/76/934db220026b5fef85f45d51a738b91dea7d70207581063cd9bd8fafcf74/matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b", size = 8012751, upload-time = "2025-12-10T22:55:42.684Z" }, - { url = "https://files.pythonhosted.org/packages/3d/b9/15fd5541ef4f5b9a17eefd379356cf12175fe577424e7b1d80676516031a/matplotlib-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3f2e409836d7f5ac2f1c013110a4d50b9f7edc26328c108915f9075d7d7a91b6", size = 8261076, upload-time = "2025-12-10T22:55:44.648Z" }, - { url = "https://files.pythonhosted.org/packages/8d/a0/2ba3473c1b66b9c74dc7107c67e9008cb1782edbe896d4c899d39ae9cf78/matplotlib-3.10.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56271f3dac49a88d7fca5060f004d9d22b865f743a12a23b1e937a0be4818ee1", size = 8148794, upload-time = "2025-12-10T22:55:46.252Z" }, - { url = "https://files.pythonhosted.org/packages/75/97/a471f1c3eb1fd6f6c24a31a5858f443891d5127e63a7788678d14e249aea/matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0a7f52498f72f13d4a25ea70f35f4cb60642b466cbb0a9be951b5bc3f45a486", size = 8718474, upload-time = "2025-12-10T22:55:47.864Z" }, - { url = "https://files.pythonhosted.org/packages/01/be/cd478f4b66f48256f42927d0acbcd63a26a893136456cd079c0cc24fbabf/matplotlib-3.10.8-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:646d95230efb9ca614a7a594d4fcacde0ac61d25e37dd51710b36477594963ce", size = 9549637, upload-time = "2025-12-10T22:55:50.048Z" }, - { url = "https://files.pythonhosted.org/packages/5d/7c/8dc289776eae5109e268c4fb92baf870678dc048a25d4ac903683b86d5bf/matplotlib-3.10.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f89c151aab2e2e23cb3fe0acad1e8b82841fd265379c4cecd0f3fcb34c15e0f6", size = 9613678, upload-time = "2025-12-10T22:55:52.21Z" }, - { url = "https://files.pythonhosted.org/packages/64/40/37612487cc8a437d4dd261b32ca21fe2d79510fe74af74e1f42becb1bdb8/matplotlib-3.10.8-cp313-cp313-win_amd64.whl", hash = "sha256:e8ea3e2d4066083e264e75c829078f9e149fa119d27e19acd503de65e0b13149", size = 8142686, upload-time = "2025-12-10T22:55:54.253Z" }, - { url = "https://files.pythonhosted.org/packages/66/52/8d8a8730e968185514680c2a6625943f70269509c3dcfc0dcf7d75928cb8/matplotlib-3.10.8-cp313-cp313-win_arm64.whl", hash = "sha256:c108a1d6fa78a50646029cb6d49808ff0fc1330fda87fa6f6250c6b5369b6645", size = 8012917, upload-time = "2025-12-10T22:55:56.268Z" }, - { url = "https://files.pythonhosted.org/packages/b5/27/51fe26e1062f298af5ef66343d8ef460e090a27fea73036c76c35821df04/matplotlib-3.10.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ad3d9833a64cf48cc4300f2b406c3d0f4f4724a91c0bd5640678a6ba7c102077", size = 8305679, upload-time = "2025-12-10T22:55:57.856Z" }, - { url = "https://files.pythonhosted.org/packages/2c/1e/4de865bc591ac8e3062e835f42dd7fe7a93168d519557837f0e37513f629/matplotlib-3.10.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:eb3823f11823deade26ce3b9f40dcb4a213da7a670013929f31d5f5ed1055b22", size = 8198336, upload-time = "2025-12-10T22:55:59.371Z" }, - { url = "https://files.pythonhosted.org/packages/c6/cb/2f7b6e75fb4dce87ef91f60cac4f6e34f4c145ab036a22318ec837971300/matplotlib-3.10.8-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d9050fee89a89ed57b4fb2c1bfac9a3d0c57a0d55aed95949eedbc42070fea39", size = 8731653, upload-time = "2025-12-10T22:56:01.032Z" }, - { url = "https://files.pythonhosted.org/packages/46/b3/bd9c57d6ba670a37ab31fb87ec3e8691b947134b201f881665b28cc039ff/matplotlib-3.10.8-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b44d07310e404ba95f8c25aa5536f154c0a8ec473303535949e52eb71d0a1565", size = 9561356, upload-time = "2025-12-10T22:56:02.95Z" }, - { url = "https://files.pythonhosted.org/packages/c0/3d/8b94a481456dfc9dfe6e39e93b5ab376e50998cddfd23f4ae3b431708f16/matplotlib-3.10.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0a33deb84c15ede243aead39f77e990469fff93ad1521163305095b77b72ce4a", size = 9614000, upload-time = "2025-12-10T22:56:05.411Z" }, - { url = "https://files.pythonhosted.org/packages/bd/cd/bc06149fe5585ba800b189a6a654a75f1f127e8aab02fd2be10df7fa500c/matplotlib-3.10.8-cp313-cp313t-win_amd64.whl", hash = "sha256:3a48a78d2786784cc2413e57397981fb45c79e968d99656706018d6e62e57958", size = 8220043, upload-time = "2025-12-10T22:56:07.551Z" }, - { url = "https://files.pythonhosted.org/packages/e3/de/b22cf255abec916562cc04eef457c13e58a1990048de0c0c3604d082355e/matplotlib-3.10.8-cp313-cp313t-win_arm64.whl", hash = "sha256:15d30132718972c2c074cd14638c7f4592bd98719e2308bccea40e0538bc0cb5", size = 8062075, upload-time = "2025-12-10T22:56:09.178Z" }, - { url = "https://files.pythonhosted.org/packages/3c/43/9c0ff7a2f11615e516c3b058e1e6e8f9614ddeca53faca06da267c48345d/matplotlib-3.10.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b53285e65d4fa4c86399979e956235deb900be5baa7fc1218ea67fbfaeaadd6f", size = 8262481, upload-time = "2025-12-10T22:56:10.885Z" }, - { url = "https://files.pythonhosted.org/packages/6f/ca/e8ae28649fcdf039fda5ef554b40a95f50592a3c47e6f7270c9561c12b07/matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b", size = 8151473, upload-time = "2025-12-10T22:56:12.377Z" }, - { url = "https://files.pythonhosted.org/packages/f1/6f/009d129ae70b75e88cbe7e503a12a4c0670e08ed748a902c2568909e9eb5/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cf267add95b1c88300d96ca837833d4112756045364f5c734a2276038dae27d", size = 9553896, upload-time = "2025-12-10T22:56:14.432Z" }, - { url = "https://files.pythonhosted.org/packages/f5/26/4221a741eb97967bc1fd5e4c52b9aa5a91b2f4ec05b59f6def4d820f9df9/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008", size = 9824193, upload-time = "2025-12-10T22:56:16.29Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f3/3abf75f38605772cf48a9daf5821cd4f563472f38b4b828c6fba6fa6d06e/matplotlib-3.10.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:41703cc95688f2516b480f7f339d8851a6035f18e100ee6a32bc0b8536a12a9c", size = 9615444, upload-time = "2025-12-10T22:56:18.155Z" }, - { url = "https://files.pythonhosted.org/packages/93/a5/de89ac80f10b8dc615807ee1133cd99ac74082581196d4d9590bea10690d/matplotlib-3.10.8-cp314-cp314-win_amd64.whl", hash = "sha256:83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11", size = 8272719, upload-time = "2025-12-10T22:56:20.366Z" }, - { url = "https://files.pythonhosted.org/packages/69/ce/b006495c19ccc0a137b48083168a37bd056392dee02f87dba0472f2797fe/matplotlib-3.10.8-cp314-cp314-win_arm64.whl", hash = "sha256:2c1998e92cd5999e295a731bcb2911c75f597d937341f3030cc24ef2733d78a8", size = 8144205, upload-time = "2025-12-10T22:56:22.239Z" }, - { url = "https://files.pythonhosted.org/packages/68/d9/b31116a3a855bd313c6fcdb7226926d59b041f26061c6c5b1be66a08c826/matplotlib-3.10.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b5a2b97dbdc7d4f353ebf343744f1d1f1cca8aa8bfddb4262fcf4306c3761d50", size = 8305785, upload-time = "2025-12-10T22:56:24.218Z" }, - { url = "https://files.pythonhosted.org/packages/1e/90/6effe8103f0272685767ba5f094f453784057072f49b393e3ea178fe70a5/matplotlib-3.10.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3f5c3e4da343bba819f0234186b9004faba952cc420fbc522dc4e103c1985908", size = 8198361, upload-time = "2025-12-10T22:56:26.787Z" }, - { url = "https://files.pythonhosted.org/packages/d7/65/a73188711bea603615fc0baecca1061429ac16940e2385433cc778a9d8e7/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f62550b9a30afde8c1c3ae450e5eb547d579dd69b25c2fc7a1c67f934c1717a", size = 9561357, upload-time = "2025-12-10T22:56:28.953Z" }, - { url = "https://files.pythonhosted.org/packages/f4/3d/b5c5d5d5be8ce63292567f0e2c43dde9953d3ed86ac2de0a72e93c8f07a1/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:495672de149445ec1b772ff2c9ede9b769e3cb4f0d0aa7fa730d7f59e2d4e1c1", size = 9823610, upload-time = "2025-12-10T22:56:31.455Z" }, - { url = "https://files.pythonhosted.org/packages/4d/4b/e7beb6bbd49f6bae727a12b270a2654d13c397576d25bd6786e47033300f/matplotlib-3.10.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:595ba4d8fe983b88f0eec8c26a241e16d6376fe1979086232f481f8f3f67494c", size = 9614011, upload-time = "2025-12-10T22:56:33.85Z" }, - { url = "https://files.pythonhosted.org/packages/7c/e6/76f2813d31f032e65f6f797e3f2f6e4aab95b65015924b1c51370395c28a/matplotlib-3.10.8-cp314-cp314t-win_amd64.whl", hash = "sha256:25d380fe8b1dc32cf8f0b1b448470a77afb195438bafdf1d858bfb876f3edf7b", size = 8362801, upload-time = "2025-12-10T22:56:36.107Z" }, - { url = "https://files.pythonhosted.org/packages/5d/49/d651878698a0b67f23aa28e17f45a6d6dd3d3f933fa29087fa4ce5947b5a/matplotlib-3.10.8-cp314-cp314t-win_arm64.whl", hash = "sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f", size = 8192560, upload-time = "2025-12-10T22:56:38.008Z" }, - { url = "https://files.pythonhosted.org/packages/04/30/3afaa31c757f34b7725ab9d2ba8b48b5e89c2019c003e7d0ead143aabc5a/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6da7c2ce169267d0d066adcf63758f0604aa6c3eebf67458930f9d9b79ad1db1", size = 8249198, upload-time = "2025-12-10T22:56:45.584Z" }, - { url = "https://files.pythonhosted.org/packages/48/2f/6334aec331f57485a642a7c8be03cb286f29111ae71c46c38b363230063c/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9153c3292705be9f9c64498a8872118540c3f4123d1a1c840172edf262c8be4a", size = 8136817, upload-time = "2025-12-10T22:56:47.339Z" }, - { url = "https://files.pythonhosted.org/packages/73/e4/6d6f14b2a759c622f191b2d67e9075a3f56aaccb3be4bb9bb6890030d0a0/matplotlib-3.10.8-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2", size = 8713867, upload-time = "2025-12-10T22:56:48.954Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/8a/76/d3c6e3a13fe484ebe7718d14e269c9569c4eb0020a968a327acb3b9a8fe6/matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3", size = 34806269 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/86/de7e3a1cdcfc941483af70609edc06b83e7c8a0e0dc9ac325200a3f4d220/matplotlib-3.10.8-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6be43b667360fef5c754dda5d25a32e6307a03c204f3c0fc5468b78fa87b4160", size = 8251215 }, + { url = "https://files.pythonhosted.org/packages/fd/14/baad3222f424b19ce6ad243c71de1ad9ec6b2e4eb1e458a48fdc6d120401/matplotlib-3.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2b336e2d91a3d7006864e0990c83b216fcdca64b5a6484912902cef87313d78", size = 8139625 }, + { url = "https://files.pythonhosted.org/packages/8f/a0/7024215e95d456de5883e6732e708d8187d9753a21d32f8ddb3befc0c445/matplotlib-3.10.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efb30e3baaea72ce5928e32bab719ab4770099079d66726a62b11b1ef7273be4", size = 8712614 }, + { url = "https://files.pythonhosted.org/packages/5a/f4/b8347351da9a5b3f41e26cf547252d861f685c6867d179a7c9d60ad50189/matplotlib-3.10.8-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d56a1efd5bfd61486c8bc968fa18734464556f0fb8e51690f4ac25d85cbbbbc2", size = 9540997 }, + { url = "https://files.pythonhosted.org/packages/9e/c0/c7b914e297efe0bc36917bf216b2acb91044b91e930e878ae12981e461e5/matplotlib-3.10.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238b7ce5717600615c895050239ec955d91f321c209dd110db988500558e70d6", size = 9596825 }, + { url = "https://files.pythonhosted.org/packages/6f/d3/a4bbc01c237ab710a1f22b4da72f4ff6d77eb4c7735ea9811a94ae239067/matplotlib-3.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:18821ace09c763ec93aef5eeff087ee493a24051936d7b9ebcad9662f66501f9", size = 8135090 }, + { url = "https://files.pythonhosted.org/packages/89/dd/a0b6588f102beab33ca6f5218b31725216577b2a24172f327eaf6417d5c9/matplotlib-3.10.8-cp311-cp311-win_arm64.whl", hash = "sha256:bab485bcf8b1c7d2060b4fcb6fc368a9e6f4cd754c9c2fea281f4be21df394a2", size = 8012377 }, + { url = "https://files.pythonhosted.org/packages/9e/67/f997cdcbb514012eb0d10cd2b4b332667997fb5ebe26b8d41d04962fa0e6/matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a", size = 8260453 }, + { url = "https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58", size = 8148321 }, + { url = "https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04", size = 8716944 }, + { url = "https://files.pythonhosted.org/packages/00/f9/7638f5cc82ec8a7aa005de48622eecc3ed7c9854b96ba15bd76b7fd27574/matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f", size = 9550099 }, + { url = "https://files.pythonhosted.org/packages/57/61/78cd5920d35b29fd2a0fe894de8adf672ff52939d2e9b43cb83cd5ce1bc7/matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466", size = 9613040 }, + { url = "https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf", size = 8142717 }, + { url = "https://files.pythonhosted.org/packages/f1/76/934db220026b5fef85f45d51a738b91dea7d70207581063cd9bd8fafcf74/matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b", size = 8012751 }, + { url = "https://files.pythonhosted.org/packages/3d/b9/15fd5541ef4f5b9a17eefd379356cf12175fe577424e7b1d80676516031a/matplotlib-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3f2e409836d7f5ac2f1c013110a4d50b9f7edc26328c108915f9075d7d7a91b6", size = 8261076 }, + { url = "https://files.pythonhosted.org/packages/8d/a0/2ba3473c1b66b9c74dc7107c67e9008cb1782edbe896d4c899d39ae9cf78/matplotlib-3.10.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56271f3dac49a88d7fca5060f004d9d22b865f743a12a23b1e937a0be4818ee1", size = 8148794 }, + { url = "https://files.pythonhosted.org/packages/75/97/a471f1c3eb1fd6f6c24a31a5858f443891d5127e63a7788678d14e249aea/matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0a7f52498f72f13d4a25ea70f35f4cb60642b466cbb0a9be951b5bc3f45a486", size = 8718474 }, + { url = "https://files.pythonhosted.org/packages/01/be/cd478f4b66f48256f42927d0acbcd63a26a893136456cd079c0cc24fbabf/matplotlib-3.10.8-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:646d95230efb9ca614a7a594d4fcacde0ac61d25e37dd51710b36477594963ce", size = 9549637 }, + { url = "https://files.pythonhosted.org/packages/5d/7c/8dc289776eae5109e268c4fb92baf870678dc048a25d4ac903683b86d5bf/matplotlib-3.10.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f89c151aab2e2e23cb3fe0acad1e8b82841fd265379c4cecd0f3fcb34c15e0f6", size = 9613678 }, + { url = "https://files.pythonhosted.org/packages/64/40/37612487cc8a437d4dd261b32ca21fe2d79510fe74af74e1f42becb1bdb8/matplotlib-3.10.8-cp313-cp313-win_amd64.whl", hash = "sha256:e8ea3e2d4066083e264e75c829078f9e149fa119d27e19acd503de65e0b13149", size = 8142686 }, + { url = "https://files.pythonhosted.org/packages/66/52/8d8a8730e968185514680c2a6625943f70269509c3dcfc0dcf7d75928cb8/matplotlib-3.10.8-cp313-cp313-win_arm64.whl", hash = "sha256:c108a1d6fa78a50646029cb6d49808ff0fc1330fda87fa6f6250c6b5369b6645", size = 8012917 }, + { url = "https://files.pythonhosted.org/packages/b5/27/51fe26e1062f298af5ef66343d8ef460e090a27fea73036c76c35821df04/matplotlib-3.10.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ad3d9833a64cf48cc4300f2b406c3d0f4f4724a91c0bd5640678a6ba7c102077", size = 8305679 }, + { url = "https://files.pythonhosted.org/packages/2c/1e/4de865bc591ac8e3062e835f42dd7fe7a93168d519557837f0e37513f629/matplotlib-3.10.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:eb3823f11823deade26ce3b9f40dcb4a213da7a670013929f31d5f5ed1055b22", size = 8198336 }, + { url = "https://files.pythonhosted.org/packages/c6/cb/2f7b6e75fb4dce87ef91f60cac4f6e34f4c145ab036a22318ec837971300/matplotlib-3.10.8-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d9050fee89a89ed57b4fb2c1bfac9a3d0c57a0d55aed95949eedbc42070fea39", size = 8731653 }, + { url = "https://files.pythonhosted.org/packages/46/b3/bd9c57d6ba670a37ab31fb87ec3e8691b947134b201f881665b28cc039ff/matplotlib-3.10.8-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b44d07310e404ba95f8c25aa5536f154c0a8ec473303535949e52eb71d0a1565", size = 9561356 }, + { url = "https://files.pythonhosted.org/packages/c0/3d/8b94a481456dfc9dfe6e39e93b5ab376e50998cddfd23f4ae3b431708f16/matplotlib-3.10.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0a33deb84c15ede243aead39f77e990469fff93ad1521163305095b77b72ce4a", size = 9614000 }, + { url = "https://files.pythonhosted.org/packages/bd/cd/bc06149fe5585ba800b189a6a654a75f1f127e8aab02fd2be10df7fa500c/matplotlib-3.10.8-cp313-cp313t-win_amd64.whl", hash = "sha256:3a48a78d2786784cc2413e57397981fb45c79e968d99656706018d6e62e57958", size = 8220043 }, + { url = "https://files.pythonhosted.org/packages/e3/de/b22cf255abec916562cc04eef457c13e58a1990048de0c0c3604d082355e/matplotlib-3.10.8-cp313-cp313t-win_arm64.whl", hash = "sha256:15d30132718972c2c074cd14638c7f4592bd98719e2308bccea40e0538bc0cb5", size = 8062075 }, + { url = "https://files.pythonhosted.org/packages/3c/43/9c0ff7a2f11615e516c3b058e1e6e8f9614ddeca53faca06da267c48345d/matplotlib-3.10.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b53285e65d4fa4c86399979e956235deb900be5baa7fc1218ea67fbfaeaadd6f", size = 8262481 }, + { url = "https://files.pythonhosted.org/packages/6f/ca/e8ae28649fcdf039fda5ef554b40a95f50592a3c47e6f7270c9561c12b07/matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b", size = 8151473 }, + { url = "https://files.pythonhosted.org/packages/f1/6f/009d129ae70b75e88cbe7e503a12a4c0670e08ed748a902c2568909e9eb5/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cf267add95b1c88300d96ca837833d4112756045364f5c734a2276038dae27d", size = 9553896 }, + { url = "https://files.pythonhosted.org/packages/f5/26/4221a741eb97967bc1fd5e4c52b9aa5a91b2f4ec05b59f6def4d820f9df9/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008", size = 9824193 }, + { url = "https://files.pythonhosted.org/packages/1f/f3/3abf75f38605772cf48a9daf5821cd4f563472f38b4b828c6fba6fa6d06e/matplotlib-3.10.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:41703cc95688f2516b480f7f339d8851a6035f18e100ee6a32bc0b8536a12a9c", size = 9615444 }, + { url = "https://files.pythonhosted.org/packages/93/a5/de89ac80f10b8dc615807ee1133cd99ac74082581196d4d9590bea10690d/matplotlib-3.10.8-cp314-cp314-win_amd64.whl", hash = "sha256:83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11", size = 8272719 }, + { url = "https://files.pythonhosted.org/packages/69/ce/b006495c19ccc0a137b48083168a37bd056392dee02f87dba0472f2797fe/matplotlib-3.10.8-cp314-cp314-win_arm64.whl", hash = "sha256:2c1998e92cd5999e295a731bcb2911c75f597d937341f3030cc24ef2733d78a8", size = 8144205 }, + { url = "https://files.pythonhosted.org/packages/68/d9/b31116a3a855bd313c6fcdb7226926d59b041f26061c6c5b1be66a08c826/matplotlib-3.10.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b5a2b97dbdc7d4f353ebf343744f1d1f1cca8aa8bfddb4262fcf4306c3761d50", size = 8305785 }, + { url = "https://files.pythonhosted.org/packages/1e/90/6effe8103f0272685767ba5f094f453784057072f49b393e3ea178fe70a5/matplotlib-3.10.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3f5c3e4da343bba819f0234186b9004faba952cc420fbc522dc4e103c1985908", size = 8198361 }, + { url = "https://files.pythonhosted.org/packages/d7/65/a73188711bea603615fc0baecca1061429ac16940e2385433cc778a9d8e7/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f62550b9a30afde8c1c3ae450e5eb547d579dd69b25c2fc7a1c67f934c1717a", size = 9561357 }, + { url = "https://files.pythonhosted.org/packages/f4/3d/b5c5d5d5be8ce63292567f0e2c43dde9953d3ed86ac2de0a72e93c8f07a1/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:495672de149445ec1b772ff2c9ede9b769e3cb4f0d0aa7fa730d7f59e2d4e1c1", size = 9823610 }, + { url = "https://files.pythonhosted.org/packages/4d/4b/e7beb6bbd49f6bae727a12b270a2654d13c397576d25bd6786e47033300f/matplotlib-3.10.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:595ba4d8fe983b88f0eec8c26a241e16d6376fe1979086232f481f8f3f67494c", size = 9614011 }, + { url = "https://files.pythonhosted.org/packages/7c/e6/76f2813d31f032e65f6f797e3f2f6e4aab95b65015924b1c51370395c28a/matplotlib-3.10.8-cp314-cp314t-win_amd64.whl", hash = "sha256:25d380fe8b1dc32cf8f0b1b448470a77afb195438bafdf1d858bfb876f3edf7b", size = 8362801 }, + { url = "https://files.pythonhosted.org/packages/5d/49/d651878698a0b67f23aa28e17f45a6d6dd3d3f933fa29087fa4ce5947b5a/matplotlib-3.10.8-cp314-cp314t-win_arm64.whl", hash = "sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f", size = 8192560 }, + { url = "https://files.pythonhosted.org/packages/04/30/3afaa31c757f34b7725ab9d2ba8b48b5e89c2019c003e7d0ead143aabc5a/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6da7c2ce169267d0d066adcf63758f0604aa6c3eebf67458930f9d9b79ad1db1", size = 8249198 }, + { url = "https://files.pythonhosted.org/packages/48/2f/6334aec331f57485a642a7c8be03cb286f29111ae71c46c38b363230063c/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9153c3292705be9f9c64498a8872118540c3f4123d1a1c840172edf262c8be4a", size = 8136817 }, + { url = "https://files.pythonhosted.org/packages/73/e4/6d6f14b2a759c622f191b2d67e9075a3f56aaccb3be4bb9bb6890030d0a0/matplotlib-3.10.8-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2", size = 8713867 }, ] [[package]] @@ -1772,27 +1824,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c7/74/97e72a36efd4ae2bccb3463284300f8953f199b5ffbc04cbbb0ec78f74b1/matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe", size = 8110, upload-time = "2025-10-23T09:00:22.126Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/74/97e72a36efd4ae2bccb3463284300f8953f199b5ffbc04cbbb0ec78f74b1/matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe", size = 8110 } wheels = [ - { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" }, + { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516 }, ] [[package]] name = "mistune" version = "3.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/55/d01f0c4b45ade6536c51170b9043db8b2ec6ddf4a35c7ea3f5f559ac935b/mistune-3.2.0.tar.gz", hash = "sha256:708487c8a8cdd99c9d90eb3ed4c3ed961246ff78ac82f03418f5183ab70e398a", size = 95467, upload-time = "2025-12-23T11:36:34.994Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/55/d01f0c4b45ade6536c51170b9043db8b2ec6ddf4a35c7ea3f5f559ac935b/mistune-3.2.0.tar.gz", hash = "sha256:708487c8a8cdd99c9d90eb3ed4c3ed961246ff78ac82f03418f5183ab70e398a", size = 95467 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl", hash = "sha256:febdc629a3c78616b94393c6580551e0e34cc289987ec6c35ed3f4be42d0eee1", size = 53598, upload-time = "2025-12-23T11:36:33.211Z" }, + { url = "https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl", hash = "sha256:febdc629a3c78616b94393c6580551e0e34cc289987ec6c35ed3f4be42d0eee1", size = 53598 }, ] [[package]] name = "mpmath" version = "1.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106 } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198 }, ] [[package]] @@ -1805,9 +1857,9 @@ dependencies = [ { name = "nbformat" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/56/91/1c1d5a4b9a9ebba2b4e32b8c852c2975c872aec1fe42ab5e516b2cecd193/nbclient-0.10.4.tar.gz", hash = "sha256:1e54091b16e6da39e297b0ece3e10f6f29f4ac4e8ee515d29f8a7099bd6553c9", size = 62554, upload-time = "2025-12-23T07:45:46.369Z" } +sdist = { url = "https://files.pythonhosted.org/packages/56/91/1c1d5a4b9a9ebba2b4e32b8c852c2975c872aec1fe42ab5e516b2cecd193/nbclient-0.10.4.tar.gz", hash = "sha256:1e54091b16e6da39e297b0ece3e10f6f29f4ac4e8ee515d29f8a7099bd6553c9", size = 62554 } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl", hash = "sha256:9162df5a7373d70d606527300a95a975a47c137776cd942e52d9c7e29ff83440", size = 25465, upload-time = "2025-12-23T07:45:44.51Z" }, + { url = "https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl", hash = "sha256:9162df5a7373d70d606527300a95a975a47c137776cd942e52d9c7e29ff83440", size = 25465 }, ] [[package]] @@ -1830,9 +1882,9 @@ dependencies = [ { name = "pygments" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/38/47/81f886b699450d0569f7bc551df2b1673d18df7ff25cc0c21ca36ed8a5ff/nbconvert-7.17.0.tar.gz", hash = "sha256:1b2696f1b5be12309f6c7d707c24af604b87dfaf6d950794c7b07acab96dda78", size = 862855, upload-time = "2026-01-29T16:37:48.478Z" } +sdist = { url = "https://files.pythonhosted.org/packages/38/47/81f886b699450d0569f7bc551df2b1673d18df7ff25cc0c21ca36ed8a5ff/nbconvert-7.17.0.tar.gz", hash = "sha256:1b2696f1b5be12309f6c7d707c24af604b87dfaf6d950794c7b07acab96dda78", size = 862855 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl", hash = "sha256:4f99a63b337b9a23504347afdab24a11faa7d86b405e5c8f9881cd313336d518", size = 261510, upload-time = "2026-01-29T16:37:46.322Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl", hash = "sha256:4f99a63b337b9a23504347afdab24a11faa7d86b405e5c8f9881cd313336d518", size = 261510 }, ] [[package]] @@ -1845,36 +1897,36 @@ dependencies = [ { name = "jupyter-core" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749, upload-time = "2024-04-04T11:20:37.371Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454, upload-time = "2024-04-04T11:20:34.895Z" }, + { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454 }, ] [[package]] name = "nest-asyncio" version = "1.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195 }, ] [[package]] name = "networkx" version = "3.6.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504 }, ] [[package]] name = "nodeenv" version = "1.10.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611 } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438 }, ] [[package]] @@ -1884,9 +1936,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jupyter-server" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/54/d2/92fa3243712b9a3e8bafaf60aac366da1cada3639ca767ff4b5b3654ec28/notebook_shim-0.2.4.tar.gz", hash = "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb", size = 13167, upload-time = "2024-02-14T23:35:18.353Z" } +sdist = { url = "https://files.pythonhosted.org/packages/54/d2/92fa3243712b9a3e8bafaf60aac366da1cada3639ca767ff4b5b3654ec28/notebook_shim-0.2.4.tar.gz", hash = "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb", size = 13167 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef", size = 13307, upload-time = "2024-02-14T23:35:16.286Z" }, + { url = "https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef", size = 13307 }, ] [[package]] @@ -1897,241 +1949,256 @@ dependencies = [ { name = "numpy" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/44/bd/8a391e7c356366224734efd24da929cc4796fff468bfb179fe1af6548535/numcodecs-0.16.5.tar.gz", hash = "sha256:0d0fb60852f84c0bd9543cc4d2ab9eefd37fc8efcc410acd4777e62a1d300318", size = 6276387, upload-time = "2025-11-21T02:49:48.986Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/af/85/1ac101a40ead81eaa1c7dc49a8827a30e2e436211b43ebdc63c590eb1347/numcodecs-0.16.5-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:78382dcea50622f2ef1e6e7a71dbe7f861d8fe376b27b7c297c26907304fef1e", size = 1621795, upload-time = "2025-11-21T02:49:17.418Z" }, - { url = "https://files.pythonhosted.org/packages/0e/cc/0d97ef55dda48cb0f93d7b92d761208e7a99bd2eea6b0e859426e6a99a21/numcodecs-0.16.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2d04a19cb57a3c519b4127ac377cca6471aee1990d7c18f5b1e3a4fe1306689", size = 1153030, upload-time = "2025-11-21T02:49:19.089Z" }, - { url = "https://files.pythonhosted.org/packages/5e/41/e120ee1b390730ac5987cde2afd82e2b8442cec315ab40b94b0373e93e73/numcodecs-0.16.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c043af648eb280cd61785c99c22ff5c3c3460f906eb51a8511327c4f5111b283", size = 8510503, upload-time = "2025-11-21T02:49:20.324Z" }, - { url = "https://files.pythonhosted.org/packages/54/4b/195ac84cc8f6077b4f0f421e8daee21b7f1bd88cb7716414234379fe68ec/numcodecs-0.16.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c398919ef2eb0e56b8e97456f622640bfd3deed06de3acc976989cbcb22628a3", size = 9123428, upload-time = "2025-11-21T02:49:22.328Z" }, - { url = "https://files.pythonhosted.org/packages/0f/5b/af02c417954f46e5c7bd5163ac251f535877d909fce54861c99ae197f6f6/numcodecs-0.16.5-cp311-cp311-win_amd64.whl", hash = "sha256:3820860ed302d4d84a1c66e70981ff959d5eb712555be4e7d8ced49888594773", size = 801542, upload-time = "2025-11-21T02:49:24.265Z" }, - { url = "https://files.pythonhosted.org/packages/75/cc/55420f3641a67f78392dc0bc5d02cb9eb0a9dcebf2848d1ac77253ca61fa/numcodecs-0.16.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:24e675dc8d1550cd976a99479b87d872cb142632c75cc402fea04c08c4898523", size = 1656287, upload-time = "2025-11-21T02:49:25.755Z" }, - { url = "https://files.pythonhosted.org/packages/f5/6c/86644987505dcb90ba6d627d6989c27bafb0699f9fd00187e06d05ea8594/numcodecs-0.16.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:94ddfa4341d1a3ab99989d13b01b5134abb687d3dab2ead54b450aefe4ad5bd6", size = 1148899, upload-time = "2025-11-21T02:49:26.87Z" }, - { url = "https://files.pythonhosted.org/packages/97/1e/98aaddf272552d9fef1f0296a9939d1487914a239e98678f6b20f8b0a5c8/numcodecs-0.16.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b554ab9ecf69de7ca2b6b5e8bc696bd9747559cb4dd5127bd08d7a28bec59c3a", size = 8534814, upload-time = "2025-11-21T02:49:28.547Z" }, - { url = "https://files.pythonhosted.org/packages/fb/53/78c98ef5c8b2b784453487f3e4d6c017b20747c58b470393e230c78d18e8/numcodecs-0.16.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ad1a379a45bd3491deab8ae6548313946744f868c21d5340116977ea3be5b1d6", size = 9173471, upload-time = "2025-11-21T02:49:30.444Z" }, - { url = "https://files.pythonhosted.org/packages/1c/20/2fdec87fc7f8cec950d2b0bea603c12dc9f05b4966dc5924ba5a36a61bf6/numcodecs-0.16.5-cp312-cp312-win_amd64.whl", hash = "sha256:845a9857886ffe4a3172ba1c537ae5bcc01e65068c31cf1fce1a844bd1da050f", size = 801412, upload-time = "2025-11-21T02:49:32.123Z" }, - { url = "https://files.pythonhosted.org/packages/38/38/071ced5a5fd1c85ba0e14ba721b66b053823e5176298c2f707e50bed11d9/numcodecs-0.16.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25be3a516ab677dad890760d357cfe081a371d9c0a2e9a204562318ac5969de3", size = 1654359, upload-time = "2025-11-21T02:49:33.673Z" }, - { url = "https://files.pythonhosted.org/packages/d1/c0/5f84ba7525577c1b9909fc2d06ef11314825fc4ad4378f61d0e4c9883b4a/numcodecs-0.16.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0107e839ef75b854e969cb577e140b1aadb9847893937636582d23a2a4c6ce50", size = 1144237, upload-time = "2025-11-21T02:49:35.294Z" }, - { url = "https://files.pythonhosted.org/packages/0b/00/787ea5f237b8ea7bc67140c99155f9c00b5baf11c49afc5f3bfefa298f95/numcodecs-0.16.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:015a7c859ecc2a06e2a548f64008c0ec3aaecabc26456c2c62f4278d8fc20597", size = 8483064, upload-time = "2025-11-21T02:49:36.454Z" }, - { url = "https://files.pythonhosted.org/packages/c4/e6/d359fdd37498e74d26a167f7a51e54542e642ea47181eb4e643a69a066c3/numcodecs-0.16.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84230b4b9dad2392f2a84242bd6e3e659ac137b5a1ce3571d6965fca673e0903", size = 9126063, upload-time = "2025-11-21T02:49:38.018Z" }, - { url = "https://files.pythonhosted.org/packages/27/72/6663cc0382ddbb866136c255c837bcb96cc7ce5e83562efec55e1b995941/numcodecs-0.16.5-cp313-cp313-win_amd64.whl", hash = "sha256:5088145502ad1ebf677ec47d00eb6f0fd600658217db3e0c070c321c85d6cf3d", size = 799275, upload-time = "2025-11-21T02:49:39.558Z" }, - { url = "https://files.pythonhosted.org/packages/3c/9e/38e7ca8184c958b51f45d56a4aeceb1134ecde2d8bd157efadc98502cc42/numcodecs-0.16.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b05647b8b769e6bc8016e9fd4843c823ce5c9f2337c089fb5c9c4da05e5275de", size = 1654721, upload-time = "2025-11-21T02:49:40.602Z" }, - { url = "https://files.pythonhosted.org/packages/a1/37/260fa42e7b2b08e6e00ad632f8dd620961a60a459426c26cea390f8c68d0/numcodecs-0.16.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3832bd1b5af8bb3e413076b7d93318c8e7d7b68935006b9fa36ca057d1725a8f", size = 1146887, upload-time = "2025-11-21T02:49:41.721Z" }, - { url = "https://files.pythonhosted.org/packages/4e/15/e2e1151b5a8b14a15dfd4bb4abccce7fff7580f39bc34092780088835f3a/numcodecs-0.16.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49f7b7d24f103187f53135bed28bb9f0ed6b2e14c604664726487bb6d7c882e1", size = 8476987, upload-time = "2025-11-21T02:49:43.363Z" }, - { url = "https://files.pythonhosted.org/packages/6d/30/16a57fc4d9fb0ba06c600408bd6634f2f1753c54a7a351c99c5e09b51ee2/numcodecs-0.16.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aec9736d81b70f337d89c4070ee3ffeff113f386fd789492fa152d26a15043e4", size = 9102377, upload-time = "2025-11-21T02:49:45.508Z" }, - { url = "https://files.pythonhosted.org/packages/31/a5/a0425af36c20d55a3ea884db4b4efca25a43bea9214ba69ca7932dd997b4/numcodecs-0.16.5-cp314-cp314-win_amd64.whl", hash = "sha256:b16a14303800e9fb88abc39463ab4706c037647ac17e49e297faa5f7d7dbbf1d", size = 819022, upload-time = "2025-11-21T02:49:47.39Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/44/bd/8a391e7c356366224734efd24da929cc4796fff468bfb179fe1af6548535/numcodecs-0.16.5.tar.gz", hash = "sha256:0d0fb60852f84c0bd9543cc4d2ab9eefd37fc8efcc410acd4777e62a1d300318", size = 6276387 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/85/1ac101a40ead81eaa1c7dc49a8827a30e2e436211b43ebdc63c590eb1347/numcodecs-0.16.5-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:78382dcea50622f2ef1e6e7a71dbe7f861d8fe376b27b7c297c26907304fef1e", size = 1621795 }, + { url = "https://files.pythonhosted.org/packages/0e/cc/0d97ef55dda48cb0f93d7b92d761208e7a99bd2eea6b0e859426e6a99a21/numcodecs-0.16.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2d04a19cb57a3c519b4127ac377cca6471aee1990d7c18f5b1e3a4fe1306689", size = 1153030 }, + { url = "https://files.pythonhosted.org/packages/5e/41/e120ee1b390730ac5987cde2afd82e2b8442cec315ab40b94b0373e93e73/numcodecs-0.16.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c043af648eb280cd61785c99c22ff5c3c3460f906eb51a8511327c4f5111b283", size = 8510503 }, + { url = "https://files.pythonhosted.org/packages/54/4b/195ac84cc8f6077b4f0f421e8daee21b7f1bd88cb7716414234379fe68ec/numcodecs-0.16.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c398919ef2eb0e56b8e97456f622640bfd3deed06de3acc976989cbcb22628a3", size = 9123428 }, + { url = "https://files.pythonhosted.org/packages/0f/5b/af02c417954f46e5c7bd5163ac251f535877d909fce54861c99ae197f6f6/numcodecs-0.16.5-cp311-cp311-win_amd64.whl", hash = "sha256:3820860ed302d4d84a1c66e70981ff959d5eb712555be4e7d8ced49888594773", size = 801542 }, + { url = "https://files.pythonhosted.org/packages/75/cc/55420f3641a67f78392dc0bc5d02cb9eb0a9dcebf2848d1ac77253ca61fa/numcodecs-0.16.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:24e675dc8d1550cd976a99479b87d872cb142632c75cc402fea04c08c4898523", size = 1656287 }, + { url = "https://files.pythonhosted.org/packages/f5/6c/86644987505dcb90ba6d627d6989c27bafb0699f9fd00187e06d05ea8594/numcodecs-0.16.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:94ddfa4341d1a3ab99989d13b01b5134abb687d3dab2ead54b450aefe4ad5bd6", size = 1148899 }, + { url = "https://files.pythonhosted.org/packages/97/1e/98aaddf272552d9fef1f0296a9939d1487914a239e98678f6b20f8b0a5c8/numcodecs-0.16.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b554ab9ecf69de7ca2b6b5e8bc696bd9747559cb4dd5127bd08d7a28bec59c3a", size = 8534814 }, + { url = "https://files.pythonhosted.org/packages/fb/53/78c98ef5c8b2b784453487f3e4d6c017b20747c58b470393e230c78d18e8/numcodecs-0.16.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ad1a379a45bd3491deab8ae6548313946744f868c21d5340116977ea3be5b1d6", size = 9173471 }, + { url = "https://files.pythonhosted.org/packages/1c/20/2fdec87fc7f8cec950d2b0bea603c12dc9f05b4966dc5924ba5a36a61bf6/numcodecs-0.16.5-cp312-cp312-win_amd64.whl", hash = "sha256:845a9857886ffe4a3172ba1c537ae5bcc01e65068c31cf1fce1a844bd1da050f", size = 801412 }, + { url = "https://files.pythonhosted.org/packages/38/38/071ced5a5fd1c85ba0e14ba721b66b053823e5176298c2f707e50bed11d9/numcodecs-0.16.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25be3a516ab677dad890760d357cfe081a371d9c0a2e9a204562318ac5969de3", size = 1654359 }, + { url = "https://files.pythonhosted.org/packages/d1/c0/5f84ba7525577c1b9909fc2d06ef11314825fc4ad4378f61d0e4c9883b4a/numcodecs-0.16.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0107e839ef75b854e969cb577e140b1aadb9847893937636582d23a2a4c6ce50", size = 1144237 }, + { url = "https://files.pythonhosted.org/packages/0b/00/787ea5f237b8ea7bc67140c99155f9c00b5baf11c49afc5f3bfefa298f95/numcodecs-0.16.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:015a7c859ecc2a06e2a548f64008c0ec3aaecabc26456c2c62f4278d8fc20597", size = 8483064 }, + { url = "https://files.pythonhosted.org/packages/c4/e6/d359fdd37498e74d26a167f7a51e54542e642ea47181eb4e643a69a066c3/numcodecs-0.16.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84230b4b9dad2392f2a84242bd6e3e659ac137b5a1ce3571d6965fca673e0903", size = 9126063 }, + { url = "https://files.pythonhosted.org/packages/27/72/6663cc0382ddbb866136c255c837bcb96cc7ce5e83562efec55e1b995941/numcodecs-0.16.5-cp313-cp313-win_amd64.whl", hash = "sha256:5088145502ad1ebf677ec47d00eb6f0fd600658217db3e0c070c321c85d6cf3d", size = 799275 }, + { url = "https://files.pythonhosted.org/packages/3c/9e/38e7ca8184c958b51f45d56a4aeceb1134ecde2d8bd157efadc98502cc42/numcodecs-0.16.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b05647b8b769e6bc8016e9fd4843c823ce5c9f2337c089fb5c9c4da05e5275de", size = 1654721 }, + { url = "https://files.pythonhosted.org/packages/a1/37/260fa42e7b2b08e6e00ad632f8dd620961a60a459426c26cea390f8c68d0/numcodecs-0.16.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3832bd1b5af8bb3e413076b7d93318c8e7d7b68935006b9fa36ca057d1725a8f", size = 1146887 }, + { url = "https://files.pythonhosted.org/packages/4e/15/e2e1151b5a8b14a15dfd4bb4abccce7fff7580f39bc34092780088835f3a/numcodecs-0.16.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49f7b7d24f103187f53135bed28bb9f0ed6b2e14c604664726487bb6d7c882e1", size = 8476987 }, + { url = "https://files.pythonhosted.org/packages/6d/30/16a57fc4d9fb0ba06c600408bd6634f2f1753c54a7a351c99c5e09b51ee2/numcodecs-0.16.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aec9736d81b70f337d89c4070ee3ffeff113f386fd789492fa152d26a15043e4", size = 9102377 }, + { url = "https://files.pythonhosted.org/packages/31/a5/a0425af36c20d55a3ea884db4b4efca25a43bea9214ba69ca7932dd997b4/numcodecs-0.16.5-cp314-cp314-win_amd64.whl", hash = "sha256:b16a14303800e9fb88abc39463ab4706c037647ac17e49e297faa5f7d7dbbf1d", size = 819022 }, ] [[package]] name = "numpy" version = "2.4.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/44/71852273146957899753e69986246d6a176061ea183407e95418c2aa4d9a/numpy-2.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7e88598032542bd49af7c4747541422884219056c268823ef6e5e89851c8825", size = 16955478, upload-time = "2026-01-31T23:10:25.623Z" }, - { url = "https://files.pythonhosted.org/packages/74/41/5d17d4058bd0cd96bcbd4d9ff0fb2e21f52702aab9a72e4a594efa18692f/numpy-2.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7edc794af8b36ca37ef5fcb5e0d128c7e0595c7b96a2318d1badb6fcd8ee86b1", size = 14965467, upload-time = "2026-01-31T23:10:28.186Z" }, - { url = "https://files.pythonhosted.org/packages/49/48/fb1ce8136c19452ed15f033f8aee91d5defe515094e330ce368a0647846f/numpy-2.4.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6e9f61981ace1360e42737e2bae58b27bf28a1b27e781721047d84bd754d32e7", size = 5475172, upload-time = "2026-01-31T23:10:30.848Z" }, - { url = "https://files.pythonhosted.org/packages/40/a9/3feb49f17bbd1300dd2570432961f5c8a4ffeff1db6f02c7273bd020a4c9/numpy-2.4.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cb7bbb88aa74908950d979eeaa24dbdf1a865e3c7e45ff0121d8f70387b55f73", size = 6805145, upload-time = "2026-01-31T23:10:32.352Z" }, - { url = "https://files.pythonhosted.org/packages/3f/39/fdf35cbd6d6e2fcad42fcf85ac04a85a0d0fbfbf34b30721c98d602fd70a/numpy-2.4.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f069069931240b3fc703f1e23df63443dbd6390614c8c44a87d96cd0ec81eb1", size = 15966084, upload-time = "2026-01-31T23:10:34.502Z" }, - { url = "https://files.pythonhosted.org/packages/1b/46/6fa4ea94f1ddf969b2ee941290cca6f1bfac92b53c76ae5f44afe17ceb69/numpy-2.4.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c02ef4401a506fb60b411467ad501e1429a3487abca4664871d9ae0b46c8ba32", size = 16899477, upload-time = "2026-01-31T23:10:37.075Z" }, - { url = "https://files.pythonhosted.org/packages/09/a1/2a424e162b1a14a5bd860a464ab4e07513916a64ab1683fae262f735ccd2/numpy-2.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2653de5c24910e49c2b106499803124dde62a5a1fe0eedeaecf4309a5f639390", size = 17323429, upload-time = "2026-01-31T23:10:39.704Z" }, - { url = "https://files.pythonhosted.org/packages/ce/a2/73014149ff250628df72c58204822ac01d768697913881aacf839ff78680/numpy-2.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1ae241bbfc6ae276f94a170b14785e561cb5e7f626b6688cf076af4110887413", size = 18635109, upload-time = "2026-01-31T23:10:41.924Z" }, - { url = "https://files.pythonhosted.org/packages/6c/0c/73e8be2f1accd56df74abc1c5e18527822067dced5ec0861b5bb882c2ce0/numpy-2.4.2-cp311-cp311-win32.whl", hash = "sha256:df1b10187212b198dd45fa943d8985a3c8cf854aed4923796e0e019e113a1bda", size = 6237915, upload-time = "2026-01-31T23:10:45.26Z" }, - { url = "https://files.pythonhosted.org/packages/76/ae/e0265e0163cf127c24c3969d29f1c4c64551a1e375d95a13d32eab25d364/numpy-2.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:b9c618d56a29c9cb1c4da979e9899be7578d2e0b3c24d52079c166324c9e8695", size = 12607972, upload-time = "2026-01-31T23:10:47.021Z" }, - { url = "https://files.pythonhosted.org/packages/29/a5/c43029af9b8014d6ea157f192652c50042e8911f4300f8f6ed3336bf437f/numpy-2.4.2-cp311-cp311-win_arm64.whl", hash = "sha256:47c5a6ed21d9452b10227e5e8a0e1c22979811cad7dcc19d8e3e2fb8fa03f1a3", size = 10485763, upload-time = "2026-01-31T23:10:50.087Z" }, - { url = "https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a", size = 16667963, upload-time = "2026-01-31T23:10:52.147Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1", size = 14693571, upload-time = "2026-01-31T23:10:54.789Z" }, - { url = "https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e", size = 5203469, upload-time = "2026-01-31T23:10:57.343Z" }, - { url = "https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27", size = 6550820, upload-time = "2026-01-31T23:10:59.429Z" }, - { url = "https://files.pythonhosted.org/packages/b1/91/b97fdfd12dc75b02c44e26c6638241cc004d4079a0321a69c62f51470c4c/numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548", size = 15663067, upload-time = "2026-01-31T23:11:01.291Z" }, - { url = "https://files.pythonhosted.org/packages/f5/c6/a18e59f3f0b8071cc85cbc8d80cd02d68aa9710170b2553a117203d46936/numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f", size = 16619782, upload-time = "2026-01-31T23:11:03.669Z" }, - { url = "https://files.pythonhosted.org/packages/b7/83/9751502164601a79e18847309f5ceec0b1446d7b6aa12305759b72cf98b2/numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460", size = 17013128, upload-time = "2026-01-31T23:11:05.913Z" }, - { url = "https://files.pythonhosted.org/packages/61/c4/c4066322256ec740acc1c8923a10047818691d2f8aec254798f3dd90f5f2/numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba", size = 18345324, upload-time = "2026-01-31T23:11:08.248Z" }, - { url = "https://files.pythonhosted.org/packages/ab/af/6157aa6da728fa4525a755bfad486ae7e3f76d4c1864138003eb84328497/numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f", size = 5960282, upload-time = "2026-01-31T23:11:10.497Z" }, - { url = "https://files.pythonhosted.org/packages/92/0f/7ceaaeaacb40567071e94dbf2c9480c0ae453d5bb4f52bea3892c39dc83c/numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85", size = 12314210, upload-time = "2026-01-31T23:11:12.176Z" }, - { url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z" }, - { url = "https://files.pythonhosted.org/packages/a1/22/815b9fe25d1d7ae7d492152adbc7226d3eff731dffc38fe970589fcaaa38/numpy-2.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c", size = 16663696, upload-time = "2026-01-31T23:11:17.516Z" }, - { url = "https://files.pythonhosted.org/packages/09/f0/817d03a03f93ba9c6c8993de509277d84e69f9453601915e4a69554102a1/numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979", size = 14688322, upload-time = "2026-01-31T23:11:19.883Z" }, - { url = "https://files.pythonhosted.org/packages/da/b4/f805ab79293c728b9a99438775ce51885fd4f31b76178767cfc718701a39/numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98", size = 5198157, upload-time = "2026-01-31T23:11:22.375Z" }, - { url = "https://files.pythonhosted.org/packages/74/09/826e4289844eccdcd64aac27d13b0fd3f32039915dd5b9ba01baae1f436c/numpy-2.4.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef", size = 6546330, upload-time = "2026-01-31T23:11:23.958Z" }, - { url = "https://files.pythonhosted.org/packages/19/fb/cbfdbfa3057a10aea5422c558ac57538e6acc87ec1669e666d32ac198da7/numpy-2.4.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7", size = 15660968, upload-time = "2026-01-31T23:11:25.713Z" }, - { url = "https://files.pythonhosted.org/packages/04/dc/46066ce18d01645541f0186877377b9371b8fa8017fa8262002b4ef22612/numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499", size = 16607311, upload-time = "2026-01-31T23:11:28.117Z" }, - { url = "https://files.pythonhosted.org/packages/14/d9/4b5adfc39a43fa6bf918c6d544bc60c05236cc2f6339847fc5b35e6cb5b0/numpy-2.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb", size = 17012850, upload-time = "2026-01-31T23:11:30.888Z" }, - { url = "https://files.pythonhosted.org/packages/b7/20/adb6e6adde6d0130046e6fdfb7675cc62bc2f6b7b02239a09eb58435753d/numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7", size = 18334210, upload-time = "2026-01-31T23:11:33.214Z" }, - { url = "https://files.pythonhosted.org/packages/78/0e/0a73b3dff26803a8c02baa76398015ea2a5434d9b8265a7898a6028c1591/numpy-2.4.2-cp313-cp313-win32.whl", hash = "sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110", size = 5958199, upload-time = "2026-01-31T23:11:35.385Z" }, - { url = "https://files.pythonhosted.org/packages/43/bc/6352f343522fcb2c04dbaf94cb30cca6fd32c1a750c06ad6231b4293708c/numpy-2.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622", size = 12310848, upload-time = "2026-01-31T23:11:38.001Z" }, - { url = "https://files.pythonhosted.org/packages/6e/8d/6da186483e308da5da1cc6918ce913dcfe14ffde98e710bfeff2a6158d4e/numpy-2.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71", size = 10221082, upload-time = "2026-01-31T23:11:40.392Z" }, - { url = "https://files.pythonhosted.org/packages/25/a1/9510aa43555b44781968935c7548a8926274f815de42ad3997e9e83680dd/numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262", size = 14815866, upload-time = "2026-01-31T23:11:42.495Z" }, - { url = "https://files.pythonhosted.org/packages/36/30/6bbb5e76631a5ae46e7923dd16ca9d3f1c93cfa8d4ed79a129814a9d8db3/numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913", size = 5325631, upload-time = "2026-01-31T23:11:44.7Z" }, - { url = "https://files.pythonhosted.org/packages/46/00/3a490938800c1923b567b3a15cd17896e68052e2145d8662aaf3e1ffc58f/numpy-2.4.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab", size = 6646254, upload-time = "2026-01-31T23:11:46.341Z" }, - { url = "https://files.pythonhosted.org/packages/d3/e9/fac0890149898a9b609caa5af7455a948b544746e4b8fe7c212c8edd71f8/numpy-2.4.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82", size = 15720138, upload-time = "2026-01-31T23:11:48.082Z" }, - { url = "https://files.pythonhosted.org/packages/ea/5c/08887c54e68e1e28df53709f1893ce92932cc6f01f7c3d4dc952f61ffd4e/numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f", size = 16655398, upload-time = "2026-01-31T23:11:50.293Z" }, - { url = "https://files.pythonhosted.org/packages/4d/89/253db0fa0e66e9129c745e4ef25631dc37d5f1314dad2b53e907b8538e6d/numpy-2.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554", size = 17079064, upload-time = "2026-01-31T23:11:52.927Z" }, - { url = "https://files.pythonhosted.org/packages/2a/d5/cbade46ce97c59c6c3da525e8d95b7abe8a42974a1dc5c1d489c10433e88/numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257", size = 18379680, upload-time = "2026-01-31T23:11:55.22Z" }, - { url = "https://files.pythonhosted.org/packages/40/62/48f99ae172a4b63d981babe683685030e8a3df4f246c893ea5c6ef99f018/numpy-2.4.2-cp313-cp313t-win32.whl", hash = "sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657", size = 6082433, upload-time = "2026-01-31T23:11:58.096Z" }, - { url = "https://files.pythonhosted.org/packages/07/38/e054a61cfe48ad9f1ed0d188e78b7e26859d0b60ef21cd9de4897cdb5326/numpy-2.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b", size = 12451181, upload-time = "2026-01-31T23:11:59.782Z" }, - { url = "https://files.pythonhosted.org/packages/6e/a4/a05c3a6418575e185dd84d0b9680b6bb2e2dc3e4202f036b7b4e22d6e9dc/numpy-2.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1", size = 10290756, upload-time = "2026-01-31T23:12:02.438Z" }, - { url = "https://files.pythonhosted.org/packages/18/88/b7df6050bf18fdcfb7046286c6535cabbdd2064a3440fca3f069d319c16e/numpy-2.4.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:444be170853f1f9d528428eceb55f12918e4fda5d8805480f36a002f1415e09b", size = 16663092, upload-time = "2026-01-31T23:12:04.521Z" }, - { url = "https://files.pythonhosted.org/packages/25/7a/1fee4329abc705a469a4afe6e69b1ef7e915117747886327104a8493a955/numpy-2.4.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d1240d50adff70c2a88217698ca844723068533f3f5c5fa6ee2e3220e3bdb000", size = 14698770, upload-time = "2026-01-31T23:12:06.96Z" }, - { url = "https://files.pythonhosted.org/packages/fb/0b/f9e49ba6c923678ad5bc38181c08ac5e53b7a5754dbca8e581aa1a56b1ff/numpy-2.4.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:7cdde6de52fb6664b00b056341265441192d1291c130e99183ec0d4b110ff8b1", size = 5208562, upload-time = "2026-01-31T23:12:09.632Z" }, - { url = "https://files.pythonhosted.org/packages/7d/12/d7de8f6f53f9bb76997e5e4c069eda2051e3fe134e9181671c4391677bb2/numpy-2.4.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:cda077c2e5b780200b6b3e09d0b42205a3d1c68f30c6dceb90401c13bff8fe74", size = 6543710, upload-time = "2026-01-31T23:12:11.969Z" }, - { url = "https://files.pythonhosted.org/packages/09/63/c66418c2e0268a31a4cf8a8b512685748200f8e8e8ec6c507ce14e773529/numpy-2.4.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d30291931c915b2ab5717c2974bb95ee891a1cf22ebc16a8006bd59cd210d40a", size = 15677205, upload-time = "2026-01-31T23:12:14.33Z" }, - { url = "https://files.pythonhosted.org/packages/5d/6c/7f237821c9642fb2a04d2f1e88b4295677144ca93285fd76eff3bcba858d/numpy-2.4.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bba37bc29d4d85761deed3954a1bc62be7cf462b9510b51d367b769a8c8df325", size = 16611738, upload-time = "2026-01-31T23:12:16.525Z" }, - { url = "https://files.pythonhosted.org/packages/c2/a7/39c4cdda9f019b609b5c473899d87abff092fc908cfe4d1ecb2fcff453b0/numpy-2.4.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b2f0073ed0868db1dcd86e052d37279eef185b9c8db5bf61f30f46adac63c909", size = 17028888, upload-time = "2026-01-31T23:12:19.306Z" }, - { url = "https://files.pythonhosted.org/packages/da/b3/e84bb64bdfea967cc10950d71090ec2d84b49bc691df0025dddb7c26e8e3/numpy-2.4.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f54844851cdb630ceb623dcec4db3240d1ac13d4990532446761baede94996a", size = 18339556, upload-time = "2026-01-31T23:12:21.816Z" }, - { url = "https://files.pythonhosted.org/packages/88/f5/954a291bc1192a27081706862ac62bb5920fbecfbaa302f64682aa90beed/numpy-2.4.2-cp314-cp314-win32.whl", hash = "sha256:12e26134a0331d8dbd9351620f037ec470b7c75929cb8a1537f6bfe411152a1a", size = 6006899, upload-time = "2026-01-31T23:12:24.14Z" }, - { url = "https://files.pythonhosted.org/packages/05/cb/eff72a91b2efdd1bc98b3b8759f6a1654aa87612fc86e3d87d6fe4f948c4/numpy-2.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:068cdb2d0d644cdb45670810894f6a0600797a69c05f1ac478e8d31670b8ee75", size = 12443072, upload-time = "2026-01-31T23:12:26.33Z" }, - { url = "https://files.pythonhosted.org/packages/37/75/62726948db36a56428fce4ba80a115716dc4fad6a3a4352487f8bb950966/numpy-2.4.2-cp314-cp314-win_arm64.whl", hash = "sha256:6ed0be1ee58eef41231a5c943d7d1375f093142702d5723ca2eb07db9b934b05", size = 10494886, upload-time = "2026-01-31T23:12:28.488Z" }, - { url = "https://files.pythonhosted.org/packages/36/2f/ee93744f1e0661dc267e4b21940870cabfae187c092e1433b77b09b50ac4/numpy-2.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:98f16a80e917003a12c0580f97b5f875853ebc33e2eaa4bccfc8201ac6869308", size = 14818567, upload-time = "2026-01-31T23:12:30.709Z" }, - { url = "https://files.pythonhosted.org/packages/a7/24/6535212add7d76ff938d8bdc654f53f88d35cddedf807a599e180dcb8e66/numpy-2.4.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:20abd069b9cda45874498b245c8015b18ace6de8546bf50dfa8cea1696ed06ef", size = 5328372, upload-time = "2026-01-31T23:12:32.962Z" }, - { url = "https://files.pythonhosted.org/packages/5e/9d/c48f0a035725f925634bf6b8994253b43f2047f6778a54147d7e213bc5a7/numpy-2.4.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e98c97502435b53741540a5717a6749ac2ada901056c7db951d33e11c885cc7d", size = 6649306, upload-time = "2026-01-31T23:12:34.797Z" }, - { url = "https://files.pythonhosted.org/packages/81/05/7c73a9574cd4a53a25907bad38b59ac83919c0ddc8234ec157f344d57d9a/numpy-2.4.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da6cad4e82cb893db4b69105c604d805e0c3ce11501a55b5e9f9083b47d2ffe8", size = 15722394, upload-time = "2026-01-31T23:12:36.565Z" }, - { url = "https://files.pythonhosted.org/packages/35/fa/4de10089f21fc7d18442c4a767ab156b25c2a6eaf187c0db6d9ecdaeb43f/numpy-2.4.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4424677ce4b47fe73c8b5556d876571f7c6945d264201180db2dc34f676ab5", size = 16653343, upload-time = "2026-01-31T23:12:39.188Z" }, - { url = "https://files.pythonhosted.org/packages/b8/f9/d33e4ffc857f3763a57aa85650f2e82486832d7492280ac21ba9efda80da/numpy-2.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2b8f157c8a6f20eb657e240f8985cc135598b2b46985c5bccbde7616dc9c6b1e", size = 17078045, upload-time = "2026-01-31T23:12:42.041Z" }, - { url = "https://files.pythonhosted.org/packages/c8/b8/54bdb43b6225badbea6389fa038c4ef868c44f5890f95dd530a218706da3/numpy-2.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5daf6f3914a733336dab21a05cdec343144600e964d2fcdabaac0c0269874b2a", size = 18380024, upload-time = "2026-01-31T23:12:44.331Z" }, - { url = "https://files.pythonhosted.org/packages/a5/55/6e1a61ded7af8df04016d81b5b02daa59f2ea9252ee0397cb9f631efe9e5/numpy-2.4.2-cp314-cp314t-win32.whl", hash = "sha256:8c50dd1fc8826f5b26a5ee4d77ca55d88a895f4e4819c7ecc2a9f5905047a443", size = 6153937, upload-time = "2026-01-31T23:12:47.229Z" }, - { url = "https://files.pythonhosted.org/packages/45/aa/fa6118d1ed6d776b0983f3ceac9b1a5558e80df9365b1c3aa6d42bf9eee4/numpy-2.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fcf92bee92742edd401ba41135185866f7026c502617f422eb432cfeca4fe236", size = 12631844, upload-time = "2026-01-31T23:12:48.997Z" }, - { url = "https://files.pythonhosted.org/packages/32/0a/2ec5deea6dcd158f254a7b372fb09cfba5719419c8d66343bab35237b3fb/numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181", size = 10565379, upload-time = "2026-01-31T23:12:51.345Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f8/50e14d36d915ef64d8f8bc4a087fc8264d82c785eda6711f80ab7e620335/numpy-2.4.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:89f7268c009bc492f506abd6f5265defa7cb3f7487dc21d357c3d290add45082", size = 16833179, upload-time = "2026-01-31T23:12:53.5Z" }, - { url = "https://files.pythonhosted.org/packages/17/17/809b5cad63812058a8189e91a1e2d55a5a18fd04611dbad244e8aeae465c/numpy-2.4.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6dee3bb76aa4009d5a912180bf5b2de012532998d094acee25d9cb8dee3e44a", size = 14889755, upload-time = "2026-01-31T23:12:55.933Z" }, - { url = "https://files.pythonhosted.org/packages/3e/ea/181b9bcf7627fc8371720316c24db888dcb9829b1c0270abf3d288b2e29b/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:cd2bd2bbed13e213d6b55dc1d035a4f91748a7d3edc9480c13898b0353708920", size = 5399500, upload-time = "2026-01-31T23:12:58.671Z" }, - { url = "https://files.pythonhosted.org/packages/33/9f/413adf3fc955541ff5536b78fcf0754680b3c6d95103230252a2c9408d23/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:cf28c0c1d4c4bf00f509fa7eb02c58d7caf221b50b467bcb0d9bbf1584d5c821", size = 6714252, upload-time = "2026-01-31T23:13:00.518Z" }, - { url = "https://files.pythonhosted.org/packages/91/da/643aad274e29ccbdf42ecd94dafe524b81c87bcb56b83872d54827f10543/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e04ae107ac591763a47398bb45b568fc38f02dbc4aa44c063f67a131f99346cb", size = 15797142, upload-time = "2026-01-31T23:13:02.219Z" }, - { url = "https://files.pythonhosted.org/packages/66/27/965b8525e9cb5dc16481b30a1b3c21e50c7ebf6e9dbd48d0c4d0d5089c7e/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:602f65afdef699cda27ec0b9224ae5dc43e328f4c24c689deaf77133dbee74d0", size = 16727979, upload-time = "2026-01-31T23:13:04.62Z" }, - { url = "https://files.pythonhosted.org/packages/de/e5/b7d20451657664b07986c2f6e3be564433f5dcaf3482d68eaecd79afaf03/numpy-2.4.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be71bf1edb48ebbbf7f6337b5bfd2f895d1902f6335a5830b20141fc126ffba0", size = 12502577, upload-time = "2026-01-31T23:13:07.08Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/44/71852273146957899753e69986246d6a176061ea183407e95418c2aa4d9a/numpy-2.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7e88598032542bd49af7c4747541422884219056c268823ef6e5e89851c8825", size = 16955478 }, + { url = "https://files.pythonhosted.org/packages/74/41/5d17d4058bd0cd96bcbd4d9ff0fb2e21f52702aab9a72e4a594efa18692f/numpy-2.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7edc794af8b36ca37ef5fcb5e0d128c7e0595c7b96a2318d1badb6fcd8ee86b1", size = 14965467 }, + { url = "https://files.pythonhosted.org/packages/49/48/fb1ce8136c19452ed15f033f8aee91d5defe515094e330ce368a0647846f/numpy-2.4.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6e9f61981ace1360e42737e2bae58b27bf28a1b27e781721047d84bd754d32e7", size = 5475172 }, + { url = "https://files.pythonhosted.org/packages/40/a9/3feb49f17bbd1300dd2570432961f5c8a4ffeff1db6f02c7273bd020a4c9/numpy-2.4.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cb7bbb88aa74908950d979eeaa24dbdf1a865e3c7e45ff0121d8f70387b55f73", size = 6805145 }, + { url = "https://files.pythonhosted.org/packages/3f/39/fdf35cbd6d6e2fcad42fcf85ac04a85a0d0fbfbf34b30721c98d602fd70a/numpy-2.4.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f069069931240b3fc703f1e23df63443dbd6390614c8c44a87d96cd0ec81eb1", size = 15966084 }, + { url = "https://files.pythonhosted.org/packages/1b/46/6fa4ea94f1ddf969b2ee941290cca6f1bfac92b53c76ae5f44afe17ceb69/numpy-2.4.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c02ef4401a506fb60b411467ad501e1429a3487abca4664871d9ae0b46c8ba32", size = 16899477 }, + { url = "https://files.pythonhosted.org/packages/09/a1/2a424e162b1a14a5bd860a464ab4e07513916a64ab1683fae262f735ccd2/numpy-2.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2653de5c24910e49c2b106499803124dde62a5a1fe0eedeaecf4309a5f639390", size = 17323429 }, + { url = "https://files.pythonhosted.org/packages/ce/a2/73014149ff250628df72c58204822ac01d768697913881aacf839ff78680/numpy-2.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1ae241bbfc6ae276f94a170b14785e561cb5e7f626b6688cf076af4110887413", size = 18635109 }, + { url = "https://files.pythonhosted.org/packages/6c/0c/73e8be2f1accd56df74abc1c5e18527822067dced5ec0861b5bb882c2ce0/numpy-2.4.2-cp311-cp311-win32.whl", hash = "sha256:df1b10187212b198dd45fa943d8985a3c8cf854aed4923796e0e019e113a1bda", size = 6237915 }, + { url = "https://files.pythonhosted.org/packages/76/ae/e0265e0163cf127c24c3969d29f1c4c64551a1e375d95a13d32eab25d364/numpy-2.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:b9c618d56a29c9cb1c4da979e9899be7578d2e0b3c24d52079c166324c9e8695", size = 12607972 }, + { url = "https://files.pythonhosted.org/packages/29/a5/c43029af9b8014d6ea157f192652c50042e8911f4300f8f6ed3336bf437f/numpy-2.4.2-cp311-cp311-win_arm64.whl", hash = "sha256:47c5a6ed21d9452b10227e5e8a0e1c22979811cad7dcc19d8e3e2fb8fa03f1a3", size = 10485763 }, + { url = "https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a", size = 16667963 }, + { url = "https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1", size = 14693571 }, + { url = "https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e", size = 5203469 }, + { url = "https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27", size = 6550820 }, + { url = "https://files.pythonhosted.org/packages/b1/91/b97fdfd12dc75b02c44e26c6638241cc004d4079a0321a69c62f51470c4c/numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548", size = 15663067 }, + { url = "https://files.pythonhosted.org/packages/f5/c6/a18e59f3f0b8071cc85cbc8d80cd02d68aa9710170b2553a117203d46936/numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f", size = 16619782 }, + { url = "https://files.pythonhosted.org/packages/b7/83/9751502164601a79e18847309f5ceec0b1446d7b6aa12305759b72cf98b2/numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460", size = 17013128 }, + { url = "https://files.pythonhosted.org/packages/61/c4/c4066322256ec740acc1c8923a10047818691d2f8aec254798f3dd90f5f2/numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba", size = 18345324 }, + { url = "https://files.pythonhosted.org/packages/ab/af/6157aa6da728fa4525a755bfad486ae7e3f76d4c1864138003eb84328497/numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f", size = 5960282 }, + { url = "https://files.pythonhosted.org/packages/92/0f/7ceaaeaacb40567071e94dbf2c9480c0ae453d5bb4f52bea3892c39dc83c/numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85", size = 12314210 }, + { url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171 }, + { url = "https://files.pythonhosted.org/packages/a1/22/815b9fe25d1d7ae7d492152adbc7226d3eff731dffc38fe970589fcaaa38/numpy-2.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c", size = 16663696 }, + { url = "https://files.pythonhosted.org/packages/09/f0/817d03a03f93ba9c6c8993de509277d84e69f9453601915e4a69554102a1/numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979", size = 14688322 }, + { url = "https://files.pythonhosted.org/packages/da/b4/f805ab79293c728b9a99438775ce51885fd4f31b76178767cfc718701a39/numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98", size = 5198157 }, + { url = "https://files.pythonhosted.org/packages/74/09/826e4289844eccdcd64aac27d13b0fd3f32039915dd5b9ba01baae1f436c/numpy-2.4.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef", size = 6546330 }, + { url = "https://files.pythonhosted.org/packages/19/fb/cbfdbfa3057a10aea5422c558ac57538e6acc87ec1669e666d32ac198da7/numpy-2.4.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7", size = 15660968 }, + { url = "https://files.pythonhosted.org/packages/04/dc/46066ce18d01645541f0186877377b9371b8fa8017fa8262002b4ef22612/numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499", size = 16607311 }, + { url = "https://files.pythonhosted.org/packages/14/d9/4b5adfc39a43fa6bf918c6d544bc60c05236cc2f6339847fc5b35e6cb5b0/numpy-2.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb", size = 17012850 }, + { url = "https://files.pythonhosted.org/packages/b7/20/adb6e6adde6d0130046e6fdfb7675cc62bc2f6b7b02239a09eb58435753d/numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7", size = 18334210 }, + { url = "https://files.pythonhosted.org/packages/78/0e/0a73b3dff26803a8c02baa76398015ea2a5434d9b8265a7898a6028c1591/numpy-2.4.2-cp313-cp313-win32.whl", hash = "sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110", size = 5958199 }, + { url = "https://files.pythonhosted.org/packages/43/bc/6352f343522fcb2c04dbaf94cb30cca6fd32c1a750c06ad6231b4293708c/numpy-2.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622", size = 12310848 }, + { url = "https://files.pythonhosted.org/packages/6e/8d/6da186483e308da5da1cc6918ce913dcfe14ffde98e710bfeff2a6158d4e/numpy-2.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71", size = 10221082 }, + { url = "https://files.pythonhosted.org/packages/25/a1/9510aa43555b44781968935c7548a8926274f815de42ad3997e9e83680dd/numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262", size = 14815866 }, + { url = "https://files.pythonhosted.org/packages/36/30/6bbb5e76631a5ae46e7923dd16ca9d3f1c93cfa8d4ed79a129814a9d8db3/numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913", size = 5325631 }, + { url = "https://files.pythonhosted.org/packages/46/00/3a490938800c1923b567b3a15cd17896e68052e2145d8662aaf3e1ffc58f/numpy-2.4.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab", size = 6646254 }, + { url = "https://files.pythonhosted.org/packages/d3/e9/fac0890149898a9b609caa5af7455a948b544746e4b8fe7c212c8edd71f8/numpy-2.4.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82", size = 15720138 }, + { url = "https://files.pythonhosted.org/packages/ea/5c/08887c54e68e1e28df53709f1893ce92932cc6f01f7c3d4dc952f61ffd4e/numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f", size = 16655398 }, + { url = "https://files.pythonhosted.org/packages/4d/89/253db0fa0e66e9129c745e4ef25631dc37d5f1314dad2b53e907b8538e6d/numpy-2.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554", size = 17079064 }, + { url = "https://files.pythonhosted.org/packages/2a/d5/cbade46ce97c59c6c3da525e8d95b7abe8a42974a1dc5c1d489c10433e88/numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257", size = 18379680 }, + { url = "https://files.pythonhosted.org/packages/40/62/48f99ae172a4b63d981babe683685030e8a3df4f246c893ea5c6ef99f018/numpy-2.4.2-cp313-cp313t-win32.whl", hash = "sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657", size = 6082433 }, + { url = "https://files.pythonhosted.org/packages/07/38/e054a61cfe48ad9f1ed0d188e78b7e26859d0b60ef21cd9de4897cdb5326/numpy-2.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b", size = 12451181 }, + { url = "https://files.pythonhosted.org/packages/6e/a4/a05c3a6418575e185dd84d0b9680b6bb2e2dc3e4202f036b7b4e22d6e9dc/numpy-2.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1", size = 10290756 }, + { url = "https://files.pythonhosted.org/packages/18/88/b7df6050bf18fdcfb7046286c6535cabbdd2064a3440fca3f069d319c16e/numpy-2.4.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:444be170853f1f9d528428eceb55f12918e4fda5d8805480f36a002f1415e09b", size = 16663092 }, + { url = "https://files.pythonhosted.org/packages/25/7a/1fee4329abc705a469a4afe6e69b1ef7e915117747886327104a8493a955/numpy-2.4.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d1240d50adff70c2a88217698ca844723068533f3f5c5fa6ee2e3220e3bdb000", size = 14698770 }, + { url = "https://files.pythonhosted.org/packages/fb/0b/f9e49ba6c923678ad5bc38181c08ac5e53b7a5754dbca8e581aa1a56b1ff/numpy-2.4.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:7cdde6de52fb6664b00b056341265441192d1291c130e99183ec0d4b110ff8b1", size = 5208562 }, + { url = "https://files.pythonhosted.org/packages/7d/12/d7de8f6f53f9bb76997e5e4c069eda2051e3fe134e9181671c4391677bb2/numpy-2.4.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:cda077c2e5b780200b6b3e09d0b42205a3d1c68f30c6dceb90401c13bff8fe74", size = 6543710 }, + { url = "https://files.pythonhosted.org/packages/09/63/c66418c2e0268a31a4cf8a8b512685748200f8e8e8ec6c507ce14e773529/numpy-2.4.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d30291931c915b2ab5717c2974bb95ee891a1cf22ebc16a8006bd59cd210d40a", size = 15677205 }, + { url = "https://files.pythonhosted.org/packages/5d/6c/7f237821c9642fb2a04d2f1e88b4295677144ca93285fd76eff3bcba858d/numpy-2.4.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bba37bc29d4d85761deed3954a1bc62be7cf462b9510b51d367b769a8c8df325", size = 16611738 }, + { url = "https://files.pythonhosted.org/packages/c2/a7/39c4cdda9f019b609b5c473899d87abff092fc908cfe4d1ecb2fcff453b0/numpy-2.4.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b2f0073ed0868db1dcd86e052d37279eef185b9c8db5bf61f30f46adac63c909", size = 17028888 }, + { url = "https://files.pythonhosted.org/packages/da/b3/e84bb64bdfea967cc10950d71090ec2d84b49bc691df0025dddb7c26e8e3/numpy-2.4.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f54844851cdb630ceb623dcec4db3240d1ac13d4990532446761baede94996a", size = 18339556 }, + { url = "https://files.pythonhosted.org/packages/88/f5/954a291bc1192a27081706862ac62bb5920fbecfbaa302f64682aa90beed/numpy-2.4.2-cp314-cp314-win32.whl", hash = "sha256:12e26134a0331d8dbd9351620f037ec470b7c75929cb8a1537f6bfe411152a1a", size = 6006899 }, + { url = "https://files.pythonhosted.org/packages/05/cb/eff72a91b2efdd1bc98b3b8759f6a1654aa87612fc86e3d87d6fe4f948c4/numpy-2.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:068cdb2d0d644cdb45670810894f6a0600797a69c05f1ac478e8d31670b8ee75", size = 12443072 }, + { url = "https://files.pythonhosted.org/packages/37/75/62726948db36a56428fce4ba80a115716dc4fad6a3a4352487f8bb950966/numpy-2.4.2-cp314-cp314-win_arm64.whl", hash = "sha256:6ed0be1ee58eef41231a5c943d7d1375f093142702d5723ca2eb07db9b934b05", size = 10494886 }, + { url = "https://files.pythonhosted.org/packages/36/2f/ee93744f1e0661dc267e4b21940870cabfae187c092e1433b77b09b50ac4/numpy-2.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:98f16a80e917003a12c0580f97b5f875853ebc33e2eaa4bccfc8201ac6869308", size = 14818567 }, + { url = "https://files.pythonhosted.org/packages/a7/24/6535212add7d76ff938d8bdc654f53f88d35cddedf807a599e180dcb8e66/numpy-2.4.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:20abd069b9cda45874498b245c8015b18ace6de8546bf50dfa8cea1696ed06ef", size = 5328372 }, + { url = "https://files.pythonhosted.org/packages/5e/9d/c48f0a035725f925634bf6b8994253b43f2047f6778a54147d7e213bc5a7/numpy-2.4.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e98c97502435b53741540a5717a6749ac2ada901056c7db951d33e11c885cc7d", size = 6649306 }, + { url = "https://files.pythonhosted.org/packages/81/05/7c73a9574cd4a53a25907bad38b59ac83919c0ddc8234ec157f344d57d9a/numpy-2.4.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da6cad4e82cb893db4b69105c604d805e0c3ce11501a55b5e9f9083b47d2ffe8", size = 15722394 }, + { url = "https://files.pythonhosted.org/packages/35/fa/4de10089f21fc7d18442c4a767ab156b25c2a6eaf187c0db6d9ecdaeb43f/numpy-2.4.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4424677ce4b47fe73c8b5556d876571f7c6945d264201180db2dc34f676ab5", size = 16653343 }, + { url = "https://files.pythonhosted.org/packages/b8/f9/d33e4ffc857f3763a57aa85650f2e82486832d7492280ac21ba9efda80da/numpy-2.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2b8f157c8a6f20eb657e240f8985cc135598b2b46985c5bccbde7616dc9c6b1e", size = 17078045 }, + { url = "https://files.pythonhosted.org/packages/c8/b8/54bdb43b6225badbea6389fa038c4ef868c44f5890f95dd530a218706da3/numpy-2.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5daf6f3914a733336dab21a05cdec343144600e964d2fcdabaac0c0269874b2a", size = 18380024 }, + { url = "https://files.pythonhosted.org/packages/a5/55/6e1a61ded7af8df04016d81b5b02daa59f2ea9252ee0397cb9f631efe9e5/numpy-2.4.2-cp314-cp314t-win32.whl", hash = "sha256:8c50dd1fc8826f5b26a5ee4d77ca55d88a895f4e4819c7ecc2a9f5905047a443", size = 6153937 }, + { url = "https://files.pythonhosted.org/packages/45/aa/fa6118d1ed6d776b0983f3ceac9b1a5558e80df9365b1c3aa6d42bf9eee4/numpy-2.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fcf92bee92742edd401ba41135185866f7026c502617f422eb432cfeca4fe236", size = 12631844 }, + { url = "https://files.pythonhosted.org/packages/32/0a/2ec5deea6dcd158f254a7b372fb09cfba5719419c8d66343bab35237b3fb/numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181", size = 10565379 }, + { url = "https://files.pythonhosted.org/packages/f4/f8/50e14d36d915ef64d8f8bc4a087fc8264d82c785eda6711f80ab7e620335/numpy-2.4.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:89f7268c009bc492f506abd6f5265defa7cb3f7487dc21d357c3d290add45082", size = 16833179 }, + { url = "https://files.pythonhosted.org/packages/17/17/809b5cad63812058a8189e91a1e2d55a5a18fd04611dbad244e8aeae465c/numpy-2.4.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6dee3bb76aa4009d5a912180bf5b2de012532998d094acee25d9cb8dee3e44a", size = 14889755 }, + { url = "https://files.pythonhosted.org/packages/3e/ea/181b9bcf7627fc8371720316c24db888dcb9829b1c0270abf3d288b2e29b/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:cd2bd2bbed13e213d6b55dc1d035a4f91748a7d3edc9480c13898b0353708920", size = 5399500 }, + { url = "https://files.pythonhosted.org/packages/33/9f/413adf3fc955541ff5536b78fcf0754680b3c6d95103230252a2c9408d23/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:cf28c0c1d4c4bf00f509fa7eb02c58d7caf221b50b467bcb0d9bbf1584d5c821", size = 6714252 }, + { url = "https://files.pythonhosted.org/packages/91/da/643aad274e29ccbdf42ecd94dafe524b81c87bcb56b83872d54827f10543/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e04ae107ac591763a47398bb45b568fc38f02dbc4aa44c063f67a131f99346cb", size = 15797142 }, + { url = "https://files.pythonhosted.org/packages/66/27/965b8525e9cb5dc16481b30a1b3c21e50c7ebf6e9dbd48d0c4d0d5089c7e/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:602f65afdef699cda27ec0b9224ae5dc43e328f4c24c689deaf77133dbee74d0", size = 16727979 }, + { url = "https://files.pythonhosted.org/packages/de/e5/b7d20451657664b07986c2f6e3be564433f5dcaf3482d68eaecd79afaf03/numpy-2.4.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be71bf1edb48ebbbf7f6337b5bfd2f895d1902f6335a5830b20141fc126ffba0", size = 12502577 }, ] [[package]] -name = "nvidia-cublas-cu12" -version = "12.8.4.1" +name = "nvidia-cublas" +version = "13.1.0.3" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/61/e24b560ab2e2eaeb3c839129175fb330dfcfc29e5203196e5541a4c44682/nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:8ac4e771d5a348c551b2a426eda6193c19aa630236b418086020df5ba9667142", size = 594346921, upload-time = "2025-03-07T01:44:31.254Z" }, + { url = "https://files.pythonhosted.org/packages/e1/a5/fce49e2ae977e0ccc084e5adafceb4f0ac0c8333cb6863501618a7277f67/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:c86fc7f7ae36d7528288c5d88098edcb7b02c633d262e7ddbb86b0ad91be5df2", size = 542851226 }, + { url = "https://files.pythonhosted.org/packages/e7/44/423ac00af4dd95a5aeb27207e2c0d9b7118702149bf4704c3ddb55bb7429/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:ee8722c1f0145ab246bccb9e452153b5e0515fd094c3678df50b2a0888b8b171", size = 423133236 }, ] [[package]] -name = "nvidia-cuda-cupti-cu12" -version = "12.8.90" +name = "nvidia-cuda-cupti" +version = "13.0.85" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/02/2adcaa145158bf1a8295d83591d22e4103dbfd821bcaf6f3f53151ca4ffa/nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea0cb07ebda26bb9b29ba82cda34849e73c166c18162d3913575b0c9db9a6182", size = 10248621, upload-time = "2025-03-07T01:40:21.213Z" }, + { url = "https://files.pythonhosted.org/packages/2a/2a/80353b103fc20ce05ef51e928daed4b6015db4aaa9162ed0997090fe2250/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_aarch64.whl", hash = "sha256:796bd679890ee55fb14a94629b698b6db54bcfd833d391d5e94017dd9d7d3151", size = 10310827 }, + { url = "https://files.pythonhosted.org/packages/33/6d/737d164b4837a9bbd202f5ae3078975f0525a55730fe871d8ed4e3b952b0/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_x86_64.whl", hash = "sha256:4eb01c08e859bf924d222250d2e8f8b8ff6d3db4721288cf35d14252a4d933c8", size = 10715597 }, ] [[package]] -name = "nvidia-cuda-nvrtc-cu12" -version = "12.8.93" +name = "nvidia-cuda-nvrtc" +version = "13.0.88" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/6b/32f747947df2da6994e999492ab306a903659555dddc0fbdeb9d71f75e52/nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:a7756528852ef889772a84c6cd89d41dfa74667e24cca16bb31f8f061e3e9994", size = 88040029, upload-time = "2025-03-07T01:42:13.562Z" }, + { url = "https://files.pythonhosted.org/packages/c3/68/483a78f5e8f31b08fb1bb671559968c0ca3a065ac7acabfc7cee55214fd6/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:ad9b6d2ead2435f11cbb6868809d2adeeee302e9bb94bcf0539c7a40d80e8575", size = 90215200 }, + { url = "https://files.pythonhosted.org/packages/b7/dc/6bb80850e0b7edd6588d560758f17e0550893a1feaf436807d64d2da040f/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d27f20a0ca67a4bb34268a5e951033496c5b74870b868bacd046b1b8e0c3267b", size = 43015449 }, ] [[package]] -name = "nvidia-cuda-runtime-cu12" -version = "12.8.90" +name = "nvidia-cuda-runtime" +version = "13.0.96" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/9b/a997b638fcd068ad6e4d53b8551a7d30fe8b404d6f1804abf1df69838932/nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adade8dcbd0edf427b7204d480d6066d33902cab2a4707dcfc48a2d0fd44ab90", size = 954765, upload-time = "2025-03-07T01:40:01.615Z" }, + { url = "https://files.pythonhosted.org/packages/87/4f/17d7b9b8e285199c58ce28e31b5c5bbaa4d8271af06a89b6405258245de2/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef9bcbe90493a2b9d810e43d249adb3d02e98dd30200d86607d8d02687c43f55", size = 2261060 }, + { url = "https://files.pythonhosted.org/packages/2e/24/d1558f3b68b1d26e706813b1d10aa1d785e4698c425af8db8edc3dced472/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f82250d7782aa23b6cfe765ecc7db554bd3c2870c43f3d1821f1d18aebf0548", size = 2243632 }, ] [[package]] -name = "nvidia-cudnn-cu12" -version = "9.10.2.21" +name = "nvidia-cudnn-cu13" +version = "9.19.0.56" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas-cu12" }, + { name = "nvidia-cublas" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/51/e123d997aa098c61d029f76663dedbfb9bc8dcf8c60cbd6adbe42f76d049/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8", size = 706758467, upload-time = "2025-06-06T21:54:08.597Z" }, + { url = "https://files.pythonhosted.org/packages/f1/84/26025437c1e6b61a707442184fa0c03d083b661adf3a3eecfd6d21677740/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:6ed29ffaee1176c612daf442e4dd6cfeb6a0caa43ddcbeb59da94953030b1be4", size = 433781201 }, + { url = "https://files.pythonhosted.org/packages/a3/22/0b4b932655d17a6da1b92fa92ab12844b053bb2ac2475e179ba6f043da1e/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:d20e1734305e9d68889a96e3f35094d733ff1f83932ebe462753973e53a572bf", size = 366066321 }, ] [[package]] -name = "nvidia-cufft-cu12" -version = "11.3.3.83" +name = "nvidia-cufft" +version = "12.0.0.61" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink-cu12" }, + { name = "nvidia-nvjitlink" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/13/ee4e00f30e676b66ae65b4f08cb5bcbb8392c03f54f2d5413ea99a5d1c80/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74", size = 193118695, upload-time = "2025-03-07T01:45:27.821Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554 }, + { url = "https://files.pythonhosted.org/packages/a8/2f/7b57e29836ea8714f81e9898409196f47d772d5ddedddf1592eadb8ab743/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c44f692dce8fd5ffd3e3df134b6cdb9c2f72d99cf40b62c32dde45eea9ddad3", size = 214085489 }, ] [[package]] -name = "nvidia-cufile-cu12" -version = "1.13.1.3" +name = "nvidia-cufile" +version = "1.15.1.6" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/fe/1bcba1dfbfb8d01be8d93f07bfc502c93fa23afa6fd5ab3fc7c1df71038a/nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d069003be650e131b21c932ec3d8969c1715379251f8d23a1860554b1cb24fc", size = 1197834, upload-time = "2025-03-07T01:45:50.723Z" }, + { url = "https://files.pythonhosted.org/packages/3f/70/4f193de89a48b71714e74602ee14d04e4019ad36a5a9f20c425776e72cd6/nvidia_cufile-1.15.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08a3ecefae5a01c7f5117351c64f17c7c62efa5fffdbe24fc7d298da19cd0b44", size = 1223672 }, + { url = "https://files.pythonhosted.org/packages/ab/73/cc4a14c9813a8a0d509417cf5f4bdaba76e924d58beb9864f5a7baceefbf/nvidia_cufile-1.15.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:bdc0deedc61f548bddf7733bdc216456c2fdb101d020e1ab4b88d232d5e2f6d1", size = 1136992 }, ] [[package]] -name = "nvidia-curand-cu12" -version = "10.3.9.90" +name = "nvidia-curand" +version = "10.4.0.35" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/aa/6584b56dc84ebe9cf93226a5cde4d99080c8e90ab40f0c27bda7a0f29aa1/nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:b32331d4f4df5d6eefa0554c565b626c7216f87a06a4f56fab27c3b68a830ec9", size = 63619976, upload-time = "2025-03-07T01:46:23.323Z" }, + { url = "https://files.pythonhosted.org/packages/1e/72/7c2ae24fb6b63a32e6ae5d241cc65263ea18d08802aaae087d9f013335a2/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:133df5a7509c3e292aaa2b477afd0194f06ce4ea24d714d616ff36439cee349a", size = 61962106 }, + { url = "https://files.pythonhosted.org/packages/a5/9f/be0a41ca4a4917abf5cb9ae0daff1a6060cc5de950aec0396de9f3b52bc5/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:1aee33a5da6e1db083fe2b90082def8915f30f3248d5896bcec36a579d941bfc", size = 59544258 }, ] [[package]] -name = "nvidia-cusolver-cu12" -version = "11.7.3.90" +name = "nvidia-cusolver" +version = "12.0.4.66" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas-cu12" }, - { name = "nvidia-cusparse-cu12" }, - { name = "nvidia-nvjitlink-cu12" }, + { name = "nvidia-cublas" }, + { name = "nvidia-cusparse" }, + { name = "nvidia-nvjitlink" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450", size = 267506905, upload-time = "2025-03-07T01:47:16.273Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760 }, + { url = "https://files.pythonhosted.org/packages/5f/67/cba3777620cdacb99102da4042883709c41c709f4b6323c10781a9c3aa34/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0a759da5dea5c0ea10fd307de75cdeb59e7ea4fcb8add0924859b944babf1112", size = 200941980 }, ] [[package]] -name = "nvidia-cusparse-cu12" -version = "12.5.8.93" +name = "nvidia-cusparse" +version = "12.6.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink-cu12" }, + { name = "nvidia-nvjitlink" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/f5/e1854cb2f2bcd4280c44736c93550cc300ff4b8c95ebe370d0aa7d2b473d/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b", size = 288216466, upload-time = "2025-03-07T01:48:13.779Z" }, + { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568 }, + { url = "https://files.pythonhosted.org/packages/fa/18/623c77619c31d62efd55302939756966f3ecc8d724a14dab2b75f1508850/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b3c89c88d01ee0e477cb7f82ef60a11a4bcd57b6b87c33f789350b59759360b", size = 145942937 }, ] [[package]] -name = "nvidia-cusparselt-cu12" -version = "0.7.1" +name = "nvidia-cusparselt-cu13" +version = "0.8.0" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/79/12978b96bd44274fe38b5dde5cfb660b1d114f70a65ef962bcbbed99b549/nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f1bb701d6b930d5a7cea44c19ceb973311500847f81b634d802b7b539dc55623", size = 287193691, upload-time = "2025-02-26T00:15:44.104Z" }, + { url = "https://files.pythonhosted.org/packages/46/10/8dcd1175260706a2fc92a16a52e306b71d4c1ea0b0cc4a9484183399818a/nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:400c6ed1cf6780fc6efedd64ec9f1345871767e6a1a0a552a1ea0578117ea77c", size = 220791277 }, + { url = "https://files.pythonhosted.org/packages/fd/53/43b0d71f4e702fa9733f8b4571fdca50a8813f1e450b656c239beff12315/nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:25e30a8a7323935d4ad0340b95a0b69926eee755767e8e0b1cf8dd85b197d3fd", size = 169884119 }, ] [[package]] -name = "nvidia-nccl-cu12" -version = "2.27.5" +name = "nvidia-nccl-cu13" +version = "2.28.9" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/89/f7a07dc961b60645dbbf42e80f2bc85ade7feb9a491b11a1e973aa00071f/nvidia_nccl_cu12-2.27.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ad730cf15cb5d25fe849c6e6ca9eb5b76db16a80f13f425ac68d8e2e55624457", size = 322348229, upload-time = "2025-06-26T04:11:28.385Z" }, + { url = "https://files.pythonhosted.org/packages/39/55/1920646a2e43ffd4fc958536b276197ed740e9e0c54105b4bb3521591fc7/nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:01c873ba1626b54caa12272ed228dc5b2781545e0ae8ba3f432a8ef1c6d78643", size = 196561677 }, + { url = "https://files.pythonhosted.org/packages/b0/b4/878fefaad5b2bcc6fcf8d474a25e3e3774bc5133e4b58adff4d0bca238bc/nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:e4553a30f34195f3fa1da02a6da3d6337d28f2003943aa0a3d247bbc25fefc42", size = 196493177 }, ] [[package]] -name = "nvidia-nvjitlink-cu12" -version = "12.8.93" +name = "nvidia-nvjitlink" +version = "13.0.88" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/74/86a07f1d0f42998ca31312f998bd3b9a7eff7f52378f4f270c8679c77fb9/nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:81ff63371a7ebd6e6451970684f916be2eab07321b73c9d244dc2b4da7f73b88", size = 39254836, upload-time = "2025-03-07T01:49:55.661Z" }, + { url = "https://files.pythonhosted.org/packages/56/7a/123e033aaff487c77107195fa5a2b8686795ca537935a24efae476c41f05/nvidia_nvjitlink-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:13a74f429e23b921c1109976abefacc69835f2f433ebd323d3946e11d804e47b", size = 40713933 }, + { url = "https://files.pythonhosted.org/packages/ab/2c/93c5250e64df4f894f1cbb397c6fd71f79813f9fd79d7cd61de3f97b3c2d/nvidia_nvjitlink-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e931536ccc7d467a98ba1d8b89ff7fa7f1fa3b13f2b0069118cd7f47bff07d0c", size = 38768748 }, ] [[package]] -name = "nvidia-nvshmem-cu12" +name = "nvidia-nvshmem-cu13" version = "3.4.5" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/09/6ea3ea725f82e1e76684f0708bbedd871fc96da89945adeba65c3835a64c/nvidia_nvshmem_cu12-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:042f2500f24c021db8a06c5eec2539027d57460e1c1a762055a6554f72c369bd", size = 139103095, upload-time = "2025-09-06T00:32:31.266Z" }, + { url = "https://files.pythonhosted.org/packages/dc/0f/05cc9c720236dcd2db9c1ab97fff629e96821be2e63103569da0c9b72f19/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dc2a197f38e5d0376ad52cd1a2a3617d3cdc150fd5966f4aee9bcebb1d68fe9", size = 60215947 }, + { url = "https://files.pythonhosted.org/packages/3c/35/a9bf80a609e74e3b000fef598933235c908fcefcef9026042b8e6dfde2a9/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:290f0a2ee94c9f3687a02502f3b9299a9f9fe826e6d0287ee18482e78d495b80", size = 60412546 }, ] [[package]] -name = "nvidia-nvtx-cu12" -version = "12.8.90" +name = "nvidia-nvtx" +version = "13.0.85" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/eb/86626c1bbc2edb86323022371c39aa48df6fd8b0a1647bc274577f72e90b/nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b17e2001cc0d751a5bc2c6ec6d26ad95913324a4adb86788c944f8ce9ba441f", size = 89954, upload-time = "2025-03-07T01:42:44.131Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f3/d86c845465a2723ad7e1e5c36dcd75ddb82898b3f53be47ebd429fb2fa5d/nvidia_nvtx-13.0.85-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4936d1d6780fbe68db454f5e72a42ff64d1fd6397df9f363ae786930fd5c1cd4", size = 148047 }, + { url = "https://files.pythonhosted.org/packages/a8/64/3708a90d1ebe202ffdeb7185f878a3c84d15c2b2c31858da2ce0583e2def/nvidia_nvtx-13.0.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb7780edb6b14107373c835bf8b72e7a178bac7367e23da7acb108f973f157a6", size = 148878 }, ] [[package]] @@ -2147,45 +2214,45 @@ dependencies = [ { name = "sqlalchemy" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/b2/b5e12de7b4486556fe2257611b55dbabf30d0300bdb031831aa943ad20e4/optuna-4.7.0.tar.gz", hash = "sha256:d91817e2079825557bd2e97de2e8c9ae260bfc99b32712502aef8a5095b2d2c0", size = 479740, upload-time = "2026-01-19T05:45:52.604Z" } +sdist = { url = "https://files.pythonhosted.org/packages/58/b2/b5e12de7b4486556fe2257611b55dbabf30d0300bdb031831aa943ad20e4/optuna-4.7.0.tar.gz", hash = "sha256:d91817e2079825557bd2e97de2e8c9ae260bfc99b32712502aef8a5095b2d2c0", size = 479740 } wheels = [ - { url = "https://files.pythonhosted.org/packages/75/d1/6c8a4fbb38a9e3565f5c36b871262a85ecab3da48120af036b1e4937a15c/optuna-4.7.0-py3-none-any.whl", hash = "sha256:e41ec84018cecc10eabf28143573b1f0bde0ba56dba8151631a590ecbebc1186", size = 413894, upload-time = "2026-01-19T05:45:50.815Z" }, + { url = "https://files.pythonhosted.org/packages/75/d1/6c8a4fbb38a9e3565f5c36b871262a85ecab3da48120af036b1e4937a15c/optuna-4.7.0-py3-none-any.whl", hash = "sha256:e41ec84018cecc10eabf28143573b1f0bde0ba56dba8151631a590ecbebc1186", size = 413894 }, ] [[package]] name = "overrides" version = "7.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812, upload-time = "2024-01-27T21:01:33.423Z" } +sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832, upload-time = "2024-01-27T21:01:31.393Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832 }, ] [[package]] name = "packaging" version = "26.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366 }, ] [[package]] name = "pandocfilters" version = "1.5.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/70/6f/3dd4940bbe001c06a65f88e36bad298bc7a0de5036115639926b0c5c0458/pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e", size = 8454, upload-time = "2024-01-18T20:08:13.726Z" } +sdist = { url = "https://files.pythonhosted.org/packages/70/6f/3dd4940bbe001c06a65f88e36bad298bc7a0de5036115639926b0c5c0458/pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e", size = 8454 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc", size = 8663, upload-time = "2024-01-18T20:08:11.28Z" }, + { url = "https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc", size = 8663 }, ] [[package]] name = "parso" version = "0.8.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/81/76/a1e769043c0c0c9fe391b702539d594731a4362334cdf4dc25d0c09761e7/parso-0.8.6.tar.gz", hash = "sha256:2b9a0332696df97d454fa67b81618fd69c35a7b90327cbe6ba5c92d2c68a7bfd", size = 401621, upload-time = "2026-02-09T15:45:24.425Z" } +sdist = { url = "https://files.pythonhosted.org/packages/81/76/a1e769043c0c0c9fe391b702539d594731a4362334cdf4dc25d0c09761e7/parso-0.8.6.tar.gz", hash = "sha256:2b9a0332696df97d454fa67b81618fd69c35a7b90327cbe6ba5c92d2c68a7bfd", size = 401621 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl", hash = "sha256:2c549f800b70a5c4952197248825584cb00f033b29c692671d3bf08bf380baff", size = 106894, upload-time = "2026-02-09T15:45:21.391Z" }, + { url = "https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl", hash = "sha256:2c549f800b70a5c4952197248825584cb00f033b29c692671d3bf08bf380baff", size = 106894 }, ] [[package]] @@ -2196,9 +2263,9 @@ dependencies = [ { name = "locket" }, { name = "toolz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b2/3a/3f06f34820a31257ddcabdfafc2672c5816be79c7e353b02c1f318daa7d4/partd-1.4.2.tar.gz", hash = "sha256:d022c33afbdc8405c226621b015e8067888173d85f7f5ecebb3cafed9a20f02c", size = 21029, upload-time = "2024-05-06T19:51:41.945Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/3a/3f06f34820a31257ddcabdfafc2672c5816be79c7e353b02c1f318daa7d4/partd-1.4.2.tar.gz", hash = "sha256:d022c33afbdc8405c226621b015e8067888173d85f7f5ecebb3cafed9a20f02c", size = 21029 } wheels = [ - { url = "https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl", hash = "sha256:978e4ac767ec4ba5b86c6eaa52e5a2a3bc748a2ca839e8cc798f1cc6ce6efb0f", size = 18905, upload-time = "2024-05-06T19:51:39.271Z" }, + { url = "https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl", hash = "sha256:978e4ac767ec4ba5b86c6eaa52e5a2a3bc748a2ca839e8cc798f1cc6ce6efb0f", size = 18905 }, ] [[package]] @@ -2208,96 +2275,96 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ptyprocess" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772 }, ] [[package]] name = "pillow" version = "12.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/46/5da1ec4a5171ee7bf1a0efa064aba70ba3d6e0788ce3f5acd1375d23c8c0/pillow-12.1.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e879bb6cd5c73848ef3b2b48b8af9ff08c5b71ecda8048b7dd22d8a33f60be32", size = 5304084, upload-time = "2026-02-11T04:20:27.501Z" }, - { url = "https://files.pythonhosted.org/packages/78/93/a29e9bc02d1cf557a834da780ceccd54e02421627200696fcf805ebdc3fb/pillow-12.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:365b10bb9417dd4498c0e3b128018c4a624dc11c7b97d8cc54effe3b096f4c38", size = 4657866, upload-time = "2026-02-11T04:20:29.827Z" }, - { url = "https://files.pythonhosted.org/packages/13/84/583a4558d492a179d31e4aae32eadce94b9acf49c0337c4ce0b70e0a01f2/pillow-12.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d4ce8e329c93845720cd2014659ca67eac35f6433fd3050393d85f3ecef0dad5", size = 6232148, upload-time = "2026-02-11T04:20:31.329Z" }, - { url = "https://files.pythonhosted.org/packages/d5/e2/53c43334bbbb2d3b938978532fbda8e62bb6e0b23a26ce8592f36bcc4987/pillow-12.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc354a04072b765eccf2204f588a7a532c9511e8b9c7f900e1b64e3e33487090", size = 8038007, upload-time = "2026-02-11T04:20:34.225Z" }, - { url = "https://files.pythonhosted.org/packages/b8/a6/3d0e79c8a9d58150dd98e199d7c1c56861027f3829a3a60b3c2784190180/pillow-12.1.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e7976bf1910a8116b523b9f9f58bf410f3e8aa330cd9a2bb2953f9266ab49af", size = 6345418, upload-time = "2026-02-11T04:20:35.858Z" }, - { url = "https://files.pythonhosted.org/packages/a2/c8/46dfeac5825e600579157eea177be43e2f7ff4a99da9d0d0a49533509ac5/pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:597bd9c8419bc7c6af5604e55847789b69123bbe25d65cc6ad3012b4f3c98d8b", size = 7034590, upload-time = "2026-02-11T04:20:37.91Z" }, - { url = "https://files.pythonhosted.org/packages/af/bf/e6f65d3db8a8bbfeaf9e13cc0417813f6319863a73de934f14b2229ada18/pillow-12.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2c1fc0f2ca5f96a3c8407e41cca26a16e46b21060fe6d5b099d2cb01412222f5", size = 6458655, upload-time = "2026-02-11T04:20:39.496Z" }, - { url = "https://files.pythonhosted.org/packages/f9/c2/66091f3f34a25894ca129362e510b956ef26f8fb67a0e6417bc5744e56f1/pillow-12.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:578510d88c6229d735855e1f278aa305270438d36a05031dfaae5067cc8eb04d", size = 7159286, upload-time = "2026-02-11T04:20:41.139Z" }, - { url = "https://files.pythonhosted.org/packages/7b/5a/24bc8eb526a22f957d0cec6243146744966d40857e3d8deb68f7902ca6c1/pillow-12.1.1-cp311-cp311-win32.whl", hash = "sha256:7311c0a0dcadb89b36b7025dfd8326ecfa36964e29913074d47382706e516a7c", size = 6328663, upload-time = "2026-02-11T04:20:43.184Z" }, - { url = "https://files.pythonhosted.org/packages/31/03/bef822e4f2d8f9d7448c133d0a18185d3cce3e70472774fffefe8b0ed562/pillow-12.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:fbfa2a7c10cc2623f412753cddf391c7f971c52ca40a3f65dc5039b2939e8563", size = 7031448, upload-time = "2026-02-11T04:20:44.696Z" }, - { url = "https://files.pythonhosted.org/packages/49/70/f76296f53610bd17b2e7d31728b8b7825e3ac3b5b3688b51f52eab7c0818/pillow-12.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:b81b5e3511211631b3f672a595e3221252c90af017e399056d0faabb9538aa80", size = 2453651, upload-time = "2026-02-11T04:20:46.243Z" }, - { url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803, upload-time = "2026-02-11T04:20:47.653Z" }, - { url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601, upload-time = "2026-02-11T04:20:49.328Z" }, - { url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995, upload-time = "2026-02-11T04:20:51.032Z" }, - { url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012, upload-time = "2026-02-11T04:20:52.882Z" }, - { url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638, upload-time = "2026-02-11T04:20:54.444Z" }, - { url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540, upload-time = "2026-02-11T04:20:55.97Z" }, - { url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613, upload-time = "2026-02-11T04:20:57.542Z" }, - { url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745, upload-time = "2026-02-11T04:20:59.196Z" }, - { url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823, upload-time = "2026-02-11T04:21:01.385Z" }, - { url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367, upload-time = "2026-02-11T04:21:03.536Z" }, - { url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811, upload-time = "2026-02-11T04:21:05.116Z" }, - { url = "https://files.pythonhosted.org/packages/d5/11/6db24d4bd7685583caeae54b7009584e38da3c3d4488ed4cd25b439de486/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e", size = 4062689, upload-time = "2026-02-11T04:21:06.804Z" }, - { url = "https://files.pythonhosted.org/packages/33/c0/ce6d3b1fe190f0021203e0d9b5b99e57843e345f15f9ef22fcd43842fd21/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", size = 4138535, upload-time = "2026-02-11T04:21:08.452Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c6/d5eb6a4fb32a3f9c21a8c7613ec706534ea1cf9f4b3663e99f0d83f6fca8/pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6", size = 3601364, upload-time = "2026-02-11T04:21:10.194Z" }, - { url = "https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60", size = 5262561, upload-time = "2026-02-11T04:21:11.742Z" }, - { url = "https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2", size = 4657460, upload-time = "2026-02-11T04:21:13.786Z" }, - { url = "https://files.pythonhosted.org/packages/9e/1b/f1a4ea9a895b5732152789326202a82464d5254759fbacae4deea3069334/pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850", size = 6232698, upload-time = "2026-02-11T04:21:15.949Z" }, - { url = "https://files.pythonhosted.org/packages/95/f4/86f51b8745070daf21fd2e5b1fe0eb35d4db9ca26e6d58366562fb56a743/pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289", size = 8041706, upload-time = "2026-02-11T04:21:17.723Z" }, - { url = "https://files.pythonhosted.org/packages/29/9b/d6ecd956bb1266dd1045e995cce9b8d77759e740953a1c9aad9502a0461e/pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e", size = 6346621, upload-time = "2026-02-11T04:21:19.547Z" }, - { url = "https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717", size = 7038069, upload-time = "2026-02-11T04:21:21.378Z" }, - { url = "https://files.pythonhosted.org/packages/94/0e/58cb1a6bc48f746bc4cb3adb8cabff73e2742c92b3bf7a220b7cf69b9177/pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a", size = 6460040, upload-time = "2026-02-11T04:21:23.148Z" }, - { url = "https://files.pythonhosted.org/packages/6c/57/9045cb3ff11eeb6c1adce3b2d60d7d299d7b273a2e6c8381a524abfdc474/pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029", size = 7164523, upload-time = "2026-02-11T04:21:25.01Z" }, - { url = "https://files.pythonhosted.org/packages/73/f2/9be9cb99f2175f0d4dbadd6616ce1bf068ee54a28277ea1bf1fbf729c250/pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b", size = 6332552, upload-time = "2026-02-11T04:21:27.238Z" }, - { url = "https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1", size = 7040108, upload-time = "2026-02-11T04:21:29.462Z" }, - { url = "https://files.pythonhosted.org/packages/d5/7d/fc09634e2aabdd0feabaff4a32f4a7d97789223e7c2042fd805ea4b4d2c2/pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a", size = 2453712, upload-time = "2026-02-11T04:21:31.072Z" }, - { url = "https://files.pythonhosted.org/packages/19/2a/b9d62794fc8a0dd14c1943df68347badbd5511103e0d04c035ffe5cf2255/pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da", size = 5264880, upload-time = "2026-02-11T04:21:32.865Z" }, - { url = "https://files.pythonhosted.org/packages/26/9d/e03d857d1347fa5ed9247e123fcd2a97b6220e15e9cb73ca0a8d91702c6e/pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc", size = 4660616, upload-time = "2026-02-11T04:21:34.97Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ec/8a6d22afd02570d30954e043f09c32772bfe143ba9285e2fdb11284952cd/pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c", size = 6269008, upload-time = "2026-02-11T04:21:36.623Z" }, - { url = "https://files.pythonhosted.org/packages/3d/1d/6d875422c9f28a4a361f495a5f68d9de4a66941dc2c619103ca335fa6446/pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8", size = 8073226, upload-time = "2026-02-11T04:21:38.585Z" }, - { url = "https://files.pythonhosted.org/packages/a1/cd/134b0b6ee5eda6dc09e25e24b40fdafe11a520bc725c1d0bbaa5e00bf95b/pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20", size = 6380136, upload-time = "2026-02-11T04:21:40.562Z" }, - { url = "https://files.pythonhosted.org/packages/7a/a9/7628f013f18f001c1b98d8fffe3452f306a70dc6aba7d931019e0492f45e/pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13", size = 7067129, upload-time = "2026-02-11T04:21:42.521Z" }, - { url = "https://files.pythonhosted.org/packages/1e/f8/66ab30a2193b277785601e82ee2d49f68ea575d9637e5e234faaa98efa4c/pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf", size = 6491807, upload-time = "2026-02-11T04:21:44.22Z" }, - { url = "https://files.pythonhosted.org/packages/da/0b/a877a6627dc8318fdb84e357c5e1a758c0941ab1ddffdafd231983788579/pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524", size = 7190954, upload-time = "2026-02-11T04:21:46.114Z" }, - { url = "https://files.pythonhosted.org/packages/83/43/6f732ff85743cf746b1361b91665d9f5155e1483817f693f8d57ea93147f/pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986", size = 6336441, upload-time = "2026-02-11T04:21:48.22Z" }, - { url = "https://files.pythonhosted.org/packages/3b/44/e865ef3986611bb75bfabdf94a590016ea327833f434558801122979cd0e/pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c", size = 7045383, upload-time = "2026-02-11T04:21:50.015Z" }, - { url = "https://files.pythonhosted.org/packages/a8/c6/f4fb24268d0c6908b9f04143697ea18b0379490cb74ba9e8d41b898bd005/pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3", size = 2456104, upload-time = "2026-02-11T04:21:51.633Z" }, - { url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652, upload-time = "2026-02-11T04:21:53.19Z" }, - { url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823, upload-time = "2026-02-11T04:22:03.088Z" }, - { url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143, upload-time = "2026-02-11T04:22:04.909Z" }, - { url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254, upload-time = "2026-02-11T04:22:07.656Z" }, - { url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499, upload-time = "2026-02-11T04:22:09.613Z" }, - { url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137, upload-time = "2026-02-11T04:22:11.434Z" }, - { url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721, upload-time = "2026-02-11T04:22:13.321Z" }, - { url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798, upload-time = "2026-02-11T04:22:15.449Z" }, - { url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315, upload-time = "2026-02-11T04:22:17.24Z" }, - { url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360, upload-time = "2026-02-11T04:22:19.111Z" }, - { url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438, upload-time = "2026-02-11T04:22:21.041Z" }, - { url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503, upload-time = "2026-02-11T04:22:22.833Z" }, - { url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748, upload-time = "2026-02-11T04:22:24.64Z" }, - { url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314, upload-time = "2026-02-11T04:22:26.685Z" }, - { url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612, upload-time = "2026-02-11T04:22:29.884Z" }, - { url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567, upload-time = "2026-02-11T04:22:31.799Z" }, - { url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951, upload-time = "2026-02-11T04:22:33.921Z" }, - { url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769, upload-time = "2026-02-11T04:22:35.877Z" }, - { url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358, upload-time = "2026-02-11T04:22:37.698Z" }, - { url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558, upload-time = "2026-02-11T04:22:39.597Z" }, - { url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028, upload-time = "2026-02-11T04:22:42.73Z" }, - { url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940, upload-time = "2026-02-11T04:22:44.543Z" }, - { url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736, upload-time = "2026-02-11T04:22:46.347Z" }, - { url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894, upload-time = "2026-02-11T04:22:48.114Z" }, - { url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446, upload-time = "2026-02-11T04:22:50.342Z" }, - { url = "https://files.pythonhosted.org/packages/56/11/5d43209aa4cb58e0cc80127956ff1796a68b928e6324bbf06ef4db34367b/pillow-12.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:600fd103672b925fe62ed08e0d874ea34d692474df6f4bf7ebe148b30f89f39f", size = 5228606, upload-time = "2026-02-11T04:22:52.106Z" }, - { url = "https://files.pythonhosted.org/packages/5f/d5/3b005b4e4fda6698b371fa6c21b097d4707585d7db99e98d9b0b87ac612a/pillow-12.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:665e1b916b043cef294bc54d47bf02d87e13f769bc4bc5fa225a24b3a6c5aca9", size = 4622321, upload-time = "2026-02-11T04:22:53.827Z" }, - { url = "https://files.pythonhosted.org/packages/df/36/ed3ea2d594356fd8037e5a01f6156c74bc8d92dbb0fa60746cc96cabb6e8/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:495c302af3aad1ca67420ddd5c7bd480c8867ad173528767d906428057a11f0e", size = 5247579, upload-time = "2026-02-11T04:22:56.094Z" }, - { url = "https://files.pythonhosted.org/packages/54/9a/9cc3e029683cf6d20ae5085da0dafc63148e3252c2f13328e553aaa13cfb/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8fd420ef0c52c88b5a035a0886f367748c72147b2b8f384c9d12656678dfdfa9", size = 6989094, upload-time = "2026-02-11T04:22:58.288Z" }, - { url = "https://files.pythonhosted.org/packages/00/98/fc53ab36da80b88df0967896b6c4b4cd948a0dc5aa40a754266aa3ae48b3/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f975aa7ef9684ce7e2c18a3aa8f8e2106ce1e46b94ab713d156b2898811651d3", size = 5313850, upload-time = "2026-02-11T04:23:00.554Z" }, - { url = "https://files.pythonhosted.org/packages/30/02/00fa585abfd9fe9d73e5f6e554dc36cc2b842898cbfc46d70353dae227f8/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8089c852a56c2966cf18835db62d9b34fef7ba74c726ad943928d494fa7f4735", size = 5963343, upload-time = "2026-02-11T04:23:02.934Z" }, - { url = "https://files.pythonhosted.org/packages/f2/26/c56ce33ca856e358d27fda9676c055395abddb82c35ac0f593877ed4562e/pillow-12.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:cb9bb857b2d057c6dfc72ac5f3b44836924ba15721882ef103cecb40d002d80e", size = 7029880, upload-time = "2026-02-11T04:23:04.783Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/46/5da1ec4a5171ee7bf1a0efa064aba70ba3d6e0788ce3f5acd1375d23c8c0/pillow-12.1.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e879bb6cd5c73848ef3b2b48b8af9ff08c5b71ecda8048b7dd22d8a33f60be32", size = 5304084 }, + { url = "https://files.pythonhosted.org/packages/78/93/a29e9bc02d1cf557a834da780ceccd54e02421627200696fcf805ebdc3fb/pillow-12.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:365b10bb9417dd4498c0e3b128018c4a624dc11c7b97d8cc54effe3b096f4c38", size = 4657866 }, + { url = "https://files.pythonhosted.org/packages/13/84/583a4558d492a179d31e4aae32eadce94b9acf49c0337c4ce0b70e0a01f2/pillow-12.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d4ce8e329c93845720cd2014659ca67eac35f6433fd3050393d85f3ecef0dad5", size = 6232148 }, + { url = "https://files.pythonhosted.org/packages/d5/e2/53c43334bbbb2d3b938978532fbda8e62bb6e0b23a26ce8592f36bcc4987/pillow-12.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc354a04072b765eccf2204f588a7a532c9511e8b9c7f900e1b64e3e33487090", size = 8038007 }, + { url = "https://files.pythonhosted.org/packages/b8/a6/3d0e79c8a9d58150dd98e199d7c1c56861027f3829a3a60b3c2784190180/pillow-12.1.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e7976bf1910a8116b523b9f9f58bf410f3e8aa330cd9a2bb2953f9266ab49af", size = 6345418 }, + { url = "https://files.pythonhosted.org/packages/a2/c8/46dfeac5825e600579157eea177be43e2f7ff4a99da9d0d0a49533509ac5/pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:597bd9c8419bc7c6af5604e55847789b69123bbe25d65cc6ad3012b4f3c98d8b", size = 7034590 }, + { url = "https://files.pythonhosted.org/packages/af/bf/e6f65d3db8a8bbfeaf9e13cc0417813f6319863a73de934f14b2229ada18/pillow-12.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2c1fc0f2ca5f96a3c8407e41cca26a16e46b21060fe6d5b099d2cb01412222f5", size = 6458655 }, + { url = "https://files.pythonhosted.org/packages/f9/c2/66091f3f34a25894ca129362e510b956ef26f8fb67a0e6417bc5744e56f1/pillow-12.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:578510d88c6229d735855e1f278aa305270438d36a05031dfaae5067cc8eb04d", size = 7159286 }, + { url = "https://files.pythonhosted.org/packages/7b/5a/24bc8eb526a22f957d0cec6243146744966d40857e3d8deb68f7902ca6c1/pillow-12.1.1-cp311-cp311-win32.whl", hash = "sha256:7311c0a0dcadb89b36b7025dfd8326ecfa36964e29913074d47382706e516a7c", size = 6328663 }, + { url = "https://files.pythonhosted.org/packages/31/03/bef822e4f2d8f9d7448c133d0a18185d3cce3e70472774fffefe8b0ed562/pillow-12.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:fbfa2a7c10cc2623f412753cddf391c7f971c52ca40a3f65dc5039b2939e8563", size = 7031448 }, + { url = "https://files.pythonhosted.org/packages/49/70/f76296f53610bd17b2e7d31728b8b7825e3ac3b5b3688b51f52eab7c0818/pillow-12.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:b81b5e3511211631b3f672a595e3221252c90af017e399056d0faabb9538aa80", size = 2453651 }, + { url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803 }, + { url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601 }, + { url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995 }, + { url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012 }, + { url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638 }, + { url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540 }, + { url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613 }, + { url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745 }, + { url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823 }, + { url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367 }, + { url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811 }, + { url = "https://files.pythonhosted.org/packages/d5/11/6db24d4bd7685583caeae54b7009584e38da3c3d4488ed4cd25b439de486/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e", size = 4062689 }, + { url = "https://files.pythonhosted.org/packages/33/c0/ce6d3b1fe190f0021203e0d9b5b99e57843e345f15f9ef22fcd43842fd21/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", size = 4138535 }, + { url = "https://files.pythonhosted.org/packages/a0/c6/d5eb6a4fb32a3f9c21a8c7613ec706534ea1cf9f4b3663e99f0d83f6fca8/pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6", size = 3601364 }, + { url = "https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60", size = 5262561 }, + { url = "https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2", size = 4657460 }, + { url = "https://files.pythonhosted.org/packages/9e/1b/f1a4ea9a895b5732152789326202a82464d5254759fbacae4deea3069334/pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850", size = 6232698 }, + { url = "https://files.pythonhosted.org/packages/95/f4/86f51b8745070daf21fd2e5b1fe0eb35d4db9ca26e6d58366562fb56a743/pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289", size = 8041706 }, + { url = "https://files.pythonhosted.org/packages/29/9b/d6ecd956bb1266dd1045e995cce9b8d77759e740953a1c9aad9502a0461e/pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e", size = 6346621 }, + { url = "https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717", size = 7038069 }, + { url = "https://files.pythonhosted.org/packages/94/0e/58cb1a6bc48f746bc4cb3adb8cabff73e2742c92b3bf7a220b7cf69b9177/pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a", size = 6460040 }, + { url = "https://files.pythonhosted.org/packages/6c/57/9045cb3ff11eeb6c1adce3b2d60d7d299d7b273a2e6c8381a524abfdc474/pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029", size = 7164523 }, + { url = "https://files.pythonhosted.org/packages/73/f2/9be9cb99f2175f0d4dbadd6616ce1bf068ee54a28277ea1bf1fbf729c250/pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b", size = 6332552 }, + { url = "https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1", size = 7040108 }, + { url = "https://files.pythonhosted.org/packages/d5/7d/fc09634e2aabdd0feabaff4a32f4a7d97789223e7c2042fd805ea4b4d2c2/pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a", size = 2453712 }, + { url = "https://files.pythonhosted.org/packages/19/2a/b9d62794fc8a0dd14c1943df68347badbd5511103e0d04c035ffe5cf2255/pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da", size = 5264880 }, + { url = "https://files.pythonhosted.org/packages/26/9d/e03d857d1347fa5ed9247e123fcd2a97b6220e15e9cb73ca0a8d91702c6e/pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc", size = 4660616 }, + { url = "https://files.pythonhosted.org/packages/f7/ec/8a6d22afd02570d30954e043f09c32772bfe143ba9285e2fdb11284952cd/pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c", size = 6269008 }, + { url = "https://files.pythonhosted.org/packages/3d/1d/6d875422c9f28a4a361f495a5f68d9de4a66941dc2c619103ca335fa6446/pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8", size = 8073226 }, + { url = "https://files.pythonhosted.org/packages/a1/cd/134b0b6ee5eda6dc09e25e24b40fdafe11a520bc725c1d0bbaa5e00bf95b/pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20", size = 6380136 }, + { url = "https://files.pythonhosted.org/packages/7a/a9/7628f013f18f001c1b98d8fffe3452f306a70dc6aba7d931019e0492f45e/pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13", size = 7067129 }, + { url = "https://files.pythonhosted.org/packages/1e/f8/66ab30a2193b277785601e82ee2d49f68ea575d9637e5e234faaa98efa4c/pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf", size = 6491807 }, + { url = "https://files.pythonhosted.org/packages/da/0b/a877a6627dc8318fdb84e357c5e1a758c0941ab1ddffdafd231983788579/pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524", size = 7190954 }, + { url = "https://files.pythonhosted.org/packages/83/43/6f732ff85743cf746b1361b91665d9f5155e1483817f693f8d57ea93147f/pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986", size = 6336441 }, + { url = "https://files.pythonhosted.org/packages/3b/44/e865ef3986611bb75bfabdf94a590016ea327833f434558801122979cd0e/pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c", size = 7045383 }, + { url = "https://files.pythonhosted.org/packages/a8/c6/f4fb24268d0c6908b9f04143697ea18b0379490cb74ba9e8d41b898bd005/pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3", size = 2456104 }, + { url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652 }, + { url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823 }, + { url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143 }, + { url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254 }, + { url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499 }, + { url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137 }, + { url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721 }, + { url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798 }, + { url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315 }, + { url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360 }, + { url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438 }, + { url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503 }, + { url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748 }, + { url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314 }, + { url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612 }, + { url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567 }, + { url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951 }, + { url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769 }, + { url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358 }, + { url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558 }, + { url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028 }, + { url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940 }, + { url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736 }, + { url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894 }, + { url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446 }, + { url = "https://files.pythonhosted.org/packages/56/11/5d43209aa4cb58e0cc80127956ff1796a68b928e6324bbf06ef4db34367b/pillow-12.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:600fd103672b925fe62ed08e0d874ea34d692474df6f4bf7ebe148b30f89f39f", size = 5228606 }, + { url = "https://files.pythonhosted.org/packages/5f/d5/3b005b4e4fda6698b371fa6c21b097d4707585d7db99e98d9b0b87ac612a/pillow-12.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:665e1b916b043cef294bc54d47bf02d87e13f769bc4bc5fa225a24b3a6c5aca9", size = 4622321 }, + { url = "https://files.pythonhosted.org/packages/df/36/ed3ea2d594356fd8037e5a01f6156c74bc8d92dbb0fa60746cc96cabb6e8/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:495c302af3aad1ca67420ddd5c7bd480c8867ad173528767d906428057a11f0e", size = 5247579 }, + { url = "https://files.pythonhosted.org/packages/54/9a/9cc3e029683cf6d20ae5085da0dafc63148e3252c2f13328e553aaa13cfb/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8fd420ef0c52c88b5a035a0886f367748c72147b2b8f384c9d12656678dfdfa9", size = 6989094 }, + { url = "https://files.pythonhosted.org/packages/00/98/fc53ab36da80b88df0967896b6c4b4cd948a0dc5aa40a754266aa3ae48b3/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f975aa7ef9684ce7e2c18a3aa8f8e2106ce1e46b94ab713d156b2898811651d3", size = 5313850 }, + { url = "https://files.pythonhosted.org/packages/30/02/00fa585abfd9fe9d73e5f6e554dc36cc2b842898cbfc46d70353dae227f8/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8089c852a56c2966cf18835db62d9b34fef7ba74c726ad943928d494fa7f4735", size = 5963343 }, + { url = "https://files.pythonhosted.org/packages/f2/26/c56ce33ca856e358d27fda9676c055395abddb82c35ac0f593877ed4562e/pillow-12.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:cb9bb857b2d057c6dfc72ac5f3b44836924ba15721882ef103cecb40d002d80e", size = 7029880 }, ] [[package]] @@ -2310,27 +2377,27 @@ dependencies = [ { name = "platformdirs" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5f/74/bc3f671997158aef171194c3c4041e549946f4784b8690baa0626a0a164b/pint-0.25.2.tar.gz", hash = "sha256:85a45d1da8fe9c9f7477fed8aef59ad2b939af3d6611507e1a9cbdacdcd3450a", size = 254467, upload-time = "2025-11-06T22:08:09.184Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/74/bc3f671997158aef171194c3c4041e549946f4784b8690baa0626a0a164b/pint-0.25.2.tar.gz", hash = "sha256:85a45d1da8fe9c9f7477fed8aef59ad2b939af3d6611507e1a9cbdacdcd3450a", size = 254467 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/88/550d41e81e6d43335603a960cd9c75c1d88f9cf01bc9d4ee8e86290aba7d/pint-0.25.2-py3-none-any.whl", hash = "sha256:ca35ab1d8eeeb6f7d9942b3cb5f34ca42b61cdd5fb3eae79531553dcca04dda7", size = 306762, upload-time = "2025-11-06T22:08:07.745Z" }, + { url = "https://files.pythonhosted.org/packages/ab/88/550d41e81e6d43335603a960cd9c75c1d88f9cf01bc9d4ee8e86290aba7d/pint-0.25.2-py3-none-any.whl", hash = "sha256:ca35ab1d8eeeb6f7d9942b3cb5f34ca42b61cdd5fb3eae79531553dcca04dda7", size = 306762 }, ] [[package]] name = "platformdirs" version = "4.9.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394, upload-time = "2026-02-16T03:56:10.574Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394 } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/31/05e764397056194206169869b50cf2fee4dbbbc71b344705b9c0d878d4d8/platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd", size = 21168, upload-time = "2026-02-16T03:56:08.891Z" }, + { url = "https://files.pythonhosted.org/packages/48/31/05e764397056194206169869b50cf2fee4dbbbc71b344705b9c0d878d4d8/platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd", size = 21168 }, ] [[package]] name = "pluggy" version = "1.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, ] [[package]] @@ -2344,18 +2411,18 @@ dependencies = [ { name = "pyyaml" }, { name = "virtualenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z" } +sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" }, + { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437 }, ] [[package]] name = "prometheus-client" version = "0.24.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f0/58/a794d23feb6b00fc0c72787d7e87d872a6730dd9ed7c7b3e954637d8f280/prometheus_client-0.24.1.tar.gz", hash = "sha256:7e0ced7fbbd40f7b84962d5d2ab6f17ef88a72504dcf7c0b40737b43b2a461f9", size = 85616, upload-time = "2026-01-14T15:26:26.965Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/58/a794d23feb6b00fc0c72787d7e87d872a6730dd9ed7c7b3e954637d8f280/prometheus_client-0.24.1.tar.gz", hash = "sha256:7e0ced7fbbd40f7b84962d5d2ab6f17ef88a72504dcf7c0b40737b43b2a461f9", size = 85616 } wheels = [ - { url = "https://files.pythonhosted.org/packages/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.1-py3-none-any.whl", hash = "sha256:150db128af71a5c2482b36e588fc8a6b95e498750da4b17065947c16070f4055", size = 64057, upload-time = "2026-01-14T15:26:24.42Z" }, + { url = "https://files.pythonhosted.org/packages/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.1-py3-none-any.whl", hash = "sha256:150db128af71a5c2482b36e588fc8a6b95e498750da4b17065947c16070f4055", size = 64057 }, ] [[package]] @@ -2365,126 +2432,126 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "wcwidth" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198 } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431 }, ] [[package]] name = "protobuf" version = "6.33.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465, upload-time = "2026-01-29T21:51:33.494Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769, upload-time = "2026-01-29T21:51:21.751Z" }, - { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118, upload-time = "2026-01-29T21:51:24.022Z" }, - { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766, upload-time = "2026-01-29T21:51:25.413Z" }, - { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638, upload-time = "2026-01-29T21:51:26.423Z" }, - { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411, upload-time = "2026-01-29T21:51:27.446Z" }, - { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465, upload-time = "2026-01-29T21:51:28.925Z" }, - { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" }, + { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769 }, + { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118 }, + { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766 }, + { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638 }, + { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411 }, + { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465 }, + { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687 }, ] [[package]] name = "psutil" version = "7.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, - { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, - { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, - { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, - { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, - { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, - { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, - { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, - { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, - { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, - { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, - { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, - { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, - { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, - { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, - { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, - { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, - { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, - { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, - { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595 }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082 }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476 }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062 }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893 }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589 }, + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664 }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087 }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383 }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210 }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228 }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284 }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090 }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859 }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560 }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997 }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972 }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266 }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737 }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617 }, ] [[package]] name = "psygnal" version = "0.15.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4e/79/20c3e23e75272e9ddf018097cf872ab088bccba978888472656629efa4a3/psygnal-0.15.1.tar.gz", hash = "sha256:f64f62dee2306fc1c22050a59b6c6cdad126e04b0cf50e393ff858a1da719096", size = 123147, upload-time = "2026-01-04T16:38:41.959Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/a7/69495410025cc4298765545ce3b8c635cd4c8d3a362b7fbbc15b80e9fc8f/psygnal-0.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1adc41515f648696990964433f1e25d8dfd306813a3645366c85e01986ba57a0", size = 581002, upload-time = "2026-01-04T16:38:12.753Z" }, - { url = "https://files.pythonhosted.org/packages/75/1f/19a8126ccf3cd3974ba5d08a435a049b666961d90f5848ba83599d7a29de/psygnal-0.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:38ff18455b2ac73d4e8eea82ef298ce904b52e4dfdc603a24380c9c440e37519", size = 567775, upload-time = "2026-01-04T16:38:14.04Z" }, - { url = "https://files.pythonhosted.org/packages/54/c5/b1348880d603edb82128a721397a1ddcf3dfcf5384fe5689db6e471118ae/psygnal-0.15.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c923c322eeefb1140886927cfe7bda7c32341087e290e812b9c69a624ab72d54", size = 855961, upload-time = "2026-01-04T16:38:15.612Z" }, - { url = "https://files.pythonhosted.org/packages/e6/42/3da2d6f3583bd1a849f7faa2fd3492b14bfda05012519ceaea5992658af0/psygnal-0.15.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2714ddaa41ea3134c0ee91cebd5fb11a88f254ea1d5948806ab0ad5f8be603d5", size = 862721, upload-time = "2026-01-04T16:38:17.059Z" }, - { url = "https://files.pythonhosted.org/packages/4d/14/6fc7e97fdecf7e8c5c105684bab784920312a3259800d8b53e3cf8783f42/psygnal-0.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:877516056a5a383427a647fff2fad5179eaa3e12de2c083c273e748435414aef", size = 415696, upload-time = "2026-01-04T16:38:18.355Z" }, - { url = "https://files.pythonhosted.org/packages/76/65/b7bbca96bc477aa9ac2264e5907b2f4ccfcd1319f776dd1f35eec06cc2f4/psygnal-0.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8d56f0f35eaf4a21f660de76885222faf9e8c7112454528d3394d464f3d4d1a3", size = 598340, upload-time = "2026-01-04T16:38:19.752Z" }, - { url = "https://files.pythonhosted.org/packages/40/f2/56577465a1b42a5e6780bb5fab53fb68f8bfd72f0131ed397576529af724/psygnal-0.15.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0febcf757a1323d9b8bd75735ee3569213d8110012a7bf0f478e85c5ab459fc6", size = 575311, upload-time = "2026-01-04T16:38:21.137Z" }, - { url = "https://files.pythonhosted.org/packages/79/81/f642ac08104049383076f83480ed412c9626e068769a1c34873c595bec0e/psygnal-0.15.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b5e4837dfbfa4974dabe0795e32be9aadcd87603adf734738ce1114f72238a05", size = 889770, upload-time = "2026-01-04T16:38:22.629Z" }, - { url = "https://files.pythonhosted.org/packages/de/43/e571fa40b72780abed080ef829e5ad98017b6fe48d28c15a2404e006b676/psygnal-0.15.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:07b4c4e03bbf4e8cad7e25f4fbc1ba9575fb9c3d14991bc7edfeb8b09c8d6d54", size = 881105, upload-time = "2026-01-04T16:38:23.896Z" }, - { url = "https://files.pythonhosted.org/packages/e3/26/ef3ab825eb08eaecbbceeeb56383694fe64ce399dbfd1d0767bb85688785/psygnal-0.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:4f0ce91b9c18e92281bf2c3fc4bb4e808d90f0b023d0a37b302d354188520338", size = 418969, upload-time = "2026-01-04T16:38:25.731Z" }, - { url = "https://files.pythonhosted.org/packages/46/21/5a142165d27063abf5921807d3c3d973f5d44ab414a13b210839a43ead4d/psygnal-0.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2087aadc9404f007f79c2899e329932869e362c50de58b90631c5f49b4768cc5", size = 596768, upload-time = "2026-01-04T16:38:27.053Z" }, - { url = "https://files.pythonhosted.org/packages/e1/25/c1712931d61c118691e73daf29ef708c679ea9ba187c797dd5deee360411/psygnal-0.15.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0f3bf68ca42569dfdce20c6cf915d34b78b9e3ddddacb9f78728224fda6946b4", size = 574808, upload-time = "2026-01-04T16:38:28.779Z" }, - { url = "https://files.pythonhosted.org/packages/2d/4f/3593e5adb88a188c798604aed95fbc1479f30230e7f51e8f2c770e6a3832/psygnal-0.15.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e9fca977f5335deea39aed22e31d9795983e4f243e59a7d3c4105793adb7693d", size = 885616, upload-time = "2026-01-04T16:38:30.081Z" }, - { url = "https://files.pythonhosted.org/packages/58/4c/14779ed4c3a1d71fa1a9a87ecfb184ad3335dd64681067f77c1c47b14ae9/psygnal-0.15.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c85b7d05b92ccbec47c75ab8a5545eda462e81a492c82424aba5ab81a3ad89d", size = 876516, upload-time = "2026-01-04T16:38:31.422Z" }, - { url = "https://files.pythonhosted.org/packages/3e/bc/4f771e3cdcde4db4023dbf36d6f0aab44e02b9de719353c22954b655e2ff/psygnal-0.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:ac0e693b29e0a429e97315a52313321855bef6140e9975b7ae78b4d93c8fbb42", size = 419172, upload-time = "2026-01-04T16:38:32.82Z" }, - { url = "https://files.pythonhosted.org/packages/f4/2e/975bd61727578d88df62797f78390965ca7905780cf01eb59cb095a13638/psygnal-0.15.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:803fc33c4280c822c6f4b22e6c3ea7c4483e190f3cc69e69350098b3799476f3", size = 595706, upload-time = "2026-01-04T16:38:34.139Z" }, - { url = "https://files.pythonhosted.org/packages/b8/55/e487f1d91497eb75e86c3fdfef69a21b1cab24d023383dd7648b08797d6a/psygnal-0.15.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4f53b4b83355b0a785b745987fd04e59bbf169a9028ed81a68ca7e05fb76d458", size = 575133, upload-time = "2026-01-04T16:38:35.448Z" }, - { url = "https://files.pythonhosted.org/packages/bf/2f/f286355accd0e68d3eef52e63c8b9ab6ba33ec3107177719a036b3319657/psygnal-0.15.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bcbca12190f5aa65c1f8fb04a81fa6f4463c5f5dde25cd74c3a56ceff6f37b02", size = 889565, upload-time = "2026-01-04T16:38:37.003Z" }, - { url = "https://files.pythonhosted.org/packages/fc/dc/40c6026c88d7f9220ecc913afe0501045a512c9b82f9b7e036bb089dc287/psygnal-0.15.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1ac399566852fe4354ce26a1acbe12319232e8c2b615fe5ad1e114c547095cf6", size = 880863, upload-time = "2026-01-04T16:38:38.381Z" }, - { url = "https://files.pythonhosted.org/packages/b7/85/b4f45ec3057c473b5622fc002b3a636a698c34d3a0917a064ff5247f1984/psygnal-0.15.1-cp314-cp314-win_amd64.whl", hash = "sha256:d3a03055f331ce91d44581c71edb79938ccc133a94af2ce7ad3a18fa57ac7be5", size = 423654, upload-time = "2026-01-04T16:38:39.7Z" }, - { url = "https://files.pythonhosted.org/packages/46/49/7742544684bee728ec123515d2694cee859aa2a705951a461230b00f18cc/psygnal-0.15.1-py3-none-any.whl", hash = "sha256:4221140e633e45b076953c64bcb9b41a744833527f9a037c1ca98bc270798cbf", size = 90638, upload-time = "2026-01-04T16:38:40.841Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/4e/79/20c3e23e75272e9ddf018097cf872ab088bccba978888472656629efa4a3/psygnal-0.15.1.tar.gz", hash = "sha256:f64f62dee2306fc1c22050a59b6c6cdad126e04b0cf50e393ff858a1da719096", size = 123147 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/a7/69495410025cc4298765545ce3b8c635cd4c8d3a362b7fbbc15b80e9fc8f/psygnal-0.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1adc41515f648696990964433f1e25d8dfd306813a3645366c85e01986ba57a0", size = 581002 }, + { url = "https://files.pythonhosted.org/packages/75/1f/19a8126ccf3cd3974ba5d08a435a049b666961d90f5848ba83599d7a29de/psygnal-0.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:38ff18455b2ac73d4e8eea82ef298ce904b52e4dfdc603a24380c9c440e37519", size = 567775 }, + { url = "https://files.pythonhosted.org/packages/54/c5/b1348880d603edb82128a721397a1ddcf3dfcf5384fe5689db6e471118ae/psygnal-0.15.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c923c322eeefb1140886927cfe7bda7c32341087e290e812b9c69a624ab72d54", size = 855961 }, + { url = "https://files.pythonhosted.org/packages/e6/42/3da2d6f3583bd1a849f7faa2fd3492b14bfda05012519ceaea5992658af0/psygnal-0.15.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2714ddaa41ea3134c0ee91cebd5fb11a88f254ea1d5948806ab0ad5f8be603d5", size = 862721 }, + { url = "https://files.pythonhosted.org/packages/4d/14/6fc7e97fdecf7e8c5c105684bab784920312a3259800d8b53e3cf8783f42/psygnal-0.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:877516056a5a383427a647fff2fad5179eaa3e12de2c083c273e748435414aef", size = 415696 }, + { url = "https://files.pythonhosted.org/packages/76/65/b7bbca96bc477aa9ac2264e5907b2f4ccfcd1319f776dd1f35eec06cc2f4/psygnal-0.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8d56f0f35eaf4a21f660de76885222faf9e8c7112454528d3394d464f3d4d1a3", size = 598340 }, + { url = "https://files.pythonhosted.org/packages/40/f2/56577465a1b42a5e6780bb5fab53fb68f8bfd72f0131ed397576529af724/psygnal-0.15.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0febcf757a1323d9b8bd75735ee3569213d8110012a7bf0f478e85c5ab459fc6", size = 575311 }, + { url = "https://files.pythonhosted.org/packages/79/81/f642ac08104049383076f83480ed412c9626e068769a1c34873c595bec0e/psygnal-0.15.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b5e4837dfbfa4974dabe0795e32be9aadcd87603adf734738ce1114f72238a05", size = 889770 }, + { url = "https://files.pythonhosted.org/packages/de/43/e571fa40b72780abed080ef829e5ad98017b6fe48d28c15a2404e006b676/psygnal-0.15.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:07b4c4e03bbf4e8cad7e25f4fbc1ba9575fb9c3d14991bc7edfeb8b09c8d6d54", size = 881105 }, + { url = "https://files.pythonhosted.org/packages/e3/26/ef3ab825eb08eaecbbceeeb56383694fe64ce399dbfd1d0767bb85688785/psygnal-0.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:4f0ce91b9c18e92281bf2c3fc4bb4e808d90f0b023d0a37b302d354188520338", size = 418969 }, + { url = "https://files.pythonhosted.org/packages/46/21/5a142165d27063abf5921807d3c3d973f5d44ab414a13b210839a43ead4d/psygnal-0.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2087aadc9404f007f79c2899e329932869e362c50de58b90631c5f49b4768cc5", size = 596768 }, + { url = "https://files.pythonhosted.org/packages/e1/25/c1712931d61c118691e73daf29ef708c679ea9ba187c797dd5deee360411/psygnal-0.15.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0f3bf68ca42569dfdce20c6cf915d34b78b9e3ddddacb9f78728224fda6946b4", size = 574808 }, + { url = "https://files.pythonhosted.org/packages/2d/4f/3593e5adb88a188c798604aed95fbc1479f30230e7f51e8f2c770e6a3832/psygnal-0.15.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e9fca977f5335deea39aed22e31d9795983e4f243e59a7d3c4105793adb7693d", size = 885616 }, + { url = "https://files.pythonhosted.org/packages/58/4c/14779ed4c3a1d71fa1a9a87ecfb184ad3335dd64681067f77c1c47b14ae9/psygnal-0.15.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c85b7d05b92ccbec47c75ab8a5545eda462e81a492c82424aba5ab81a3ad89d", size = 876516 }, + { url = "https://files.pythonhosted.org/packages/3e/bc/4f771e3cdcde4db4023dbf36d6f0aab44e02b9de719353c22954b655e2ff/psygnal-0.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:ac0e693b29e0a429e97315a52313321855bef6140e9975b7ae78b4d93c8fbb42", size = 419172 }, + { url = "https://files.pythonhosted.org/packages/f4/2e/975bd61727578d88df62797f78390965ca7905780cf01eb59cb095a13638/psygnal-0.15.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:803fc33c4280c822c6f4b22e6c3ea7c4483e190f3cc69e69350098b3799476f3", size = 595706 }, + { url = "https://files.pythonhosted.org/packages/b8/55/e487f1d91497eb75e86c3fdfef69a21b1cab24d023383dd7648b08797d6a/psygnal-0.15.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4f53b4b83355b0a785b745987fd04e59bbf169a9028ed81a68ca7e05fb76d458", size = 575133 }, + { url = "https://files.pythonhosted.org/packages/bf/2f/f286355accd0e68d3eef52e63c8b9ab6ba33ec3107177719a036b3319657/psygnal-0.15.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bcbca12190f5aa65c1f8fb04a81fa6f4463c5f5dde25cd74c3a56ceff6f37b02", size = 889565 }, + { url = "https://files.pythonhosted.org/packages/fc/dc/40c6026c88d7f9220ecc913afe0501045a512c9b82f9b7e036bb089dc287/psygnal-0.15.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1ac399566852fe4354ce26a1acbe12319232e8c2b615fe5ad1e114c547095cf6", size = 880863 }, + { url = "https://files.pythonhosted.org/packages/b7/85/b4f45ec3057c473b5622fc002b3a636a698c34d3a0917a064ff5247f1984/psygnal-0.15.1-cp314-cp314-win_amd64.whl", hash = "sha256:d3a03055f331ce91d44581c71edb79938ccc133a94af2ce7ad3a18fa57ac7be5", size = 423654 }, + { url = "https://files.pythonhosted.org/packages/46/49/7742544684bee728ec123515d2694cee859aa2a705951a461230b00f18cc/psygnal-0.15.1-py3-none-any.whl", hash = "sha256:4221140e633e45b076953c64bcb9b41a744833527f9a037c1ca98bc270798cbf", size = 90638 }, ] [[package]] name = "ptyprocess" version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762 } wheels = [ - { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993 }, ] [[package]] name = "pure-eval" version = "0.2.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752 } wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842 }, ] [[package]] name = "pycparser" version = "3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172 }, ] [[package]] name = "pygments" version = "2.19.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 }, ] [[package]] name = "pyparsing" version = "3.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574 } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781 }, ] [[package]] @@ -2498,9 +2565,9 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801 }, ] [[package]] @@ -2512,29 +2579,29 @@ dependencies = [ { name = "pluggy" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, + { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424 }, ] [[package]] name = "python-box" version = "7.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/0f/34e7ee0a72f1464b4c7a2e8bafb389f230477256af586bc82bcfad85295a/python_box-7.4.1.tar.gz", hash = "sha256:e412e36c25fca8223560516d53ef6c7993591c3b0ec8bb4ec582bf7defdd79f0", size = 49859, upload-time = "2026-02-21T16:21:16.008Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/0f/34e7ee0a72f1464b4c7a2e8bafb389f230477256af586bc82bcfad85295a/python_box-7.4.1.tar.gz", hash = "sha256:e412e36c25fca8223560516d53ef6c7993591c3b0ec8bb4ec582bf7defdd79f0", size = 49859 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/a8/c8bcd3ff0905ec549273ea3485e6b9f2039f57baab419123fb18f964f829/python_box-7.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3f76dad8be9d57d65a3edc792b952f7afe3991515aa6eba616cf5efb2fbb2e0c", size = 1870869, upload-time = "2026-02-21T16:21:34.16Z" }, - { url = "https://files.pythonhosted.org/packages/0f/bc/9382766d388e258363a18a094e251d2624e3c524614c733d1afa989d9770/python_box-7.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c66582f41a94d46cb0896d468b0efebf9bc4c3a5634cd15373d871767c2e741d", size = 4494287, upload-time = "2026-02-21T16:26:03.131Z" }, - { url = "https://files.pythonhosted.org/packages/8c/cf/b9d1d4550615f69f6f9c72767f026543442739e5c56adf6f844b50d88251/python_box-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:43c62f66d694eb6410f51eb2eb5726f9b466e6f685e5dc90b5cd11f7b3047362", size = 1321085, upload-time = "2026-02-21T16:22:01.093Z" }, - { url = "https://files.pythonhosted.org/packages/4d/d9/d05f317b38b42253422d8483f5d7dc16d382c99ddc253e426639a0f2f235/python_box-7.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dfb91effff00d9e23486c4f0db3b19e03d602ebb7c9e20fc6a287c704fad2552", size = 1849441, upload-time = "2026-02-21T16:21:37.314Z" }, - { url = "https://files.pythonhosted.org/packages/ba/a3/383eb3d658f36c6e531c8cf1e348ccb4b5031231df4aeb7742bb159a3166/python_box-7.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f977f00e715b030cee6ffef2322ff8ce100ffbf1dbcc4ef91099c75752d5f8", size = 4485153, upload-time = "2026-02-21T16:26:04.507Z" }, - { url = "https://files.pythonhosted.org/packages/65/f9/5de3c18415dd6f5286f00e6539c0ae3cceb1c6aaf28d1d5f17b0b568c97f/python_box-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ca9a18fd15326bc267e9cc7e0e6e3a0cb78d11507940f43f687adf7e156d882", size = 1295520, upload-time = "2026-02-21T16:22:26.192Z" }, - { url = "https://files.pythonhosted.org/packages/ec/e9/48d1b1eb21efc3f82a31b037b6903c9139018f686d96d251faa4cb0d593a/python_box-7.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:85db37b43094bf6c4884b931fb149a7850db5ce331f6e191edf98b453e6cf2d6", size = 1845195, upload-time = "2026-02-21T16:21:46.235Z" }, - { url = "https://files.pythonhosted.org/packages/da/79/48d38c855f277223caf3aa79518476f95abc07f04386940855b7bd3d95f6/python_box-7.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bb204822c7638bd2dbed5c55d6ab264c6903c37d18dee5c45bdbda58b2e1e17a", size = 4468245, upload-time = "2026-02-21T16:26:05.701Z" }, - { url = "https://files.pythonhosted.org/packages/17/1d/7a1e04f37674399e0f3076cfe1fa358f6a51540ae98299a06f2c0424c471/python_box-7.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:615da3fafd41572aec1b905832555c0ea08b6fbc27cc917356e257a9a5721af7", size = 1295564, upload-time = "2026-02-21T16:22:36.547Z" }, - { url = "https://files.pythonhosted.org/packages/94/a2/771b5e526bba2214ac2d30e321209a66680c40788616a45cf01005e95204/python_box-7.4.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:33c6701faa51fd87f0dcc538873c0fad2b3a1cc3750eab85835cd071cadf1948", size = 1875508, upload-time = "2026-02-21T16:21:37.432Z" }, - { url = "https://files.pythonhosted.org/packages/a6/5f/0e7ea7640ba60ff459ce37e340d816ac5e91b7a9a7c3c161f9dabe622be6/python_box-7.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:ae8c540a0457f52350211d24690211251912018e1e0c1857f50792729d6f562c", size = 1314304, upload-time = "2026-02-21T16:22:22.173Z" }, - { url = "https://files.pythonhosted.org/packages/06/a6/5d3f3abf46b37aa44b1f6788d287c8b4f2319b55013191dddf25b9e6d62c/python_box-7.4.1-py3-none-any.whl", hash = "sha256:a3b0d84d003882fb6abe505b1b883b3a5dcbf226b0fe168d24bc5ff75d9826e5", size = 30402, upload-time = "2026-02-21T16:21:14.78Z" }, + { url = "https://files.pythonhosted.org/packages/f8/a8/c8bcd3ff0905ec549273ea3485e6b9f2039f57baab419123fb18f964f829/python_box-7.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3f76dad8be9d57d65a3edc792b952f7afe3991515aa6eba616cf5efb2fbb2e0c", size = 1870869 }, + { url = "https://files.pythonhosted.org/packages/0f/bc/9382766d388e258363a18a094e251d2624e3c524614c733d1afa989d9770/python_box-7.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c66582f41a94d46cb0896d468b0efebf9bc4c3a5634cd15373d871767c2e741d", size = 4494287 }, + { url = "https://files.pythonhosted.org/packages/8c/cf/b9d1d4550615f69f6f9c72767f026543442739e5c56adf6f844b50d88251/python_box-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:43c62f66d694eb6410f51eb2eb5726f9b466e6f685e5dc90b5cd11f7b3047362", size = 1321085 }, + { url = "https://files.pythonhosted.org/packages/4d/d9/d05f317b38b42253422d8483f5d7dc16d382c99ddc253e426639a0f2f235/python_box-7.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dfb91effff00d9e23486c4f0db3b19e03d602ebb7c9e20fc6a287c704fad2552", size = 1849441 }, + { url = "https://files.pythonhosted.org/packages/ba/a3/383eb3d658f36c6e531c8cf1e348ccb4b5031231df4aeb7742bb159a3166/python_box-7.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f977f00e715b030cee6ffef2322ff8ce100ffbf1dbcc4ef91099c75752d5f8", size = 4485153 }, + { url = "https://files.pythonhosted.org/packages/65/f9/5de3c18415dd6f5286f00e6539c0ae3cceb1c6aaf28d1d5f17b0b568c97f/python_box-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ca9a18fd15326bc267e9cc7e0e6e3a0cb78d11507940f43f687adf7e156d882", size = 1295520 }, + { url = "https://files.pythonhosted.org/packages/ec/e9/48d1b1eb21efc3f82a31b037b6903c9139018f686d96d251faa4cb0d593a/python_box-7.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:85db37b43094bf6c4884b931fb149a7850db5ce331f6e191edf98b453e6cf2d6", size = 1845195 }, + { url = "https://files.pythonhosted.org/packages/da/79/48d38c855f277223caf3aa79518476f95abc07f04386940855b7bd3d95f6/python_box-7.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bb204822c7638bd2dbed5c55d6ab264c6903c37d18dee5c45bdbda58b2e1e17a", size = 4468245 }, + { url = "https://files.pythonhosted.org/packages/17/1d/7a1e04f37674399e0f3076cfe1fa358f6a51540ae98299a06f2c0424c471/python_box-7.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:615da3fafd41572aec1b905832555c0ea08b6fbc27cc917356e257a9a5721af7", size = 1295564 }, + { url = "https://files.pythonhosted.org/packages/94/a2/771b5e526bba2214ac2d30e321209a66680c40788616a45cf01005e95204/python_box-7.4.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:33c6701faa51fd87f0dcc538873c0fad2b3a1cc3750eab85835cd071cadf1948", size = 1875508 }, + { url = "https://files.pythonhosted.org/packages/a6/5f/0e7ea7640ba60ff459ce37e340d816ac5e91b7a9a7c3c161f9dabe622be6/python_box-7.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:ae8c540a0457f52350211d24690211251912018e1e0c1857f50792729d6f562c", size = 1314304 }, + { url = "https://files.pythonhosted.org/packages/06/a6/5d3f3abf46b37aa44b1f6788d287c8b4f2319b55013191dddf25b9e6d62c/python_box-7.4.1-py3-none-any.whl", hash = "sha256:a3b0d84d003882fb6abe505b1b883b3a5dcbf226b0fe168d24bc5ff75d9826e5", size = 30402 }, ] [[package]] @@ -2544,93 +2611,93 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +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, upload-time = "2024-03-01T18:36:18.57Z" }, + { 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 = "python-json-logger" version = "4.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/29/bf/eca6a3d43db1dae7070f70e160ab20b807627ba953663ba07928cdd3dc58/python_json_logger-4.0.0.tar.gz", hash = "sha256:f58e68eb46e1faed27e0f574a55a0455eecd7b8a5b88b85a784519ba3cff047f", size = 17683, upload-time = "2025-10-06T04:15:18.984Z" } +sdist = { url = "https://files.pythonhosted.org/packages/29/bf/eca6a3d43db1dae7070f70e160ab20b807627ba953663ba07928cdd3dc58/python_json_logger-4.0.0.tar.gz", hash = "sha256:f58e68eb46e1faed27e0f574a55a0455eecd7b8a5b88b85a784519ba3cff047f", size = 17683 } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl", hash = "sha256:af09c9daf6a813aa4cc7180395f50f2a9e5fa056034c9953aec92e381c5ba1e2", size = 15548, upload-time = "2025-10-06T04:15:17.553Z" }, + { url = "https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl", hash = "sha256:af09c9daf6a813aa4cc7180395f50f2a9e5fa056034c9953aec92e381c5ba1e2", size = 15548 }, ] [[package]] name = "pywinpty" version = "3.0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f7/54/37c7370ba91f579235049dc26cd2c5e657d2a943e01820844ffc81f32176/pywinpty-3.0.3.tar.gz", hash = "sha256:523441dc34d231fb361b4b00f8c99d3f16de02f5005fd544a0183112bcc22412", size = 31309, upload-time = "2026-02-04T21:51:09.524Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/54/37c7370ba91f579235049dc26cd2c5e657d2a943e01820844ffc81f32176/pywinpty-3.0.3.tar.gz", hash = "sha256:523441dc34d231fb361b4b00f8c99d3f16de02f5005fd544a0183112bcc22412", size = 31309 } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/c3/3e75075c7f71735f22b66fab0481f2c98e3a4d58cba55cb50ba29114bcf6/pywinpty-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:dff25a9a6435f527d7c65608a7e62783fc12076e7d44487a4911ee91be5a8ac8", size = 2114430, upload-time = "2026-02-04T21:54:19.485Z" }, - { url = "https://files.pythonhosted.org/packages/8d/1e/8a54166a8c5e4f5cb516514bdf4090be4d51a71e8d9f6d98c0aa00fe45d4/pywinpty-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:fbc1e230e5b193eef4431cba3f39996a288f9958f9c9f092c8a961d930ee8f68", size = 236191, upload-time = "2026-02-04T21:50:36.239Z" }, - { url = "https://files.pythonhosted.org/packages/7c/d4/aeb5e1784d2c5bff6e189138a9ca91a090117459cea0c30378e1f2db3d54/pywinpty-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:c9081df0e49ffa86d15db4a6ba61530630e48707f987df42c9d3313537e81fc0", size = 2113098, upload-time = "2026-02-04T21:54:37.711Z" }, - { url = "https://files.pythonhosted.org/packages/b9/53/7278223c493ccfe4883239cf06c823c56460a8010e0fc778eef67858dc14/pywinpty-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:15e79d870e18b678fb8a5a6105fd38496b55697c66e6fc0378236026bc4d59e9", size = 234901, upload-time = "2026-02-04T21:53:31.35Z" }, - { url = "https://files.pythonhosted.org/packages/e5/cb/58d6ed3fd429c96a90ef01ac9a617af10a6d41469219c25e7dc162abbb71/pywinpty-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9c91dbb026050c77bdcef964e63a4f10f01a639113c4d3658332614544c467ab", size = 2112686, upload-time = "2026-02-04T21:52:03.035Z" }, - { url = "https://files.pythonhosted.org/packages/fd/50/724ed5c38c504d4e58a88a072776a1e880d970789deaeb2b9f7bd9a5141a/pywinpty-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:fe1f7911805127c94cf51f89ab14096c6f91ffdcacf993d2da6082b2142a2523", size = 234591, upload-time = "2026-02-04T21:52:29.821Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ad/90a110538696b12b39fd8758a06d70ded899308198ad2305ac68e361126e/pywinpty-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:3f07a6cf1c1d470d284e614733c3d0f726d2c85e78508ea10a403140c3c0c18a", size = 2112360, upload-time = "2026-02-04T21:55:33.397Z" }, - { url = "https://files.pythonhosted.org/packages/44/0f/7ffa221757a220402bc79fda44044c3f2cc57338d878ab7d622add6f4581/pywinpty-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:15c7c0b6f8e9d87aabbaff76468dabf6e6121332c40fc1d83548d02a9d6a3759", size = 233107, upload-time = "2026-02-04T21:51:45.455Z" }, - { url = "https://files.pythonhosted.org/packages/28/88/2ff917caff61e55f38bcdb27de06ee30597881b2cae44fbba7627be015c4/pywinpty-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:d4b6b7b0fe0cdcd02e956bd57cfe9f4e5a06514eecf3b5ae174da4f951b58be9", size = 2113282, upload-time = "2026-02-04T21:52:08.188Z" }, - { url = "https://files.pythonhosted.org/packages/63/32/40a775343ace542cc43ece3f1d1fce454021521ecac41c4c4573081c2336/pywinpty-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:34789d685fc0d547ce0c8a65e5a70e56f77d732fa6e03c8f74fefb8cbb252019", size = 234207, upload-time = "2026-02-04T21:51:58.687Z" }, - { url = "https://files.pythonhosted.org/packages/8d/54/5d5e52f4cb75028104ca6faf36c10f9692389b1986d34471663b4ebebd6d/pywinpty-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:0c37e224a47a971d1a6e08649a1714dac4f63c11920780977829ed5c8cadead1", size = 2112910, upload-time = "2026-02-04T21:52:30.976Z" }, - { url = "https://files.pythonhosted.org/packages/0a/44/dcd184824e21d4620b06c7db9fbb15c3ad0a0f1fa2e6de79969fb82647ec/pywinpty-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:c4e9c3dff7d86ba81937438d5819f19f385a39d8f592d4e8af67148ceb4f6ab5", size = 233425, upload-time = "2026-02-04T21:51:56.754Z" }, + { url = "https://files.pythonhosted.org/packages/79/c3/3e75075c7f71735f22b66fab0481f2c98e3a4d58cba55cb50ba29114bcf6/pywinpty-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:dff25a9a6435f527d7c65608a7e62783fc12076e7d44487a4911ee91be5a8ac8", size = 2114430 }, + { url = "https://files.pythonhosted.org/packages/8d/1e/8a54166a8c5e4f5cb516514bdf4090be4d51a71e8d9f6d98c0aa00fe45d4/pywinpty-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:fbc1e230e5b193eef4431cba3f39996a288f9958f9c9f092c8a961d930ee8f68", size = 236191 }, + { url = "https://files.pythonhosted.org/packages/7c/d4/aeb5e1784d2c5bff6e189138a9ca91a090117459cea0c30378e1f2db3d54/pywinpty-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:c9081df0e49ffa86d15db4a6ba61530630e48707f987df42c9d3313537e81fc0", size = 2113098 }, + { url = "https://files.pythonhosted.org/packages/b9/53/7278223c493ccfe4883239cf06c823c56460a8010e0fc778eef67858dc14/pywinpty-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:15e79d870e18b678fb8a5a6105fd38496b55697c66e6fc0378236026bc4d59e9", size = 234901 }, + { url = "https://files.pythonhosted.org/packages/e5/cb/58d6ed3fd429c96a90ef01ac9a617af10a6d41469219c25e7dc162abbb71/pywinpty-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9c91dbb026050c77bdcef964e63a4f10f01a639113c4d3658332614544c467ab", size = 2112686 }, + { url = "https://files.pythonhosted.org/packages/fd/50/724ed5c38c504d4e58a88a072776a1e880d970789deaeb2b9f7bd9a5141a/pywinpty-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:fe1f7911805127c94cf51f89ab14096c6f91ffdcacf993d2da6082b2142a2523", size = 234591 }, + { url = "https://files.pythonhosted.org/packages/f7/ad/90a110538696b12b39fd8758a06d70ded899308198ad2305ac68e361126e/pywinpty-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:3f07a6cf1c1d470d284e614733c3d0f726d2c85e78508ea10a403140c3c0c18a", size = 2112360 }, + { url = "https://files.pythonhosted.org/packages/44/0f/7ffa221757a220402bc79fda44044c3f2cc57338d878ab7d622add6f4581/pywinpty-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:15c7c0b6f8e9d87aabbaff76468dabf6e6121332c40fc1d83548d02a9d6a3759", size = 233107 }, + { url = "https://files.pythonhosted.org/packages/28/88/2ff917caff61e55f38bcdb27de06ee30597881b2cae44fbba7627be015c4/pywinpty-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:d4b6b7b0fe0cdcd02e956bd57cfe9f4e5a06514eecf3b5ae174da4f951b58be9", size = 2113282 }, + { url = "https://files.pythonhosted.org/packages/63/32/40a775343ace542cc43ece3f1d1fce454021521ecac41c4c4573081c2336/pywinpty-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:34789d685fc0d547ce0c8a65e5a70e56f77d732fa6e03c8f74fefb8cbb252019", size = 234207 }, + { url = "https://files.pythonhosted.org/packages/8d/54/5d5e52f4cb75028104ca6faf36c10f9692389b1986d34471663b4ebebd6d/pywinpty-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:0c37e224a47a971d1a6e08649a1714dac4f63c11920780977829ed5c8cadead1", size = 2112910 }, + { url = "https://files.pythonhosted.org/packages/0a/44/dcd184824e21d4620b06c7db9fbb15c3ad0a0f1fa2e6de79969fb82647ec/pywinpty-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:c4e9c3dff7d86ba81937438d5819f19f385a39d8f592d4e8af67148ceb4f6ab5", size = 233425 }, ] [[package]] name = "pyyaml" version = "6.0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, - { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, - { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, - { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, - { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, - { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, - { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, - { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, - { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, - { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, - { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, - { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, - { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, - { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, - { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, - { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, - { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, - { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, - { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, - { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, - { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, - { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, - { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, - { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, - { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, - { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, - { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, - { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, - { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, - { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, - { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, - { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, - { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, - { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, - { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, - { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, - { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, - { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, - { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, - { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, - { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, - { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, - { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826 }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577 }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556 }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114 }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638 }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463 }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986 }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543 }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763 }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063 }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973 }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116 }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011 }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870 }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089 }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181 }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658 }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003 }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344 }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669 }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252 }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081 }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159 }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626 }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613 }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115 }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427 }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090 }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246 }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814 }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809 }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454 }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355 }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175 }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228 }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194 }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429 }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912 }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108 }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641 }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901 }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132 }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261 }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272 }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923 }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062 }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341 }, ] [[package]] @@ -2640,55 +2707,55 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "implementation_name == 'pypy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86", size = 1333328, upload-time = "2025-09-08T23:07:45.946Z" }, - { url = "https://files.pythonhosted.org/packages/bd/a0/fc7e78a23748ad5443ac3275943457e8452da67fda347e05260261108cbc/pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581", size = 908803, upload-time = "2025-09-08T23:07:47.551Z" }, - { url = "https://files.pythonhosted.org/packages/7e/22/37d15eb05f3bdfa4abea6f6d96eb3bb58585fbd3e4e0ded4e743bc650c97/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f", size = 668836, upload-time = "2025-09-08T23:07:49.436Z" }, - { url = "https://files.pythonhosted.org/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e", size = 857038, upload-time = "2025-09-08T23:07:51.234Z" }, - { url = "https://files.pythonhosted.org/packages/cb/eb/bfdcb41d0db9cd233d6fb22dc131583774135505ada800ebf14dfb0a7c40/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15c8bd0fe0dabf808e2d7a681398c4e5ded70a551ab47482067a572c054c8e2e", size = 1657531, upload-time = "2025-09-08T23:07:52.795Z" }, - { url = "https://files.pythonhosted.org/packages/ab/21/e3180ca269ed4a0de5c34417dfe71a8ae80421198be83ee619a8a485b0c7/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bafcb3dd171b4ae9f19ee6380dfc71ce0390fefaf26b504c0e5f628d7c8c54f2", size = 2034786, upload-time = "2025-09-08T23:07:55.047Z" }, - { url = "https://files.pythonhosted.org/packages/3b/b1/5e21d0b517434b7f33588ff76c177c5a167858cc38ef740608898cd329f2/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e829529fcaa09937189178115c49c504e69289abd39967cd8a4c215761373394", size = 1894220, upload-time = "2025-09-08T23:07:57.172Z" }, - { url = "https://files.pythonhosted.org/packages/03/f2/44913a6ff6941905efc24a1acf3d3cb6146b636c546c7406c38c49c403d4/pyzmq-27.1.0-cp311-cp311-win32.whl", hash = "sha256:6df079c47d5902af6db298ec92151db82ecb557af663098b92f2508c398bb54f", size = 567155, upload-time = "2025-09-08T23:07:59.05Z" }, - { url = "https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97", size = 633428, upload-time = "2025-09-08T23:08:00.663Z" }, - { url = "https://files.pythonhosted.org/packages/ae/14/01afebc96c5abbbd713ecfc7469cfb1bc801c819a74ed5c9fad9a48801cb/pyzmq-27.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:eca6b47df11a132d1745eb3b5b5e557a7dae2c303277aa0e69c6ba91b8736e07", size = 559497, upload-time = "2025-09-08T23:08:02.15Z" }, - { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, - { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, - { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, - { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" }, - { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" }, - { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" }, - { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" }, - { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, - { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, - { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, - { url = "https://files.pythonhosted.org/packages/60/cb/84a13459c51da6cec1b7b1dc1a47e6db6da50b77ad7fd9c145842750a011/pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5", size = 1122436, upload-time = "2025-09-08T23:08:20.801Z" }, - { url = "https://files.pythonhosted.org/packages/dc/b6/94414759a69a26c3dd674570a81813c46a078767d931a6c70ad29fc585cb/pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6", size = 1156301, upload-time = "2025-09-08T23:08:22.47Z" }, - { url = "https://files.pythonhosted.org/packages/a5/ad/15906493fd40c316377fd8a8f6b1f93104f97a752667763c9b9c1b71d42d/pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7", size = 1341197, upload-time = "2025-09-08T23:08:24.286Z" }, - { url = "https://files.pythonhosted.org/packages/14/1d/d343f3ce13db53a54cb8946594e567410b2125394dafcc0268d8dda027e0/pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05", size = 897275, upload-time = "2025-09-08T23:08:26.063Z" }, - { url = "https://files.pythonhosted.org/packages/69/2d/d83dd6d7ca929a2fc67d2c3005415cdf322af7751d773524809f9e585129/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9", size = 660469, upload-time = "2025-09-08T23:08:27.623Z" }, - { url = "https://files.pythonhosted.org/packages/3e/cd/9822a7af117f4bc0f1952dbe9ef8358eb50a24928efd5edf54210b850259/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128", size = 847961, upload-time = "2025-09-08T23:08:29.672Z" }, - { url = "https://files.pythonhosted.org/packages/9a/12/f003e824a19ed73be15542f172fd0ec4ad0b60cf37436652c93b9df7c585/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39", size = 1650282, upload-time = "2025-09-08T23:08:31.349Z" }, - { url = "https://files.pythonhosted.org/packages/d5/4a/e82d788ed58e9a23995cee70dbc20c9aded3d13a92d30d57ec2291f1e8a3/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97", size = 2024468, upload-time = "2025-09-08T23:08:33.543Z" }, - { url = "https://files.pythonhosted.org/packages/d9/94/2da0a60841f757481e402b34bf4c8bf57fa54a5466b965de791b1e6f747d/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db", size = 1885394, upload-time = "2025-09-08T23:08:35.51Z" }, - { url = "https://files.pythonhosted.org/packages/4f/6f/55c10e2e49ad52d080dc24e37adb215e5b0d64990b57598abc2e3f01725b/pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c", size = 574964, upload-time = "2025-09-08T23:08:37.178Z" }, - { url = "https://files.pythonhosted.org/packages/87/4d/2534970ba63dd7c522d8ca80fb92777f362c0f321900667c615e2067cb29/pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2", size = 641029, upload-time = "2025-09-08T23:08:40.595Z" }, - { url = "https://files.pythonhosted.org/packages/f6/fa/f8aea7a28b0641f31d40dea42d7ef003fded31e184ef47db696bc74cd610/pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e", size = 561541, upload-time = "2025-09-08T23:08:42.668Z" }, - { url = "https://files.pythonhosted.org/packages/87/45/19efbb3000956e82d0331bafca5d9ac19ea2857722fa2caacefb6042f39d/pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a", size = 1341197, upload-time = "2025-09-08T23:08:44.973Z" }, - { url = "https://files.pythonhosted.org/packages/48/43/d72ccdbf0d73d1343936296665826350cb1e825f92f2db9db3e61c2162a2/pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea", size = 897175, upload-time = "2025-09-08T23:08:46.601Z" }, - { url = "https://files.pythonhosted.org/packages/2f/2e/a483f73a10b65a9ef0161e817321d39a770b2acf8bcf3004a28d90d14a94/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96", size = 660427, upload-time = "2025-09-08T23:08:48.187Z" }, - { url = "https://files.pythonhosted.org/packages/f5/d2/5f36552c2d3e5685abe60dfa56f91169f7a2d99bbaf67c5271022ab40863/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d", size = 847929, upload-time = "2025-09-08T23:08:49.76Z" }, - { url = "https://files.pythonhosted.org/packages/c4/2a/404b331f2b7bf3198e9945f75c4c521f0c6a3a23b51f7a4a401b94a13833/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146", size = 1650193, upload-time = "2025-09-08T23:08:51.7Z" }, - { url = "https://files.pythonhosted.org/packages/1c/0b/f4107e33f62a5acf60e3ded67ed33d79b4ce18de432625ce2fc5093d6388/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd", size = 2024388, upload-time = "2025-09-08T23:08:53.393Z" }, - { url = "https://files.pythonhosted.org/packages/0d/01/add31fe76512642fd6e40e3a3bd21f4b47e242c8ba33efb6809e37076d9b/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a", size = 1885316, upload-time = "2025-09-08T23:08:55.702Z" }, - { url = "https://files.pythonhosted.org/packages/c4/59/a5f38970f9bf07cee96128de79590bb354917914a9be11272cfc7ff26af0/pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92", size = 587472, upload-time = "2025-09-08T23:08:58.18Z" }, - { url = "https://files.pythonhosted.org/packages/70/d8/78b1bad170f93fcf5e3536e70e8fadac55030002275c9a29e8f5719185de/pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0", size = 661401, upload-time = "2025-09-08T23:08:59.802Z" }, - { url = "https://files.pythonhosted.org/packages/81/d6/4bfbb40c9a0b42fc53c7cf442f6385db70b40f74a783130c5d0a5aa62228/pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7", size = 575170, upload-time = "2025-09-08T23:09:01.418Z" }, - { url = "https://files.pythonhosted.org/packages/4c/c6/c4dcdecdbaa70969ee1fdced6d7b8f60cfabe64d25361f27ac4665a70620/pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066", size = 836265, upload-time = "2025-09-08T23:09:49.376Z" }, - { url = "https://files.pythonhosted.org/packages/3e/79/f38c92eeaeb03a2ccc2ba9866f0439593bb08c5e3b714ac1d553e5c96e25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604", size = 800208, upload-time = "2025-09-08T23:09:51.073Z" }, - { url = "https://files.pythonhosted.org/packages/49/0e/3f0d0d335c6b3abb9b7b723776d0b21fa7f3a6c819a0db6097059aada160/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c", size = 567747, upload-time = "2025-09-08T23:09:52.698Z" }, - { url = "https://files.pythonhosted.org/packages/a1/cf/f2b3784d536250ffd4be70e049f3b60981235d70c6e8ce7e3ef21e1adb25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f605d884e7c8be8fe1aa94e0a783bf3f591b84c24e4bc4f3e7564c82ac25e271", size = 747371, upload-time = "2025-09-08T23:09:54.563Z" }, - { url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86", size = 1333328 }, + { url = "https://files.pythonhosted.org/packages/bd/a0/fc7e78a23748ad5443ac3275943457e8452da67fda347e05260261108cbc/pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581", size = 908803 }, + { url = "https://files.pythonhosted.org/packages/7e/22/37d15eb05f3bdfa4abea6f6d96eb3bb58585fbd3e4e0ded4e743bc650c97/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f", size = 668836 }, + { url = "https://files.pythonhosted.org/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e", size = 857038 }, + { url = "https://files.pythonhosted.org/packages/cb/eb/bfdcb41d0db9cd233d6fb22dc131583774135505ada800ebf14dfb0a7c40/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15c8bd0fe0dabf808e2d7a681398c4e5ded70a551ab47482067a572c054c8e2e", size = 1657531 }, + { url = "https://files.pythonhosted.org/packages/ab/21/e3180ca269ed4a0de5c34417dfe71a8ae80421198be83ee619a8a485b0c7/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bafcb3dd171b4ae9f19ee6380dfc71ce0390fefaf26b504c0e5f628d7c8c54f2", size = 2034786 }, + { url = "https://files.pythonhosted.org/packages/3b/b1/5e21d0b517434b7f33588ff76c177c5a167858cc38ef740608898cd329f2/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e829529fcaa09937189178115c49c504e69289abd39967cd8a4c215761373394", size = 1894220 }, + { url = "https://files.pythonhosted.org/packages/03/f2/44913a6ff6941905efc24a1acf3d3cb6146b636c546c7406c38c49c403d4/pyzmq-27.1.0-cp311-cp311-win32.whl", hash = "sha256:6df079c47d5902af6db298ec92151db82ecb557af663098b92f2508c398bb54f", size = 567155 }, + { url = "https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97", size = 633428 }, + { url = "https://files.pythonhosted.org/packages/ae/14/01afebc96c5abbbd713ecfc7469cfb1bc801c819a74ed5c9fad9a48801cb/pyzmq-27.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:eca6b47df11a132d1745eb3b5b5e557a7dae2c303277aa0e69c6ba91b8736e07", size = 559497 }, + { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279 }, + { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645 }, + { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574 }, + { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995 }, + { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070 }, + { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121 }, + { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550 }, + { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184 }, + { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480 }, + { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993 }, + { url = "https://files.pythonhosted.org/packages/60/cb/84a13459c51da6cec1b7b1dc1a47e6db6da50b77ad7fd9c145842750a011/pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5", size = 1122436 }, + { url = "https://files.pythonhosted.org/packages/dc/b6/94414759a69a26c3dd674570a81813c46a078767d931a6c70ad29fc585cb/pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6", size = 1156301 }, + { url = "https://files.pythonhosted.org/packages/a5/ad/15906493fd40c316377fd8a8f6b1f93104f97a752667763c9b9c1b71d42d/pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7", size = 1341197 }, + { url = "https://files.pythonhosted.org/packages/14/1d/d343f3ce13db53a54cb8946594e567410b2125394dafcc0268d8dda027e0/pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05", size = 897275 }, + { url = "https://files.pythonhosted.org/packages/69/2d/d83dd6d7ca929a2fc67d2c3005415cdf322af7751d773524809f9e585129/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9", size = 660469 }, + { url = "https://files.pythonhosted.org/packages/3e/cd/9822a7af117f4bc0f1952dbe9ef8358eb50a24928efd5edf54210b850259/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128", size = 847961 }, + { url = "https://files.pythonhosted.org/packages/9a/12/f003e824a19ed73be15542f172fd0ec4ad0b60cf37436652c93b9df7c585/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39", size = 1650282 }, + { url = "https://files.pythonhosted.org/packages/d5/4a/e82d788ed58e9a23995cee70dbc20c9aded3d13a92d30d57ec2291f1e8a3/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97", size = 2024468 }, + { url = "https://files.pythonhosted.org/packages/d9/94/2da0a60841f757481e402b34bf4c8bf57fa54a5466b965de791b1e6f747d/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db", size = 1885394 }, + { url = "https://files.pythonhosted.org/packages/4f/6f/55c10e2e49ad52d080dc24e37adb215e5b0d64990b57598abc2e3f01725b/pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c", size = 574964 }, + { url = "https://files.pythonhosted.org/packages/87/4d/2534970ba63dd7c522d8ca80fb92777f362c0f321900667c615e2067cb29/pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2", size = 641029 }, + { url = "https://files.pythonhosted.org/packages/f6/fa/f8aea7a28b0641f31d40dea42d7ef003fded31e184ef47db696bc74cd610/pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e", size = 561541 }, + { url = "https://files.pythonhosted.org/packages/87/45/19efbb3000956e82d0331bafca5d9ac19ea2857722fa2caacefb6042f39d/pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a", size = 1341197 }, + { url = "https://files.pythonhosted.org/packages/48/43/d72ccdbf0d73d1343936296665826350cb1e825f92f2db9db3e61c2162a2/pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea", size = 897175 }, + { url = "https://files.pythonhosted.org/packages/2f/2e/a483f73a10b65a9ef0161e817321d39a770b2acf8bcf3004a28d90d14a94/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96", size = 660427 }, + { url = "https://files.pythonhosted.org/packages/f5/d2/5f36552c2d3e5685abe60dfa56f91169f7a2d99bbaf67c5271022ab40863/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d", size = 847929 }, + { url = "https://files.pythonhosted.org/packages/c4/2a/404b331f2b7bf3198e9945f75c4c521f0c6a3a23b51f7a4a401b94a13833/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146", size = 1650193 }, + { url = "https://files.pythonhosted.org/packages/1c/0b/f4107e33f62a5acf60e3ded67ed33d79b4ce18de432625ce2fc5093d6388/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd", size = 2024388 }, + { url = "https://files.pythonhosted.org/packages/0d/01/add31fe76512642fd6e40e3a3bd21f4b47e242c8ba33efb6809e37076d9b/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a", size = 1885316 }, + { url = "https://files.pythonhosted.org/packages/c4/59/a5f38970f9bf07cee96128de79590bb354917914a9be11272cfc7ff26af0/pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92", size = 587472 }, + { url = "https://files.pythonhosted.org/packages/70/d8/78b1bad170f93fcf5e3536e70e8fadac55030002275c9a29e8f5719185de/pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0", size = 661401 }, + { url = "https://files.pythonhosted.org/packages/81/d6/4bfbb40c9a0b42fc53c7cf442f6385db70b40f74a783130c5d0a5aa62228/pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7", size = 575170 }, + { url = "https://files.pythonhosted.org/packages/4c/c6/c4dcdecdbaa70969ee1fdced6d7b8f60cfabe64d25361f27ac4665a70620/pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066", size = 836265 }, + { url = "https://files.pythonhosted.org/packages/3e/79/f38c92eeaeb03a2ccc2ba9866f0439593bb08c5e3b714ac1d553e5c96e25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604", size = 800208 }, + { url = "https://files.pythonhosted.org/packages/49/0e/3f0d0d335c6b3abb9b7b723776d0b21fa7f3a6c819a0db6097059aada160/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c", size = 567747 }, + { url = "https://files.pythonhosted.org/packages/a1/cf/f2b3784d536250ffd4be70e049f3b60981235d70c6e8ce7e3ef21e1adb25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f605d884e7c8be8fe1aa94e0a783bf3f591b84c24e4bc4f3e7564c82ac25e271", size = 747371 }, + { url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862 }, ] [[package]] @@ -2795,9 +2862,9 @@ dependencies = [ { name = "rpds-py" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766 }, ] [[package]] @@ -2810,9 +2877,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738 }, ] [[package]] @@ -2822,18 +2889,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513, upload-time = "2021-05-12T16:37:54.178Z" } +sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490, upload-time = "2021-05-12T16:37:52.536Z" }, + { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490 }, ] [[package]] name = "rfc3986-validator" version = "0.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/da/88/f270de456dd7d11dcc808abfa291ecdd3f45ff44e3b549ffa01b126464d0/rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055", size = 6760, upload-time = "2019-10-28T16:00:19.144Z" } +sdist = { url = "https://files.pythonhosted.org/packages/da/88/f270de456dd7d11dcc808abfa291ecdd3f45ff44e3b549ffa01b126464d0/rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055", size = 6760 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9", size = 4242, upload-time = "2019-10-28T16:00:13.976Z" }, + { url = "https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9", size = 4242 }, ] [[package]] @@ -2843,9 +2910,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "lark" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/06/37c1a5557acf449e8e406a830a05bf885ac47d33270aec454ef78675008d/rfc3987_syntax-1.1.0.tar.gz", hash = "sha256:717a62cbf33cffdd16dfa3a497d81ce48a660ea691b1ddd7be710c22f00b4a0d", size = 14239, upload-time = "2025-07-18T01:05:05.015Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/06/37c1a5557acf449e8e406a830a05bf885ac47d33270aec454ef78675008d/rfc3987_syntax-1.1.0.tar.gz", hash = "sha256:717a62cbf33cffdd16dfa3a497d81ce48a660ea691b1ddd7be710c22f00b4a0d", size = 14239 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl", hash = "sha256:6c3d97604e4c5ce9f714898e05401a0445a641cfa276432b0a648c80856f6a3f", size = 8046, upload-time = "2025-07-18T01:05:03.843Z" }, + { url = "https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl", hash = "sha256:6c3d97604e4c5ce9f714898e05401a0445a641cfa276432b0a648c80856f6a3f", size = 8046 }, ] [[package]] @@ -2860,178 +2927,178 @@ dependencies = [ { name = "python-dateutil" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/2f/cbcd0a75fce3bf955fb3317dae037a60bcdbe848c98105afaa25c31d1475/rosettasciio-0.12.0.tar.gz", hash = "sha256:e02575d451e9c3d301fcb68c319ce0728175df9940feff5b09b855e18945a093", size = 1180824, upload-time = "2025-12-29T17:33:06.459Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/00/34/0b9aa833f2dfdb8f22ad6fb337b71b638f9a937abb2796f8112bc507195f/rosettasciio-0.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c529232347e251abd07dd8b9f92f618994cdc289aca687a1df21e2846936a3d3", size = 817077, upload-time = "2025-12-29T17:32:03.968Z" }, - { url = "https://files.pythonhosted.org/packages/15/2b/caac69f14e9ee9486901bca609fe66fb2d21ad144ccaa6e562f696115bc3/rosettasciio-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:33bc1382770b74a1b4c5c9605656cf2f3a63e0409903bce3620a1d5d8401d270", size = 816215, upload-time = "2025-12-29T17:32:05.776Z" }, - { url = "https://files.pythonhosted.org/packages/b0/9b/345d28ce5a802d82048de418a427b3239e4f9ff44553709eef7a3a2b5134/rosettasciio-0.12.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:931d5251c25f4db537573671723fddcb1938684ed3047425f2f35cc9c89bab2b", size = 1271765, upload-time = "2025-12-29T17:32:07.928Z" }, - { url = "https://files.pythonhosted.org/packages/c9/62/de7bd49e68760d4deda46d58f089e936e1a6bcd14dbd4e7acb4d9c7c1769/rosettasciio-0.12.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:754345908d083083d54aa24572290eb44632fa9ef0c6fca7d547a9e6754a2e88", size = 1276286, upload-time = "2025-12-29T17:32:09.831Z" }, - { url = "https://files.pythonhosted.org/packages/9b/5c/d1f62362790d601b0749089ab30f6193e0a328af523b0569f36361deeda2/rosettasciio-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a57699c95226d6f71714629d23e58045c0ee3378e0aa00e8fe5fb0da88aff2ba", size = 805836, upload-time = "2025-12-29T17:32:12.107Z" }, - { url = "https://files.pythonhosted.org/packages/3c/75/f6f81c945da3c3b8d67c14d4ca26dda0ac3421ad9074bc54196019cfe119/rosettasciio-0.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:e1203f9960a2026122d2900062db1ea48700aaec4fa427ed731cb7f8b0433740", size = 794372, upload-time = "2025-12-29T17:32:13.931Z" }, - { url = "https://files.pythonhosted.org/packages/8b/42/48eebee13ace6e0c714433111ef7854b023f63ced48ccb38c26c4a325e8b/rosettasciio-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:18608de5b37a197d15cf4236d045adfb398dbcd2bc0917a8a193e9a5c87dc871", size = 818054, upload-time = "2025-12-29T17:32:15.366Z" }, - { url = "https://files.pythonhosted.org/packages/90/d6/0971527544fc27f59856700c6220aee2199bd631c51c4eb05bf3edd1e58d/rosettasciio-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2567a490f4be1ce7402551f21ed32f5f34dc5956286fa819594bdc899999d32f", size = 816403, upload-time = "2025-12-29T17:32:16.85Z" }, - { url = "https://files.pythonhosted.org/packages/87/c9/81e6fceaeba01f6d3bf36de9e43145590acba43063f2566d45f11d9a0999/rosettasciio-0.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0bf7ce36dbed455554892c8b84732ec25f3026da8374c56e1d491aac6efddf35", size = 1274114, upload-time = "2025-12-29T17:32:18.488Z" }, - { url = "https://files.pythonhosted.org/packages/88/cb/325acfd3255132574c6fa975362b03bf286db9b8dce0f57eaef9d4d2bf9c/rosettasciio-0.12.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:45f74c2414704cba896c13c09102a960b1624b610f7a2e7a177d70ebea6bb2a6", size = 1278900, upload-time = "2025-12-29T17:32:19.97Z" }, - { url = "https://files.pythonhosted.org/packages/24/4e/b0bcce83693873e26fc7d104708b8b14f710562e6b414c28a8b35c9030d7/rosettasciio-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:7e7bfc6f23604ae5133db33241053c265d35b0649d85029e85b2c261796bd865", size = 806447, upload-time = "2025-12-29T17:32:21.349Z" }, - { url = "https://files.pythonhosted.org/packages/c1/ac/aa09ee2a13608bdbed8049183a9f6983146de38f67d20fc91819f90fd7fc/rosettasciio-0.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:f15b9ab80c2ebd468c554b026bddd62cee02b9caa3d5ef20e354214c321e3ac0", size = 794087, upload-time = "2025-12-29T17:32:22.783Z" }, - { url = "https://files.pythonhosted.org/packages/72/ee/99d31a8514f82b47cebf3a4adad44c04e96c7d05d6821aaba5b737d6f819/rosettasciio-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0289c5f7bbb065880041f1fa1e777ddaf0dea37cc667c0da5d05cfbec6885a1c", size = 817296, upload-time = "2025-12-29T17:32:24.206Z" }, - { url = "https://files.pythonhosted.org/packages/61/26/83a280dfcd703cd2300e7ae4586dc0df2d24d2ed7a42693a355c9f3f875e/rosettasciio-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:35d6d8e8b947277fa6e5a829e0a367cdc1c8fbf29dccdfc667cec12dd1da8d3d", size = 815608, upload-time = "2025-12-29T17:32:26.191Z" }, - { url = "https://files.pythonhosted.org/packages/0b/8e/87e07335406125cff5095d72cffd0360a9fe4c76067659c5ccac5ee712e7/rosettasciio-0.12.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f88523c38eba7c991d711668667aac5c146737c613bbf4e1325f3b0d174eedf", size = 1267905, upload-time = "2025-12-29T17:32:27.612Z" }, - { url = "https://files.pythonhosted.org/packages/cb/7b/f10c0d7d8110cf14a51c47ddeba0fcd32633f87eefdd77d667da1e44c16d/rosettasciio-0.12.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:97727411c3c7560d4349d39582e1888b7e5bda1dcda21d48e30e2047e2f6e45c", size = 1277855, upload-time = "2025-12-29T17:32:29.694Z" }, - { url = "https://files.pythonhosted.org/packages/30/0f/daaa1b14c9fb2b295ff52be0b13604d59d7ae4968cb5779abfd414d61ddb/rosettasciio-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:7769f2caa80c761d7f30adaf5aa11da2dadf4149a0df8f0c939400e2ad924a1c", size = 806235, upload-time = "2025-12-29T17:32:31.571Z" }, - { url = "https://files.pythonhosted.org/packages/ef/ce/89729079e3527cf7cd2bb5e90c0d466d0852ee5cc7267a7f45cc8cdc5482/rosettasciio-0.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:5f307f3abfc061499535313d55b6d630cead7e450040d72276923a98342df930", size = 793943, upload-time = "2025-12-29T17:32:32.989Z" }, - { url = "https://files.pythonhosted.org/packages/33/5e/049b670a32e09cc788e085b503d083ab81f341837c2f1e46bd7ab4714d98/rosettasciio-0.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7ae3e37730b81cfb9b894a0e562693befa2ea44471c8c469ef82caf15c8ca42d", size = 821718, upload-time = "2025-12-29T17:32:35.3Z" }, - { url = "https://files.pythonhosted.org/packages/cf/a1/4884bcb0e187246ec24aee3175d1b2294cfd97b42afee8ccd7ec33a4c8b2/rosettasciio-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:07288b70421dc7ca91ccf59c91f7d9675d425582eae499662f6c3a27f1562e7f", size = 821588, upload-time = "2025-12-29T17:32:37.094Z" }, - { url = "https://files.pythonhosted.org/packages/9b/e8/b223f9b63614982b09f1c29b118466102580b95c518705db792866edb645/rosettasciio-0.12.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c810177218abfc74729f175856bf082bc435ad964ca5ceb1158a87fd23dbe992", size = 1281610, upload-time = "2025-12-29T17:32:38.621Z" }, - { url = "https://files.pythonhosted.org/packages/a4/53/f779609a7f0a887ea97233a00482b3034b262c982549e81337fdcc26bd8c/rosettasciio-0.12.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f9ab696534dc4cc5fd3b3eeac3758ab9c5260b09b4aea0cd94ea52decbede97", size = 1268743, upload-time = "2025-12-29T17:32:40.257Z" }, - { url = "https://files.pythonhosted.org/packages/34/b4/4ec99972f93b605a662ce09e56b2cc562051900492c8516dd935c0db3087/rosettasciio-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c6aecd74a7a0248843efc392a689a8dc68044f7183e2b8b9252c91335ee2f16f", size = 816540, upload-time = "2025-12-29T17:32:41.644Z" }, - { url = "https://files.pythonhosted.org/packages/73/0d/d1d99c3b9614a7525c082737f11c140671d690bdd9fc6a6085f1282bc713/rosettasciio-0.12.0-cp313-cp313t-win_arm64.whl", hash = "sha256:30fe143cd4aede2260cdff388a5686ad4725a27207d095f0c35b6e1fa09e0663", size = 798650, upload-time = "2025-12-29T17:32:43.09Z" }, - { url = "https://files.pythonhosted.org/packages/36/9f/98eaa0d086d6f569b40b8ec13b62d99c2ab6c60bf7a8a5d6999c45a18e6b/rosettasciio-0.12.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:268697caf043823c69e7c6c397db364a798abc1e3c31e56d48c4e1142cb85af7", size = 817569, upload-time = "2025-12-29T17:32:44.6Z" }, - { url = "https://files.pythonhosted.org/packages/18/60/09cb9b438107032251f49c07fad46539814adddfc58eece4790d3ab65a9f/rosettasciio-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fc66c00a4e3c4a564741e77296c120135c0a3fbc4b56640bc812298ca83b5a3c", size = 816443, upload-time = "2025-12-29T17:32:46.279Z" }, - { url = "https://files.pythonhosted.org/packages/61/90/be7c89b63f1cd5c702f23f0247b1a93040c867e37b3817004b89ec2b89a6/rosettasciio-0.12.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fa6023d4f206c37d0e2a4ef12070a0ef03a44ca5a6fbc25ae5ee58be72f236da", size = 1265935, upload-time = "2025-12-29T17:32:47.788Z" }, - { url = "https://files.pythonhosted.org/packages/c5/08/6b9a0a11686d8e10f43da14262129b4b4eb02a7f0a6e8d681b8f6e308fa8/rosettasciio-0.12.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84adcd658b7be1b03b1f8f36b3f5690c43d87b20b8cc8723db561fbfe05510d1", size = 1273714, upload-time = "2025-12-29T17:32:49.394Z" }, - { url = "https://files.pythonhosted.org/packages/f0/a5/50517ea5c856c64e6405ea9c550acd4cdea4b4f595144d73ad344bdca6cf/rosettasciio-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:59b51d45a888ee9361b5b5fb459adf7f5c0ac0e62f20610331e8af94ae5b695b", size = 805318, upload-time = "2025-12-29T17:32:50.861Z" }, - { url = "https://files.pythonhosted.org/packages/48/23/90534c33567d1473dbf59efcb4ba27699d5471824e0036937cb699bbb0cf/rosettasciio-0.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:4623e8263af07174c3fbe02981d15920910ce2d7eaf989ddc100a74bc162b2f6", size = 793074, upload-time = "2025-12-29T17:32:52.227Z" }, - { url = "https://files.pythonhosted.org/packages/62/ca/d6e04ac4b41e8f92e7754bc02f115a4bfa70a782815aacc2d5cad1a21b25/rosettasciio-0.12.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8e445003a48000f6dd462c180b8cea7a54a0e5ce17fe0706228fac619b80cce8", size = 822221, upload-time = "2025-12-29T17:32:54.074Z" }, - { url = "https://files.pythonhosted.org/packages/c4/a5/078548b2760ba2c529de3b4e677d48030d41092d011b4034cb0e2c23310b/rosettasciio-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:608aa659d42654dcd28eeb7076761a736fbbcb59876f8907d5160f9f2d7c5f5d", size = 821917, upload-time = "2025-12-29T17:32:57.044Z" }, - { url = "https://files.pythonhosted.org/packages/bd/ee/88dcea135add00ef59d03138b0b8378028153b00f4c0374ebd86cd19fe58/rosettasciio-0.12.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d5bccd360790e1c17bca31223f4e946eb4b19b55b9c1aae937365ed45839df0", size = 1282089, upload-time = "2025-12-29T17:32:58.535Z" }, - { url = "https://files.pythonhosted.org/packages/05/83/b4e55db04a54cd50ce85ad44604cef619e5a0e72b1eedf1f9d71a77fc0aa/rosettasciio-0.12.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04fa825a951b6fe11c4fca422e30e2d03156d04b197d0a75649db8562f4c7dcd", size = 1269179, upload-time = "2025-12-29T17:33:00.169Z" }, - { url = "https://files.pythonhosted.org/packages/5b/c1/d3ce8ada006d8e0e608859c335c83069cbc1dbce548a07ab870d3d336ee9/rosettasciio-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:3f89eeea6a624179fc11924a904ad7f4699a332a1d7b13176e3e9c2ca116e00a", size = 817913, upload-time = "2025-12-29T17:33:01.779Z" }, - { url = "https://files.pythonhosted.org/packages/4c/08/d41bf9dbbfe60463ab68268c5458d644fcb2d7cab9453c958a53b577b9ce/rosettasciio-0.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:38aec6a42a0810bee065502467f941d563a51d236af762432c65e212bac27ccf", size = 797652, upload-time = "2025-12-29T17:33:03.331Z" }, - { url = "https://files.pythonhosted.org/packages/56/00/772baf64631f9ec440b3291b708a04a00442aad796625cc0f0c6f8a1c0c4/rosettasciio-0.12.0-py3-none-any.whl", hash = "sha256:3974e75b1502b4e974c227f0b80d35240011c445b6d63394482fd9e6afc5c9fb", size = 567619, upload-time = "2025-12-29T17:33:04.939Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/2e/2f/cbcd0a75fce3bf955fb3317dae037a60bcdbe848c98105afaa25c31d1475/rosettasciio-0.12.0.tar.gz", hash = "sha256:e02575d451e9c3d301fcb68c319ce0728175df9940feff5b09b855e18945a093", size = 1180824 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/34/0b9aa833f2dfdb8f22ad6fb337b71b638f9a937abb2796f8112bc507195f/rosettasciio-0.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c529232347e251abd07dd8b9f92f618994cdc289aca687a1df21e2846936a3d3", size = 817077 }, + { url = "https://files.pythonhosted.org/packages/15/2b/caac69f14e9ee9486901bca609fe66fb2d21ad144ccaa6e562f696115bc3/rosettasciio-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:33bc1382770b74a1b4c5c9605656cf2f3a63e0409903bce3620a1d5d8401d270", size = 816215 }, + { url = "https://files.pythonhosted.org/packages/b0/9b/345d28ce5a802d82048de418a427b3239e4f9ff44553709eef7a3a2b5134/rosettasciio-0.12.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:931d5251c25f4db537573671723fddcb1938684ed3047425f2f35cc9c89bab2b", size = 1271765 }, + { url = "https://files.pythonhosted.org/packages/c9/62/de7bd49e68760d4deda46d58f089e936e1a6bcd14dbd4e7acb4d9c7c1769/rosettasciio-0.12.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:754345908d083083d54aa24572290eb44632fa9ef0c6fca7d547a9e6754a2e88", size = 1276286 }, + { url = "https://files.pythonhosted.org/packages/9b/5c/d1f62362790d601b0749089ab30f6193e0a328af523b0569f36361deeda2/rosettasciio-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a57699c95226d6f71714629d23e58045c0ee3378e0aa00e8fe5fb0da88aff2ba", size = 805836 }, + { url = "https://files.pythonhosted.org/packages/3c/75/f6f81c945da3c3b8d67c14d4ca26dda0ac3421ad9074bc54196019cfe119/rosettasciio-0.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:e1203f9960a2026122d2900062db1ea48700aaec4fa427ed731cb7f8b0433740", size = 794372 }, + { url = "https://files.pythonhosted.org/packages/8b/42/48eebee13ace6e0c714433111ef7854b023f63ced48ccb38c26c4a325e8b/rosettasciio-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:18608de5b37a197d15cf4236d045adfb398dbcd2bc0917a8a193e9a5c87dc871", size = 818054 }, + { url = "https://files.pythonhosted.org/packages/90/d6/0971527544fc27f59856700c6220aee2199bd631c51c4eb05bf3edd1e58d/rosettasciio-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2567a490f4be1ce7402551f21ed32f5f34dc5956286fa819594bdc899999d32f", size = 816403 }, + { url = "https://files.pythonhosted.org/packages/87/c9/81e6fceaeba01f6d3bf36de9e43145590acba43063f2566d45f11d9a0999/rosettasciio-0.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0bf7ce36dbed455554892c8b84732ec25f3026da8374c56e1d491aac6efddf35", size = 1274114 }, + { url = "https://files.pythonhosted.org/packages/88/cb/325acfd3255132574c6fa975362b03bf286db9b8dce0f57eaef9d4d2bf9c/rosettasciio-0.12.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:45f74c2414704cba896c13c09102a960b1624b610f7a2e7a177d70ebea6bb2a6", size = 1278900 }, + { url = "https://files.pythonhosted.org/packages/24/4e/b0bcce83693873e26fc7d104708b8b14f710562e6b414c28a8b35c9030d7/rosettasciio-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:7e7bfc6f23604ae5133db33241053c265d35b0649d85029e85b2c261796bd865", size = 806447 }, + { url = "https://files.pythonhosted.org/packages/c1/ac/aa09ee2a13608bdbed8049183a9f6983146de38f67d20fc91819f90fd7fc/rosettasciio-0.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:f15b9ab80c2ebd468c554b026bddd62cee02b9caa3d5ef20e354214c321e3ac0", size = 794087 }, + { url = "https://files.pythonhosted.org/packages/72/ee/99d31a8514f82b47cebf3a4adad44c04e96c7d05d6821aaba5b737d6f819/rosettasciio-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0289c5f7bbb065880041f1fa1e777ddaf0dea37cc667c0da5d05cfbec6885a1c", size = 817296 }, + { url = "https://files.pythonhosted.org/packages/61/26/83a280dfcd703cd2300e7ae4586dc0df2d24d2ed7a42693a355c9f3f875e/rosettasciio-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:35d6d8e8b947277fa6e5a829e0a367cdc1c8fbf29dccdfc667cec12dd1da8d3d", size = 815608 }, + { url = "https://files.pythonhosted.org/packages/0b/8e/87e07335406125cff5095d72cffd0360a9fe4c76067659c5ccac5ee712e7/rosettasciio-0.12.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f88523c38eba7c991d711668667aac5c146737c613bbf4e1325f3b0d174eedf", size = 1267905 }, + { url = "https://files.pythonhosted.org/packages/cb/7b/f10c0d7d8110cf14a51c47ddeba0fcd32633f87eefdd77d667da1e44c16d/rosettasciio-0.12.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:97727411c3c7560d4349d39582e1888b7e5bda1dcda21d48e30e2047e2f6e45c", size = 1277855 }, + { url = "https://files.pythonhosted.org/packages/30/0f/daaa1b14c9fb2b295ff52be0b13604d59d7ae4968cb5779abfd414d61ddb/rosettasciio-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:7769f2caa80c761d7f30adaf5aa11da2dadf4149a0df8f0c939400e2ad924a1c", size = 806235 }, + { url = "https://files.pythonhosted.org/packages/ef/ce/89729079e3527cf7cd2bb5e90c0d466d0852ee5cc7267a7f45cc8cdc5482/rosettasciio-0.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:5f307f3abfc061499535313d55b6d630cead7e450040d72276923a98342df930", size = 793943 }, + { url = "https://files.pythonhosted.org/packages/33/5e/049b670a32e09cc788e085b503d083ab81f341837c2f1e46bd7ab4714d98/rosettasciio-0.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7ae3e37730b81cfb9b894a0e562693befa2ea44471c8c469ef82caf15c8ca42d", size = 821718 }, + { url = "https://files.pythonhosted.org/packages/cf/a1/4884bcb0e187246ec24aee3175d1b2294cfd97b42afee8ccd7ec33a4c8b2/rosettasciio-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:07288b70421dc7ca91ccf59c91f7d9675d425582eae499662f6c3a27f1562e7f", size = 821588 }, + { url = "https://files.pythonhosted.org/packages/9b/e8/b223f9b63614982b09f1c29b118466102580b95c518705db792866edb645/rosettasciio-0.12.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c810177218abfc74729f175856bf082bc435ad964ca5ceb1158a87fd23dbe992", size = 1281610 }, + { url = "https://files.pythonhosted.org/packages/a4/53/f779609a7f0a887ea97233a00482b3034b262c982549e81337fdcc26bd8c/rosettasciio-0.12.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f9ab696534dc4cc5fd3b3eeac3758ab9c5260b09b4aea0cd94ea52decbede97", size = 1268743 }, + { url = "https://files.pythonhosted.org/packages/34/b4/4ec99972f93b605a662ce09e56b2cc562051900492c8516dd935c0db3087/rosettasciio-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c6aecd74a7a0248843efc392a689a8dc68044f7183e2b8b9252c91335ee2f16f", size = 816540 }, + { url = "https://files.pythonhosted.org/packages/73/0d/d1d99c3b9614a7525c082737f11c140671d690bdd9fc6a6085f1282bc713/rosettasciio-0.12.0-cp313-cp313t-win_arm64.whl", hash = "sha256:30fe143cd4aede2260cdff388a5686ad4725a27207d095f0c35b6e1fa09e0663", size = 798650 }, + { url = "https://files.pythonhosted.org/packages/36/9f/98eaa0d086d6f569b40b8ec13b62d99c2ab6c60bf7a8a5d6999c45a18e6b/rosettasciio-0.12.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:268697caf043823c69e7c6c397db364a798abc1e3c31e56d48c4e1142cb85af7", size = 817569 }, + { url = "https://files.pythonhosted.org/packages/18/60/09cb9b438107032251f49c07fad46539814adddfc58eece4790d3ab65a9f/rosettasciio-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fc66c00a4e3c4a564741e77296c120135c0a3fbc4b56640bc812298ca83b5a3c", size = 816443 }, + { url = "https://files.pythonhosted.org/packages/61/90/be7c89b63f1cd5c702f23f0247b1a93040c867e37b3817004b89ec2b89a6/rosettasciio-0.12.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fa6023d4f206c37d0e2a4ef12070a0ef03a44ca5a6fbc25ae5ee58be72f236da", size = 1265935 }, + { url = "https://files.pythonhosted.org/packages/c5/08/6b9a0a11686d8e10f43da14262129b4b4eb02a7f0a6e8d681b8f6e308fa8/rosettasciio-0.12.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84adcd658b7be1b03b1f8f36b3f5690c43d87b20b8cc8723db561fbfe05510d1", size = 1273714 }, + { url = "https://files.pythonhosted.org/packages/f0/a5/50517ea5c856c64e6405ea9c550acd4cdea4b4f595144d73ad344bdca6cf/rosettasciio-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:59b51d45a888ee9361b5b5fb459adf7f5c0ac0e62f20610331e8af94ae5b695b", size = 805318 }, + { url = "https://files.pythonhosted.org/packages/48/23/90534c33567d1473dbf59efcb4ba27699d5471824e0036937cb699bbb0cf/rosettasciio-0.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:4623e8263af07174c3fbe02981d15920910ce2d7eaf989ddc100a74bc162b2f6", size = 793074 }, + { url = "https://files.pythonhosted.org/packages/62/ca/d6e04ac4b41e8f92e7754bc02f115a4bfa70a782815aacc2d5cad1a21b25/rosettasciio-0.12.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8e445003a48000f6dd462c180b8cea7a54a0e5ce17fe0706228fac619b80cce8", size = 822221 }, + { url = "https://files.pythonhosted.org/packages/c4/a5/078548b2760ba2c529de3b4e677d48030d41092d011b4034cb0e2c23310b/rosettasciio-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:608aa659d42654dcd28eeb7076761a736fbbcb59876f8907d5160f9f2d7c5f5d", size = 821917 }, + { url = "https://files.pythonhosted.org/packages/bd/ee/88dcea135add00ef59d03138b0b8378028153b00f4c0374ebd86cd19fe58/rosettasciio-0.12.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d5bccd360790e1c17bca31223f4e946eb4b19b55b9c1aae937365ed45839df0", size = 1282089 }, + { url = "https://files.pythonhosted.org/packages/05/83/b4e55db04a54cd50ce85ad44604cef619e5a0e72b1eedf1f9d71a77fc0aa/rosettasciio-0.12.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04fa825a951b6fe11c4fca422e30e2d03156d04b197d0a75649db8562f4c7dcd", size = 1269179 }, + { url = "https://files.pythonhosted.org/packages/5b/c1/d3ce8ada006d8e0e608859c335c83069cbc1dbce548a07ab870d3d336ee9/rosettasciio-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:3f89eeea6a624179fc11924a904ad7f4699a332a1d7b13176e3e9c2ca116e00a", size = 817913 }, + { url = "https://files.pythonhosted.org/packages/4c/08/d41bf9dbbfe60463ab68268c5458d644fcb2d7cab9453c958a53b577b9ce/rosettasciio-0.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:38aec6a42a0810bee065502467f941d563a51d236af762432c65e212bac27ccf", size = 797652 }, + { url = "https://files.pythonhosted.org/packages/56/00/772baf64631f9ec440b3291b708a04a00442aad796625cc0f0c6f8a1c0c4/rosettasciio-0.12.0-py3-none-any.whl", hash = "sha256:3974e75b1502b4e974c227f0b80d35240011c445b6d63394482fd9e6afc5c9fb", size = 567619 }, ] [[package]] name = "rpds-py" version = "0.30.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157, upload-time = "2025-11-30T20:21:53.789Z" }, - { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676, upload-time = "2025-11-30T20:21:55.475Z" }, - { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938, upload-time = "2025-11-30T20:21:57.079Z" }, - { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932, upload-time = "2025-11-30T20:21:58.47Z" }, - { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830, upload-time = "2025-11-30T20:21:59.699Z" }, - { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033, upload-time = "2025-11-30T20:22:00.991Z" }, - { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828, upload-time = "2025-11-30T20:22:02.723Z" }, - { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683, upload-time = "2025-11-30T20:22:04.367Z" }, - { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583, upload-time = "2025-11-30T20:22:05.814Z" }, - { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496, upload-time = "2025-11-30T20:22:07.713Z" }, - { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669, upload-time = "2025-11-30T20:22:09.312Z" }, - { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011, upload-time = "2025-11-30T20:22:11.309Z" }, - { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406, upload-time = "2025-11-30T20:22:13.101Z" }, - { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024, upload-time = "2025-11-30T20:22:14.853Z" }, - { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069, upload-time = "2025-11-30T20:22:16.577Z" }, - { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, - { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, - { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, - { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, - { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, - { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, - { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, - { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, - { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, - { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, - { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, - { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, - { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, - { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, - { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, - { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, - { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, - { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, - { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, - { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, - { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, - { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, - { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, - { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, - { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, - { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, - { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, - { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, - { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, - { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, - { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, - { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, - { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, - { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, - { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, - { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, - { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, - { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, - { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, - { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, - { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, - { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, - { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, - { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, - { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, - { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, - { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, - { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, - { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, - { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, - { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, - { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, - { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, - { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, - { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, - { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, - { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, - { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, - { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, - { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, - { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, - { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, - { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, - { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, - { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, - { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, - { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, - { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, - { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, - { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, - { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, - { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, - { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292, upload-time = "2025-11-30T20:24:16.537Z" }, - { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128, upload-time = "2025-11-30T20:24:18.086Z" }, - { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542, upload-time = "2025-11-30T20:24:20.092Z" }, - { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004, upload-time = "2025-11-30T20:24:22.231Z" }, - { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063, upload-time = "2025-11-30T20:24:24.302Z" }, - { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099, upload-time = "2025-11-30T20:24:25.916Z" }, - { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177, upload-time = "2025-11-30T20:24:27.834Z" }, - { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015, upload-time = "2025-11-30T20:24:29.457Z" }, - { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736, upload-time = "2025-11-30T20:24:31.22Z" }, - { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981, upload-time = "2025-11-30T20:24:32.934Z" }, - { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782, upload-time = "2025-11-30T20:24:35.169Z" }, - { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157 }, + { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676 }, + { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938 }, + { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932 }, + { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830 }, + { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033 }, + { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828 }, + { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683 }, + { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583 }, + { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496 }, + { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669 }, + { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011 }, + { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406 }, + { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024 }, + { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069 }, + { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086 }, + { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053 }, + { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763 }, + { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951 }, + { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622 }, + { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492 }, + { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080 }, + { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680 }, + { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589 }, + { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289 }, + { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737 }, + { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120 }, + { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782 }, + { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463 }, + { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868 }, + { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887 }, + { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904 }, + { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945 }, + { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783 }, + { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021 }, + { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589 }, + { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025 }, + { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895 }, + { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799 }, + { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731 }, + { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027 }, + { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020 }, + { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139 }, + { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224 }, + { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645 }, + { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443 }, + { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375 }, + { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850 }, + { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812 }, + { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841 }, + { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149 }, + { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843 }, + { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507 }, + { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949 }, + { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790 }, + { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217 }, + { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806 }, + { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341 }, + { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768 }, + { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099 }, + { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192 }, + { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080 }, + { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841 }, + { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670 }, + { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005 }, + { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112 }, + { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049 }, + { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661 }, + { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606 }, + { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126 }, + { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371 }, + { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298 }, + { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604 }, + { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391 }, + { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868 }, + { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747 }, + { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795 }, + { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330 }, + { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194 }, + { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340 }, + { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765 }, + { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834 }, + { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470 }, + { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630 }, + { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148 }, + { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030 }, + { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570 }, + { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532 }, + { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292 }, + { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128 }, + { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542 }, + { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004 }, + { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063 }, + { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099 }, + { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177 }, + { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015 }, + { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736 }, + { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981 }, + { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782 }, + { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191 }, ] [[package]] name = "ruff" version = "0.15.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/06/04/eab13a954e763b0606f460443fcbf6bb5a0faf06890ea3754ff16523dce5/ruff-0.15.2.tar.gz", hash = "sha256:14b965afee0969e68bb871eba625343b8673375f457af4abe98553e8bbb98342", size = 4558148, upload-time = "2026-02-19T22:32:20.271Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/04/eab13a954e763b0606f460443fcbf6bb5a0faf06890ea3754ff16523dce5/ruff-0.15.2.tar.gz", hash = "sha256:14b965afee0969e68bb871eba625343b8673375f457af4abe98553e8bbb98342", size = 4558148 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/70/3a4dc6d09b13cb3e695f28307e5d889b2e1a66b7af9c5e257e796695b0e6/ruff-0.15.2-py3-none-linux_armv6l.whl", hash = "sha256:120691a6fdae2f16d65435648160f5b81a9625288f75544dc40637436b5d3c0d", size = 10430565, upload-time = "2026-02-19T22:32:41.824Z" }, - { url = "https://files.pythonhosted.org/packages/71/0b/bb8457b56185ece1305c666dc895832946d24055be90692381c31d57466d/ruff-0.15.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a89056d831256099658b6bba4037ac6dd06f49d194199215befe2bb10457ea5e", size = 10820354, upload-time = "2026-02-19T22:32:07.366Z" }, - { url = "https://files.pythonhosted.org/packages/2d/c1/e0532d7f9c9e0b14c46f61b14afd563298b8b83f337b6789ddd987e46121/ruff-0.15.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e36dee3a64be0ebd23c86ffa3aa3fd3ac9a712ff295e192243f814a830b6bd87", size = 10170767, upload-time = "2026-02-19T22:32:13.188Z" }, - { url = "https://files.pythonhosted.org/packages/47/e8/da1aa341d3af017a21c7a62fb5ec31d4e7ad0a93ab80e3a508316efbcb23/ruff-0.15.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9fb47b6d9764677f8c0a193c0943ce9a05d6763523f132325af8a858eadc2b9", size = 10529591, upload-time = "2026-02-19T22:32:02.547Z" }, - { url = "https://files.pythonhosted.org/packages/93/74/184fbf38e9f3510231fbc5e437e808f0b48c42d1df9434b208821efcd8d6/ruff-0.15.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f376990f9d0d6442ea9014b19621d8f2aaf2b8e39fdbfc79220b7f0c596c9b80", size = 10260771, upload-time = "2026-02-19T22:32:36.938Z" }, - { url = "https://files.pythonhosted.org/packages/05/ac/605c20b8e059a0bc4b42360414baa4892ff278cec1c91fff4be0dceedefd/ruff-0.15.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dcc987551952d73cbf5c88d9fdee815618d497e4df86cd4c4824cc59d5dd75f", size = 11045791, upload-time = "2026-02-19T22:32:31.642Z" }, - { url = "https://files.pythonhosted.org/packages/fd/52/db6e419908f45a894924d410ac77d64bdd98ff86901d833364251bd08e22/ruff-0.15.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42a47fd785cbe8c01b9ff45031af875d101b040ad8f4de7bbb716487c74c9a77", size = 11879271, upload-time = "2026-02-19T22:32:29.305Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d8/7992b18f2008bdc9231d0f10b16df7dda964dbf639e2b8b4c1b4e91b83af/ruff-0.15.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cbe9f49354866e575b4c6943856989f966421870e85cd2ac94dccb0a9dcb2fea", size = 11303707, upload-time = "2026-02-19T22:32:22.492Z" }, - { url = "https://files.pythonhosted.org/packages/d7/02/849b46184bcfdd4b64cde61752cc9a146c54759ed036edd11857e9b8443b/ruff-0.15.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7a672c82b5f9887576087d97be5ce439f04bbaf548ee987b92d3a7dede41d3a", size = 11149151, upload-time = "2026-02-19T22:32:44.234Z" }, - { url = "https://files.pythonhosted.org/packages/70/04/f5284e388bab60d1d3b99614a5a9aeb03e0f333847e2429bebd2aaa1feec/ruff-0.15.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ecc64f46f7019e2bcc3cdc05d4a7da958b629a5ab7033195e11a438403d956", size = 11091132, upload-time = "2026-02-19T22:32:24.691Z" }, - { url = "https://files.pythonhosted.org/packages/fa/ae/88d844a21110e14d92cf73d57363fab59b727ebeabe78009b9ccb23500af/ruff-0.15.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:8dcf243b15b561c655c1ef2f2b0050e5d50db37fe90115507f6ff37d865dc8b4", size = 10504717, upload-time = "2026-02-19T22:32:26.75Z" }, - { url = "https://files.pythonhosted.org/packages/64/27/867076a6ada7f2b9c8292884ab44d08fd2ba71bd2b5364d4136f3cd537e1/ruff-0.15.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dab6941c862c05739774677c6273166d2510d254dac0695c0e3f5efa1b5585de", size = 10263122, upload-time = "2026-02-19T22:32:10.036Z" }, - { url = "https://files.pythonhosted.org/packages/e7/ef/faf9321d550f8ebf0c6373696e70d1758e20ccdc3951ad7af00c0956be7c/ruff-0.15.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b9164f57fc36058e9a6806eb92af185b0697c9fe4c7c52caa431c6554521e5c", size = 10735295, upload-time = "2026-02-19T22:32:39.227Z" }, - { url = "https://files.pythonhosted.org/packages/2f/55/e8089fec62e050ba84d71b70e7834b97709ca9b7aba10c1a0b196e493f97/ruff-0.15.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:80d24fcae24d42659db7e335b9e1531697a7102c19185b8dc4a028b952865fd8", size = 11241641, upload-time = "2026-02-19T22:32:34.617Z" }, - { url = "https://files.pythonhosted.org/packages/23/01/1c30526460f4d23222d0fabd5888868262fd0e2b71a00570ca26483cd993/ruff-0.15.2-py3-none-win32.whl", hash = "sha256:fd5ff9e5f519a7e1bd99cbe8daa324010a74f5e2ebc97c6242c08f26f3714f6f", size = 10507885, upload-time = "2026-02-19T22:32:15.635Z" }, - { url = "https://files.pythonhosted.org/packages/5c/10/3d18e3bbdf8fc50bbb4ac3cc45970aa5a9753c5cb51bf9ed9a3cd8b79fa3/ruff-0.15.2-py3-none-win_amd64.whl", hash = "sha256:d20014e3dfa400f3ff84830dfb5755ece2de45ab62ecea4af6b7262d0fb4f7c5", size = 11623725, upload-time = "2026-02-19T22:32:04.947Z" }, - { url = "https://files.pythonhosted.org/packages/6d/78/097c0798b1dab9f8affe73da9642bb4500e098cb27fd8dc9724816ac747b/ruff-0.15.2-py3-none-win_arm64.whl", hash = "sha256:cabddc5822acdc8f7b5527b36ceac55cc51eec7b1946e60181de8fe83ca8876e", size = 10941649, upload-time = "2026-02-19T22:32:18.108Z" }, + { url = "https://files.pythonhosted.org/packages/2f/70/3a4dc6d09b13cb3e695f28307e5d889b2e1a66b7af9c5e257e796695b0e6/ruff-0.15.2-py3-none-linux_armv6l.whl", hash = "sha256:120691a6fdae2f16d65435648160f5b81a9625288f75544dc40637436b5d3c0d", size = 10430565 }, + { url = "https://files.pythonhosted.org/packages/71/0b/bb8457b56185ece1305c666dc895832946d24055be90692381c31d57466d/ruff-0.15.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a89056d831256099658b6bba4037ac6dd06f49d194199215befe2bb10457ea5e", size = 10820354 }, + { url = "https://files.pythonhosted.org/packages/2d/c1/e0532d7f9c9e0b14c46f61b14afd563298b8b83f337b6789ddd987e46121/ruff-0.15.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e36dee3a64be0ebd23c86ffa3aa3fd3ac9a712ff295e192243f814a830b6bd87", size = 10170767 }, + { url = "https://files.pythonhosted.org/packages/47/e8/da1aa341d3af017a21c7a62fb5ec31d4e7ad0a93ab80e3a508316efbcb23/ruff-0.15.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9fb47b6d9764677f8c0a193c0943ce9a05d6763523f132325af8a858eadc2b9", size = 10529591 }, + { url = "https://files.pythonhosted.org/packages/93/74/184fbf38e9f3510231fbc5e437e808f0b48c42d1df9434b208821efcd8d6/ruff-0.15.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f376990f9d0d6442ea9014b19621d8f2aaf2b8e39fdbfc79220b7f0c596c9b80", size = 10260771 }, + { url = "https://files.pythonhosted.org/packages/05/ac/605c20b8e059a0bc4b42360414baa4892ff278cec1c91fff4be0dceedefd/ruff-0.15.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dcc987551952d73cbf5c88d9fdee815618d497e4df86cd4c4824cc59d5dd75f", size = 11045791 }, + { url = "https://files.pythonhosted.org/packages/fd/52/db6e419908f45a894924d410ac77d64bdd98ff86901d833364251bd08e22/ruff-0.15.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42a47fd785cbe8c01b9ff45031af875d101b040ad8f4de7bbb716487c74c9a77", size = 11879271 }, + { url = "https://files.pythonhosted.org/packages/3e/d8/7992b18f2008bdc9231d0f10b16df7dda964dbf639e2b8b4c1b4e91b83af/ruff-0.15.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cbe9f49354866e575b4c6943856989f966421870e85cd2ac94dccb0a9dcb2fea", size = 11303707 }, + { url = "https://files.pythonhosted.org/packages/d7/02/849b46184bcfdd4b64cde61752cc9a146c54759ed036edd11857e9b8443b/ruff-0.15.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7a672c82b5f9887576087d97be5ce439f04bbaf548ee987b92d3a7dede41d3a", size = 11149151 }, + { url = "https://files.pythonhosted.org/packages/70/04/f5284e388bab60d1d3b99614a5a9aeb03e0f333847e2429bebd2aaa1feec/ruff-0.15.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ecc64f46f7019e2bcc3cdc05d4a7da958b629a5ab7033195e11a438403d956", size = 11091132 }, + { url = "https://files.pythonhosted.org/packages/fa/ae/88d844a21110e14d92cf73d57363fab59b727ebeabe78009b9ccb23500af/ruff-0.15.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:8dcf243b15b561c655c1ef2f2b0050e5d50db37fe90115507f6ff37d865dc8b4", size = 10504717 }, + { url = "https://files.pythonhosted.org/packages/64/27/867076a6ada7f2b9c8292884ab44d08fd2ba71bd2b5364d4136f3cd537e1/ruff-0.15.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dab6941c862c05739774677c6273166d2510d254dac0695c0e3f5efa1b5585de", size = 10263122 }, + { url = "https://files.pythonhosted.org/packages/e7/ef/faf9321d550f8ebf0c6373696e70d1758e20ccdc3951ad7af00c0956be7c/ruff-0.15.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b9164f57fc36058e9a6806eb92af185b0697c9fe4c7c52caa431c6554521e5c", size = 10735295 }, + { url = "https://files.pythonhosted.org/packages/2f/55/e8089fec62e050ba84d71b70e7834b97709ca9b7aba10c1a0b196e493f97/ruff-0.15.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:80d24fcae24d42659db7e335b9e1531697a7102c19185b8dc4a028b952865fd8", size = 11241641 }, + { url = "https://files.pythonhosted.org/packages/23/01/1c30526460f4d23222d0fabd5888868262fd0e2b71a00570ca26483cd993/ruff-0.15.2-py3-none-win32.whl", hash = "sha256:fd5ff9e5f519a7e1bd99cbe8daa324010a74f5e2ebc97c6242c08f26f3714f6f", size = 10507885 }, + { url = "https://files.pythonhosted.org/packages/5c/10/3d18e3bbdf8fc50bbb4ac3cc45970aa5a9753c5cb51bf9ed9a3cd8b79fa3/ruff-0.15.2-py3-none-win_amd64.whl", hash = "sha256:d20014e3dfa400f3ff84830dfb5755ece2de45ab62ecea4af6b7262d0fb4f7c5", size = 11623725 }, + { url = "https://files.pythonhosted.org/packages/6d/78/097c0798b1dab9f8affe73da9642bb4500e098cb27fd8dc9724816ac747b/ruff-0.15.2-py3-none-win_arm64.whl", hash = "sha256:cabddc5822acdc8f7b5527b36ceac55cc51eec7b1946e60181de8fe83ca8876e", size = 10941649 }, ] [[package]] @@ -3048,56 +3115,56 @@ dependencies = [ { name = "scipy" }, { name = "tifffile" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/b4/2528bb43c67d48053a7a649a9666432dc307d66ba02e3a6d5c40f46655df/scikit_image-0.26.0.tar.gz", hash = "sha256:f5f970ab04efad85c24714321fcc91613fcb64ef2a892a13167df2f3e59199fa", size = 22729739, upload-time = "2025-12-20T17:12:21.824Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/16/8a407688b607f86f81f8c649bf0d68a2a6d67375f18c2d660aba20f5b648/scikit_image-0.26.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b1ede33a0fb3731457eaf53af6361e73dd510f449dac437ab54573b26788baf0", size = 12355510, upload-time = "2025-12-20T17:10:31.628Z" }, - { url = "https://files.pythonhosted.org/packages/6b/f9/7efc088ececb6f6868fd4475e16cfafc11f242ce9ab5fc3557d78b5da0d4/scikit_image-0.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7af7aa331c6846bd03fa28b164c18d0c3fd419dbb888fb05e958ac4257a78fdd", size = 12056334, upload-time = "2025-12-20T17:10:34.559Z" }, - { url = "https://files.pythonhosted.org/packages/9f/1e/bc7fb91fb5ff65ef42346c8b7ee8b09b04eabf89235ab7dbfdfd96cbd1ea/scikit_image-0.26.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ea6207d9e9d21c3f464efe733121c0504e494dbdc7728649ff3e23c3c5a4953", size = 13297768, upload-time = "2025-12-20T17:10:37.733Z" }, - { url = "https://files.pythonhosted.org/packages/a5/2a/e71c1a7d90e70da67b88ccc609bd6ae54798d5847369b15d3a8052232f9d/scikit_image-0.26.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74aa5518ccea28121f57a95374581d3b979839adc25bb03f289b1bc9b99c58af", size = 13711217, upload-time = "2025-12-20T17:10:40.935Z" }, - { url = "https://files.pythonhosted.org/packages/d4/59/9637ee12c23726266b91296791465218973ce1ad3e4c56fc81e4d8e7d6e1/scikit_image-0.26.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d5c244656de905e195a904e36dbc18585e06ecf67d90f0482cbde63d7f9ad59d", size = 14337782, upload-time = "2025-12-20T17:10:43.452Z" }, - { url = "https://files.pythonhosted.org/packages/e7/5c/a3e1e0860f9294663f540c117e4bf83d55e5b47c281d475cc06227e88411/scikit_image-0.26.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21a818ee6ca2f2131b9e04d8eb7637b5c18773ebe7b399ad23dcc5afaa226d2d", size = 14805997, upload-time = "2025-12-20T17:10:45.93Z" }, - { url = "https://files.pythonhosted.org/packages/d3/c6/2eeacf173da041a9e388975f54e5c49df750757fcfc3ee293cdbbae1ea0a/scikit_image-0.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:9490360c8d3f9a7e85c8de87daf7c0c66507960cf4947bb9610d1751928721c7", size = 11878486, upload-time = "2025-12-20T17:10:48.246Z" }, - { url = "https://files.pythonhosted.org/packages/c3/a4/a852c4949b9058d585e762a66bf7e9a2cd3be4795cd940413dfbfbb0ce79/scikit_image-0.26.0-cp311-cp311-win_arm64.whl", hash = "sha256:0baa0108d2d027f34d748e84e592b78acc23e965a5de0e4bb03cf371de5c0581", size = 11346518, upload-time = "2025-12-20T17:10:50.575Z" }, - { url = "https://files.pythonhosted.org/packages/99/e8/e13757982264b33a1621628f86b587e9a73a13f5256dad49b19ba7dc9083/scikit_image-0.26.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d454b93a6fa770ac5ae2d33570f8e7a321bb80d29511ce4b6b78058ebe176e8c", size = 12376452, upload-time = "2025-12-20T17:10:52.796Z" }, - { url = "https://files.pythonhosted.org/packages/e3/be/f8dd17d0510f9911f9f17ba301f7455328bf13dae416560126d428de9568/scikit_image-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3409e89d66eff5734cd2b672d1c48d2759360057e714e1d92a11df82c87cba37", size = 12061567, upload-time = "2025-12-20T17:10:55.207Z" }, - { url = "https://files.pythonhosted.org/packages/b3/2b/c70120a6880579fb42b91567ad79feb4772f7be72e8d52fec403a3dde0c6/scikit_image-0.26.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c717490cec9e276afb0438dd165b7c3072d6c416709cc0f9f5a4c1070d23a44", size = 13084214, upload-time = "2025-12-20T17:10:57.468Z" }, - { url = "https://files.pythonhosted.org/packages/f4/a2/70401a107d6d7466d64b466927e6b96fcefa99d57494b972608e2f8be50f/scikit_image-0.26.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7df650e79031634ac90b11e64a9eedaf5a5e06fcd09bcd03a34be01745744466", size = 13561683, upload-time = "2025-12-20T17:10:59.49Z" }, - { url = "https://files.pythonhosted.org/packages/13/a5/48bdfd92794c5002d664e0910a349d0a1504671ef5ad358150f21643c79a/scikit_image-0.26.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cefd85033e66d4ea35b525bb0937d7f42d4cdcfed2d1888e1570d5ce450d3932", size = 14112147, upload-time = "2025-12-20T17:11:02.083Z" }, - { url = "https://files.pythonhosted.org/packages/ee/b5/ac71694da92f5def5953ca99f18a10fe98eac2dd0a34079389b70b4d0394/scikit_image-0.26.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3f5bf622d7c0435884e1e141ebbe4b2804e16b2dd23ae4c6183e2ea99233be70", size = 14661625, upload-time = "2025-12-20T17:11:04.528Z" }, - { url = "https://files.pythonhosted.org/packages/23/4d/a3cc1e96f080e253dad2251bfae7587cf2b7912bcd76fd43fd366ff35a87/scikit_image-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:abed017474593cd3056ae0fe948d07d0747b27a085e92df5474f4955dd65aec0", size = 11911059, upload-time = "2025-12-20T17:11:06.61Z" }, - { url = "https://files.pythonhosted.org/packages/35/8a/d1b8055f584acc937478abf4550d122936f420352422a1a625eef2c605d8/scikit_image-0.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:4d57e39ef67a95d26860c8caf9b14b8fb130f83b34c6656a77f191fa6d1d04d8", size = 11348740, upload-time = "2025-12-20T17:11:09.118Z" }, - { url = "https://files.pythonhosted.org/packages/4f/48/02357ffb2cca35640f33f2cfe054a4d6d5d7a229b88880a64f1e45c11f4e/scikit_image-0.26.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a2e852eccf41d2d322b8e60144e124802873a92b8d43a6f96331aa42888491c7", size = 12346329, upload-time = "2025-12-20T17:11:11.599Z" }, - { url = "https://files.pythonhosted.org/packages/67/b9/b792c577cea2c1e94cda83b135a656924fc57c428e8a6d302cd69aac1b60/scikit_image-0.26.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:98329aab3bc87db352b9887f64ce8cdb8e75f7c2daa19927f2e121b797b678d5", size = 12031726, upload-time = "2025-12-20T17:11:13.871Z" }, - { url = "https://files.pythonhosted.org/packages/07/a9/9564250dfd65cb20404a611016db52afc6268b2b371cd19c7538ea47580f/scikit_image-0.26.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:915bb3ba66455cf8adac00dc8fdf18a4cd29656aec7ddd38cb4dda90289a6f21", size = 13094910, upload-time = "2025-12-20T17:11:16.2Z" }, - { url = "https://files.pythonhosted.org/packages/a3/b8/0d8eeb5a9fd7d34ba84f8a55753a0a3e2b5b51b2a5a0ade648a8db4a62f7/scikit_image-0.26.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b36ab5e778bf50af5ff386c3ac508027dc3aaeccf2161bdf96bde6848f44d21b", size = 13660939, upload-time = "2025-12-20T17:11:18.464Z" }, - { url = "https://files.pythonhosted.org/packages/2f/d6/91d8973584d4793d4c1a847d388e34ef1218d835eeddecfc9108d735b467/scikit_image-0.26.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:09bad6a5d5949c7896c8347424c4cca899f1d11668030e5548813ab9c2865dcb", size = 14138938, upload-time = "2025-12-20T17:11:20.919Z" }, - { url = "https://files.pythonhosted.org/packages/39/9a/7e15d8dc10d6bbf212195fb39bdeb7f226c46dd53f9c63c312e111e2e175/scikit_image-0.26.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:aeb14db1ed09ad4bee4ceb9e635547a8d5f3549be67fc6c768c7f923e027e6cd", size = 14752243, upload-time = "2025-12-20T17:11:23.347Z" }, - { url = "https://files.pythonhosted.org/packages/8f/58/2b11b933097bc427e42b4a8b15f7de8f24f2bac1fd2779d2aea1431b2c31/scikit_image-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:ac529eb9dbd5954f9aaa2e3fe9a3fd9661bfe24e134c688587d811a0233127f1", size = 11906770, upload-time = "2025-12-20T17:11:25.297Z" }, - { url = "https://files.pythonhosted.org/packages/ad/ec/96941474a18a04b69b6f6562a5bd79bd68049fa3728d3b350976eccb8b93/scikit_image-0.26.0-cp313-cp313-win_arm64.whl", hash = "sha256:a2d211bc355f59725efdcae699b93b30348a19416cc9e017f7b2fb599faf7219", size = 11342506, upload-time = "2025-12-20T17:11:27.399Z" }, - { url = "https://files.pythonhosted.org/packages/03/e5/c1a9962b0cf1952f42d32b4a2e48eed520320dbc4d2ff0b981c6fa508b6b/scikit_image-0.26.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9eefb4adad066da408a7601c4c24b07af3b472d90e08c3e7483d4e9e829d8c49", size = 12663278, upload-time = "2025-12-20T17:11:29.358Z" }, - { url = "https://files.pythonhosted.org/packages/ae/97/c1a276a59ce8e4e24482d65c1a3940d69c6b3873279193b7ebd04e5ee56b/scikit_image-0.26.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6caec76e16c970c528d15d1c757363334d5cb3069f9cea93d2bead31820511f3", size = 12405142, upload-time = "2025-12-20T17:11:31.282Z" }, - { url = "https://files.pythonhosted.org/packages/d4/4a/f1cbd1357caef6c7993f7efd514d6e53d8fd6f7fe01c4714d51614c53289/scikit_image-0.26.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a07200fe09b9d99fcdab959859fe0f7db8df6333d6204344425d476850ce3604", size = 12942086, upload-time = "2025-12-20T17:11:33.683Z" }, - { url = "https://files.pythonhosted.org/packages/5b/6f/74d9fb87c5655bd64cf00b0c44dc3d6206d9002e5f6ba1c9aeb13236f6bf/scikit_image-0.26.0-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92242351bccf391fc5df2d1529d15470019496d2498d615beb68da85fe7fdf37", size = 13265667, upload-time = "2025-12-20T17:11:36.11Z" }, - { url = "https://files.pythonhosted.org/packages/a7/73/faddc2413ae98d863f6fa2e3e14da4467dd38e788e1c23346cf1a2b06b97/scikit_image-0.26.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:52c496f75a7e45844d951557f13c08c81487c6a1da2e3c9c8a39fcde958e02cc", size = 14001966, upload-time = "2025-12-20T17:11:38.55Z" }, - { url = "https://files.pythonhosted.org/packages/02/94/9f46966fa042b5d57c8cd641045372b4e0df0047dd400e77ea9952674110/scikit_image-0.26.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:20ef4a155e2e78b8ab973998e04d8a361d49d719e65412405f4dadd9155a61d9", size = 14359526, upload-time = "2025-12-20T17:11:41.087Z" }, - { url = "https://files.pythonhosted.org/packages/5d/b4/2840fe38f10057f40b1c9f8fb98a187a370936bf144a4ac23452c5ef1baf/scikit_image-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c9087cf7d0e7f33ab5c46d2068d86d785e70b05400a891f73a13400f1e1faf6a", size = 12287629, upload-time = "2025-12-20T17:11:43.11Z" }, - { url = "https://files.pythonhosted.org/packages/22/ba/73b6ca70796e71f83ab222690e35a79612f0117e5aaf167151b7d46f5f2c/scikit_image-0.26.0-cp313-cp313t-win_arm64.whl", hash = "sha256:27d58bc8b2acd351f972c6508c1b557cfed80299826080a4d803dd29c51b707e", size = 11647755, upload-time = "2025-12-20T17:11:45.279Z" }, - { url = "https://files.pythonhosted.org/packages/51/44/6b744f92b37ae2833fd423cce8f806d2368859ec325a699dc30389e090b9/scikit_image-0.26.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:63af3d3a26125f796f01052052f86806da5b5e54c6abef152edb752683075a9c", size = 12365810, upload-time = "2025-12-20T17:11:47.357Z" }, - { url = "https://files.pythonhosted.org/packages/40/f5/83590d9355191f86ac663420fec741b82cc547a4afe7c4c1d986bf46e4db/scikit_image-0.26.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ce00600cd70d4562ed59f80523e18cdcc1fae0e10676498a01f73c255774aefd", size = 12075717, upload-time = "2025-12-20T17:11:49.483Z" }, - { url = "https://files.pythonhosted.org/packages/72/48/253e7cf5aee6190459fe136c614e2cbccc562deceb4af96e0863f1b8ee29/scikit_image-0.26.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6381edf972b32e4f54085449afde64365a57316637496c1325a736987083e2ab", size = 13161520, upload-time = "2025-12-20T17:11:51.58Z" }, - { url = "https://files.pythonhosted.org/packages/73/c3/cec6a3cbaadfdcc02bd6ff02f3abfe09eaa7f4d4e0a525a1e3a3f4bce49c/scikit_image-0.26.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6624a76c6085218248154cc7e1500e6b488edcd9499004dd0d35040607d7505", size = 13684340, upload-time = "2025-12-20T17:11:53.708Z" }, - { url = "https://files.pythonhosted.org/packages/d4/0d/39a776f675d24164b3a267aa0db9f677a4cb20127660d8bf4fd7fef66817/scikit_image-0.26.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f775f0e420faac9c2aa6757135f4eb468fb7b70e0b67fa77a5e79be3c30ee331", size = 14203839, upload-time = "2025-12-20T17:11:55.89Z" }, - { url = "https://files.pythonhosted.org/packages/ee/25/2514df226bbcedfe9b2caafa1ba7bc87231a0c339066981b182b08340e06/scikit_image-0.26.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede4d6d255cc5da9faeb2f9ba7fedbc990abbc652db429f40a16b22e770bb578", size = 14770021, upload-time = "2025-12-20T17:11:58.014Z" }, - { url = "https://files.pythonhosted.org/packages/8d/5b/0671dc91c0c79340c3fe202f0549c7d3681eb7640fe34ab68a5f090a7c7f/scikit_image-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:0660b83968c15293fd9135e8d860053ee19500d52bf55ca4fb09de595a1af650", size = 12023490, upload-time = "2025-12-20T17:12:00.013Z" }, - { url = "https://files.pythonhosted.org/packages/65/08/7c4cb59f91721f3de07719085212a0b3962e3e3f2d1818cbac4eeb1ea53e/scikit_image-0.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:b8d14d3181c21c11170477a42542c1addc7072a90b986675a71266ad17abc37f", size = 11473782, upload-time = "2025-12-20T17:12:01.983Z" }, - { url = "https://files.pythonhosted.org/packages/49/41/65c4258137acef3d73cb561ac55512eacd7b30bb4f4a11474cad526bc5db/scikit_image-0.26.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:cde0bbd57e6795eba83cb10f71a677f7239271121dc950bc060482834a668ad1", size = 12686060, upload-time = "2025-12-20T17:12:03.886Z" }, - { url = "https://files.pythonhosted.org/packages/e7/32/76971f8727b87f1420a962406388a50e26667c31756126444baf6668f559/scikit_image-0.26.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:163e9afb5b879562b9aeda0dd45208a35316f26cc7a3aed54fd601604e5cf46f", size = 12422628, upload-time = "2025-12-20T17:12:05.921Z" }, - { url = "https://files.pythonhosted.org/packages/37/0d/996febd39f757c40ee7b01cdb861867327e5c8e5f595a634e8201462d958/scikit_image-0.26.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724f79fd9b6cb6f4a37864fe09f81f9f5d5b9646b6868109e1b100d1a7019e59", size = 12962369, upload-time = "2025-12-20T17:12:07.912Z" }, - { url = "https://files.pythonhosted.org/packages/48/b4/612d354f946c9600e7dea012723c11d47e8d455384e530f6daaaeb9bf62c/scikit_image-0.26.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3268f13310e6857508bd87202620df996199a016a1d281b309441d227c822394", size = 13272431, upload-time = "2025-12-20T17:12:10.255Z" }, - { url = "https://files.pythonhosted.org/packages/0a/6e/26c00b466e06055a086de2c6e2145fe189ccdc9a1d11ccc7de020f2591ad/scikit_image-0.26.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fac96a1f9b06cd771cbbb3cd96c5332f36d4efd839b1d8b053f79e5887acde62", size = 14016362, upload-time = "2025-12-20T17:12:12.793Z" }, - { url = "https://files.pythonhosted.org/packages/47/88/00a90402e1775634043c2a0af8a3c76ad450866d9fa444efcc43b553ba2d/scikit_image-0.26.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2c1e7bd342f43e7a97e571b3f03ba4c1293ea1a35c3f13f41efdc8a81c1dc8f2", size = 14364151, upload-time = "2025-12-20T17:12:14.909Z" }, - { url = "https://files.pythonhosted.org/packages/da/ca/918d8d306bd43beacff3b835c6d96fac0ae64c0857092f068b88db531a7c/scikit_image-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b702c3bb115e1dcf4abf5297429b5c90f2189655888cbed14921f3d26f81d3a4", size = 12413484, upload-time = "2025-12-20T17:12:17.046Z" }, - { url = "https://files.pythonhosted.org/packages/dc/cd/4da01329b5a8d47ff7ec3c99a2b02465a8017b186027590dc7425cee0b56/scikit_image-0.26.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0608aa4a9ec39e0843de10d60edb2785a30c1c47819b67866dd223ebd149acaf", size = 11769501, upload-time = "2025-12-20T17:12:19.339Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/a1/b4/2528bb43c67d48053a7a649a9666432dc307d66ba02e3a6d5c40f46655df/scikit_image-0.26.0.tar.gz", hash = "sha256:f5f970ab04efad85c24714321fcc91613fcb64ef2a892a13167df2f3e59199fa", size = 22729739 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/16/8a407688b607f86f81f8c649bf0d68a2a6d67375f18c2d660aba20f5b648/scikit_image-0.26.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b1ede33a0fb3731457eaf53af6361e73dd510f449dac437ab54573b26788baf0", size = 12355510 }, + { url = "https://files.pythonhosted.org/packages/6b/f9/7efc088ececb6f6868fd4475e16cfafc11f242ce9ab5fc3557d78b5da0d4/scikit_image-0.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7af7aa331c6846bd03fa28b164c18d0c3fd419dbb888fb05e958ac4257a78fdd", size = 12056334 }, + { url = "https://files.pythonhosted.org/packages/9f/1e/bc7fb91fb5ff65ef42346c8b7ee8b09b04eabf89235ab7dbfdfd96cbd1ea/scikit_image-0.26.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ea6207d9e9d21c3f464efe733121c0504e494dbdc7728649ff3e23c3c5a4953", size = 13297768 }, + { url = "https://files.pythonhosted.org/packages/a5/2a/e71c1a7d90e70da67b88ccc609bd6ae54798d5847369b15d3a8052232f9d/scikit_image-0.26.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74aa5518ccea28121f57a95374581d3b979839adc25bb03f289b1bc9b99c58af", size = 13711217 }, + { url = "https://files.pythonhosted.org/packages/d4/59/9637ee12c23726266b91296791465218973ce1ad3e4c56fc81e4d8e7d6e1/scikit_image-0.26.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d5c244656de905e195a904e36dbc18585e06ecf67d90f0482cbde63d7f9ad59d", size = 14337782 }, + { url = "https://files.pythonhosted.org/packages/e7/5c/a3e1e0860f9294663f540c117e4bf83d55e5b47c281d475cc06227e88411/scikit_image-0.26.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21a818ee6ca2f2131b9e04d8eb7637b5c18773ebe7b399ad23dcc5afaa226d2d", size = 14805997 }, + { url = "https://files.pythonhosted.org/packages/d3/c6/2eeacf173da041a9e388975f54e5c49df750757fcfc3ee293cdbbae1ea0a/scikit_image-0.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:9490360c8d3f9a7e85c8de87daf7c0c66507960cf4947bb9610d1751928721c7", size = 11878486 }, + { url = "https://files.pythonhosted.org/packages/c3/a4/a852c4949b9058d585e762a66bf7e9a2cd3be4795cd940413dfbfbb0ce79/scikit_image-0.26.0-cp311-cp311-win_arm64.whl", hash = "sha256:0baa0108d2d027f34d748e84e592b78acc23e965a5de0e4bb03cf371de5c0581", size = 11346518 }, + { url = "https://files.pythonhosted.org/packages/99/e8/e13757982264b33a1621628f86b587e9a73a13f5256dad49b19ba7dc9083/scikit_image-0.26.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d454b93a6fa770ac5ae2d33570f8e7a321bb80d29511ce4b6b78058ebe176e8c", size = 12376452 }, + { url = "https://files.pythonhosted.org/packages/e3/be/f8dd17d0510f9911f9f17ba301f7455328bf13dae416560126d428de9568/scikit_image-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3409e89d66eff5734cd2b672d1c48d2759360057e714e1d92a11df82c87cba37", size = 12061567 }, + { url = "https://files.pythonhosted.org/packages/b3/2b/c70120a6880579fb42b91567ad79feb4772f7be72e8d52fec403a3dde0c6/scikit_image-0.26.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c717490cec9e276afb0438dd165b7c3072d6c416709cc0f9f5a4c1070d23a44", size = 13084214 }, + { url = "https://files.pythonhosted.org/packages/f4/a2/70401a107d6d7466d64b466927e6b96fcefa99d57494b972608e2f8be50f/scikit_image-0.26.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7df650e79031634ac90b11e64a9eedaf5a5e06fcd09bcd03a34be01745744466", size = 13561683 }, + { url = "https://files.pythonhosted.org/packages/13/a5/48bdfd92794c5002d664e0910a349d0a1504671ef5ad358150f21643c79a/scikit_image-0.26.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cefd85033e66d4ea35b525bb0937d7f42d4cdcfed2d1888e1570d5ce450d3932", size = 14112147 }, + { url = "https://files.pythonhosted.org/packages/ee/b5/ac71694da92f5def5953ca99f18a10fe98eac2dd0a34079389b70b4d0394/scikit_image-0.26.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3f5bf622d7c0435884e1e141ebbe4b2804e16b2dd23ae4c6183e2ea99233be70", size = 14661625 }, + { url = "https://files.pythonhosted.org/packages/23/4d/a3cc1e96f080e253dad2251bfae7587cf2b7912bcd76fd43fd366ff35a87/scikit_image-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:abed017474593cd3056ae0fe948d07d0747b27a085e92df5474f4955dd65aec0", size = 11911059 }, + { url = "https://files.pythonhosted.org/packages/35/8a/d1b8055f584acc937478abf4550d122936f420352422a1a625eef2c605d8/scikit_image-0.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:4d57e39ef67a95d26860c8caf9b14b8fb130f83b34c6656a77f191fa6d1d04d8", size = 11348740 }, + { url = "https://files.pythonhosted.org/packages/4f/48/02357ffb2cca35640f33f2cfe054a4d6d5d7a229b88880a64f1e45c11f4e/scikit_image-0.26.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a2e852eccf41d2d322b8e60144e124802873a92b8d43a6f96331aa42888491c7", size = 12346329 }, + { url = "https://files.pythonhosted.org/packages/67/b9/b792c577cea2c1e94cda83b135a656924fc57c428e8a6d302cd69aac1b60/scikit_image-0.26.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:98329aab3bc87db352b9887f64ce8cdb8e75f7c2daa19927f2e121b797b678d5", size = 12031726 }, + { url = "https://files.pythonhosted.org/packages/07/a9/9564250dfd65cb20404a611016db52afc6268b2b371cd19c7538ea47580f/scikit_image-0.26.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:915bb3ba66455cf8adac00dc8fdf18a4cd29656aec7ddd38cb4dda90289a6f21", size = 13094910 }, + { url = "https://files.pythonhosted.org/packages/a3/b8/0d8eeb5a9fd7d34ba84f8a55753a0a3e2b5b51b2a5a0ade648a8db4a62f7/scikit_image-0.26.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b36ab5e778bf50af5ff386c3ac508027dc3aaeccf2161bdf96bde6848f44d21b", size = 13660939 }, + { url = "https://files.pythonhosted.org/packages/2f/d6/91d8973584d4793d4c1a847d388e34ef1218d835eeddecfc9108d735b467/scikit_image-0.26.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:09bad6a5d5949c7896c8347424c4cca899f1d11668030e5548813ab9c2865dcb", size = 14138938 }, + { url = "https://files.pythonhosted.org/packages/39/9a/7e15d8dc10d6bbf212195fb39bdeb7f226c46dd53f9c63c312e111e2e175/scikit_image-0.26.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:aeb14db1ed09ad4bee4ceb9e635547a8d5f3549be67fc6c768c7f923e027e6cd", size = 14752243 }, + { url = "https://files.pythonhosted.org/packages/8f/58/2b11b933097bc427e42b4a8b15f7de8f24f2bac1fd2779d2aea1431b2c31/scikit_image-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:ac529eb9dbd5954f9aaa2e3fe9a3fd9661bfe24e134c688587d811a0233127f1", size = 11906770 }, + { url = "https://files.pythonhosted.org/packages/ad/ec/96941474a18a04b69b6f6562a5bd79bd68049fa3728d3b350976eccb8b93/scikit_image-0.26.0-cp313-cp313-win_arm64.whl", hash = "sha256:a2d211bc355f59725efdcae699b93b30348a19416cc9e017f7b2fb599faf7219", size = 11342506 }, + { url = "https://files.pythonhosted.org/packages/03/e5/c1a9962b0cf1952f42d32b4a2e48eed520320dbc4d2ff0b981c6fa508b6b/scikit_image-0.26.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9eefb4adad066da408a7601c4c24b07af3b472d90e08c3e7483d4e9e829d8c49", size = 12663278 }, + { url = "https://files.pythonhosted.org/packages/ae/97/c1a276a59ce8e4e24482d65c1a3940d69c6b3873279193b7ebd04e5ee56b/scikit_image-0.26.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6caec76e16c970c528d15d1c757363334d5cb3069f9cea93d2bead31820511f3", size = 12405142 }, + { url = "https://files.pythonhosted.org/packages/d4/4a/f1cbd1357caef6c7993f7efd514d6e53d8fd6f7fe01c4714d51614c53289/scikit_image-0.26.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a07200fe09b9d99fcdab959859fe0f7db8df6333d6204344425d476850ce3604", size = 12942086 }, + { url = "https://files.pythonhosted.org/packages/5b/6f/74d9fb87c5655bd64cf00b0c44dc3d6206d9002e5f6ba1c9aeb13236f6bf/scikit_image-0.26.0-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92242351bccf391fc5df2d1529d15470019496d2498d615beb68da85fe7fdf37", size = 13265667 }, + { url = "https://files.pythonhosted.org/packages/a7/73/faddc2413ae98d863f6fa2e3e14da4467dd38e788e1c23346cf1a2b06b97/scikit_image-0.26.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:52c496f75a7e45844d951557f13c08c81487c6a1da2e3c9c8a39fcde958e02cc", size = 14001966 }, + { url = "https://files.pythonhosted.org/packages/02/94/9f46966fa042b5d57c8cd641045372b4e0df0047dd400e77ea9952674110/scikit_image-0.26.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:20ef4a155e2e78b8ab973998e04d8a361d49d719e65412405f4dadd9155a61d9", size = 14359526 }, + { url = "https://files.pythonhosted.org/packages/5d/b4/2840fe38f10057f40b1c9f8fb98a187a370936bf144a4ac23452c5ef1baf/scikit_image-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c9087cf7d0e7f33ab5c46d2068d86d785e70b05400a891f73a13400f1e1faf6a", size = 12287629 }, + { url = "https://files.pythonhosted.org/packages/22/ba/73b6ca70796e71f83ab222690e35a79612f0117e5aaf167151b7d46f5f2c/scikit_image-0.26.0-cp313-cp313t-win_arm64.whl", hash = "sha256:27d58bc8b2acd351f972c6508c1b557cfed80299826080a4d803dd29c51b707e", size = 11647755 }, + { url = "https://files.pythonhosted.org/packages/51/44/6b744f92b37ae2833fd423cce8f806d2368859ec325a699dc30389e090b9/scikit_image-0.26.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:63af3d3a26125f796f01052052f86806da5b5e54c6abef152edb752683075a9c", size = 12365810 }, + { url = "https://files.pythonhosted.org/packages/40/f5/83590d9355191f86ac663420fec741b82cc547a4afe7c4c1d986bf46e4db/scikit_image-0.26.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ce00600cd70d4562ed59f80523e18cdcc1fae0e10676498a01f73c255774aefd", size = 12075717 }, + { url = "https://files.pythonhosted.org/packages/72/48/253e7cf5aee6190459fe136c614e2cbccc562deceb4af96e0863f1b8ee29/scikit_image-0.26.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6381edf972b32e4f54085449afde64365a57316637496c1325a736987083e2ab", size = 13161520 }, + { url = "https://files.pythonhosted.org/packages/73/c3/cec6a3cbaadfdcc02bd6ff02f3abfe09eaa7f4d4e0a525a1e3a3f4bce49c/scikit_image-0.26.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6624a76c6085218248154cc7e1500e6b488edcd9499004dd0d35040607d7505", size = 13684340 }, + { url = "https://files.pythonhosted.org/packages/d4/0d/39a776f675d24164b3a267aa0db9f677a4cb20127660d8bf4fd7fef66817/scikit_image-0.26.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f775f0e420faac9c2aa6757135f4eb468fb7b70e0b67fa77a5e79be3c30ee331", size = 14203839 }, + { url = "https://files.pythonhosted.org/packages/ee/25/2514df226bbcedfe9b2caafa1ba7bc87231a0c339066981b182b08340e06/scikit_image-0.26.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede4d6d255cc5da9faeb2f9ba7fedbc990abbc652db429f40a16b22e770bb578", size = 14770021 }, + { url = "https://files.pythonhosted.org/packages/8d/5b/0671dc91c0c79340c3fe202f0549c7d3681eb7640fe34ab68a5f090a7c7f/scikit_image-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:0660b83968c15293fd9135e8d860053ee19500d52bf55ca4fb09de595a1af650", size = 12023490 }, + { url = "https://files.pythonhosted.org/packages/65/08/7c4cb59f91721f3de07719085212a0b3962e3e3f2d1818cbac4eeb1ea53e/scikit_image-0.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:b8d14d3181c21c11170477a42542c1addc7072a90b986675a71266ad17abc37f", size = 11473782 }, + { url = "https://files.pythonhosted.org/packages/49/41/65c4258137acef3d73cb561ac55512eacd7b30bb4f4a11474cad526bc5db/scikit_image-0.26.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:cde0bbd57e6795eba83cb10f71a677f7239271121dc950bc060482834a668ad1", size = 12686060 }, + { url = "https://files.pythonhosted.org/packages/e7/32/76971f8727b87f1420a962406388a50e26667c31756126444baf6668f559/scikit_image-0.26.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:163e9afb5b879562b9aeda0dd45208a35316f26cc7a3aed54fd601604e5cf46f", size = 12422628 }, + { url = "https://files.pythonhosted.org/packages/37/0d/996febd39f757c40ee7b01cdb861867327e5c8e5f595a634e8201462d958/scikit_image-0.26.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724f79fd9b6cb6f4a37864fe09f81f9f5d5b9646b6868109e1b100d1a7019e59", size = 12962369 }, + { url = "https://files.pythonhosted.org/packages/48/b4/612d354f946c9600e7dea012723c11d47e8d455384e530f6daaaeb9bf62c/scikit_image-0.26.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3268f13310e6857508bd87202620df996199a016a1d281b309441d227c822394", size = 13272431 }, + { url = "https://files.pythonhosted.org/packages/0a/6e/26c00b466e06055a086de2c6e2145fe189ccdc9a1d11ccc7de020f2591ad/scikit_image-0.26.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fac96a1f9b06cd771cbbb3cd96c5332f36d4efd839b1d8b053f79e5887acde62", size = 14016362 }, + { url = "https://files.pythonhosted.org/packages/47/88/00a90402e1775634043c2a0af8a3c76ad450866d9fa444efcc43b553ba2d/scikit_image-0.26.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2c1e7bd342f43e7a97e571b3f03ba4c1293ea1a35c3f13f41efdc8a81c1dc8f2", size = 14364151 }, + { url = "https://files.pythonhosted.org/packages/da/ca/918d8d306bd43beacff3b835c6d96fac0ae64c0857092f068b88db531a7c/scikit_image-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b702c3bb115e1dcf4abf5297429b5c90f2189655888cbed14921f3d26f81d3a4", size = 12413484 }, + { url = "https://files.pythonhosted.org/packages/dc/cd/4da01329b5a8d47ff7ec3c99a2b02465a8017b186027590dc7425cee0b56/scikit_image-0.26.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0608aa4a9ec39e0843de10d60edb2785a30c1c47819b67866dd223ebd149acaf", size = 11769501 }, ] [[package]] @@ -3107,104 +3174,104 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:1f95b894f13729334fb990162e911c9e5dc1ab390c58aa6cbecb389c5b5e28ec", size = 31613675, upload-time = "2026-02-23T00:16:00.13Z" }, - { url = "https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:e18f12c6b0bc5a592ed23d3f7b891f68fd7f8241d69b7883769eb5d5dfb52696", size = 28162057, upload-time = "2026-02-23T00:16:09.456Z" }, - { url = "https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee", size = 20334032, upload-time = "2026-02-23T00:16:17.358Z" }, - { url = "https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:766e0dc5a616d026a3a1cffa379af959671729083882f50307e18175797b3dfd", size = 22709533, upload-time = "2026-02-23T00:16:25.791Z" }, - { url = "https://files.pythonhosted.org/packages/4d/60/8804678875fc59362b0fb759ab3ecce1f09c10a735680318ac30da8cd76b/scipy-1.17.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:744b2bf3640d907b79f3fd7874efe432d1cf171ee721243e350f55234b4cec4c", size = 33062057, upload-time = "2026-02-23T00:16:36.931Z" }, - { url = "https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43af8d1f3bea642559019edfe64e9b11192a8978efbd1539d7bc2aaa23d92de4", size = 35349300, upload-time = "2026-02-23T00:16:49.108Z" }, - { url = "https://files.pythonhosted.org/packages/b4/3d/7ccbbdcbb54c8fdc20d3b6930137c782a163fa626f0aef920349873421ba/scipy-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd96a1898c0a47be4520327e01f874acfd61fb48a9420f8aa9f6483412ffa444", size = 35127333, upload-time = "2026-02-23T00:17:01.293Z" }, - { url = "https://files.pythonhosted.org/packages/e8/19/f926cb11c42b15ba08e3a71e376d816ac08614f769b4f47e06c3580c836a/scipy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4eb6c25dd62ee8d5edf68a8e1c171dd71c292fdae95d8aeb3dd7d7de4c364082", size = 37741314, upload-time = "2026-02-23T00:17:12.576Z" }, - { url = "https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff", size = 36607512, upload-time = "2026-02-23T00:17:23.424Z" }, - { url = "https://files.pythonhosted.org/packages/68/7f/bdd79ceaad24b671543ffe0ef61ed8e659440eb683b66f033454dcee90eb/scipy-1.17.1-cp311-cp311-win_arm64.whl", hash = "sha256:9ecb4efb1cd6e8c4afea0daa91a87fbddbce1b99d2895d151596716c0b2e859d", size = 24599248, upload-time = "2026-02-23T00:17:34.561Z" }, - { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, - { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, - { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, - { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, - { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, - { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, - { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, - { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, - { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, - { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, - { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z" }, - { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z" }, - { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z" }, - { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z" }, - { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z" }, - { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z" }, - { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z" }, - { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z" }, - { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z" }, - { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z" }, - { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z" }, - { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z" }, - { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z" }, - { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z" }, - { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z" }, - { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z" }, - { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z" }, - { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z" }, - { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z" }, - { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z" }, - { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510, upload-time = "2026-02-23T00:21:01.015Z" }, - { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131, upload-time = "2026-02-23T00:21:05.888Z" }, - { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032, upload-time = "2026-02-23T00:21:09.904Z" }, - { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766, upload-time = "2026-02-23T00:21:14.313Z" }, - { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007, upload-time = "2026-02-23T00:21:19.663Z" }, - { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333, upload-time = "2026-02-23T00:21:25.278Z" }, - { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066, upload-time = "2026-02-23T00:21:31.358Z" }, - { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763, upload-time = "2026-02-23T00:21:37.247Z" }, - { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984, upload-time = "2026-02-23T00:22:35.023Z" }, - { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877, upload-time = "2026-02-23T00:22:39.798Z" }, - { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750, upload-time = "2026-02-23T00:21:42.289Z" }, - { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858, upload-time = "2026-02-23T00:21:47.706Z" }, - { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723, upload-time = "2026-02-23T00:21:52.039Z" }, - { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098, upload-time = "2026-02-23T00:21:56.185Z" }, - { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397, upload-time = "2026-02-23T00:22:01.404Z" }, - { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163, upload-time = "2026-02-23T00:22:07.024Z" }, - { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291, upload-time = "2026-02-23T00:22:12.585Z" }, - { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317, upload-time = "2026-02-23T00:22:18.513Z" }, - { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327, upload-time = "2026-02-23T00:22:24.442Z" }, - { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:1f95b894f13729334fb990162e911c9e5dc1ab390c58aa6cbecb389c5b5e28ec", size = 31613675 }, + { url = "https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:e18f12c6b0bc5a592ed23d3f7b891f68fd7f8241d69b7883769eb5d5dfb52696", size = 28162057 }, + { url = "https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee", size = 20334032 }, + { url = "https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:766e0dc5a616d026a3a1cffa379af959671729083882f50307e18175797b3dfd", size = 22709533 }, + { url = "https://files.pythonhosted.org/packages/4d/60/8804678875fc59362b0fb759ab3ecce1f09c10a735680318ac30da8cd76b/scipy-1.17.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:744b2bf3640d907b79f3fd7874efe432d1cf171ee721243e350f55234b4cec4c", size = 33062057 }, + { url = "https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43af8d1f3bea642559019edfe64e9b11192a8978efbd1539d7bc2aaa23d92de4", size = 35349300 }, + { url = "https://files.pythonhosted.org/packages/b4/3d/7ccbbdcbb54c8fdc20d3b6930137c782a163fa626f0aef920349873421ba/scipy-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd96a1898c0a47be4520327e01f874acfd61fb48a9420f8aa9f6483412ffa444", size = 35127333 }, + { url = "https://files.pythonhosted.org/packages/e8/19/f926cb11c42b15ba08e3a71e376d816ac08614f769b4f47e06c3580c836a/scipy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4eb6c25dd62ee8d5edf68a8e1c171dd71c292fdae95d8aeb3dd7d7de4c364082", size = 37741314 }, + { url = "https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff", size = 36607512 }, + { url = "https://files.pythonhosted.org/packages/68/7f/bdd79ceaad24b671543ffe0ef61ed8e659440eb683b66f033454dcee90eb/scipy-1.17.1-cp311-cp311-win_arm64.whl", hash = "sha256:9ecb4efb1cd6e8c4afea0daa91a87fbddbce1b99d2895d151596716c0b2e859d", size = 24599248 }, + { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954 }, + { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662 }, + { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366 }, + { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017 }, + { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842 }, + { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890 }, + { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557 }, + { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856 }, + { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682 }, + { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340 }, + { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199 }, + { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001 }, + { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719 }, + { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595 }, + { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429 }, + { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952 }, + { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063 }, + { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449 }, + { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943 }, + { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621 }, + { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708 }, + { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135 }, + { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977 }, + { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601 }, + { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667 }, + { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159 }, + { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771 }, + { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910 }, + { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980 }, + { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543 }, + { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510 }, + { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131 }, + { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032 }, + { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766 }, + { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007 }, + { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333 }, + { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066 }, + { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763 }, + { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984 }, + { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877 }, + { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750 }, + { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858 }, + { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723 }, + { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098 }, + { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397 }, + { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163 }, + { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291 }, + { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317 }, + { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327 }, + { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165 }, ] [[package]] name = "send2trash" version = "2.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c5/f0/184b4b5f8d00f2a92cf96eec8967a3d550b52cf94362dad1100df9e48d57/send2trash-2.1.0.tar.gz", hash = "sha256:1c72b39f09457db3c05ce1d19158c2cbef4c32b8bedd02c155e49282b7ea7459", size = 17255, upload-time = "2026-01-14T06:27:36.056Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/f0/184b4b5f8d00f2a92cf96eec8967a3d550b52cf94362dad1100df9e48d57/send2trash-2.1.0.tar.gz", hash = "sha256:1c72b39f09457db3c05ce1d19158c2cbef4c32b8bedd02c155e49282b7ea7459", size = 17255 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/78/504fdd027da3b84ff1aecd9f6957e65f35134534ccc6da8628eb71e76d3f/send2trash-2.1.0-py3-none-any.whl", hash = "sha256:0da2f112e6d6bb22de6aa6daa7e144831a4febf2a87261451c4ad849fe9a873c", size = 17610, upload-time = "2026-01-14T06:27:35.218Z" }, + { url = "https://files.pythonhosted.org/packages/1c/78/504fdd027da3b84ff1aecd9f6957e65f35134534ccc6da8628eb71e76d3f/send2trash-2.1.0-py3-none-any.whl", hash = "sha256:0da2f112e6d6bb22de6aa6daa7e144831a4febf2a87261451c4ad849fe9a873c", size = 17610 }, ] [[package]] name = "setuptools" -version = "82.0.0" +version = "81.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/f3/748f4d6f65d1756b9ae577f329c951cda23fb900e4de9f70900ced962085/setuptools-82.0.0.tar.gz", hash = "sha256:22e0a2d69474c6ae4feb01951cb69d515ed23728cf96d05513d36e42b62b37cb", size = 1144893, upload-time = "2026-02-08T15:08:40.206Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/1c/73e719955c59b8e424d015ab450f51c0af856ae46ea2da83eba51cc88de1/setuptools-81.0.0.tar.gz", hash = "sha256:487b53915f52501f0a79ccfd0c02c165ffe06631443a886740b91af4b7a5845a", size = 1198299 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl", hash = "sha256:70b18734b607bd1da571d097d236cfcfacaf01de45717d59e6e04b96877532e0", size = 1003468, upload-time = "2026-02-08T15:08:38.723Z" }, + { url = "https://files.pythonhosted.org/packages/e1/e3/c164c88b2e5ce7b24d667b9bd83589cf4f3520d97cad01534cd3c4f55fdb/setuptools-81.0.0-py3-none-any.whl", hash = "sha256:fdd925d5c5d9f62e4b74b30d6dd7828ce236fd6ed998a08d81de62ce5a6310d6", size = 1062021 }, ] [[package]] name = "six" version = "1.17.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +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, upload-time = "2024-12-04T17:35:26.475Z" }, + { 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 = "soupsieve" version = "2.8.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627 } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, + { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016 }, ] [[package]] @@ -3215,45 +3282,45 @@ dependencies = [ { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/aa/9ce0f3e7a9829ead5c8ce549392f33a12c4555a6c0609bb27d882e9c7ddf/sqlalchemy-2.0.46.tar.gz", hash = "sha256:cf36851ee7219c170bb0793dbc3da3e80c582e04a5437bc601bfe8c85c9216d7", size = 9865393, upload-time = "2026-01-21T18:03:45.119Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/69/ac/b42ad16800d0885105b59380ad69aad0cce5a65276e269ce2729a2343b6a/sqlalchemy-2.0.46-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:261c4b1f101b4a411154f1da2b76497d73abbfc42740029205d4d01fa1052684", size = 2154851, upload-time = "2026-01-21T18:27:30.54Z" }, - { url = "https://files.pythonhosted.org/packages/a0/60/d8710068cb79f64d002ebed62a7263c00c8fd95f4ebd4b5be8f7ca93f2bc/sqlalchemy-2.0.46-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:181903fe8c1b9082995325f1b2e84ac078b1189e2819380c2303a5f90e114a62", size = 3311241, upload-time = "2026-01-21T18:32:33.45Z" }, - { url = "https://files.pythonhosted.org/packages/2b/0f/20c71487c7219ab3aa7421c7c62d93824c97c1460f2e8bb72404b0192d13/sqlalchemy-2.0.46-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:590be24e20e2424a4c3c1b0835e9405fa3d0af5823a1a9fc02e5dff56471515f", size = 3310741, upload-time = "2026-01-21T18:44:57.887Z" }, - { url = "https://files.pythonhosted.org/packages/65/80/d26d00b3b249ae000eee4db206fcfc564bf6ca5030e4747adf451f4b5108/sqlalchemy-2.0.46-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7568fe771f974abadce52669ef3a03150ff03186d8eb82613bc8adc435a03f01", size = 3263116, upload-time = "2026-01-21T18:32:35.044Z" }, - { url = "https://files.pythonhosted.org/packages/da/ee/74dda7506640923821340541e8e45bd3edd8df78664f1f2e0aae8077192b/sqlalchemy-2.0.46-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf7e1e78af38047e08836d33502c7a278915698b7c2145d045f780201679999", size = 3285327, upload-time = "2026-01-21T18:44:59.254Z" }, - { url = "https://files.pythonhosted.org/packages/9f/25/6dcf8abafff1389a21c7185364de145107b7394ecdcb05233815b236330d/sqlalchemy-2.0.46-cp311-cp311-win32.whl", hash = "sha256:9d80ea2ac519c364a7286e8d765d6cd08648f5b21ca855a8017d9871f075542d", size = 2114564, upload-time = "2026-01-21T18:33:15.85Z" }, - { url = "https://files.pythonhosted.org/packages/93/5f/e081490f8523adc0088f777e4ebad3cac21e498ec8a3d4067074e21447a1/sqlalchemy-2.0.46-cp311-cp311-win_amd64.whl", hash = "sha256:585af6afe518732d9ccd3aea33af2edaae4a7aa881af5d8f6f4fe3a368699597", size = 2139233, upload-time = "2026-01-21T18:33:17.528Z" }, - { url = "https://files.pythonhosted.org/packages/b6/35/d16bfa235c8b7caba3730bba43e20b1e376d2224f407c178fbf59559f23e/sqlalchemy-2.0.46-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3a9a72b0da8387f15d5810f1facca8f879de9b85af8c645138cba61ea147968c", size = 2153405, upload-time = "2026-01-21T19:05:54.143Z" }, - { url = "https://files.pythonhosted.org/packages/06/6c/3192e24486749862f495ddc6584ed730c0c994a67550ec395d872a2ad650/sqlalchemy-2.0.46-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2347c3f0efc4de367ba00218e0ae5c4ba2306e47216ef80d6e31761ac97cb0b9", size = 3334702, upload-time = "2026-01-21T18:46:45.384Z" }, - { url = "https://files.pythonhosted.org/packages/ea/a2/b9f33c8d68a3747d972a0bb758c6b63691f8fb8a49014bc3379ba15d4274/sqlalchemy-2.0.46-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9094c8b3197db12aa6f05c51c05daaad0a92b8c9af5388569847b03b1007fb1b", size = 3347664, upload-time = "2026-01-21T18:40:09.979Z" }, - { url = "https://files.pythonhosted.org/packages/aa/d2/3e59e2a91eaec9db7e8dc6b37b91489b5caeb054f670f32c95bcba98940f/sqlalchemy-2.0.46-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37fee2164cf21417478b6a906adc1a91d69ae9aba8f9533e67ce882f4bb1de53", size = 3277372, upload-time = "2026-01-21T18:46:47.168Z" }, - { url = "https://files.pythonhosted.org/packages/dd/dd/67bc2e368b524e2192c3927b423798deda72c003e73a1e94c21e74b20a85/sqlalchemy-2.0.46-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b1e14b2f6965a685c7128bd315e27387205429c2e339eeec55cb75ca4ab0ea2e", size = 3312425, upload-time = "2026-01-21T18:40:11.548Z" }, - { url = "https://files.pythonhosted.org/packages/43/82/0ecd68e172bfe62247e96cb47867c2d68752566811a4e8c9d8f6e7c38a65/sqlalchemy-2.0.46-cp312-cp312-win32.whl", hash = "sha256:412f26bb4ba942d52016edc8d12fb15d91d3cd46b0047ba46e424213ad407bcb", size = 2113155, upload-time = "2026-01-21T18:42:49.748Z" }, - { url = "https://files.pythonhosted.org/packages/bc/2a/2821a45742073fc0331dc132552b30de68ba9563230853437cac54b2b53e/sqlalchemy-2.0.46-cp312-cp312-win_amd64.whl", hash = "sha256:ea3cd46b6713a10216323cda3333514944e510aa691c945334713fca6b5279ff", size = 2140078, upload-time = "2026-01-21T18:42:51.197Z" }, - { url = "https://files.pythonhosted.org/packages/b3/4b/fa7838fe20bb752810feed60e45625a9a8b0102c0c09971e2d1d95362992/sqlalchemy-2.0.46-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:93a12da97cca70cea10d4b4fc602589c4511f96c1f8f6c11817620c021d21d00", size = 2150268, upload-time = "2026-01-21T19:05:56.621Z" }, - { url = "https://files.pythonhosted.org/packages/46/c1/b34dccd712e8ea846edf396e00973dda82d598cb93762e55e43e6835eba9/sqlalchemy-2.0.46-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af865c18752d416798dae13f83f38927c52f085c52e2f32b8ab0fef46fdd02c2", size = 3276511, upload-time = "2026-01-21T18:46:49.022Z" }, - { url = "https://files.pythonhosted.org/packages/96/48/a04d9c94753e5d5d096c628c82a98c4793b9c08ca0e7155c3eb7d7db9f24/sqlalchemy-2.0.46-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8d679b5f318423eacb61f933a9a0f75535bfca7056daeadbf6bd5bcee6183aee", size = 3292881, upload-time = "2026-01-21T18:40:13.089Z" }, - { url = "https://files.pythonhosted.org/packages/be/f4/06eda6e91476f90a7d8058f74311cb65a2fb68d988171aced81707189131/sqlalchemy-2.0.46-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64901e08c33462acc9ec3bad27fc7a5c2b6491665f2aa57564e57a4f5d7c52ad", size = 3224559, upload-time = "2026-01-21T18:46:50.974Z" }, - { url = "https://files.pythonhosted.org/packages/ab/a2/d2af04095412ca6345ac22b33b89fe8d6f32a481e613ffcb2377d931d8d0/sqlalchemy-2.0.46-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e8ac45e8f4eaac0f9f8043ea0e224158855c6a4329fd4ee37c45c61e3beb518e", size = 3262728, upload-time = "2026-01-21T18:40:14.883Z" }, - { url = "https://files.pythonhosted.org/packages/31/48/1980c7caa5978a3b8225b4d230e69a2a6538a3562b8b31cea679b6933c83/sqlalchemy-2.0.46-cp313-cp313-win32.whl", hash = "sha256:8d3b44b3d0ab2f1319d71d9863d76eeb46766f8cf9e921ac293511804d39813f", size = 2111295, upload-time = "2026-01-21T18:42:52.366Z" }, - { url = "https://files.pythonhosted.org/packages/2d/54/f8d65bbde3d877617c4720f3c9f60e99bb7266df0d5d78b6e25e7c149f35/sqlalchemy-2.0.46-cp313-cp313-win_amd64.whl", hash = "sha256:77f8071d8fbcbb2dd11b7fd40dedd04e8ebe2eb80497916efedba844298065ef", size = 2137076, upload-time = "2026-01-21T18:42:53.924Z" }, - { url = "https://files.pythonhosted.org/packages/56/ba/9be4f97c7eb2b9d5544f2624adfc2853e796ed51d2bb8aec90bc94b7137e/sqlalchemy-2.0.46-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1e8cc6cc01da346dc92d9509a63033b9b1bda4fed7a7a7807ed385c7dccdc10", size = 3556533, upload-time = "2026-01-21T18:33:06.636Z" }, - { url = "https://files.pythonhosted.org/packages/20/a6/b1fc6634564dbb4415b7ed6419cdfeaadefd2c39cdab1e3aa07a5f2474c2/sqlalchemy-2.0.46-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:96c7cca1a4babaaf3bfff3e4e606e38578856917e52f0384635a95b226c87764", size = 3523208, upload-time = "2026-01-21T18:45:08.436Z" }, - { url = "https://files.pythonhosted.org/packages/a1/d8/41e0bdfc0f930ff236f86fccd12962d8fa03713f17ed57332d38af6a3782/sqlalchemy-2.0.46-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b2a9f9aee38039cf4755891a1e50e1effcc42ea6ba053743f452c372c3152b1b", size = 3464292, upload-time = "2026-01-21T18:33:08.208Z" }, - { url = "https://files.pythonhosted.org/packages/f0/8b/9dcbec62d95bea85f5ecad9b8d65b78cc30fb0ffceeb3597961f3712549b/sqlalchemy-2.0.46-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:db23b1bf8cfe1f7fda19018e7207b20cdb5168f83c437ff7e95d19e39289c447", size = 3473497, upload-time = "2026-01-21T18:45:10.552Z" }, - { url = "https://files.pythonhosted.org/packages/e9/f8/5ecdfc73383ec496de038ed1614de9e740a82db9ad67e6e4514ebc0708a3/sqlalchemy-2.0.46-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:56bdd261bfd0895452006d5316cbf35739c53b9bb71a170a331fa0ea560b2ada", size = 2152079, upload-time = "2026-01-21T19:05:58.477Z" }, - { url = "https://files.pythonhosted.org/packages/e5/bf/eba3036be7663ce4d9c050bc3d63794dc29fbe01691f2bf5ccb64e048d20/sqlalchemy-2.0.46-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33e462154edb9493f6c3ad2125931e273bbd0be8ae53f3ecd1c161ea9a1dd366", size = 3272216, upload-time = "2026-01-21T18:46:52.634Z" }, - { url = "https://files.pythonhosted.org/packages/05/45/1256fb597bb83b58a01ddb600c59fe6fdf0e5afe333f0456ed75c0f8d7bd/sqlalchemy-2.0.46-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bcdce05f056622a632f1d44bb47dbdb677f58cad393612280406ce37530eb6d", size = 3277208, upload-time = "2026-01-21T18:40:16.38Z" }, - { url = "https://files.pythonhosted.org/packages/d9/a0/2053b39e4e63b5d7ceb3372cface0859a067c1ddbd575ea7e9985716f771/sqlalchemy-2.0.46-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e84b09a9b0f19accedcbeff5c2caf36e0dd537341a33aad8d680336152dc34e", size = 3221994, upload-time = "2026-01-21T18:46:54.622Z" }, - { url = "https://files.pythonhosted.org/packages/1e/87/97713497d9502553c68f105a1cb62786ba1ee91dea3852ae4067ed956a50/sqlalchemy-2.0.46-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4f52f7291a92381e9b4de9050b0a65ce5d6a763333406861e33906b8aa4906bf", size = 3243990, upload-time = "2026-01-21T18:40:18.253Z" }, - { url = "https://files.pythonhosted.org/packages/a8/87/5d1b23548f420ff823c236f8bea36b1a997250fd2f892e44a3838ca424f4/sqlalchemy-2.0.46-cp314-cp314-win32.whl", hash = "sha256:70ed2830b169a9960193f4d4322d22be5c0925357d82cbf485b3369893350908", size = 2114215, upload-time = "2026-01-21T18:42:55.232Z" }, - { url = "https://files.pythonhosted.org/packages/3a/20/555f39cbcf0c10cf452988b6a93c2a12495035f68b3dbd1a408531049d31/sqlalchemy-2.0.46-cp314-cp314-win_amd64.whl", hash = "sha256:3c32e993bc57be6d177f7d5d31edb93f30726d798ad86ff9066d75d9bf2e0b6b", size = 2139867, upload-time = "2026-01-21T18:42:56.474Z" }, - { url = "https://files.pythonhosted.org/packages/3e/f0/f96c8057c982d9d8a7a68f45d69c674bc6f78cad401099692fe16521640a/sqlalchemy-2.0.46-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4dafb537740eef640c4d6a7c254611dca2df87eaf6d14d6a5fca9d1f4c3fc0fa", size = 3561202, upload-time = "2026-01-21T18:33:10.337Z" }, - { url = "https://files.pythonhosted.org/packages/d7/53/3b37dda0a5b137f21ef608d8dfc77b08477bab0fe2ac9d3e0a66eaeab6fc/sqlalchemy-2.0.46-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42a1643dc5427b69aca967dae540a90b0fbf57eaf248f13a90ea5930e0966863", size = 3526296, upload-time = "2026-01-21T18:45:12.657Z" }, - { url = "https://files.pythonhosted.org/packages/33/75/f28622ba6dde79cd545055ea7bd4062dc934e0621f7b3be2891f8563f8de/sqlalchemy-2.0.46-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ff33c6e6ad006bbc0f34f5faf941cfc62c45841c64c0a058ac38c799f15b5ede", size = 3470008, upload-time = "2026-01-21T18:33:11.725Z" }, - { url = "https://files.pythonhosted.org/packages/a9/42/4afecbbc38d5e99b18acef446453c76eec6fbd03db0a457a12a056836e22/sqlalchemy-2.0.46-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:82ec52100ec1e6ec671563bbd02d7c7c8d0b9e71a0723c72f22ecf52d1755330", size = 3476137, upload-time = "2026-01-21T18:45:15.001Z" }, - { url = "https://files.pythonhosted.org/packages/fc/a1/9c4efa03300926601c19c18582531b45aededfb961ab3c3585f1e24f120b/sqlalchemy-2.0.46-py3-none-any.whl", hash = "sha256:f9c11766e7e7c0a2767dda5acb006a118640c9fc0a4104214b96269bfb78399e", size = 1937882, upload-time = "2026-01-21T18:22:10.456Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/06/aa/9ce0f3e7a9829ead5c8ce549392f33a12c4555a6c0609bb27d882e9c7ddf/sqlalchemy-2.0.46.tar.gz", hash = "sha256:cf36851ee7219c170bb0793dbc3da3e80c582e04a5437bc601bfe8c85c9216d7", size = 9865393 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/ac/b42ad16800d0885105b59380ad69aad0cce5a65276e269ce2729a2343b6a/sqlalchemy-2.0.46-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:261c4b1f101b4a411154f1da2b76497d73abbfc42740029205d4d01fa1052684", size = 2154851 }, + { url = "https://files.pythonhosted.org/packages/a0/60/d8710068cb79f64d002ebed62a7263c00c8fd95f4ebd4b5be8f7ca93f2bc/sqlalchemy-2.0.46-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:181903fe8c1b9082995325f1b2e84ac078b1189e2819380c2303a5f90e114a62", size = 3311241 }, + { url = "https://files.pythonhosted.org/packages/2b/0f/20c71487c7219ab3aa7421c7c62d93824c97c1460f2e8bb72404b0192d13/sqlalchemy-2.0.46-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:590be24e20e2424a4c3c1b0835e9405fa3d0af5823a1a9fc02e5dff56471515f", size = 3310741 }, + { url = "https://files.pythonhosted.org/packages/65/80/d26d00b3b249ae000eee4db206fcfc564bf6ca5030e4747adf451f4b5108/sqlalchemy-2.0.46-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7568fe771f974abadce52669ef3a03150ff03186d8eb82613bc8adc435a03f01", size = 3263116 }, + { url = "https://files.pythonhosted.org/packages/da/ee/74dda7506640923821340541e8e45bd3edd8df78664f1f2e0aae8077192b/sqlalchemy-2.0.46-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf7e1e78af38047e08836d33502c7a278915698b7c2145d045f780201679999", size = 3285327 }, + { url = "https://files.pythonhosted.org/packages/9f/25/6dcf8abafff1389a21c7185364de145107b7394ecdcb05233815b236330d/sqlalchemy-2.0.46-cp311-cp311-win32.whl", hash = "sha256:9d80ea2ac519c364a7286e8d765d6cd08648f5b21ca855a8017d9871f075542d", size = 2114564 }, + { url = "https://files.pythonhosted.org/packages/93/5f/e081490f8523adc0088f777e4ebad3cac21e498ec8a3d4067074e21447a1/sqlalchemy-2.0.46-cp311-cp311-win_amd64.whl", hash = "sha256:585af6afe518732d9ccd3aea33af2edaae4a7aa881af5d8f6f4fe3a368699597", size = 2139233 }, + { url = "https://files.pythonhosted.org/packages/b6/35/d16bfa235c8b7caba3730bba43e20b1e376d2224f407c178fbf59559f23e/sqlalchemy-2.0.46-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3a9a72b0da8387f15d5810f1facca8f879de9b85af8c645138cba61ea147968c", size = 2153405 }, + { url = "https://files.pythonhosted.org/packages/06/6c/3192e24486749862f495ddc6584ed730c0c994a67550ec395d872a2ad650/sqlalchemy-2.0.46-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2347c3f0efc4de367ba00218e0ae5c4ba2306e47216ef80d6e31761ac97cb0b9", size = 3334702 }, + { url = "https://files.pythonhosted.org/packages/ea/a2/b9f33c8d68a3747d972a0bb758c6b63691f8fb8a49014bc3379ba15d4274/sqlalchemy-2.0.46-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9094c8b3197db12aa6f05c51c05daaad0a92b8c9af5388569847b03b1007fb1b", size = 3347664 }, + { url = "https://files.pythonhosted.org/packages/aa/d2/3e59e2a91eaec9db7e8dc6b37b91489b5caeb054f670f32c95bcba98940f/sqlalchemy-2.0.46-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37fee2164cf21417478b6a906adc1a91d69ae9aba8f9533e67ce882f4bb1de53", size = 3277372 }, + { url = "https://files.pythonhosted.org/packages/dd/dd/67bc2e368b524e2192c3927b423798deda72c003e73a1e94c21e74b20a85/sqlalchemy-2.0.46-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b1e14b2f6965a685c7128bd315e27387205429c2e339eeec55cb75ca4ab0ea2e", size = 3312425 }, + { url = "https://files.pythonhosted.org/packages/43/82/0ecd68e172bfe62247e96cb47867c2d68752566811a4e8c9d8f6e7c38a65/sqlalchemy-2.0.46-cp312-cp312-win32.whl", hash = "sha256:412f26bb4ba942d52016edc8d12fb15d91d3cd46b0047ba46e424213ad407bcb", size = 2113155 }, + { url = "https://files.pythonhosted.org/packages/bc/2a/2821a45742073fc0331dc132552b30de68ba9563230853437cac54b2b53e/sqlalchemy-2.0.46-cp312-cp312-win_amd64.whl", hash = "sha256:ea3cd46b6713a10216323cda3333514944e510aa691c945334713fca6b5279ff", size = 2140078 }, + { url = "https://files.pythonhosted.org/packages/b3/4b/fa7838fe20bb752810feed60e45625a9a8b0102c0c09971e2d1d95362992/sqlalchemy-2.0.46-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:93a12da97cca70cea10d4b4fc602589c4511f96c1f8f6c11817620c021d21d00", size = 2150268 }, + { url = "https://files.pythonhosted.org/packages/46/c1/b34dccd712e8ea846edf396e00973dda82d598cb93762e55e43e6835eba9/sqlalchemy-2.0.46-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af865c18752d416798dae13f83f38927c52f085c52e2f32b8ab0fef46fdd02c2", size = 3276511 }, + { url = "https://files.pythonhosted.org/packages/96/48/a04d9c94753e5d5d096c628c82a98c4793b9c08ca0e7155c3eb7d7db9f24/sqlalchemy-2.0.46-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8d679b5f318423eacb61f933a9a0f75535bfca7056daeadbf6bd5bcee6183aee", size = 3292881 }, + { url = "https://files.pythonhosted.org/packages/be/f4/06eda6e91476f90a7d8058f74311cb65a2fb68d988171aced81707189131/sqlalchemy-2.0.46-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64901e08c33462acc9ec3bad27fc7a5c2b6491665f2aa57564e57a4f5d7c52ad", size = 3224559 }, + { url = "https://files.pythonhosted.org/packages/ab/a2/d2af04095412ca6345ac22b33b89fe8d6f32a481e613ffcb2377d931d8d0/sqlalchemy-2.0.46-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e8ac45e8f4eaac0f9f8043ea0e224158855c6a4329fd4ee37c45c61e3beb518e", size = 3262728 }, + { url = "https://files.pythonhosted.org/packages/31/48/1980c7caa5978a3b8225b4d230e69a2a6538a3562b8b31cea679b6933c83/sqlalchemy-2.0.46-cp313-cp313-win32.whl", hash = "sha256:8d3b44b3d0ab2f1319d71d9863d76eeb46766f8cf9e921ac293511804d39813f", size = 2111295 }, + { url = "https://files.pythonhosted.org/packages/2d/54/f8d65bbde3d877617c4720f3c9f60e99bb7266df0d5d78b6e25e7c149f35/sqlalchemy-2.0.46-cp313-cp313-win_amd64.whl", hash = "sha256:77f8071d8fbcbb2dd11b7fd40dedd04e8ebe2eb80497916efedba844298065ef", size = 2137076 }, + { url = "https://files.pythonhosted.org/packages/56/ba/9be4f97c7eb2b9d5544f2624adfc2853e796ed51d2bb8aec90bc94b7137e/sqlalchemy-2.0.46-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1e8cc6cc01da346dc92d9509a63033b9b1bda4fed7a7a7807ed385c7dccdc10", size = 3556533 }, + { url = "https://files.pythonhosted.org/packages/20/a6/b1fc6634564dbb4415b7ed6419cdfeaadefd2c39cdab1e3aa07a5f2474c2/sqlalchemy-2.0.46-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:96c7cca1a4babaaf3bfff3e4e606e38578856917e52f0384635a95b226c87764", size = 3523208 }, + { url = "https://files.pythonhosted.org/packages/a1/d8/41e0bdfc0f930ff236f86fccd12962d8fa03713f17ed57332d38af6a3782/sqlalchemy-2.0.46-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b2a9f9aee38039cf4755891a1e50e1effcc42ea6ba053743f452c372c3152b1b", size = 3464292 }, + { url = "https://files.pythonhosted.org/packages/f0/8b/9dcbec62d95bea85f5ecad9b8d65b78cc30fb0ffceeb3597961f3712549b/sqlalchemy-2.0.46-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:db23b1bf8cfe1f7fda19018e7207b20cdb5168f83c437ff7e95d19e39289c447", size = 3473497 }, + { url = "https://files.pythonhosted.org/packages/e9/f8/5ecdfc73383ec496de038ed1614de9e740a82db9ad67e6e4514ebc0708a3/sqlalchemy-2.0.46-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:56bdd261bfd0895452006d5316cbf35739c53b9bb71a170a331fa0ea560b2ada", size = 2152079 }, + { url = "https://files.pythonhosted.org/packages/e5/bf/eba3036be7663ce4d9c050bc3d63794dc29fbe01691f2bf5ccb64e048d20/sqlalchemy-2.0.46-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33e462154edb9493f6c3ad2125931e273bbd0be8ae53f3ecd1c161ea9a1dd366", size = 3272216 }, + { url = "https://files.pythonhosted.org/packages/05/45/1256fb597bb83b58a01ddb600c59fe6fdf0e5afe333f0456ed75c0f8d7bd/sqlalchemy-2.0.46-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bcdce05f056622a632f1d44bb47dbdb677f58cad393612280406ce37530eb6d", size = 3277208 }, + { url = "https://files.pythonhosted.org/packages/d9/a0/2053b39e4e63b5d7ceb3372cface0859a067c1ddbd575ea7e9985716f771/sqlalchemy-2.0.46-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e84b09a9b0f19accedcbeff5c2caf36e0dd537341a33aad8d680336152dc34e", size = 3221994 }, + { url = "https://files.pythonhosted.org/packages/1e/87/97713497d9502553c68f105a1cb62786ba1ee91dea3852ae4067ed956a50/sqlalchemy-2.0.46-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4f52f7291a92381e9b4de9050b0a65ce5d6a763333406861e33906b8aa4906bf", size = 3243990 }, + { url = "https://files.pythonhosted.org/packages/a8/87/5d1b23548f420ff823c236f8bea36b1a997250fd2f892e44a3838ca424f4/sqlalchemy-2.0.46-cp314-cp314-win32.whl", hash = "sha256:70ed2830b169a9960193f4d4322d22be5c0925357d82cbf485b3369893350908", size = 2114215 }, + { url = "https://files.pythonhosted.org/packages/3a/20/555f39cbcf0c10cf452988b6a93c2a12495035f68b3dbd1a408531049d31/sqlalchemy-2.0.46-cp314-cp314-win_amd64.whl", hash = "sha256:3c32e993bc57be6d177f7d5d31edb93f30726d798ad86ff9066d75d9bf2e0b6b", size = 2139867 }, + { url = "https://files.pythonhosted.org/packages/3e/f0/f96c8057c982d9d8a7a68f45d69c674bc6f78cad401099692fe16521640a/sqlalchemy-2.0.46-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4dafb537740eef640c4d6a7c254611dca2df87eaf6d14d6a5fca9d1f4c3fc0fa", size = 3561202 }, + { url = "https://files.pythonhosted.org/packages/d7/53/3b37dda0a5b137f21ef608d8dfc77b08477bab0fe2ac9d3e0a66eaeab6fc/sqlalchemy-2.0.46-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42a1643dc5427b69aca967dae540a90b0fbf57eaf248f13a90ea5930e0966863", size = 3526296 }, + { url = "https://files.pythonhosted.org/packages/33/75/f28622ba6dde79cd545055ea7bd4062dc934e0621f7b3be2891f8563f8de/sqlalchemy-2.0.46-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ff33c6e6ad006bbc0f34f5faf941cfc62c45841c64c0a058ac38c799f15b5ede", size = 3470008 }, + { url = "https://files.pythonhosted.org/packages/a9/42/4afecbbc38d5e99b18acef446453c76eec6fbd03db0a457a12a056836e22/sqlalchemy-2.0.46-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:82ec52100ec1e6ec671563bbd02d7c7c8d0b9e71a0723c72f22ecf52d1755330", size = 3476137 }, + { url = "https://files.pythonhosted.org/packages/fc/a1/9c4efa03300926601c19c18582531b45aededfb961ab3c3585f1e24f120b/sqlalchemy-2.0.46-py3-none-any.whl", hash = "sha256:f9c11766e7e7c0a2767dda5acb006a118640c9fc0a4104214b96269bfb78399e", size = 1937882 }, ] [[package]] @@ -3265,9 +3332,9 @@ dependencies = [ { name = "executing" }, { name = "pure-eval" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521 }, ] [[package]] @@ -3277,9 +3344,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mpmath" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353 }, ] [[package]] @@ -3299,7 +3366,7 @@ dependencies = [ { name = "werkzeug" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/d9/a5db55f88f258ac669a92858b70a714bbbd5acd993820b41ec4a96a4d77f/tensorboard-2.20.0-py3-none-any.whl", hash = "sha256:9dc9f978cb84c0723acf9a345d96c184f0293d18f166bb8d59ee098e6cfaaba6", size = 5525680, upload-time = "2025-07-17T19:20:49.638Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/a5db55f88f258ac669a92858b70a714bbbd5acd993820b41ec4a96a4d77f/tensorboard-2.20.0-py3-none-any.whl", hash = "sha256:9dc9f978cb84c0723acf9a345d96c184f0293d18f166bb8d59ee098e6cfaaba6", size = 5525680 }, ] [[package]] @@ -3307,9 +3374,9 @@ name = "tensorboard-data-server" version = "0.7.2" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/13/e503968fefabd4c6b2650af21e110aa8466fe21432cd7c43a84577a89438/tensorboard_data_server-0.7.2-py3-none-any.whl", hash = "sha256:7e0610d205889588983836ec05dc098e80f97b7e7bbff7e994ebb78f578d0ddb", size = 2356, upload-time = "2023-10-23T21:23:32.16Z" }, - { url = "https://files.pythonhosted.org/packages/b7/85/dabeaf902892922777492e1d253bb7e1264cadce3cea932f7ff599e53fea/tensorboard_data_server-0.7.2-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:9fe5d24221b29625dbc7328b0436ca7fc1c23de4acf4d272f1180856e32f9f60", size = 4823598, upload-time = "2023-10-23T21:23:33.714Z" }, - { url = "https://files.pythonhosted.org/packages/73/c6/825dab04195756cf8ff2e12698f22513b3db2f64925bdd41671bfb33aaa5/tensorboard_data_server-0.7.2-py3-none-manylinux_2_31_x86_64.whl", hash = "sha256:ef687163c24185ae9754ed5650eb5bc4d84ff257aabdc33f0cc6f74d8ba54530", size = 6590363, upload-time = "2023-10-23T21:23:35.583Z" }, + { url = "https://files.pythonhosted.org/packages/7a/13/e503968fefabd4c6b2650af21e110aa8466fe21432cd7c43a84577a89438/tensorboard_data_server-0.7.2-py3-none-any.whl", hash = "sha256:7e0610d205889588983836ec05dc098e80f97b7e7bbff7e994ebb78f578d0ddb", size = 2356 }, + { url = "https://files.pythonhosted.org/packages/b7/85/dabeaf902892922777492e1d253bb7e1264cadce3cea932f7ff599e53fea/tensorboard_data_server-0.7.2-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:9fe5d24221b29625dbc7328b0436ca7fc1c23de4acf4d272f1180856e32f9f60", size = 4823598 }, + { url = "https://files.pythonhosted.org/packages/73/c6/825dab04195756cf8ff2e12698f22513b3db2f64925bdd41671bfb33aaa5/tensorboard_data_server-0.7.2-py3-none-manylinux_2_31_x86_64.whl", hash = "sha256:ef687163c24185ae9754ed5650eb5bc4d84ff257aabdc33f0cc6f74d8ba54530", size = 6590363 }, ] [[package]] @@ -3321,9 +3388,9 @@ dependencies = [ { name = "pywinpty", marker = "os_name == 'nt'" }, { name = "tornado" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8a/11/965c6fd8e5cc254f1fe142d547387da17a8ebfd75a3455f637c663fb38a0/terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e", size = 32701, upload-time = "2024-03-12T14:34:39.026Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/11/965c6fd8e5cc254f1fe142d547387da17a8ebfd75a3455f637c663fb38a0/terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e", size = 32701 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0", size = 14154, upload-time = "2024-03-12T14:34:36.569Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0", size = 14154 }, ] [[package]] @@ -3333,9 +3400,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/90/80/0ddd8dc74c22e1e5efcfb152303b025f8f4a5010ae9936f1e57f7d7f9256/tifffile-2026.2.20.tar.gz", hash = "sha256:b98a7fc6ea4fa0e9919734857eebc6e2cb2c3a95468a930d4a948a9a49646ab7", size = 377196, upload-time = "2026-02-20T20:09:34.608Z" } +sdist = { url = "https://files.pythonhosted.org/packages/90/80/0ddd8dc74c22e1e5efcfb152303b025f8f4a5010ae9936f1e57f7d7f9256/tifffile-2026.2.20.tar.gz", hash = "sha256:b98a7fc6ea4fa0e9919734857eebc6e2cb2c3a95468a930d4a948a9a49646ab7", size = 377196 } wheels = [ - { url = "https://files.pythonhosted.org/packages/86/07/0cd5cad2fdb7d32515561bc26da041654f3b3c0abc299f4730f30b89271d/tifffile-2026.2.20-py3-none-any.whl", hash = "sha256:a83e0e991647e39d5912369998ef02d858f89effe30064403a1a123b5daef8fb", size = 234528, upload-time = "2026-02-20T20:09:33.278Z" }, + { url = "https://files.pythonhosted.org/packages/86/07/0cd5cad2fdb7d32515561bc26da041654f3b3c0abc299f4730f30b89271d/tifffile-2026.2.20-py3-none-any.whl", hash = "sha256:a83e0e991647e39d5912369998ef02d858f89effe30064403a1a123b5daef8fb", size = 234528 }, ] [[package]] @@ -3345,141 +3412,128 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "webencodings" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085, upload-time = "2024-10-24T14:58:29.895Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610, upload-time = "2024-10-24T14:58:28.029Z" }, + { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610 }, ] [[package]] name = "tomli" version = "2.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" }, - { url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" }, - { url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" }, - { url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" }, - { url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" }, - { url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" }, - { url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" }, - { url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" }, - { url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" }, - { url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" }, - { url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" }, - { url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" }, - { url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" }, - { url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" }, - { url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" }, - { url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" }, - { url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" }, - { url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" }, - { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" }, - { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" }, - { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" }, - { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" }, - { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" }, - { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" }, - { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" }, - { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" }, - { url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" }, - { url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" }, - { url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" }, - { url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" }, - { url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" }, - { url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" }, - { url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" }, - { url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" }, - { url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" }, - { url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" }, - { url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" }, - { url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" }, - { url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" }, - { url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" }, - { url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" }, - { url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" }, - { url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" }, - { url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" }, - { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663 }, + { url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469 }, + { url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039 }, + { url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007 }, + { url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875 }, + { url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271 }, + { url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770 }, + { url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626 }, + { url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842 }, + { url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894 }, + { url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053 }, + { url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481 }, + { url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720 }, + { url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014 }, + { url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820 }, + { url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712 }, + { url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296 }, + { url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553 }, + { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915 }, + { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038 }, + { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245 }, + { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335 }, + { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962 }, + { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396 }, + { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530 }, + { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227 }, + { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748 }, + { url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725 }, + { url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901 }, + { url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375 }, + { url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639 }, + { url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897 }, + { url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697 }, + { url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567 }, + { url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556 }, + { url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014 }, + { url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339 }, + { url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490 }, + { url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398 }, + { url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515 }, + { url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806 }, + { url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340 }, + { url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106 }, + { url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504 }, + { url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561 }, + { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477 }, ] [[package]] name = "toolz" version = "1.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/11/d6/114b492226588d6ff54579d95847662fc69196bdeec318eb45393b24c192/toolz-1.1.0.tar.gz", hash = "sha256:27a5c770d068c110d9ed9323f24f1543e83b2f300a687b7891c1a6d56b697b5b", size = 52613, upload-time = "2025-10-17T04:03:21.661Z" } +sdist = { url = "https://files.pythonhosted.org/packages/11/d6/114b492226588d6ff54579d95847662fc69196bdeec318eb45393b24c192/toolz-1.1.0.tar.gz", hash = "sha256:27a5c770d068c110d9ed9323f24f1543e83b2f300a687b7891c1a6d56b697b5b", size = 52613 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl", hash = "sha256:15ccc861ac51c53696de0a5d6d4607f99c210739caf987b5d2054f3efed429d8", size = 58093, upload-time = "2025-10-17T04:03:20.435Z" }, + { url = "https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl", hash = "sha256:15ccc861ac51c53696de0a5d6d4607f99c210739caf987b5d2054f3efed429d8", size = 58093 }, ] [[package]] name = "torch" -version = "2.10.0" +version = "2.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cuda-bindings", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "cuda-bindings", marker = "sys_platform == 'linux'" }, + { name = "cuda-toolkit", extra = ["cublas", "cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"], marker = "sys_platform == 'linux'" }, { name = "filelock" }, { name = "fsspec" }, { name = "jinja2" }, { name = "networkx" }, - { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cufile-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cusparselt-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-nvshmem-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "setuptools", marker = "python_full_version >= '3.12'" }, + { name = "nvidia-cudnn-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-cusparselt-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nccl-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nvshmem-cu13", marker = "sys_platform == 'linux'" }, + { name = "setuptools" }, { name = "sympy" }, - { name = "triton", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "triton", marker = "sys_platform == 'linux'" }, { name = "typing-extensions" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/8b/4b61d6e13f7108f36910df9ab4b58fd389cc2520d54d81b88660804aad99/torch-2.10.0-2-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:418997cb02d0a0f1497cf6a09f63166f9f5df9f3e16c8a716ab76a72127c714f", size = 79423467, upload-time = "2026-02-10T21:44:48.711Z" }, - { url = "https://files.pythonhosted.org/packages/d3/54/a2ba279afcca44bbd320d4e73675b282fcee3d81400ea1b53934efca6462/torch-2.10.0-2-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:13ec4add8c3faaed8d13e0574f5cd4a323c11655546f91fbe6afa77b57423574", size = 79498202, upload-time = "2026-02-10T21:44:52.603Z" }, - { url = "https://files.pythonhosted.org/packages/ec/23/2c9fe0c9c27f7f6cb865abcea8a4568f29f00acaeadfc6a37f6801f84cb4/torch-2.10.0-2-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:e521c9f030a3774ed770a9c011751fb47c4d12029a3d6522116e48431f2ff89e", size = 79498254, upload-time = "2026-02-10T21:44:44.095Z" }, - { url = "https://files.pythonhosted.org/packages/78/89/f5554b13ebd71e05c0b002f95148033e730d3f7067f67423026cc9c69410/torch-2.10.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:3282d9febd1e4e476630a099692b44fdc214ee9bf8ee5377732d9d9dfe5712e4", size = 145992610, upload-time = "2026-01-21T16:25:26.327Z" }, - { url = "https://files.pythonhosted.org/packages/ae/30/a3a2120621bf9c17779b169fc17e3dc29b230c29d0f8222f499f5e159aa8/torch-2.10.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a2f9edd8dbc99f62bc4dfb78af7bf89499bca3d753423ac1b4e06592e467b763", size = 915607863, upload-time = "2026-01-21T16:25:06.696Z" }, - { url = "https://files.pythonhosted.org/packages/6f/3d/c87b33c5f260a2a8ad68da7147e105f05868c281c63d65ed85aa4da98c66/torch-2.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:29b7009dba4b7a1c960260fc8ac85022c784250af43af9fb0ebafc9883782ebd", size = 113723116, upload-time = "2026-01-21T16:25:21.916Z" }, - { url = "https://files.pythonhosted.org/packages/61/d8/15b9d9d3a6b0c01b883787bd056acbe5cc321090d4b216d3ea89a8fcfdf3/torch-2.10.0-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:b7bd80f3477b830dd166c707c5b0b82a898e7b16f59a7d9d42778dd058272e8b", size = 79423461, upload-time = "2026-01-21T16:24:50.266Z" }, - { url = "https://files.pythonhosted.org/packages/cc/af/758e242e9102e9988969b5e621d41f36b8f258bb4a099109b7a4b4b50ea4/torch-2.10.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:5fd4117d89ffd47e3dcc71e71a22efac24828ad781c7e46aaaf56bf7f2796acf", size = 145996088, upload-time = "2026-01-21T16:24:44.171Z" }, - { url = "https://files.pythonhosted.org/packages/23/8e/3c74db5e53bff7ed9e34c8123e6a8bfef718b2450c35eefab85bb4a7e270/torch-2.10.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:787124e7db3b379d4f1ed54dd12ae7c741c16a4d29b49c0226a89bea50923ffb", size = 915711952, upload-time = "2026-01-21T16:23:53.503Z" }, - { url = "https://files.pythonhosted.org/packages/6e/01/624c4324ca01f66ae4c7cd1b74eb16fb52596dce66dbe51eff95ef9e7a4c/torch-2.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:2c66c61f44c5f903046cc696d088e21062644cbe541c7f1c4eaae88b2ad23547", size = 113757972, upload-time = "2026-01-21T16:24:39.516Z" }, - { url = "https://files.pythonhosted.org/packages/c9/5c/dee910b87c4d5c0fcb41b50839ae04df87c1cfc663cf1b5fca7ea565eeaa/torch-2.10.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:6d3707a61863d1c4d6ebba7be4ca320f42b869ee657e9b2c21c736bf17000294", size = 79498198, upload-time = "2026-01-21T16:24:34.704Z" }, - { url = "https://files.pythonhosted.org/packages/c9/6f/f2e91e34e3fcba2e3fc8d8f74e7d6c22e74e480bbd1db7bc8900fdf3e95c/torch-2.10.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:5c4d217b14741e40776dd7074d9006fd28b8a97ef5654db959d8635b2fe5f29b", size = 146004247, upload-time = "2026-01-21T16:24:29.335Z" }, - { url = "https://files.pythonhosted.org/packages/98/fb/5160261aeb5e1ee12ee95fe599d0541f7c976c3701d607d8fc29e623229f/torch-2.10.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6b71486353fce0f9714ca0c9ef1c850a2ae766b409808acd58e9678a3edb7738", size = 915716445, upload-time = "2026-01-21T16:22:45.353Z" }, - { url = "https://files.pythonhosted.org/packages/6a/16/502fb1b41e6d868e8deb5b0e3ae926bbb36dab8ceb0d1b769b266ad7b0c3/torch-2.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:c2ee399c644dc92ef7bc0d4f7e74b5360c37cdbe7c5ba11318dda49ffac2bc57", size = 113757050, upload-time = "2026-01-21T16:24:19.204Z" }, - { url = "https://files.pythonhosted.org/packages/1a/0b/39929b148f4824bc3ad6f9f72a29d4ad865bcf7ebfc2fa67584773e083d2/torch-2.10.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:3202429f58309b9fa96a614885eace4b7995729f44beb54d3e4a47773649d382", size = 79851305, upload-time = "2026-01-21T16:24:09.209Z" }, - { url = "https://files.pythonhosted.org/packages/d8/14/21fbce63bc452381ba5f74a2c0a959fdf5ad5803ccc0c654e752e0dbe91a/torch-2.10.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:aae1b29cd68e50a9397f5ee897b9c24742e9e306f88a807a27d617f07adb3bd8", size = 146005472, upload-time = "2026-01-21T16:22:29.022Z" }, - { url = "https://files.pythonhosted.org/packages/54/fd/b207d1c525cb570ef47f3e9f836b154685011fce11a2f444ba8a4084d042/torch-2.10.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:6021db85958db2f07ec94e1bc77212721ba4920c12a18dc552d2ae36a3eb163f", size = 915612644, upload-time = "2026-01-21T16:21:47.019Z" }, - { url = "https://files.pythonhosted.org/packages/36/53/0197f868c75f1050b199fe58f9bf3bf3aecac9b4e85cc9c964383d745403/torch-2.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff43db38af76fda183156153983c9a096fc4c78d0cd1e07b14a2314c7f01c2c8", size = 113997015, upload-time = "2026-01-21T16:23:00.767Z" }, - { url = "https://files.pythonhosted.org/packages/0e/13/e76b4d9c160e89fff48bf16b449ea324bda84745d2ab30294c37c2434c0d/torch-2.10.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:cdf2a523d699b70d613243211ecaac14fe9c5df8a0b0a9c02add60fb2a413e0f", size = 79498248, upload-time = "2026-01-21T16:23:09.315Z" }, - { url = "https://files.pythonhosted.org/packages/4f/93/716b5ac0155f1be70ed81bacc21269c3ece8dba0c249b9994094110bfc51/torch-2.10.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:bf0d9ff448b0218e0433aeb198805192346c4fd659c852370d5cc245f602a06a", size = 79464992, upload-time = "2026-01-21T16:23:05.162Z" }, - { url = "https://files.pythonhosted.org/packages/69/2b/51e663ff190c9d16d4a8271203b71bc73a16aa7619b9f271a69b9d4a936b/torch-2.10.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:233aed0659a2503b831d8a67e9da66a62c996204c0bba4f4c442ccc0c68a3f60", size = 146018567, upload-time = "2026-01-21T16:22:23.393Z" }, - { url = "https://files.pythonhosted.org/packages/5e/cd/4b95ef7f293b927c283db0b136c42be91c8ec6845c44de0238c8c23bdc80/torch-2.10.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:682497e16bdfa6efeec8cde66531bc8d1fbbbb4d8788ec6173c089ed3cc2bfe5", size = 915721646, upload-time = "2026-01-21T16:21:16.983Z" }, - { url = "https://files.pythonhosted.org/packages/56/97/078a007208f8056d88ae43198833469e61a0a355abc0b070edd2c085eb9a/torch-2.10.0-cp314-cp314-win_amd64.whl", hash = "sha256:6528f13d2a8593a1a412ea07a99812495bec07e9224c28b2a25c0a30c7da025c", size = 113752373, upload-time = "2026-01-21T16:22:13.471Z" }, - { url = "https://files.pythonhosted.org/packages/d8/94/71994e7d0d5238393df9732fdab607e37e2b56d26a746cb59fdb415f8966/torch-2.10.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:f5ab4ba32383061be0fb74bda772d470140a12c1c3b58a0cfbf3dae94d164c28", size = 79850324, upload-time = "2026-01-21T16:22:09.494Z" }, - { url = "https://files.pythonhosted.org/packages/e2/65/1a05346b418ea8ccd10360eef4b3e0ce688fba544e76edec26913a8d0ee0/torch-2.10.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:716b01a176c2a5659c98f6b01bf868244abdd896526f1c692712ab36dbaf9b63", size = 146006482, upload-time = "2026-01-21T16:22:18.42Z" }, - { url = "https://files.pythonhosted.org/packages/1d/b9/5f6f9d9e859fc3235f60578fa64f52c9c6e9b4327f0fe0defb6de5c0de31/torch-2.10.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:d8f5912ba938233f86361e891789595ff35ca4b4e2ac8fe3670895e5976731d6", size = 915613050, upload-time = "2026-01-21T16:20:49.035Z" }, - { url = "https://files.pythonhosted.org/packages/66/4d/35352043ee0eaffdeff154fad67cd4a31dbed7ff8e3be1cc4549717d6d51/torch-2.10.0-cp314-cp314t-win_amd64.whl", hash = "sha256:71283a373f0ee2c89e0f0d5f446039bdabe8dbc3c9ccf35f0f784908b0acd185", size = 113995816, upload-time = "2026-01-21T16:22:05.312Z" }, + { url = "https://files.pythonhosted.org/packages/ae/0d/98b410492609e34a155fa8b121b55c7dca229f39636851c3a9ec20edea21/torch-2.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7b6a60d48062809f58595509c524b88e6ddec3ebe25833d6462eeab81e5f2ce4", size = 80529712 }, + { url = "https://files.pythonhosted.org/packages/84/03/acea680005f098f79fd70c1d9d5ccc0cb4296ec2af539a0450108232fc0c/torch-2.11.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:d91aac77f24082809d2c5a93f52a5f085032740a1ebc9252a7b052ef5a4fddc6", size = 419718178 }, + { url = "https://files.pythonhosted.org/packages/8c/8b/d7be22fbec9ffee6cff31a39f8750d4b3a65d349a286cf4aec74c2375662/torch-2.11.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:7aa2f9bbc6d4595ba72138026b2074be1233186150e9292865e04b7a63b8c67a", size = 530604548 }, + { url = "https://files.pythonhosted.org/packages/d1/bd/9912d30b68845256aabbb4a40aeefeef3c3b20db5211ccda653544ada4b6/torch-2.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:73e24aaf8f36ab90d95cd1761208b2eb70841c2a9ca1a3f9061b39fc5331b708", size = 114519675 }, + { url = "https://files.pythonhosted.org/packages/6f/8b/69e3008d78e5cee2b30183340cc425081b78afc5eff3d080daab0adda9aa/torch-2.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b5866312ee6e52ea625cd211dcb97d6a2cdc1131a5f15cc0d87eec948f6dd34", size = 80606338 }, + { url = "https://files.pythonhosted.org/packages/13/16/42e5915ebe4868caa6bac83a8ed59db57f12e9a61b7d749d584776ed53d5/torch-2.11.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f99924682ef0aa6a4ab3b1b76f40dc6e273fca09f367d15a524266db100a723f", size = 419731115 }, + { url = "https://files.pythonhosted.org/packages/1a/c9/82638ef24d7877510f83baf821f5619a61b45568ce21c0a87a91576510aa/torch-2.11.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:0f68f4ac6d95d12e896c3b7a912b5871619542ec54d3649cf48cc1edd4dd2756", size = 530712279 }, + { url = "https://files.pythonhosted.org/packages/1c/ff/6756f1c7ee302f6d202120e0f4f05b432b839908f9071157302cedfc5232/torch-2.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:fbf39280699d1b869f55eac536deceaa1b60bd6788ba74f399cc67e60a5fab10", size = 114556047 }, + { url = "https://files.pythonhosted.org/packages/87/89/5ea6722763acee56b045435fb84258db7375c48165ec8be7880ab2b281c5/torch-2.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e6debd97ccd3205bbb37eb806a9d8219e1139d15419982c09e23ef7d4369d18", size = 80606801 }, + { url = "https://files.pythonhosted.org/packages/32/d1/8ed2173589cbfe744ed54e5a73efc107c0085ba5777ee93a5f4c1ab90553/torch-2.11.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:63a68fa59de8f87acc7e85a5478bb2dddbb3392b7593ec3e78827c793c4b73fd", size = 419732382 }, + { url = "https://files.pythonhosted.org/packages/3d/e1/b73f7c575a4b8f87a5928f50a1e35416b5e27295d8be9397d5293e7e8d4c/torch-2.11.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:cc89b9b173d9adfab59fd227f0ab5e5516d9a52b658ae41d64e59d2e55a418db", size = 530711509 }, + { url = "https://files.pythonhosted.org/packages/66/82/3e3fcdd388fbe54e29fd3f991f36846ff4ac90b0d0181e9c8f7236565f82/torch-2.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:4dda3b3f52d121063a731ddb835f010dc137b920d7fec2778e52f60d8e4bf0cd", size = 114555842 }, + { url = "https://files.pythonhosted.org/packages/db/38/8ac78069621b8c2b4979c2f96dc8409ef5e9c4189f6aac629189a78677ca/torch-2.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8b394322f49af4362d4f80e424bcaca7efcd049619af03a4cf4501520bdf0fb4", size = 80959574 }, + { url = "https://files.pythonhosted.org/packages/6d/6c/56bfb37073e7136e6dd86bfc6af7339946dd684e0ecf2155ac0eee687ae1/torch-2.11.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:2658f34ce7e2dabf4ec73b45e2ca68aedad7a5be87ea756ad656eaf32bf1e1ea", size = 419732324 }, + { url = "https://files.pythonhosted.org/packages/07/f4/1b666b6d61d3394cca306ea543ed03a64aad0a201b6cd159f1d41010aeb1/torch-2.11.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:98bb213c3084cfe176302949bdc360074b18a9da7ab59ef2edc9d9f742504778", size = 530596026 }, + { url = "https://files.pythonhosted.org/packages/48/6b/30d1459fa7e4b67e9e3fe1685ca1d8bb4ce7c62ef436c3a615963c6c866c/torch-2.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a97b94bbf62992949b4730c6cd2cc9aee7b335921ee8dc207d930f2ed09ae2db", size = 114793702 }, + { url = "https://files.pythonhosted.org/packages/26/0d/8603382f61abd0db35841148ddc1ffd607bf3100b11c6e1dab6d2fc44e72/torch-2.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:01018087326984a33b64e04c8cb5c2795f9120e0d775ada1f6638840227b04d7", size = 80573442 }, + { url = "https://files.pythonhosted.org/packages/c7/86/7cd7c66cb9cec6be330fff36db5bd0eef386d80c031b581ec81be1d4b26c/torch-2.11.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:2bb3cc54bd0dea126b0060bb1ec9de0f9c7f7342d93d436646516b0330cd5be7", size = 419749385 }, + { url = "https://files.pythonhosted.org/packages/47/e8/b98ca2d39b2e0e4730c0ee52537e488e7008025bc77ca89552ff91021f7c/torch-2.11.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:4dc8b3809469b6c30b411bb8c4cad3828efd26236153d9beb6a3ec500f211a60", size = 530716756 }, + { url = "https://files.pythonhosted.org/packages/78/88/d4a4cda8362f8a30d1ed428564878c3cafb0d87971fbd3947d4c84552095/torch-2.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:2b4e811728bd0cc58fb2b0948fe939a1ee2bf1422f6025be2fca4c7bd9d79718", size = 114552300 }, + { url = "https://files.pythonhosted.org/packages/bf/46/4419098ed6d801750f26567b478fc185c3432e11e2cad712bc6b4c2ab0d0/torch-2.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8245477871c3700d4370352ffec94b103cfcb737229445cf9946cddb7b2ca7cd", size = 80959460 }, + { url = "https://files.pythonhosted.org/packages/fd/66/54a56a4a6ceaffb567231994a9745821d3af922a854ed33b0b3a278e0a99/torch-2.11.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:ab9a8482f475f9ba20e12db84b0e55e2f58784bdca43a854a6ccd3fd4b9f75e6", size = 419735835 }, + { url = "https://files.pythonhosted.org/packages/b1/e7/0b6665f533aa9e337662dc190425abc0af1fe3234088f4454c52393ded61/torch-2.11.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:563ed3d25542d7e7bbc5b235ccfacfeb97fb470c7fee257eae599adb8005c8a2", size = 530613405 }, + { url = "https://files.pythonhosted.org/packages/cf/bf/c8d12a2c86dbfd7f40fb2f56fbf5a505ccf2d9ce131eb559dfc7c51e1a04/torch-2.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b2a43985ff5ef6ddd923bbcf99943e5f58059805787c5c9a2622bf05ca2965b0", size = 114792991 }, ] [[package]] name = "torchinfo" version = "1.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/d9/2b811d1c0812e9ef23e6cf2dbe022becbe6c5ab065e33fd80ee05c0cd996/torchinfo-1.8.0.tar.gz", hash = "sha256:72e94b0e9a3e64dc583a8e5b7940b8938a1ac0f033f795457f27e6f4e7afa2e9", size = 25880, upload-time = "2023-05-14T19:23:26.377Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/d9/2b811d1c0812e9ef23e6cf2dbe022becbe6c5ab065e33fd80ee05c0cd996/torchinfo-1.8.0.tar.gz", hash = "sha256:72e94b0e9a3e64dc583a8e5b7940b8938a1ac0f033f795457f27e6f4e7afa2e9", size = 25880 } wheels = [ - { url = "https://files.pythonhosted.org/packages/72/25/973bd6128381951b23cdcd8a9870c6dcfc5606cb864df8eabd82e529f9c1/torchinfo-1.8.0-py3-none-any.whl", hash = "sha256:2e911c2918603f945c26ff21a3a838d12709223dc4ccf243407bce8b6e897b46", size = 23377, upload-time = "2023-05-14T19:23:24.141Z" }, + { url = "https://files.pythonhosted.org/packages/72/25/973bd6128381951b23cdcd8a9870c6dcfc5606cb864df8eabd82e529f9c1/torchinfo-1.8.0-py3-none-any.whl", hash = "sha256:2e911c2918603f945c26ff21a3a838d12709223dc4ccf243407bce8b6e897b46", size = 23377 }, ] [[package]] @@ -3492,14 +3546,14 @@ dependencies = [ { name = "packaging" }, { name = "torch" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/85/2e/48a887a59ecc4a10ce9e8b35b3e3c5cef29d902c4eac143378526e7485cb/torchmetrics-1.8.2.tar.gz", hash = "sha256:cf64a901036bf107f17a524009eea7781c9c5315d130713aeca5747a686fe7a5", size = 580679, upload-time = "2025-09-03T14:00:54.077Z" } +sdist = { url = "https://files.pythonhosted.org/packages/85/2e/48a887a59ecc4a10ce9e8b35b3e3c5cef29d902c4eac143378526e7485cb/torchmetrics-1.8.2.tar.gz", hash = "sha256:cf64a901036bf107f17a524009eea7781c9c5315d130713aeca5747a686fe7a5", size = 580679 } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/21/aa0f434434c48490f91b65962b1ce863fdcce63febc166ca9fe9d706c2b6/torchmetrics-1.8.2-py3-none-any.whl", hash = "sha256:08382fd96b923e39e904c4d570f3d49e2cc71ccabd2a94e0f895d1f0dac86242", size = 983161, upload-time = "2025-09-03T14:00:51.921Z" }, + { url = "https://files.pythonhosted.org/packages/02/21/aa0f434434c48490f91b65962b1ce863fdcce63febc166ca9fe9d706c2b6/torchmetrics-1.8.2-py3-none-any.whl", hash = "sha256:08382fd96b923e39e904c4d570f3d49e2cc71ccabd2a94e0f895d1f0dac86242", size = 983161 }, ] [[package]] name = "torchvision" -version = "0.25.0" +version = "0.26.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, @@ -3507,49 +3561,49 @@ dependencies = [ { name = "torch" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/be/c704bceaf11c4f6b19d64337a34a877fcdfe3bd68160a8c9ae9bea4a35a3/torchvision-0.25.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:db74a551946b75d19f9996c419a799ffdf6a223ecf17c656f90da011f1d75b20", size = 1874923, upload-time = "2026-01-21T16:27:46.574Z" }, - { url = "https://files.pythonhosted.org/packages/ae/e9/f143cd71232430de1f547ceab840f68c55e127d72558b1061a71d0b193cd/torchvision-0.25.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:f49964f96644dbac2506dffe1a0a7ec0f2bf8cf7a588c3319fed26e6329ffdf3", size = 2344808, upload-time = "2026-01-21T16:27:43.191Z" }, - { url = "https://files.pythonhosted.org/packages/43/ae/ad5d6165797de234c9658752acb4fce65b78a6a18d82efdf8367c940d8da/torchvision-0.25.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:153c0d2cbc34b7cf2da19d73450f24ba36d2b75ec9211b9962b5022fb9e4ecee", size = 8070752, upload-time = "2026-01-21T16:27:33.748Z" }, - { url = "https://files.pythonhosted.org/packages/23/19/55b28aecdc7f38df57b8eb55eb0b14a62b470ed8efeb22cdc74224df1d6a/torchvision-0.25.0-cp311-cp311-win_amd64.whl", hash = "sha256:ea580ffd6094cc01914ad32f8c8118174f18974629af905cea08cb6d5d48c7b7", size = 4038722, upload-time = "2026-01-21T16:27:41.355Z" }, - { url = "https://files.pythonhosted.org/packages/56/3a/6ea0d73f49a9bef38a1b3a92e8dd455cea58470985d25635beab93841748/torchvision-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c2abe430c90b1d5e552680037d68da4eb80a5852ebb1c811b2b89d299b10573b", size = 1874920, upload-time = "2026-01-21T16:27:45.348Z" }, - { url = "https://files.pythonhosted.org/packages/51/f8/c0e1ef27c66e15406fece94930e7d6feee4cb6374bbc02d945a630d6426e/torchvision-0.25.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:b75deafa2dfea3e2c2a525559b04783515e3463f6e830cb71de0fb7ea36fe233", size = 2344556, upload-time = "2026-01-21T16:27:40.125Z" }, - { url = "https://files.pythonhosted.org/packages/68/2f/f24b039169db474e8688f649377de082a965fbf85daf4e46c44412f1d15a/torchvision-0.25.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f25aa9e380865b11ea6e9d99d84df86b9cc959f1a007cd966fc6f1ab2ed0e248", size = 8072351, upload-time = "2026-01-21T16:27:21.074Z" }, - { url = "https://files.pythonhosted.org/packages/ad/16/8f650c2e288977cf0f8f85184b90ee56ed170a4919347fc74ee99286ed6f/torchvision-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:f9c55ae8d673ab493325d1267cbd285bb94d56f99626c00ac4644de32a59ede3", size = 4303059, upload-time = "2026-01-21T16:27:11.08Z" }, - { url = "https://files.pythonhosted.org/packages/f5/5b/1562a04a6a5a4cf8cf40016a0cdeda91ede75d6962cff7f809a85ae966a5/torchvision-0.25.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:24e11199e4d84ba9c5ee7825ebdf1cd37ce8deec225117f10243cae984ced3ec", size = 1874918, upload-time = "2026-01-21T16:27:39.02Z" }, - { url = "https://files.pythonhosted.org/packages/36/b1/3d6c42f62c272ce34fcce609bb8939bdf873dab5f1b798fd4e880255f129/torchvision-0.25.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:5f271136d2d2c0b7a24c5671795c6e4fd8da4e0ea98aeb1041f62bc04c4370ef", size = 2309106, upload-time = "2026-01-21T16:27:30.624Z" }, - { url = "https://files.pythonhosted.org/packages/c7/60/59bb9c8b67cce356daeed4cb96a717caa4f69c9822f72e223a0eae7a9bd9/torchvision-0.25.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:855c0dc6d37f462482da7531c6788518baedca1e0847f3df42a911713acdfe52", size = 8071522, upload-time = "2026-01-21T16:27:29.392Z" }, - { url = "https://files.pythonhosted.org/packages/32/a5/9a9b1de0720f884ea50dbf9acb22cbe5312e51d7b8c4ac6ba9b51efd9bba/torchvision-0.25.0-cp313-cp313-win_amd64.whl", hash = "sha256:cef0196be31be421f6f462d1e9da1101be7332d91984caa6f8022e6c78a5877f", size = 4321911, upload-time = "2026-01-21T16:27:35.195Z" }, - { url = "https://files.pythonhosted.org/packages/52/99/dca81ed21ebaeff2b67cc9f815a20fdaa418b69f5f9ea4c6ed71721470db/torchvision-0.25.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a8f8061284395ce31bcd460f2169013382ccf411148ceb2ee38e718e9860f5a7", size = 1896209, upload-time = "2026-01-21T16:27:32.159Z" }, - { url = "https://files.pythonhosted.org/packages/28/cc/2103149761fdb4eaed58a53e8437b2d716d48f05174fab1d9fcf1e2a2244/torchvision-0.25.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:146d02c9876858420adf41f3189fe90e3d6a409cbfa65454c09f25fb33bf7266", size = 2310735, upload-time = "2026-01-21T16:27:22.327Z" }, - { url = "https://files.pythonhosted.org/packages/76/ad/f4c985ad52ddd3b22711c588501be1b330adaeaf6850317f66751711b78c/torchvision-0.25.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:c4d395cb2c4a2712f6eb93a34476cdf7aae74bb6ea2ea1917f858e96344b00aa", size = 8089557, upload-time = "2026-01-21T16:27:27.666Z" }, - { url = "https://files.pythonhosted.org/packages/63/cc/0ea68b5802e5e3c31f44b307e74947bad5a38cc655231d845534ed50ddb8/torchvision-0.25.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5e6b449e9fa7d642142c0e27c41e5a43b508d57ed8e79b7c0a0c28652da8678c", size = 4344260, upload-time = "2026-01-21T16:27:17.018Z" }, - { url = "https://files.pythonhosted.org/packages/9e/1f/fa839532660e2602b7e704d65010787c5bb296258b44fa8b9c1cd6175e7d/torchvision-0.25.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:620a236288d594dcec7634c754484542dc0a5c1b0e0b83a34bda5e91e9b7c3a1", size = 1896193, upload-time = "2026-01-21T16:27:24.785Z" }, - { url = "https://files.pythonhosted.org/packages/80/ed/d51889da7ceaf5ff7a0574fb28f9b6b223df19667265395891f81b364ab3/torchvision-0.25.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:0b5e7f50002a8145a98c5694a018e738c50e2972608310c7e88e1bd4c058f6ce", size = 2309331, upload-time = "2026-01-21T16:27:19.97Z" }, - { url = "https://files.pythonhosted.org/packages/90/a5/f93fcffaddd8f12f9e812256830ec9c9ca65abbf1bc369379f9c364d1ff4/torchvision-0.25.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:632db02300e83793812eee4f61ae6a2686dab10b4cfd628b620dc47747aa9d03", size = 8088713, upload-time = "2026-01-21T16:27:15.281Z" }, - { url = "https://files.pythonhosted.org/packages/1f/eb/d0096eed5690d962853213f2ee00d91478dfcb586b62dbbb449fb8abc3a6/torchvision-0.25.0-cp314-cp314-win_amd64.whl", hash = "sha256:d1abd5ed030c708f5dbf4812ad5f6fbe9384b63c40d6bd79f8df41a4a759a917", size = 4325058, upload-time = "2026-01-21T16:27:26.165Z" }, - { url = "https://files.pythonhosted.org/packages/97/36/96374a4c7ab50dea9787ce987815614ccfe988a42e10ac1a2e3e5b60319a/torchvision-0.25.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ad9a8a5877782944d99186e4502a614770fe906626d76e9cd32446a0ac3075f2", size = 1896207, upload-time = "2026-01-21T16:27:23.383Z" }, - { url = "https://files.pythonhosted.org/packages/b5/e2/7abb10a867db79b226b41da419b63b69c0bd5b82438c4a4ed50e084c552f/torchvision-0.25.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:40a122c3cf4d14b651f095e0f672b688dde78632783fc5cd3d4d5e4f6a828563", size = 2310741, upload-time = "2026-01-21T16:27:18.712Z" }, - { url = "https://files.pythonhosted.org/packages/08/e6/0927784e6ffc340b6676befde1c60260bd51641c9c574b9298d791a9cda4/torchvision-0.25.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:846890161b825b38aa85fc37fb3ba5eea74e7091ff28bab378287111483b6443", size = 8089772, upload-time = "2026-01-21T16:27:14.048Z" }, - { url = "https://files.pythonhosted.org/packages/b6/37/e7ca4ec820d434c0f23f824eb29f0676a0c3e7a118f1514f5b949c3356da/torchvision-0.25.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f07f01d27375ad89d72aa2b3f2180f07da95dd9d2e4c758e015c0acb2da72977", size = 4425879, upload-time = "2026-01-21T16:27:12.579Z" }, + { url = "https://files.pythonhosted.org/packages/b4/bd/d552a2521bade3295b2c6e7a4a0d1022261cab7ca7011f4e2a330dbb3caa/torchvision-0.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55bd6ad4ae77be01ba67a410b05b51f53b0d0ee45f146eb6a0dfb9007e70ab3c", size = 1863499 }, + { url = "https://files.pythonhosted.org/packages/33/bf/21b899792b08cae7a298551c68398a79e333697479ed311b3b067aab4bdc/torchvision-0.26.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:1c55dc8affbcc0eb2060fbabbe996ae9e5839b24bb6419777f17848945a411b1", size = 7767527 }, + { url = "https://files.pythonhosted.org/packages/9a/45/57bbf9e216850d065e66dd31a50f57424b607f1d878ab8956e56a1f4e36b/torchvision-0.26.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:fd10b5f994c210f4f6d6761cf686f82d748554adf486cb0979770c3252868c8f", size = 7519925 }, + { url = "https://files.pythonhosted.org/packages/10/58/ed8f7754299f3e91d6414b6dc09f62b3fa7c6e5d63dfe48d69ab81498a37/torchvision-0.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:de6424b12887ad884f39a0ee446994ae3cd3b6a00a9cafe1bead85a031132af0", size = 3983834 }, + { url = "https://files.pythonhosted.org/packages/ae/e7/56b47cc3b132aea90ccce22bcb8975dec688b002150012acc842846039d0/torchvision-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c409e1c3fdebec7a3834465086dbda8bf7680eff79abf7fd2f10c6b59520a7a4", size = 1863502 }, + { url = "https://files.pythonhosted.org/packages/f4/ec/5c31c92c08b65662fe9604a4067ae8232582805949f11ddc042cebe818ed/torchvision-0.26.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:406557718e62fdf10f5706e88d8a5ec000f872da913bf629aab9297622585547", size = 7767944 }, + { url = "https://files.pythonhosted.org/packages/f5/d8/cb6ccda1a1f35a6597645818641701207b3e8e13553e75fce5d86bac74b2/torchvision-0.26.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d61a5abb6b42a0c0c311996c2ac4b83a94418a97182c83b055a2a4ae985e05aa", size = 7522205 }, + { url = "https://files.pythonhosted.org/packages/1c/a9/c272623a0f735c35f0f6cd6dc74784d4f970e800cf063bb76687895a2ab9/torchvision-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:7993c01648e7c61d191b018e84d38fe0825c8fcb2720cd0f37caf7ba14404aa1", size = 4255155 }, + { url = "https://files.pythonhosted.org/packages/da/80/0762f77f53605d10c9477be39bb47722cc8e383bbbc2531471ce0e396c07/torchvision-0.26.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5d63dd43162691258b1b3529b9041bac7d54caa37eae0925f997108268cbf7c4", size = 1860809 }, + { url = "https://files.pythonhosted.org/packages/e6/81/0b3e58d1478c660a5af4268713486b2df7203f35abd9195fea87348a5178/torchvision-0.26.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a39c7a26538c41fda453f9a9692b5ff9b35a5437db1d94f3027f6f509c160eac", size = 7727494 }, + { url = "https://files.pythonhosted.org/packages/b6/dc/d9ab5d29115aa05e12e30f1397a3eeae1d88a511241dc3bce48dc4342675/torchvision-0.26.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:b7e6213620bbf97742e5f79832f9e9d769e6cf0f744c5b53dad80b76db633691", size = 7521747 }, + { url = "https://files.pythonhosted.org/packages/a9/1b/f1bc86a918c5f6feab1eeff11982e2060f4704332e96185463d27855bdf5/torchvision-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:4280c35ec8cba1fcc8294fb87e136924708726864c379e4c54494797d86bc474", size = 4319880 }, + { url = "https://files.pythonhosted.org/packages/66/28/b4ad0a723ed95b003454caffcc41894b34bd8379df340848cae2c33871de/torchvision-0.26.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:358fc4726d0c08615b6d83b3149854f11efb2a564ed1acb6fce882e151412d23", size = 1951973 }, + { url = "https://files.pythonhosted.org/packages/71/e2/7a89096e6cf2f3336353b5338ba925e0addf9d8601920340e6bdf47e8eb3/torchvision-0.26.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:3daf9cc149cf3cdcbd4df9c59dae69ffca86c6823250442c3bbfd63fc2e26c61", size = 7728679 }, + { url = "https://files.pythonhosted.org/packages/69/1d/4e1eebc17d18ce080a11dcf3df3f8f717f0efdfa00983f06e8ba79259f61/torchvision-0.26.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:82c3965eca27e86a316e31e4c3e5a16d353e0bcbe0ef8efa2e66502c54493c4b", size = 7609138 }, + { url = "https://files.pythonhosted.org/packages/f3/a4/f1155e943ae5b32400d7000adc81c79bb0392b16ceb33bcf13e02e48cced/torchvision-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ebc043cc5a4f0bf22e7680806dbba37ffb19e70f6953bbb44ed1a90aeb5c9bea", size = 4248202 }, + { url = "https://files.pythonhosted.org/packages/7f/c8/9bffa9c7f7bdf95b2a0a2dc535c290b9f1cc580c3fb3033ab1246ffffdeb/torchvision-0.26.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:eb61804eb9dbe88c5a2a6c4da8dec1d80d2d0a6f18c999c524e32266cb1ebcd3", size = 1860813 }, + { url = "https://files.pythonhosted.org/packages/7b/ac/48f28ffd227991f2e14f4392dde7e8dc14352bb9428c1ef4a4bbf5f7ed85/torchvision-0.26.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:9a904f2131cbfadab4df828088a9f66291ad33f49ff853872aed1f86848ef776", size = 7727777 }, + { url = "https://files.pythonhosted.org/packages/a4/21/a2266f7f1b0e58e624ff15fd6f01041f59182c49551ece0db9a183071329/torchvision-0.26.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:0f3e572efe62ad645017ea847e0b5e4f2f638d4e39f05bc011d1eb9ac68d4806", size = 7522174 }, + { url = "https://files.pythonhosted.org/packages/fc/ba/1666f90bc0bdd77aaa11dcc42bb9f621a9c3668819c32430452e3d404730/torchvision-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:114bec0c0e98aa4ba446f63e2fe7a2cbca37b39ac933987ee4804f65de121800", size = 4348469 }, + { url = "https://files.pythonhosted.org/packages/45/8f/1f0402ac55c2ae15651ff831957d083fe70b2d12282e72612a30ba601512/torchvision-0.26.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:b7d3e295624a28b3b1769228ce1345d94cf4d390dd31136766f76f2d20f718da", size = 1860826 }, + { url = "https://files.pythonhosted.org/packages/d2/6a/18a582fe3c5ee26f49b5c9fb21ad8016b4d1c06d10178894a58653946fda/torchvision-0.26.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:7058c5878262937e876f20c25867b33724586aa4499e2853b2d52b99a5e51953", size = 7729089 }, + { url = "https://files.pythonhosted.org/packages/c5/9b/f7e119b59499edc00c55c03adc9ec3bd96144d9b81c46852c431f9c64a9a/torchvision-0.26.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:8008474855623c6ba52876589dc52df0aa66e518c25eca841445348e5f79844c", size = 7522704 }, + { url = "https://files.pythonhosted.org/packages/d0/6a/09f3844c10643f6c0de5d95abc863420cfaf194c88c7dffd0ac523e2015f/torchvision-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e9d0e022c19a78552fb055d0414d47fecb4a649309b9968573daea160ba6869c", size = 4454275 }, ] [[package]] name = "tornado" version = "6.5.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/37/1d/0a336abf618272d53f62ebe274f712e213f5a03c0b2339575430b8362ef2/tornado-6.5.4.tar.gz", hash = "sha256:a22fa9047405d03260b483980635f0b041989d8bcc9a313f8fe18b411d84b1d7", size = 513632, upload-time = "2025-12-15T19:21:03.836Z" } +sdist = { url = "https://files.pythonhosted.org/packages/37/1d/0a336abf618272d53f62ebe274f712e213f5a03c0b2339575430b8362ef2/tornado-6.5.4.tar.gz", hash = "sha256:a22fa9047405d03260b483980635f0b041989d8bcc9a313f8fe18b411d84b1d7", size = 513632 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d6241c1a16b1c9e4cc28148b1cda97dd1c6cb4fb7068ac1bedc610768dff0ba9", size = 443909, upload-time = "2025-12-15T19:20:48.382Z" }, - { url = "https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2d50f63dda1d2cac3ae1fa23d254e16b5e38153758470e9956cbc3d813d40843", size = 442163, upload-time = "2025-12-15T19:20:49.791Z" }, - { url = "https://files.pythonhosted.org/packages/ba/b5/206f82d51e1bfa940ba366a8d2f83904b15942c45a78dd978b599870ab44/tornado-6.5.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1cf66105dc6acb5af613c054955b8137e34a03698aa53272dbda4afe252be17", size = 445746, upload-time = "2025-12-15T19:20:51.491Z" }, - { url = "https://files.pythonhosted.org/packages/8e/9d/1a3338e0bd30ada6ad4356c13a0a6c35fbc859063fa7eddb309183364ac1/tornado-6.5.4-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50ff0a58b0dc97939d29da29cd624da010e7f804746621c78d14b80238669335", size = 445083, upload-time = "2025-12-15T19:20:52.778Z" }, - { url = "https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fb5e04efa54cf0baabdd10061eb4148e0be137166146fff835745f59ab9f7f", size = 445315, upload-time = "2025-12-15T19:20:53.996Z" }, - { url = "https://files.pythonhosted.org/packages/27/07/2273972f69ca63dbc139694a3fc4684edec3ea3f9efabf77ed32483b875c/tornado-6.5.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9c86b1643b33a4cd415f8d0fe53045f913bf07b4a3ef646b735a6a86047dda84", size = 446003, upload-time = "2025-12-15T19:20:56.101Z" }, - { url = "https://files.pythonhosted.org/packages/d1/83/41c52e47502bf7260044413b6770d1a48dda2f0246f95ee1384a3cd9c44a/tornado-6.5.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:6eb82872335a53dd063a4f10917b3efd28270b56a33db69009606a0312660a6f", size = 445412, upload-time = "2025-12-15T19:20:57.398Z" }, - { url = "https://files.pythonhosted.org/packages/10/c7/bc96917f06cbee182d44735d4ecde9c432e25b84f4c2086143013e7b9e52/tornado-6.5.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6076d5dda368c9328ff41ab5d9dd3608e695e8225d1cd0fd1e006f05da3635a8", size = 445392, upload-time = "2025-12-15T19:20:58.692Z" }, - { url = "https://files.pythonhosted.org/packages/0c/1a/d7592328d037d36f2d2462f4bc1fbb383eec9278bc786c1b111cbbd44cfa/tornado-6.5.4-cp39-abi3-win32.whl", hash = "sha256:1768110f2411d5cd281bac0a090f707223ce77fd110424361092859e089b38d1", size = 446481, upload-time = "2025-12-15T19:21:00.008Z" }, - { url = "https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl", hash = "sha256:fa07d31e0cd85c60713f2b995da613588aa03e1303d75705dca6af8babc18ddc", size = 446886, upload-time = "2025-12-15T19:21:01.287Z" }, - { url = "https://files.pythonhosted.org/packages/50/49/8dc3fd90902f70084bd2cd059d576ddb4f8bb44c2c7c0e33a11422acb17e/tornado-6.5.4-cp39-abi3-win_arm64.whl", hash = "sha256:053e6e16701eb6cbe641f308f4c1a9541f91b6261991160391bfc342e8a551a1", size = 445910, upload-time = "2025-12-15T19:21:02.571Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d6241c1a16b1c9e4cc28148b1cda97dd1c6cb4fb7068ac1bedc610768dff0ba9", size = 443909 }, + { url = "https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2d50f63dda1d2cac3ae1fa23d254e16b5e38153758470e9956cbc3d813d40843", size = 442163 }, + { url = "https://files.pythonhosted.org/packages/ba/b5/206f82d51e1bfa940ba366a8d2f83904b15942c45a78dd978b599870ab44/tornado-6.5.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1cf66105dc6acb5af613c054955b8137e34a03698aa53272dbda4afe252be17", size = 445746 }, + { url = "https://files.pythonhosted.org/packages/8e/9d/1a3338e0bd30ada6ad4356c13a0a6c35fbc859063fa7eddb309183364ac1/tornado-6.5.4-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50ff0a58b0dc97939d29da29cd624da010e7f804746621c78d14b80238669335", size = 445083 }, + { url = "https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fb5e04efa54cf0baabdd10061eb4148e0be137166146fff835745f59ab9f7f", size = 445315 }, + { url = "https://files.pythonhosted.org/packages/27/07/2273972f69ca63dbc139694a3fc4684edec3ea3f9efabf77ed32483b875c/tornado-6.5.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9c86b1643b33a4cd415f8d0fe53045f913bf07b4a3ef646b735a6a86047dda84", size = 446003 }, + { url = "https://files.pythonhosted.org/packages/d1/83/41c52e47502bf7260044413b6770d1a48dda2f0246f95ee1384a3cd9c44a/tornado-6.5.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:6eb82872335a53dd063a4f10917b3efd28270b56a33db69009606a0312660a6f", size = 445412 }, + { url = "https://files.pythonhosted.org/packages/10/c7/bc96917f06cbee182d44735d4ecde9c432e25b84f4c2086143013e7b9e52/tornado-6.5.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6076d5dda368c9328ff41ab5d9dd3608e695e8225d1cd0fd1e006f05da3635a8", size = 445392 }, + { url = "https://files.pythonhosted.org/packages/0c/1a/d7592328d037d36f2d2462f4bc1fbb383eec9278bc786c1b111cbbd44cfa/tornado-6.5.4-cp39-abi3-win32.whl", hash = "sha256:1768110f2411d5cd281bac0a090f707223ce77fd110424361092859e089b38d1", size = 446481 }, + { url = "https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl", hash = "sha256:fa07d31e0cd85c60713f2b995da613588aa03e1303d75705dca6af8babc18ddc", size = 446886 }, + { url = "https://files.pythonhosted.org/packages/50/49/8dc3fd90902f70084bd2cd059d576ddb4f8bb44c2c7c0e33a11422acb17e/tornado-6.5.4-cp39-abi3-win_arm64.whl", hash = "sha256:053e6e16701eb6cbe641f308f4c1a9541f91b6261991160391bfc342e8a551a1", size = 445910 }, ] [[package]] @@ -3559,18 +3613,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } +sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598 } wheels = [ - { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, + { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374 }, ] [[package]] name = "traitlets" version = "5.14.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621 } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359 }, ] [[package]] @@ -3578,48 +3632,54 @@ name = "triton" version = "3.6.0" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/12/b05ba554d2c623bffa59922b94b0775673de251f468a9609bc9e45de95e9/triton-3.6.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8e323d608e3a9bfcc2d9efcc90ceefb764a82b99dea12a86d643c72539ad5d3", size = 188214640, upload-time = "2026-01-20T16:00:35.869Z" }, - { url = "https://files.pythonhosted.org/packages/ab/a8/cdf8b3e4c98132f965f88c2313a4b493266832ad47fb52f23d14d4f86bb5/triton-3.6.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74caf5e34b66d9f3a429af689c1c7128daba1d8208df60e81106b115c00d6fca", size = 188266850, upload-time = "2026-01-20T16:00:43.041Z" }, - { url = "https://files.pythonhosted.org/packages/f9/0b/37d991d8c130ce81a8728ae3c25b6e60935838e9be1b58791f5997b24a54/triton-3.6.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10c7f76c6e72d2ef08df639e3d0d30729112f47a56b0c81672edc05ee5116ac9", size = 188289450, upload-time = "2026-01-20T16:00:49.136Z" }, - { url = "https://files.pythonhosted.org/packages/35/f8/9c66bfc55361ec6d0e4040a0337fb5924ceb23de4648b8a81ae9d33b2b38/triton-3.6.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d002e07d7180fd65e622134fbd980c9a3d4211fb85224b56a0a0efbd422ab72f", size = 188400296, upload-time = "2026-01-20T16:00:56.042Z" }, - { url = "https://files.pythonhosted.org/packages/df/3d/9e7eee57b37c80cec63322c0231bb6da3cfe535a91d7a4d64896fcb89357/triton-3.6.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a17a5d5985f0ac494ed8a8e54568f092f7057ef60e1b0fa09d3fd1512064e803", size = 188273063, upload-time = "2026-01-20T16:01:07.278Z" }, - { url = "https://files.pythonhosted.org/packages/f6/56/6113c23ff46c00aae423333eb58b3e60bdfe9179d542781955a5e1514cb3/triton-3.6.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46bd1c1af4b6704e554cad2eeb3b0a6513a980d470ccfa63189737340c7746a7", size = 188397994, upload-time = "2026-01-20T16:01:14.236Z" }, + { url = "https://files.pythonhosted.org/packages/0f/2c/96f92f3c60387e14cc45aed49487f3486f89ea27106c1b1376913c62abe4/triton-3.6.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49df5ef37379c0c2b5c0012286f80174fcf0e073e5ade1ca9a86c36814553651", size = 176081190 }, + { url = "https://files.pythonhosted.org/packages/e0/12/b05ba554d2c623bffa59922b94b0775673de251f468a9609bc9e45de95e9/triton-3.6.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8e323d608e3a9bfcc2d9efcc90ceefb764a82b99dea12a86d643c72539ad5d3", size = 188214640 }, + { url = "https://files.pythonhosted.org/packages/17/5d/08201db32823bdf77a0e2b9039540080b2e5c23a20706ddba942924ebcd6/triton-3.6.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:374f52c11a711fd062b4bfbb201fd9ac0a5febd28a96fb41b4a0f51dde3157f4", size = 176128243 }, + { url = "https://files.pythonhosted.org/packages/ab/a8/cdf8b3e4c98132f965f88c2313a4b493266832ad47fb52f23d14d4f86bb5/triton-3.6.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74caf5e34b66d9f3a429af689c1c7128daba1d8208df60e81106b115c00d6fca", size = 188266850 }, + { url = "https://files.pythonhosted.org/packages/3c/12/34d71b350e89a204c2c7777a9bba0dcf2f19a5bfdd70b57c4dbc5ffd7154/triton-3.6.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448e02fe6dc898e9e5aa89cf0ee5c371e99df5aa5e8ad976a80b93334f3494fd", size = 176133521 }, + { url = "https://files.pythonhosted.org/packages/f9/0b/37d991d8c130ce81a8728ae3c25b6e60935838e9be1b58791f5997b24a54/triton-3.6.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10c7f76c6e72d2ef08df639e3d0d30729112f47a56b0c81672edc05ee5116ac9", size = 188289450 }, + { url = "https://files.pythonhosted.org/packages/ce/4e/41b0c8033b503fd3cfcd12392cdd256945026a91ff02452bef40ec34bee7/triton-3.6.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1722e172d34e32abc3eb7711d0025bb69d7959ebea84e3b7f7a341cd7ed694d6", size = 176276087 }, + { url = "https://files.pythonhosted.org/packages/35/f8/9c66bfc55361ec6d0e4040a0337fb5924ceb23de4648b8a81ae9d33b2b38/triton-3.6.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d002e07d7180fd65e622134fbd980c9a3d4211fb85224b56a0a0efbd422ab72f", size = 188400296 }, + { url = "https://files.pythonhosted.org/packages/49/55/5ecf0dcaa0f2fbbd4420f7ef227ee3cb172e91e5fede9d0ecaddc43363b4/triton-3.6.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5523241e7d1abca00f1d240949eebdd7c673b005edbbce0aca95b8191f1d43", size = 176138577 }, + { url = "https://files.pythonhosted.org/packages/df/3d/9e7eee57b37c80cec63322c0231bb6da3cfe535a91d7a4d64896fcb89357/triton-3.6.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a17a5d5985f0ac494ed8a8e54568f092f7057ef60e1b0fa09d3fd1512064e803", size = 188273063 }, + { url = "https://files.pythonhosted.org/packages/48/db/56ee649cab5eaff4757541325aca81f52d02d4a7cd3506776cad2451e060/triton-3.6.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b3a97e8ed304dfa9bd23bb41ca04cdf6b2e617d5e782a8653d616037a5d537d", size = 176274804 }, + { url = "https://files.pythonhosted.org/packages/f6/56/6113c23ff46c00aae423333eb58b3e60bdfe9179d542781955a5e1514cb3/triton-3.6.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46bd1c1af4b6704e554cad2eeb3b0a6513a980d470ccfa63189737340c7746a7", size = 188397994 }, ] [[package]] name = "typing-extensions" version = "4.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391 } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614 }, ] [[package]] name = "tzdata" version = "2025.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521 }, ] [[package]] name = "uri-template" version = "1.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/31/c7/0336f2bd0bcbada6ccef7aaa25e443c118a704f828a0620c6fa0207c1b64/uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7", size = 21678, upload-time = "2023-06-21T01:49:05.374Z" } +sdist = { url = "https://files.pythonhosted.org/packages/31/c7/0336f2bd0bcbada6ccef7aaa25e443c118a704f828a0620c6fa0207c1b64/uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7", size = 21678 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363", size = 11140, upload-time = "2023-06-21T01:49:03.467Z" }, + { url = "https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363", size = 11140 }, ] [[package]] name = "urllib3" version = "2.6.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556 } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584 }, ] [[package]] @@ -3631,45 +3691,45 @@ dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d2/03/a94d404ca09a89a7301a7008467aed525d4cdeb9186d262154dd23208709/virtualenv-20.38.0.tar.gz", hash = "sha256:94f39b1abaea5185bf7ea5a46702b56f1d0c9aa2f41a6c2b8b0af4ddc74c10a7", size = 5864558, upload-time = "2026-02-19T07:48:02.385Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d2/03/a94d404ca09a89a7301a7008467aed525d4cdeb9186d262154dd23208709/virtualenv-20.38.0.tar.gz", hash = "sha256:94f39b1abaea5185bf7ea5a46702b56f1d0c9aa2f41a6c2b8b0af4ddc74c10a7", size = 5864558 } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/d7/394801755d4c8684b655d35c665aea7836ec68320304f62ab3c94395b442/virtualenv-20.38.0-py3-none-any.whl", hash = "sha256:d6e78e5889de3a4742df2d3d44e779366325a90cf356f15621fddace82431794", size = 5837778, upload-time = "2026-02-19T07:47:59.778Z" }, + { url = "https://files.pythonhosted.org/packages/42/d7/394801755d4c8684b655d35c665aea7836ec68320304f62ab3c94395b442/virtualenv-20.38.0-py3-none-any.whl", hash = "sha256:d6e78e5889de3a4742df2d3d44e779366325a90cf356f15621fddace82431794", size = 5837778 }, ] [[package]] name = "wcwidth" version = "0.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/35/a2/8e3becb46433538a38726c948d3399905a4c7cabd0df578ede5dc51f0ec2/wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159", size = 159684, upload-time = "2026-02-06T19:19:40.919Z" } +sdist = { url = "https://files.pythonhosted.org/packages/35/a2/8e3becb46433538a38726c948d3399905a4c7cabd0df578ede5dc51f0ec2/wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159", size = 159684 } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189, upload-time = "2026-02-06T19:19:39.646Z" }, + { url = "https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189 }, ] [[package]] name = "webcolors" version = "25.10.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/7a/eb316761ec35664ea5174709a68bbd3389de60d4a1ebab8808bfc264ed67/webcolors-25.10.0.tar.gz", hash = "sha256:62abae86504f66d0f6364c2a8520de4a0c47b80c03fc3a5f1815fedbef7c19bf", size = 53491, upload-time = "2025-10-31T07:51:03.977Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/7a/eb316761ec35664ea5174709a68bbd3389de60d4a1ebab8808bfc264ed67/webcolors-25.10.0.tar.gz", hash = "sha256:62abae86504f66d0f6364c2a8520de4a0c47b80c03fc3a5f1815fedbef7c19bf", size = 53491 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl", hash = "sha256:032c727334856fc0b968f63daa252a1ac93d33db2f5267756623c210e57a4f1d", size = 14905, upload-time = "2025-10-31T07:51:01.778Z" }, + { url = "https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl", hash = "sha256:032c727334856fc0b968f63daa252a1ac93d33db2f5267756623c210e57a4f1d", size = 14905 }, ] [[package]] name = "webencodings" version = "0.5.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721, upload-time = "2017-04-05T20:21:34.189Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" }, + { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774 }, ] [[package]] name = "websocket-client" version = "1.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576, upload-time = "2025-10-07T21:16:36.495Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576 } wheels = [ - { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616, upload-time = "2025-10-07T21:16:34.951Z" }, + { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616 }, ] [[package]] @@ -3679,18 +3739,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/61/f1/ee81806690a87dab5f5653c1f146c92bc066d7f4cebc603ef88eb9e13957/werkzeug-3.1.6.tar.gz", hash = "sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25", size = 864736, upload-time = "2026-02-19T15:17:18.884Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/f1/ee81806690a87dab5f5653c1f146c92bc066d7f4cebc603ef88eb9e13957/werkzeug-3.1.6.tar.gz", hash = "sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25", size = 864736 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/ec/d58832f89ede95652fd01f4f24236af7d32b70cab2196dfcc2d2fd13c5c2/werkzeug-3.1.6-py3-none-any.whl", hash = "sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131", size = 225166, upload-time = "2026-02-19T15:17:17.475Z" }, + { url = "https://files.pythonhosted.org/packages/4d/ec/d58832f89ede95652fd01f4f24236af7d32b70cab2196dfcc2d2fd13c5c2/werkzeug-3.1.6-py3-none-any.whl", hash = "sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131", size = 225166 }, ] [[package]] name = "widgetsnbextension" version = "4.0.15" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bd/f4/c67440c7fb409a71b7404b7aefcd7569a9c0d6bd071299bf4198ae7a5d95/widgetsnbextension-4.0.15.tar.gz", hash = "sha256:de8610639996f1567952d763a5a41af8af37f2575a41f9852a38f947eb82a3b9", size = 1097402, upload-time = "2025-11-01T21:15:55.178Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/f4/c67440c7fb409a71b7404b7aefcd7569a9c0d6bd071299bf4198ae7a5d95/widgetsnbextension-4.0.15.tar.gz", hash = "sha256:de8610639996f1567952d763a5a41af8af37f2575a41f9852a38f947eb82a3b9", size = 1097402 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl", hash = "sha256:8156704e4346a571d9ce73b84bee86a29906c9abfd7223b7228a28899ccf3366", size = 2196503, upload-time = "2025-11-01T21:15:53.565Z" }, + { url = "https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl", hash = "sha256:8156704e4346a571d9ce73b84bee86a29906c9abfd7223b7228a28899ccf3366", size = 2196503 }, ] [[package]] @@ -3705,16 +3765,16 @@ dependencies = [ { name = "packaging" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/76/7fa87f57c112c7b9c82f0a730f8b6f333e792574812872e2cd45ab604199/zarr-3.1.5.tar.gz", hash = "sha256:fbe0c79675a40c996de7ca08e80a1c0a20537bd4a9f43418b6d101395c0bba2b", size = 366825, upload-time = "2025-11-21T14:06:01.492Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/76/7fa87f57c112c7b9c82f0a730f8b6f333e792574812872e2cd45ab604199/zarr-3.1.5.tar.gz", hash = "sha256:fbe0c79675a40c996de7ca08e80a1c0a20537bd4a9f43418b6d101395c0bba2b", size = 366825 } wheels = [ - { url = "https://files.pythonhosted.org/packages/44/15/bb13b4913ef95ad5448490821eee4671d0e67673342e4d4070854e5fe081/zarr-3.1.5-py3-none-any.whl", hash = "sha256:29cd905afb6235b94c09decda4258c888fcb79bb6c862ef7c0b8fe009b5c8563", size = 284067, upload-time = "2025-11-21T14:05:59.235Z" }, + { url = "https://files.pythonhosted.org/packages/44/15/bb13b4913ef95ad5448490821eee4671d0e67673342e4d4070854e5fe081/zarr-3.1.5-py3-none-any.whl", hash = "sha256:29cd905afb6235b94c09decda4258c888fcb79bb6c862ef7c0b8fe009b5c8563", size = 284067 }, ] [[package]] name = "zipp" version = "3.23.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276 }, ] From 70ecd70e748067934c3fa1a8ad1e495673af4044 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Mar 2026 17:21:11 -0700 Subject: [PATCH 196/335] Added show_metrics to SIRT reconstructions as well --- src/quantem/tomography/tomography.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index a5fcccfa..6491ef3c 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -443,6 +443,7 @@ def reconstruct( reset: bool = False, inline_alignment: bool = False, smoothing_sigma: float | None = None, + show_metrics: bool = False, ): if obj_constraints is not None: if isinstance(obj_constraints, dict): @@ -491,6 +492,15 @@ def reconstruct( if mode == "fbp": break + if show_metrics: + fig, ax = plt.subplots() + ax.plot(self._epoch_losses) + ax.set_xlabel("Iteration") + ax.set_ylabel("Loss") + ax.set_title("Reconstruction Loss") + ax.set_yscale("log") + plt.show() + # --- Conventional reconstruction method --- def _adaptive_relaxation(self, n_power_iter: int = 10) -> float: raise NotImplementedError( From 1517b33a97bd4ff748c47fc8ff711acc118df05b Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Mon, 23 Mar 2026 17:40:41 -0700 Subject: [PATCH 197/335] updating viz with ShowParams --- src/quantem/core/visualization/__init__.py | 1 + .../visualization/custom_normalizations.py | 13 +++-- .../core/visualization/visualization.py | 56 ++++++++++++++----- .../core/visualization/visualization_utils.py | 25 ++++++++- 4 files changed, 75 insertions(+), 20 deletions(-) diff --git a/src/quantem/core/visualization/__init__.py b/src/quantem/core/visualization/__init__.py index dcf7055b..d6f8dc95 100644 --- a/src/quantem/core/visualization/__init__.py +++ b/src/quantem/core/visualization/__init__.py @@ -2,6 +2,7 @@ CustomNormalization as CustomNormalization, NormalizationConfig as NormalizationConfig, ) +from quantem.core.visualization.show_params import ShowParams as ShowParams from quantem.core.visualization.visualization import show_2d as show_2d from quantem.core.visualization.visualization_utils import ( ScalebarConfig as ScalebarConfig, diff --git a/src/quantem/core/visualization/custom_normalizations.py b/src/quantem/core/visualization/custom_normalizations.py index ac3a015c..554af378 100644 --- a/src/quantem/core/visualization/custom_normalizations.py +++ b/src/quantem/core/visualization/custom_normalizations.py @@ -1,5 +1,6 @@ from abc import ABC, abstractmethod from dataclasses import dataclass +from typing import Literal import numpy as np from matplotlib import colors @@ -461,8 +462,8 @@ class CustomNormalization(colors.Normalize): def __init__( self, - interval_type: str = "quantile", - stretch_type: str = "linear", + interval_type: Literal["quantile", "manual", "centered"] = "quantile", + stretch_type: Literal["linear", "power", "logarithmic", "asinh"] = "linear", *, data: NDArray | None = None, lower_quantile: float = 0.02, @@ -599,8 +600,8 @@ class NormalizationConfig: Linear range for "asinh" stretch type. """ - interval_type: str = "quantile" - stretch_type: str = "linear" + interval_type: Literal["quantile", "manual", "centered"] = "quantile" + stretch_type: Literal["linear", "power", "logarithmic", "asinh"] = "linear" lower_quantile: float = 0.02 upper_quantile: float = 0.98 vmin: float | None = None @@ -673,5 +674,7 @@ def _resolve_normalization(norm, **kwargs) -> NormalizationConfig: return NORMALIZATION_PRESETS[norm]() elif isinstance(norm, NormalizationConfig): return norm + elif hasattr(norm, "to_config"): + return norm.to_config() else: - raise TypeError("norm must be None, dict, str, or NormalizationConfig") + raise TypeError("norm must be None, dict, str, NormalizationConfig, or ShowParams.Norm") diff --git a/src/quantem/core/visualization/visualization.py b/src/quantem/core/visualization/visualization.py index ae3dd508..72400527 100644 --- a/src/quantem/core/visualization/visualization.py +++ b/src/quantem/core/visualization/visualization.py @@ -1,7 +1,9 @@ +from __future__ import annotations + import os import warnings from collections.abc import Sequence -from typing import Any, Optional, Union, cast +from typing import TYPE_CHECKING, Any, Optional, Union, cast import matplotlib as mpl import matplotlib.pyplot as plt @@ -17,6 +19,7 @@ NormalizationConfig, _resolve_normalization, ) +from quantem.core.visualization.show_params import ShowParams from quantem.core.visualization.visualization_utils import ( ScalebarConfig, _resolve_scalebar, @@ -27,12 +30,17 @@ list_of_arrays_to_rgba, ) +if TYPE_CHECKING: + import torch + +ArrayLike = Union[NDArray, "torch.Tensor"] + def _show_2d_array( array: NDArray, *, - norm: Optional[Union[NormalizationConfig, dict, str]] = None, - scalebar: Optional[Union[ScalebarConfig, dict, bool]] = None, + norm: Optional[Union[NormalizationConfig, ShowParams.Norm, dict, str]] = None, + scalebar: Optional[Union[ScalebarConfig, ShowParams.Scalebar, dict, bool]] = None, cmap: Union[str, colors.Colormap] = "gray", chroma_boost: float = 1.0, cbar: bool = False, @@ -151,6 +159,8 @@ def _show_2d_array( scalebar_config.pad_px, scalebar_config.color, scalebar_config.loc, + scalebar_config.fontsize, + scalebar_config.bold, ) for spine in ax.spines.values(): # fixes asymmetry of bbox for some reason @@ -163,8 +173,8 @@ def _show_2d_array( def _show_2d_combined( list_of_arrays: Sequence[NDArray], *, - norm: Optional[Union[NormalizationConfig, dict, str]] = None, - scalebar: Optional[Union[ScalebarConfig, dict, bool]] = None, + norm: Optional[Union[NormalizationConfig, ShowParams.Norm, dict, str]] = None, + scalebar: Optional[Union[ScalebarConfig, ShowParams.Scalebar, dict, bool]] = None, cmap: Union[str, colors.Colormap] = "gray", chroma_boost: float = 1.0, cbar: bool = False, @@ -267,6 +277,8 @@ def _show_2d_combined( scalebar_config.pad_px, scalebar_config.color, scalebar_config.loc, + scalebar_config.fontsize, + scalebar_config.bold, ) return fig, ax @@ -389,8 +401,18 @@ def _norm_show_args( def _normalize_show_args_to_grid( shape: tuple[int, int], - norm: NormalizationConfig | dict | str | Sequence[dict | str] | None = None, - scalebar: ScalebarConfig | dict | bool | Sequence[bool | dict | None] | None = None, + norm: NormalizationConfig + | ShowParams.Norm + | dict + | str + | Sequence[NormalizationConfig | ShowParams.Norm | dict | str] + | None = None, + scalebar: ScalebarConfig + | ShowParams.Scalebar + | dict + | bool + | Sequence[ScalebarConfig | ShowParams.Scalebar | dict | bool | None] + | None = None, cmap: str | colors.Colormap | Sequence[str] | Sequence[Sequence[str]] = "gray", cbar: bool | Sequence[bool] | Sequence[Sequence[bool]] = False, title: str | Sequence[str] | Sequence[Sequence[str]] | None = None, @@ -425,14 +447,22 @@ def _normalize_show_args_to_grid( def show_2d( - arrays: Union[NDArray, Sequence[NDArray], Sequence[Sequence[NDArray]]], + arrays: ArrayLike | Sequence[ArrayLike] | Sequence[Sequence[ArrayLike]], *, - norm: NormalizationConfig + norm: ( + NormalizationConfig + | ShowParams.Norm + | dict + | str + | Sequence[NormalizationConfig | ShowParams.Norm | dict | str] + | None + ) = None, + scalebar: ScalebarConfig + | ShowParams.Scalebar | dict - | str - | Sequence[dict | str] - | None = None, # TODO: Revamp NormalizationConfig, ScalebarConfig with Dataclasses - scalebar: ScalebarConfig | dict | bool | Sequence[bool | dict | None] | None = None, + | bool + | Sequence[ScalebarConfig | ShowParams.Scalebar | dict | bool | None] + | None = None, cmap: str | colors.Colormap | Sequence[str] | Sequence[Sequence[str]] = "gray", cbar: bool | Sequence[bool] | Sequence[Sequence[bool]] = False, title: str | Sequence[str] | Sequence[Sequence[str]] | None = None, diff --git a/src/quantem/core/visualization/visualization_utils.py b/src/quantem/core/visualization/visualization_utils.py index b69e3c86..d3111476 100644 --- a/src/quantem/core/visualization/visualization_utils.py +++ b/src/quantem/core/visualization/visualization_utils.py @@ -139,6 +139,10 @@ class ScalebarConfig: loc : str or int, default="lower right" Location of the scale bar on the plot. Can be a string like "lower right" or an integer location code. + fontsize : int, default=12 + Font size of the scale bar label in points. + bold : bool, default=True + Whether to render the scale bar label in bold. """ sampling: float = 1.0 @@ -148,6 +152,8 @@ class ScalebarConfig: pad_px: float = 0.5 color: str = "white" loc: Union[str, int] = "lower right" + fontsize: int = 12 + bold: bool = False SCALEBAR_KWARGS = [ @@ -190,8 +196,12 @@ def _resolve_scalebar(cfg: Any, **kwargs) -> Optional[ScalebarConfig]: return ScalebarConfig(**cfg) elif isinstance(cfg, ScalebarConfig): return cfg + elif hasattr(cfg, "to_config"): + return cfg.to_config() else: - raise TypeError("scalebar must be None, dict, bool, or ScalebarConfig") + raise TypeError( + "scalebar must be None, dict, bool, ScalebarConfig, or ShowParams.Scalebar" + ) def estimate_scalebar_length(length: float, sampling: float) -> Tuple[float, float]: @@ -273,6 +283,8 @@ def add_scalebar_to_ax( pad_px: float, color: str, loc: Union[str, int], + fontsize: int = 12, + bold: bool = True, ) -> None: """Add a scale bar to a matplotlib axis. @@ -297,7 +309,13 @@ def add_scalebar_to_ax( Color of the scale bar. loc : str or int Location of the scale bar on the plot. + fontsize : int + Font size of the scale bar label in points. + bold : bool + Whether to render the scale bar label in bold. """ + from matplotlib.font_manager import FontProperties + if length_units is None: length_units, length_px = estimate_scalebar_length(array_size, sampling) else: @@ -315,6 +333,8 @@ def add_scalebar_to_ax( loc_strings = {v: k for k, v in loc_codes.items()} loc = loc_strings[loc] + fontprops = FontProperties(size=fontsize, weight="bold" if bold else "normal") + bar = AnchoredSizeBar( ax.transData, length_px, @@ -324,7 +344,8 @@ def add_scalebar_to_ax( color=color, frameon=False, label_top=loc[:3] == "low", - size_vertical=int(width_px), # Convert to int as required by AnchoredSizeBar + size_vertical=int(width_px), + fontproperties=fontprops, ) ax.add_artist(bar) From c91148f83c8ef1ee7323e3e6447809778af0261b Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Mar 2026 17:43:13 -0700 Subject: [PATCH 198/335] Added a check to make sure that if a list(tuple) is given it should match the number of iterations, otherwise raises an error --- src/quantem/tomography/tomography.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 6491ef3c..ea990169 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -156,6 +156,10 @@ def reconstruct( if isinstance(num_samples_per_ray, int): num_samples_per_ray = num_samples_per_ray else: + if len(num_samples_per_ray) != num_iter: + raise ValueError( + "num_samples_per_ray schedule must have the same length as num_iter" + ) print("num_samples_per_ray schedule provided.") loss_func = get_loss_module(name=loss_type, dtype=self.obj_model.dtype, **loss_func_kwargs) From c2f46fed7999ec15babcc54c38a9155e137d7e9d Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Mar 2026 19:52:01 -0700 Subject: [PATCH 199/335] Setting up DDP for obj_model print statement in TomographyBase now only set to global_rank = 0 --- src/quantem/tomography/tomography_base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/quantem/tomography/tomography_base.py b/src/quantem/tomography/tomography_base.py index 8c747c63..7e2670fb 100644 --- a/src/quantem/tomography/tomography_base.py +++ b/src/quantem/tomography/tomography_base.py @@ -52,7 +52,8 @@ def __init__( self._lrs: dict[str, float] = {} # DDP Initialization if isinstance(obj_model, ObjectINR): - print("Setting up DDP for obj_model") + if self.global_rank == 0: + print("Setting up DDP for obj_model") self.setup_distributed(device=device) self.dset = dset From 8e1c1ef166a632ee73928d35bfb157192df95887 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Mar 2026 19:54:35 -0700 Subject: [PATCH 200/335] uv.lock fix? --- uv.lock | 3776 +++++++++++++++++++++++++++---------------------------- 1 file changed, 1841 insertions(+), 1935 deletions(-) diff --git a/uv.lock b/uv.lock index 8c11022d..7d2b81dc 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 1 +revision = 3 requires-python = ">=3.11" resolution-markers = [ "python_full_version >= '3.14'", @@ -17,9 +17,9 @@ members = [ name = "absl-py" version = "2.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/64/c7/8de93764ad66968d19329a7e0c147a2bb3c7054c554d4a119111b8f9440f/absl_py-2.4.0.tar.gz", hash = "sha256:8c6af82722b35cf71e0f4d1d47dcaebfff286e27110a99fc359349b247dfb5d4", size = 116543 } +sdist = { url = "https://files.pythonhosted.org/packages/64/c7/8de93764ad66968d19329a7e0c147a2bb3c7054c554d4a119111b8f9440f/absl_py-2.4.0.tar.gz", hash = "sha256:8c6af82722b35cf71e0f4d1d47dcaebfff286e27110a99fc359349b247dfb5d4", size = 116543, upload-time = "2026-01-28T10:17:05.322Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/a6/907a406bb7d359e6a63f99c313846d9eec4f7e6f7437809e03aa00fa3074/absl_py-2.4.0-py3-none-any.whl", hash = "sha256:88476fd881ca8aab94ffa78b7b6c632a782ab3ba1cd19c9bd423abc4fb4cd28d", size = 135750 }, + { url = "https://files.pythonhosted.org/packages/18/a6/907a406bb7d359e6a63f99c313846d9eec4f7e6f7437809e03aa00fa3074/absl_py-2.4.0-py3-none-any.whl", hash = "sha256:88476fd881ca8aab94ffa78b7b6c632a782ab3ba1cd19c9bd423abc4fb4cd28d", size = 135750, upload-time = "2026-01-28T10:17:04.19Z" }, ] [[package]] @@ -31,9 +31,9 @@ dependencies = [ { name = "sqlalchemy" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/94/13/8b084e0f2efb0275a1d534838844926f798bd766566b1375174e2448cd31/alembic-1.18.4.tar.gz", hash = "sha256:cb6e1fd84b6174ab8dbb2329f86d631ba9559dd78df550b57804d607672cedbc", size = 2056725 } +sdist = { url = "https://files.pythonhosted.org/packages/94/13/8b084e0f2efb0275a1d534838844926f798bd766566b1375174e2448cd31/alembic-1.18.4.tar.gz", hash = "sha256:cb6e1fd84b6174ab8dbb2329f86d631ba9559dd78df550b57804d607672cedbc", size = 2056725, upload-time = "2026-02-10T16:00:47.195Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/29/6533c317b74f707ea28f8d633734dbda2119bbadfc61b2f3640ba835d0f7/alembic-1.18.4-py3-none-any.whl", hash = "sha256:a5ed4adcf6d8a4cb575f3d759f071b03cd6e5c7618eb796cb52497be25bfe19a", size = 263893 }, + { url = "https://files.pythonhosted.org/packages/d2/29/6533c317b74f707ea28f8d633734dbda2119bbadfc61b2f3640ba835d0f7/alembic-1.18.4-py3-none-any.whl", hash = "sha256:a5ed4adcf6d8a4cb575f3d759f071b03cd6e5c7618eb796cb52497be25bfe19a", size = 263893, upload-time = "2026-02-10T16:00:49.997Z" }, ] [[package]] @@ -44,9 +44,9 @@ dependencies = [ { name = "idna" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685 } +sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592 }, + { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, ] [[package]] @@ -58,18 +58,18 @@ dependencies = [ { name = "psygnal" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/be/5e/cbea445bf062b81e4d366ca29dae4f0aedc7a64f384afc24670e07bec560/anywidget-0.9.21.tar.gz", hash = "sha256:b8d0172029ac426573053c416c6a587838661612208bb390fa0607862e594b27", size = 390517 } +sdist = { url = "https://files.pythonhosted.org/packages/be/5e/cbea445bf062b81e4d366ca29dae4f0aedc7a64f384afc24670e07bec560/anywidget-0.9.21.tar.gz", hash = "sha256:b8d0172029ac426573053c416c6a587838661612208bb390fa0607862e594b27", size = 390517, upload-time = "2025-11-12T17:06:03.035Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/03/c17464bbf682ea87e7e3de2ddc63395e359a78ae9c01f55fc78759ecbd79/anywidget-0.9.21-py3-none-any.whl", hash = "sha256:78c268e0fbdb1dfd15da37fb578f9cf0a0df58a430e68d9156942b7a9391a761", size = 231797 }, + { url = "https://files.pythonhosted.org/packages/5b/03/c17464bbf682ea87e7e3de2ddc63395e359a78ae9c01f55fc78759ecbd79/anywidget-0.9.21-py3-none-any.whl", hash = "sha256:78c268e0fbdb1dfd15da37fb578f9cf0a0df58a430e68d9156942b7a9391a761", size = 231797, upload-time = "2025-11-12T17:06:01.564Z" }, ] [[package]] name = "appnope" version = "0.1.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170 } +sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170, upload-time = "2024-02-06T09:43:11.258Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321 }, + { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321, upload-time = "2024-02-06T09:43:09.663Z" }, ] [[package]] @@ -79,9 +79,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "argon2-cffi-bindings" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706 } +sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657 }, + { url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657, upload-time = "2025-06-03T06:55:30.804Z" }, ] [[package]] @@ -91,28 +91,28 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/60/97/3c0a35f46e52108d4707c44b95cfe2afcafc50800b5450c197454569b776/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3d3f05610594151994ca9ccb3c771115bdb4daef161976a266f0dd8aa9996b8f", size = 54393 }, - { url = "https://files.pythonhosted.org/packages/9d/f4/98bbd6ee89febd4f212696f13c03ca302b8552e7dbf9c8efa11ea4a388c3/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8b8efee945193e667a396cbc7b4fb7d357297d6234d30a489905d96caabde56b", size = 29328 }, - { url = "https://files.pythonhosted.org/packages/43/24/90a01c0ef12ac91a6be05969f29944643bc1e5e461155ae6559befa8f00b/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3c6702abc36bf3ccba3f802b799505def420a1b7039862014a65db3205967f5a", size = 31269 }, - { url = "https://files.pythonhosted.org/packages/d4/d3/942aa10782b2697eee7af5e12eeff5ebb325ccfb86dd8abda54174e377e4/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1c70058c6ab1e352304ac7e3b52554daadacd8d453c1752e547c76e9c99ac44", size = 86558 }, - { url = "https://files.pythonhosted.org/packages/0d/82/b484f702fec5536e71836fc2dbc8c5267b3f6e78d2d539b4eaa6f0db8bf8/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2fd3bfbff3c5d74fef31a722f729bf93500910db650c925c2d6ef879a7e51cb", size = 92364 }, - { url = "https://files.pythonhosted.org/packages/c9/c1/a606ff83b3f1735f3759ad0f2cd9e038a0ad11a3de3b6c673aa41c24bb7b/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4f9665de60b1b0e99bcd6be4f17d90339698ce954cfd8d9cf4f91c995165a92", size = 85637 }, - { url = "https://files.pythonhosted.org/packages/44/b4/678503f12aceb0262f84fa201f6027ed77d71c5019ae03b399b97caa2f19/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ba92837e4a9aa6a508c8d2d7883ed5a8f6c308c89a4790e1e447a220deb79a85", size = 91934 }, - { url = "https://files.pythonhosted.org/packages/f0/c7/f36bd08ef9bd9f0a9cff9428406651f5937ce27b6c5b07b92d41f91ae541/argon2_cffi_bindings-25.1.0-cp314-cp314t-win32.whl", hash = "sha256:84a461d4d84ae1295871329b346a97f68eade8c53b6ed9a7ca2d7467f3c8ff6f", size = 28158 }, - { url = "https://files.pythonhosted.org/packages/b3/80/0106a7448abb24a2c467bf7d527fe5413b7fdfa4ad6d6a96a43a62ef3988/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b55aec3565b65f56455eebc9b9f34130440404f27fe21c3b375bf1ea4d8fbae6", size = 32597 }, - { url = "https://files.pythonhosted.org/packages/05/b8/d663c9caea07e9180b2cb662772865230715cbd573ba3b5e81793d580316/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:87c33a52407e4c41f3b70a9c2d3f6056d88b10dad7695be708c5021673f55623", size = 28231 }, - { url = "https://files.pythonhosted.org/packages/1d/57/96b8b9f93166147826da5f90376e784a10582dd39a393c99bb62cfcf52f0/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500", size = 54121 }, - { url = "https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44", size = 29177 }, - { url = "https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0", size = 31090 }, - { url = "https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6", size = 81246 }, - { url = "https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a", size = 87126 }, - { url = "https://files.pythonhosted.org/packages/72/70/7a2993a12b0ffa2a9271259b79cc616e2389ed1a4d93842fac5a1f923ffd/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d", size = 80343 }, - { url = "https://files.pythonhosted.org/packages/78/9a/4e5157d893ffc712b74dbd868c7f62365618266982b64accab26bab01edc/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99", size = 86777 }, - { url = "https://files.pythonhosted.org/packages/74/cd/15777dfde1c29d96de7f18edf4cc94c385646852e7c7b0320aa91ccca583/argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2", size = 27180 }, - { url = "https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98", size = 31715 }, - { url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149 }, +sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441, upload-time = "2025-07-30T10:02:05.147Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/97/3c0a35f46e52108d4707c44b95cfe2afcafc50800b5450c197454569b776/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3d3f05610594151994ca9ccb3c771115bdb4daef161976a266f0dd8aa9996b8f", size = 54393, upload-time = "2025-07-30T10:01:40.97Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f4/98bbd6ee89febd4f212696f13c03ca302b8552e7dbf9c8efa11ea4a388c3/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8b8efee945193e667a396cbc7b4fb7d357297d6234d30a489905d96caabde56b", size = 29328, upload-time = "2025-07-30T10:01:41.916Z" }, + { url = "https://files.pythonhosted.org/packages/43/24/90a01c0ef12ac91a6be05969f29944643bc1e5e461155ae6559befa8f00b/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3c6702abc36bf3ccba3f802b799505def420a1b7039862014a65db3205967f5a", size = 31269, upload-time = "2025-07-30T10:01:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/d4/d3/942aa10782b2697eee7af5e12eeff5ebb325ccfb86dd8abda54174e377e4/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1c70058c6ab1e352304ac7e3b52554daadacd8d453c1752e547c76e9c99ac44", size = 86558, upload-time = "2025-07-30T10:01:43.943Z" }, + { url = "https://files.pythonhosted.org/packages/0d/82/b484f702fec5536e71836fc2dbc8c5267b3f6e78d2d539b4eaa6f0db8bf8/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2fd3bfbff3c5d74fef31a722f729bf93500910db650c925c2d6ef879a7e51cb", size = 92364, upload-time = "2025-07-30T10:01:44.887Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c1/a606ff83b3f1735f3759ad0f2cd9e038a0ad11a3de3b6c673aa41c24bb7b/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4f9665de60b1b0e99bcd6be4f17d90339698ce954cfd8d9cf4f91c995165a92", size = 85637, upload-time = "2025-07-30T10:01:46.225Z" }, + { url = "https://files.pythonhosted.org/packages/44/b4/678503f12aceb0262f84fa201f6027ed77d71c5019ae03b399b97caa2f19/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ba92837e4a9aa6a508c8d2d7883ed5a8f6c308c89a4790e1e447a220deb79a85", size = 91934, upload-time = "2025-07-30T10:01:47.203Z" }, + { url = "https://files.pythonhosted.org/packages/f0/c7/f36bd08ef9bd9f0a9cff9428406651f5937ce27b6c5b07b92d41f91ae541/argon2_cffi_bindings-25.1.0-cp314-cp314t-win32.whl", hash = "sha256:84a461d4d84ae1295871329b346a97f68eade8c53b6ed9a7ca2d7467f3c8ff6f", size = 28158, upload-time = "2025-07-30T10:01:48.341Z" }, + { url = "https://files.pythonhosted.org/packages/b3/80/0106a7448abb24a2c467bf7d527fe5413b7fdfa4ad6d6a96a43a62ef3988/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b55aec3565b65f56455eebc9b9f34130440404f27fe21c3b375bf1ea4d8fbae6", size = 32597, upload-time = "2025-07-30T10:01:49.112Z" }, + { url = "https://files.pythonhosted.org/packages/05/b8/d663c9caea07e9180b2cb662772865230715cbd573ba3b5e81793d580316/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:87c33a52407e4c41f3b70a9c2d3f6056d88b10dad7695be708c5021673f55623", size = 28231, upload-time = "2025-07-30T10:01:49.92Z" }, + { url = "https://files.pythonhosted.org/packages/1d/57/96b8b9f93166147826da5f90376e784a10582dd39a393c99bb62cfcf52f0/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500", size = 54121, upload-time = "2025-07-30T10:01:50.815Z" }, + { url = "https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44", size = 29177, upload-time = "2025-07-30T10:01:51.681Z" }, + { url = "https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0", size = 31090, upload-time = "2025-07-30T10:01:53.184Z" }, + { url = "https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6", size = 81246, upload-time = "2025-07-30T10:01:54.145Z" }, + { url = "https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a", size = 87126, upload-time = "2025-07-30T10:01:55.074Z" }, + { url = "https://files.pythonhosted.org/packages/72/70/7a2993a12b0ffa2a9271259b79cc616e2389ed1a4d93842fac5a1f923ffd/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d", size = 80343, upload-time = "2025-07-30T10:01:56.007Z" }, + { url = "https://files.pythonhosted.org/packages/78/9a/4e5157d893ffc712b74dbd868c7f62365618266982b64accab26bab01edc/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99", size = 86777, upload-time = "2025-07-30T10:01:56.943Z" }, + { url = "https://files.pythonhosted.org/packages/74/cd/15777dfde1c29d96de7f18edf4cc94c385646852e7c7b0320aa91ccca583/argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2", size = 27180, upload-time = "2025-07-30T10:01:57.759Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98", size = 31715, upload-time = "2025-07-30T10:01:58.56Z" }, + { url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149, upload-time = "2025-07-30T10:01:59.329Z" }, ] [[package]] @@ -123,45 +123,45 @@ dependencies = [ { name = "python-dateutil" }, { name = "tzdata" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/33/032cdc44182491aa708d06a68b62434140d8c50820a087fac7af37703357/arrow-1.4.0.tar.gz", hash = "sha256:ed0cc050e98001b8779e84d461b0098c4ac597e88704a655582b21d116e526d7", size = 152931 } +sdist = { url = "https://files.pythonhosted.org/packages/b9/33/032cdc44182491aa708d06a68b62434140d8c50820a087fac7af37703357/arrow-1.4.0.tar.gz", hash = "sha256:ed0cc050e98001b8779e84d461b0098c4ac597e88704a655582b21d116e526d7", size = 152931, upload-time = "2025-10-18T17:46:46.761Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl", hash = "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205", size = 68797 }, + { url = "https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl", hash = "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205", size = 68797, upload-time = "2025-10-18T17:46:45.663Z" }, ] [[package]] name = "asttokens" version = "3.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308 } +sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047 }, + { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" }, ] [[package]] name = "async-lru" -version = "2.3.0" +version = "2.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e8/1f/989ecfef8e64109a489fff357450cb73fa73a865a92bd8c272170a6922c2/async_lru-2.3.0.tar.gz", hash = "sha256:89bdb258a0140d7313cf8f4031d816a042202faa61d0ab310a0a538baa1c24b6", size = 16332, upload-time = "2026-03-19T01:04:32.413Z" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8a/ca724066c32a53fa75f59e0f21aa822fdaa8a0dffa112d223634e3caabf9/async_lru-2.2.0.tar.gz", hash = "sha256:80abae2a237dbc6c60861d621619af39f0d920aea306de34cb992c879e01370c", size = 14654, upload-time = "2026-02-20T19:11:43.848Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl", hash = "sha256:eea27b01841909316f2cc739807acea1c623df2be8c5cfad7583286397bb8315", size = 8403, upload-time = "2026-03-19T01:04:30.883Z" }, + { url = "https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl", hash = "sha256:e2c1cf731eba202b59c5feedaef14ffd9d02ad0037fcda64938699f2c380eafe", size = 7890, upload-time = "2026-02-20T19:11:42.273Z" }, ] [[package]] name = "attrs" -version = "26.1.0" +version = "25.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, ] [[package]] name = "babel" version = "2.18.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554 } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845 }, + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, ] [[package]] @@ -172,9 +172,9 @@ dependencies = [ { name = "soupsieve" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737 } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721 }, + { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, ] [[package]] @@ -184,9 +184,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "webencodings" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/07/18/3c8523962314be6bf4c8989c79ad9531c825210dd13a8669f6b84336e8bd/bleach-6.3.0.tar.gz", hash = "sha256:6f3b91b1c0a02bb9a78b5a454c92506aa0fdf197e1d5e114d2e00c6f64306d22", size = 203533 } +sdist = { url = "https://files.pythonhosted.org/packages/07/18/3c8523962314be6bf4c8989c79ad9531c825210dd13a8669f6b84336e8bd/bleach-6.3.0.tar.gz", hash = "sha256:6f3b91b1c0a02bb9a78b5a454c92506aa0fdf197e1d5e114d2e00c6f64306d22", size = 203533, upload-time = "2025-10-27T17:57:39.211Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl", hash = "sha256:fe10ec77c93ddf3d13a73b035abaac7a9f5e436513864ccdad516693213c65d6", size = 164437 }, + { url = "https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl", hash = "sha256:fe10ec77c93ddf3d13a73b035abaac7a9f5e436513864ccdad516693213c65d6", size = 164437, upload-time = "2025-10-27T17:57:37.538Z" }, ] [package.optional-dependencies] @@ -210,165 +210,149 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pycparser", marker = "implementation_name != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344 }, - { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560 }, - { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613 }, - { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476 }, - { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374 }, - { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597 }, - { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574 }, - { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971 }, - { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972 }, - { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078 }, - { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076 }, - { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820 }, - { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635 }, - { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271 }, - { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048 }, - { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529 }, - { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097 }, - { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983 }, - { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519 }, - { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572 }, - { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963 }, - { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361 }, - { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932 }, - { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557 }, - { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762 }, - { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230 }, - { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043 }, - { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446 }, - { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101 }, - { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948 }, - { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422 }, - { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499 }, - { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928 }, - { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302 }, - { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909 }, - { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402 }, - { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780 }, - { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320 }, - { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487 }, - { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049 }, - { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793 }, - { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300 }, - { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244 }, - { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828 }, - { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926 }, - { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328 }, - { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650 }, - { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687 }, - { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773 }, - { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013 }, - { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593 }, - { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354 }, - { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480 }, - { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584 }, - { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443 }, - { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437 }, - { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487 }, - { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726 }, - { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195 }, +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, ] [[package]] name = "cfgv" version = "3.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334 } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445 }, + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, ] [[package]] name = "charset-normalizer" -version = "3.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/60/e3bec1881450851b087e301bedc3daa9377a4d45f1c26aa90b0b235e38aa/charset_normalizer-3.4.6.tar.gz", hash = "sha256:1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6", size = 143363, upload-time = "2026-03-15T18:53:25.478Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/28/ff6f234e628a2de61c458be2779cb182bc03f6eec12200d4a525bbfc9741/charset_normalizer-3.4.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:82060f995ab5003a2d6e0f4ad29065b7672b6593c8c63559beefe5b443242c3e", size = 293582, upload-time = "2026-03-15T18:50:25.454Z" }, - { url = "https://files.pythonhosted.org/packages/1c/b7/b1a117e5385cbdb3205f6055403c2a2a220c5ea80b8716c324eaf75c5c95/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60c74963d8350241a79cb8feea80e54d518f72c26db618862a8f53e5023deaf9", size = 197240, upload-time = "2026-03-15T18:50:27.196Z" }, - { url = "https://files.pythonhosted.org/packages/a1/5f/2574f0f09f3c3bc1b2f992e20bce6546cb1f17e111c5be07308dc5427956/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6e4333fb15c83f7d1482a76d45a0818897b3d33f00efd215528ff7c51b8e35d", size = 217363, upload-time = "2026-03-15T18:50:28.601Z" }, - { url = "https://files.pythonhosted.org/packages/4a/d1/0ae20ad77bc949ddd39b51bf383b6ca932f2916074c95cad34ae465ab71f/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bc72863f4d9aba2e8fd9085e63548a324ba706d2ea2c83b260da08a59b9482de", size = 212994, upload-time = "2026-03-15T18:50:30.102Z" }, - { url = "https://files.pythonhosted.org/packages/60/ac/3233d262a310c1b12633536a07cde5ddd16985e6e7e238e9f3f9423d8eb9/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9cc4fc6c196d6a8b76629a70ddfcd4635a6898756e2d9cac5565cf0654605d73", size = 204697, upload-time = "2026-03-15T18:50:31.654Z" }, - { url = "https://files.pythonhosted.org/packages/25/3c/8a18fc411f085b82303cfb7154eed5bd49c77035eb7608d049468b53f87c/charset_normalizer-3.4.6-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:0c173ce3a681f309f31b87125fecec7a5d1347261ea11ebbb856fa6006b23c8c", size = 191673, upload-time = "2026-03-15T18:50:33.433Z" }, - { url = "https://files.pythonhosted.org/packages/ff/a7/11cfe61d6c5c5c7438d6ba40919d0306ed83c9ab957f3d4da2277ff67836/charset_normalizer-3.4.6-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c907cdc8109f6c619e6254212e794d6548373cc40e1ec75e6e3823d9135d29cc", size = 201120, upload-time = "2026-03-15T18:50:35.105Z" }, - { url = "https://files.pythonhosted.org/packages/b5/10/cf491fa1abd47c02f69687046b896c950b92b6cd7337a27e6548adbec8e4/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:404a1e552cf5b675a87f0651f8b79f5f1e6fd100ee88dc612f89aa16abd4486f", size = 200911, upload-time = "2026-03-15T18:50:36.819Z" }, - { url = "https://files.pythonhosted.org/packages/28/70/039796160b48b18ed466fde0af84c1b090c4e288fae26cd674ad04a2d703/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e3c701e954abf6fc03a49f7c579cc80c2c6cc52525340ca3186c41d3f33482ef", size = 192516, upload-time = "2026-03-15T18:50:38.228Z" }, - { url = "https://files.pythonhosted.org/packages/ff/34/c56f3223393d6ff3124b9e78f7de738047c2d6bc40a4f16ac0c9d7a1cb3c/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7a6967aaf043bceabab5412ed6bd6bd26603dae84d5cb75bf8d9a74a4959d398", size = 218795, upload-time = "2026-03-15T18:50:39.664Z" }, - { url = "https://files.pythonhosted.org/packages/e8/3b/ce2d4f86c5282191a041fdc5a4ce18f1c6bd40a5bd1f74cf8625f08d51c1/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5feb91325bbceade6afab43eb3b508c63ee53579fe896c77137ded51c6b6958e", size = 201833, upload-time = "2026-03-15T18:50:41.552Z" }, - { url = "https://files.pythonhosted.org/packages/3b/9b/b6a9f76b0fd7c5b5ec58b228ff7e85095370282150f0bd50b3126f5506d6/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f820f24b09e3e779fe84c3c456cb4108a7aa639b0d1f02c28046e11bfcd088ed", size = 213920, upload-time = "2026-03-15T18:50:43.33Z" }, - { url = "https://files.pythonhosted.org/packages/ae/98/7bc23513a33d8172365ed30ee3a3b3fe1ece14a395e5fc94129541fc6003/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b35b200d6a71b9839a46b9b7fff66b6638bb52fc9658aa58796b0326595d3021", size = 206951, upload-time = "2026-03-15T18:50:44.789Z" }, - { url = "https://files.pythonhosted.org/packages/32/73/c0b86f3d1458468e11aec870e6b3feac931facbe105a894b552b0e518e79/charset_normalizer-3.4.6-cp311-cp311-win32.whl", hash = "sha256:9ca4c0b502ab399ef89248a2c84c54954f77a070f28e546a85e91da627d1301e", size = 143703, upload-time = "2026-03-15T18:50:46.103Z" }, - { url = "https://files.pythonhosted.org/packages/c6/e3/76f2facfe8eddee0bbd38d2594e709033338eae44ebf1738bcefe0a06185/charset_normalizer-3.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:a9e68c9d88823b274cf1e72f28cb5dc89c990edf430b0bfd3e2fb0785bfeabf4", size = 153857, upload-time = "2026-03-15T18:50:47.563Z" }, - { url = "https://files.pythonhosted.org/packages/e2/dc/9abe19c9b27e6cd3636036b9d1b387b78c40dedbf0b47f9366737684b4b0/charset_normalizer-3.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:97d0235baafca5f2b09cf332cc275f021e694e8362c6bb9c96fc9a0eb74fc316", size = 142751, upload-time = "2026-03-15T18:50:49.234Z" }, - { url = "https://files.pythonhosted.org/packages/e5/62/c0815c992c9545347aeea7859b50dc9044d147e2e7278329c6e02ac9a616/charset_normalizer-3.4.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ef7fedc7a6ecbe99969cd09632516738a97eeb8bd7258bf8a0f23114c057dab", size = 295154, upload-time = "2026-03-15T18:50:50.88Z" }, - { url = "https://files.pythonhosted.org/packages/a8/37/bdca6613c2e3c58c7421891d80cc3efa1d32e882f7c4a7ee6039c3fc951a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4ea868bc28109052790eb2b52a9ab33f3aa7adc02f96673526ff47419490e21", size = 199191, upload-time = "2026-03-15T18:50:52.658Z" }, - { url = "https://files.pythonhosted.org/packages/6c/92/9934d1bbd69f7f398b38c5dae1cbf9cc672e7c34a4adf7b17c0a9c17d15d/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:836ab36280f21fc1a03c99cd05c6b7af70d2697e374c7af0b61ed271401a72a2", size = 218674, upload-time = "2026-03-15T18:50:54.102Z" }, - { url = "https://files.pythonhosted.org/packages/af/90/25f6ab406659286be929fd89ab0e78e38aa183fc374e03aa3c12d730af8a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f1ce721c8a7dfec21fcbdfe04e8f68174183cf4e8188e0645e92aa23985c57ff", size = 215259, upload-time = "2026-03-15T18:50:55.616Z" }, - { url = "https://files.pythonhosted.org/packages/4e/ef/79a463eb0fff7f96afa04c1d4c51f8fc85426f918db467854bfb6a569ce3/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e28d62a8fc7a1fa411c43bd65e346f3bce9716dc51b897fbe930c5987b402d5", size = 207276, upload-time = "2026-03-15T18:50:57.054Z" }, - { url = "https://files.pythonhosted.org/packages/f7/72/d0426afec4b71dc159fa6b4e68f868cd5a3ecd918fec5813a15d292a7d10/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:530d548084c4a9f7a16ed4a294d459b4f229db50df689bfe92027452452943a0", size = 195161, upload-time = "2026-03-15T18:50:58.686Z" }, - { url = "https://files.pythonhosted.org/packages/bf/18/c82b06a68bfcb6ce55e508225d210c7e6a4ea122bfc0748892f3dc4e8e11/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30f445ae60aad5e1f8bdbb3108e39f6fbc09f4ea16c815c66578878325f8f15a", size = 203452, upload-time = "2026-03-15T18:51:00.196Z" }, - { url = "https://files.pythonhosted.org/packages/44/d6/0c25979b92f8adafdbb946160348d8d44aa60ce99afdc27df524379875cb/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ac2393c73378fea4e52aa56285a3d64be50f1a12395afef9cce47772f60334c2", size = 202272, upload-time = "2026-03-15T18:51:01.703Z" }, - { url = "https://files.pythonhosted.org/packages/2e/3d/7fea3e8fe84136bebbac715dd1221cc25c173c57a699c030ab9b8900cbb7/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:90ca27cd8da8118b18a52d5f547859cc1f8354a00cd1e8e5120df3e30d6279e5", size = 195622, upload-time = "2026-03-15T18:51:03.526Z" }, - { url = "https://files.pythonhosted.org/packages/57/8a/d6f7fd5cb96c58ef2f681424fbca01264461336d2a7fc875e4446b1f1346/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e5a94886bedca0f9b78fecd6afb6629142fd2605aa70a125d49f4edc6037ee6", size = 220056, upload-time = "2026-03-15T18:51:05.269Z" }, - { url = "https://files.pythonhosted.org/packages/16/50/478cdda782c8c9c3fb5da3cc72dd7f331f031e7f1363a893cdd6ca0f8de0/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:695f5c2823691a25f17bc5d5ffe79fa90972cc34b002ac6c843bb8a1720e950d", size = 203751, upload-time = "2026-03-15T18:51:06.858Z" }, - { url = "https://files.pythonhosted.org/packages/75/fc/cc2fcac943939c8e4d8791abfa139f685e5150cae9f94b60f12520feaa9b/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:231d4da14bcd9301310faf492051bee27df11f2bc7549bc0bb41fef11b82daa2", size = 216563, upload-time = "2026-03-15T18:51:08.564Z" }, - { url = "https://files.pythonhosted.org/packages/a8/b7/a4add1d9a5f68f3d037261aecca83abdb0ab15960a3591d340e829b37298/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a056d1ad2633548ca18ffa2f85c202cfb48b68615129143915b8dc72a806a923", size = 209265, upload-time = "2026-03-15T18:51:10.312Z" }, - { url = "https://files.pythonhosted.org/packages/6c/18/c094561b5d64a24277707698e54b7f67bd17a4f857bbfbb1072bba07c8bf/charset_normalizer-3.4.6-cp312-cp312-win32.whl", hash = "sha256:c2274ca724536f173122f36c98ce188fd24ce3dad886ec2b7af859518ce008a4", size = 144229, upload-time = "2026-03-15T18:51:11.694Z" }, - { url = "https://files.pythonhosted.org/packages/ab/20/0567efb3a8fd481b8f34f739ebddc098ed062a59fed41a8d193a61939e8f/charset_normalizer-3.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:c8ae56368f8cc97c7e40a7ee18e1cedaf8e780cd8bc5ed5ac8b81f238614facb", size = 154277, upload-time = "2026-03-15T18:51:13.004Z" }, - { url = "https://files.pythonhosted.org/packages/15/57/28d79b44b51933119e21f65479d0864a8d5893e494cf5daab15df0247c17/charset_normalizer-3.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:899d28f422116b08be5118ef350c292b36fc15ec2daeb9ea987c89281c7bb5c4", size = 142817, upload-time = "2026-03-15T18:51:14.408Z" }, - { url = "https://files.pythonhosted.org/packages/1e/1d/4fdabeef4e231153b6ed7567602f3b68265ec4e5b76d6024cf647d43d981/charset_normalizer-3.4.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:11afb56037cbc4b1555a34dd69151e8e069bee82e613a73bef6e714ce733585f", size = 294823, upload-time = "2026-03-15T18:51:15.755Z" }, - { url = "https://files.pythonhosted.org/packages/47/7b/20e809b89c69d37be748d98e84dce6820bf663cf19cf6b942c951a3e8f41/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:423fb7e748a08f854a08a222b983f4df1912b1daedce51a72bd24fe8f26a1843", size = 198527, upload-time = "2026-03-15T18:51:17.177Z" }, - { url = "https://files.pythonhosted.org/packages/37/a6/4f8d27527d59c039dce6f7622593cdcd3d70a8504d87d09eb11e9fdc6062/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d73beaac5e90173ac3deb9928a74763a6d230f494e4bfb422c217a0ad8e629bf", size = 218388, upload-time = "2026-03-15T18:51:18.934Z" }, - { url = "https://files.pythonhosted.org/packages/f6/9b/4770ccb3e491a9bacf1c46cc8b812214fe367c86a96353ccc6daf87b01ec/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d60377dce4511655582e300dc1e5a5f24ba0cb229005a1d5c8d0cb72bb758ab8", size = 214563, upload-time = "2026-03-15T18:51:20.374Z" }, - { url = "https://files.pythonhosted.org/packages/2b/58/a199d245894b12db0b957d627516c78e055adc3a0d978bc7f65ddaf7c399/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:530e8cebeea0d76bdcf93357aa5e41336f48c3dc709ac52da2bb167c5b8271d9", size = 206587, upload-time = "2026-03-15T18:51:21.807Z" }, - { url = "https://files.pythonhosted.org/packages/7e/70/3def227f1ec56f5c69dfc8392b8bd63b11a18ca8178d9211d7cc5e5e4f27/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:a26611d9987b230566f24a0a125f17fe0de6a6aff9f25c9f564aaa2721a5fb88", size = 194724, upload-time = "2026-03-15T18:51:23.508Z" }, - { url = "https://files.pythonhosted.org/packages/58/ab/9318352e220c05efd31c2779a23b50969dc94b985a2efa643ed9077bfca5/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:34315ff4fc374b285ad7f4a0bf7dcbfe769e1b104230d40f49f700d4ab6bbd84", size = 202956, upload-time = "2026-03-15T18:51:25.239Z" }, - { url = "https://files.pythonhosted.org/packages/75/13/f3550a3ac25b70f87ac98c40d3199a8503676c2f1620efbf8d42095cfc40/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ddd609f9e1af8c7bd6e2aca279c931aefecd148a14402d4e368f3171769fd", size = 201923, upload-time = "2026-03-15T18:51:26.682Z" }, - { url = "https://files.pythonhosted.org/packages/1b/db/c5c643b912740b45e8eec21de1bbab8e7fc085944d37e1e709d3dcd9d72f/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:80d0a5615143c0b3225e5e3ef22c8d5d51f3f72ce0ea6fb84c943546c7b25b6c", size = 195366, upload-time = "2026-03-15T18:51:28.129Z" }, - { url = "https://files.pythonhosted.org/packages/5a/67/3b1c62744f9b2448443e0eb160d8b001c849ec3fef591e012eda6484787c/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:92734d4d8d187a354a556626c221cd1a892a4e0802ccb2af432a1d85ec012194", size = 219752, upload-time = "2026-03-15T18:51:29.556Z" }, - { url = "https://files.pythonhosted.org/packages/f6/98/32ffbaf7f0366ffb0445930b87d103f6b406bc2c271563644bde8a2b1093/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:613f19aa6e082cf96e17e3ffd89383343d0d589abda756b7764cf78361fd41dc", size = 203296, upload-time = "2026-03-15T18:51:30.921Z" }, - { url = "https://files.pythonhosted.org/packages/41/12/5d308c1bbe60cabb0c5ef511574a647067e2a1f631bc8634fcafaccd8293/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2b1a63e8224e401cafe7739f77efd3f9e7f5f2026bda4aead8e59afab537784f", size = 215956, upload-time = "2026-03-15T18:51:32.399Z" }, - { url = "https://files.pythonhosted.org/packages/53/e9/5f85f6c5e20669dbe56b165c67b0260547dea97dba7e187938833d791687/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6cceb5473417d28edd20c6c984ab6fee6c6267d38d906823ebfe20b03d607dc2", size = 208652, upload-time = "2026-03-15T18:51:34.214Z" }, - { url = "https://files.pythonhosted.org/packages/f1/11/897052ea6af56df3eef3ca94edafee410ca699ca0c7b87960ad19932c55e/charset_normalizer-3.4.6-cp313-cp313-win32.whl", hash = "sha256:d7de2637729c67d67cf87614b566626057e95c303bc0a55ffe391f5205e7003d", size = 143940, upload-time = "2026-03-15T18:51:36.15Z" }, - { url = "https://files.pythonhosted.org/packages/a1/5c/724b6b363603e419829f561c854b87ed7c7e31231a7908708ac086cdf3e2/charset_normalizer-3.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:572d7c822caf521f0525ba1bce1a622a0b85cf47ffbdae6c9c19e3b5ac3c4389", size = 154101, upload-time = "2026-03-15T18:51:37.876Z" }, - { url = "https://files.pythonhosted.org/packages/01/a5/7abf15b4c0968e47020f9ca0935fb3274deb87cb288cd187cad92e8cdffd/charset_normalizer-3.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a4474d924a47185a06411e0064b803c68be044be2d60e50e8bddcc2649957c1f", size = 143109, upload-time = "2026-03-15T18:51:39.565Z" }, - { url = "https://files.pythonhosted.org/packages/25/6f/ffe1e1259f384594063ea1869bfb6be5cdb8bc81020fc36c3636bc8302a1/charset_normalizer-3.4.6-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9cc6e6d9e571d2f863fa77700701dae73ed5f78881efc8b3f9a4398772ff53e8", size = 294458, upload-time = "2026-03-15T18:51:41.134Z" }, - { url = "https://files.pythonhosted.org/packages/56/60/09bb6c13a8c1016c2ed5c6a6488e4ffef506461aa5161662bd7636936fb1/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5960d965e67165d75b7c7ffc60a83ec5abfc5c11b764ec13ea54fbef8b4421", size = 199277, upload-time = "2026-03-15T18:51:42.953Z" }, - { url = "https://files.pythonhosted.org/packages/00/50/dcfbb72a5138bbefdc3332e8d81a23494bf67998b4b100703fd15fa52d81/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b3694e3f87f8ac7ce279d4355645b3c878d24d1424581b46282f24b92f5a4ae2", size = 218758, upload-time = "2026-03-15T18:51:44.339Z" }, - { url = "https://files.pythonhosted.org/packages/03/b3/d79a9a191bb75f5aa81f3aaaa387ef29ce7cb7a9e5074ba8ea095cc073c2/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5d11595abf8dd942a77883a39d81433739b287b6aa71620f15164f8096221b30", size = 215299, upload-time = "2026-03-15T18:51:45.871Z" }, - { url = "https://files.pythonhosted.org/packages/76/7e/bc8911719f7084f72fd545f647601ea3532363927f807d296a8c88a62c0d/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7bda6eebafd42133efdca535b04ccb338ab29467b3f7bf79569883676fc628db", size = 206811, upload-time = "2026-03-15T18:51:47.308Z" }, - { url = "https://files.pythonhosted.org/packages/e2/40/c430b969d41dda0c465aa36cc7c2c068afb67177bef50905ac371b28ccc7/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:bbc8c8650c6e51041ad1be191742b8b421d05bbd3410f43fa2a00c8db87678e8", size = 193706, upload-time = "2026-03-15T18:51:48.849Z" }, - { url = "https://files.pythonhosted.org/packages/48/15/e35e0590af254f7df984de1323640ef375df5761f615b6225ba8deb9799a/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22c6f0c2fbc31e76c3b8a86fba1a56eda6166e238c29cdd3d14befdb4a4e4815", size = 202706, upload-time = "2026-03-15T18:51:50.257Z" }, - { url = "https://files.pythonhosted.org/packages/5e/bd/f736f7b9cc5e93a18b794a50346bb16fbfd6b37f99e8f306f7951d27c17c/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7edbed096e4a4798710ed6bc75dcaa2a21b68b6c356553ac4823c3658d53743a", size = 202497, upload-time = "2026-03-15T18:51:52.012Z" }, - { url = "https://files.pythonhosted.org/packages/9d/ba/2cc9e3e7dfdf7760a6ed8da7446d22536f3d0ce114ac63dee2a5a3599e62/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7f9019c9cb613f084481bd6a100b12e1547cf2efe362d873c2e31e4035a6fa43", size = 193511, upload-time = "2026-03-15T18:51:53.723Z" }, - { url = "https://files.pythonhosted.org/packages/9e/cb/5be49b5f776e5613be07298c80e1b02a2d900f7a7de807230595c85a8b2e/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:58c948d0d086229efc484fe2f30c2d382c86720f55cd9bc33591774348ad44e0", size = 220133, upload-time = "2026-03-15T18:51:55.333Z" }, - { url = "https://files.pythonhosted.org/packages/83/43/99f1b5dad345accb322c80c7821071554f791a95ee50c1c90041c157ae99/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:419a9d91bd238052642a51938af8ac05da5b3343becde08d5cdeab9046df9ee1", size = 203035, upload-time = "2026-03-15T18:51:56.736Z" }, - { url = "https://files.pythonhosted.org/packages/87/9a/62c2cb6a531483b55dddff1a68b3d891a8b498f3ca555fbcf2978e804d9d/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5273b9f0b5835ff0350c0828faea623c68bfa65b792720c453e22b25cc72930f", size = 216321, upload-time = "2026-03-15T18:51:58.17Z" }, - { url = "https://files.pythonhosted.org/packages/6e/79/94a010ff81e3aec7c293eb82c28f930918e517bc144c9906a060844462eb/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0e901eb1049fdb80f5bd11ed5ea1e498ec423102f7a9b9e4645d5b8204ff2815", size = 208973, upload-time = "2026-03-15T18:51:59.998Z" }, - { url = "https://files.pythonhosted.org/packages/2a/57/4ecff6d4ec8585342f0c71bc03efaa99cb7468f7c91a57b105bcd561cea8/charset_normalizer-3.4.6-cp314-cp314-win32.whl", hash = "sha256:b4ff1d35e8c5bd078be89349b6f3a845128e685e751b6ea1169cf2160b344c4d", size = 144610, upload-time = "2026-03-15T18:52:02.213Z" }, - { url = "https://files.pythonhosted.org/packages/80/94/8434a02d9d7f168c25767c64671fead8d599744a05d6a6c877144c754246/charset_normalizer-3.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:74119174722c4349af9708993118581686f343adc1c8c9c007d59be90d077f3f", size = 154962, upload-time = "2026-03-15T18:52:03.658Z" }, - { url = "https://files.pythonhosted.org/packages/46/4c/48f2cdbfd923026503dfd67ccea45c94fd8fe988d9056b468579c66ed62b/charset_normalizer-3.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:e5bcc1a1ae744e0bb59641171ae53743760130600da8db48cbb6e4918e186e4e", size = 143595, upload-time = "2026-03-15T18:52:05.123Z" }, - { url = "https://files.pythonhosted.org/packages/31/93/8878be7569f87b14f1d52032946131bcb6ebbd8af3e20446bc04053dc3f1/charset_normalizer-3.4.6-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ad8faf8df23f0378c6d527d8b0b15ea4a2e23c89376877c598c4870d1b2c7866", size = 314828, upload-time = "2026-03-15T18:52:06.831Z" }, - { url = "https://files.pythonhosted.org/packages/06/b6/fae511ca98aac69ecc35cde828b0a3d146325dd03d99655ad38fc2cc3293/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5ea69428fa1b49573eef0cc44a1d43bebd45ad0c611eb7d7eac760c7ae771bc", size = 208138, upload-time = "2026-03-15T18:52:08.239Z" }, - { url = "https://files.pythonhosted.org/packages/54/57/64caf6e1bf07274a1e0b7c160a55ee9e8c9ec32c46846ce59b9c333f7008/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:06a7e86163334edfc5d20fe104db92fcd666e5a5df0977cb5680a506fe26cc8e", size = 224679, upload-time = "2026-03-15T18:52:10.043Z" }, - { url = "https://files.pythonhosted.org/packages/aa/cb/9ff5a25b9273ef160861b41f6937f86fae18b0792fe0a8e75e06acb08f1d/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e1f6e2f00a6b8edb562826e4632e26d063ac10307e80f7461f7de3ad8ef3f077", size = 223475, upload-time = "2026-03-15T18:52:11.854Z" }, - { url = "https://files.pythonhosted.org/packages/fc/97/440635fc093b8d7347502a377031f9605a1039c958f3cd18dcacffb37743/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95b52c68d64c1878818687a473a10547b3292e82b6f6fe483808fb1468e2f52f", size = 215230, upload-time = "2026-03-15T18:52:13.325Z" }, - { url = "https://files.pythonhosted.org/packages/cd/24/afff630feb571a13f07c8539fbb502d2ab494019492aaffc78ef41f1d1d0/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:7504e9b7dc05f99a9bbb4525c67a2c155073b44d720470a148b34166a69c054e", size = 199045, upload-time = "2026-03-15T18:52:14.752Z" }, - { url = "https://files.pythonhosted.org/packages/e5/17/d1399ecdaf7e0498c327433e7eefdd862b41236a7e484355b8e0e5ebd64b/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:172985e4ff804a7ad08eebec0a1640ece87ba5041d565fff23c8f99c1f389484", size = 211658, upload-time = "2026-03-15T18:52:16.278Z" }, - { url = "https://files.pythonhosted.org/packages/b5/38/16baa0affb957b3d880e5ac2144caf3f9d7de7bc4a91842e447fbb5e8b67/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4be9f4830ba8741527693848403e2c457c16e499100963ec711b1c6f2049b7c7", size = 210769, upload-time = "2026-03-15T18:52:17.782Z" }, - { url = "https://files.pythonhosted.org/packages/05/34/c531bc6ac4c21da9ddfddb3107be2287188b3ea4b53b70fc58f2a77ac8d8/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:79090741d842f564b1b2827c0b82d846405b744d31e84f18d7a7b41c20e473ff", size = 201328, upload-time = "2026-03-15T18:52:19.553Z" }, - { url = "https://files.pythonhosted.org/packages/fa/73/a5a1e9ca5f234519c1953608a03fe109c306b97fdfb25f09182babad51a7/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:87725cfb1a4f1f8c2fc9890ae2f42094120f4b44db9360be5d99a4c6b0e03a9e", size = 225302, upload-time = "2026-03-15T18:52:21.043Z" }, - { url = "https://files.pythonhosted.org/packages/ba/f6/cd782923d112d296294dea4bcc7af5a7ae0f86ab79f8fefbda5526b6cfc0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:fcce033e4021347d80ed9c66dcf1e7b1546319834b74445f561d2e2221de5659", size = 211127, upload-time = "2026-03-15T18:52:22.491Z" }, - { url = "https://files.pythonhosted.org/packages/0e/c5/0b6898950627af7d6103a449b22320372c24c6feda91aa24e201a478d161/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:ca0276464d148c72defa8bb4390cce01b4a0e425f3b50d1435aa6d7a18107602", size = 222840, upload-time = "2026-03-15T18:52:24.113Z" }, - { url = "https://files.pythonhosted.org/packages/7d/25/c4bba773bef442cbdc06111d40daa3de5050a676fa26e85090fc54dd12f0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:197c1a244a274bb016dd8b79204850144ef77fe81c5b797dc389327adb552407", size = 216890, upload-time = "2026-03-15T18:52:25.541Z" }, - { url = "https://files.pythonhosted.org/packages/35/1a/05dacadb0978da72ee287b0143097db12f2e7e8d3ffc4647da07a383b0b7/charset_normalizer-3.4.6-cp314-cp314t-win32.whl", hash = "sha256:2a24157fa36980478dd1770b585c0f30d19e18f4fb0c47c13aa568f871718579", size = 155379, upload-time = "2026-03-15T18:52:27.05Z" }, - { url = "https://files.pythonhosted.org/packages/5d/7a/d269d834cb3a76291651256f3b9a5945e81d0a49ab9f4a498964e83c0416/charset_normalizer-3.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:cd5e2801c89992ed8c0a3f0293ae83c159a60d9a5d685005383ef4caca77f2c4", size = 169043, upload-time = "2026-03-15T18:52:28.502Z" }, - { url = "https://files.pythonhosted.org/packages/23/06/28b29fba521a37a8932c6a84192175c34d49f84a6d4773fa63d05f9aff22/charset_normalizer-3.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:47955475ac79cc504ef2704b192364e51d0d473ad452caedd0002605f780101c", size = 148523, upload-time = "2026-03-15T18:52:29.956Z" }, - { url = "https://files.pythonhosted.org/packages/2a/68/687187c7e26cb24ccbd88e5069f5ef00eba804d36dde11d99aad0838ab45/charset_normalizer-3.4.6-py3-none-any.whl", hash = "sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69", size = 61455, upload-time = "2026-03-15T18:53:23.833Z" }, +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, ] [[package]] @@ -378,18 +362,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065 } +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274 }, + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, ] [[package]] name = "cloudpickle" version = "3.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330 } +sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228 }, + { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" }, ] [[package]] @@ -401,18 +385,18 @@ dependencies = [ { name = "matplotlib" }, { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2f/ca/39152b506133881be74bd46cced27ec10d9bbc85db5bdfc22c8f8e4011f9/cmasher-1.9.2.tar.gz", hash = "sha256:6c90c2cec87146e3210d3e7f2321fceee38ac7d87c136cd0de25160a14f57ff5", size = 520860 } +sdist = { url = "https://files.pythonhosted.org/packages/2f/ca/39152b506133881be74bd46cced27ec10d9bbc85db5bdfc22c8f8e4011f9/cmasher-1.9.2.tar.gz", hash = "sha256:6c90c2cec87146e3210d3e7f2321fceee38ac7d87c136cd0de25160a14f57ff5", size = 520860, upload-time = "2024-11-19T11:17:02.084Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/d2/6fcb93a60777fef7662d84ba60846d2dee9b6b70b4a62472515110f79cee/cmasher-1.9.2-py3-none-any.whl", hash = "sha256:2fe45fde06051050dda5c023a527ba9066ca21f161c793f22839a6ebe6e4bbbb", size = 506496 }, + { url = "https://files.pythonhosted.org/packages/91/d2/6fcb93a60777fef7662d84ba60846d2dee9b6b70b4a62472515110f79cee/cmasher-1.9.2-py3-none-any.whl", hash = "sha256:2fe45fde06051050dda5c023a527ba9066ca21f161c793f22839a6ebe6e4bbbb", size = 506496, upload-time = "2024-11-19T11:16:58.549Z" }, ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] [[package]] @@ -422,9 +406,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a2/61/f083b5ac52e505dfc1c624eafbf8c7589a0d7f32daa398d2e7590efa5fda/colorlog-6.10.1.tar.gz", hash = "sha256:eb4ae5cb65fe7fec7773c2306061a8e63e02efc2c72eba9d27b0fa23c94f1321", size = 17162 } +sdist = { url = "https://files.pythonhosted.org/packages/a2/61/f083b5ac52e505dfc1c624eafbf8c7589a0d7f32daa398d2e7590efa5fda/colorlog-6.10.1.tar.gz", hash = "sha256:eb4ae5cb65fe7fec7773c2306061a8e63e02efc2c72eba9d27b0fa23c94f1321", size = 17162, upload-time = "2025-10-16T16:14:11.978Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/c1/e419ef3723a074172b68aaa89c9f3de486ed4c2399e2dbd8113a4fdcaf9e/colorlog-6.10.1-py3-none-any.whl", hash = "sha256:2d7e8348291948af66122cff006c9f8da6255d224e7cf8e37d8de2df3bad8c9c", size = 11743 }, + { url = "https://files.pythonhosted.org/packages/6d/c1/e419ef3723a074172b68aaa89c9f3de486ed4c2399e2dbd8113a4fdcaf9e/colorlog-6.10.1-py3-none-any.whl", hash = "sha256:2d7e8348291948af66122cff006c9f8da6255d224e7cf8e37d8de2df3bad8c9c", size = 11743, upload-time = "2025-10-16T16:14:10.512Z" }, ] [[package]] @@ -434,18 +418,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/75/e4/aa41ae14c5c061205715006c8834496d86ec7500f1edda5981f0f0190cc6/colorspacious-1.1.2.tar.gz", hash = "sha256:5e9072e8cdca889dac445c35c9362a22ccf758e97b00b79ff0d5a7ba3e11b618", size = 688573 } +sdist = { url = "https://files.pythonhosted.org/packages/75/e4/aa41ae14c5c061205715006c8834496d86ec7500f1edda5981f0f0190cc6/colorspacious-1.1.2.tar.gz", hash = "sha256:5e9072e8cdca889dac445c35c9362a22ccf758e97b00b79ff0d5a7ba3e11b618", size = 688573, upload-time = "2018-04-08T04:27:30.83Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/a1/318b9aeca7b9856410ededa4f52d6f82174d1a41e64bdd70d951e532675a/colorspacious-1.1.2-py2.py3-none-any.whl", hash = "sha256:c78befa603cea5dccb332464e7dd29e96469eebf6cd5133029153d1e69e3fd6f", size = 37735 }, + { url = "https://files.pythonhosted.org/packages/ab/a1/318b9aeca7b9856410ededa4f52d6f82174d1a41e64bdd70d951e532675a/colorspacious-1.1.2-py2.py3-none-any.whl", hash = "sha256:c78befa603cea5dccb332464e7dd29e96469eebf6cd5133029153d1e69e3fd6f", size = 37735, upload-time = "2018-04-08T04:27:22.143Z" }, ] [[package]] name = "comm" version = "0.2.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/13/7d740c5849255756bc17888787313b61fd38a0a8304fc4f073dfc46122aa/comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971", size = 6319 } +sdist = { url = "https://files.pythonhosted.org/packages/4c/13/7d740c5849255756bc17888787313b61fd38a0a8304fc4f073dfc46122aa/comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971", size = 6319, upload-time = "2025-07-25T14:02:04.452Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294 }, + { url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294, upload-time = "2025-07-25T14:02:02.896Z" }, ] [[package]] @@ -455,178 +439,178 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773 }, - { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149 }, - { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222 }, - { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234 }, - { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555 }, - { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238 }, - { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218 }, - { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867 }, - { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677 }, - { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234 }, - { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123 }, - { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419 }, - { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979 }, - { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653 }, - { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536 }, - { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397 }, - { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601 }, - { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288 }, - { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386 }, - { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018 }, - { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567 }, - { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655 }, - { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257 }, - { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034 }, - { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672 }, - { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234 }, - { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169 }, - { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859 }, - { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062 }, - { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932 }, - { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024 }, - { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578 }, - { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524 }, - { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730 }, - { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897 }, - { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751 }, - { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486 }, - { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106 }, - { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548 }, - { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297 }, - { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023 }, - { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157 }, - { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570 }, - { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713 }, - { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189 }, - { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251 }, - { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810 }, - { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871 }, - { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264 }, - { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819 }, - { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650 }, - { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833 }, - { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692 }, - { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424 }, - { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300 }, - { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769 }, - { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892 }, - { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748 }, - { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554 }, - { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118 }, - { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555 }, - { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295 }, - { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027 }, - { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428 }, - { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331 }, - { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831 }, - { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809 }, - { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593 }, - { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202 }, - { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207 }, - { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315 }, +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" }, + { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" }, + { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" }, + { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" }, + { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" }, + { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" }, + { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" }, + { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" }, + { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" }, + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, + { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, + { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, + { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, + { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, + { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, + { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, + { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, + { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, + { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, + { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, + { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, + { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, + { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, + { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, + { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, + { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, + { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, + { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, + { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, + { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, + { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, + { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, + { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, + { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, + { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, + { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" }, + { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" }, + { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" }, + { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, ] [[package]] name = "coverage" -version = "7.13.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967, upload-time = "2026-03-17T10:33:18.341Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/37/d24c8f8220ff07b839b2c043ea4903a33b0f455abe673ae3c03bbdb7f212/coverage-7.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66a80c616f80181f4d643b0f9e709d97bcea413ecd9631e1dedc7401c8e6695d", size = 219381, upload-time = "2026-03-17T10:30:14.68Z" }, - { url = "https://files.pythonhosted.org/packages/35/8b/cd129b0ca4afe886a6ce9d183c44d8301acbd4ef248622e7c49a23145605/coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:145ede53ccbafb297c1c9287f788d1bc3efd6c900da23bf6931b09eafc931587", size = 219880, upload-time = "2026-03-17T10:30:16.231Z" }, - { url = "https://files.pythonhosted.org/packages/55/2f/e0e5b237bffdb5d6c530ce87cc1d413a5b7d7dfd60fb067ad6d254c35c76/coverage-7.13.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0672854dc733c342fa3e957e0605256d2bf5934feeac328da9e0b5449634a642", size = 250303, upload-time = "2026-03-17T10:30:17.748Z" }, - { url = "https://files.pythonhosted.org/packages/92/be/b1afb692be85b947f3401375851484496134c5554e67e822c35f28bf2fbc/coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ec10e2a42b41c923c2209b846126c6582db5e43a33157e9870ba9fb70dc7854b", size = 252218, upload-time = "2026-03-17T10:30:19.804Z" }, - { url = "https://files.pythonhosted.org/packages/da/69/2f47bb6fa1b8d1e3e5d0c4be8ccb4313c63d742476a619418f85740d597b/coverage-7.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be3d4bbad9d4b037791794ddeedd7d64a56f5933a2c1373e18e9e568b9141686", size = 254326, upload-time = "2026-03-17T10:30:21.321Z" }, - { url = "https://files.pythonhosted.org/packages/d5/d0/79db81da58965bd29dabc8f4ad2a2af70611a57cba9d1ec006f072f30a54/coverage-7.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d2afbc5cc54d286bfb54541aa50b64cdb07a718227168c87b9e2fb8f25e1743", size = 256267, upload-time = "2026-03-17T10:30:23.094Z" }, - { url = "https://files.pythonhosted.org/packages/e5/32/d0d7cc8168f91ddab44c0ce4806b969df5f5fdfdbb568eaca2dbc2a04936/coverage-7.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3ad050321264c49c2fa67bb599100456fc51d004b82534f379d16445da40fb75", size = 250430, upload-time = "2026-03-17T10:30:25.311Z" }, - { url = "https://files.pythonhosted.org/packages/4d/06/a055311d891ddbe231cd69fdd20ea4be6e3603ffebddf8704b8ca8e10a3c/coverage-7.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7300c8a6d13335b29bb76d7651c66af6bd8658517c43499f110ddc6717bfc209", size = 252017, upload-time = "2026-03-17T10:30:27.284Z" }, - { url = "https://files.pythonhosted.org/packages/d6/f6/d0fd2d21e29a657b5f77a2fe7082e1568158340dceb941954f776dce1b7b/coverage-7.13.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:eb07647a5738b89baab047f14edd18ded523de60f3b30e75c2acc826f79c839a", size = 250080, upload-time = "2026-03-17T10:30:29.481Z" }, - { url = "https://files.pythonhosted.org/packages/4e/ab/0d7fb2efc2e9a5eb7ddcc6e722f834a69b454b7e6e5888c3a8567ecffb31/coverage-7.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9adb6688e3b53adffefd4a52d72cbd8b02602bfb8f74dcd862337182fd4d1a4e", size = 253843, upload-time = "2026-03-17T10:30:31.301Z" }, - { url = "https://files.pythonhosted.org/packages/ba/6f/7467b917bbf5408610178f62a49c0ed4377bb16c1657f689cc61470da8ce/coverage-7.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7c8d4bc913dd70b93488d6c496c77f3aff5ea99a07e36a18f865bca55adef8bd", size = 249802, upload-time = "2026-03-17T10:30:33.358Z" }, - { url = "https://files.pythonhosted.org/packages/75/2c/1172fb689df92135f5bfbbd69fc83017a76d24ea2e2f3a1154007e2fb9f8/coverage-7.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e3c426ffc4cd952f54ee9ffbdd10345709ecc78a3ecfd796a57236bfad0b9b8", size = 250707, upload-time = "2026-03-17T10:30:35.2Z" }, - { url = "https://files.pythonhosted.org/packages/67/21/9ac389377380a07884e3b48ba7a620fcd9dbfaf1d40565facdc6b36ec9ef/coverage-7.13.5-cp311-cp311-win32.whl", hash = "sha256:259b69bb83ad9894c4b25be2528139eecba9a82646ebdda2d9db1ba28424a6bf", size = 221880, upload-time = "2026-03-17T10:30:36.775Z" }, - { url = "https://files.pythonhosted.org/packages/af/7f/4cd8a92531253f9d7c1bbecd9fa1b472907fb54446ca768c59b531248dc5/coverage-7.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:258354455f4e86e3e9d0d17571d522e13b4e1e19bf0f8596bcf9476d61e7d8a9", size = 222816, upload-time = "2026-03-17T10:30:38.891Z" }, - { url = "https://files.pythonhosted.org/packages/12/a6/1d3f6155fb0010ca68eba7fe48ca6c9da7385058b77a95848710ecf189b1/coverage-7.13.5-cp311-cp311-win_arm64.whl", hash = "sha256:bff95879c33ec8da99fc9b6fe345ddb5be6414b41d6d1ad1c8f188d26f36e028", size = 221483, upload-time = "2026-03-17T10:30:40.463Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c3/a396306ba7db865bf96fc1fb3b7fd29bcbf3d829df642e77b13555163cd6/coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01", size = 219554, upload-time = "2026-03-17T10:30:42.208Z" }, - { url = "https://files.pythonhosted.org/packages/a6/16/a68a19e5384e93f811dccc51034b1fd0b865841c390e3c931dcc4699e035/coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422", size = 219908, upload-time = "2026-03-17T10:30:43.906Z" }, - { url = "https://files.pythonhosted.org/packages/29/72/20b917c6793af3a5ceb7fb9c50033f3ec7865f2911a1416b34a7cfa0813b/coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f", size = 251419, upload-time = "2026-03-17T10:30:45.545Z" }, - { url = "https://files.pythonhosted.org/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5", size = 254159, upload-time = "2026-03-17T10:30:47.204Z" }, - { url = "https://files.pythonhosted.org/packages/9d/00/7b0edcfe64e2ed4c0340dac14a52ad0f4c9bd0b8b5e531af7d55b703db7c/coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376", size = 255270, upload-time = "2026-03-17T10:30:48.812Z" }, - { url = "https://files.pythonhosted.org/packages/93/89/7ffc4ba0f5d0a55c1e84ea7cee39c9fc06af7b170513d83fbf3bbefce280/coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256", size = 257538, upload-time = "2026-03-17T10:30:50.77Z" }, - { url = "https://files.pythonhosted.org/packages/81/bd/73ddf85f93f7e6fa83e77ccecb6162d9415c79007b4bc124008a4995e4a7/coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c", size = 251821, upload-time = "2026-03-17T10:30:52.5Z" }, - { url = "https://files.pythonhosted.org/packages/a0/81/278aff4e8dec4926a0bcb9486320752811f543a3ce5b602cc7a29978d073/coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5", size = 253191, upload-time = "2026-03-17T10:30:54.543Z" }, - { url = "https://files.pythonhosted.org/packages/70/ee/fe1621488e2e0a58d7e94c4800f0d96f79671553488d401a612bebae324b/coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09", size = 251337, upload-time = "2026-03-17T10:30:56.663Z" }, - { url = "https://files.pythonhosted.org/packages/37/a6/f79fb37aa104b562207cc23cb5711ab6793608e246cae1e93f26b2236ed9/coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9", size = 255404, upload-time = "2026-03-17T10:30:58.427Z" }, - { url = "https://files.pythonhosted.org/packages/75/f0/ed15262a58ec81ce457ceb717b7f78752a1713556b19081b76e90896e8d4/coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf", size = 250903, upload-time = "2026-03-17T10:31:00.093Z" }, - { url = "https://files.pythonhosted.org/packages/0f/e9/9129958f20e7e9d4d56d51d42ccf708d15cac355ff4ac6e736e97a9393d2/coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c", size = 252780, upload-time = "2026-03-17T10:31:01.916Z" }, - { url = "https://files.pythonhosted.org/packages/a4/d7/0ad9b15812d81272db94379fe4c6df8fd17781cc7671fdfa30c76ba5ff7b/coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf", size = 222093, upload-time = "2026-03-17T10:31:03.642Z" }, - { url = "https://files.pythonhosted.org/packages/29/3d/821a9a5799fac2556bcf0bd37a70d1d11fa9e49784b6d22e92e8b2f85f18/coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810", size = 222900, upload-time = "2026-03-17T10:31:05.651Z" }, - { url = "https://files.pythonhosted.org/packages/d4/fa/2238c2ad08e35cf4f020ea721f717e09ec3152aea75d191a7faf3ef009a8/coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de", size = 221515, upload-time = "2026-03-17T10:31:07.293Z" }, - { url = "https://files.pythonhosted.org/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1", size = 219576, upload-time = "2026-03-17T10:31:09.045Z" }, - { url = "https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3", size = 219942, upload-time = "2026-03-17T10:31:10.708Z" }, - { url = "https://files.pythonhosted.org/packages/5f/13/93419671cee82b780bab7ea96b67c8ef448f5f295f36bf5031154ec9a790/coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26", size = 250935, upload-time = "2026-03-17T10:31:12.392Z" }, - { url = "https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3", size = 253541, upload-time = "2026-03-17T10:31:14.247Z" }, - { url = "https://files.pythonhosted.org/packages/4e/5e/3ee3b835647be646dcf3c65a7c6c18f87c27326a858f72ab22c12730773d/coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b", size = 254780, upload-time = "2026-03-17T10:31:16.193Z" }, - { url = "https://files.pythonhosted.org/packages/44/b3/cb5bd1a04cfcc49ede6cd8409d80bee17661167686741e041abc7ee1b9a9/coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a", size = 256912, upload-time = "2026-03-17T10:31:17.89Z" }, - { url = "https://files.pythonhosted.org/packages/1b/66/c1dceb7b9714473800b075f5c8a84f4588f887a90eb8645282031676e242/coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969", size = 251165, upload-time = "2026-03-17T10:31:19.605Z" }, - { url = "https://files.pythonhosted.org/packages/b7/62/5502b73b97aa2e53ea22a39cf8649ff44827bef76d90bf638777daa27a9d/coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161", size = 252908, upload-time = "2026-03-17T10:31:21.312Z" }, - { url = "https://files.pythonhosted.org/packages/7d/37/7792c2d69854397ca77a55c4646e5897c467928b0e27f2d235d83b5d08c6/coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15", size = 250873, upload-time = "2026-03-17T10:31:23.565Z" }, - { url = "https://files.pythonhosted.org/packages/a3/23/bc866fb6163be52a8a9e5d708ba0d3b1283c12158cefca0a8bbb6e247a43/coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1", size = 255030, upload-time = "2026-03-17T10:31:25.58Z" }, - { url = "https://files.pythonhosted.org/packages/7d/8b/ef67e1c222ef49860701d346b8bbb70881bef283bd5f6cbba68a39a086c7/coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6", size = 250694, upload-time = "2026-03-17T10:31:27.316Z" }, - { url = "https://files.pythonhosted.org/packages/46/0d/866d1f74f0acddbb906db212e096dee77a8e2158ca5e6bb44729f9d93298/coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17", size = 252469, upload-time = "2026-03-17T10:31:29.472Z" }, - { url = "https://files.pythonhosted.org/packages/7a/f5/be742fec31118f02ce42b21c6af187ad6a344fed546b56ca60caacc6a9a0/coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85", size = 222112, upload-time = "2026-03-17T10:31:31.526Z" }, - { url = "https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b", size = 222923, upload-time = "2026-03-17T10:31:33.633Z" }, - { url = "https://files.pythonhosted.org/packages/48/af/fea819c12a095781f6ccd504890aaddaf88b8fab263c4940e82c7b770124/coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664", size = 221540, upload-time = "2026-03-17T10:31:35.445Z" }, - { url = "https://files.pythonhosted.org/packages/23/d2/17879af479df7fbbd44bd528a31692a48f6b25055d16482fdf5cdb633805/coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d", size = 220262, upload-time = "2026-03-17T10:31:37.184Z" }, - { url = "https://files.pythonhosted.org/packages/5b/4c/d20e554f988c8f91d6a02c5118f9abbbf73a8768a3048cb4962230d5743f/coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0", size = 220617, upload-time = "2026-03-17T10:31:39.245Z" }, - { url = "https://files.pythonhosted.org/packages/29/9c/f9f5277b95184f764b24e7231e166dfdb5780a46d408a2ac665969416d61/coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806", size = 261912, upload-time = "2026-03-17T10:31:41.324Z" }, - { url = "https://files.pythonhosted.org/packages/d5/f6/7f1ab39393eeb50cfe4747ae8ef0e4fc564b989225aa1152e13a180d74f8/coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3", size = 263987, upload-time = "2026-03-17T10:31:43.724Z" }, - { url = "https://files.pythonhosted.org/packages/a0/d7/62c084fb489ed9c6fbdf57e006752e7c516ea46fd690e5ed8b8617c7d52e/coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9", size = 266416, upload-time = "2026-03-17T10:31:45.769Z" }, - { url = "https://files.pythonhosted.org/packages/a9/f6/df63d8660e1a0bff6125947afda112a0502736f470d62ca68b288ea762d8/coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd", size = 267558, upload-time = "2026-03-17T10:31:48.293Z" }, - { url = "https://files.pythonhosted.org/packages/5b/02/353ca81d36779bd108f6d384425f7139ac3c58c750dcfaafe5d0bee6436b/coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606", size = 261163, upload-time = "2026-03-17T10:31:50.125Z" }, - { url = "https://files.pythonhosted.org/packages/2c/16/2e79106d5749bcaf3aee6d309123548e3276517cd7851faa8da213bc61bf/coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e", size = 263981, upload-time = "2026-03-17T10:31:51.961Z" }, - { url = "https://files.pythonhosted.org/packages/29/c7/c29e0c59ffa6942030ae6f50b88ae49988e7e8da06de7ecdbf49c6d4feae/coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0", size = 261604, upload-time = "2026-03-17T10:31:53.872Z" }, - { url = "https://files.pythonhosted.org/packages/40/48/097cdc3db342f34006a308ab41c3a7c11c3f0d84750d340f45d88a782e00/coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87", size = 265321, upload-time = "2026-03-17T10:31:55.997Z" }, - { url = "https://files.pythonhosted.org/packages/bb/1f/4994af354689e14fd03a75f8ec85a9a68d94e0188bbdab3fc1516b55e512/coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479", size = 260502, upload-time = "2026-03-17T10:31:58.308Z" }, - { url = "https://files.pythonhosted.org/packages/22/c6/9bb9ef55903e628033560885f5c31aa227e46878118b63ab15dc7ba87797/coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2", size = 262688, upload-time = "2026-03-17T10:32:00.141Z" }, - { url = "https://files.pythonhosted.org/packages/14/4f/f5df9007e50b15e53e01edea486814783a7f019893733d9e4d6caad75557/coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a", size = 222788, upload-time = "2026-03-17T10:32:02.246Z" }, - { url = "https://files.pythonhosted.org/packages/e1/98/aa7fccaa97d0f3192bec013c4e6fd6d294a6ed44b640e6bb61f479e00ed5/coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819", size = 223851, upload-time = "2026-03-17T10:32:04.416Z" }, - { url = "https://files.pythonhosted.org/packages/3d/8b/e5c469f7352651e5f013198e9e21f97510b23de957dd06a84071683b4b60/coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911", size = 222104, upload-time = "2026-03-17T10:32:06.65Z" }, - { url = "https://files.pythonhosted.org/packages/8e/77/39703f0d1d4b478bfd30191d3c14f53caf596fac00efb3f8f6ee23646439/coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f", size = 219621, upload-time = "2026-03-17T10:32:08.589Z" }, - { url = "https://files.pythonhosted.org/packages/e2/3e/51dff36d99ae14639a133d9b164d63e628532e2974d8b1edb99dd1ebc733/coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e", size = 219953, upload-time = "2026-03-17T10:32:10.507Z" }, - { url = "https://files.pythonhosted.org/packages/6a/6c/1f1917b01eb647c2f2adc9962bd66c79eb978951cab61bdc1acab3290c07/coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a", size = 250992, upload-time = "2026-03-17T10:32:12.41Z" }, - { url = "https://files.pythonhosted.org/packages/22/e5/06b1f88f42a5a99df42ce61208bdec3bddb3d261412874280a19796fc09c/coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510", size = 253503, upload-time = "2026-03-17T10:32:14.449Z" }, - { url = "https://files.pythonhosted.org/packages/80/28/2a148a51e5907e504fa7b85490277734e6771d8844ebcc48764a15e28155/coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247", size = 254852, upload-time = "2026-03-17T10:32:16.56Z" }, - { url = "https://files.pythonhosted.org/packages/61/77/50e8d3d85cc0b7ebe09f30f151d670e302c7ff4a1bf6243f71dd8b0981fa/coverage-7.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6", size = 257161, upload-time = "2026-03-17T10:32:19.004Z" }, - { url = "https://files.pythonhosted.org/packages/3b/c4/b5fd1d4b7bf8d0e75d997afd3925c59ba629fc8616f1b3aae7605132e256/coverage-7.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0", size = 251021, upload-time = "2026-03-17T10:32:21.344Z" }, - { url = "https://files.pythonhosted.org/packages/f8/66/6ea21f910e92d69ef0b1c3346ea5922a51bad4446c9126db2ae96ee24c4c/coverage-7.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882", size = 252858, upload-time = "2026-03-17T10:32:23.506Z" }, - { url = "https://files.pythonhosted.org/packages/9e/ea/879c83cb5d61aa2a35fb80e72715e92672daef8191b84911a643f533840c/coverage-7.13.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740", size = 250823, upload-time = "2026-03-17T10:32:25.516Z" }, - { url = "https://files.pythonhosted.org/packages/8a/fb/616d95d3adb88b9803b275580bdeee8bd1b69a886d057652521f83d7322f/coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16", size = 255099, upload-time = "2026-03-17T10:32:27.944Z" }, - { url = "https://files.pythonhosted.org/packages/1c/93/25e6917c90ec1c9a56b0b26f6cad6408e5f13bb6b35d484a0d75c9cf000d/coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0", size = 250638, upload-time = "2026-03-17T10:32:29.914Z" }, - { url = "https://files.pythonhosted.org/packages/fc/7b/dc1776b0464145a929deed214aef9fb1493f159b59ff3c7eeeedf91eddd0/coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0", size = 252295, upload-time = "2026-03-17T10:32:31.981Z" }, - { url = "https://files.pythonhosted.org/packages/ea/fb/99cbbc56a26e07762a2740713f3c8f9f3f3106e3a3dd8cc4474954bccd34/coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc", size = 222360, upload-time = "2026-03-17T10:32:34.233Z" }, - { url = "https://files.pythonhosted.org/packages/8d/b7/4758d4f73fb536347cc5e4ad63662f9d60ba9118cb6785e9616b2ce5d7fa/coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633", size = 223174, upload-time = "2026-03-17T10:32:36.369Z" }, - { url = "https://files.pythonhosted.org/packages/2c/f2/24d84e1dfe70f8ac9fdf30d338239860d0d1d5da0bda528959d0ebc9da28/coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8", size = 221739, upload-time = "2026-03-17T10:32:38.736Z" }, - { url = "https://files.pythonhosted.org/packages/60/5b/4a168591057b3668c2428bff25dd3ebc21b629d666d90bcdfa0217940e84/coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b", size = 220351, upload-time = "2026-03-17T10:32:41.196Z" }, - { url = "https://files.pythonhosted.org/packages/f5/21/1fd5c4dbfe4a58b6b99649125635df46decdfd4a784c3cd6d410d303e370/coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c", size = 220612, upload-time = "2026-03-17T10:32:43.204Z" }, - { url = "https://files.pythonhosted.org/packages/d6/fe/2a924b3055a5e7e4512655a9d4609781b0d62334fa0140c3e742926834e2/coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9", size = 261985, upload-time = "2026-03-17T10:32:45.514Z" }, - { url = "https://files.pythonhosted.org/packages/d7/0d/c8928f2bd518c45990fe1a2ab8db42e914ef9b726c975facc4282578c3eb/coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29", size = 264107, upload-time = "2026-03-17T10:32:47.971Z" }, - { url = "https://files.pythonhosted.org/packages/ef/ae/4ae35bbd9a0af9d820362751f0766582833c211224b38665c0f8de3d487f/coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607", size = 266513, upload-time = "2026-03-17T10:32:50.1Z" }, - { url = "https://files.pythonhosted.org/packages/9c/20/d326174c55af36f74eac6ae781612d9492f060ce8244b570bb9d50d9d609/coverage-7.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90", size = 267650, upload-time = "2026-03-17T10:32:52.391Z" }, - { url = "https://files.pythonhosted.org/packages/7a/5e/31484d62cbd0eabd3412e30d74386ece4a0837d4f6c3040a653878bfc019/coverage-7.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3", size = 261089, upload-time = "2026-03-17T10:32:54.544Z" }, - { url = "https://files.pythonhosted.org/packages/e9/d8/49a72d6de146eebb0b7e48cc0f4bc2c0dd858e3d4790ab2b39a2872b62bd/coverage-7.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab", size = 263982, upload-time = "2026-03-17T10:32:56.803Z" }, - { url = "https://files.pythonhosted.org/packages/06/3b/0351f1bd566e6e4dd39e978efe7958bde1d32f879e85589de147654f57bb/coverage-7.13.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562", size = 261579, upload-time = "2026-03-17T10:32:59.466Z" }, - { url = "https://files.pythonhosted.org/packages/5d/ce/796a2a2f4017f554d7810f5c573449b35b1e46788424a548d4d19201b222/coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2", size = 265316, upload-time = "2026-03-17T10:33:01.847Z" }, - { url = "https://files.pythonhosted.org/packages/3d/16/d5ae91455541d1a78bc90abf495be600588aff8f6db5c8b0dae739fa39c9/coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea", size = 260427, upload-time = "2026-03-17T10:33:03.945Z" }, - { url = "https://files.pythonhosted.org/packages/48/11/07f413dba62db21fb3fad5d0de013a50e073cc4e2dc4306e770360f6dfc8/coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a", size = 262745, upload-time = "2026-03-17T10:33:06.285Z" }, - { url = "https://files.pythonhosted.org/packages/91/15/d792371332eb4663115becf4bad47e047d16234b1aff687b1b18c58d60ae/coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215", size = 223146, upload-time = "2026-03-17T10:33:08.756Z" }, - { url = "https://files.pythonhosted.org/packages/db/51/37221f59a111dca5e85be7dbf09696323b5b9f13ff65e0641d535ed06ea8/coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43", size = 224254, upload-time = "2026-03-17T10:33:11.174Z" }, - { url = "https://files.pythonhosted.org/packages/54/83/6acacc889de8987441aa7d5adfbdbf33d288dad28704a67e574f1df9bcbb/coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45", size = 222276, upload-time = "2026-03-17T10:33:13.466Z" }, - { url = "https://files.pythonhosted.org/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346, upload-time = "2026-03-17T10:33:15.691Z" }, +version = "7.13.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/ad/b59e5b451cf7172b8d1043dc0fa718f23aab379bc1521ee13d4bd9bfa960/coverage-7.13.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d490ba50c3f35dd7c17953c68f3270e7ccd1c6642e2d2afe2d8e720b98f5a053", size = 219278, upload-time = "2026-02-09T12:56:31.673Z" }, + { url = "https://files.pythonhosted.org/packages/f1/17/0cb7ca3de72e5f4ef2ec2fa0089beafbcaaaead1844e8b8a63d35173d77d/coverage-7.13.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:19bc3c88078789f8ef36acb014d7241961dbf883fd2533d18cb1e7a5b4e28b11", size = 219783, upload-time = "2026-02-09T12:56:33.104Z" }, + { url = "https://files.pythonhosted.org/packages/ab/63/325d8e5b11e0eaf6d0f6a44fad444ae58820929a9b0de943fa377fe73e85/coverage-7.13.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3998e5a32e62fdf410c0dbd3115df86297995d6e3429af80b8798aad894ca7aa", size = 250200, upload-time = "2026-02-09T12:56:34.474Z" }, + { url = "https://files.pythonhosted.org/packages/76/53/c16972708cbb79f2942922571a687c52bd109a7bd51175aeb7558dff2236/coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7", size = 252114, upload-time = "2026-02-09T12:56:35.749Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c2/7ab36d8b8cc412bec9ea2d07c83c48930eb4ba649634ba00cb7e4e0f9017/coverage-7.13.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3aa4e7b9e416774b21797365b358a6e827ffadaaca81b69ee02946852449f00", size = 254220, upload-time = "2026-02-09T12:56:37.796Z" }, + { url = "https://files.pythonhosted.org/packages/d6/4d/cf52c9a3322c89a0e6febdfbc83bb45c0ed3c64ad14081b9503adee702e7/coverage-7.13.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:71ca20079dd8f27fcf808817e281e90220475cd75115162218d0e27549f95fef", size = 256164, upload-time = "2026-02-09T12:56:39.016Z" }, + { url = "https://files.pythonhosted.org/packages/78/e9/eb1dd17bd6de8289df3580e967e78294f352a5df8a57ff4671ee5fc3dcd0/coverage-7.13.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e2f25215f1a359ab17320b47bcdaca3e6e6356652e8256f2441e4ef972052903", size = 250325, upload-time = "2026-02-09T12:56:40.668Z" }, + { url = "https://files.pythonhosted.org/packages/71/07/8c1542aa873728f72267c07278c5cc0ec91356daf974df21335ccdb46368/coverage-7.13.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d65b2d373032411e86960604dc4edac91fdfb5dca539461cf2cbe78327d1e64f", size = 251913, upload-time = "2026-02-09T12:56:41.97Z" }, + { url = "https://files.pythonhosted.org/packages/74/d7/c62e2c5e4483a748e27868e4c32ad3daa9bdddbba58e1bc7a15e252baa74/coverage-7.13.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94eb63f9b363180aff17de3e7c8760c3ba94664ea2695c52f10111244d16a299", size = 249974, upload-time = "2026-02-09T12:56:43.323Z" }, + { url = "https://files.pythonhosted.org/packages/98/9f/4c5c015a6e98ced54efd0f5cf8d31b88e5504ecb6857585fc0161bb1e600/coverage-7.13.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e856bf6616714c3a9fbc270ab54103f4e685ba236fa98c054e8f87f266c93505", size = 253741, upload-time = "2026-02-09T12:56:45.155Z" }, + { url = "https://files.pythonhosted.org/packages/bd/59/0f4eef89b9f0fcd9633b5d350016f54126ab49426a70ff4c4e87446cabdc/coverage-7.13.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:65dfcbe305c3dfe658492df2d85259e0d79ead4177f9ae724b6fb245198f55d6", size = 249695, upload-time = "2026-02-09T12:56:46.636Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2c/b7476f938deb07166f3eb281a385c262675d688ff4659ad56c6c6b8e2e70/coverage-7.13.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b507778ae8a4c915436ed5c2e05b4a6cecfa70f734e19c22a005152a11c7b6a9", size = 250599, upload-time = "2026-02-09T12:56:48.13Z" }, + { url = "https://files.pythonhosted.org/packages/b8/34/c3420709d9846ee3785b9f2831b4d94f276f38884032dca1457fa83f7476/coverage-7.13.4-cp311-cp311-win32.whl", hash = "sha256:784fc3cf8be001197b652d51d3fd259b1e2262888693a4636e18879f613a62a9", size = 221780, upload-time = "2026-02-09T12:56:50.479Z" }, + { url = "https://files.pythonhosted.org/packages/61/08/3d9c8613079d2b11c185b865de9a4c1a68850cfda2b357fae365cf609f29/coverage-7.13.4-cp311-cp311-win_amd64.whl", hash = "sha256:2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f", size = 222715, upload-time = "2026-02-09T12:56:51.815Z" }, + { url = "https://files.pythonhosted.org/packages/18/1a/54c3c80b2f056164cc0a6cdcb040733760c7c4be9d780fe655f356f433e4/coverage-7.13.4-cp311-cp311-win_arm64.whl", hash = "sha256:79e73a76b854d9c6088fe5d8b2ebe745f8681c55f7397c3c0a016192d681045f", size = 221385, upload-time = "2026-02-09T12:56:53.194Z" }, + { url = "https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459", size = 219449, upload-time = "2026-02-09T12:56:54.889Z" }, + { url = "https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3", size = 219810, upload-time = "2026-02-09T12:56:56.33Z" }, + { url = "https://files.pythonhosted.org/packages/78/72/2f372b726d433c9c35e56377cf1d513b4c16fe51841060d826b95caacec1/coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634", size = 251308, upload-time = "2026-02-09T12:56:57.858Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3", size = 254052, upload-time = "2026-02-09T12:56:59.754Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ac/45dc2e19a1939098d783c846e130b8f862fbb50d09e0af663988f2f21973/coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa", size = 255165, upload-time = "2026-02-09T12:57:01.287Z" }, + { url = "https://files.pythonhosted.org/packages/2d/4d/26d236ff35abc3b5e63540d3386e4c3b192168c1d96da5cb2f43c640970f/coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3", size = 257432, upload-time = "2026-02-09T12:57:02.637Z" }, + { url = "https://files.pythonhosted.org/packages/ec/55/14a966c757d1348b2e19caf699415a2a4c4f7feaa4bbc6326a51f5c7dd1b/coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a", size = 251716, upload-time = "2026-02-09T12:57:04.056Z" }, + { url = "https://files.pythonhosted.org/packages/77/33/50116647905837c66d28b2af1321b845d5f5d19be9655cb84d4a0ea806b4/coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7", size = 253089, upload-time = "2026-02-09T12:57:05.503Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b4/8efb11a46e3665d92635a56e4f2d4529de6d33f2cb38afd47d779d15fc99/coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc", size = 251232, upload-time = "2026-02-09T12:57:06.879Z" }, + { url = "https://files.pythonhosted.org/packages/51/24/8cd73dd399b812cc76bb0ac260e671c4163093441847ffe058ac9fda1e32/coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47", size = 255299, upload-time = "2026-02-09T12:57:08.245Z" }, + { url = "https://files.pythonhosted.org/packages/03/94/0a4b12f1d0e029ce1ccc1c800944a9984cbe7d678e470bb6d3c6bc38a0da/coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985", size = 250796, upload-time = "2026-02-09T12:57:10.142Z" }, + { url = "https://files.pythonhosted.org/packages/73/44/6002fbf88f6698ca034360ce474c406be6d5a985b3fdb3401128031eef6b/coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0", size = 252673, upload-time = "2026-02-09T12:57:12.197Z" }, + { url = "https://files.pythonhosted.org/packages/de/c6/a0279f7c00e786be75a749a5674e6fa267bcbd8209cd10c9a450c655dfa7/coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246", size = 221990, upload-time = "2026-02-09T12:57:14.085Z" }, + { url = "https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126", size = 222800, upload-time = "2026-02-09T12:57:15.944Z" }, + { url = "https://files.pythonhosted.org/packages/47/ac/92da44ad9a6f4e3a7debd178949d6f3769bedca33830ce9b1dcdab589a37/coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d", size = 221415, upload-time = "2026-02-09T12:57:17.497Z" }, + { url = "https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9", size = 219474, upload-time = "2026-02-09T12:57:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac", size = 219844, upload-time = "2026-02-09T12:57:20.66Z" }, + { url = "https://files.pythonhosted.org/packages/97/fd/7e859f8fab324cef6c4ad7cff156ca7c489fef9179d5749b0c8d321281c2/coverage-7.13.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea", size = 250832, upload-time = "2026-02-09T12:57:22.007Z" }, + { url = "https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b", size = 253434, upload-time = "2026-02-09T12:57:23.339Z" }, + { url = "https://files.pythonhosted.org/packages/5a/88/6728a7ad17428b18d836540630487231f5470fb82454871149502f5e5aa2/coverage-7.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525", size = 254676, upload-time = "2026-02-09T12:57:24.774Z" }, + { url = "https://files.pythonhosted.org/packages/7c/bc/21244b1b8cedf0dff0a2b53b208015fe798d5f2a8d5348dbfece04224fff/coverage-7.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242", size = 256807, upload-time = "2026-02-09T12:57:26.125Z" }, + { url = "https://files.pythonhosted.org/packages/97/a0/ddba7ed3251cff51006737a727d84e05b61517d1784a9988a846ba508877/coverage-7.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148", size = 251058, upload-time = "2026-02-09T12:57:27.614Z" }, + { url = "https://files.pythonhosted.org/packages/9b/55/e289addf7ff54d3a540526f33751951bf0878f3809b47f6dfb3def69c6f7/coverage-7.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a", size = 252805, upload-time = "2026-02-09T12:57:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/13/4e/cc276b1fa4a59be56d96f1dabddbdc30f4ba22e3b1cd42504c37b3313255/coverage-7.13.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23", size = 250766, upload-time = "2026-02-09T12:57:30.522Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/1093b8f93018f8b41a8cf29636c9292502f05e4a113d4d107d14a3acd044/coverage-7.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80", size = 254923, upload-time = "2026-02-09T12:57:31.946Z" }, + { url = "https://files.pythonhosted.org/packages/8b/55/ea2796da2d42257f37dbea1aab239ba9263b31bd91d5527cdd6db5efe174/coverage-7.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea", size = 250591, upload-time = "2026-02-09T12:57:33.842Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/7c4bb72aacf8af5020675aa633e59c1fbe296d22aed191b6a5b711eb2bc7/coverage-7.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a", size = 252364, upload-time = "2026-02-09T12:57:35.743Z" }, + { url = "https://files.pythonhosted.org/packages/5c/38/a8d2ec0146479c20bbaa7181b5b455a0c41101eed57f10dd19a78ab44c80/coverage-7.13.4-cp313-cp313-win32.whl", hash = "sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d", size = 222010, upload-time = "2026-02-09T12:57:37.25Z" }, + { url = "https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd", size = 222818, upload-time = "2026-02-09T12:57:38.734Z" }, + { url = "https://files.pythonhosted.org/packages/04/d1/934918a138c932c90d78301f45f677fb05c39a3112b96fd2c8e60503cdc7/coverage-7.13.4-cp313-cp313-win_arm64.whl", hash = "sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af", size = 221438, upload-time = "2026-02-09T12:57:40.223Z" }, + { url = "https://files.pythonhosted.org/packages/52/57/ee93ced533bcb3e6df961c0c6e42da2fc6addae53fb95b94a89b1e33ebd7/coverage-7.13.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d", size = 220165, upload-time = "2026-02-09T12:57:41.639Z" }, + { url = "https://files.pythonhosted.org/packages/c5/e0/969fc285a6fbdda49d91af278488d904dcd7651b2693872f0ff94e40e84a/coverage-7.13.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12", size = 220516, upload-time = "2026-02-09T12:57:44.215Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b8/9531944e16267e2735a30a9641ff49671f07e8138ecf1ca13db9fd2560c7/coverage-7.13.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b", size = 261804, upload-time = "2026-02-09T12:57:45.989Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f3/e63df6d500314a2a60390d1989240d5f27318a7a68fa30ad3806e2a9323e/coverage-7.13.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9", size = 263885, upload-time = "2026-02-09T12:57:47.42Z" }, + { url = "https://files.pythonhosted.org/packages/f3/67/7654810de580e14b37670b60a09c599fa348e48312db5b216d730857ffe6/coverage-7.13.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092", size = 266308, upload-time = "2026-02-09T12:57:49.345Z" }, + { url = "https://files.pythonhosted.org/packages/37/6f/39d41eca0eab3cc82115953ad41c4e77935286c930e8fad15eaed1389d83/coverage-7.13.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9", size = 267452, upload-time = "2026-02-09T12:57:50.811Z" }, + { url = "https://files.pythonhosted.org/packages/50/6d/39c0fbb8fc5cd4d2090811e553c2108cf5112e882f82505ee7495349a6bf/coverage-7.13.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26", size = 261057, upload-time = "2026-02-09T12:57:52.447Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a2/60010c669df5fa603bb5a97fb75407e191a846510da70ac657eb696b7fce/coverage-7.13.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2", size = 263875, upload-time = "2026-02-09T12:57:53.938Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d9/63b22a6bdbd17f1f96e9ed58604c2a6b0e72a9133e37d663bef185877cf6/coverage-7.13.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940", size = 261500, upload-time = "2026-02-09T12:57:56.012Z" }, + { url = "https://files.pythonhosted.org/packages/70/bf/69f86ba1ad85bc3ad240e4c0e57a2e620fbc0e1645a47b5c62f0e941ad7f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c", size = 265212, upload-time = "2026-02-09T12:57:57.5Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f2/5f65a278a8c2148731831574c73e42f57204243d33bedaaf18fa79c5958f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0", size = 260398, upload-time = "2026-02-09T12:57:59.027Z" }, + { url = "https://files.pythonhosted.org/packages/ef/80/6e8280a350ee9fea92f14b8357448a242dcaa243cb2c72ab0ca591f66c8c/coverage-7.13.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b", size = 262584, upload-time = "2026-02-09T12:58:01.129Z" }, + { url = "https://files.pythonhosted.org/packages/22/63/01ff182fc95f260b539590fb12c11ad3e21332c15f9799cb5e2386f71d9f/coverage-7.13.4-cp313-cp313t-win32.whl", hash = "sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9", size = 222688, upload-time = "2026-02-09T12:58:02.736Z" }, + { url = "https://files.pythonhosted.org/packages/a9/43/89de4ef5d3cd53b886afa114065f7e9d3707bdb3e5efae13535b46ae483d/coverage-7.13.4-cp313-cp313t-win_amd64.whl", hash = "sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd", size = 223746, upload-time = "2026-02-09T12:58:05.362Z" }, + { url = "https://files.pythonhosted.org/packages/35/39/7cf0aa9a10d470a5309b38b289b9bb07ddeac5d61af9b664fe9775a4cb3e/coverage-7.13.4-cp313-cp313t-win_arm64.whl", hash = "sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997", size = 222003, upload-time = "2026-02-09T12:58:06.952Z" }, + { url = "https://files.pythonhosted.org/packages/92/11/a9cf762bb83386467737d32187756a42094927150c3e107df4cb078e8590/coverage-7.13.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:300deaee342f90696ed186e3a00c71b5b3d27bffe9e827677954f4ee56969601", size = 219522, upload-time = "2026-02-09T12:58:08.623Z" }, + { url = "https://files.pythonhosted.org/packages/d3/28/56e6d892b7b052236d67c95f1936b6a7cf7c3e2634bf27610b8cbd7f9c60/coverage-7.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29e3220258d682b6226a9b0925bc563ed9a1ebcff3cad30f043eceea7eaf2689", size = 219855, upload-time = "2026-02-09T12:58:10.176Z" }, + { url = "https://files.pythonhosted.org/packages/e5/69/233459ee9eb0c0d10fcc2fe425a029b3fa5ce0f040c966ebce851d030c70/coverage-7.13.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:391ee8f19bef69210978363ca930f7328081c6a0152f1166c91f0b5fdd2a773c", size = 250887, upload-time = "2026-02-09T12:58:12.503Z" }, + { url = "https://files.pythonhosted.org/packages/06/90/2cdab0974b9b5bbc1623f7876b73603aecac11b8d95b85b5b86b32de5eab/coverage-7.13.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0dd7ab8278f0d58a0128ba2fca25824321f05d059c1441800e934ff2efa52129", size = 253396, upload-time = "2026-02-09T12:58:14.615Z" }, + { url = "https://files.pythonhosted.org/packages/ac/15/ea4da0f85bf7d7b27635039e649e99deb8173fe551096ea15017f7053537/coverage-7.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78cdf0d578b15148b009ccf18c686aa4f719d887e76e6b40c38ffb61d264a552", size = 254745, upload-time = "2026-02-09T12:58:16.162Z" }, + { url = "https://files.pythonhosted.org/packages/99/11/bb356e86920c655ca4d61daee4e2bbc7258f0a37de0be32d233b561134ff/coverage-7.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:48685fee12c2eb3b27c62f2658e7ea21e9c3239cba5a8a242801a0a3f6a8c62a", size = 257055, upload-time = "2026-02-09T12:58:17.892Z" }, + { url = "https://files.pythonhosted.org/packages/c9/0f/9ae1f8cb17029e09da06ca4e28c9e1d5c1c0a511c7074592e37e0836c915/coverage-7.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4e83efc079eb39480e6346a15a1bcb3e9b04759c5202d157e1dd4303cd619356", size = 250911, upload-time = "2026-02-09T12:58:19.495Z" }, + { url = "https://files.pythonhosted.org/packages/89/3a/adfb68558fa815cbc29747b553bc833d2150228f251b127f1ce97e48547c/coverage-7.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecae9737b72408d6a950f7e525f30aca12d4bd8dd95e37342e5beb3a2a8c4f71", size = 252754, upload-time = "2026-02-09T12:58:21.064Z" }, + { url = "https://files.pythonhosted.org/packages/32/b1/540d0c27c4e748bd3cd0bd001076ee416eda993c2bae47a73b7cc9357931/coverage-7.13.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ae4578f8528569d3cf303fef2ea569c7f4c4059a38c8667ccef15c6e1f118aa5", size = 250720, upload-time = "2026-02-09T12:58:22.622Z" }, + { url = "https://files.pythonhosted.org/packages/c7/95/383609462b3ffb1fe133014a7c84fc0dd01ed55ac6140fa1093b5af7ebb1/coverage-7.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6fdef321fdfbb30a197efa02d48fcd9981f0d8ad2ae8903ac318adc653f5df98", size = 254994, upload-time = "2026-02-09T12:58:24.548Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ba/1761138e86c81680bfc3c49579d66312865457f9fe405b033184e5793cb3/coverage-7.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b0f6ccf3dbe577170bebfce1318707d0e8c3650003cb4b3a9dd744575daa8b5", size = 250531, upload-time = "2026-02-09T12:58:26.271Z" }, + { url = "https://files.pythonhosted.org/packages/f8/8e/05900df797a9c11837ab59c4d6fe94094e029582aab75c3309a93e6fb4e3/coverage-7.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75fcd519f2a5765db3f0e391eb3b7d150cce1a771bf4c9f861aeab86c767a3c0", size = 252189, upload-time = "2026-02-09T12:58:27.807Z" }, + { url = "https://files.pythonhosted.org/packages/00/bd/29c9f2db9ea4ed2738b8a9508c35626eb205d51af4ab7bf56a21a2e49926/coverage-7.13.4-cp314-cp314-win32.whl", hash = "sha256:8e798c266c378da2bd819b0677df41ab46d78065fb2a399558f3f6cae78b2fbb", size = 222258, upload-time = "2026-02-09T12:58:29.441Z" }, + { url = "https://files.pythonhosted.org/packages/a7/4d/1f8e723f6829977410efeb88f73673d794075091c8c7c18848d273dc9d73/coverage-7.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505", size = 223073, upload-time = "2026-02-09T12:58:31.026Z" }, + { url = "https://files.pythonhosted.org/packages/51/5b/84100025be913b44e082ea32abcf1afbf4e872f5120b7a1cab1d331b1e13/coverage-7.13.4-cp314-cp314-win_arm64.whl", hash = "sha256:ad27098a189e5838900ce4c2a99f2fe42a0bf0c2093c17c69b45a71579e8d4a2", size = 221638, upload-time = "2026-02-09T12:58:32.599Z" }, + { url = "https://files.pythonhosted.org/packages/a7/e4/c884a405d6ead1370433dad1e3720216b4f9fd8ef5b64bfd984a2a60a11a/coverage-7.13.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:85480adfb35ffc32d40918aad81b89c69c9cc5661a9b8a81476d3e645321a056", size = 220246, upload-time = "2026-02-09T12:58:34.181Z" }, + { url = "https://files.pythonhosted.org/packages/81/5c/4d7ed8b23b233b0fffbc9dfec53c232be2e695468523242ea9fd30f97ad2/coverage-7.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:79be69cf7f3bf9b0deeeb062eab7ac7f36cd4cc4c4dd694bd28921ba4d8596cc", size = 220514, upload-time = "2026-02-09T12:58:35.704Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6f/3284d4203fd2f28edd73034968398cd2d4cb04ab192abc8cff007ea35679/coverage-7.13.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:caa421e2684e382c5d8973ac55e4f36bed6821a9bad5c953494de960c74595c9", size = 261877, upload-time = "2026-02-09T12:58:37.864Z" }, + { url = "https://files.pythonhosted.org/packages/09/aa/b672a647bbe1556a85337dc95bfd40d146e9965ead9cc2fe81bde1e5cbce/coverage-7.13.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14375934243ee05f56c45393fe2ce81fe5cc503c07cee2bdf1725fb8bef3ffaf", size = 264004, upload-time = "2026-02-09T12:58:39.492Z" }, + { url = "https://files.pythonhosted.org/packages/79/a1/aa384dbe9181f98bba87dd23dda436f0c6cf2e148aecbb4e50fc51c1a656/coverage-7.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25a41c3104d08edb094d9db0d905ca54d0cd41c928bb6be3c4c799a54753af55", size = 266408, upload-time = "2026-02-09T12:58:41.852Z" }, + { url = "https://files.pythonhosted.org/packages/53/5e/5150bf17b4019bc600799f376bb9606941e55bd5a775dc1e096b6ffea952/coverage-7.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f01afcff62bf9a08fb32b2c1d6e924236c0383c02c790732b6537269e466a72", size = 267544, upload-time = "2026-02-09T12:58:44.093Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ed/f1de5c675987a4a7a672250d2c5c9d73d289dbf13410f00ed7181d8017dd/coverage-7.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eb9078108fbf0bcdde37c3f4779303673c2fa1fe8f7956e68d447d0dd426d38a", size = 260980, upload-time = "2026-02-09T12:58:45.721Z" }, + { url = "https://files.pythonhosted.org/packages/b3/e3/fe758d01850aa172419a6743fe76ba8b92c29d181d4f676ffe2dae2ba631/coverage-7.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e086334e8537ddd17e5f16a344777c1ab8194986ec533711cbe6c41cde841b6", size = 263871, upload-time = "2026-02-09T12:58:47.334Z" }, + { url = "https://files.pythonhosted.org/packages/b6/76/b829869d464115e22499541def9796b25312b8cf235d3bb00b39f1675395/coverage-7.13.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:725d985c5ab621268b2edb8e50dfe57633dc69bda071abc470fed55a14935fd3", size = 261472, upload-time = "2026-02-09T12:58:48.995Z" }, + { url = "https://files.pythonhosted.org/packages/14/9e/caedb1679e73e2f6ad240173f55218488bfe043e38da577c4ec977489915/coverage-7.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3c06f0f1337c667b971ca2f975523347e63ec5e500b9aa5882d91931cd3ef750", size = 265210, upload-time = "2026-02-09T12:58:51.178Z" }, + { url = "https://files.pythonhosted.org/packages/3a/10/0dd02cb009b16ede425b49ec344aba13a6ae1dc39600840ea6abcb085ac4/coverage-7.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:590c0ed4bf8e85f745e6b805b2e1c457b2e33d5255dd9729743165253bc9ad39", size = 260319, upload-time = "2026-02-09T12:58:53.081Z" }, + { url = "https://files.pythonhosted.org/packages/92/8e/234d2c927af27c6d7a5ffad5bd2cf31634c46a477b4c7adfbfa66baf7ebb/coverage-7.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eb30bf180de3f632cd043322dad5751390e5385108b2807368997d1a92a509d0", size = 262638, upload-time = "2026-02-09T12:58:55.258Z" }, + { url = "https://files.pythonhosted.org/packages/2f/64/e5547c8ff6964e5965c35a480855911b61509cce544f4d442caa759a0702/coverage-7.13.4-cp314-cp314t-win32.whl", hash = "sha256:c4240e7eded42d131a2d2c4dec70374b781b043ddc79a9de4d55ca71f8e98aea", size = 223040, upload-time = "2026-02-09T12:58:56.936Z" }, + { url = "https://files.pythonhosted.org/packages/c7/96/38086d58a181aac86d503dfa9c47eb20715a79c3e3acbdf786e92e5c09a8/coverage-7.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4c7d3cc01e7350f2f0f6f7036caaf5673fb56b6998889ccfe9e1c1fe75a9c932", size = 224148, upload-time = "2026-02-09T12:58:58.645Z" }, + { url = "https://files.pythonhosted.org/packages/ce/72/8d10abd3740a0beb98c305e0c3faf454366221c0f37a8bcf8f60020bb65a/coverage-7.13.4-cp314-cp314t-win_arm64.whl", hash = "sha256:23e3f687cf945070d1c90f85db66d11e3025665d8dafa831301a0e0038f3db9b", size = 222172, upload-time = "2026-02-09T12:59:00.396Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" }, ] [package.optional-dependencies] @@ -636,44 +620,40 @@ toml = [ [[package]] name = "cuda-bindings" -version = "13.2.0" +version = "12.9.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cuda-pathfinder" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/a9/3a8241c6e19483ac1f1dcf5c10238205dcb8a6e9d0d4d4709240dff28ff4/cuda_bindings-13.2.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:721104c603f059780d287969be3d194a18d0cc3b713ed9049065a1107706759d", size = 5730273 }, - { url = "https://files.pythonhosted.org/packages/e9/94/2748597f47bb1600cd466b20cab4159f1530a3a33fe7f70fee199b3abb9e/cuda_bindings-13.2.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1eba9504ac70667dd48313395fe05157518fd6371b532790e96fbb31bbb5a5e1", size = 6313924 }, - { url = "https://files.pythonhosted.org/packages/52/c8/b2589d68acf7e3d63e2be330b84bc25712e97ed799affbca7edd7eae25d6/cuda_bindings-13.2.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e865447abfb83d6a98ad5130ed3c70b1fc295ae3eeee39fd07b4ddb0671b6788", size = 5722404 }, - { url = "https://files.pythonhosted.org/packages/1f/92/f899f7bbb5617bb65ec52a6eac1e9a1447a86b916c4194f8a5001b8cde0c/cuda_bindings-13.2.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46d8776a55d6d5da9dd6e9858fba2efcda2abe6743871dee47dd06eb8cb6d955", size = 6320619 }, - { url = "https://files.pythonhosted.org/packages/df/93/eef988860a3ca985f82c4f3174fc0cdd94e07331ba9a92e8e064c260337f/cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6629ca2df6f795b784752409bcaedbd22a7a651b74b56a165ebc0c9dcbd504d0", size = 5614610 }, - { url = "https://files.pythonhosted.org/packages/18/23/6db3aba46864aee357ab2415135b3fe3da7e9f1fa0221fa2a86a5968099c/cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7dca0da053d3b4cc4869eff49c61c03f3c5dbaa0bcd712317a358d5b8f3f385d", size = 6149914 }, - { url = "https://files.pythonhosted.org/packages/c0/87/87a014f045b77c6de5c8527b0757fe644417b184e5367db977236a141602/cuda_bindings-13.2.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6464b30f46692d6c7f65d4a0e0450d81dd29de3afc1bb515653973d01c2cd6e", size = 5685673 }, - { url = "https://files.pythonhosted.org/packages/ee/5e/c0fe77a73aaefd3fff25ffaccaac69c5a63eafdf8b9a4c476626ef0ac703/cuda_bindings-13.2.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4af9f3e1be603fa12d5ad6cfca7844c9d230befa9792b5abdf7dd79979c3626", size = 6191386 }, - { url = "https://files.pythonhosted.org/packages/5f/58/ed2c3b39c8dd5f96aa7a4abef0d47a73932c7a988e30f5fa428f00ed0da1/cuda_bindings-13.2.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df850a1ff8ce1b3385257b08e47b70e959932f5f432d0a4e46a355962b4e4771", size = 5507469 }, - { url = "https://files.pythonhosted.org/packages/1f/01/0c941b112ceeb21439b05895eace78ca1aa2eaaf695c8521a068fd9b4c00/cuda_bindings-13.2.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8a16384c6494e5485f39314b0b4afb04bee48d49edb16d5d8593fd35bbd231b", size = 6059693 }, + { url = "https://files.pythonhosted.org/packages/45/e7/b47792cc2d01c7e1d37c32402182524774dadd2d26339bd224e0e913832e/cuda_bindings-12.9.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c912a3d9e6b6651853eed8eed96d6800d69c08e94052c292fec3f282c5a817c9", size = 12210593, upload-time = "2025-10-21T14:51:36.574Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c1/dabe88f52c3e3760d861401bb994df08f672ec893b8f7592dc91626adcf3/cuda_bindings-12.9.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fda147a344e8eaeca0c6ff113d2851ffca8f7dfc0a6c932374ee5c47caa649c8", size = 12151019, upload-time = "2025-10-21T14:51:43.167Z" }, + { url = "https://files.pythonhosted.org/packages/63/56/e465c31dc9111be3441a9ba7df1941fe98f4aa6e71e8788a3fb4534ce24d/cuda_bindings-12.9.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:32bdc5a76906be4c61eb98f546a6786c5773a881f3b166486449b5d141e4a39f", size = 11906628, upload-time = "2025-10-21T14:51:49.905Z" }, + { url = "https://files.pythonhosted.org/packages/a3/84/1e6be415e37478070aeeee5884c2022713c1ecc735e6d82d744de0252eee/cuda_bindings-12.9.4-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56e0043c457a99ac473ddc926fe0dc4046694d99caef633e92601ab52cbe17eb", size = 11925991, upload-time = "2025-10-21T14:51:56.535Z" }, + { url = "https://files.pythonhosted.org/packages/d1/af/6dfd8f2ed90b1d4719bc053ff8940e494640fe4212dc3dd72f383e4992da/cuda_bindings-12.9.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8b72ee72a9cc1b531db31eebaaee5c69a8ec3500e32c6933f2d3b15297b53686", size = 11922703, upload-time = "2025-10-21T14:52:03.585Z" }, + { url = "https://files.pythonhosted.org/packages/6c/19/90ac264acc00f6df8a49378eedec9fd2db3061bf9263bf9f39fd3d8377c3/cuda_bindings-12.9.4-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d80bffc357df9988dca279734bc9674c3934a654cab10cadeed27ce17d8635ee", size = 11924658, upload-time = "2025-10-21T14:52:10.411Z" }, ] [[package]] name = "cuda-pathfinder" -version = "1.4.3" +version = "1.4.0" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/59/911a1a597264f1fb7ac176995a0f0b6062e37f8c1b6e0f23071a76838507/cuda_pathfinder-1.4.3-py3-none-any.whl", hash = "sha256:4345d8ead1f701c4fb8a99be6bc1843a7348b6ba0ef3b031f5a2d66fb128ae4c", size = 47951, upload-time = "2026-03-16T21:31:25.526Z" }, + { url = "https://files.pythonhosted.org/packages/ff/60/d8f1dbfb7f06b94c662e98c95189e6f39b817da638bc8fcea0d003f89e5d/cuda_pathfinder-1.4.0-py3-none-any.whl", hash = "sha256:437079ca59e7b61ae439ecc501d69ed87b3accc34d58153ef1e54815e2c2e118", size = 38406, upload-time = "2026-02-25T22:13:00.807Z" }, ] [[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 } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321 }, + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, ] [[package]] name = "dask" -version = "2026.3.0" +version = "2026.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -685,9 +665,9 @@ dependencies = [ { name = "pyyaml" }, { name = "toolz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d7/2a/5d8cc1579590af86576dde890254440e478c7174b93a02095ecfc2e6ba38/dask-2026.3.0.tar.gz", hash = "sha256:f7d96c8274e8a900d217c1ff6ea8d1bbf0b4c2c21e74a409644498d925eb8f85", size = 11000710, upload-time = "2026-03-18T07:10:14.945Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/52/b0f9172b22778def907db1ff173249e4eb41f054b46a9c83b1528aaf811f/dask-2026.1.2.tar.gz", hash = "sha256:1136683de2750d98ea792670f7434e6c1cfce90cab2cc2f2495a9e60fd25a4fc", size = 10997838, upload-time = "2026-01-30T21:04:20.54Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl", hash = "sha256:be614b9242b0b38288060fb2d7696125946469c98a1c30e174883fd199e0428d", size = 1485630, upload-time = "2026-03-18T07:10:12.832Z" }, + { url = "https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl", hash = "sha256:46a0cf3b8d87f78a3d2e6b145aea4418a6d6d606fe6a16c79bd8ca2bb862bc91", size = 1482084, upload-time = "2026-01-30T21:04:18.363Z" }, ] [package.optional-dependencies] @@ -699,61 +679,61 @@ array = [ name = "debugpy" version = "1.8.20" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/b7/cd8080344452e4874aae67c40d8940e2b4d47b01601a8fd9f44786c757c7/debugpy-1.8.20.tar.gz", hash = "sha256:55bc8701714969f1ab89a6d5f2f3d40c36f91b2cbe2f65d98bf8196f6a6a2c33", size = 1645207 } +sdist = { url = "https://files.pythonhosted.org/packages/e0/b7/cd8080344452e4874aae67c40d8940e2b4d47b01601a8fd9f44786c757c7/debugpy-1.8.20.tar.gz", hash = "sha256:55bc8701714969f1ab89a6d5f2f3d40c36f91b2cbe2f65d98bf8196f6a6a2c33", size = 1645207, upload-time = "2026-01-29T23:03:28.199Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/56/c3baf5cbe4dd77427fd9aef99fcdade259ad128feeb8a786c246adb838e5/debugpy-1.8.20-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:eada6042ad88fa1571b74bd5402ee8b86eded7a8f7b827849761700aff171f1b", size = 2208318 }, - { url = "https://files.pythonhosted.org/packages/9a/7d/4fa79a57a8e69fe0d9763e98d1110320f9ecd7f1f362572e3aafd7417c9d/debugpy-1.8.20-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:7de0b7dfeedc504421032afba845ae2a7bcc32ddfb07dae2c3ca5442f821c344", size = 3171493 }, - { url = "https://files.pythonhosted.org/packages/7d/f2/1e8f8affe51e12a26f3a8a8a4277d6e60aa89d0a66512f63b1e799d424a4/debugpy-1.8.20-cp311-cp311-win32.whl", hash = "sha256:773e839380cf459caf73cc533ea45ec2737a5cc184cf1b3b796cd4fd98504fec", size = 5209240 }, - { url = "https://files.pythonhosted.org/packages/d5/92/1cb532e88560cbee973396254b21bece8c5d7c2ece958a67afa08c9f10dc/debugpy-1.8.20-cp311-cp311-win_amd64.whl", hash = "sha256:1f7650546e0eded1902d0f6af28f787fa1f1dbdbc97ddabaf1cd963a405930cb", size = 5233481 }, - { url = "https://files.pythonhosted.org/packages/14/57/7f34f4736bfb6e00f2e4c96351b07805d83c9a7b33d28580ae01374430f7/debugpy-1.8.20-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:4ae3135e2089905a916909ef31922b2d733d756f66d87345b3e5e52b7a55f13d", size = 2550686 }, - { url = "https://files.pythonhosted.org/packages/ab/78/b193a3975ca34458f6f0e24aaf5c3e3da72f5401f6054c0dfd004b41726f/debugpy-1.8.20-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:88f47850a4284b88bd2bfee1f26132147d5d504e4e86c22485dfa44b97e19b4b", size = 4310588 }, - { url = "https://files.pythonhosted.org/packages/c1/55/f14deb95eaf4f30f07ef4b90a8590fc05d9e04df85ee379712f6fb6736d7/debugpy-1.8.20-cp312-cp312-win32.whl", hash = "sha256:4057ac68f892064e5f98209ab582abfee3b543fb55d2e87610ddc133a954d390", size = 5331372 }, - { url = "https://files.pythonhosted.org/packages/a1/39/2bef246368bd42f9bd7cba99844542b74b84dacbdbea0833e610f384fee8/debugpy-1.8.20-cp312-cp312-win_amd64.whl", hash = "sha256:a1a8f851e7cf171330679ef6997e9c579ef6dd33c9098458bd9986a0f4ca52e3", size = 5372835 }, - { url = "https://files.pythonhosted.org/packages/15/e2/fc500524cc6f104a9d049abc85a0a8b3f0d14c0a39b9c140511c61e5b40b/debugpy-1.8.20-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:5dff4bb27027821fdfcc9e8f87309a28988231165147c31730128b1c983e282a", size = 2539560 }, - { url = "https://files.pythonhosted.org/packages/90/83/fb33dcea789ed6018f8da20c5a9bc9d82adc65c0c990faed43f7c955da46/debugpy-1.8.20-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:84562982dd7cf5ebebfdea667ca20a064e096099997b175fe204e86817f64eaf", size = 4293272 }, - { url = "https://files.pythonhosted.org/packages/a6/25/b1e4a01bfb824d79a6af24b99ef291e24189080c93576dfd9b1a2815cd0f/debugpy-1.8.20-cp313-cp313-win32.whl", hash = "sha256:da11dea6447b2cadbf8ce2bec59ecea87cc18d2c574980f643f2d2dfe4862393", size = 5331208 }, - { url = "https://files.pythonhosted.org/packages/13/f7/a0b368ce54ffff9e9028c098bd2d28cfc5b54f9f6c186929083d4c60ba58/debugpy-1.8.20-cp313-cp313-win_amd64.whl", hash = "sha256:eb506e45943cab2efb7c6eafdd65b842f3ae779f020c82221f55aca9de135ed7", size = 5372930 }, - { url = "https://files.pythonhosted.org/packages/33/2e/f6cb9a8a13f5058f0a20fe09711a7b726232cd5a78c6a7c05b2ec726cff9/debugpy-1.8.20-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:9c74df62fc064cd5e5eaca1353a3ef5a5d50da5eb8058fcef63106f7bebe6173", size = 2538066 }, - { url = "https://files.pythonhosted.org/packages/c5/56/6ddca50b53624e1ca3ce1d1e49ff22db46c47ea5fb4c0cc5c9b90a616364/debugpy-1.8.20-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:077a7447589ee9bc1ff0cdf443566d0ecf540ac8aa7333b775ebcb8ce9f4ecad", size = 4269425 }, - { url = "https://files.pythonhosted.org/packages/c5/d9/d64199c14a0d4c476df46c82470a3ce45c8d183a6796cfb5e66533b3663c/debugpy-1.8.20-cp314-cp314-win32.whl", hash = "sha256:352036a99dd35053b37b7803f748efc456076f929c6a895556932eaf2d23b07f", size = 5331407 }, - { url = "https://files.pythonhosted.org/packages/e0/d9/1f07395b54413432624d61524dfd98c1a7c7827d2abfdb8829ac92638205/debugpy-1.8.20-cp314-cp314-win_amd64.whl", hash = "sha256:a98eec61135465b062846112e5ecf2eebb855305acc1dfbae43b72903b8ab5be", size = 5372521 }, - { url = "https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl", hash = "sha256:5be9bed9ae3be00665a06acaa48f8329d2b9632f15fd09f6a9a8c8d9907e54d7", size = 5337658 }, + { url = "https://files.pythonhosted.org/packages/51/56/c3baf5cbe4dd77427fd9aef99fcdade259ad128feeb8a786c246adb838e5/debugpy-1.8.20-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:eada6042ad88fa1571b74bd5402ee8b86eded7a8f7b827849761700aff171f1b", size = 2208318, upload-time = "2026-01-29T23:03:36.481Z" }, + { url = "https://files.pythonhosted.org/packages/9a/7d/4fa79a57a8e69fe0d9763e98d1110320f9ecd7f1f362572e3aafd7417c9d/debugpy-1.8.20-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:7de0b7dfeedc504421032afba845ae2a7bcc32ddfb07dae2c3ca5442f821c344", size = 3171493, upload-time = "2026-01-29T23:03:37.775Z" }, + { url = "https://files.pythonhosted.org/packages/7d/f2/1e8f8affe51e12a26f3a8a8a4277d6e60aa89d0a66512f63b1e799d424a4/debugpy-1.8.20-cp311-cp311-win32.whl", hash = "sha256:773e839380cf459caf73cc533ea45ec2737a5cc184cf1b3b796cd4fd98504fec", size = 5209240, upload-time = "2026-01-29T23:03:39.109Z" }, + { url = "https://files.pythonhosted.org/packages/d5/92/1cb532e88560cbee973396254b21bece8c5d7c2ece958a67afa08c9f10dc/debugpy-1.8.20-cp311-cp311-win_amd64.whl", hash = "sha256:1f7650546e0eded1902d0f6af28f787fa1f1dbdbc97ddabaf1cd963a405930cb", size = 5233481, upload-time = "2026-01-29T23:03:40.659Z" }, + { url = "https://files.pythonhosted.org/packages/14/57/7f34f4736bfb6e00f2e4c96351b07805d83c9a7b33d28580ae01374430f7/debugpy-1.8.20-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:4ae3135e2089905a916909ef31922b2d733d756f66d87345b3e5e52b7a55f13d", size = 2550686, upload-time = "2026-01-29T23:03:42.023Z" }, + { url = "https://files.pythonhosted.org/packages/ab/78/b193a3975ca34458f6f0e24aaf5c3e3da72f5401f6054c0dfd004b41726f/debugpy-1.8.20-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:88f47850a4284b88bd2bfee1f26132147d5d504e4e86c22485dfa44b97e19b4b", size = 4310588, upload-time = "2026-01-29T23:03:43.314Z" }, + { url = "https://files.pythonhosted.org/packages/c1/55/f14deb95eaf4f30f07ef4b90a8590fc05d9e04df85ee379712f6fb6736d7/debugpy-1.8.20-cp312-cp312-win32.whl", hash = "sha256:4057ac68f892064e5f98209ab582abfee3b543fb55d2e87610ddc133a954d390", size = 5331372, upload-time = "2026-01-29T23:03:45.526Z" }, + { url = "https://files.pythonhosted.org/packages/a1/39/2bef246368bd42f9bd7cba99844542b74b84dacbdbea0833e610f384fee8/debugpy-1.8.20-cp312-cp312-win_amd64.whl", hash = "sha256:a1a8f851e7cf171330679ef6997e9c579ef6dd33c9098458bd9986a0f4ca52e3", size = 5372835, upload-time = "2026-01-29T23:03:47.245Z" }, + { url = "https://files.pythonhosted.org/packages/15/e2/fc500524cc6f104a9d049abc85a0a8b3f0d14c0a39b9c140511c61e5b40b/debugpy-1.8.20-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:5dff4bb27027821fdfcc9e8f87309a28988231165147c31730128b1c983e282a", size = 2539560, upload-time = "2026-01-29T23:03:48.738Z" }, + { url = "https://files.pythonhosted.org/packages/90/83/fb33dcea789ed6018f8da20c5a9bc9d82adc65c0c990faed43f7c955da46/debugpy-1.8.20-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:84562982dd7cf5ebebfdea667ca20a064e096099997b175fe204e86817f64eaf", size = 4293272, upload-time = "2026-01-29T23:03:50.169Z" }, + { url = "https://files.pythonhosted.org/packages/a6/25/b1e4a01bfb824d79a6af24b99ef291e24189080c93576dfd9b1a2815cd0f/debugpy-1.8.20-cp313-cp313-win32.whl", hash = "sha256:da11dea6447b2cadbf8ce2bec59ecea87cc18d2c574980f643f2d2dfe4862393", size = 5331208, upload-time = "2026-01-29T23:03:51.547Z" }, + { url = "https://files.pythonhosted.org/packages/13/f7/a0b368ce54ffff9e9028c098bd2d28cfc5b54f9f6c186929083d4c60ba58/debugpy-1.8.20-cp313-cp313-win_amd64.whl", hash = "sha256:eb506e45943cab2efb7c6eafdd65b842f3ae779f020c82221f55aca9de135ed7", size = 5372930, upload-time = "2026-01-29T23:03:53.585Z" }, + { url = "https://files.pythonhosted.org/packages/33/2e/f6cb9a8a13f5058f0a20fe09711a7b726232cd5a78c6a7c05b2ec726cff9/debugpy-1.8.20-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:9c74df62fc064cd5e5eaca1353a3ef5a5d50da5eb8058fcef63106f7bebe6173", size = 2538066, upload-time = "2026-01-29T23:03:54.999Z" }, + { url = "https://files.pythonhosted.org/packages/c5/56/6ddca50b53624e1ca3ce1d1e49ff22db46c47ea5fb4c0cc5c9b90a616364/debugpy-1.8.20-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:077a7447589ee9bc1ff0cdf443566d0ecf540ac8aa7333b775ebcb8ce9f4ecad", size = 4269425, upload-time = "2026-01-29T23:03:56.518Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d9/d64199c14a0d4c476df46c82470a3ce45c8d183a6796cfb5e66533b3663c/debugpy-1.8.20-cp314-cp314-win32.whl", hash = "sha256:352036a99dd35053b37b7803f748efc456076f929c6a895556932eaf2d23b07f", size = 5331407, upload-time = "2026-01-29T23:03:58.481Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d9/1f07395b54413432624d61524dfd98c1a7c7827d2abfdb8829ac92638205/debugpy-1.8.20-cp314-cp314-win_amd64.whl", hash = "sha256:a98eec61135465b062846112e5ecf2eebb855305acc1dfbae43b72903b8ab5be", size = 5372521, upload-time = "2026-01-29T23:03:59.864Z" }, + { url = "https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl", hash = "sha256:5be9bed9ae3be00665a06acaa48f8329d2b9632f15fd09f6a9a8c8d9907e54d7", size = 5337658, upload-time = "2026-01-29T23:04:17.404Z" }, ] [[package]] name = "decorator" version = "5.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711 } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190 }, + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, ] [[package]] name = "defusedxml" version = "0.7.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520 } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604 }, + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, ] [[package]] name = "dill" version = "0.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/81/e1/56027a71e31b02ddc53c7d65b01e68edf64dea2932122fe7746a516f75d5/dill-0.4.1.tar.gz", hash = "sha256:423092df4182177d4d8ba8290c8a5b640c66ab35ec7da59ccfa00f6fa3eea5fa", size = 187315 } +sdist = { url = "https://files.pythonhosted.org/packages/81/e1/56027a71e31b02ddc53c7d65b01e68edf64dea2932122fe7746a516f75d5/dill-0.4.1.tar.gz", hash = "sha256:423092df4182177d4d8ba8290c8a5b640c66ab35ec7da59ccfa00f6fa3eea5fa", size = 187315, upload-time = "2026-01-19T02:36:56.85Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d", size = 120019 }, + { url = "https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d", size = 120019, upload-time = "2026-01-19T02:36:55.663Z" }, ] [[package]] name = "distlib" version = "0.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605 } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047 }, + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, ] [[package]] @@ -763,36 +743,36 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/25/71/80cc718ff6d7abfbabacb1f57aaa42e9c1552bfdd01e64ddd704e4a03638/donfig-0.8.1.post1.tar.gz", hash = "sha256:3bef3413a4c1c601b585e8d297256d0c1470ea012afa6e8461dc28bfb7c23f52", size = 19506 } +sdist = { url = "https://files.pythonhosted.org/packages/25/71/80cc718ff6d7abfbabacb1f57aaa42e9c1552bfdd01e64ddd704e4a03638/donfig-0.8.1.post1.tar.gz", hash = "sha256:3bef3413a4c1c601b585e8d297256d0c1470ea012afa6e8461dc28bfb7c23f52", size = 19506, upload-time = "2024-05-23T14:14:31.513Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/d5/c5db1ea3394c6e1732fb3286b3bd878b59507a8f77d32a2cebda7d7b7cd4/donfig-0.8.1.post1-py3-none-any.whl", hash = "sha256:2a3175ce74a06109ff9307d90a230f81215cbac9a751f4d1c6194644b8204f9d", size = 21592 }, + { url = "https://files.pythonhosted.org/packages/0c/d5/c5db1ea3394c6e1732fb3286b3bd878b59507a8f77d32a2cebda7d7b7cd4/donfig-0.8.1.post1-py3-none-any.whl", hash = "sha256:2a3175ce74a06109ff9307d90a230f81215cbac9a751f4d1c6194644b8204f9d", size = 21592, upload-time = "2024-05-23T14:13:55.283Z" }, ] [[package]] name = "executing" version = "2.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488 } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317 }, + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, ] [[package]] name = "fastjsonschema" version = "2.21.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/b5/23b216d9d985a956623b6bd12d4086b60f0059b27799f23016af04a74ea1/fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de", size = 374130 } +sdist = { url = "https://files.pythonhosted.org/packages/20/b5/23b216d9d985a956623b6bd12d4086b60f0059b27799f23016af04a74ea1/fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de", size = 374130, upload-time = "2025-08-14T18:49:36.666Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463", size = 24024 }, + { url = "https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463", size = 24024, upload-time = "2025-08-14T18:49:34.776Z" }, ] [[package]] name = "filelock" -version = "3.25.2" +version = "3.25.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/b8/00651a0f559862f3bb7d6f7477b192afe3f583cc5e26403b44e59a55ab34/filelock-3.25.2.tar.gz", hash = "sha256:b64ece2b38f4ca29dd3e810287aa8c48182bbecd1ae6e9ae126c9b35f1382694", size = 40480, upload-time = "2026-03-11T20:45:38.487Z" } +sdist = { url = "https://files.pythonhosted.org/packages/77/18/a1fd2231c679dcb9726204645721b12498aeac28e1ad0601038f94b42556/filelock-3.25.0.tar.gz", hash = "sha256:8f00faf3abf9dc730a1ffe9c354ae5c04e079ab7d3a683b7c32da5dd05f26af3", size = 40158, upload-time = "2026-03-01T15:08:45.916Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl", hash = "sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70", size = 26759, upload-time = "2026-03-11T20:45:37.437Z" }, + { url = "https://files.pythonhosted.org/packages/f9/0b/de6f54d4a8bedfe8645c41497f3c18d749f0bd3218170c667bf4b81d0cdd/filelock-3.25.0-py3-none-any.whl", hash = "sha256:5ccf8069f7948f494968fc0713c10e5c182a9c9d9eef3a636307a20c2490f047", size = 26427, upload-time = "2026-03-01T15:08:44.593Z" }, ] [[package]] @@ -802,9 +782,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/55/b0/8a21e330561c65653d010ef112bf38f60890051d244ede197ddaa08e50c1/flexcache-0.3.tar.gz", hash = "sha256:18743bd5a0621bfe2cf8d519e4c3bfdf57a269c15d1ced3fb4b64e0ff4600656", size = 15816 } +sdist = { url = "https://files.pythonhosted.org/packages/55/b0/8a21e330561c65653d010ef112bf38f60890051d244ede197ddaa08e50c1/flexcache-0.3.tar.gz", hash = "sha256:18743bd5a0621bfe2cf8d519e4c3bfdf57a269c15d1ced3fb4b64e0ff4600656", size = 15816, upload-time = "2024-03-09T03:21:07.555Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/cd/c883e1a7c447479d6e13985565080e3fea88ab5a107c21684c813dba1875/flexcache-0.3-py3-none-any.whl", hash = "sha256:d43c9fea82336af6e0115e308d9d33a185390b8346a017564611f1466dcd2e32", size = 13263 }, + { url = "https://files.pythonhosted.org/packages/27/cd/c883e1a7c447479d6e13985565080e3fea88ab5a107c21684c813dba1875/flexcache-0.3-py3-none-any.whl", hash = "sha256:d43c9fea82336af6e0115e308d9d33a185390b8346a017564611f1466dcd2e32", size = 13263, upload-time = "2024-03-09T03:21:05.635Z" }, ] [[package]] @@ -814,158 +794,153 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/82/99/b4de7e39e8eaf8207ba1a8fa2241dd98b2ba72ae6e16960d8351736d8702/flexparser-0.4.tar.gz", hash = "sha256:266d98905595be2ccc5da964fe0a2c3526fbbffdc45b65b3146d75db992ef6b2", size = 31799 } +sdist = { url = "https://files.pythonhosted.org/packages/82/99/b4de7e39e8eaf8207ba1a8fa2241dd98b2ba72ae6e16960d8351736d8702/flexparser-0.4.tar.gz", hash = "sha256:266d98905595be2ccc5da964fe0a2c3526fbbffdc45b65b3146d75db992ef6b2", size = 31799, upload-time = "2024-11-07T02:00:56.249Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/5e/3be305568fe5f34448807976dc82fc151d76c3e0e03958f34770286278c1/flexparser-0.4-py3-none-any.whl", hash = "sha256:3738b456192dcb3e15620f324c447721023c0293f6af9955b481e91d00179846", size = 27625 }, + { url = "https://files.pythonhosted.org/packages/fe/5e/3be305568fe5f34448807976dc82fc151d76c3e0e03958f34770286278c1/flexparser-0.4-py3-none-any.whl", hash = "sha256:3738b456192dcb3e15620f324c447721023c0293f6af9955b481e91d00179846", size = 27625, upload-time = "2024-11-07T02:00:54.523Z" }, ] [[package]] name = "fonttools" -version = "4.62.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9a/08/7012b00a9a5874311b639c3920270c36ee0c445b69d9989a85e5c92ebcb0/fonttools-4.62.1.tar.gz", hash = "sha256:e54c75fd6041f1122476776880f7c3c3295ffa31962dc6ebe2543c00dca58b5d", size = 3580737, upload-time = "2026-03-13T13:54:25.52Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/39/23ff32561ec8d45a4d48578b4d241369d9270dc50926c017570e60893701/fonttools-4.62.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:40975849bac44fb0b9253d77420c6d8b523ac4dcdcefeff6e4d706838a5b80f7", size = 2871039, upload-time = "2026-03-13T13:52:33.127Z" }, - { url = "https://files.pythonhosted.org/packages/24/7f/66d3f8a9338a9b67fe6e1739f47e1cd5cee78bd3bc1206ef9b0b982289a5/fonttools-4.62.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9dde91633f77fa576879a0c76b1d89de373cae751a98ddf0109d54e173b40f14", size = 2416346, upload-time = "2026-03-13T13:52:35.676Z" }, - { url = "https://files.pythonhosted.org/packages/aa/53/5276ceba7bff95da7793a07c5284e1da901cf00341ce5e2f3273056c0cca/fonttools-4.62.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6acb4109f8bee00fec985c8c7afb02299e35e9c94b57287f3ea542f28bd0b0a7", size = 5100897, upload-time = "2026-03-13T13:52:38.102Z" }, - { url = "https://files.pythonhosted.org/packages/cc/a1/40a5c4d8e28b0851d53a8eeeb46fbd73c325a2a9a165f290a5ed90e6c597/fonttools-4.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1c5c25671ce8805e0d080e2ffdeca7f1e86778c5cbfbeae86d7f866d8830517b", size = 5071078, upload-time = "2026-03-13T13:52:41.305Z" }, - { url = "https://files.pythonhosted.org/packages/e3/be/d378fca4c65ea1956fee6d90ace6e861776809cbbc5af22388a090c3c092/fonttools-4.62.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a5d8825e1140f04e6c99bb7d37a9e31c172f3bc208afbe02175339e699c710e1", size = 5076908, upload-time = "2026-03-13T13:52:44.122Z" }, - { url = "https://files.pythonhosted.org/packages/f8/d9/ae6a1d0693a4185a84605679c8a1f719a55df87b9c6e8e817bfdd9ef5936/fonttools-4.62.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:268abb1cb221e66c014acc234e872b7870d8b5d4657a83a8f4205094c32d2416", size = 5202275, upload-time = "2026-03-13T13:52:46.591Z" }, - { url = "https://files.pythonhosted.org/packages/54/6c/af95d9c4efb15cabff22642b608342f2bd67137eea6107202d91b5b03184/fonttools-4.62.1-cp311-cp311-win32.whl", hash = "sha256:942b03094d7edbb99bdf1ae7e9090898cad7bf9030b3d21f33d7072dbcb51a53", size = 2293075, upload-time = "2026-03-13T13:52:48.711Z" }, - { url = "https://files.pythonhosted.org/packages/d3/97/bf54c5b3f2be34e1f143e6db838dfdc54f2ffa3e68c738934c82f3b2a08d/fonttools-4.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:e8514f4924375f77084e81467e63238b095abda5107620f49421c368a6017ed2", size = 2344593, upload-time = "2026-03-13T13:52:50.725Z" }, - { url = "https://files.pythonhosted.org/packages/47/d4/dbacced3953544b9a93088cc10ef2b596d348c983d5c67a404fa41ec51ba/fonttools-4.62.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:90365821debbd7db678809c7491ca4acd1e0779b9624cdc6ddaf1f31992bf974", size = 2870219, upload-time = "2026-03-13T13:52:53.664Z" }, - { url = "https://files.pythonhosted.org/packages/66/9e/a769c8e99b81e5a87ab7e5e7236684de4e96246aae17274e5347d11ebd78/fonttools-4.62.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12859ff0b47dd20f110804c3e0d0970f7b832f561630cd879969011541a464a9", size = 2414891, upload-time = "2026-03-13T13:52:56.493Z" }, - { url = "https://files.pythonhosted.org/packages/69/64/f19a9e3911968c37e1e620e14dfc5778299e1474f72f4e57c5ec771d9489/fonttools-4.62.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c125ffa00c3d9003cdaaf7f2c79e6e535628093e14b5de1dccb08859b680936", size = 5033197, upload-time = "2026-03-13T13:52:59.179Z" }, - { url = "https://files.pythonhosted.org/packages/9b/8a/99c8b3c3888c5c474c08dbfd7c8899786de9604b727fcefb055b42c84bba/fonttools-4.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:149f7d84afca659d1a97e39a4778794a2f83bf344c5ee5134e09995086cc2392", size = 4988768, upload-time = "2026-03-13T13:53:02.761Z" }, - { url = "https://files.pythonhosted.org/packages/d1/c6/0f904540d3e6ab463c1243a0d803504826a11604c72dd58c2949796a1762/fonttools-4.62.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0aa72c43a601cfa9273bb1ae0518f1acadc01ee181a6fc60cd758d7fdadffc04", size = 4971512, upload-time = "2026-03-13T13:53:05.678Z" }, - { url = "https://files.pythonhosted.org/packages/29/0b/5cbef6588dc9bd6b5c9ad6a4d5a8ca384d0cea089da31711bbeb4f9654a6/fonttools-4.62.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:19177c8d96c7c36359266e571c5173bcee9157b59cfc8cb0153c5673dc5a3a7d", size = 5122723, upload-time = "2026-03-13T13:53:08.662Z" }, - { url = "https://files.pythonhosted.org/packages/4a/47/b3a5342d381595ef439adec67848bed561ab7fdb1019fa522e82101b7d9c/fonttools-4.62.1-cp312-cp312-win32.whl", hash = "sha256:a24decd24d60744ee8b4679d38e88b8303d86772053afc29b19d23bb8207803c", size = 2281278, upload-time = "2026-03-13T13:53:10.998Z" }, - { url = "https://files.pythonhosted.org/packages/28/b1/0c2ab56a16f409c6c8a68816e6af707827ad5d629634691ff60a52879792/fonttools-4.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:9e7863e10b3de72376280b515d35b14f5eeed639d1aa7824f4cf06779ec65e42", size = 2331414, upload-time = "2026-03-13T13:53:13.992Z" }, - { url = "https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c22b1014017111c401469e3acc5433e6acf6ebcc6aa9efb538a533c800971c79", size = 2865155, upload-time = "2026-03-13T13:53:16.132Z" }, - { url = "https://files.pythonhosted.org/packages/03/c5/0e3966edd5ec668d41dfe418787726752bc07e2f5fd8c8f208615e61fa89/fonttools-4.62.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68959f5fc58ed4599b44aad161c2837477d7f35f5f79402d97439974faebfebe", size = 2412802, upload-time = "2026-03-13T13:53:18.878Z" }, - { url = "https://files.pythonhosted.org/packages/52/94/e6ac4b44026de7786fe46e3bfa0c87e51d5d70a841054065d49cd62bb909/fonttools-4.62.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef46db46c9447103b8f3ff91e8ba009d5fe181b1920a83757a5762551e32bb68", size = 5013926, upload-time = "2026-03-13T13:53:21.379Z" }, - { url = "https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6706d1cb1d5e6251a97ad3c1b9347505c5615c112e66047abbef0f8545fa30d1", size = 4964575, upload-time = "2026-03-13T13:53:23.857Z" }, - { url = "https://files.pythonhosted.org/packages/46/76/7d051671e938b1881670528fec69cc4044315edd71a229c7fd712eaa5119/fonttools-4.62.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e7abd2b1e11736f58c1de27819e1955a53267c21732e78243fa2fa2e5c1e069", size = 4953693, upload-time = "2026-03-13T13:53:26.569Z" }, - { url = "https://files.pythonhosted.org/packages/1f/ae/b41f8628ec0be3c1b934fc12b84f4576a5c646119db4d3bdd76a217c90b5/fonttools-4.62.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:403d28ce06ebfc547fbcb0cb8b7f7cc2f7a2d3e1a67ba9a34b14632df9e080f9", size = 5094920, upload-time = "2026-03-13T13:53:29.329Z" }, - { url = "https://files.pythonhosted.org/packages/f2/f6/53a1e9469331a23dcc400970a27a4caa3d9f6edbf5baab0260285238b884/fonttools-4.62.1-cp313-cp313-win32.whl", hash = "sha256:93c316e0f5301b2adbe6a5f658634307c096fd5aae60a5b3412e4f3e1728ab24", size = 2279928, upload-time = "2026-03-13T13:53:32.352Z" }, - { url = "https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl", hash = "sha256:7aa21ff53e28a9c2157acbc44e5b401149d3c9178107130e82d74ceb500e5056", size = 2330514, upload-time = "2026-03-13T13:53:34.991Z" }, - { url = "https://files.pythonhosted.org/packages/36/f0/2888cdac391807d68d90dcb16ef858ddc1b5309bfc6966195a459dd326e2/fonttools-4.62.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fa1d16210b6b10a826d71bed68dd9ec24a9e218d5a5e2797f37c573e7ec215ca", size = 2864442, upload-time = "2026-03-13T13:53:37.509Z" }, - { url = "https://files.pythonhosted.org/packages/4b/b2/e521803081f8dc35990816b82da6360fa668a21b44da4b53fc9e77efcd62/fonttools-4.62.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:aa69d10ed420d8121118e628ad47d86e4caa79ba37f968597b958f6cceab7eca", size = 2410901, upload-time = "2026-03-13T13:53:40.55Z" }, - { url = "https://files.pythonhosted.org/packages/00/a4/8c3511ff06e53110039358dbbdc1a65d72157a054638387aa2ada300a8b8/fonttools-4.62.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd13b7999d59c5eb1c2b442eb2d0c427cb517a0b7a1f5798fc5c9e003f5ff782", size = 4999608, upload-time = "2026-03-13T13:53:42.798Z" }, - { url = "https://files.pythonhosted.org/packages/28/63/cd0c3b26afe60995a5295f37c246a93d454023726c3261cfbb3559969bb9/fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8d337fdd49a79b0d51c4da87bc38169d21c3abbf0c1aa9367eff5c6656fb6dae", size = 4912726, upload-time = "2026-03-13T13:53:45.405Z" }, - { url = "https://files.pythonhosted.org/packages/70/b9/ac677cb07c24c685cf34f64e140617d58789d67a3dd524164b63648c6114/fonttools-4.62.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d241cdc4a67b5431c6d7f115fdf63335222414995e3a1df1a41e1182acd4bcc7", size = 4951422, upload-time = "2026-03-13T13:53:48.326Z" }, - { url = "https://files.pythonhosted.org/packages/e6/10/11c08419a14b85b7ca9a9faca321accccc8842dd9e0b1c8a72908de05945/fonttools-4.62.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c05557a78f8fa514da0f869556eeda40887a8abc77c76ee3f74cf241778afd5a", size = 5060979, upload-time = "2026-03-13T13:53:51.366Z" }, - { url = "https://files.pythonhosted.org/packages/4e/3c/12eea4a4cf054e7ab058ed5ceada43b46809fce2bf319017c4d63ae55bb4/fonttools-4.62.1-cp314-cp314-win32.whl", hash = "sha256:49a445d2f544ce4a69338694cad575ba97b9a75fff02720da0882d1a73f12800", size = 2283733, upload-time = "2026-03-13T13:53:53.606Z" }, - { url = "https://files.pythonhosted.org/packages/6b/67/74b070029043186b5dd13462c958cb7c7f811be0d2e634309d9a1ffb1505/fonttools-4.62.1-cp314-cp314-win_amd64.whl", hash = "sha256:1eecc128c86c552fb963fe846ca4e011b1be053728f798185a1687502f6d398e", size = 2335663, upload-time = "2026-03-13T13:53:56.23Z" }, - { url = "https://files.pythonhosted.org/packages/42/c5/4d2ed3ca6e33617fc5624467da353337f06e7f637707478903c785bd8e20/fonttools-4.62.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1596aeaddf7f78e21e68293c011316a25267b3effdaccaf4d59bc9159d681b82", size = 2947288, upload-time = "2026-03-13T13:53:59.397Z" }, - { url = "https://files.pythonhosted.org/packages/1f/e9/7ab11ddfda48ed0f89b13380e5595ba572619c27077be0b2c447a63ff351/fonttools-4.62.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8f8fca95d3bb3208f59626a4b0ea6e526ee51f5a8ad5d91821c165903e8d9260", size = 2449023, upload-time = "2026-03-13T13:54:01.642Z" }, - { url = "https://files.pythonhosted.org/packages/b2/10/a800fa090b5e8819942e54e19b55fc7c21fe14a08757c3aa3ca8db358939/fonttools-4.62.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee91628c08e76f77b533d65feb3fbe6d9dad699f95be51cf0d022db94089cdc4", size = 5137599, upload-time = "2026-03-13T13:54:04.495Z" }, - { url = "https://files.pythonhosted.org/packages/37/dc/8ccd45033fffd74deb6912fa1ca524643f584b94c87a16036855b498a1ed/fonttools-4.62.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f37df1cac61d906e7b836abe356bc2f34c99d4477467755c216b72aa3dc748b", size = 4920933, upload-time = "2026-03-13T13:54:07.557Z" }, - { url = "https://files.pythonhosted.org/packages/99/eb/e618adefb839598d25ac8136cd577925d6c513dc0d931d93b8af956210f0/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:92bb00a947e666169c99b43753c4305fc95a890a60ef3aeb2a6963e07902cc87", size = 5016232, upload-time = "2026-03-13T13:54:10.611Z" }, - { url = "https://files.pythonhosted.org/packages/d9/5f/9b5c9bfaa8ec82def8d8168c4f13615990d6ce5996fe52bd49bfb5e05134/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bdfe592802ef939a0e33106ea4a318eeb17822c7ee168c290273cbd5fabd746c", size = 5042987, upload-time = "2026-03-13T13:54:13.569Z" }, - { url = "https://files.pythonhosted.org/packages/90/aa/dfbbe24c6a6afc5c203d90cc0343e24bcbb09e76d67c4d6eef8c2558d7ba/fonttools-4.62.1-cp314-cp314t-win32.whl", hash = "sha256:b820fcb92d4655513d8402d5b219f94481c4443d825b4372c75a2072aa4b357a", size = 2348021, upload-time = "2026-03-13T13:54:16.98Z" }, - { url = "https://files.pythonhosted.org/packages/13/6f/ae9c4e4dd417948407b680855c2c7790efb52add6009aaecff1e3bc50e8e/fonttools-4.62.1-cp314-cp314t-win_amd64.whl", hash = "sha256:59b372b4f0e113d3746b88985f1c796e7bf830dd54b28374cd85c2b8acd7583e", size = 2414147, upload-time = "2026-03-13T13:54:19.416Z" }, - { url = "https://files.pythonhosted.org/packages/fd/ba/56147c165442cc5ba7e82ecf301c9a68353cede498185869e6e02b4c264f/fonttools-4.62.1-py3-none-any.whl", hash = "sha256:7487782e2113861f4ddcc07c3436450659e3caa5e470b27dc2177cade2d8e7fd", size = 1152647, upload-time = "2026-03-13T13:54:22.735Z" }, +version = "4.61.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69", size = 3565756, upload-time = "2025-12-12T17:31:24.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/12/bf9f4eaa2fad039356cc627587e30ed008c03f1cebd3034376b5ee8d1d44/fonttools-4.61.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09", size = 2852213, upload-time = "2025-12-12T17:29:46.675Z" }, + { url = "https://files.pythonhosted.org/packages/ac/49/4138d1acb6261499bedde1c07f8c2605d1d8f9d77a151e5507fd3ef084b6/fonttools-4.61.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37", size = 2401689, upload-time = "2025-12-12T17:29:48.769Z" }, + { url = "https://files.pythonhosted.org/packages/e5/fe/e6ce0fe20a40e03aef906af60aa87668696f9e4802fa283627d0b5ed777f/fonttools-4.61.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77efb033d8d7ff233385f30c62c7c79271c8885d5c9657d967ede124671bbdfb", size = 5058809, upload-time = "2025-12-12T17:29:51.701Z" }, + { url = "https://files.pythonhosted.org/packages/79/61/1ca198af22f7dd22c17ab86e9024ed3c06299cfdb08170640e9996d501a0/fonttools-4.61.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9", size = 5036039, upload-time = "2025-12-12T17:29:53.659Z" }, + { url = "https://files.pythonhosted.org/packages/99/cc/fa1801e408586b5fce4da9f5455af8d770f4fc57391cd5da7256bb364d38/fonttools-4.61.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0de30bfe7745c0d1ffa2b0b7048fb7123ad0d71107e10ee090fa0b16b9452e87", size = 5034714, upload-time = "2025-12-12T17:29:55.592Z" }, + { url = "https://files.pythonhosted.org/packages/bf/aa/b7aeafe65adb1b0a925f8f25725e09f078c635bc22754f3fecb7456955b0/fonttools-4.61.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58b0ee0ab5b1fc9921eccfe11d1435added19d6494dde14e323f25ad2bc30c56", size = 5158648, upload-time = "2025-12-12T17:29:57.861Z" }, + { url = "https://files.pythonhosted.org/packages/99/f9/08ea7a38663328881384c6e7777bbefc46fd7d282adfd87a7d2b84ec9d50/fonttools-4.61.1-cp311-cp311-win32.whl", hash = "sha256:f79b168428351d11e10c5aeb61a74e1851ec221081299f4cf56036a95431c43a", size = 2280681, upload-time = "2025-12-12T17:29:59.943Z" }, + { url = "https://files.pythonhosted.org/packages/07/ad/37dd1ae5fa6e01612a1fbb954f0927681f282925a86e86198ccd7b15d515/fonttools-4.61.1-cp311-cp311-win_amd64.whl", hash = "sha256:fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7", size = 2331951, upload-time = "2025-12-12T17:30:02.254Z" }, + { url = "https://files.pythonhosted.org/packages/6f/16/7decaa24a1bd3a70c607b2e29f0adc6159f36a7e40eaba59846414765fd4/fonttools-4.61.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e", size = 2851593, upload-time = "2025-12-12T17:30:04.225Z" }, + { url = "https://files.pythonhosted.org/packages/94/98/3c4cb97c64713a8cf499b3245c3bf9a2b8fd16a3e375feff2aed78f96259/fonttools-4.61.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2", size = 2400231, upload-time = "2025-12-12T17:30:06.47Z" }, + { url = "https://files.pythonhosted.org/packages/b7/37/82dbef0f6342eb01f54bca073ac1498433d6ce71e50c3c3282b655733b31/fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796", size = 4954103, upload-time = "2025-12-12T17:30:08.432Z" }, + { url = "https://files.pythonhosted.org/packages/6c/44/f3aeac0fa98e7ad527f479e161aca6c3a1e47bb6996b053d45226fe37bf2/fonttools-4.61.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d", size = 5004295, upload-time = "2025-12-12T17:30:10.56Z" }, + { url = "https://files.pythonhosted.org/packages/14/e8/7424ced75473983b964d09f6747fa09f054a6d656f60e9ac9324cf40c743/fonttools-4.61.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8", size = 4944109, upload-time = "2025-12-12T17:30:12.874Z" }, + { url = "https://files.pythonhosted.org/packages/c8/8b/6391b257fa3d0b553d73e778f953a2f0154292a7a7a085e2374b111e5410/fonttools-4.61.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0", size = 5093598, upload-time = "2025-12-12T17:30:15.79Z" }, + { url = "https://files.pythonhosted.org/packages/d9/71/fd2ea96cdc512d92da5678a1c98c267ddd4d8c5130b76d0f7a80f9a9fde8/fonttools-4.61.1-cp312-cp312-win32.whl", hash = "sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261", size = 2269060, upload-time = "2025-12-12T17:30:18.058Z" }, + { url = "https://files.pythonhosted.org/packages/80/3b/a3e81b71aed5a688e89dfe0e2694b26b78c7d7f39a5ffd8a7d75f54a12a8/fonttools-4.61.1-cp312-cp312-win_amd64.whl", hash = "sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9", size = 2319078, upload-time = "2025-12-12T17:30:22.862Z" }, + { url = "https://files.pythonhosted.org/packages/4b/cf/00ba28b0990982530addb8dc3e9e6f2fa9cb5c20df2abdda7baa755e8fe1/fonttools-4.61.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c56c488ab471628ff3bfa80964372fc13504ece601e0d97a78ee74126b2045c", size = 2846454, upload-time = "2025-12-12T17:30:24.938Z" }, + { url = "https://files.pythonhosted.org/packages/5a/ca/468c9a8446a2103ae645d14fee3f610567b7042aba85031c1c65e3ef7471/fonttools-4.61.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc492779501fa723b04d0ab1f5be046797fee17d27700476edc7ee9ae535a61e", size = 2398191, upload-time = "2025-12-12T17:30:27.343Z" }, + { url = "https://files.pythonhosted.org/packages/a3/4b/d67eedaed19def5967fade3297fed8161b25ba94699efc124b14fb68cdbc/fonttools-4.61.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:64102ca87e84261419c3747a0d20f396eb024bdbeb04c2bfb37e2891f5fadcb5", size = 4928410, upload-time = "2025-12-12T17:30:29.771Z" }, + { url = "https://files.pythonhosted.org/packages/b0/8d/6fb3494dfe61a46258cd93d979cf4725ded4eb46c2a4ca35e4490d84daea/fonttools-4.61.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c1b526c8d3f615a7b1867f38a9410849c8f4aef078535742198e942fba0e9bd", size = 4984460, upload-time = "2025-12-12T17:30:32.073Z" }, + { url = "https://files.pythonhosted.org/packages/f7/f1/a47f1d30b3dc00d75e7af762652d4cbc3dff5c2697a0dbd5203c81afd9c3/fonttools-4.61.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:41ed4b5ec103bd306bb68f81dc166e77409e5209443e5773cb4ed837bcc9b0d3", size = 4925800, upload-time = "2025-12-12T17:30:34.339Z" }, + { url = "https://files.pythonhosted.org/packages/a7/01/e6ae64a0981076e8a66906fab01539799546181e32a37a0257b77e4aa88b/fonttools-4.61.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b501c862d4901792adaec7c25b1ecc749e2662543f68bb194c42ba18d6eec98d", size = 5067859, upload-time = "2025-12-12T17:30:36.593Z" }, + { url = "https://files.pythonhosted.org/packages/73/aa/28e40b8d6809a9b5075350a86779163f074d2b617c15d22343fce81918db/fonttools-4.61.1-cp313-cp313-win32.whl", hash = "sha256:4d7092bb38c53bbc78e9255a59158b150bcdc115a1e3b3ce0b5f267dc35dd63c", size = 2267821, upload-time = "2025-12-12T17:30:38.478Z" }, + { url = "https://files.pythonhosted.org/packages/1a/59/453c06d1d83dc0951b69ef692d6b9f1846680342927df54e9a1ca91c6f90/fonttools-4.61.1-cp313-cp313-win_amd64.whl", hash = "sha256:21e7c8d76f62ab13c9472ccf74515ca5b9a761d1bde3265152a6dc58700d895b", size = 2318169, upload-time = "2025-12-12T17:30:40.951Z" }, + { url = "https://files.pythonhosted.org/packages/32/8f/4e7bf82c0cbb738d3c2206c920ca34ca74ef9dabde779030145d28665104/fonttools-4.61.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fff4f534200a04b4a36e7ae3cb74493afe807b517a09e99cb4faa89a34ed6ecd", size = 2846094, upload-time = "2025-12-12T17:30:43.511Z" }, + { url = "https://files.pythonhosted.org/packages/71/09/d44e45d0a4f3a651f23a1e9d42de43bc643cce2971b19e784cc67d823676/fonttools-4.61.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d9203500f7c63545b4ce3799319fe4d9feb1a1b89b28d3cb5abd11b9dd64147e", size = 2396589, upload-time = "2025-12-12T17:30:45.681Z" }, + { url = "https://files.pythonhosted.org/packages/89/18/58c64cafcf8eb677a99ef593121f719e6dcbdb7d1c594ae5a10d4997ca8a/fonttools-4.61.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa646ecec9528bef693415c79a86e733c70a4965dd938e9a226b0fc64c9d2e6c", size = 4877892, upload-time = "2025-12-12T17:30:47.709Z" }, + { url = "https://files.pythonhosted.org/packages/8a/ec/9e6b38c7ba1e09eb51db849d5450f4c05b7e78481f662c3b79dbde6f3d04/fonttools-4.61.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f35ad7805edba3aac1a3710d104592df59f4b957e30108ae0ba6c10b11dd75", size = 4972884, upload-time = "2025-12-12T17:30:49.656Z" }, + { url = "https://files.pythonhosted.org/packages/5e/87/b5339da8e0256734ba0dbbf5b6cdebb1dd79b01dc8c270989b7bcd465541/fonttools-4.61.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b931ae8f62db78861b0ff1ac017851764602288575d65b8e8ff1963fed419063", size = 4924405, upload-time = "2025-12-12T17:30:51.735Z" }, + { url = "https://files.pythonhosted.org/packages/0b/47/e3409f1e1e69c073a3a6fd8cb886eb18c0bae0ee13db2c8d5e7f8495e8b7/fonttools-4.61.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b148b56f5de675ee16d45e769e69f87623a4944f7443850bf9a9376e628a89d2", size = 5035553, upload-time = "2025-12-12T17:30:54.823Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b6/1f6600161b1073a984294c6c031e1a56ebf95b6164249eecf30012bb2e38/fonttools-4.61.1-cp314-cp314-win32.whl", hash = "sha256:9b666a475a65f4e839d3d10473fad6d47e0a9db14a2f4a224029c5bfde58ad2c", size = 2271915, upload-time = "2025-12-12T17:30:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/52/7b/91e7b01e37cc8eb0e1f770d08305b3655e4f002fc160fb82b3390eabacf5/fonttools-4.61.1-cp314-cp314-win_amd64.whl", hash = "sha256:4f5686e1fe5fce75d82d93c47a438a25bf0d1319d2843a926f741140b2b16e0c", size = 2323487, upload-time = "2025-12-12T17:30:59.804Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/908ad78e46c61c3e3ed70c3b58ff82ab48437faf84ec84f109592cabbd9f/fonttools-4.61.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e76ce097e3c57c4bcb67c5aa24a0ecdbd9f74ea9219997a707a4061fbe2707aa", size = 2929571, upload-time = "2025-12-12T17:31:02.574Z" }, + { url = "https://files.pythonhosted.org/packages/bd/41/975804132c6dea64cdbfbaa59f3518a21c137a10cccf962805b301ac6ab2/fonttools-4.61.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9cfef3ab326780c04d6646f68d4b4742aae222e8b8ea1d627c74e38afcbc9d91", size = 2435317, upload-time = "2025-12-12T17:31:04.974Z" }, + { url = "https://files.pythonhosted.org/packages/b0/5a/aef2a0a8daf1ebaae4cfd83f84186d4a72ee08fd6a8451289fcd03ffa8a4/fonttools-4.61.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a75c301f96db737e1c5ed5fd7d77d9c34466de16095a266509e13da09751bd19", size = 4882124, upload-time = "2025-12-12T17:31:07.456Z" }, + { url = "https://files.pythonhosted.org/packages/80/33/d6db3485b645b81cea538c9d1c9219d5805f0877fda18777add4671c5240/fonttools-4.61.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91669ccac46bbc1d09e9273546181919064e8df73488ea087dcac3e2968df9ba", size = 5100391, upload-time = "2025-12-12T17:31:09.732Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d6/675ba631454043c75fcf76f0ca5463eac8eb0666ea1d7badae5fea001155/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c33ab3ca9d3ccd581d58e989d67554e42d8d4ded94ab3ade3508455fe70e65f7", size = 4978800, upload-time = "2025-12-12T17:31:11.681Z" }, + { url = "https://files.pythonhosted.org/packages/7f/33/d3ec753d547a8d2bdaedd390d4a814e8d5b45a093d558f025c6b990b554c/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:664c5a68ec406f6b1547946683008576ef8b38275608e1cee6c061828171c118", size = 5006426, upload-time = "2025-12-12T17:31:13.764Z" }, + { url = "https://files.pythonhosted.org/packages/b4/40/cc11f378b561a67bea850ab50063366a0d1dd3f6d0a30ce0f874b0ad5664/fonttools-4.61.1-cp314-cp314t-win32.whl", hash = "sha256:aed04cabe26f30c1647ef0e8fbb207516fd40fe9472e9439695f5c6998e60ac5", size = 2335377, upload-time = "2025-12-12T17:31:16.49Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ff/c9a2b66b39f8628531ea58b320d66d951267c98c6a38684daa8f50fb02f8/fonttools-4.61.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2180f14c141d2f0f3da43f3a81bc8aa4684860f6b0e6f9e165a4831f24e6a23b", size = 2400613, upload-time = "2025-12-12T17:31:18.769Z" }, + { url = "https://files.pythonhosted.org/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371", size = 1148996, upload-time = "2025-12-12T17:31:21.03Z" }, ] [[package]] name = "fqdn" version = "1.5.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015 } +sdist = { url = "https://files.pythonhosted.org/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015, upload-time = "2021-03-11T07:16:29.08Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121 }, + { url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121, upload-time = "2021-03-11T07:16:28.351Z" }, ] [[package]] name = "fsspec" version = "2026.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/51/7c/f60c259dcbf4f0c47cc4ddb8f7720d2dcdc8888c8e5ad84c73ea4531cc5b/fsspec-2026.2.0.tar.gz", hash = "sha256:6544e34b16869f5aacd5b90bdf1a71acb37792ea3ddf6125ee69a22a53fb8bff", size = 313441 } +sdist = { url = "https://files.pythonhosted.org/packages/51/7c/f60c259dcbf4f0c47cc4ddb8f7720d2dcdc8888c8e5ad84c73ea4531cc5b/fsspec-2026.2.0.tar.gz", hash = "sha256:6544e34b16869f5aacd5b90bdf1a71acb37792ea3ddf6125ee69a22a53fb8bff", size = 313441, upload-time = "2026-02-05T21:50:53.743Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl", hash = "sha256:98de475b5cb3bd66bedd5c4679e87b4fdfe1a3bf4d707b151b3c07e58c9a2437", size = 202505 }, + { url = "https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl", hash = "sha256:98de475b5cb3bd66bedd5c4679e87b4fdfe1a3bf4d707b151b3c07e58c9a2437", size = 202505, upload-time = "2026-02-05T21:50:51.819Z" }, ] [[package]] name = "google-crc32c" version = "1.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/03/41/4b9c02f99e4c5fb477122cd5437403b552873f014616ac1d19ac8221a58d/google_crc32c-1.8.0.tar.gz", hash = "sha256:a428e25fb7691024de47fecfbff7ff957214da51eddded0da0ae0e0f03a2cf79", size = 14192 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/ef/21ccfaab3d5078d41efe8612e0ed0bfc9ce22475de074162a91a25f7980d/google_crc32c-1.8.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:014a7e68d623e9a4222d663931febc3033c5c7c9730785727de2a81f87d5bab8", size = 31298 }, - { url = "https://files.pythonhosted.org/packages/c5/b8/f8413d3f4b676136e965e764ceedec904fe38ae8de0cdc52a12d8eb1096e/google_crc32c-1.8.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:86cfc00fe45a0ac7359e5214a1704e51a99e757d0272554874f419f79838c5f7", size = 30872 }, - { url = "https://files.pythonhosted.org/packages/f6/fd/33aa4ec62b290477181c55bb1c9302c9698c58c0ce9a6ab4874abc8b0d60/google_crc32c-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:19b40d637a54cb71e0829179f6cb41835f0fbd9e8eb60552152a8b52c36cbe15", size = 33243 }, - { url = "https://files.pythonhosted.org/packages/71/03/4820b3bd99c9653d1a5210cb32f9ba4da9681619b4d35b6a052432df4773/google_crc32c-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:17446feb05abddc187e5441a45971b8394ea4c1b6efd88ab0af393fd9e0a156a", size = 33608 }, - { url = "https://files.pythonhosted.org/packages/7c/43/acf61476a11437bf9733fb2f70599b1ced11ec7ed9ea760fdd9a77d0c619/google_crc32c-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:71734788a88f551fbd6a97be9668a0020698e07b2bf5b3aa26a36c10cdfb27b2", size = 34439 }, - { url = "https://files.pythonhosted.org/packages/e9/5f/7307325b1198b59324c0fa9807cafb551afb65e831699f2ce211ad5c8240/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:4b8286b659c1335172e39563ab0a768b8015e88e08329fa5321f774275fc3113", size = 31300 }, - { url = "https://files.pythonhosted.org/packages/21/8e/58c0d5d86e2220e6a37befe7e6a94dd2f6006044b1a33edf1ff6d9f7e319/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:2a3dc3318507de089c5384cc74d54318401410f82aa65b2d9cdde9d297aca7cb", size = 30867 }, - { url = "https://files.pythonhosted.org/packages/ce/a9/a780cc66f86335a6019f557a8aaca8fbb970728f0efd2430d15ff1beae0e/google_crc32c-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14f87e04d613dfa218d6135e81b78272c3b904e2a7053b841481b38a7d901411", size = 33364 }, - { url = "https://files.pythonhosted.org/packages/21/3f/3457ea803db0198c9aaca2dd373750972ce28a26f00544b6b85088811939/google_crc32c-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb5c869c2923d56cb0c8e6bcdd73c009c36ae39b652dbe46a05eb4ef0ad01454", size = 33740 }, - { url = "https://files.pythonhosted.org/packages/df/c0/87c2073e0c72515bb8733d4eef7b21548e8d189f094b5dad20b0ecaf64f6/google_crc32c-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:3cc0c8912038065eafa603b238abf252e204accab2a704c63b9e14837a854962", size = 34437 }, - { url = "https://files.pythonhosted.org/packages/d1/db/000f15b41724589b0e7bc24bc7a8967898d8d3bc8caf64c513d91ef1f6c0/google_crc32c-1.8.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:3ebb04528e83b2634857f43f9bb8ef5b2bbe7f10f140daeb01b58f972d04736b", size = 31297 }, - { url = "https://files.pythonhosted.org/packages/d7/0d/8ebed0c39c53a7e838e2a486da8abb0e52de135f1b376ae2f0b160eb4c1a/google_crc32c-1.8.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:450dc98429d3e33ed2926fc99ee81001928d63460f8538f21a5d6060912a8e27", size = 30867 }, - { url = "https://files.pythonhosted.org/packages/ce/42/b468aec74a0354b34c8cbf748db20d6e350a68a2b0912e128cabee49806c/google_crc32c-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3b9776774b24ba76831609ffbabce8cdf6fa2bd5e9df37b594221c7e333a81fa", size = 33344 }, - { url = "https://files.pythonhosted.org/packages/1c/e8/b33784d6fc77fb5062a8a7854e43e1e618b87d5ddf610a88025e4de6226e/google_crc32c-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:89c17d53d75562edfff86679244830599ee0a48efc216200691de8b02ab6b2b8", size = 33694 }, - { url = "https://files.pythonhosted.org/packages/92/b1/d3cbd4d988afb3d8e4db94ca953df429ed6db7282ed0e700d25e6c7bfc8d/google_crc32c-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:57a50a9035b75643996fbf224d6661e386c7162d1dfdab9bc4ca790947d1007f", size = 34435 }, - { url = "https://files.pythonhosted.org/packages/21/88/8ecf3c2b864a490b9e7010c84fd203ec8cf3b280651106a3a74dd1b0ca72/google_crc32c-1.8.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:e6584b12cb06796d285d09e33f63309a09368b9d806a551d8036a4207ea43697", size = 31301 }, - { url = "https://files.pythonhosted.org/packages/36/c6/f7ff6c11f5ca215d9f43d3629163727a272eabc356e5c9b2853df2bfe965/google_crc32c-1.8.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:f4b51844ef67d6cf2e9425983274da75f18b1597bb2c998e1c0a0e8d46f8f651", size = 30868 }, - { url = "https://files.pythonhosted.org/packages/56/15/c25671c7aad70f8179d858c55a6ae8404902abe0cdcf32a29d581792b491/google_crc32c-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b0d1a7afc6e8e4635564ba8aa5c0548e3173e41b6384d7711a9123165f582de2", size = 33381 }, - { url = "https://files.pythonhosted.org/packages/42/fa/f50f51260d7b0ef5d4898af122d8a7ec5a84e2984f676f746445f783705f/google_crc32c-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8b3f68782f3cbd1bce027e48768293072813469af6a61a86f6bb4977a4380f21", size = 33734 }, - { url = "https://files.pythonhosted.org/packages/08/a5/7b059810934a09fb3ccb657e0843813c1fee1183d3bc2c8041800374aa2c/google_crc32c-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:d511b3153e7011a27ab6ee6bb3a5404a55b994dc1a7322c0b87b29606d9790e2", size = 34878 }, - { url = "https://files.pythonhosted.org/packages/52/c5/c171e4d8c44fec1422d801a6d2e5d7ddabd733eeda505c79730ee9607f07/google_crc32c-1.8.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:87fa445064e7db928226b2e6f0d5304ab4cd0339e664a4e9a25029f384d9bb93", size = 28615 }, - { url = "https://files.pythonhosted.org/packages/9c/97/7d75fe37a7a6ed171a2cf17117177e7aab7e6e0d115858741b41e9dd4254/google_crc32c-1.8.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f639065ea2042d5c034bf258a9f085eaa7af0cd250667c0635a3118e8f92c69c", size = 28800 }, +sdist = { url = "https://files.pythonhosted.org/packages/03/41/4b9c02f99e4c5fb477122cd5437403b552873f014616ac1d19ac8221a58d/google_crc32c-1.8.0.tar.gz", hash = "sha256:a428e25fb7691024de47fecfbff7ff957214da51eddded0da0ae0e0f03a2cf79", size = 14192, upload-time = "2025-12-16T00:35:25.142Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/ef/21ccfaab3d5078d41efe8612e0ed0bfc9ce22475de074162a91a25f7980d/google_crc32c-1.8.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:014a7e68d623e9a4222d663931febc3033c5c7c9730785727de2a81f87d5bab8", size = 31298, upload-time = "2025-12-16T00:20:32.241Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b8/f8413d3f4b676136e965e764ceedec904fe38ae8de0cdc52a12d8eb1096e/google_crc32c-1.8.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:86cfc00fe45a0ac7359e5214a1704e51a99e757d0272554874f419f79838c5f7", size = 30872, upload-time = "2025-12-16T00:33:58.785Z" }, + { url = "https://files.pythonhosted.org/packages/f6/fd/33aa4ec62b290477181c55bb1c9302c9698c58c0ce9a6ab4874abc8b0d60/google_crc32c-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:19b40d637a54cb71e0829179f6cb41835f0fbd9e8eb60552152a8b52c36cbe15", size = 33243, upload-time = "2025-12-16T00:40:21.46Z" }, + { url = "https://files.pythonhosted.org/packages/71/03/4820b3bd99c9653d1a5210cb32f9ba4da9681619b4d35b6a052432df4773/google_crc32c-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:17446feb05abddc187e5441a45971b8394ea4c1b6efd88ab0af393fd9e0a156a", size = 33608, upload-time = "2025-12-16T00:40:22.204Z" }, + { url = "https://files.pythonhosted.org/packages/7c/43/acf61476a11437bf9733fb2f70599b1ced11ec7ed9ea760fdd9a77d0c619/google_crc32c-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:71734788a88f551fbd6a97be9668a0020698e07b2bf5b3aa26a36c10cdfb27b2", size = 34439, upload-time = "2025-12-16T00:35:20.458Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5f/7307325b1198b59324c0fa9807cafb551afb65e831699f2ce211ad5c8240/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:4b8286b659c1335172e39563ab0a768b8015e88e08329fa5321f774275fc3113", size = 31300, upload-time = "2025-12-16T00:21:56.723Z" }, + { url = "https://files.pythonhosted.org/packages/21/8e/58c0d5d86e2220e6a37befe7e6a94dd2f6006044b1a33edf1ff6d9f7e319/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:2a3dc3318507de089c5384cc74d54318401410f82aa65b2d9cdde9d297aca7cb", size = 30867, upload-time = "2025-12-16T00:38:31.302Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a9/a780cc66f86335a6019f557a8aaca8fbb970728f0efd2430d15ff1beae0e/google_crc32c-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14f87e04d613dfa218d6135e81b78272c3b904e2a7053b841481b38a7d901411", size = 33364, upload-time = "2025-12-16T00:40:22.96Z" }, + { url = "https://files.pythonhosted.org/packages/21/3f/3457ea803db0198c9aaca2dd373750972ce28a26f00544b6b85088811939/google_crc32c-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb5c869c2923d56cb0c8e6bcdd73c009c36ae39b652dbe46a05eb4ef0ad01454", size = 33740, upload-time = "2025-12-16T00:40:23.96Z" }, + { url = "https://files.pythonhosted.org/packages/df/c0/87c2073e0c72515bb8733d4eef7b21548e8d189f094b5dad20b0ecaf64f6/google_crc32c-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:3cc0c8912038065eafa603b238abf252e204accab2a704c63b9e14837a854962", size = 34437, upload-time = "2025-12-16T00:35:21.395Z" }, + { url = "https://files.pythonhosted.org/packages/d1/db/000f15b41724589b0e7bc24bc7a8967898d8d3bc8caf64c513d91ef1f6c0/google_crc32c-1.8.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:3ebb04528e83b2634857f43f9bb8ef5b2bbe7f10f140daeb01b58f972d04736b", size = 31297, upload-time = "2025-12-16T00:23:20.709Z" }, + { url = "https://files.pythonhosted.org/packages/d7/0d/8ebed0c39c53a7e838e2a486da8abb0e52de135f1b376ae2f0b160eb4c1a/google_crc32c-1.8.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:450dc98429d3e33ed2926fc99ee81001928d63460f8538f21a5d6060912a8e27", size = 30867, upload-time = "2025-12-16T00:43:14.628Z" }, + { url = "https://files.pythonhosted.org/packages/ce/42/b468aec74a0354b34c8cbf748db20d6e350a68a2b0912e128cabee49806c/google_crc32c-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3b9776774b24ba76831609ffbabce8cdf6fa2bd5e9df37b594221c7e333a81fa", size = 33344, upload-time = "2025-12-16T00:40:24.742Z" }, + { url = "https://files.pythonhosted.org/packages/1c/e8/b33784d6fc77fb5062a8a7854e43e1e618b87d5ddf610a88025e4de6226e/google_crc32c-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:89c17d53d75562edfff86679244830599ee0a48efc216200691de8b02ab6b2b8", size = 33694, upload-time = "2025-12-16T00:40:25.505Z" }, + { url = "https://files.pythonhosted.org/packages/92/b1/d3cbd4d988afb3d8e4db94ca953df429ed6db7282ed0e700d25e6c7bfc8d/google_crc32c-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:57a50a9035b75643996fbf224d6661e386c7162d1dfdab9bc4ca790947d1007f", size = 34435, upload-time = "2025-12-16T00:35:22.107Z" }, + { url = "https://files.pythonhosted.org/packages/21/88/8ecf3c2b864a490b9e7010c84fd203ec8cf3b280651106a3a74dd1b0ca72/google_crc32c-1.8.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:e6584b12cb06796d285d09e33f63309a09368b9d806a551d8036a4207ea43697", size = 31301, upload-time = "2025-12-16T00:24:48.527Z" }, + { url = "https://files.pythonhosted.org/packages/36/c6/f7ff6c11f5ca215d9f43d3629163727a272eabc356e5c9b2853df2bfe965/google_crc32c-1.8.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:f4b51844ef67d6cf2e9425983274da75f18b1597bb2c998e1c0a0e8d46f8f651", size = 30868, upload-time = "2025-12-16T00:48:12.163Z" }, + { url = "https://files.pythonhosted.org/packages/56/15/c25671c7aad70f8179d858c55a6ae8404902abe0cdcf32a29d581792b491/google_crc32c-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b0d1a7afc6e8e4635564ba8aa5c0548e3173e41b6384d7711a9123165f582de2", size = 33381, upload-time = "2025-12-16T00:40:26.268Z" }, + { url = "https://files.pythonhosted.org/packages/42/fa/f50f51260d7b0ef5d4898af122d8a7ec5a84e2984f676f746445f783705f/google_crc32c-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8b3f68782f3cbd1bce027e48768293072813469af6a61a86f6bb4977a4380f21", size = 33734, upload-time = "2025-12-16T00:40:27.028Z" }, + { url = "https://files.pythonhosted.org/packages/08/a5/7b059810934a09fb3ccb657e0843813c1fee1183d3bc2c8041800374aa2c/google_crc32c-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:d511b3153e7011a27ab6ee6bb3a5404a55b994dc1a7322c0b87b29606d9790e2", size = 34878, upload-time = "2025-12-16T00:35:23.142Z" }, + { url = "https://files.pythonhosted.org/packages/52/c5/c171e4d8c44fec1422d801a6d2e5d7ddabd733eeda505c79730ee9607f07/google_crc32c-1.8.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:87fa445064e7db928226b2e6f0d5304ab4cd0339e664a4e9a25029f384d9bb93", size = 28615, upload-time = "2025-12-16T00:40:29.298Z" }, + { url = "https://files.pythonhosted.org/packages/9c/97/7d75fe37a7a6ed171a2cf17117177e7aab7e6e0d115858741b41e9dd4254/google_crc32c-1.8.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f639065ea2042d5c034bf258a9f085eaa7af0cd250667c0635a3118e8f92c69c", size = 28800, upload-time = "2025-12-16T00:40:30.322Z" }, ] [[package]] name = "greenlet" version = "3.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a3/51/1664f6b78fc6ebbd98019a1fd730e83fa78f2db7058f72b1463d3612b8db/greenlet-3.3.2.tar.gz", hash = "sha256:2eaf067fc6d886931c7962e8c6bede15d2f01965560f3359b27c80bde2d151f2", size = 188267 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/47/16400cb42d18d7a6bb46f0626852c1718612e35dcb0dffa16bbaffdf5dd2/greenlet-3.3.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:c56692189a7d1c7606cb794be0a8381470d95c57ce5be03fb3d0ef57c7853b86", size = 278890 }, - { url = "https://files.pythonhosted.org/packages/a3/90/42762b77a5b6aa96cd8c0e80612663d39211e8ae8a6cd47c7f1249a66262/greenlet-3.3.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ebd458fa8285960f382841da585e02201b53a5ec2bac6b156fc623b5ce4499f", size = 581120 }, - { url = "https://files.pythonhosted.org/packages/bf/6f/f3d64f4fa0a9c7b5c5b3c810ff1df614540d5aa7d519261b53fba55d4df9/greenlet-3.3.2-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a443358b33c4ec7b05b79a7c8b466f5d275025e750298be7340f8fc63dff2a55", size = 594363 }, - { url = "https://files.pythonhosted.org/packages/9c/8b/1430a04657735a3f23116c2e0d5eb10220928846e4537a938a41b350bed6/greenlet-3.3.2-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4375a58e49522698d3e70cc0b801c19433021b5c37686f7ce9c65b0d5c8677d2", size = 605046 }, - { url = "https://files.pythonhosted.org/packages/72/83/3e06a52aca8128bdd4dcd67e932b809e76a96ab8c232a8b025b2850264c5/greenlet-3.3.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e2cd90d413acbf5e77ae41e5d3c9b3ac1d011a756d7284d7f3f2b806bbd6358", size = 594156 }, - { url = "https://files.pythonhosted.org/packages/70/79/0de5e62b873e08fe3cef7dbe84e5c4bc0e8ed0c7ff131bccb8405cd107c8/greenlet-3.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:442b6057453c8cb29b4fb36a2ac689382fc71112273726e2423f7f17dc73bf99", size = 1554649 }, - { url = "https://files.pythonhosted.org/packages/5a/00/32d30dee8389dc36d42170a9c66217757289e2afb0de59a3565260f38373/greenlet-3.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:45abe8eb6339518180d5a7fa47fa01945414d7cca5ecb745346fc6a87d2750be", size = 1619472 }, - { url = "https://files.pythonhosted.org/packages/f1/3a/efb2cf697fbccdf75b24e2c18025e7dfa54c4f31fab75c51d0fe79942cef/greenlet-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e692b2dae4cc7077cbb11b47d258533b48c8fde69a33d0d8a82e2fe8d8531d5", size = 230389 }, - { url = "https://files.pythonhosted.org/packages/e1/a1/65bbc059a43a7e2143ec4fc1f9e3f673e04f9c7b371a494a101422ac4fd5/greenlet-3.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:02b0a8682aecd4d3c6c18edf52bc8e51eacdd75c8eac52a790a210b06aa295fd", size = 229645 }, - { url = "https://files.pythonhosted.org/packages/ea/ab/1608e5a7578e62113506740b88066bf09888322a311cff602105e619bd87/greenlet-3.3.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ac8d61d4343b799d1e526db579833d72f23759c71e07181c2d2944e429eb09cd", size = 280358 }, - { url = "https://files.pythonhosted.org/packages/a5/23/0eae412a4ade4e6623ff7626e38998cb9b11e9ff1ebacaa021e4e108ec15/greenlet-3.3.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ceec72030dae6ac0c8ed7591b96b70410a8be370b6a477b1dbc072856ad02bd", size = 601217 }, - { url = "https://files.pythonhosted.org/packages/f8/16/5b1678a9c07098ecb9ab2dd159fafaf12e963293e61ee8d10ecb55273e5e/greenlet-3.3.2-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2a5be83a45ce6188c045bcc44b0ee037d6a518978de9a5d97438548b953a1ac", size = 611792 }, - { url = "https://files.pythonhosted.org/packages/5c/c5/cc09412a29e43406eba18d61c70baa936e299bc27e074e2be3806ed29098/greenlet-3.3.2-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae9e21c84035c490506c17002f5c8ab25f980205c3e61ddb3a2a2a2e6c411fcb", size = 626250 }, - { url = "https://files.pythonhosted.org/packages/50/1f/5155f55bd71cabd03765a4aac9ac446be129895271f73872c36ebd4b04b6/greenlet-3.3.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e99d1749147ac21dde49b99c9abffcbc1e2d55c67501465ef0930d6e78e070", size = 613875 }, - { url = "https://files.pythonhosted.org/packages/fc/dd/845f249c3fcd69e32df80cdab059b4be8b766ef5830a3d0aa9d6cad55beb/greenlet-3.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c956a19350e2c37f2c48b336a3afb4bff120b36076d9d7fb68cb44e05d95b79", size = 1571467 }, - { url = "https://files.pythonhosted.org/packages/2a/50/2649fe21fcc2b56659a452868e695634722a6655ba245d9f77f5656010bf/greenlet-3.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c6f8ba97d17a1e7d664151284cb3315fc5f8353e75221ed4324f84eb162b395", size = 1640001 }, - { url = "https://files.pythonhosted.org/packages/9b/40/cc802e067d02af8b60b6771cea7d57e21ef5e6659912814babb42b864713/greenlet-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:34308836d8370bddadb41f5a7ce96879b72e2fdfb4e87729330c6ab52376409f", size = 231081 }, - { url = "https://files.pythonhosted.org/packages/58/2e/fe7f36ff1982d6b10a60d5e0740c759259a7d6d2e1dc41da6d96de32fff6/greenlet-3.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:d3a62fa76a32b462a97198e4c9e99afb9ab375115e74e9a83ce180e7a496f643", size = 230331 }, - { url = "https://files.pythonhosted.org/packages/ac/48/f8b875fa7dea7dd9b33245e37f065af59df6a25af2f9561efa8d822fde51/greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:aa6ac98bdfd716a749b84d4034486863fd81c3abde9aa3cf8eff9127981a4ae4", size = 279120 }, - { url = "https://files.pythonhosted.org/packages/49/8d/9771d03e7a8b1ee456511961e1b97a6d77ae1dea4a34a5b98eee706689d3/greenlet-3.3.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab0c7e7901a00bc0a7284907273dc165b32e0d109a6713babd04471327ff7986", size = 603238 }, - { url = "https://files.pythonhosted.org/packages/59/0e/4223c2bbb63cd5c97f28ffb2a8aee71bdfb30b323c35d409450f51b91e3e/greenlet-3.3.2-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d248d8c23c67d2291ffd47af766e2a3aa9fa1c6703155c099feb11f526c63a92", size = 614219 }, - { url = "https://files.pythonhosted.org/packages/94/2b/4d012a69759ac9d77210b8bfb128bc621125f5b20fc398bce3940d036b1c/greenlet-3.3.2-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ccd21bb86944ca9be6d967cf7691e658e43417782bce90b5d2faeda0ff78a7dd", size = 628268 }, - { url = "https://files.pythonhosted.org/packages/7a/34/259b28ea7a2a0c904b11cd36c79b8cef8019b26ee5dbe24e73b469dea347/greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6997d360a4e6a4e936c0f9625b1c20416b8a0ea18a8e19cabbefc712e7397ab", size = 616774 }, - { url = "https://files.pythonhosted.org/packages/0a/03/996c2d1689d486a6e199cb0f1cf9e4aa940c500e01bdf201299d7d61fa69/greenlet-3.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64970c33a50551c7c50491671265d8954046cb6e8e2999aacdd60e439b70418a", size = 1571277 }, - { url = "https://files.pythonhosted.org/packages/d9/c4/2570fc07f34a39f2caf0bf9f24b0a1a0a47bc2e8e465b2c2424821389dfc/greenlet-3.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1a9172f5bf6bd88e6ba5a84e0a68afeac9dc7b6b412b245dd64f52d83c81e55b", size = 1640455 }, - { url = "https://files.pythonhosted.org/packages/91/39/5ef5aa23bc545aa0d31e1b9b55822b32c8da93ba657295840b6b34124009/greenlet-3.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:a7945dd0eab63ded0a48e4dcade82939783c172290a7903ebde9e184333ca124", size = 230961 }, - { url = "https://files.pythonhosted.org/packages/62/6b/a89f8456dcb06becff288f563618e9f20deed8dd29beea14f9a168aef64b/greenlet-3.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:394ead29063ee3515b4e775216cb756b2e3b4a7e55ae8fd884f17fa579e6b327", size = 230221 }, - { url = "https://files.pythonhosted.org/packages/3f/ae/8bffcbd373b57a5992cd077cbe8858fff39110480a9d50697091faea6f39/greenlet-3.3.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8d1658d7291f9859beed69a776c10822a0a799bc4bfe1bd4272bb60e62507dab", size = 279650 }, - { url = "https://files.pythonhosted.org/packages/d1/c0/45f93f348fa49abf32ac8439938726c480bd96b2a3c6f4d949ec0124b69f/greenlet-3.3.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18cb1b7337bca281915b3c5d5ae19f4e76d35e1df80f4ad3c1a7be91fadf1082", size = 650295 }, - { url = "https://files.pythonhosted.org/packages/b3/de/dd7589b3f2b8372069ab3e4763ea5329940fc7ad9dcd3e272a37516d7c9b/greenlet-3.3.2-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2e47408e8ce1c6f1ceea0dffcdf6ebb85cc09e55c7af407c99f1112016e45e9", size = 662163 }, - { url = "https://files.pythonhosted.org/packages/cd/ac/85804f74f1ccea31ba518dcc8ee6f14c79f73fe36fa1beba38930806df09/greenlet-3.3.2-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e3cb43ce200f59483eb82949bf1835a99cf43d7571e900d7c8d5c62cdf25d2f9", size = 675371 }, - { url = "https://files.pythonhosted.org/packages/d2/d8/09bfa816572a4d83bccd6750df1926f79158b1c36c5f73786e26dbe4ee38/greenlet-3.3.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63d10328839d1973e5ba35e98cccbca71b232b14051fd957b6f8b6e8e80d0506", size = 664160 }, - { url = "https://files.pythonhosted.org/packages/48/cf/56832f0c8255d27f6c35d41b5ec91168d74ec721d85f01a12131eec6b93c/greenlet-3.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e4ab3cfb02993c8cc248ea73d7dae6cec0253e9afa311c9b37e603ca9fad2ce", size = 1619181 }, - { url = "https://files.pythonhosted.org/packages/0a/23/b90b60a4aabb4cec0796e55f25ffbfb579a907c3898cd2905c8918acaa16/greenlet-3.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94ad81f0fd3c0c0681a018a976e5c2bd2ca2d9d94895f23e7bb1af4e8af4e2d5", size = 1687713 }, - { url = "https://files.pythonhosted.org/packages/f3/ca/2101ca3d9223a1dc125140dbc063644dca76df6ff356531eb27bc267b446/greenlet-3.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:8c4dd0f3997cf2512f7601563cc90dfb8957c0cff1e3a1b23991d4ea1776c492", size = 232034 }, - { url = "https://files.pythonhosted.org/packages/f6/4a/ecf894e962a59dea60f04877eea0fd5724618da89f1867b28ee8b91e811f/greenlet-3.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:cd6f9e2bbd46321ba3bbb4c8a15794d32960e3b0ae2cc4d49a1a53d314805d71", size = 231437 }, - { url = "https://files.pythonhosted.org/packages/98/6d/8f2ef704e614bcf58ed43cfb8d87afa1c285e98194ab2cfad351bf04f81e/greenlet-3.3.2-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:e26e72bec7ab387ac80caa7496e0f908ff954f31065b0ffc1f8ecb1338b11b54", size = 286617 }, - { url = "https://files.pythonhosted.org/packages/5e/0d/93894161d307c6ea237a43988f27eba0947b360b99ac5239ad3fe09f0b47/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b466dff7a4ffda6ca975979bab80bdadde979e29fc947ac3be4451428d8b0e4", size = 655189 }, - { url = "https://files.pythonhosted.org/packages/f5/2c/d2d506ebd8abcb57386ec4f7ba20f4030cbe56eae541bc6fd6ef399c0b41/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b8bddc5b73c9720bea487b3bffdb1840fe4e3656fba3bd40aa1489e9f37877ff", size = 658225 }, - { url = "https://files.pythonhosted.org/packages/d1/67/8197b7e7e602150938049d8e7f30de1660cfb87e4c8ee349b42b67bdb2e1/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:59b3e2c40f6706b05a9cd299c836c6aa2378cabe25d021acd80f13abf81181cf", size = 666581 }, - { url = "https://files.pythonhosted.org/packages/8e/30/3a09155fbf728673a1dea713572d2d31159f824a37c22da82127056c44e4/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b26b0f4428b871a751968285a1ac9648944cea09807177ac639b030bddebcea4", size = 657907 }, - { url = "https://files.pythonhosted.org/packages/f3/fd/d05a4b7acd0154ed758797f0a43b4c0962a843bedfe980115e842c5b2d08/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1fb39a11ee2e4d94be9a76671482be9398560955c9e568550de0224e41104727", size = 1618857 }, - { url = "https://files.pythonhosted.org/packages/6f/e1/50ee92a5db521de8f35075b5eff060dd43d39ebd46c2181a2042f7070385/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:20154044d9085151bc309e7689d6f7ba10027f8f5a8c0676ad398b951913d89e", size = 1680010 }, - { url = "https://files.pythonhosted.org/packages/29/4b/45d90626aef8e65336bed690106d1382f7a43665e2249017e9527df8823b/greenlet-3.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c04c5e06ec3e022cbfe2cd4a846e1d4e50087444f875ff6d2c2ad8445495cf1a", size = 237086 }, +sdist = { url = "https://files.pythonhosted.org/packages/a3/51/1664f6b78fc6ebbd98019a1fd730e83fa78f2db7058f72b1463d3612b8db/greenlet-3.3.2.tar.gz", hash = "sha256:2eaf067fc6d886931c7962e8c6bede15d2f01965560f3359b27c80bde2d151f2", size = 188267, upload-time = "2026-02-20T20:54:15.531Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/47/16400cb42d18d7a6bb46f0626852c1718612e35dcb0dffa16bbaffdf5dd2/greenlet-3.3.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:c56692189a7d1c7606cb794be0a8381470d95c57ce5be03fb3d0ef57c7853b86", size = 278890, upload-time = "2026-02-20T20:19:39.263Z" }, + { url = "https://files.pythonhosted.org/packages/a3/90/42762b77a5b6aa96cd8c0e80612663d39211e8ae8a6cd47c7f1249a66262/greenlet-3.3.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ebd458fa8285960f382841da585e02201b53a5ec2bac6b156fc623b5ce4499f", size = 581120, upload-time = "2026-02-20T20:47:30.161Z" }, + { url = "https://files.pythonhosted.org/packages/bf/6f/f3d64f4fa0a9c7b5c5b3c810ff1df614540d5aa7d519261b53fba55d4df9/greenlet-3.3.2-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a443358b33c4ec7b05b79a7c8b466f5d275025e750298be7340f8fc63dff2a55", size = 594363, upload-time = "2026-02-20T20:55:56.965Z" }, + { url = "https://files.pythonhosted.org/packages/72/83/3e06a52aca8128bdd4dcd67e932b809e76a96ab8c232a8b025b2850264c5/greenlet-3.3.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e2cd90d413acbf5e77ae41e5d3c9b3ac1d011a756d7284d7f3f2b806bbd6358", size = 594156, upload-time = "2026-02-20T20:20:59.955Z" }, + { url = "https://files.pythonhosted.org/packages/70/79/0de5e62b873e08fe3cef7dbe84e5c4bc0e8ed0c7ff131bccb8405cd107c8/greenlet-3.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:442b6057453c8cb29b4fb36a2ac689382fc71112273726e2423f7f17dc73bf99", size = 1554649, upload-time = "2026-02-20T20:49:32.293Z" }, + { url = "https://files.pythonhosted.org/packages/5a/00/32d30dee8389dc36d42170a9c66217757289e2afb0de59a3565260f38373/greenlet-3.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:45abe8eb6339518180d5a7fa47fa01945414d7cca5ecb745346fc6a87d2750be", size = 1619472, upload-time = "2026-02-20T20:21:07.966Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3a/efb2cf697fbccdf75b24e2c18025e7dfa54c4f31fab75c51d0fe79942cef/greenlet-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e692b2dae4cc7077cbb11b47d258533b48c8fde69a33d0d8a82e2fe8d8531d5", size = 230389, upload-time = "2026-02-20T20:17:18.772Z" }, + { url = "https://files.pythonhosted.org/packages/e1/a1/65bbc059a43a7e2143ec4fc1f9e3f673e04f9c7b371a494a101422ac4fd5/greenlet-3.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:02b0a8682aecd4d3c6c18edf52bc8e51eacdd75c8eac52a790a210b06aa295fd", size = 229645, upload-time = "2026-02-20T20:18:18.695Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ab/1608e5a7578e62113506740b88066bf09888322a311cff602105e619bd87/greenlet-3.3.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ac8d61d4343b799d1e526db579833d72f23759c71e07181c2d2944e429eb09cd", size = 280358, upload-time = "2026-02-20T20:17:43.971Z" }, + { url = "https://files.pythonhosted.org/packages/a5/23/0eae412a4ade4e6623ff7626e38998cb9b11e9ff1ebacaa021e4e108ec15/greenlet-3.3.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ceec72030dae6ac0c8ed7591b96b70410a8be370b6a477b1dbc072856ad02bd", size = 601217, upload-time = "2026-02-20T20:47:31.462Z" }, + { url = "https://files.pythonhosted.org/packages/f8/16/5b1678a9c07098ecb9ab2dd159fafaf12e963293e61ee8d10ecb55273e5e/greenlet-3.3.2-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2a5be83a45ce6188c045bcc44b0ee037d6a518978de9a5d97438548b953a1ac", size = 611792, upload-time = "2026-02-20T20:55:58.423Z" }, + { url = "https://files.pythonhosted.org/packages/50/1f/5155f55bd71cabd03765a4aac9ac446be129895271f73872c36ebd4b04b6/greenlet-3.3.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e99d1749147ac21dde49b99c9abffcbc1e2d55c67501465ef0930d6e78e070", size = 613875, upload-time = "2026-02-20T20:21:01.102Z" }, + { url = "https://files.pythonhosted.org/packages/fc/dd/845f249c3fcd69e32df80cdab059b4be8b766ef5830a3d0aa9d6cad55beb/greenlet-3.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c956a19350e2c37f2c48b336a3afb4bff120b36076d9d7fb68cb44e05d95b79", size = 1571467, upload-time = "2026-02-20T20:49:33.495Z" }, + { url = "https://files.pythonhosted.org/packages/2a/50/2649fe21fcc2b56659a452868e695634722a6655ba245d9f77f5656010bf/greenlet-3.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c6f8ba97d17a1e7d664151284cb3315fc5f8353e75221ed4324f84eb162b395", size = 1640001, upload-time = "2026-02-20T20:21:09.154Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/cc802e067d02af8b60b6771cea7d57e21ef5e6659912814babb42b864713/greenlet-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:34308836d8370bddadb41f5a7ce96879b72e2fdfb4e87729330c6ab52376409f", size = 231081, upload-time = "2026-02-20T20:17:28.121Z" }, + { url = "https://files.pythonhosted.org/packages/58/2e/fe7f36ff1982d6b10a60d5e0740c759259a7d6d2e1dc41da6d96de32fff6/greenlet-3.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:d3a62fa76a32b462a97198e4c9e99afb9ab375115e74e9a83ce180e7a496f643", size = 230331, upload-time = "2026-02-20T20:17:23.34Z" }, + { url = "https://files.pythonhosted.org/packages/ac/48/f8b875fa7dea7dd9b33245e37f065af59df6a25af2f9561efa8d822fde51/greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:aa6ac98bdfd716a749b84d4034486863fd81c3abde9aa3cf8eff9127981a4ae4", size = 279120, upload-time = "2026-02-20T20:19:01.9Z" }, + { url = "https://files.pythonhosted.org/packages/49/8d/9771d03e7a8b1ee456511961e1b97a6d77ae1dea4a34a5b98eee706689d3/greenlet-3.3.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab0c7e7901a00bc0a7284907273dc165b32e0d109a6713babd04471327ff7986", size = 603238, upload-time = "2026-02-20T20:47:32.873Z" }, + { url = "https://files.pythonhosted.org/packages/59/0e/4223c2bbb63cd5c97f28ffb2a8aee71bdfb30b323c35d409450f51b91e3e/greenlet-3.3.2-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d248d8c23c67d2291ffd47af766e2a3aa9fa1c6703155c099feb11f526c63a92", size = 614219, upload-time = "2026-02-20T20:55:59.817Z" }, + { url = "https://files.pythonhosted.org/packages/7a/34/259b28ea7a2a0c904b11cd36c79b8cef8019b26ee5dbe24e73b469dea347/greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6997d360a4e6a4e936c0f9625b1c20416b8a0ea18a8e19cabbefc712e7397ab", size = 616774, upload-time = "2026-02-20T20:21:02.454Z" }, + { url = "https://files.pythonhosted.org/packages/0a/03/996c2d1689d486a6e199cb0f1cf9e4aa940c500e01bdf201299d7d61fa69/greenlet-3.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64970c33a50551c7c50491671265d8954046cb6e8e2999aacdd60e439b70418a", size = 1571277, upload-time = "2026-02-20T20:49:34.795Z" }, + { url = "https://files.pythonhosted.org/packages/d9/c4/2570fc07f34a39f2caf0bf9f24b0a1a0a47bc2e8e465b2c2424821389dfc/greenlet-3.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1a9172f5bf6bd88e6ba5a84e0a68afeac9dc7b6b412b245dd64f52d83c81e55b", size = 1640455, upload-time = "2026-02-20T20:21:10.261Z" }, + { url = "https://files.pythonhosted.org/packages/91/39/5ef5aa23bc545aa0d31e1b9b55822b32c8da93ba657295840b6b34124009/greenlet-3.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:a7945dd0eab63ded0a48e4dcade82939783c172290a7903ebde9e184333ca124", size = 230961, upload-time = "2026-02-20T20:16:58.461Z" }, + { url = "https://files.pythonhosted.org/packages/62/6b/a89f8456dcb06becff288f563618e9f20deed8dd29beea14f9a168aef64b/greenlet-3.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:394ead29063ee3515b4e775216cb756b2e3b4a7e55ae8fd884f17fa579e6b327", size = 230221, upload-time = "2026-02-20T20:17:37.152Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ae/8bffcbd373b57a5992cd077cbe8858fff39110480a9d50697091faea6f39/greenlet-3.3.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8d1658d7291f9859beed69a776c10822a0a799bc4bfe1bd4272bb60e62507dab", size = 279650, upload-time = "2026-02-20T20:18:00.783Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c0/45f93f348fa49abf32ac8439938726c480bd96b2a3c6f4d949ec0124b69f/greenlet-3.3.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18cb1b7337bca281915b3c5d5ae19f4e76d35e1df80f4ad3c1a7be91fadf1082", size = 650295, upload-time = "2026-02-20T20:47:34.036Z" }, + { url = "https://files.pythonhosted.org/packages/b3/de/dd7589b3f2b8372069ab3e4763ea5329940fc7ad9dcd3e272a37516d7c9b/greenlet-3.3.2-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2e47408e8ce1c6f1ceea0dffcdf6ebb85cc09e55c7af407c99f1112016e45e9", size = 662163, upload-time = "2026-02-20T20:56:01.295Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d8/09bfa816572a4d83bccd6750df1926f79158b1c36c5f73786e26dbe4ee38/greenlet-3.3.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63d10328839d1973e5ba35e98cccbca71b232b14051fd957b6f8b6e8e80d0506", size = 664160, upload-time = "2026-02-20T20:21:04.015Z" }, + { url = "https://files.pythonhosted.org/packages/48/cf/56832f0c8255d27f6c35d41b5ec91168d74ec721d85f01a12131eec6b93c/greenlet-3.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e4ab3cfb02993c8cc248ea73d7dae6cec0253e9afa311c9b37e603ca9fad2ce", size = 1619181, upload-time = "2026-02-20T20:49:36.052Z" }, + { url = "https://files.pythonhosted.org/packages/0a/23/b90b60a4aabb4cec0796e55f25ffbfb579a907c3898cd2905c8918acaa16/greenlet-3.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94ad81f0fd3c0c0681a018a976e5c2bd2ca2d9d94895f23e7bb1af4e8af4e2d5", size = 1687713, upload-time = "2026-02-20T20:21:11.684Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ca/2101ca3d9223a1dc125140dbc063644dca76df6ff356531eb27bc267b446/greenlet-3.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:8c4dd0f3997cf2512f7601563cc90dfb8957c0cff1e3a1b23991d4ea1776c492", size = 232034, upload-time = "2026-02-20T20:20:08.186Z" }, + { url = "https://files.pythonhosted.org/packages/f6/4a/ecf894e962a59dea60f04877eea0fd5724618da89f1867b28ee8b91e811f/greenlet-3.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:cd6f9e2bbd46321ba3bbb4c8a15794d32960e3b0ae2cc4d49a1a53d314805d71", size = 231437, upload-time = "2026-02-20T20:18:59.722Z" }, + { url = "https://files.pythonhosted.org/packages/98/6d/8f2ef704e614bcf58ed43cfb8d87afa1c285e98194ab2cfad351bf04f81e/greenlet-3.3.2-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:e26e72bec7ab387ac80caa7496e0f908ff954f31065b0ffc1f8ecb1338b11b54", size = 286617, upload-time = "2026-02-20T20:19:29.856Z" }, + { url = "https://files.pythonhosted.org/packages/5e/0d/93894161d307c6ea237a43988f27eba0947b360b99ac5239ad3fe09f0b47/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b466dff7a4ffda6ca975979bab80bdadde979e29fc947ac3be4451428d8b0e4", size = 655189, upload-time = "2026-02-20T20:47:35.742Z" }, + { url = "https://files.pythonhosted.org/packages/f5/2c/d2d506ebd8abcb57386ec4f7ba20f4030cbe56eae541bc6fd6ef399c0b41/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b8bddc5b73c9720bea487b3bffdb1840fe4e3656fba3bd40aa1489e9f37877ff", size = 658225, upload-time = "2026-02-20T20:56:02.527Z" }, + { url = "https://files.pythonhosted.org/packages/8e/30/3a09155fbf728673a1dea713572d2d31159f824a37c22da82127056c44e4/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b26b0f4428b871a751968285a1ac9648944cea09807177ac639b030bddebcea4", size = 657907, upload-time = "2026-02-20T20:21:05.259Z" }, + { url = "https://files.pythonhosted.org/packages/f3/fd/d05a4b7acd0154ed758797f0a43b4c0962a843bedfe980115e842c5b2d08/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1fb39a11ee2e4d94be9a76671482be9398560955c9e568550de0224e41104727", size = 1618857, upload-time = "2026-02-20T20:49:37.309Z" }, + { url = "https://files.pythonhosted.org/packages/6f/e1/50ee92a5db521de8f35075b5eff060dd43d39ebd46c2181a2042f7070385/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:20154044d9085151bc309e7689d6f7ba10027f8f5a8c0676ad398b951913d89e", size = 1680010, upload-time = "2026-02-20T20:21:13.427Z" }, + { url = "https://files.pythonhosted.org/packages/29/4b/45d90626aef8e65336bed690106d1382f7a43665e2249017e9527df8823b/greenlet-3.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c04c5e06ec3e022cbfe2cd4a846e1d4e50087444f875ff6d2c2ad8445495cf1a", size = 237086, upload-time = "2026-02-20T20:20:45.786Z" }, ] [[package]] @@ -1023,60 +998,52 @@ wheels = [ name = "h11" version = "0.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250 } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 }, + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] [[package]] name = "h5py" -version = "3.16.0" +version = "3.15.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/db/33/acd0ce6863b6c0d7735007df01815403f5589a21ff8c2e1ee2587a38f548/h5py-3.16.0.tar.gz", hash = "sha256:a0dbaad796840ccaa67a4c144a0d0c8080073c34c76d5a6941d6818678ef2738", size = 446526, upload-time = "2026-03-06T13:49:08.07Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/95/a825894f3e45cbac7554c4e97314ce886b233a20033787eda755ca8fecc7/h5py-3.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:719439d14b83f74eeb080e9650a6c7aa6d0d9ea0ca7f804347b05fac6fbf18af", size = 3721663, upload-time = "2026-03-06T13:47:49.599Z" }, - { url = "https://files.pythonhosted.org/packages/bf/3b/38ff88b347c3e346cda1d3fc1b65a7aa75d40632228d8b8a5d7b58508c24/h5py-3.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c3f0a0e136f2e95dd0b67146abb6668af4f1a69c81ef8651a2d316e8e01de447", size = 3087630, upload-time = "2026-03-06T13:47:51.249Z" }, - { url = "https://files.pythonhosted.org/packages/98/a8/2594cef906aee761601eff842c7dc598bea2b394a3e1c00966832b8eeb7c/h5py-3.16.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:a6fbc5367d4046801f9b7db9191b31895f22f1c6df1f9987d667854cac493538", size = 4823472, upload-time = "2026-03-06T13:47:53.085Z" }, - { url = "https://files.pythonhosted.org/packages/52/a0/c1f604538ff6db22a0690be2dc44ab59178e115f63c917794e529356ab23/h5py-3.16.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:fb1720028d99040792bb2fb31facb8da44a6f29df7697e0b84f0d79aff2e9bd3", size = 5027150, upload-time = "2026-03-06T13:47:55.043Z" }, - { url = "https://files.pythonhosted.org/packages/2e/fd/301739083c2fc4fd89950f9bcfce75d6e14b40b0ca3d40e48a8993d1722c/h5py-3.16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:314b6054fe0b1051c2b0cb2df5cbdab15622fb05e80f202e3b6a5eee0d6fe365", size = 4814544, upload-time = "2026-03-06T13:47:56.893Z" }, - { url = "https://files.pythonhosted.org/packages/4c/42/2193ed41ccee78baba8fcc0cff2c925b8b9ee3793305b23e1f22c20bf4c7/h5py-3.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ffbab2fedd6581f6aa31cf1639ca2cb86e02779de525667892ebf4cc9fd26434", size = 5034013, upload-time = "2026-03-06T13:47:59.01Z" }, - { url = "https://files.pythonhosted.org/packages/f7/20/e6c0ff62ca2ad1a396a34f4380bafccaaf8791ff8fccf3d995a1fc12d417/h5py-3.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:17d1f1630f92ad74494a9a7392ab25982ce2b469fc62da6074c0ce48366a2999", size = 3191673, upload-time = "2026-03-06T13:48:00.626Z" }, - { url = "https://files.pythonhosted.org/packages/f2/48/239cbe352ac4f2b8243a8e620fa1a2034635f633731493a7ff1ed71e8658/h5py-3.16.0-cp311-cp311-win_arm64.whl", hash = "sha256:85b9c49dd58dc44cf70af944784e2c2038b6f799665d0dcbbc812a26e0faa859", size = 2673834, upload-time = "2026-03-06T13:48:02.579Z" }, - { url = "https://files.pythonhosted.org/packages/c8/c0/5d4119dba94093bbafede500d3defd2f5eab7897732998c04b54021e530b/h5py-3.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c5313566f4643121a78503a473f0fb1e6dcc541d5115c44f05e037609c565c4d", size = 3685604, upload-time = "2026-03-06T13:48:04.198Z" }, - { url = "https://files.pythonhosted.org/packages/b0/42/c84efcc1d4caebafb1ecd8be4643f39c85c47a80fe254d92b8b43b1eadaf/h5py-3.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:42b012933a83e1a558c673176676a10ce2fd3759976a0fedee1e672d1e04fc9d", size = 3061940, upload-time = "2026-03-06T13:48:05.783Z" }, - { url = "https://files.pythonhosted.org/packages/89/84/06281c82d4d1686fde1ac6b0f307c50918f1c0151062445ab3b6fa5a921d/h5py-3.16.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:ff24039e2573297787c3063df64b60aab0591980ac898329a08b0320e0cf2527", size = 5198852, upload-time = "2026-03-06T13:48:07.482Z" }, - { url = "https://files.pythonhosted.org/packages/9e/e9/1a19e42cd43cc1365e127db6aae85e1c671da1d9a5d746f4d34a50edb577/h5py-3.16.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:dfc21898ff025f1e8e67e194965a95a8d4754f452f83454538f98f8a3fcb207e", size = 5405250, upload-time = "2026-03-06T13:48:09.628Z" }, - { url = "https://files.pythonhosted.org/packages/b7/8e/9790c1655eabeb85b92b1ecab7d7e62a2069e53baefd58c98f0909c7a948/h5py-3.16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:698dd69291272642ffda44a0ecd6cd3bda5faf9621452d255f57ce91487b9794", size = 5190108, upload-time = "2026-03-06T13:48:11.26Z" }, - { url = "https://files.pythonhosted.org/packages/51/d7/ab693274f1bd7e8c5f9fdd6c7003a88d59bedeaf8752716a55f532924fbb/h5py-3.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2b2c02b0a160faed5fb33f1ba8a264a37ee240b22e049ecc827345d0d9043074", size = 5419216, upload-time = "2026-03-06T13:48:13.322Z" }, - { url = "https://files.pythonhosted.org/packages/03/c1/0976b235cf29ead553e22f2fb6385a8252b533715e00d0ae52ed7b900582/h5py-3.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:96b422019a1c8975c2d5dadcf61d4ba6f01c31f92bbde6e4649607885fe502d6", size = 3182868, upload-time = "2026-03-06T13:48:15.759Z" }, - { url = "https://files.pythonhosted.org/packages/14/d9/866b7e570b39070f92d47b0ff1800f0f8239b6f9e45f02363d7112336c1f/h5py-3.16.0-cp312-cp312-win_arm64.whl", hash = "sha256:39c2838fb1e8d97bcf1755e60ad1f3dd76a7b2a475928dc321672752678b96db", size = 2653286, upload-time = "2026-03-06T13:48:17.279Z" }, - { url = "https://files.pythonhosted.org/packages/0f/9e/6142ebfda0cb6e9349c091eae73c2e01a770b7659255248d637bec54a88b/h5py-3.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:370a845f432c2c9619db8eed334d1e610c6015796122b0e57aa46312c22617d9", size = 3671808, upload-time = "2026-03-06T13:48:19.737Z" }, - { url = "https://files.pythonhosted.org/packages/b0/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42108e93326c50c2810025aade9eac9d6827524cdccc7d4b75a546e5ab308edb", size = 3045837, upload-time = "2026-03-06T13:48:21.854Z" }, - { url = "https://files.pythonhosted.org/packages/da/1e/6172269e18cc5a484e2913ced33339aad588e02ba407fafd00d369e22ef3/h5py-3.16.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:099f2525c9dcf28de366970a5fb34879aab20491589fa89ce2863a84218bb524", size = 5193860, upload-time = "2026-03-06T13:48:24.071Z" }, - { url = "https://files.pythonhosted.org/packages/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:9300ad32dea9dfc5171f94d5f6948e159ed93e4701280b0f508773b3f582f402", size = 5400417, upload-time = "2026-03-06T13:48:25.728Z" }, - { url = "https://files.pythonhosted.org/packages/bc/81/5b62d760039eed64348c98129d17061fdfc7839fc9c04eaaad6dee1004e4/h5py-3.16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:171038f23bccddfc23f344cadabdfc9917ff554db6a0d417180d2747fe4c75a7", size = 5185214, upload-time = "2026-03-06T13:48:27.436Z" }, - { url = "https://files.pythonhosted.org/packages/28/c4/532123bcd9080e250696779c927f2cb906c8bf3447df98f5ceb8dcded539/h5py-3.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7e420b539fb6023a259a1b14d4c9f6df8cf50d7268f48e161169987a57b737ff", size = 5414598, upload-time = "2026-03-06T13:48:29.49Z" }, - { url = "https://files.pythonhosted.org/packages/c3/d9/a27997f84341fc0dfcdd1fe4179b6ba6c32a7aa880fdb8c514d4dad6fba3/h5py-3.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:18f2bbcd545e6991412253b98727374c356d67caa920e68dc79eab36bf5fedad", size = 3175509, upload-time = "2026-03-06T13:48:31.131Z" }, - { url = "https://files.pythonhosted.org/packages/a5/23/bb8647521d4fd770c30a76cfc6cb6a2f5495868904054e92f2394c5a78ff/h5py-3.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:656f00e4d903199a1d58df06b711cf3ca632b874b4207b7dbec86185b5c8c7d4", size = 2647362, upload-time = "2026-03-06T13:48:33.411Z" }, - { url = "https://files.pythonhosted.org/packages/48/3c/7fcd9b4c9eed82e91fb15568992561019ae7a829d1f696b2c844355d95dd/h5py-3.16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9c9d307c0ef862d1cd5714f72ecfafe0a5d7529c44845afa8de9f46e5ba8bd65", size = 3678608, upload-time = "2026-03-06T13:48:35.183Z" }, - { url = "https://files.pythonhosted.org/packages/6a/b7/9366ed44ced9b7ef357ab48c94205280276db9d7f064aa3012a97227e966/h5py-3.16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8c1eff849cdd53cbc73c214c30ebdb6f1bb8b64790b4b4fc36acdb5e43570210", size = 3054773, upload-time = "2026-03-06T13:48:37.139Z" }, - { url = "https://files.pythonhosted.org/packages/58/a5/4964bc0e91e86340c2bbda83420225b2f770dcf1eb8a39464871ad769436/h5py-3.16.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:e2c04d129f180019e216ee5f9c40b78a418634091c8782e1f723a6ca3658b965", size = 5198886, upload-time = "2026-03-06T13:48:38.879Z" }, - { url = "https://files.pythonhosted.org/packages/f1/16/d905e7f53e661ce2c24686c38048d8e2b750ffc4350009d41c4e6c6c9826/h5py-3.16.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:e4360f15875a532bc7b98196c7592ed4fc92672a57c0a621355961cafb17a6dd", size = 5404883, upload-time = "2026-03-06T13:48:41.324Z" }, - { url = "https://files.pythonhosted.org/packages/4b/f2/58f34cb74af46d39f4cd18ea20909a8514960c5a3e5b92fd06a28161e0a8/h5py-3.16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3fae9197390c325e62e0a1aa977f2f62d994aa87aab182abbea85479b791197c", size = 5192039, upload-time = "2026-03-06T13:48:43.117Z" }, - { url = "https://files.pythonhosted.org/packages/ce/ca/934a39c24ce2e2db017268c08da0537c20fa0be7e1549be3e977313fc8f5/h5py-3.16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:43259303989ac8adacc9986695b31e35dba6fd1e297ff9c6a04b7da5542139cc", size = 5421526, upload-time = "2026-03-06T13:48:44.838Z" }, - { url = "https://files.pythonhosted.org/packages/3e/14/615a450205e1b56d16c6783f5ccd116cde05550faad70ae077c955654a75/h5py-3.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:fa48993a0b799737ba7fd21e2350fa0a60701e58180fae9f2de834bc39a147ab", size = 3183263, upload-time = "2026-03-06T13:48:47.117Z" }, - { url = "https://files.pythonhosted.org/packages/7b/48/a6faef5ed632cae0c65ac6b214a6614a0b510c3183532c521bdb0055e117/h5py-3.16.0-cp314-cp314-win_arm64.whl", hash = "sha256:1897a771a7f40d05c262fc8f37376ec37873218544b70216872876c627640f63", size = 2663450, upload-time = "2026-03-06T13:48:48.707Z" }, - { url = "https://files.pythonhosted.org/packages/5d/32/0c8bb8aedb62c772cf7c1d427c7d1951477e8c2835f872bc0a13d1f85f86/h5py-3.16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:15922e485844f77c0b9d275396d435db3baa58292a9c2176a386e072e0cf2491", size = 3760693, upload-time = "2026-03-06T13:48:50.453Z" }, - { url = "https://files.pythonhosted.org/packages/1d/1f/fcc5977d32d6387c5c9a694afee716a5e20658ac08b3ff24fdec79fb05f2/h5py-3.16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:df02dd29bd247f98674634dfe41f89fd7c16ba3d7de8695ec958f58404a4e618", size = 3181305, upload-time = "2026-03-06T13:48:52.221Z" }, - { url = "https://files.pythonhosted.org/packages/f5/a1/af87f64b9f986889884243643621ebbd4ac72472ba8ec8cec891ac8e2ca1/h5py-3.16.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:0f456f556e4e2cebeebd9d66adf8dc321770a42593494a0b6f0af54a7567b242", size = 5074061, upload-time = "2026-03-06T13:48:54.089Z" }, - { url = "https://files.pythonhosted.org/packages/cc/d0/146f5eaff3dc246a9c7f6e5e4f42bd45cc613bce16693bcd4d1f7c958bf5/h5py-3.16.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:3e6cb3387c756de6a9492d601553dffea3fe11b5f22b443aac708c69f3f55e16", size = 5279216, upload-time = "2026-03-06T13:48:56.75Z" }, - { url = "https://files.pythonhosted.org/packages/a1/9d/12a13424f1e604fc7df9497b73c0356fb78c2fb206abd7465ce47226e8fd/h5py-3.16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8389e13a1fd745ad2856873e8187fd10268b2d9677877bb667b41aebd771d8b7", size = 5070068, upload-time = "2026-03-06T13:48:59.169Z" }, - { url = "https://files.pythonhosted.org/packages/41/8c/bbe98f813722b4873818a8db3e15aa3e625b59278566905ac439725e8070/h5py-3.16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:346df559a0f7dcb31cf8e44805319e2ab24b8957c45e7708ce503b2ec79ba725", size = 5300253, upload-time = "2026-03-06T13:49:02.033Z" }, - { url = "https://files.pythonhosted.org/packages/32/9e/87e6705b4d6890e7cecdf876e2a7d3e40654a2ae37482d79a6f1b87f7b92/h5py-3.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4c6ab014ab704b4feaa719ae783b86522ed0bf1f82184704ed3c9e4e3228796e", size = 3381671, upload-time = "2026-03-06T13:49:04.351Z" }, - { url = "https://files.pythonhosted.org/packages/96/91/9fad90cfc5f9b2489c7c26ad897157bce82f0e9534a986a221b99760b23b/h5py-3.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:faca8fb4e4319c09d83337adc80b2ca7d5c5a343c2d6f1b6388f32cfecca13c1", size = 2740706, upload-time = "2026-03-06T13:49:06.347Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/4d/6a/0d79de0b025aa85dc8864de8e97659c94cf3d23148394a954dc5ca52f8c8/h5py-3.15.1.tar.gz", hash = "sha256:c86e3ed45c4473564de55aa83b6fc9e5ead86578773dfbd93047380042e26b69", size = 426236, upload-time = "2025-10-16T10:35:27.404Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/fd/8349b48b15b47768042cff06ad6e1c229f0a4bd89225bf6b6894fea27e6d/h5py-3.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5aaa330bcbf2830150c50897ea5dcbed30b5b6d56897289846ac5b9e529ec243", size = 3434135, upload-time = "2025-10-16T10:33:47.954Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b0/1c628e26a0b95858f54aba17e1599e7f6cd241727596cc2580b72cb0a9bf/h5py-3.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c970fb80001fffabb0109eaf95116c8e7c0d3ca2de854e0901e8a04c1f098509", size = 2870958, upload-time = "2025-10-16T10:33:50.907Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e3/c255cafc9b85e6ea04e2ad1bba1416baa1d7f57fc98a214be1144087690c/h5py-3.15.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80e5bb5b9508d5d9da09f81fd00abbb3f85da8143e56b1585d59bc8ceb1dba8b", size = 4504770, upload-time = "2025-10-16T10:33:54.357Z" }, + { url = "https://files.pythonhosted.org/packages/8b/23/4ab1108e87851ccc69694b03b817d92e142966a6c4abd99e17db77f2c066/h5py-3.15.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b849ba619a066196169763c33f9f0f02e381156d61c03e000bb0100f9950faf", size = 4700329, upload-time = "2025-10-16T10:33:57.616Z" }, + { url = "https://files.pythonhosted.org/packages/a4/e4/932a3a8516e4e475b90969bf250b1924dbe3612a02b897e426613aed68f4/h5py-3.15.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e7f6c841efd4e6e5b7e82222eaf90819927b6d256ab0f3aca29675601f654f3c", size = 4152456, upload-time = "2025-10-16T10:34:00.843Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0a/f74d589883b13737021b2049ac796328f188dbb60c2ed35b101f5b95a3fc/h5py-3.15.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ca8a3a22458956ee7b40d8e39c9a9dc01f82933e4c030c964f8b875592f4d831", size = 4617295, upload-time = "2025-10-16T10:34:04.154Z" }, + { url = "https://files.pythonhosted.org/packages/23/95/499b4e56452ef8b6c95a271af0dde08dac4ddb70515a75f346d4f400579b/h5py-3.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:550e51131376889656feec4aff2170efc054a7fe79eb1da3bb92e1625d1ac878", size = 2882129, upload-time = "2025-10-16T10:34:06.886Z" }, + { url = "https://files.pythonhosted.org/packages/ce/bb/cfcc70b8a42222ba3ad4478bcef1791181ea908e2adbd7d53c66395edad5/h5py-3.15.1-cp311-cp311-win_arm64.whl", hash = "sha256:b39239947cb36a819147fc19e86b618dcb0953d1cd969f5ed71fc0de60392427", size = 2477121, upload-time = "2025-10-16T10:34:09.579Z" }, + { url = "https://files.pythonhosted.org/packages/62/b8/c0d9aa013ecfa8b7057946c080c0c07f6fa41e231d2e9bd306a2f8110bdc/h5py-3.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:316dd0f119734f324ca7ed10b5627a2de4ea42cc4dfbcedbee026aaa361c238c", size = 3399089, upload-time = "2025-10-16T10:34:12.135Z" }, + { url = "https://files.pythonhosted.org/packages/a4/5e/3c6f6e0430813c7aefe784d00c6711166f46225f5d229546eb53032c3707/h5py-3.15.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b51469890e58e85d5242e43aab29f5e9c7e526b951caab354f3ded4ac88e7b76", size = 2847803, upload-time = "2025-10-16T10:34:14.564Z" }, + { url = "https://files.pythonhosted.org/packages/00/69/ba36273b888a4a48d78f9268d2aee05787e4438557450a8442946ab8f3ec/h5py-3.15.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a33bfd5dfcea037196f7778534b1ff7e36a7f40a89e648c8f2967292eb6898e", size = 4914884, upload-time = "2025-10-16T10:34:18.452Z" }, + { url = "https://files.pythonhosted.org/packages/3a/30/d1c94066343a98bb2cea40120873193a4fed68c4ad7f8935c11caf74c681/h5py-3.15.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25c8843fec43b2cc368aa15afa1cdf83fc5e17b1c4e10cd3771ef6c39b72e5ce", size = 5109965, upload-time = "2025-10-16T10:34:21.853Z" }, + { url = "https://files.pythonhosted.org/packages/81/3d/d28172116eafc3bc9f5991b3cb3fd2c8a95f5984f50880adfdf991de9087/h5py-3.15.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a308fd8681a864c04423c0324527237a0484e2611e3441f8089fd00ed56a8171", size = 4561870, upload-time = "2025-10-16T10:34:26.69Z" }, + { url = "https://files.pythonhosted.org/packages/a5/83/393a7226024238b0f51965a7156004eaae1fcf84aa4bfecf7e582676271b/h5py-3.15.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f4a016df3f4a8a14d573b496e4d1964deb380e26031fc85fb40e417e9131888a", size = 5037161, upload-time = "2025-10-16T10:34:30.383Z" }, + { url = "https://files.pythonhosted.org/packages/cf/51/329e7436bf87ca6b0fe06dd0a3795c34bebe4ed8d6c44450a20565d57832/h5py-3.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:59b25cf02411bf12e14f803fef0b80886444c7fe21a5ad17c6a28d3f08098a1e", size = 2874165, upload-time = "2025-10-16T10:34:33.461Z" }, + { url = "https://files.pythonhosted.org/packages/09/a8/2d02b10a66747c54446e932171dd89b8b4126c0111b440e6bc05a7c852ec/h5py-3.15.1-cp312-cp312-win_arm64.whl", hash = "sha256:61d5a58a9851e01ee61c932bbbb1c98fe20aba0a5674776600fb9a361c0aa652", size = 2458214, upload-time = "2025-10-16T10:34:35.733Z" }, + { url = "https://files.pythonhosted.org/packages/88/b3/40207e0192415cbff7ea1d37b9f24b33f6d38a5a2f5d18a678de78f967ae/h5py-3.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8440fd8bee9500c235ecb7aa1917a0389a2adb80c209fa1cc485bd70e0d94a5", size = 3376511, upload-time = "2025-10-16T10:34:38.596Z" }, + { url = "https://files.pythonhosted.org/packages/31/96/ba99a003c763998035b0de4c299598125df5fc6c9ccf834f152ddd60e0fb/h5py-3.15.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ab2219dbc6fcdb6932f76b548e2b16f34a1f52b7666e998157a4dfc02e2c4123", size = 2826143, upload-time = "2025-10-16T10:34:41.342Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c2/fc6375d07ea3962df7afad7d863fe4bde18bb88530678c20d4c90c18de1d/h5py-3.15.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8cb02c3a96255149ed3ac811eeea25b655d959c6dd5ce702c9a95ff11859eb5", size = 4908316, upload-time = "2025-10-16T10:34:44.619Z" }, + { url = "https://files.pythonhosted.org/packages/d9/69/4402ea66272dacc10b298cca18ed73e1c0791ff2ae9ed218d3859f9698ac/h5py-3.15.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:121b2b7a4c1915d63737483b7bff14ef253020f617c2fb2811f67a4bed9ac5e8", size = 5103710, upload-time = "2025-10-16T10:34:48.639Z" }, + { url = "https://files.pythonhosted.org/packages/e0/f6/11f1e2432d57d71322c02a97a5567829a75f223a8c821764a0e71a65cde8/h5py-3.15.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59b0d63b318bf3cc06687def2b45afd75926bbc006f7b8cd2b1a231299fc8599", size = 4556042, upload-time = "2025-10-16T10:34:51.841Z" }, + { url = "https://files.pythonhosted.org/packages/18/88/3eda3ef16bfe7a7dbc3d8d6836bbaa7986feb5ff091395e140dc13927bcc/h5py-3.15.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e02fe77a03f652500d8bff288cbf3675f742fc0411f5a628fa37116507dc7cc0", size = 5030639, upload-time = "2025-10-16T10:34:55.257Z" }, + { url = "https://files.pythonhosted.org/packages/e5/ea/fbb258a98863f99befb10ed727152b4ae659f322e1d9c0576f8a62754e81/h5py-3.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:dea78b092fd80a083563ed79a3171258d4a4d307492e7cf8b2313d464c82ba52", size = 2864363, upload-time = "2025-10-16T10:34:58.099Z" }, + { url = "https://files.pythonhosted.org/packages/5d/c9/35021cc9cd2b2915a7da3026e3d77a05bed1144a414ff840953b33937fb9/h5py-3.15.1-cp313-cp313-win_arm64.whl", hash = "sha256:c256254a8a81e2bddc0d376e23e2a6d2dc8a1e8a2261835ed8c1281a0744cd97", size = 2449570, upload-time = "2025-10-16T10:35:00.473Z" }, + { url = "https://files.pythonhosted.org/packages/a0/2c/926eba1514e4d2e47d0e9eb16c784e717d8b066398ccfca9b283917b1bfb/h5py-3.15.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5f4fb0567eb8517c3ecd6b3c02c4f4e9da220c8932604960fd04e24ee1254763", size = 3380368, upload-time = "2025-10-16T10:35:03.117Z" }, + { url = "https://files.pythonhosted.org/packages/65/4b/d715ed454d3baa5f6ae1d30b7eca4c7a1c1084f6a2edead9e801a1541d62/h5py-3.15.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:954e480433e82d3872503104f9b285d369048c3a788b2b1a00e53d1c47c98dd2", size = 2833793, upload-time = "2025-10-16T10:35:05.623Z" }, + { url = "https://files.pythonhosted.org/packages/ef/d4/ef386c28e4579314610a8bffebbee3b69295b0237bc967340b7c653c6c10/h5py-3.15.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd125c131889ebbef0849f4a0e29cf363b48aba42f228d08b4079913b576bb3a", size = 4903199, upload-time = "2025-10-16T10:35:08.972Z" }, + { url = "https://files.pythonhosted.org/packages/33/5d/65c619e195e0b5e54ea5a95c1bb600c8ff8715e0d09676e4cce56d89f492/h5py-3.15.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28a20e1a4082a479b3d7db2169f3a5034af010b90842e75ebbf2e9e49eb4183e", size = 5097224, upload-time = "2025-10-16T10:35:12.808Z" }, + { url = "https://files.pythonhosted.org/packages/30/30/5273218400bf2da01609e1292f562c94b461fcb73c7a9e27fdadd43abc0a/h5py-3.15.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa8df5267f545b4946df8ca0d93d23382191018e4cda2deda4c2cedf9a010e13", size = 4551207, upload-time = "2025-10-16T10:35:16.24Z" }, + { url = "https://files.pythonhosted.org/packages/d3/39/a7ef948ddf4d1c556b0b2b9559534777bccc318543b3f5a1efdf6b556c9c/h5py-3.15.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99d374a21f7321a4c6ab327c4ab23bd925ad69821aeb53a1e75dd809d19f67fa", size = 5025426, upload-time = "2025-10-16T10:35:19.831Z" }, + { url = "https://files.pythonhosted.org/packages/b6/d8/7368679b8df6925b8415f9dcc9ab1dab01ddc384d2b2c24aac9191bd9ceb/h5py-3.15.1-cp314-cp314-win_amd64.whl", hash = "sha256:9c73d1d7cdb97d5b17ae385153472ce118bed607e43be11e9a9deefaa54e0734", size = 2865704, upload-time = "2025-10-16T10:35:22.658Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b7/4a806f85d62c20157e62e58e03b27513dc9c55499768530acc4f4c5ce4be/h5py-3.15.1-cp314-cp314-win_arm64.whl", hash = "sha256:a6d8c5a05a76aca9a494b4c53ce8a9c29023b7f64f625c6ce1841e92a362ccdf", size = 2465544, upload-time = "2025-10-16T10:35:25.695Z" }, ] [[package]] @@ -1086,13 +1053,13 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "h5py" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/4f/9130151e3aa475b3e4e9a611bf608107fe5c72d277d74c4cf36f164b7c81/hdf5plugin-6.0.0.tar.gz", hash = "sha256:847ed9e96b451367a110f0ba64a3b260d38d64bbf3f25751858d3b56e094cfe0", size = 66372085 } +sdist = { url = "https://files.pythonhosted.org/packages/a1/4f/9130151e3aa475b3e4e9a611bf608107fe5c72d277d74c4cf36f164b7c81/hdf5plugin-6.0.0.tar.gz", hash = "sha256:847ed9e96b451367a110f0ba64a3b260d38d64bbf3f25751858d3b56e094cfe0", size = 66372085, upload-time = "2025-10-08T18:16:28.423Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/13/15017f6210bfea843316d62f0f121e364e17bb129444ed803a256a213036/hdf5plugin-6.0.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:a59fbd5d4290a8a5334d82ccb4c6b9bfc7aaf586de7fedb88762e8601bc05fd4", size = 13339413 }, - { url = "https://files.pythonhosted.org/packages/40/bf/d1f3765fb879820d7331e30e860b684f5b78d3ec17324e8f54130cbe560b/hdf5plugin-6.0.0-py3-none-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d301f4b9295872bacf277c70628d4c5e965ee47db762d8fde2d4849f201b9897", size = 42858563 }, - { url = "https://files.pythonhosted.org/packages/0a/67/37d0b84fbbf26bf0d6a99a8f98bcd82bb6d437dc8cabee259fb3d7506ec7/hdf5plugin-6.0.0-py3-none-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:78b082ea355fe46bf5b396024de1fb662a1aaf9a5e11861ad61a5a2a6316d59d", size = 45126124 }, - { url = "https://files.pythonhosted.org/packages/ed/2f/1046d464ad1db29a4f6c70ba4e19b39baa8a6542c719eaa4e765108f07f1/hdf5plugin-6.0.0-py3-none-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:79e0524d18ddc41c0cf2e1bb2e529d4e154c286f6a1bd85f3d44019d2a17574a", size = 44857273 }, - { url = "https://files.pythonhosted.org/packages/61/b3/75478bdfee85533777de4204373f563aa7a1074355300743c3aedc33cac5/hdf5plugin-6.0.0-py3-none-win_amd64.whl", hash = "sha256:99866f90be1ceac5519e6e038669564be326c233618d59ba1f38c9dd8c32099e", size = 3379316 }, + { url = "https://files.pythonhosted.org/packages/9c/13/15017f6210bfea843316d62f0f121e364e17bb129444ed803a256a213036/hdf5plugin-6.0.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:a59fbd5d4290a8a5334d82ccb4c6b9bfc7aaf586de7fedb88762e8601bc05fd4", size = 13339413, upload-time = "2025-10-08T18:16:10.656Z" }, + { url = "https://files.pythonhosted.org/packages/40/bf/d1f3765fb879820d7331e30e860b684f5b78d3ec17324e8f54130cbe560b/hdf5plugin-6.0.0-py3-none-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d301f4b9295872bacf277c70628d4c5e965ee47db762d8fde2d4849f201b9897", size = 42858563, upload-time = "2025-10-08T18:16:14.106Z" }, + { url = "https://files.pythonhosted.org/packages/0a/67/37d0b84fbbf26bf0d6a99a8f98bcd82bb6d437dc8cabee259fb3d7506ec7/hdf5plugin-6.0.0-py3-none-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:78b082ea355fe46bf5b396024de1fb662a1aaf9a5e11861ad61a5a2a6316d59d", size = 45126124, upload-time = "2025-10-08T18:16:17.992Z" }, + { url = "https://files.pythonhosted.org/packages/ed/2f/1046d464ad1db29a4f6c70ba4e19b39baa8a6542c719eaa4e765108f07f1/hdf5plugin-6.0.0-py3-none-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:79e0524d18ddc41c0cf2e1bb2e529d4e154c286f6a1bd85f3d44019d2a17574a", size = 44857273, upload-time = "2025-10-08T18:16:22.007Z" }, + { url = "https://files.pythonhosted.org/packages/61/b3/75478bdfee85533777de4204373f563aa7a1074355300743c3aedc33cac5/hdf5plugin-6.0.0-py3-none-win_amd64.whl", hash = "sha256:99866f90be1ceac5519e6e038669564be326c233618d59ba1f38c9dd8c32099e", size = 3379316, upload-time = "2025-10-08T18:16:25.007Z" }, ] [[package]] @@ -1103,9 +1070,9 @@ dependencies = [ { name = "certifi" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484 } +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784 }, + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] [[package]] @@ -1118,61 +1085,61 @@ dependencies = [ { name = "httpcore" }, { name = "idna" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, ] [[package]] name = "identify" -version = "2.6.18" +version = "2.6.17" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/46/c4/7fb4db12296cdb11893d61c92048fe617ee853f8523b9b296ac03b43757e/identify-2.6.18.tar.gz", hash = "sha256:873ac56a5e3fd63e7438a7ecbc4d91aca692eb3fefa4534db2b7913f3fc352fd", size = 99580, upload-time = "2026-03-15T18:39:50.319Z" } +sdist = { url = "https://files.pythonhosted.org/packages/57/84/376a3b96e5a8d33a7aa2c5b3b31a4b3c364117184bf0b17418055f6ace66/identify-2.6.17.tar.gz", hash = "sha256:f816b0b596b204c9fdf076ded172322f2723cf958d02f9c3587504834c8ff04d", size = 99579, upload-time = "2026-03-01T20:04:12.702Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl", hash = "sha256:8db9d3c8ea9079db92cafb0ebf97abdc09d52e97f4dcf773a2e694048b7cd737", size = 99394, upload-time = "2026-03-15T18:39:48.915Z" }, + { url = "https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl", hash = "sha256:be5f8412d5ed4b20f2bd41a65f920990bdccaa6a4a18a08f1eefdcd0bdd885f0", size = 99382, upload-time = "2026-03-01T20:04:11.439Z" }, ] [[package]] name = "idna" version = "3.11" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582 } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008 }, + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] [[package]] name = "imageio" -version = "2.37.3" +version = "2.37.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, { name = "pillow" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/84/93bcd1300216ea50811cee96873b84a1bebf8d0489ffaf7f2a3756bab866/imageio-2.37.3.tar.gz", hash = "sha256:bbb37efbfc4c400fcd534b367b91fcd66d5da639aaa138034431a1c5e0a41451", size = 389673, upload-time = "2026-03-09T11:31:12.573Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/6f/606be632e37bf8d05b253e8626c2291d74c691ddc7bcdf7d6aaf33b32f6a/imageio-2.37.2.tar.gz", hash = "sha256:0212ef2727ac9caa5ca4b2c75ae89454312f440a756fcfc8ef1993e718f50f8a", size = 389600, upload-time = "2025-11-04T14:29:39.898Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/49/fa/391e437a34e55095173dca5f24070d89cbc233ff85bf1c29c93248c6588d/imageio-2.37.3-py3-none-any.whl", hash = "sha256:46f5bb8522cd421c0f5ae104d8268f569d856b29eb1a13b92829d1970f32c9f0", size = 317646, upload-time = "2026-03-09T11:31:10.771Z" }, + { url = "https://files.pythonhosted.org/packages/fb/fe/301e0936b79bcab4cacc7548bf2853fc28dced0a578bab1f7ef53c9aa75b/imageio-2.37.2-py3-none-any.whl", hash = "sha256:ad9adfb20335d718c03de457358ed69f141021a333c40a53e57273d8a5bd0b9b", size = 317646, upload-time = "2025-11-04T14:29:37.948Z" }, ] [[package]] name = "importlib-metadata" -version = "9.0.0" +version = "8.7.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "zipp", marker = "python_full_version < '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a9/01/15bb152d77b21318514a96f43af312635eb2500c96b55398d020c93d86ea/importlib_metadata-9.0.0.tar.gz", hash = "sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc", size = 56405, upload-time = "2026-03-20T06:42:56.999Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/3d/2d244233ac4f76e38533cfcb2991c9eb4c7bf688ae0a036d30725b8faafe/importlib_metadata-9.0.0-py3-none-any.whl", hash = "sha256:2d21d1cc5a017bd0559e36150c21c830ab1dc304dedd1b7ea85d20f45ef3edd7", size = 27789, upload-time = "2026-03-20T06:42:55.665Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, ] [[package]] name = "iniconfig" version = "2.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503 } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484 }, + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] [[package]] @@ -1183,8 +1150,7 @@ dependencies = [ { name = "appnope", marker = "sys_platform == 'darwin'" }, { name = "comm" }, { name = "debugpy" }, - { name = "ipython", version = "9.10.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "ipython", version = "9.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "ipython" }, { name = "jupyter-client" }, { name = "jupyter-core" }, { name = "matplotlib-inline" }, @@ -1195,59 +1161,31 @@ dependencies = [ { name = "tornado" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ca/8d/b68b728e2d06b9e0051019640a40a9eb7a88fcd82c2e1b5ce70bef5ff044/ipykernel-7.2.0.tar.gz", hash = "sha256:18ed160b6dee2cbb16e5f3575858bc19d8f1fe6046a9a680c708494ce31d909e", size = 176046 } +sdist = { url = "https://files.pythonhosted.org/packages/ca/8d/b68b728e2d06b9e0051019640a40a9eb7a88fcd82c2e1b5ce70bef5ff044/ipykernel-7.2.0.tar.gz", hash = "sha256:18ed160b6dee2cbb16e5f3575858bc19d8f1fe6046a9a680c708494ce31d909e", size = 176046, upload-time = "2026-02-06T16:43:27.403Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl", hash = "sha256:3bbd4420d2b3cc105cbdf3756bfc04500b1e52f090a90716851f3916c62e1661", size = 118788 }, + { url = "https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl", hash = "sha256:3bbd4420d2b3cc105cbdf3756bfc04500b1e52f090a90716851f3916c62e1661", size = 118788, upload-time = "2026-02-06T16:43:25.149Z" }, ] [[package]] name = "ipython" version = "9.10.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.12'", -] dependencies = [ - { name = "colorama", marker = "python_full_version < '3.12' and sys_platform == 'win32'" }, - { name = "decorator", marker = "python_full_version < '3.12'" }, - { name = "ipython-pygments-lexers", marker = "python_full_version < '3.12'" }, - { name = "jedi", marker = "python_full_version < '3.12'" }, - { name = "matplotlib-inline", marker = "python_full_version < '3.12'" }, - { name = "pexpect", marker = "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit", marker = "python_full_version < '3.12'" }, - { name = "pygments", marker = "python_full_version < '3.12'" }, - { name = "stack-data", marker = "python_full_version < '3.12'" }, - { name = "traitlets", marker = "python_full_version < '3.12'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "ipython-pygments-lexers" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, { name = "typing-extensions", marker = "python_full_version < '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a6/60/2111715ea11f39b1535bed6024b7dec7918b71e5e5d30855a5b503056b50/ipython-9.10.0.tar.gz", hash = "sha256:cd9e656be97618a0676d058134cd44e6dc7012c0e5cb36a9ce96a8c904adaf77", size = 4426526 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/aa/898dec789a05731cd5a9f50605b7b44a72bd198fd0d4528e11fc610177cc/ipython-9.10.0-py3-none-any.whl", hash = "sha256:c6ab68cc23bba8c7e18e9b932797014cc61ea7fd6f19de180ab9ba73e65ee58d", size = 622774 }, -] - -[[package]] -name = "ipython" -version = "9.11.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.12' and python_full_version < '3.14'", -] -dependencies = [ - { name = "colorama", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" }, - { name = "decorator", marker = "python_full_version >= '3.12'" }, - { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.12'" }, - { name = "jedi", marker = "python_full_version >= '3.12'" }, - { name = "matplotlib-inline", marker = "python_full_version >= '3.12'" }, - { name = "pexpect", marker = "python_full_version >= '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit", marker = "python_full_version >= '3.12'" }, - { name = "pygments", marker = "python_full_version >= '3.12'" }, - { name = "stack-data", marker = "python_full_version >= '3.12'" }, - { name = "traitlets", marker = "python_full_version >= '3.12'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/86/28/a4698eda5a8928a45d6b693578b135b753e14fa1c2b36ee9441e69a45576/ipython-9.11.0.tar.gz", hash = "sha256:2a94bc4406b22ecc7e4cb95b98450f3ea493a76bec8896cda11b78d7752a6667", size = 4427354, upload-time = "2026-03-05T08:57:30.549Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/60/2111715ea11f39b1535bed6024b7dec7918b71e5e5d30855a5b503056b50/ipython-9.10.0.tar.gz", hash = "sha256:cd9e656be97618a0676d058134cd44e6dc7012c0e5cb36a9ce96a8c904adaf77", size = 4426526, upload-time = "2026-02-02T10:00:33.594Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl", hash = "sha256:6922d5bcf944c6e525a76a0a304451b60a2b6f875e86656d8bc2dfda5d710e19", size = 624222, upload-time = "2026-03-05T08:57:28.94Z" }, + { url = "https://files.pythonhosted.org/packages/3d/aa/898dec789a05731cd5a9f50605b7b44a72bd198fd0d4528e11fc610177cc/ipython-9.10.0-py3-none-any.whl", hash = "sha256:c6ab68cc23bba8c7e18e9b932797014cc61ea7fd6f19de180ab9ba73e65ee58d", size = 622774, upload-time = "2026-02-02T10:00:31.503Z" }, ] [[package]] @@ -1257,9 +1195,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393 } +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074 }, + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, ] [[package]] @@ -1268,15 +1206,14 @@ version = "8.1.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "comm" }, - { name = "ipython", version = "9.10.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "ipython", version = "9.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "ipython" }, { name = "jupyterlab-widgets" }, { name = "traitlets" }, { name = "widgetsnbextension" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/ae/c5ce1edc1afe042eadb445e95b0671b03cee61895264357956e61c0d2ac0/ipywidgets-8.1.8.tar.gz", hash = "sha256:61f969306b95f85fba6b6986b7fe45d73124d1d9e3023a8068710d47a22ea668", size = 116739 } +sdist = { url = "https://files.pythonhosted.org/packages/4c/ae/c5ce1edc1afe042eadb445e95b0671b03cee61895264357956e61c0d2ac0/ipywidgets-8.1.8.tar.gz", hash = "sha256:61f969306b95f85fba6b6986b7fe45d73124d1d9e3023a8068710d47a22ea668", size = 116739, upload-time = "2025-11-01T21:18:12.393Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl", hash = "sha256:ecaca67aed704a338f88f67b1181b58f821ab5dc89c1f0f5ef99db43c1c2921e", size = 139808 }, + { url = "https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl", hash = "sha256:ecaca67aed704a338f88f67b1181b58f821ab5dc89c1f0f5ef99db43c1c2921e", size = 139808, upload-time = "2025-11-01T21:18:10.956Z" }, ] [[package]] @@ -1286,9 +1223,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "arrow" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7c/1a/3c8edc664e06e6bd06cce40c6b22da5f1429aa4224d0c590f3be21c91ead/isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", size = 11649 } +sdist = { url = "https://files.pythonhosted.org/packages/7c/1a/3c8edc664e06e6bd06cce40c6b22da5f1429aa4224d0c590f3be21c91ead/isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", size = 11649, upload-time = "2020-11-01T11:00:00.312Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321 }, + { url = "https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321, upload-time = "2020-11-01T10:59:58.02Z" }, ] [[package]] @@ -1298,9 +1235,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "parso" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287 } +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278 }, + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, ] [[package]] @@ -1310,27 +1247,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] [[package]] name = "json5" version = "0.13.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/77/e8/a3f261a66e4663f22700bc8a17c08cb83e91fbf086726e7a228398968981/json5-0.13.0.tar.gz", hash = "sha256:b1edf8d487721c0bf64d83c28e91280781f6e21f4a797d3261c7c828d4c165bf", size = 52441 } +sdist = { url = "https://files.pythonhosted.org/packages/77/e8/a3f261a66e4663f22700bc8a17c08cb83e91fbf086726e7a228398968981/json5-0.13.0.tar.gz", hash = "sha256:b1edf8d487721c0bf64d83c28e91280781f6e21f4a797d3261c7c828d4c165bf", size = 52441, upload-time = "2026-01-01T19:42:14.99Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl", hash = "sha256:9a08e1dd65f6a4d4c6fa82d216cf2477349ec2346a38fd70cc11d2557499fbcc", size = 36163 }, + { url = "https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl", hash = "sha256:9a08e1dd65f6a4d4c6fa82d216cf2477349ec2346a38fd70cc11d2557499fbcc", size = 36163, upload-time = "2026-01-01T19:42:13.962Z" }, ] [[package]] name = "jsonpointer" -version = "3.1.0" +version = "3.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/48/bf/9ecc036fbc15cf4153ea6ed4dbeed31ef043f762cccc9d44a534be8319b0/jsonpointer-3.1.0.tar.gz", hash = "sha256:f9b39abd59ba8c1de8a4ff16141605d2a8dacc4dd6cf399672cf237bfe47c211", size = 9000, upload-time = "2026-03-20T21:47:09.982Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114, upload-time = "2024-06-10T19:24:42.462Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/25/cebb241a435cbf4626b5ea096d8385c04416d7ca3082a15299b746e248fa/jsonpointer-3.1.0-py3-none-any.whl", hash = "sha256:f82aa0f745001f169b96473348370b43c3f581446889c41c807bab1db11c8e7b", size = 7651, upload-time = "2026-03-20T21:47:08.792Z" }, + { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" }, ] [[package]] @@ -1343,9 +1280,9 @@ dependencies = [ { name = "referencing" }, { name = "rpds-py" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583 } +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630 }, + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, ] [package.optional-dependencies] @@ -1368,9 +1305,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "referencing" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855 } +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437 }, + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, ] [[package]] @@ -1384,9 +1321,9 @@ dependencies = [ { name = "tornado" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/05/e4/ba649102a3bc3fbca54e7239fb924fd434c766f855693d86de0b1f2bec81/jupyter_client-8.8.0.tar.gz", hash = "sha256:d556811419a4f2d96c869af34e854e3f059b7cc2d6d01a9cd9c85c267691be3e", size = 348020 } +sdist = { url = "https://files.pythonhosted.org/packages/05/e4/ba649102a3bc3fbca54e7239fb924fd434c766f855693d86de0b1f2bec81/jupyter_client-8.8.0.tar.gz", hash = "sha256:d556811419a4f2d96c869af34e854e3f059b7cc2d6d01a9cd9c85c267691be3e", size = 348020, upload-time = "2026-01-08T13:55:47.938Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl", hash = "sha256:f93a5b99c5e23a507b773d3a1136bd6e16c67883ccdbd9a829b0bbdb98cd7d7a", size = 107371 }, + { url = "https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl", hash = "sha256:f93a5b99c5e23a507b773d3a1136bd6e16c67883ccdbd9a829b0bbdb98cd7d7a", size = 107371, upload-time = "2026-01-08T13:55:45.562Z" }, ] [[package]] @@ -1397,9 +1334,9 @@ dependencies = [ { name = "platformdirs" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/02/49/9d1284d0dc65e2c757b74c6687b6d319b02f822ad039e5c512df9194d9dd/jupyter_core-5.9.1.tar.gz", hash = "sha256:4d09aaff303b9566c3ce657f580bd089ff5c91f5f89cf7d8846c3cdf465b5508", size = 89814 } +sdist = { url = "https://files.pythonhosted.org/packages/02/49/9d1284d0dc65e2c757b74c6687b6d319b02f822ad039e5c512df9194d9dd/jupyter_core-5.9.1.tar.gz", hash = "sha256:4d09aaff303b9566c3ce657f580bd089ff5c91f5f89cf7d8846c3cdf465b5508", size = 89814, upload-time = "2025-10-16T19:19:18.444Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl", hash = "sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407", size = 29032 }, + { url = "https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl", hash = "sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407", size = 29032, upload-time = "2025-10-16T19:19:16.783Z" }, ] [[package]] @@ -1416,9 +1353,9 @@ dependencies = [ { name = "rfc3986-validator" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9d/c3/306d090461e4cf3cd91eceaff84bede12a8e52cd821c2d20c9a4fd728385/jupyter_events-0.12.0.tar.gz", hash = "sha256:fc3fce98865f6784c9cd0a56a20644fc6098f21c8c33834a8d9fe383c17e554b", size = 62196 } +sdist = { url = "https://files.pythonhosted.org/packages/9d/c3/306d090461e4cf3cd91eceaff84bede12a8e52cd821c2d20c9a4fd728385/jupyter_events-0.12.0.tar.gz", hash = "sha256:fc3fce98865f6784c9cd0a56a20644fc6098f21c8c33834a8d9fe383c17e554b", size = 62196, upload-time = "2025-02-03T17:23:41.485Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl", hash = "sha256:6464b2fa5ad10451c3d35fabc75eab39556ae1e2853ad0c0cc31b656731a97fb", size = 19430 }, + { url = "https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl", hash = "sha256:6464b2fa5ad10451c3d35fabc75eab39556ae1e2853ad0c0cc31b656731a97fb", size = 19430, upload-time = "2025-02-03T17:23:38.643Z" }, ] [[package]] @@ -1428,9 +1365,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jupyter-server" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/eb/5a/9066c9f8e94ee517133cd98dba393459a16cd48bba71a82f16a65415206c/jupyter_lsp-2.3.0.tar.gz", hash = "sha256:458aa59339dc868fb784d73364f17dbce8836e906cd75fd471a325cba02e0245", size = 54823 } +sdist = { url = "https://files.pythonhosted.org/packages/eb/5a/9066c9f8e94ee517133cd98dba393459a16cd48bba71a82f16a65415206c/jupyter_lsp-2.3.0.tar.gz", hash = "sha256:458aa59339dc868fb784d73364f17dbce8836e906cd75fd471a325cba02e0245", size = 54823, upload-time = "2025-08-27T17:47:34.671Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl", hash = "sha256:e914a3cb2addf48b1c7710914771aaf1819d46b2e5a79b0f917b5478ec93f34f", size = 76687 }, + { url = "https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl", hash = "sha256:e914a3cb2addf48b1c7710914771aaf1819d46b2e5a79b0f917b5478ec93f34f", size = 76687, upload-time = "2025-08-27T17:47:33.15Z" }, ] [[package]] @@ -1458,9 +1395,9 @@ dependencies = [ { name = "traitlets" }, { name = "websocket-client" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5b/ac/e040ec363d7b6b1f11304cc9f209dac4517ece5d5e01821366b924a64a50/jupyter_server-2.17.0.tar.gz", hash = "sha256:c38ea898566964c888b4772ae1ed58eca84592e88251d2cfc4d171f81f7e99d5", size = 731949 } +sdist = { url = "https://files.pythonhosted.org/packages/5b/ac/e040ec363d7b6b1f11304cc9f209dac4517ece5d5e01821366b924a64a50/jupyter_server-2.17.0.tar.gz", hash = "sha256:c38ea898566964c888b4772ae1ed58eca84592e88251d2cfc4d171f81f7e99d5", size = 731949, upload-time = "2025-08-21T14:42:54.042Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl", hash = "sha256:e8cb9c7db4251f51ed307e329b81b72ccf2056ff82d50524debde1ee1870e13f", size = 388221 }, + { url = "https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl", hash = "sha256:e8cb9c7db4251f51ed307e329b81b72ccf2056ff82d50524debde1ee1870e13f", size = 388221, upload-time = "2025-08-21T14:42:52.034Z" }, ] [[package]] @@ -1471,14 +1408,14 @@ dependencies = [ { name = "pywinpty", marker = "os_name == 'nt'" }, { name = "terminado" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f4/a7/bcd0a9b0cbba88986fe944aaaf91bfda603e5a50bda8ed15123f381a3b2f/jupyter_server_terminals-0.5.4.tar.gz", hash = "sha256:bbda128ed41d0be9020349f9f1f2a4ab9952a73ed5f5ac9f1419794761fb87f5", size = 31770 } +sdist = { url = "https://files.pythonhosted.org/packages/f4/a7/bcd0a9b0cbba88986fe944aaaf91bfda603e5a50bda8ed15123f381a3b2f/jupyter_server_terminals-0.5.4.tar.gz", hash = "sha256:bbda128ed41d0be9020349f9f1f2a4ab9952a73ed5f5ac9f1419794761fb87f5", size = 31770, upload-time = "2026-01-14T16:53:20.213Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl", hash = "sha256:55be353fc74a80bc7f3b20e6be50a55a61cd525626f578dcb66a5708e2007d14", size = 13704 }, + { url = "https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl", hash = "sha256:55be353fc74a80bc7f3b20e6be50a55a61cd525626f578dcb66a5708e2007d14", size = 13704, upload-time = "2026-01-14T16:53:18.738Z" }, ] [[package]] name = "jupyterlab" -version = "4.5.6" +version = "4.5.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "async-lru" }, @@ -1495,18 +1432,18 @@ dependencies = [ { name = "tornado" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ac/d5/730628e03fff2e8a8e8ccdaedde1489ab1309f9a4fa2536248884e30b7c7/jupyterlab-4.5.6.tar.gz", hash = "sha256:642fe2cfe7f0f5922a8a558ba7a0d246c7bc133b708dfe43f7b3a826d163cf42", size = 23970670, upload-time = "2026-03-11T14:17:04.531Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/2d/953a5612a34a3c799a62566a548e711d103f631672fd49650e0f2de80870/jupyterlab-4.5.5.tar.gz", hash = "sha256:eac620698c59eb810e1729909be418d9373d18137cac66637141abba613b3fda", size = 23968441, upload-time = "2026-02-23T18:57:34.339Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl", hash = "sha256:d6b3dac883aa4d9993348e0f8e95b24624f75099aed64eab6a4351a9cdd1e580", size = 12447124, upload-time = "2026-03-11T14:17:00.229Z" }, + { url = "https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl", hash = "sha256:a35694a40a8e7f2e82f387472af24e61b22adcce87b5a8ab97a5d9c486202a6d", size = 12446824, upload-time = "2026-02-23T18:57:30.398Z" }, ] [[package]] name = "jupyterlab-pygments" version = "0.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/90/51/9187be60d989df97f5f0aba133fa54e7300f17616e065d1ada7d7646b6d6/jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d", size = 512900 } +sdist = { url = "https://files.pythonhosted.org/packages/90/51/9187be60d989df97f5f0aba133fa54e7300f17616e065d1ada7d7646b6d6/jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d", size = 512900, upload-time = "2023-11-23T09:26:37.44Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884 }, + { url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884, upload-time = "2023-11-23T09:26:34.325Z" }, ] [[package]] @@ -1522,145 +1459,129 @@ dependencies = [ { name = "packaging" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d6/2c/90153f189e421e93c4bb4f9e3f59802a1f01abd2ac5cf40b152d7f735232/jupyterlab_server-2.28.0.tar.gz", hash = "sha256:35baa81898b15f93573e2deca50d11ac0ae407ebb688299d3a5213265033712c", size = 76996 } +sdist = { url = "https://files.pythonhosted.org/packages/d6/2c/90153f189e421e93c4bb4f9e3f59802a1f01abd2ac5cf40b152d7f735232/jupyterlab_server-2.28.0.tar.gz", hash = "sha256:35baa81898b15f93573e2deca50d11ac0ae407ebb688299d3a5213265033712c", size = 76996, upload-time = "2025-10-22T13:59:18.37Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl", hash = "sha256:e4355b148fdcf34d312bbbc80f22467d6d20460e8b8736bf235577dd18506968", size = 59830 }, + { url = "https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl", hash = "sha256:e4355b148fdcf34d312bbbc80f22467d6d20460e8b8736bf235577dd18506968", size = 59830, upload-time = "2025-10-22T13:59:16.767Z" }, ] [[package]] name = "jupyterlab-widgets" version = "3.0.16" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/26/2d/ef58fed122b268c69c0aa099da20bc67657cdfb2e222688d5731bd5b971d/jupyterlab_widgets-3.0.16.tar.gz", hash = "sha256:423da05071d55cf27a9e602216d35a3a65a3e41cdf9c5d3b643b814ce38c19e0", size = 897423 } +sdist = { url = "https://files.pythonhosted.org/packages/26/2d/ef58fed122b268c69c0aa099da20bc67657cdfb2e222688d5731bd5b971d/jupyterlab_widgets-3.0.16.tar.gz", hash = "sha256:423da05071d55cf27a9e602216d35a3a65a3e41cdf9c5d3b643b814ce38c19e0", size = 897423, upload-time = "2025-11-01T21:11:29.724Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl", hash = "sha256:45fa36d9c6422cf2559198e4db481aa243c7a32d9926b500781c830c80f7ecf8", size = 914926 }, + { url = "https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl", hash = "sha256:45fa36d9c6422cf2559198e4db481aa243c7a32d9926b500781c830c80f7ecf8", size = 914926, upload-time = "2025-11-01T21:11:28.008Z" }, ] [[package]] name = "kiwisolver" -version = "1.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/67/9c61eccb13f0bdca9307614e782fec49ffdde0f7a2314935d489fa93cd9c/kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a", size = 103482, upload-time = "2026-03-09T13:15:53.382Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/12/dd/a495a9c104be1c476f0386e714252caf2b7eca883915422a64c50b88c6f5/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9eed0f7edbb274413b6ee781cca50541c8c0facd3d6fd289779e494340a2b85c", size = 122798, upload-time = "2026-03-09T13:12:58.963Z" }, - { url = "https://files.pythonhosted.org/packages/11/60/37b4047a2af0cf5ef6d8b4b26e91829ae6fc6a2d1f74524bcb0e7cd28a32/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c4923e404d6bcd91b6779c009542e5647fef32e4a5d75e115e3bbac6f2335eb", size = 66216, upload-time = "2026-03-09T13:13:00.155Z" }, - { url = "https://files.pythonhosted.org/packages/0a/aa/510dc933d87767584abfe03efa445889996c70c2990f6f87c3ebaa0a18c5/kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0df54df7e686afa55e6f21fb86195224a6d9beb71d637e8d7920c95cf0f89aac", size = 63911, upload-time = "2026-03-09T13:13:01.671Z" }, - { url = "https://files.pythonhosted.org/packages/80/46/bddc13df6c2a40741e0cc7865bb1c9ed4796b6760bd04ce5fae3928ef917/kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2517e24d7315eb51c10664cdb865195df38ab74456c677df67bb47f12d088a27", size = 1438209, upload-time = "2026-03-09T13:13:03.385Z" }, - { url = "https://files.pythonhosted.org/packages/fd/d6/76621246f5165e5372f02f5e6f3f48ea336a8f9e96e43997d45b240ed8cd/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff710414307fefa903e0d9bdf300972f892c23477829f49504e59834f4195398", size = 1248888, upload-time = "2026-03-09T13:13:05.231Z" }, - { url = "https://files.pythonhosted.org/packages/b2/c1/31559ec6fb39a5b48035ce29bb63ade628f321785f38c384dee3e2c08bc1/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6176c1811d9d5a04fa391c490cc44f451e240697a16977f11c6f722efb9041db", size = 1266304, upload-time = "2026-03-09T13:13:06.743Z" }, - { url = "https://files.pythonhosted.org/packages/5e/ef/1cb8276f2d29cc6a41e0a042f27946ca347d3a4a75acf85d0a16aa6dcc82/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50847dca5d197fcbd389c805aa1a1cf32f25d2e7273dc47ab181a517666b68cc", size = 1319650, upload-time = "2026-03-09T13:13:08.607Z" }, - { url = "https://files.pythonhosted.org/packages/4c/e4/5ba3cecd7ce6236ae4a80f67e5d5531287337d0e1f076ca87a5abe4cd5d0/kiwisolver-1.5.0-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:01808c6d15f4c3e8559595d6d1fe6411c68e4a3822b4b9972b44473b24f4e679", size = 970949, upload-time = "2026-03-09T13:13:10.299Z" }, - { url = "https://files.pythonhosted.org/packages/5a/69/dc61f7ae9a2f071f26004ced87f078235b5507ab6e5acd78f40365655034/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1f9f4121ec58628c96baa3de1a55a4e3a333c5102c8e94b64e23bf7b2083309", size = 2199125, upload-time = "2026-03-09T13:13:11.841Z" }, - { url = "https://files.pythonhosted.org/packages/e5/7b/abbe0f1b5afa85f8d084b73e90e5f801c0939eba16ac2e49af7c61a6c28d/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b7d335370ae48a780c6e6a6bbfa97342f563744c39c35562f3f367665f5c1de2", size = 2293783, upload-time = "2026-03-09T13:13:14.399Z" }, - { url = "https://files.pythonhosted.org/packages/8a/80/5908ae149d96d81580d604c7f8aefd0e98f4fd728cf172f477e9f2a81744/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:800ee55980c18545af444d93fdd60c56b580db5cc54867d8cbf8a1dc0829938c", size = 1960726, upload-time = "2026-03-09T13:13:16.047Z" }, - { url = "https://files.pythonhosted.org/packages/84/08/a78cb776f8c085b7143142ce479859cfec086bd09ee638a317040b6ef420/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c438f6ca858697c9ab67eb28246c92508af972e114cac34e57a6d4ba17a3ac08", size = 2464738, upload-time = "2026-03-09T13:13:17.897Z" }, - { url = "https://files.pythonhosted.org/packages/b1/e1/65584da5356ed6cb12c63791a10b208860ac40a83de165cb6a6751a686e3/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8c63c91f95173f9c2a67c7c526b2cea976828a0e7fced9cdcead2802dc10f8a4", size = 2270718, upload-time = "2026-03-09T13:13:19.421Z" }, - { url = "https://files.pythonhosted.org/packages/be/6c/28f17390b62b8f2f520e2915095b3c94d88681ecf0041e75389d9667f202/kiwisolver-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:beb7f344487cdcb9e1efe4b7a29681b74d34c08f0043a327a74da852a6749e7b", size = 73480, upload-time = "2026-03-09T13:13:20.818Z" }, - { url = "https://files.pythonhosted.org/packages/d8/0e/2ee5debc4f77a625778fec5501ff3e8036fe361b7ee28ae402a485bb9694/kiwisolver-1.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:ad4ae4ffd1ee9cd11357b4c66b612da9888f4f4daf2f36995eda64bd45370cac", size = 64930, upload-time = "2026-03-09T13:13:21.997Z" }, - { url = "https://files.pythonhosted.org/packages/4d/b2/818b74ebea34dabe6d0c51cb1c572e046730e64844da6ed646d5298c40ce/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9", size = 123158, upload-time = "2026-03-09T13:13:23.127Z" }, - { url = "https://files.pythonhosted.org/packages/bf/d9/405320f8077e8e1c5c4bd6adc45e1e6edf6d727b6da7f2e2533cf58bff71/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588", size = 66388, upload-time = "2026-03-09T13:13:24.765Z" }, - { url = "https://files.pythonhosted.org/packages/99/9f/795fedf35634f746151ca8839d05681ceb6287fbed6cc1c9bf235f7887c2/kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819", size = 64068, upload-time = "2026-03-09T13:13:25.878Z" }, - { url = "https://files.pythonhosted.org/packages/c4/13/680c54afe3e65767bed7ec1a15571e1a2f1257128733851ade24abcefbcc/kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f", size = 1477934, upload-time = "2026-03-09T13:13:27.166Z" }, - { url = "https://files.pythonhosted.org/packages/c8/2f/cebfcdb60fd6a9b0f6b47a9337198bcbad6fbe15e68189b7011fd914911f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf", size = 1278537, upload-time = "2026-03-09T13:13:28.707Z" }, - { url = "https://files.pythonhosted.org/packages/f2/0d/9b782923aada3fafb1d6b84e13121954515c669b18af0c26e7d21f579855/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d", size = 1296685, upload-time = "2026-03-09T13:13:30.528Z" }, - { url = "https://files.pythonhosted.org/packages/27/70/83241b6634b04fe44e892688d5208332bde130f38e610c0418f9ede47ded/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083", size = 1346024, upload-time = "2026-03-09T13:13:32.818Z" }, - { url = "https://files.pythonhosted.org/packages/e4/db/30ed226fb271ae1a6431fc0fe0edffb2efe23cadb01e798caeb9f2ceae8f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6", size = 987241, upload-time = "2026-03-09T13:13:34.435Z" }, - { url = "https://files.pythonhosted.org/packages/ec/bd/c314595208e4c9587652d50959ead9e461995389664e490f4dce7ff0f782/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1", size = 2227742, upload-time = "2026-03-09T13:13:36.4Z" }, - { url = "https://files.pythonhosted.org/packages/c1/43/0499cec932d935229b5543d073c2b87c9c22846aab48881e9d8d6e742a2d/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0", size = 2323966, upload-time = "2026-03-09T13:13:38.204Z" }, - { url = "https://files.pythonhosted.org/packages/3d/6f/79b0d760907965acfd9d61826a3d41f8f093c538f55cd2633d3f0db269f6/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15", size = 1977417, upload-time = "2026-03-09T13:13:39.966Z" }, - { url = "https://files.pythonhosted.org/packages/ab/31/01d0537c41cb75a551a438c3c7a80d0c60d60b81f694dac83dd436aec0d0/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314", size = 2491238, upload-time = "2026-03-09T13:13:41.698Z" }, - { url = "https://files.pythonhosted.org/packages/e4/34/8aefdd0be9cfd00a44509251ba864f5caf2991e36772e61c408007e7f417/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9", size = 2294947, upload-time = "2026-03-09T13:13:43.343Z" }, - { url = "https://files.pythonhosted.org/packages/ad/cf/0348374369ca588f8fe9c338fae49fa4e16eeb10ffb3d012f23a54578a9e/kiwisolver-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384", size = 73569, upload-time = "2026-03-09T13:13:45.792Z" }, - { url = "https://files.pythonhosted.org/packages/28/26/192b26196e2316e2bd29deef67e37cdf9870d9af8e085e521afff0fed526/kiwisolver-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7", size = 64997, upload-time = "2026-03-09T13:13:46.878Z" }, - { url = "https://files.pythonhosted.org/packages/9d/69/024d6711d5ba575aa65d5538042e99964104e97fa153a9f10bc369182bc2/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fd40bb9cd0891c4c3cb1ddf83f8bbfa15731a248fdc8162669405451e2724b09", size = 123166, upload-time = "2026-03-09T13:13:48.032Z" }, - { url = "https://files.pythonhosted.org/packages/ce/48/adbb40df306f587054a348831220812b9b1d787aff714cfbc8556e38fccd/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0e1403fd7c26d77c1f03e096dc58a5c726503fa0db0456678b8668f76f521e3", size = 66395, upload-time = "2026-03-09T13:13:49.365Z" }, - { url = "https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd", size = 64065, upload-time = "2026-03-09T13:13:50.562Z" }, - { url = "https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3", size = 1477903, upload-time = "2026-03-09T13:13:52.084Z" }, - { url = "https://files.pythonhosted.org/packages/18/d8/55638d89ffd27799d5cc3d8aa28e12f4ce7a64d67b285114dbedc8ea4136/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c50b89ffd3e1a911c69a1dd3de7173c0cd10b130f56222e57898683841e4f96", size = 1278751, upload-time = "2026-03-09T13:13:54.673Z" }, - { url = "https://files.pythonhosted.org/packages/b8/97/b4c8d0d18421ecceba20ad8701358453b88e32414e6f6950b5a4bad54e65/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4db576bb8c3ef9365f8b40fe0f671644de6736ae2c27a2c62d7d8a1b4329f099", size = 1296793, upload-time = "2026-03-09T13:13:56.287Z" }, - { url = "https://files.pythonhosted.org/packages/c4/10/f862f94b6389d8957448ec9df59450b81bec4abb318805375c401a1e6892/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b85aad90cea8ac6797a53b5d5f2e967334fa4d1149f031c4537569972596cb8", size = 1346041, upload-time = "2026-03-09T13:13:58.269Z" }, - { url = "https://files.pythonhosted.org/packages/a3/6a/f1650af35821eaf09de398ec0bc2aefc8f211f0cda50204c9f1673741ba9/kiwisolver-1.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:d36ca54cb4c6c4686f7cbb7b817f66f5911c12ddb519450bbe86707155028f87", size = 987292, upload-time = "2026-03-09T13:13:59.871Z" }, - { url = "https://files.pythonhosted.org/packages/de/19/d7fb82984b9238115fe629c915007be608ebd23dc8629703d917dbfaffd4/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:38f4a703656f493b0ad185211ccfca7f0386120f022066b018eb5296d8613e23", size = 2227865, upload-time = "2026-03-09T13:14:01.401Z" }, - { url = "https://files.pythonhosted.org/packages/7f/b9/46b7f386589fd222dac9e9de9c956ce5bcefe2ee73b4e79891381dda8654/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ac2360e93cb41be81121755c6462cff3beaa9967188c866e5fce5cf13170859", size = 2324369, upload-time = "2026-03-09T13:14:02.972Z" }, - { url = "https://files.pythonhosted.org/packages/92/8b/95e237cf3d9c642960153c769ddcbe278f182c8affb20cecc1cc983e7cc5/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c95cab08d1965db3d84a121f1c7ce7479bdd4072c9b3dafd8fecce48a2e6b902", size = 1977989, upload-time = "2026-03-09T13:14:04.503Z" }, - { url = "https://files.pythonhosted.org/packages/1b/95/980c9df53501892784997820136c01f62bc1865e31b82b9560f980c0e649/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc20894c3d21194d8041a28b65622d5b86db786da6e3cfe73f0c762951a61167", size = 2491645, upload-time = "2026-03-09T13:14:06.106Z" }, - { url = "https://files.pythonhosted.org/packages/cb/32/900647fd0840abebe1561792c6b31e6a7c0e278fc3973d30572a965ca14c/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a32f72973f0f950c1920475d5c5ea3d971b81b6f0ec53b8d0a956cc965f22e0", size = 2295237, upload-time = "2026-03-09T13:14:08.891Z" }, - { url = "https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276", size = 73573, upload-time = "2026-03-09T13:14:12.327Z" }, - { url = "https://files.pythonhosted.org/packages/4d/d2/64be2e429eb4fca7f7e1c52a91b12663aeaf25de3895e5cca0f47ef2a8d0/kiwisolver-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa8eb9ecdb7efb0b226acec134e0d709e87a909fa4971a54c0c4f6e88635484c", size = 64998, upload-time = "2026-03-09T13:14:13.469Z" }, - { url = "https://files.pythonhosted.org/packages/b0/69/ce68dd0c85755ae2de490bf015b62f2cea5f6b14ff00a463f9d0774449ff/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db485b3847d182b908b483b2ed133c66d88d49cacf98fd278fadafe11b4478d1", size = 125700, upload-time = "2026-03-09T13:14:14.636Z" }, - { url = "https://files.pythonhosted.org/packages/74/aa/937aac021cf9d4349990d47eb319309a51355ed1dbdc9c077cdc9224cb11/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:be12f931839a3bdfe28b584db0e640a65a8bcbc24560ae3fdb025a449b3d754e", size = 67537, upload-time = "2026-03-09T13:14:15.808Z" }, - { url = "https://files.pythonhosted.org/packages/ee/20/3a87fbece2c40ad0f6f0aefa93542559159c5f99831d596050e8afae7a9f/kiwisolver-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:16b85d37c2cbb3253226d26e64663f755d88a03439a9c47df6246b35defbdfb7", size = 65514, upload-time = "2026-03-09T13:14:18.035Z" }, - { url = "https://files.pythonhosted.org/packages/f0/7f/f943879cda9007c45e1f7dba216d705c3a18d6b35830e488b6c6a4e7cdf0/kiwisolver-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4432b835675f0ea7414aab3d37d119f7226d24869b7a829caeab49ebda407b0c", size = 1584848, upload-time = "2026-03-09T13:14:19.745Z" }, - { url = "https://files.pythonhosted.org/packages/37/f8/4d4f85cc1870c127c88d950913370dd76138482161cd07eabbc450deff01/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b0feb50971481a2cc44d94e88bdb02cdd497618252ae226b8eb1201b957e368", size = 1391542, upload-time = "2026-03-09T13:14:21.54Z" }, - { url = "https://files.pythonhosted.org/packages/04/0b/65dd2916c84d252b244bd405303220f729e7c17c9d7d33dca6feeff9ffc4/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56fa888f10d0f367155e76ce849fa1166fc9730d13bd2d65a2aa13b6f5424489", size = 1404447, upload-time = "2026-03-09T13:14:23.205Z" }, - { url = "https://files.pythonhosted.org/packages/39/5c/2606a373247babce9b1d056c03a04b65f3cf5290a8eac5d7bdead0a17e21/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:940dda65d5e764406b9fb92761cbf462e4e63f712ab60ed98f70552e496f3bf1", size = 1455918, upload-time = "2026-03-09T13:14:24.74Z" }, - { url = "https://files.pythonhosted.org/packages/d5/d1/c6078b5756670658e9192a2ef11e939c92918833d2745f85cd14a6004bdf/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:89fc958c702ee9a745e4700378f5d23fddbc46ff89e8fdbf5395c24d5c1452a3", size = 1072856, upload-time = "2026-03-09T13:14:26.597Z" }, - { url = "https://files.pythonhosted.org/packages/cb/c8/7def6ddf16eb2b3741d8b172bdaa9af882b03c78e9b0772975408801fa63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9027d773c4ff81487181a925945743413f6069634d0b122d0b37684ccf4f1e18", size = 2333580, upload-time = "2026-03-09T13:14:28.237Z" }, - { url = "https://files.pythonhosted.org/packages/9e/87/2ac1fce0eb1e616fcd3c35caa23e665e9b1948bb984f4764790924594128/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5b233ea3e165e43e35dba1d2b8ecc21cf070b45b65ae17dd2747d2713d942021", size = 2423018, upload-time = "2026-03-09T13:14:30.018Z" }, - { url = "https://files.pythonhosted.org/packages/67/13/c6700ccc6cc218716bfcda4935e4b2997039869b4ad8a94f364c5a3b8e63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ce9bf03dad3b46408c08649c6fbd6ca28a9fce0eb32fdfffa6775a13103b5310", size = 2062804, upload-time = "2026-03-09T13:14:32.888Z" }, - { url = "https://files.pythonhosted.org/packages/1b/bd/877056304626943ff0f1f44c08f584300c199b887cb3176cd7e34f1515f1/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:fc4d3f1fb9ca0ae9f97b095963bc6326f1dbfd3779d6679a1e016b9baaa153d3", size = 2597482, upload-time = "2026-03-09T13:14:34.971Z" }, - { url = "https://files.pythonhosted.org/packages/75/19/c60626c47bf0f8ac5dcf72c6c98e266d714f2fbbfd50cf6dab5ede3aaa50/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f443b4825c50a51ee68585522ab4a1d1257fac65896f282b4c6763337ac9f5d2", size = 2394328, upload-time = "2026-03-09T13:14:36.816Z" }, - { url = "https://files.pythonhosted.org/packages/47/84/6a6d5e5bb8273756c27b7d810d47f7ef2f1f9b9fd23c9ee9a3f8c75c9cef/kiwisolver-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:893ff3a711d1b515ba9da14ee090519bad4610ed1962fbe298a434e8c5f8db53", size = 68410, upload-time = "2026-03-09T13:14:38.695Z" }, - { url = "https://files.pythonhosted.org/packages/e4/d7/060f45052f2a01ad5762c8fdecd6d7a752b43400dc29ff75cd47225a40fd/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8df31fe574b8b3993cc61764f40941111b25c2d9fea13d3ce24a49907cd2d615", size = 123231, upload-time = "2026-03-09T13:14:41.323Z" }, - { url = "https://files.pythonhosted.org/packages/c2/a7/78da680eadd06ff35edef6ef68a1ad273bad3e2a0936c9a885103230aece/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1d49a49ac4cbfb7c1375301cd1ec90169dfeae55ff84710d782260ce77a75a02", size = 66489, upload-time = "2026-03-09T13:14:42.534Z" }, - { url = "https://files.pythonhosted.org/packages/49/b2/97980f3ad4fae37dd7fe31626e2bf75fbf8bdf5d303950ec1fab39a12da8/kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0cbe94b69b819209a62cb27bdfa5dc2a8977d8de2f89dfd97ba4f53ed3af754e", size = 64063, upload-time = "2026-03-09T13:14:44.759Z" }, - { url = "https://files.pythonhosted.org/packages/e7/f9/b06c934a6aa8bc91f566bd2a214fd04c30506c2d9e2b6b171953216a65b6/kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80aa065ffd378ff784822a6d7c3212f2d5f5e9c3589614b5c228b311fd3063ac", size = 1475913, upload-time = "2026-03-09T13:14:46.247Z" }, - { url = "https://files.pythonhosted.org/packages/6b/f0/f768ae564a710135630672981231320bc403cf9152b5596ec5289de0f106/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e7f886f47ab881692f278ae901039a234e4025a68e6dfab514263a0b1c4ae05", size = 1282782, upload-time = "2026-03-09T13:14:48.458Z" }, - { url = "https://files.pythonhosted.org/packages/e2/9f/1de7aad00697325f05238a5f2eafbd487fb637cc27a558b5367a5f37fb7f/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5060731cc3ed12ca3a8b57acd4aeca5bbc2f49216dd0bec1650a1acd89486bcd", size = 1300815, upload-time = "2026-03-09T13:14:50.721Z" }, - { url = "https://files.pythonhosted.org/packages/5a/c2/297f25141d2e468e0ce7f7a7b92e0cf8918143a0cbd3422c1ad627e85a06/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a4aa69609f40fce3cbc3f87b2061f042eee32f94b8f11db707b66a26461591a", size = 1347925, upload-time = "2026-03-09T13:14:52.304Z" }, - { url = "https://files.pythonhosted.org/packages/b9/d3/f4c73a02eb41520c47610207b21afa8cdd18fdbf64ffd94674ae21c4812d/kiwisolver-1.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:d168fda2dbff7b9b5f38e693182d792a938c31db4dac3a80a4888de603c99554", size = 991322, upload-time = "2026-03-09T13:14:54.637Z" }, - { url = "https://files.pythonhosted.org/packages/7b/46/d3f2efef7732fcda98d22bf4ad5d3d71d545167a852ca710a494f4c15343/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:413b820229730d358efd838ecbab79902fe97094565fdc80ddb6b0a18c18a581", size = 2232857, upload-time = "2026-03-09T13:14:56.471Z" }, - { url = "https://files.pythonhosted.org/packages/3f/ec/2d9756bf2b6d26ae4349b8d3662fb3993f16d80c1f971c179ce862b9dbae/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5124d1ea754509b09e53738ec185584cc609aae4a3b510aaf4ed6aa047ef9303", size = 2329376, upload-time = "2026-03-09T13:14:58.072Z" }, - { url = "https://files.pythonhosted.org/packages/8f/9f/876a0a0f2260f1bde92e002b3019a5fabc35e0939c7d945e0fa66185eb20/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e4415a8db000bf49a6dd1c478bf70062eaacff0f462b92b0ba68791a905861f9", size = 1982549, upload-time = "2026-03-09T13:14:59.668Z" }, - { url = "https://files.pythonhosted.org/packages/6c/4f/ba3624dfac23a64d54ac4179832860cb537c1b0af06024936e82ca4154a0/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d618fd27420381a4f6044faa71f46d8bfd911bd077c555f7138ed88729bfbe79", size = 2494680, upload-time = "2026-03-09T13:15:01.364Z" }, - { url = "https://files.pythonhosted.org/packages/39/b7/97716b190ab98911b20d10bf92eca469121ec483b8ce0edd314f51bc85af/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5092eb5b1172947f57d6ea7d89b2f29650414e4293c47707eb499ec07a0ac796", size = 2297905, upload-time = "2026-03-09T13:15:03.925Z" }, - { url = "https://files.pythonhosted.org/packages/a3/36/4e551e8aa55c9188bca9abb5096805edbf7431072b76e2298e34fd3a3008/kiwisolver-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e", size = 75086, upload-time = "2026-03-09T13:15:07.775Z" }, - { url = "https://files.pythonhosted.org/packages/70/15/9b90f7df0e31a003c71649cf66ef61c3c1b862f48c81007fa2383c8bd8d7/kiwisolver-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df", size = 66577, upload-time = "2026-03-09T13:15:09.139Z" }, - { url = "https://files.pythonhosted.org/packages/17/01/7dc8c5443ff42b38e72731643ed7cf1ed9bf01691ae5cdca98501999ed83/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d1ffeb80b5676463d7a7d56acbe8e37a20ce725570e09549fe738e02ca6b7e1e", size = 125794, upload-time = "2026-03-09T13:15:10.525Z" }, - { url = "https://files.pythonhosted.org/packages/46/8a/b4ebe46ebaac6a303417fab10c2e165c557ddaff558f9699d302b256bc53/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc4d8e252f532ab46a1de9349e2d27b91fce46736a9eedaa37beaca66f574ed4", size = 67646, upload-time = "2026-03-09T13:15:12.016Z" }, - { url = "https://files.pythonhosted.org/packages/60/35/10a844afc5f19d6f567359bf4789e26661755a2f36200d5d1ed8ad0126e5/kiwisolver-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6783e069732715ad0c3ce96dbf21dbc2235ab0593f2baf6338101f70371f4028", size = 65511, upload-time = "2026-03-09T13:15:13.311Z" }, - { url = "https://files.pythonhosted.org/packages/f8/8a/685b297052dd041dcebce8e8787b58923b6e78acc6115a0dc9189011c44b/kiwisolver-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7c4c09a490dc4d4a7f8cbee56c606a320f9dc28cf92a7157a39d1ce7676a657", size = 1584858, upload-time = "2026-03-09T13:15:15.103Z" }, - { url = "https://files.pythonhosted.org/packages/9e/80/04865e3d4638ac5bddec28908916df4a3075b8c6cc101786a96803188b96/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a075bd7bd19c70cf67c8badfa36cf7c5d8de3c9ddb8420c51e10d9c50e94920", size = 1392539, upload-time = "2026-03-09T13:15:16.661Z" }, - { url = "https://files.pythonhosted.org/packages/ba/01/77a19cacc0893fa13fafa46d1bba06fb4dc2360b3292baf4b56d8e067b24/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bdd3e53429ff02aa319ba59dfe4ceeec345bf46cf180ec2cf6fd5b942e7975e9", size = 1405310, upload-time = "2026-03-09T13:15:18.229Z" }, - { url = "https://files.pythonhosted.org/packages/53/39/bcaf5d0cca50e604cfa9b4e3ae1d64b50ca1ae5b754122396084599ef903/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cdcb35dc9d807259c981a85531048ede628eabcffb3239adf3d17463518992d", size = 1456244, upload-time = "2026-03-09T13:15:20.444Z" }, - { url = "https://files.pythonhosted.org/packages/d0/7a/72c187abc6975f6978c3e39b7cf67aeb8b3c0a8f9790aa7fd412855e9e1f/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:70d593af6a6ca332d1df73d519fddb5148edb15cd90d5f0155e3746a6d4fcc65", size = 1073154, upload-time = "2026-03-09T13:15:22.039Z" }, - { url = "https://files.pythonhosted.org/packages/c7/ca/cf5b25783ebbd59143b4371ed0c8428a278abe68d6d0104b01865b1bbd0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:377815a8616074cabbf3f53354e1d040c35815a134e01d7614b7692e4bf8acfa", size = 2334377, upload-time = "2026-03-09T13:15:23.741Z" }, - { url = "https://files.pythonhosted.org/packages/4a/e5/b1f492adc516796e88751282276745340e2a72dcd0d36cf7173e0daf3210/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0255a027391d52944eae1dbb5d4cc5903f57092f3674e8e544cdd2622826b3f0", size = 2425288, upload-time = "2026-03-09T13:15:25.789Z" }, - { url = "https://files.pythonhosted.org/packages/e6/e5/9b21fbe91a61b8f409d74a26498706e97a48008bfcd1864373d32a6ba31c/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9", size = 2063158, upload-time = "2026-03-09T13:15:27.63Z" }, - { url = "https://files.pythonhosted.org/packages/b1/02/83f47986138310f95ea95531f851b2a62227c11cbc3e690ae1374fe49f0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e3aafb33aed7479377e5e9a82e9d4bf87063741fc99fc7ae48b0f16e32bdd6f", size = 2597260, upload-time = "2026-03-09T13:15:29.421Z" }, - { url = "https://files.pythonhosted.org/packages/07/18/43a5f24608d8c313dd189cf838c8e68d75b115567c6279de7796197cfb6a/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7a116ae737f0000343218c4edf5bd45893bfeaff0993c0b215d7124c9f77646", size = 2394403, upload-time = "2026-03-09T13:15:31.517Z" }, - { url = "https://files.pythonhosted.org/packages/3b/b5/98222136d839b8afabcaa943b09bd05888c2d36355b7e448550211d1fca4/kiwisolver-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681", size = 79687, upload-time = "2026-03-09T13:15:33.204Z" }, - { url = "https://files.pythonhosted.org/packages/99/a2/ca7dc962848040befed12732dff6acae7fb3c4f6fc4272b3f6c9a30b8713/kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57", size = 70032, upload-time = "2026-03-09T13:15:34.411Z" }, - { url = "https://files.pythonhosted.org/packages/1c/fa/2910df836372d8761bb6eff7d8bdcb1613b5c2e03f260efe7abe34d388a7/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797", size = 130262, upload-time = "2026-03-09T13:15:35.629Z" }, - { url = "https://files.pythonhosted.org/packages/0f/41/c5f71f9f00aabcc71fee8b7475e3f64747282580c2fe748961ba29b18385/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f6764a4ccab3078db14a632420930f6186058750df066b8ea2a7106df91d3203", size = 138036, upload-time = "2026-03-09T13:15:36.894Z" }, - { url = "https://files.pythonhosted.org/packages/fa/06/7399a607f434119c6e1fdc8ec89a8d51ccccadf3341dee4ead6bd14caaf5/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31c13da98624f957b0fb1b5bae5383b2333c2c3f6793d9825dd5ce79b525cb7", size = 194295, upload-time = "2026-03-09T13:15:38.22Z" }, - { url = "https://files.pythonhosted.org/packages/b5/91/53255615acd2a1eaca307ede3c90eb550bae9c94581f8c00081b6b1c8f44/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:1f1489f769582498610e015a8ef2d36f28f505ab3096d0e16b4858a9ec214f57", size = 75987, upload-time = "2026-03-09T13:15:39.65Z" }, - { url = "https://files.pythonhosted.org/packages/e9/eb/5fcbbbf9a0e2c3a35effb88831a483345326bbc3a030a3b5b69aee647f84/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ec4c85dc4b687c7f7f15f553ff26a98bfe8c58f5f7f0ac8905f0ba4c7be60232", size = 59532, upload-time = "2026-03-09T13:15:47.047Z" }, - { url = "https://files.pythonhosted.org/packages/c3/9b/e17104555bb4db148fd52327feea1e96be4b88e8e008b029002c281a21ab/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:12e91c215a96e39f57989c8912ae761286ac5a9584d04030ceb3368a357f017a", size = 57420, upload-time = "2026-03-09T13:15:48.199Z" }, - { url = "https://files.pythonhosted.org/packages/48/44/2b5b95b7aa39fb2d8d9d956e0f3d5d45aef2ae1d942d4c3ffac2f9cfed1a/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be4a51a55833dc29ab5d7503e7bcb3b3af3402d266018137127450005cdfe737", size = 79892, upload-time = "2026-03-09T13:15:49.694Z" }, - { url = "https://files.pythonhosted.org/packages/52/7d/7157f9bba6b455cfb4632ed411e199fc8b8977642c2b12082e1bd9e6d173/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daae526907e262de627d8f70058a0f64acc9e2641c164c99c8f594b34a799a16", size = 77603, upload-time = "2026-03-09T13:15:50.945Z" }, - { url = "https://files.pythonhosted.org/packages/0a/dd/8050c947d435c8d4bc94e3252f4d8bb8a76cfb424f043a8680be637a57f1/kiwisolver-1.5.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:59cd8683f575d96df5bb48f6add94afc055012c29e28124fcae2b63661b9efb1", size = 73558, upload-time = "2026-03-09T13:15:52.112Z" }, +version = "1.4.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/ab/c80b0d5a9d8a1a65f4f815f2afff9798b12c3b9f31f1d304dd233dd920e2/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16", size = 124167, upload-time = "2025-08-10T21:25:53.403Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c0/27fe1a68a39cf62472a300e2879ffc13c0538546c359b86f149cc19f6ac3/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089", size = 66579, upload-time = "2025-08-10T21:25:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/31/a2/a12a503ac1fd4943c50f9822678e8015a790a13b5490354c68afb8489814/kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543", size = 65309, upload-time = "2025-08-10T21:25:55.76Z" }, + { url = "https://files.pythonhosted.org/packages/66/e1/e533435c0be77c3f64040d68d7a657771194a63c279f55573188161e81ca/kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61", size = 1435596, upload-time = "2025-08-10T21:25:56.861Z" }, + { url = "https://files.pythonhosted.org/packages/67/1e/51b73c7347f9aabdc7215aa79e8b15299097dc2f8e67dee2b095faca9cb0/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1", size = 1246548, upload-time = "2025-08-10T21:25:58.246Z" }, + { url = "https://files.pythonhosted.org/packages/21/aa/72a1c5d1e430294f2d32adb9542719cfb441b5da368d09d268c7757af46c/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872", size = 1263618, upload-time = "2025-08-10T21:25:59.857Z" }, + { url = "https://files.pythonhosted.org/packages/a3/af/db1509a9e79dbf4c260ce0cfa3903ea8945f6240e9e59d1e4deb731b1a40/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26", size = 1317437, upload-time = "2025-08-10T21:26:01.105Z" }, + { url = "https://files.pythonhosted.org/packages/e0/f2/3ea5ee5d52abacdd12013a94130436e19969fa183faa1e7c7fbc89e9a42f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028", size = 2195742, upload-time = "2025-08-10T21:26:02.675Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9b/1efdd3013c2d9a2566aa6a337e9923a00590c516add9a1e89a768a3eb2fc/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771", size = 2290810, upload-time = "2025-08-10T21:26:04.009Z" }, + { url = "https://files.pythonhosted.org/packages/fb/e5/cfdc36109ae4e67361f9bc5b41323648cb24a01b9ade18784657e022e65f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a", size = 2461579, upload-time = "2025-08-10T21:26:05.317Z" }, + { url = "https://files.pythonhosted.org/packages/62/86/b589e5e86c7610842213994cdea5add00960076bef4ae290c5fa68589cac/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464", size = 2268071, upload-time = "2025-08-10T21:26:06.686Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c6/f8df8509fd1eee6c622febe54384a96cfaf4d43bf2ccec7a0cc17e4715c9/kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2", size = 73840, upload-time = "2025-08-10T21:26:07.94Z" }, + { url = "https://files.pythonhosted.org/packages/e2/2d/16e0581daafd147bc11ac53f032a2b45eabac897f42a338d0a13c1e5c436/kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7", size = 65159, upload-time = "2025-08-10T21:26:09.048Z" }, + { url = "https://files.pythonhosted.org/packages/86/c9/13573a747838aeb1c76e3267620daa054f4152444d1f3d1a2324b78255b5/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", size = 123686, upload-time = "2025-08-10T21:26:10.034Z" }, + { url = "https://files.pythonhosted.org/packages/51/ea/2ecf727927f103ffd1739271ca19c424d0e65ea473fbaeea1c014aea93f6/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2", size = 66460, upload-time = "2025-08-10T21:26:11.083Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/51f5464373ce2aeb5194508298a508b6f21d3867f499556263c64c621914/kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14", size = 64952, upload-time = "2025-08-10T21:26:12.058Z" }, + { url = "https://files.pythonhosted.org/packages/70/90/6d240beb0f24b74371762873e9b7f499f1e02166a2d9c5801f4dbf8fa12e/kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04", size = 1474756, upload-time = "2025-08-10T21:26:13.096Z" }, + { url = "https://files.pythonhosted.org/packages/12/42/f36816eaf465220f683fb711efdd1bbf7a7005a2473d0e4ed421389bd26c/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752", size = 1276404, upload-time = "2025-08-10T21:26:14.457Z" }, + { url = "https://files.pythonhosted.org/packages/2e/64/bc2de94800adc830c476dce44e9b40fd0809cddeef1fde9fcf0f73da301f/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77", size = 1294410, upload-time = "2025-08-10T21:26:15.73Z" }, + { url = "https://files.pythonhosted.org/packages/5f/42/2dc82330a70aa8e55b6d395b11018045e58d0bb00834502bf11509f79091/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198", size = 1343631, upload-time = "2025-08-10T21:26:17.045Z" }, + { url = "https://files.pythonhosted.org/packages/22/fd/f4c67a6ed1aab149ec5a8a401c323cee7a1cbe364381bb6c9c0d564e0e20/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d", size = 2224963, upload-time = "2025-08-10T21:26:18.737Z" }, + { url = "https://files.pythonhosted.org/packages/45/aa/76720bd4cb3713314677d9ec94dcc21ced3f1baf4830adde5bb9b2430a5f/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab", size = 2321295, upload-time = "2025-08-10T21:26:20.11Z" }, + { url = "https://files.pythonhosted.org/packages/80/19/d3ec0d9ab711242f56ae0dc2fc5d70e298bb4a1f9dfab44c027668c673a1/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2", size = 2487987, upload-time = "2025-08-10T21:26:21.49Z" }, + { url = "https://files.pythonhosted.org/packages/39/e9/61e4813b2c97e86b6fdbd4dd824bf72d28bcd8d4849b8084a357bc0dd64d/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145", size = 2291817, upload-time = "2025-08-10T21:26:22.812Z" }, + { url = "https://files.pythonhosted.org/packages/a0/41/85d82b0291db7504da3c2defe35c9a8a5c9803a730f297bd823d11d5fb77/kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54", size = 73895, upload-time = "2025-08-10T21:26:24.37Z" }, + { url = "https://files.pythonhosted.org/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992, upload-time = "2025-08-10T21:26:25.732Z" }, + { url = "https://files.pythonhosted.org/packages/31/c1/c2686cda909742ab66c7388e9a1a8521a59eb89f8bcfbee28fc980d07e24/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8", size = 123681, upload-time = "2025-08-10T21:26:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2", size = 66464, upload-time = "2025-08-10T21:26:27.733Z" }, + { url = "https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f", size = 64961, upload-time = "2025-08-10T21:26:28.729Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098", size = 1474607, upload-time = "2025-08-10T21:26:29.798Z" }, + { url = "https://files.pythonhosted.org/packages/d9/28/aac26d4c882f14de59041636292bc838db8961373825df23b8eeb807e198/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed", size = 1276546, upload-time = "2025-08-10T21:26:31.401Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ad/8bfc1c93d4cc565e5069162f610ba2f48ff39b7de4b5b8d93f69f30c4bed/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525", size = 1294482, upload-time = "2025-08-10T21:26:32.721Z" }, + { url = "https://files.pythonhosted.org/packages/da/f1/6aca55ff798901d8ce403206d00e033191f63d82dd708a186e0ed2067e9c/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78", size = 1343720, upload-time = "2025-08-10T21:26:34.032Z" }, + { url = "https://files.pythonhosted.org/packages/d1/91/eed031876c595c81d90d0f6fc681ece250e14bf6998c3d7c419466b523b7/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b", size = 2224907, upload-time = "2025-08-10T21:26:35.824Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ec/4d1925f2e49617b9cca9c34bfa11adefad49d00db038e692a559454dfb2e/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799", size = 2321334, upload-time = "2025-08-10T21:26:37.534Z" }, + { url = "https://files.pythonhosted.org/packages/43/cb/450cd4499356f68802750c6ddc18647b8ea01ffa28f50d20598e0befe6e9/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3", size = 2488313, upload-time = "2025-08-10T21:26:39.191Z" }, + { url = "https://files.pythonhosted.org/packages/71/67/fc76242bd99f885651128a5d4fa6083e5524694b7c88b489b1b55fdc491d/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c", size = 2291970, upload-time = "2025-08-10T21:26:40.828Z" }, + { url = "https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d", size = 73894, upload-time = "2025-08-10T21:26:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/95/38/dce480814d25b99a391abbddadc78f7c117c6da34be68ca8b02d5848b424/kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2", size = 64995, upload-time = "2025-08-10T21:26:43.889Z" }, + { url = "https://files.pythonhosted.org/packages/e2/37/7d218ce5d92dadc5ebdd9070d903e0c7cf7edfe03f179433ac4d13ce659c/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1", size = 126510, upload-time = "2025-08-10T21:26:44.915Z" }, + { url = "https://files.pythonhosted.org/packages/23/b0/e85a2b48233daef4b648fb657ebbb6f8367696a2d9548a00b4ee0eb67803/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1", size = 67903, upload-time = "2025-08-10T21:26:45.934Z" }, + { url = "https://files.pythonhosted.org/packages/44/98/f2425bc0113ad7de24da6bb4dae1343476e95e1d738be7c04d31a5d037fd/kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11", size = 66402, upload-time = "2025-08-10T21:26:47.101Z" }, + { url = "https://files.pythonhosted.org/packages/98/d8/594657886df9f34c4177cc353cc28ca7e6e5eb562d37ccc233bff43bbe2a/kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c", size = 1582135, upload-time = "2025-08-10T21:26:48.665Z" }, + { url = "https://files.pythonhosted.org/packages/5c/c6/38a115b7170f8b306fc929e166340c24958347308ea3012c2b44e7e295db/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197", size = 1389409, upload-time = "2025-08-10T21:26:50.335Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3b/e04883dace81f24a568bcee6eb3001da4ba05114afa622ec9b6fafdc1f5e/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c", size = 1401763, upload-time = "2025-08-10T21:26:51.867Z" }, + { url = "https://files.pythonhosted.org/packages/9f/80/20ace48e33408947af49d7d15c341eaee69e4e0304aab4b7660e234d6288/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185", size = 1453643, upload-time = "2025-08-10T21:26:53.592Z" }, + { url = "https://files.pythonhosted.org/packages/64/31/6ce4380a4cd1f515bdda976a1e90e547ccd47b67a1546d63884463c92ca9/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748", size = 2330818, upload-time = "2025-08-10T21:26:55.051Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e9/3f3fcba3bcc7432c795b82646306e822f3fd74df0ee81f0fa067a1f95668/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64", size = 2419963, upload-time = "2025-08-10T21:26:56.421Z" }, + { url = "https://files.pythonhosted.org/packages/99/43/7320c50e4133575c66e9f7dadead35ab22d7c012a3b09bb35647792b2a6d/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff", size = 2594639, upload-time = "2025-08-10T21:26:57.882Z" }, + { url = "https://files.pythonhosted.org/packages/65/d6/17ae4a270d4a987ef8a385b906d2bdfc9fce502d6dc0d3aea865b47f548c/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07", size = 2391741, upload-time = "2025-08-10T21:26:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/2a/8f/8f6f491d595a9e5912971f3f863d81baddccc8a4d0c3749d6a0dd9ffc9df/kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c", size = 68646, upload-time = "2025-08-10T21:27:00.52Z" }, + { url = "https://files.pythonhosted.org/packages/6b/32/6cc0fbc9c54d06c2969faa9c1d29f5751a2e51809dd55c69055e62d9b426/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386", size = 123806, upload-time = "2025-08-10T21:27:01.537Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/2bfb1d4a4823d92e8cbb420fe024b8d2167f72079b3bb941207c42570bdf/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552", size = 66605, upload-time = "2025-08-10T21:27:03.335Z" }, + { url = "https://files.pythonhosted.org/packages/f7/69/00aafdb4e4509c2ca6064646cba9cd4b37933898f426756adb2cb92ebbed/kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3", size = 64925, upload-time = "2025-08-10T21:27:04.339Z" }, + { url = "https://files.pythonhosted.org/packages/43/dc/51acc6791aa14e5cb6d8a2e28cefb0dc2886d8862795449d021334c0df20/kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58", size = 1472414, upload-time = "2025-08-10T21:27:05.437Z" }, + { url = "https://files.pythonhosted.org/packages/3d/bb/93fa64a81db304ac8a246f834d5094fae4b13baf53c839d6bb6e81177129/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4", size = 1281272, upload-time = "2025-08-10T21:27:07.063Z" }, + { url = "https://files.pythonhosted.org/packages/70/e6/6df102916960fb8d05069d4bd92d6d9a8202d5a3e2444494e7cd50f65b7a/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df", size = 1298578, upload-time = "2025-08-10T21:27:08.452Z" }, + { url = "https://files.pythonhosted.org/packages/7c/47/e142aaa612f5343736b087864dbaebc53ea8831453fb47e7521fa8658f30/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6", size = 1345607, upload-time = "2025-08-10T21:27:10.125Z" }, + { url = "https://files.pythonhosted.org/packages/54/89/d641a746194a0f4d1a3670fb900d0dbaa786fb98341056814bc3f058fa52/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5", size = 2230150, upload-time = "2025-08-10T21:27:11.484Z" }, + { url = "https://files.pythonhosted.org/packages/aa/6b/5ee1207198febdf16ac11f78c5ae40861b809cbe0e6d2a8d5b0b3044b199/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf", size = 2325979, upload-time = "2025-08-10T21:27:12.917Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ff/b269eefd90f4ae14dcc74973d5a0f6d28d3b9bb1afd8c0340513afe6b39a/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5", size = 2491456, upload-time = "2025-08-10T21:27:14.353Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d4/10303190bd4d30de547534601e259a4fbf014eed94aae3e5521129215086/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce", size = 2294621, upload-time = "2025-08-10T21:27:15.808Z" }, + { url = "https://files.pythonhosted.org/packages/28/e0/a9a90416fce5c0be25742729c2ea52105d62eda6c4be4d803c2a7be1fa50/kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7", size = 75417, upload-time = "2025-08-10T21:27:17.436Z" }, + { url = "https://files.pythonhosted.org/packages/1f/10/6949958215b7a9a264299a7db195564e87900f709db9245e4ebdd3c70779/kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c", size = 66582, upload-time = "2025-08-10T21:27:18.436Z" }, + { url = "https://files.pythonhosted.org/packages/ec/79/60e53067903d3bc5469b369fe0dfc6b3482e2133e85dae9daa9527535991/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548", size = 126514, upload-time = "2025-08-10T21:27:19.465Z" }, + { url = "https://files.pythonhosted.org/packages/25/d1/4843d3e8d46b072c12a38c97c57fab4608d36e13fe47d47ee96b4d61ba6f/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d", size = 67905, upload-time = "2025-08-10T21:27:20.51Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ae/29ffcbd239aea8b93108de1278271ae764dfc0d803a5693914975f200596/kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c", size = 66399, upload-time = "2025-08-10T21:27:21.496Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ae/d7ba902aa604152c2ceba5d352d7b62106bedbccc8e95c3934d94472bfa3/kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122", size = 1582197, upload-time = "2025-08-10T21:27:22.604Z" }, + { url = "https://files.pythonhosted.org/packages/f2/41/27c70d427eddb8bc7e4f16420a20fefc6f480312122a59a959fdfe0445ad/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64", size = 1390125, upload-time = "2025-08-10T21:27:24.036Z" }, + { url = "https://files.pythonhosted.org/packages/41/42/b3799a12bafc76d962ad69083f8b43b12bf4fe78b097b12e105d75c9b8f1/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134", size = 1402612, upload-time = "2025-08-10T21:27:25.773Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b5/a210ea073ea1cfaca1bb5c55a62307d8252f531beb364e18aa1e0888b5a0/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370", size = 1453990, upload-time = "2025-08-10T21:27:27.089Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ce/a829eb8c033e977d7ea03ed32fb3c1781b4fa0433fbadfff29e39c676f32/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21", size = 2331601, upload-time = "2025-08-10T21:27:29.343Z" }, + { url = "https://files.pythonhosted.org/packages/e0/4b/b5e97eb142eb9cd0072dacfcdcd31b1c66dc7352b0f7c7255d339c0edf00/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a", size = 2422041, upload-time = "2025-08-10T21:27:30.754Z" }, + { url = "https://files.pythonhosted.org/packages/40/be/8eb4cd53e1b85ba4edc3a9321666f12b83113a178845593307a3e7891f44/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f", size = 2594897, upload-time = "2025-08-10T21:27:32.803Z" }, + { url = "https://files.pythonhosted.org/packages/99/dd/841e9a66c4715477ea0abc78da039832fbb09dac5c35c58dc4c41a407b8a/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369", size = 2391835, upload-time = "2025-08-10T21:27:34.23Z" }, + { url = "https://files.pythonhosted.org/packages/0c/28/4b2e5c47a0da96896fdfdb006340ade064afa1e63675d01ea5ac222b6d52/kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891", size = 79988, upload-time = "2025-08-10T21:27:35.587Z" }, + { url = "https://files.pythonhosted.org/packages/80/be/3578e8afd18c88cdf9cb4cffde75a96d2be38c5a903f1ed0ceec061bd09e/kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32", size = 70260, upload-time = "2025-08-10T21:27:36.606Z" }, + { url = "https://files.pythonhosted.org/packages/a3/0f/36d89194b5a32c054ce93e586d4049b6c2c22887b0eb229c61c68afd3078/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5", size = 60104, upload-time = "2025-08-10T21:27:43.287Z" }, + { url = "https://files.pythonhosted.org/packages/52/ba/4ed75f59e4658fd21fe7dde1fee0ac397c678ec3befba3fe6482d987af87/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa", size = 58592, upload-time = "2025-08-10T21:27:44.314Z" }, + { url = "https://files.pythonhosted.org/packages/33/01/a8ea7c5ea32a9b45ceeaee051a04c8ed4320f5add3c51bfa20879b765b70/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2", size = 80281, upload-time = "2025-08-10T21:27:45.369Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/dbd2ecdce306f1d07a1aaf324817ee993aab7aee9db47ceac757deabafbe/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f", size = 78009, upload-time = "2025-08-10T21:27:46.376Z" }, + { url = "https://files.pythonhosted.org/packages/da/e9/0d4add7873a73e462aeb45c036a2dead2562b825aa46ba326727b3f31016/kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1", size = 73929, upload-time = "2025-08-10T21:27:48.236Z" }, ] [[package]] name = "lark" version = "1.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/da/34/28fff3ab31ccff1fd4f6c7c7b0ceb2b6968d8ea4950663eadcb5720591a0/lark-1.3.1.tar.gz", hash = "sha256:b426a7a6d6d53189d318f2b6236ab5d6429eaf09259f1ca33eb716eed10d2905", size = 382732 } +sdist = { url = "https://files.pythonhosted.org/packages/da/34/28fff3ab31ccff1fd4f6c7c7b0ceb2b6968d8ea4950663eadcb5720591a0/lark-1.3.1.tar.gz", hash = "sha256:b426a7a6d6d53189d318f2b6236ab5d6429eaf09259f1ca33eb716eed10d2905", size = 382732, upload-time = "2025-10-27T18:25:56.653Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl", hash = "sha256:c629b661023a014c37da873b4ff58a817398d12635d3bbb2c5a03be7fe5d1e12", size = 113151 }, + { url = "https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl", hash = "sha256:c629b661023a014c37da873b4ff58a817398d12635d3bbb2c5a03be7fe5d1e12", size = 113151, upload-time = "2025-10-27T18:25:54.882Z" }, ] [[package]] name = "lazy-loader" -version = "0.5" +version = "0.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/49/ac/21a1f8aa3777f5658576777ea76bfb124b702c520bbe90edf4ae9915eafa/lazy_loader-0.5.tar.gz", hash = "sha256:717f9179a0dbed357012ddad50a5ad3d5e4d9a0b8712680d4e687f5e6e6ed9b3", size = 15294, upload-time = "2026-03-06T15:45:09.054Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6b/c875b30a1ba490860c93da4cabf479e03f584eba06fe5963f6f6644653d8/lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1", size = 15431, upload-time = "2024-04-05T13:03:12.261Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl", hash = "sha256:ab0ea149e9c554d4ffeeb21105ac60bed7f3b4fd69b1d2360a4add51b170b005", size = 8044, upload-time = "2026-03-06T15:45:07.668Z" }, + { url = "https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc", size = 12097, upload-time = "2024-04-05T13:03:10.514Z" }, ] [[package]] @@ -1671,18 +1592,18 @@ dependencies = [ { name = "packaging" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f1/45/7fa8f56b17dc0f0a41ec70dd307ecd6787254483549843bef4c30ab5adce/lightning_utilities-0.15.3.tar.gz", hash = "sha256:792ae0204c79f6859721ac7f386c237a33b0ed06ba775009cb894e010a842033", size = 33553 } +sdist = { url = "https://files.pythonhosted.org/packages/f1/45/7fa8f56b17dc0f0a41ec70dd307ecd6787254483549843bef4c30ab5adce/lightning_utilities-0.15.3.tar.gz", hash = "sha256:792ae0204c79f6859721ac7f386c237a33b0ed06ba775009cb894e010a842033", size = 33553, upload-time = "2026-02-22T14:48:53.348Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/f4/ead6e0e37209b07c9baa3e984ccdb0348ca370b77cea3aaea8ddbb097e00/lightning_utilities-0.15.3-py3-none-any.whl", hash = "sha256:6c55f1bee70084a1cbeaa41ada96e4b3a0fea5909e844dd335bd80f5a73c5f91", size = 31906 }, + { url = "https://files.pythonhosted.org/packages/25/f4/ead6e0e37209b07c9baa3e984ccdb0348ca370b77cea3aaea8ddbb097e00/lightning_utilities-0.15.3-py3-none-any.whl", hash = "sha256:6c55f1bee70084a1cbeaa41ada96e4b3a0fea5909e844dd335bd80f5a73c5f91", size = 31906, upload-time = "2026-02-22T14:48:52.488Z" }, ] [[package]] name = "locket" version = "1.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2f/83/97b29fe05cb6ae28d2dbd30b81e2e402a3eed5f460c26e9eaa5895ceacf5/locket-1.0.0.tar.gz", hash = "sha256:5c0d4c052a8bbbf750e056a8e65ccd309086f4f0f18a2eac306a8dfa4112a632", size = 4350 } +sdist = { url = "https://files.pythonhosted.org/packages/2f/83/97b29fe05cb6ae28d2dbd30b81e2e402a3eed5f460c26e9eaa5895ceacf5/locket-1.0.0.tar.gz", hash = "sha256:5c0d4c052a8bbbf750e056a8e65ccd309086f4f0f18a2eac306a8dfa4112a632", size = 4350, upload-time = "2022-04-20T22:04:44.312Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl", hash = "sha256:b6c819a722f7b6bd955b80781788e4a66a55628b858d347536b7e81325a3a5e3", size = 4398 }, + { url = "https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl", hash = "sha256:b6c819a722f7b6bd955b80781788e4a66a55628b858d347536b7e81325a3a5e3", size = 4398, upload-time = "2022-04-20T22:04:42.23Z" }, ] [[package]] @@ -1692,92 +1613,92 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474 } +sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474, upload-time = "2025-04-10T12:44:31.16Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509 }, + { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" }, ] [[package]] name = "markdown" version = "3.10.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805 } +sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180 }, + { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, ] [[package]] name = "markupsafe" version = "3.0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631 }, - { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058 }, - { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287 }, - { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940 }, - { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887 }, - { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692 }, - { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471 }, - { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923 }, - { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572 }, - { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077 }, - { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876 }, - { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615 }, - { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020 }, - { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332 }, - { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947 }, - { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962 }, - { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760 }, - { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529 }, - { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015 }, - { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540 }, - { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105 }, - { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906 }, - { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622 }, - { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029 }, - { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374 }, - { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980 }, - { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990 }, - { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784 }, - { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588 }, - { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041 }, - { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543 }, - { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113 }, - { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911 }, - { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658 }, - { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066 }, - { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639 }, - { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569 }, - { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284 }, - { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801 }, - { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769 }, - { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642 }, - { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612 }, - { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200 }, - { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973 }, - { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619 }, - { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029 }, - { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408 }, - { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005 }, - { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048 }, - { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821 }, - { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606 }, - { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043 }, - { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747 }, - { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341 }, - { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073 }, - { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661 }, - { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069 }, - { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670 }, - { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598 }, - { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261 }, - { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835 }, - { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733 }, - { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672 }, - { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819 }, - { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426 }, - { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146 }, +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, ] [[package]] @@ -1795,53 +1716,53 @@ dependencies = [ { name = "pyparsing" }, { name = "python-dateutil" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8a/76/d3c6e3a13fe484ebe7718d14e269c9569c4eb0020a968a327acb3b9a8fe6/matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3", size = 34806269 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/86/de7e3a1cdcfc941483af70609edc06b83e7c8a0e0dc9ac325200a3f4d220/matplotlib-3.10.8-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6be43b667360fef5c754dda5d25a32e6307a03c204f3c0fc5468b78fa87b4160", size = 8251215 }, - { url = "https://files.pythonhosted.org/packages/fd/14/baad3222f424b19ce6ad243c71de1ad9ec6b2e4eb1e458a48fdc6d120401/matplotlib-3.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2b336e2d91a3d7006864e0990c83b216fcdca64b5a6484912902cef87313d78", size = 8139625 }, - { url = "https://files.pythonhosted.org/packages/8f/a0/7024215e95d456de5883e6732e708d8187d9753a21d32f8ddb3befc0c445/matplotlib-3.10.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efb30e3baaea72ce5928e32bab719ab4770099079d66726a62b11b1ef7273be4", size = 8712614 }, - { url = "https://files.pythonhosted.org/packages/5a/f4/b8347351da9a5b3f41e26cf547252d861f685c6867d179a7c9d60ad50189/matplotlib-3.10.8-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d56a1efd5bfd61486c8bc968fa18734464556f0fb8e51690f4ac25d85cbbbbc2", size = 9540997 }, - { url = "https://files.pythonhosted.org/packages/9e/c0/c7b914e297efe0bc36917bf216b2acb91044b91e930e878ae12981e461e5/matplotlib-3.10.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238b7ce5717600615c895050239ec955d91f321c209dd110db988500558e70d6", size = 9596825 }, - { url = "https://files.pythonhosted.org/packages/6f/d3/a4bbc01c237ab710a1f22b4da72f4ff6d77eb4c7735ea9811a94ae239067/matplotlib-3.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:18821ace09c763ec93aef5eeff087ee493a24051936d7b9ebcad9662f66501f9", size = 8135090 }, - { url = "https://files.pythonhosted.org/packages/89/dd/a0b6588f102beab33ca6f5218b31725216577b2a24172f327eaf6417d5c9/matplotlib-3.10.8-cp311-cp311-win_arm64.whl", hash = "sha256:bab485bcf8b1c7d2060b4fcb6fc368a9e6f4cd754c9c2fea281f4be21df394a2", size = 8012377 }, - { url = "https://files.pythonhosted.org/packages/9e/67/f997cdcbb514012eb0d10cd2b4b332667997fb5ebe26b8d41d04962fa0e6/matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a", size = 8260453 }, - { url = "https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58", size = 8148321 }, - { url = "https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04", size = 8716944 }, - { url = "https://files.pythonhosted.org/packages/00/f9/7638f5cc82ec8a7aa005de48622eecc3ed7c9854b96ba15bd76b7fd27574/matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f", size = 9550099 }, - { url = "https://files.pythonhosted.org/packages/57/61/78cd5920d35b29fd2a0fe894de8adf672ff52939d2e9b43cb83cd5ce1bc7/matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466", size = 9613040 }, - { url = "https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf", size = 8142717 }, - { url = "https://files.pythonhosted.org/packages/f1/76/934db220026b5fef85f45d51a738b91dea7d70207581063cd9bd8fafcf74/matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b", size = 8012751 }, - { url = "https://files.pythonhosted.org/packages/3d/b9/15fd5541ef4f5b9a17eefd379356cf12175fe577424e7b1d80676516031a/matplotlib-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3f2e409836d7f5ac2f1c013110a4d50b9f7edc26328c108915f9075d7d7a91b6", size = 8261076 }, - { url = "https://files.pythonhosted.org/packages/8d/a0/2ba3473c1b66b9c74dc7107c67e9008cb1782edbe896d4c899d39ae9cf78/matplotlib-3.10.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56271f3dac49a88d7fca5060f004d9d22b865f743a12a23b1e937a0be4818ee1", size = 8148794 }, - { url = "https://files.pythonhosted.org/packages/75/97/a471f1c3eb1fd6f6c24a31a5858f443891d5127e63a7788678d14e249aea/matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0a7f52498f72f13d4a25ea70f35f4cb60642b466cbb0a9be951b5bc3f45a486", size = 8718474 }, - { url = "https://files.pythonhosted.org/packages/01/be/cd478f4b66f48256f42927d0acbcd63a26a893136456cd079c0cc24fbabf/matplotlib-3.10.8-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:646d95230efb9ca614a7a594d4fcacde0ac61d25e37dd51710b36477594963ce", size = 9549637 }, - { url = "https://files.pythonhosted.org/packages/5d/7c/8dc289776eae5109e268c4fb92baf870678dc048a25d4ac903683b86d5bf/matplotlib-3.10.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f89c151aab2e2e23cb3fe0acad1e8b82841fd265379c4cecd0f3fcb34c15e0f6", size = 9613678 }, - { url = "https://files.pythonhosted.org/packages/64/40/37612487cc8a437d4dd261b32ca21fe2d79510fe74af74e1f42becb1bdb8/matplotlib-3.10.8-cp313-cp313-win_amd64.whl", hash = "sha256:e8ea3e2d4066083e264e75c829078f9e149fa119d27e19acd503de65e0b13149", size = 8142686 }, - { url = "https://files.pythonhosted.org/packages/66/52/8d8a8730e968185514680c2a6625943f70269509c3dcfc0dcf7d75928cb8/matplotlib-3.10.8-cp313-cp313-win_arm64.whl", hash = "sha256:c108a1d6fa78a50646029cb6d49808ff0fc1330fda87fa6f6250c6b5369b6645", size = 8012917 }, - { url = "https://files.pythonhosted.org/packages/b5/27/51fe26e1062f298af5ef66343d8ef460e090a27fea73036c76c35821df04/matplotlib-3.10.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ad3d9833a64cf48cc4300f2b406c3d0f4f4724a91c0bd5640678a6ba7c102077", size = 8305679 }, - { url = "https://files.pythonhosted.org/packages/2c/1e/4de865bc591ac8e3062e835f42dd7fe7a93168d519557837f0e37513f629/matplotlib-3.10.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:eb3823f11823deade26ce3b9f40dcb4a213da7a670013929f31d5f5ed1055b22", size = 8198336 }, - { url = "https://files.pythonhosted.org/packages/c6/cb/2f7b6e75fb4dce87ef91f60cac4f6e34f4c145ab036a22318ec837971300/matplotlib-3.10.8-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d9050fee89a89ed57b4fb2c1bfac9a3d0c57a0d55aed95949eedbc42070fea39", size = 8731653 }, - { url = "https://files.pythonhosted.org/packages/46/b3/bd9c57d6ba670a37ab31fb87ec3e8691b947134b201f881665b28cc039ff/matplotlib-3.10.8-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b44d07310e404ba95f8c25aa5536f154c0a8ec473303535949e52eb71d0a1565", size = 9561356 }, - { url = "https://files.pythonhosted.org/packages/c0/3d/8b94a481456dfc9dfe6e39e93b5ab376e50998cddfd23f4ae3b431708f16/matplotlib-3.10.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0a33deb84c15ede243aead39f77e990469fff93ad1521163305095b77b72ce4a", size = 9614000 }, - { url = "https://files.pythonhosted.org/packages/bd/cd/bc06149fe5585ba800b189a6a654a75f1f127e8aab02fd2be10df7fa500c/matplotlib-3.10.8-cp313-cp313t-win_amd64.whl", hash = "sha256:3a48a78d2786784cc2413e57397981fb45c79e968d99656706018d6e62e57958", size = 8220043 }, - { url = "https://files.pythonhosted.org/packages/e3/de/b22cf255abec916562cc04eef457c13e58a1990048de0c0c3604d082355e/matplotlib-3.10.8-cp313-cp313t-win_arm64.whl", hash = "sha256:15d30132718972c2c074cd14638c7f4592bd98719e2308bccea40e0538bc0cb5", size = 8062075 }, - { url = "https://files.pythonhosted.org/packages/3c/43/9c0ff7a2f11615e516c3b058e1e6e8f9614ddeca53faca06da267c48345d/matplotlib-3.10.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b53285e65d4fa4c86399979e956235deb900be5baa7fc1218ea67fbfaeaadd6f", size = 8262481 }, - { url = "https://files.pythonhosted.org/packages/6f/ca/e8ae28649fcdf039fda5ef554b40a95f50592a3c47e6f7270c9561c12b07/matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b", size = 8151473 }, - { url = "https://files.pythonhosted.org/packages/f1/6f/009d129ae70b75e88cbe7e503a12a4c0670e08ed748a902c2568909e9eb5/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cf267add95b1c88300d96ca837833d4112756045364f5c734a2276038dae27d", size = 9553896 }, - { url = "https://files.pythonhosted.org/packages/f5/26/4221a741eb97967bc1fd5e4c52b9aa5a91b2f4ec05b59f6def4d820f9df9/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008", size = 9824193 }, - { url = "https://files.pythonhosted.org/packages/1f/f3/3abf75f38605772cf48a9daf5821cd4f563472f38b4b828c6fba6fa6d06e/matplotlib-3.10.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:41703cc95688f2516b480f7f339d8851a6035f18e100ee6a32bc0b8536a12a9c", size = 9615444 }, - { url = "https://files.pythonhosted.org/packages/93/a5/de89ac80f10b8dc615807ee1133cd99ac74082581196d4d9590bea10690d/matplotlib-3.10.8-cp314-cp314-win_amd64.whl", hash = "sha256:83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11", size = 8272719 }, - { url = "https://files.pythonhosted.org/packages/69/ce/b006495c19ccc0a137b48083168a37bd056392dee02f87dba0472f2797fe/matplotlib-3.10.8-cp314-cp314-win_arm64.whl", hash = "sha256:2c1998e92cd5999e295a731bcb2911c75f597d937341f3030cc24ef2733d78a8", size = 8144205 }, - { url = "https://files.pythonhosted.org/packages/68/d9/b31116a3a855bd313c6fcdb7226926d59b041f26061c6c5b1be66a08c826/matplotlib-3.10.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b5a2b97dbdc7d4f353ebf343744f1d1f1cca8aa8bfddb4262fcf4306c3761d50", size = 8305785 }, - { url = "https://files.pythonhosted.org/packages/1e/90/6effe8103f0272685767ba5f094f453784057072f49b393e3ea178fe70a5/matplotlib-3.10.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3f5c3e4da343bba819f0234186b9004faba952cc420fbc522dc4e103c1985908", size = 8198361 }, - { url = "https://files.pythonhosted.org/packages/d7/65/a73188711bea603615fc0baecca1061429ac16940e2385433cc778a9d8e7/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f62550b9a30afde8c1c3ae450e5eb547d579dd69b25c2fc7a1c67f934c1717a", size = 9561357 }, - { url = "https://files.pythonhosted.org/packages/f4/3d/b5c5d5d5be8ce63292567f0e2c43dde9953d3ed86ac2de0a72e93c8f07a1/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:495672de149445ec1b772ff2c9ede9b769e3cb4f0d0aa7fa730d7f59e2d4e1c1", size = 9823610 }, - { url = "https://files.pythonhosted.org/packages/4d/4b/e7beb6bbd49f6bae727a12b270a2654d13c397576d25bd6786e47033300f/matplotlib-3.10.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:595ba4d8fe983b88f0eec8c26a241e16d6376fe1979086232f481f8f3f67494c", size = 9614011 }, - { url = "https://files.pythonhosted.org/packages/7c/e6/76f2813d31f032e65f6f797e3f2f6e4aab95b65015924b1c51370395c28a/matplotlib-3.10.8-cp314-cp314t-win_amd64.whl", hash = "sha256:25d380fe8b1dc32cf8f0b1b448470a77afb195438bafdf1d858bfb876f3edf7b", size = 8362801 }, - { url = "https://files.pythonhosted.org/packages/5d/49/d651878698a0b67f23aa28e17f45a6d6dd3d3f933fa29087fa4ce5947b5a/matplotlib-3.10.8-cp314-cp314t-win_arm64.whl", hash = "sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f", size = 8192560 }, - { url = "https://files.pythonhosted.org/packages/04/30/3afaa31c757f34b7725ab9d2ba8b48b5e89c2019c003e7d0ead143aabc5a/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6da7c2ce169267d0d066adcf63758f0604aa6c3eebf67458930f9d9b79ad1db1", size = 8249198 }, - { url = "https://files.pythonhosted.org/packages/48/2f/6334aec331f57485a642a7c8be03cb286f29111ae71c46c38b363230063c/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9153c3292705be9f9c64498a8872118540c3f4123d1a1c840172edf262c8be4a", size = 8136817 }, - { url = "https://files.pythonhosted.org/packages/73/e4/6d6f14b2a759c622f191b2d67e9075a3f56aaccb3be4bb9bb6890030d0a0/matplotlib-3.10.8-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2", size = 8713867 }, +sdist = { url = "https://files.pythonhosted.org/packages/8a/76/d3c6e3a13fe484ebe7718d14e269c9569c4eb0020a968a327acb3b9a8fe6/matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3", size = 34806269, upload-time = "2025-12-10T22:56:51.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/86/de7e3a1cdcfc941483af70609edc06b83e7c8a0e0dc9ac325200a3f4d220/matplotlib-3.10.8-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6be43b667360fef5c754dda5d25a32e6307a03c204f3c0fc5468b78fa87b4160", size = 8251215, upload-time = "2025-12-10T22:55:16.175Z" }, + { url = "https://files.pythonhosted.org/packages/fd/14/baad3222f424b19ce6ad243c71de1ad9ec6b2e4eb1e458a48fdc6d120401/matplotlib-3.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2b336e2d91a3d7006864e0990c83b216fcdca64b5a6484912902cef87313d78", size = 8139625, upload-time = "2025-12-10T22:55:17.712Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a0/7024215e95d456de5883e6732e708d8187d9753a21d32f8ddb3befc0c445/matplotlib-3.10.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efb30e3baaea72ce5928e32bab719ab4770099079d66726a62b11b1ef7273be4", size = 8712614, upload-time = "2025-12-10T22:55:20.8Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f4/b8347351da9a5b3f41e26cf547252d861f685c6867d179a7c9d60ad50189/matplotlib-3.10.8-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d56a1efd5bfd61486c8bc968fa18734464556f0fb8e51690f4ac25d85cbbbbc2", size = 9540997, upload-time = "2025-12-10T22:55:23.258Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c0/c7b914e297efe0bc36917bf216b2acb91044b91e930e878ae12981e461e5/matplotlib-3.10.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238b7ce5717600615c895050239ec955d91f321c209dd110db988500558e70d6", size = 9596825, upload-time = "2025-12-10T22:55:25.217Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d3/a4bbc01c237ab710a1f22b4da72f4ff6d77eb4c7735ea9811a94ae239067/matplotlib-3.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:18821ace09c763ec93aef5eeff087ee493a24051936d7b9ebcad9662f66501f9", size = 8135090, upload-time = "2025-12-10T22:55:27.162Z" }, + { url = "https://files.pythonhosted.org/packages/89/dd/a0b6588f102beab33ca6f5218b31725216577b2a24172f327eaf6417d5c9/matplotlib-3.10.8-cp311-cp311-win_arm64.whl", hash = "sha256:bab485bcf8b1c7d2060b4fcb6fc368a9e6f4cd754c9c2fea281f4be21df394a2", size = 8012377, upload-time = "2025-12-10T22:55:29.185Z" }, + { url = "https://files.pythonhosted.org/packages/9e/67/f997cdcbb514012eb0d10cd2b4b332667997fb5ebe26b8d41d04962fa0e6/matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a", size = 8260453, upload-time = "2025-12-10T22:55:30.709Z" }, + { url = "https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58", size = 8148321, upload-time = "2025-12-10T22:55:33.265Z" }, + { url = "https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04", size = 8716944, upload-time = "2025-12-10T22:55:34.922Z" }, + { url = "https://files.pythonhosted.org/packages/00/f9/7638f5cc82ec8a7aa005de48622eecc3ed7c9854b96ba15bd76b7fd27574/matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f", size = 9550099, upload-time = "2025-12-10T22:55:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/57/61/78cd5920d35b29fd2a0fe894de8adf672ff52939d2e9b43cb83cd5ce1bc7/matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466", size = 9613040, upload-time = "2025-12-10T22:55:38.715Z" }, + { url = "https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf", size = 8142717, upload-time = "2025-12-10T22:55:41.103Z" }, + { url = "https://files.pythonhosted.org/packages/f1/76/934db220026b5fef85f45d51a738b91dea7d70207581063cd9bd8fafcf74/matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b", size = 8012751, upload-time = "2025-12-10T22:55:42.684Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b9/15fd5541ef4f5b9a17eefd379356cf12175fe577424e7b1d80676516031a/matplotlib-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3f2e409836d7f5ac2f1c013110a4d50b9f7edc26328c108915f9075d7d7a91b6", size = 8261076, upload-time = "2025-12-10T22:55:44.648Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a0/2ba3473c1b66b9c74dc7107c67e9008cb1782edbe896d4c899d39ae9cf78/matplotlib-3.10.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56271f3dac49a88d7fca5060f004d9d22b865f743a12a23b1e937a0be4818ee1", size = 8148794, upload-time = "2025-12-10T22:55:46.252Z" }, + { url = "https://files.pythonhosted.org/packages/75/97/a471f1c3eb1fd6f6c24a31a5858f443891d5127e63a7788678d14e249aea/matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0a7f52498f72f13d4a25ea70f35f4cb60642b466cbb0a9be951b5bc3f45a486", size = 8718474, upload-time = "2025-12-10T22:55:47.864Z" }, + { url = "https://files.pythonhosted.org/packages/01/be/cd478f4b66f48256f42927d0acbcd63a26a893136456cd079c0cc24fbabf/matplotlib-3.10.8-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:646d95230efb9ca614a7a594d4fcacde0ac61d25e37dd51710b36477594963ce", size = 9549637, upload-time = "2025-12-10T22:55:50.048Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/8dc289776eae5109e268c4fb92baf870678dc048a25d4ac903683b86d5bf/matplotlib-3.10.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f89c151aab2e2e23cb3fe0acad1e8b82841fd265379c4cecd0f3fcb34c15e0f6", size = 9613678, upload-time = "2025-12-10T22:55:52.21Z" }, + { url = "https://files.pythonhosted.org/packages/64/40/37612487cc8a437d4dd261b32ca21fe2d79510fe74af74e1f42becb1bdb8/matplotlib-3.10.8-cp313-cp313-win_amd64.whl", hash = "sha256:e8ea3e2d4066083e264e75c829078f9e149fa119d27e19acd503de65e0b13149", size = 8142686, upload-time = "2025-12-10T22:55:54.253Z" }, + { url = "https://files.pythonhosted.org/packages/66/52/8d8a8730e968185514680c2a6625943f70269509c3dcfc0dcf7d75928cb8/matplotlib-3.10.8-cp313-cp313-win_arm64.whl", hash = "sha256:c108a1d6fa78a50646029cb6d49808ff0fc1330fda87fa6f6250c6b5369b6645", size = 8012917, upload-time = "2025-12-10T22:55:56.268Z" }, + { url = "https://files.pythonhosted.org/packages/b5/27/51fe26e1062f298af5ef66343d8ef460e090a27fea73036c76c35821df04/matplotlib-3.10.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ad3d9833a64cf48cc4300f2b406c3d0f4f4724a91c0bd5640678a6ba7c102077", size = 8305679, upload-time = "2025-12-10T22:55:57.856Z" }, + { url = "https://files.pythonhosted.org/packages/2c/1e/4de865bc591ac8e3062e835f42dd7fe7a93168d519557837f0e37513f629/matplotlib-3.10.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:eb3823f11823deade26ce3b9f40dcb4a213da7a670013929f31d5f5ed1055b22", size = 8198336, upload-time = "2025-12-10T22:55:59.371Z" }, + { url = "https://files.pythonhosted.org/packages/c6/cb/2f7b6e75fb4dce87ef91f60cac4f6e34f4c145ab036a22318ec837971300/matplotlib-3.10.8-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d9050fee89a89ed57b4fb2c1bfac9a3d0c57a0d55aed95949eedbc42070fea39", size = 8731653, upload-time = "2025-12-10T22:56:01.032Z" }, + { url = "https://files.pythonhosted.org/packages/46/b3/bd9c57d6ba670a37ab31fb87ec3e8691b947134b201f881665b28cc039ff/matplotlib-3.10.8-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b44d07310e404ba95f8c25aa5536f154c0a8ec473303535949e52eb71d0a1565", size = 9561356, upload-time = "2025-12-10T22:56:02.95Z" }, + { url = "https://files.pythonhosted.org/packages/c0/3d/8b94a481456dfc9dfe6e39e93b5ab376e50998cddfd23f4ae3b431708f16/matplotlib-3.10.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0a33deb84c15ede243aead39f77e990469fff93ad1521163305095b77b72ce4a", size = 9614000, upload-time = "2025-12-10T22:56:05.411Z" }, + { url = "https://files.pythonhosted.org/packages/bd/cd/bc06149fe5585ba800b189a6a654a75f1f127e8aab02fd2be10df7fa500c/matplotlib-3.10.8-cp313-cp313t-win_amd64.whl", hash = "sha256:3a48a78d2786784cc2413e57397981fb45c79e968d99656706018d6e62e57958", size = 8220043, upload-time = "2025-12-10T22:56:07.551Z" }, + { url = "https://files.pythonhosted.org/packages/e3/de/b22cf255abec916562cc04eef457c13e58a1990048de0c0c3604d082355e/matplotlib-3.10.8-cp313-cp313t-win_arm64.whl", hash = "sha256:15d30132718972c2c074cd14638c7f4592bd98719e2308bccea40e0538bc0cb5", size = 8062075, upload-time = "2025-12-10T22:56:09.178Z" }, + { url = "https://files.pythonhosted.org/packages/3c/43/9c0ff7a2f11615e516c3b058e1e6e8f9614ddeca53faca06da267c48345d/matplotlib-3.10.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b53285e65d4fa4c86399979e956235deb900be5baa7fc1218ea67fbfaeaadd6f", size = 8262481, upload-time = "2025-12-10T22:56:10.885Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ca/e8ae28649fcdf039fda5ef554b40a95f50592a3c47e6f7270c9561c12b07/matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b", size = 8151473, upload-time = "2025-12-10T22:56:12.377Z" }, + { url = "https://files.pythonhosted.org/packages/f1/6f/009d129ae70b75e88cbe7e503a12a4c0670e08ed748a902c2568909e9eb5/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cf267add95b1c88300d96ca837833d4112756045364f5c734a2276038dae27d", size = 9553896, upload-time = "2025-12-10T22:56:14.432Z" }, + { url = "https://files.pythonhosted.org/packages/f5/26/4221a741eb97967bc1fd5e4c52b9aa5a91b2f4ec05b59f6def4d820f9df9/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008", size = 9824193, upload-time = "2025-12-10T22:56:16.29Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/3abf75f38605772cf48a9daf5821cd4f563472f38b4b828c6fba6fa6d06e/matplotlib-3.10.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:41703cc95688f2516b480f7f339d8851a6035f18e100ee6a32bc0b8536a12a9c", size = 9615444, upload-time = "2025-12-10T22:56:18.155Z" }, + { url = "https://files.pythonhosted.org/packages/93/a5/de89ac80f10b8dc615807ee1133cd99ac74082581196d4d9590bea10690d/matplotlib-3.10.8-cp314-cp314-win_amd64.whl", hash = "sha256:83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11", size = 8272719, upload-time = "2025-12-10T22:56:20.366Z" }, + { url = "https://files.pythonhosted.org/packages/69/ce/b006495c19ccc0a137b48083168a37bd056392dee02f87dba0472f2797fe/matplotlib-3.10.8-cp314-cp314-win_arm64.whl", hash = "sha256:2c1998e92cd5999e295a731bcb2911c75f597d937341f3030cc24ef2733d78a8", size = 8144205, upload-time = "2025-12-10T22:56:22.239Z" }, + { url = "https://files.pythonhosted.org/packages/68/d9/b31116a3a855bd313c6fcdb7226926d59b041f26061c6c5b1be66a08c826/matplotlib-3.10.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b5a2b97dbdc7d4f353ebf343744f1d1f1cca8aa8bfddb4262fcf4306c3761d50", size = 8305785, upload-time = "2025-12-10T22:56:24.218Z" }, + { url = "https://files.pythonhosted.org/packages/1e/90/6effe8103f0272685767ba5f094f453784057072f49b393e3ea178fe70a5/matplotlib-3.10.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3f5c3e4da343bba819f0234186b9004faba952cc420fbc522dc4e103c1985908", size = 8198361, upload-time = "2025-12-10T22:56:26.787Z" }, + { url = "https://files.pythonhosted.org/packages/d7/65/a73188711bea603615fc0baecca1061429ac16940e2385433cc778a9d8e7/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f62550b9a30afde8c1c3ae450e5eb547d579dd69b25c2fc7a1c67f934c1717a", size = 9561357, upload-time = "2025-12-10T22:56:28.953Z" }, + { url = "https://files.pythonhosted.org/packages/f4/3d/b5c5d5d5be8ce63292567f0e2c43dde9953d3ed86ac2de0a72e93c8f07a1/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:495672de149445ec1b772ff2c9ede9b769e3cb4f0d0aa7fa730d7f59e2d4e1c1", size = 9823610, upload-time = "2025-12-10T22:56:31.455Z" }, + { url = "https://files.pythonhosted.org/packages/4d/4b/e7beb6bbd49f6bae727a12b270a2654d13c397576d25bd6786e47033300f/matplotlib-3.10.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:595ba4d8fe983b88f0eec8c26a241e16d6376fe1979086232f481f8f3f67494c", size = 9614011, upload-time = "2025-12-10T22:56:33.85Z" }, + { url = "https://files.pythonhosted.org/packages/7c/e6/76f2813d31f032e65f6f797e3f2f6e4aab95b65015924b1c51370395c28a/matplotlib-3.10.8-cp314-cp314t-win_amd64.whl", hash = "sha256:25d380fe8b1dc32cf8f0b1b448470a77afb195438bafdf1d858bfb876f3edf7b", size = 8362801, upload-time = "2025-12-10T22:56:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/5d/49/d651878698a0b67f23aa28e17f45a6d6dd3d3f933fa29087fa4ce5947b5a/matplotlib-3.10.8-cp314-cp314t-win_arm64.whl", hash = "sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f", size = 8192560, upload-time = "2025-12-10T22:56:38.008Z" }, + { url = "https://files.pythonhosted.org/packages/04/30/3afaa31c757f34b7725ab9d2ba8b48b5e89c2019c003e7d0ead143aabc5a/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6da7c2ce169267d0d066adcf63758f0604aa6c3eebf67458930f9d9b79ad1db1", size = 8249198, upload-time = "2025-12-10T22:56:45.584Z" }, + { url = "https://files.pythonhosted.org/packages/48/2f/6334aec331f57485a642a7c8be03cb286f29111ae71c46c38b363230063c/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9153c3292705be9f9c64498a8872118540c3f4123d1a1c840172edf262c8be4a", size = 8136817, upload-time = "2025-12-10T22:56:47.339Z" }, + { url = "https://files.pythonhosted.org/packages/73/e4/6d6f14b2a759c622f191b2d67e9075a3f56aaccb3be4bb9bb6890030d0a0/matplotlib-3.10.8-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2", size = 8713867, upload-time = "2025-12-10T22:56:48.954Z" }, ] [[package]] @@ -1851,27 +1772,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c7/74/97e72a36efd4ae2bccb3463284300f8953f199b5ffbc04cbbb0ec78f74b1/matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe", size = 8110 } +sdist = { url = "https://files.pythonhosted.org/packages/c7/74/97e72a36efd4ae2bccb3463284300f8953f199b5ffbc04cbbb0ec78f74b1/matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe", size = 8110, upload-time = "2025-10-23T09:00:22.126Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516 }, + { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" }, ] [[package]] name = "mistune" version = "3.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/55/d01f0c4b45ade6536c51170b9043db8b2ec6ddf4a35c7ea3f5f559ac935b/mistune-3.2.0.tar.gz", hash = "sha256:708487c8a8cdd99c9d90eb3ed4c3ed961246ff78ac82f03418f5183ab70e398a", size = 95467 } +sdist = { url = "https://files.pythonhosted.org/packages/9d/55/d01f0c4b45ade6536c51170b9043db8b2ec6ddf4a35c7ea3f5f559ac935b/mistune-3.2.0.tar.gz", hash = "sha256:708487c8a8cdd99c9d90eb3ed4c3ed961246ff78ac82f03418f5183ab70e398a", size = 95467, upload-time = "2025-12-23T11:36:34.994Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl", hash = "sha256:febdc629a3c78616b94393c6580551e0e34cc289987ec6c35ed3f4be42d0eee1", size = 53598 }, + { url = "https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl", hash = "sha256:febdc629a3c78616b94393c6580551e0e34cc289987ec6c35ed3f4be42d0eee1", size = 53598, upload-time = "2025-12-23T11:36:33.211Z" }, ] [[package]] name = "mpmath" version = "1.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106 } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198 }, + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, ] [[package]] @@ -1884,9 +1805,9 @@ dependencies = [ { name = "nbformat" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/56/91/1c1d5a4b9a9ebba2b4e32b8c852c2975c872aec1fe42ab5e516b2cecd193/nbclient-0.10.4.tar.gz", hash = "sha256:1e54091b16e6da39e297b0ece3e10f6f29f4ac4e8ee515d29f8a7099bd6553c9", size = 62554 } +sdist = { url = "https://files.pythonhosted.org/packages/56/91/1c1d5a4b9a9ebba2b4e32b8c852c2975c872aec1fe42ab5e516b2cecd193/nbclient-0.10.4.tar.gz", hash = "sha256:1e54091b16e6da39e297b0ece3e10f6f29f4ac4e8ee515d29f8a7099bd6553c9", size = 62554, upload-time = "2025-12-23T07:45:46.369Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl", hash = "sha256:9162df5a7373d70d606527300a95a975a47c137776cd942e52d9c7e29ff83440", size = 25465 }, + { url = "https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl", hash = "sha256:9162df5a7373d70d606527300a95a975a47c137776cd942e52d9c7e29ff83440", size = 25465, upload-time = "2025-12-23T07:45:44.51Z" }, ] [[package]] @@ -1909,9 +1830,9 @@ dependencies = [ { name = "pygments" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/38/47/81f886b699450d0569f7bc551df2b1673d18df7ff25cc0c21ca36ed8a5ff/nbconvert-7.17.0.tar.gz", hash = "sha256:1b2696f1b5be12309f6c7d707c24af604b87dfaf6d950794c7b07acab96dda78", size = 862855 } +sdist = { url = "https://files.pythonhosted.org/packages/38/47/81f886b699450d0569f7bc551df2b1673d18df7ff25cc0c21ca36ed8a5ff/nbconvert-7.17.0.tar.gz", hash = "sha256:1b2696f1b5be12309f6c7d707c24af604b87dfaf6d950794c7b07acab96dda78", size = 862855, upload-time = "2026-01-29T16:37:48.478Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl", hash = "sha256:4f99a63b337b9a23504347afdab24a11faa7d86b405e5c8f9881cd313336d518", size = 261510 }, + { url = "https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl", hash = "sha256:4f99a63b337b9a23504347afdab24a11faa7d86b405e5c8f9881cd313336d518", size = 261510, upload-time = "2026-01-29T16:37:46.322Z" }, ] [[package]] @@ -1924,36 +1845,36 @@ dependencies = [ { name = "jupyter-core" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749 } +sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749, upload-time = "2024-04-04T11:20:37.371Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454 }, + { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454, upload-time = "2024-04-04T11:20:34.895Z" }, ] [[package]] name = "nest-asyncio" version = "1.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418 } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195 }, + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, ] [[package]] name = "networkx" version = "3.6.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025 } +sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504 }, + { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, ] [[package]] name = "nodeenv" version = "1.10.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611 } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438 }, + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, ] [[package]] @@ -1963,9 +1884,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jupyter-server" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/54/d2/92fa3243712b9a3e8bafaf60aac366da1cada3639ca767ff4b5b3654ec28/notebook_shim-0.2.4.tar.gz", hash = "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb", size = 13167 } +sdist = { url = "https://files.pythonhosted.org/packages/54/d2/92fa3243712b9a3e8bafaf60aac366da1cada3639ca767ff4b5b3654ec28/notebook_shim-0.2.4.tar.gz", hash = "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb", size = 13167, upload-time = "2024-02-14T23:35:18.353Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef", size = 13307 }, + { url = "https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef", size = 13307, upload-time = "2024-02-14T23:35:16.286Z" }, ] [[package]] @@ -1976,261 +1897,246 @@ dependencies = [ { name = "numpy" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/44/bd/8a391e7c356366224734efd24da929cc4796fff468bfb179fe1af6548535/numcodecs-0.16.5.tar.gz", hash = "sha256:0d0fb60852f84c0bd9543cc4d2ab9eefd37fc8efcc410acd4777e62a1d300318", size = 6276387 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/af/85/1ac101a40ead81eaa1c7dc49a8827a30e2e436211b43ebdc63c590eb1347/numcodecs-0.16.5-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:78382dcea50622f2ef1e6e7a71dbe7f861d8fe376b27b7c297c26907304fef1e", size = 1621795 }, - { url = "https://files.pythonhosted.org/packages/0e/cc/0d97ef55dda48cb0f93d7b92d761208e7a99bd2eea6b0e859426e6a99a21/numcodecs-0.16.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2d04a19cb57a3c519b4127ac377cca6471aee1990d7c18f5b1e3a4fe1306689", size = 1153030 }, - { url = "https://files.pythonhosted.org/packages/5e/41/e120ee1b390730ac5987cde2afd82e2b8442cec315ab40b94b0373e93e73/numcodecs-0.16.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c043af648eb280cd61785c99c22ff5c3c3460f906eb51a8511327c4f5111b283", size = 8510503 }, - { url = "https://files.pythonhosted.org/packages/54/4b/195ac84cc8f6077b4f0f421e8daee21b7f1bd88cb7716414234379fe68ec/numcodecs-0.16.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c398919ef2eb0e56b8e97456f622640bfd3deed06de3acc976989cbcb22628a3", size = 9123428 }, - { url = "https://files.pythonhosted.org/packages/0f/5b/af02c417954f46e5c7bd5163ac251f535877d909fce54861c99ae197f6f6/numcodecs-0.16.5-cp311-cp311-win_amd64.whl", hash = "sha256:3820860ed302d4d84a1c66e70981ff959d5eb712555be4e7d8ced49888594773", size = 801542 }, - { url = "https://files.pythonhosted.org/packages/75/cc/55420f3641a67f78392dc0bc5d02cb9eb0a9dcebf2848d1ac77253ca61fa/numcodecs-0.16.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:24e675dc8d1550cd976a99479b87d872cb142632c75cc402fea04c08c4898523", size = 1656287 }, - { url = "https://files.pythonhosted.org/packages/f5/6c/86644987505dcb90ba6d627d6989c27bafb0699f9fd00187e06d05ea8594/numcodecs-0.16.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:94ddfa4341d1a3ab99989d13b01b5134abb687d3dab2ead54b450aefe4ad5bd6", size = 1148899 }, - { url = "https://files.pythonhosted.org/packages/97/1e/98aaddf272552d9fef1f0296a9939d1487914a239e98678f6b20f8b0a5c8/numcodecs-0.16.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b554ab9ecf69de7ca2b6b5e8bc696bd9747559cb4dd5127bd08d7a28bec59c3a", size = 8534814 }, - { url = "https://files.pythonhosted.org/packages/fb/53/78c98ef5c8b2b784453487f3e4d6c017b20747c58b470393e230c78d18e8/numcodecs-0.16.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ad1a379a45bd3491deab8ae6548313946744f868c21d5340116977ea3be5b1d6", size = 9173471 }, - { url = "https://files.pythonhosted.org/packages/1c/20/2fdec87fc7f8cec950d2b0bea603c12dc9f05b4966dc5924ba5a36a61bf6/numcodecs-0.16.5-cp312-cp312-win_amd64.whl", hash = "sha256:845a9857886ffe4a3172ba1c537ae5bcc01e65068c31cf1fce1a844bd1da050f", size = 801412 }, - { url = "https://files.pythonhosted.org/packages/38/38/071ced5a5fd1c85ba0e14ba721b66b053823e5176298c2f707e50bed11d9/numcodecs-0.16.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25be3a516ab677dad890760d357cfe081a371d9c0a2e9a204562318ac5969de3", size = 1654359 }, - { url = "https://files.pythonhosted.org/packages/d1/c0/5f84ba7525577c1b9909fc2d06ef11314825fc4ad4378f61d0e4c9883b4a/numcodecs-0.16.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0107e839ef75b854e969cb577e140b1aadb9847893937636582d23a2a4c6ce50", size = 1144237 }, - { url = "https://files.pythonhosted.org/packages/0b/00/787ea5f237b8ea7bc67140c99155f9c00b5baf11c49afc5f3bfefa298f95/numcodecs-0.16.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:015a7c859ecc2a06e2a548f64008c0ec3aaecabc26456c2c62f4278d8fc20597", size = 8483064 }, - { url = "https://files.pythonhosted.org/packages/c4/e6/d359fdd37498e74d26a167f7a51e54542e642ea47181eb4e643a69a066c3/numcodecs-0.16.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84230b4b9dad2392f2a84242bd6e3e659ac137b5a1ce3571d6965fca673e0903", size = 9126063 }, - { url = "https://files.pythonhosted.org/packages/27/72/6663cc0382ddbb866136c255c837bcb96cc7ce5e83562efec55e1b995941/numcodecs-0.16.5-cp313-cp313-win_amd64.whl", hash = "sha256:5088145502ad1ebf677ec47d00eb6f0fd600658217db3e0c070c321c85d6cf3d", size = 799275 }, - { url = "https://files.pythonhosted.org/packages/3c/9e/38e7ca8184c958b51f45d56a4aeceb1134ecde2d8bd157efadc98502cc42/numcodecs-0.16.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b05647b8b769e6bc8016e9fd4843c823ce5c9f2337c089fb5c9c4da05e5275de", size = 1654721 }, - { url = "https://files.pythonhosted.org/packages/a1/37/260fa42e7b2b08e6e00ad632f8dd620961a60a459426c26cea390f8c68d0/numcodecs-0.16.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3832bd1b5af8bb3e413076b7d93318c8e7d7b68935006b9fa36ca057d1725a8f", size = 1146887 }, - { url = "https://files.pythonhosted.org/packages/4e/15/e2e1151b5a8b14a15dfd4bb4abccce7fff7580f39bc34092780088835f3a/numcodecs-0.16.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49f7b7d24f103187f53135bed28bb9f0ed6b2e14c604664726487bb6d7c882e1", size = 8476987 }, - { url = "https://files.pythonhosted.org/packages/6d/30/16a57fc4d9fb0ba06c600408bd6634f2f1753c54a7a351c99c5e09b51ee2/numcodecs-0.16.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aec9736d81b70f337d89c4070ee3ffeff113f386fd789492fa152d26a15043e4", size = 9102377 }, - { url = "https://files.pythonhosted.org/packages/31/a5/a0425af36c20d55a3ea884db4b4efca25a43bea9214ba69ca7932dd997b4/numcodecs-0.16.5-cp314-cp314-win_amd64.whl", hash = "sha256:b16a14303800e9fb88abc39463ab4706c037647ac17e49e297faa5f7d7dbbf1d", size = 819022 }, +sdist = { url = "https://files.pythonhosted.org/packages/44/bd/8a391e7c356366224734efd24da929cc4796fff468bfb179fe1af6548535/numcodecs-0.16.5.tar.gz", hash = "sha256:0d0fb60852f84c0bd9543cc4d2ab9eefd37fc8efcc410acd4777e62a1d300318", size = 6276387, upload-time = "2025-11-21T02:49:48.986Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/85/1ac101a40ead81eaa1c7dc49a8827a30e2e436211b43ebdc63c590eb1347/numcodecs-0.16.5-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:78382dcea50622f2ef1e6e7a71dbe7f861d8fe376b27b7c297c26907304fef1e", size = 1621795, upload-time = "2025-11-21T02:49:17.418Z" }, + { url = "https://files.pythonhosted.org/packages/0e/cc/0d97ef55dda48cb0f93d7b92d761208e7a99bd2eea6b0e859426e6a99a21/numcodecs-0.16.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2d04a19cb57a3c519b4127ac377cca6471aee1990d7c18f5b1e3a4fe1306689", size = 1153030, upload-time = "2025-11-21T02:49:19.089Z" }, + { url = "https://files.pythonhosted.org/packages/5e/41/e120ee1b390730ac5987cde2afd82e2b8442cec315ab40b94b0373e93e73/numcodecs-0.16.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c043af648eb280cd61785c99c22ff5c3c3460f906eb51a8511327c4f5111b283", size = 8510503, upload-time = "2025-11-21T02:49:20.324Z" }, + { url = "https://files.pythonhosted.org/packages/54/4b/195ac84cc8f6077b4f0f421e8daee21b7f1bd88cb7716414234379fe68ec/numcodecs-0.16.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c398919ef2eb0e56b8e97456f622640bfd3deed06de3acc976989cbcb22628a3", size = 9123428, upload-time = "2025-11-21T02:49:22.328Z" }, + { url = "https://files.pythonhosted.org/packages/0f/5b/af02c417954f46e5c7bd5163ac251f535877d909fce54861c99ae197f6f6/numcodecs-0.16.5-cp311-cp311-win_amd64.whl", hash = "sha256:3820860ed302d4d84a1c66e70981ff959d5eb712555be4e7d8ced49888594773", size = 801542, upload-time = "2025-11-21T02:49:24.265Z" }, + { url = "https://files.pythonhosted.org/packages/75/cc/55420f3641a67f78392dc0bc5d02cb9eb0a9dcebf2848d1ac77253ca61fa/numcodecs-0.16.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:24e675dc8d1550cd976a99479b87d872cb142632c75cc402fea04c08c4898523", size = 1656287, upload-time = "2025-11-21T02:49:25.755Z" }, + { url = "https://files.pythonhosted.org/packages/f5/6c/86644987505dcb90ba6d627d6989c27bafb0699f9fd00187e06d05ea8594/numcodecs-0.16.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:94ddfa4341d1a3ab99989d13b01b5134abb687d3dab2ead54b450aefe4ad5bd6", size = 1148899, upload-time = "2025-11-21T02:49:26.87Z" }, + { url = "https://files.pythonhosted.org/packages/97/1e/98aaddf272552d9fef1f0296a9939d1487914a239e98678f6b20f8b0a5c8/numcodecs-0.16.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b554ab9ecf69de7ca2b6b5e8bc696bd9747559cb4dd5127bd08d7a28bec59c3a", size = 8534814, upload-time = "2025-11-21T02:49:28.547Z" }, + { url = "https://files.pythonhosted.org/packages/fb/53/78c98ef5c8b2b784453487f3e4d6c017b20747c58b470393e230c78d18e8/numcodecs-0.16.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ad1a379a45bd3491deab8ae6548313946744f868c21d5340116977ea3be5b1d6", size = 9173471, upload-time = "2025-11-21T02:49:30.444Z" }, + { url = "https://files.pythonhosted.org/packages/1c/20/2fdec87fc7f8cec950d2b0bea603c12dc9f05b4966dc5924ba5a36a61bf6/numcodecs-0.16.5-cp312-cp312-win_amd64.whl", hash = "sha256:845a9857886ffe4a3172ba1c537ae5bcc01e65068c31cf1fce1a844bd1da050f", size = 801412, upload-time = "2025-11-21T02:49:32.123Z" }, + { url = "https://files.pythonhosted.org/packages/38/38/071ced5a5fd1c85ba0e14ba721b66b053823e5176298c2f707e50bed11d9/numcodecs-0.16.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25be3a516ab677dad890760d357cfe081a371d9c0a2e9a204562318ac5969de3", size = 1654359, upload-time = "2025-11-21T02:49:33.673Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c0/5f84ba7525577c1b9909fc2d06ef11314825fc4ad4378f61d0e4c9883b4a/numcodecs-0.16.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0107e839ef75b854e969cb577e140b1aadb9847893937636582d23a2a4c6ce50", size = 1144237, upload-time = "2025-11-21T02:49:35.294Z" }, + { url = "https://files.pythonhosted.org/packages/0b/00/787ea5f237b8ea7bc67140c99155f9c00b5baf11c49afc5f3bfefa298f95/numcodecs-0.16.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:015a7c859ecc2a06e2a548f64008c0ec3aaecabc26456c2c62f4278d8fc20597", size = 8483064, upload-time = "2025-11-21T02:49:36.454Z" }, + { url = "https://files.pythonhosted.org/packages/c4/e6/d359fdd37498e74d26a167f7a51e54542e642ea47181eb4e643a69a066c3/numcodecs-0.16.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84230b4b9dad2392f2a84242bd6e3e659ac137b5a1ce3571d6965fca673e0903", size = 9126063, upload-time = "2025-11-21T02:49:38.018Z" }, + { url = "https://files.pythonhosted.org/packages/27/72/6663cc0382ddbb866136c255c837bcb96cc7ce5e83562efec55e1b995941/numcodecs-0.16.5-cp313-cp313-win_amd64.whl", hash = "sha256:5088145502ad1ebf677ec47d00eb6f0fd600658217db3e0c070c321c85d6cf3d", size = 799275, upload-time = "2025-11-21T02:49:39.558Z" }, + { url = "https://files.pythonhosted.org/packages/3c/9e/38e7ca8184c958b51f45d56a4aeceb1134ecde2d8bd157efadc98502cc42/numcodecs-0.16.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b05647b8b769e6bc8016e9fd4843c823ce5c9f2337c089fb5c9c4da05e5275de", size = 1654721, upload-time = "2025-11-21T02:49:40.602Z" }, + { url = "https://files.pythonhosted.org/packages/a1/37/260fa42e7b2b08e6e00ad632f8dd620961a60a459426c26cea390f8c68d0/numcodecs-0.16.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3832bd1b5af8bb3e413076b7d93318c8e7d7b68935006b9fa36ca057d1725a8f", size = 1146887, upload-time = "2025-11-21T02:49:41.721Z" }, + { url = "https://files.pythonhosted.org/packages/4e/15/e2e1151b5a8b14a15dfd4bb4abccce7fff7580f39bc34092780088835f3a/numcodecs-0.16.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49f7b7d24f103187f53135bed28bb9f0ed6b2e14c604664726487bb6d7c882e1", size = 8476987, upload-time = "2025-11-21T02:49:43.363Z" }, + { url = "https://files.pythonhosted.org/packages/6d/30/16a57fc4d9fb0ba06c600408bd6634f2f1753c54a7a351c99c5e09b51ee2/numcodecs-0.16.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aec9736d81b70f337d89c4070ee3ffeff113f386fd789492fa152d26a15043e4", size = 9102377, upload-time = "2025-11-21T02:49:45.508Z" }, + { url = "https://files.pythonhosted.org/packages/31/a5/a0425af36c20d55a3ea884db4b4efca25a43bea9214ba69ca7932dd997b4/numcodecs-0.16.5-cp314-cp314-win_amd64.whl", hash = "sha256:b16a14303800e9fb88abc39463ab4706c037647ac17e49e297faa5f7d7dbbf1d", size = 819022, upload-time = "2025-11-21T02:49:47.39Z" }, ] [[package]] name = "numpy" -version = "2.4.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/10/8b/c265f4823726ab832de836cdd184d0986dcf94480f81e8739692a7ac7af2/numpy-2.4.3.tar.gz", hash = "sha256:483a201202b73495f00dbc83796c6ae63137a9bdade074f7648b3e32613412dd", size = 20727743, upload-time = "2026-03-09T07:58:53.426Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/51/5093a2df15c4dc19da3f79d1021e891f5dcf1d9d1db6ba38891d5590f3fe/numpy-2.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:33b3bf58ee84b172c067f56aeadc7ee9ab6de69c5e800ab5b10295d54c581adb", size = 16957183, upload-time = "2026-03-09T07:55:57.774Z" }, - { url = "https://files.pythonhosted.org/packages/b5/7c/c061f3de0630941073d2598dc271ac2f6cbcf5c83c74a5870fea07488333/numpy-2.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8ba7b51e71c05aa1f9bc3641463cd82308eab40ce0d5c7e1fd4038cbf9938147", size = 14968734, upload-time = "2026-03-09T07:56:00.494Z" }, - { url = "https://files.pythonhosted.org/packages/ef/27/d26c85cbcd86b26e4f125b0668e7a7c0542d19dd7d23ee12e87b550e95b5/numpy-2.4.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a1988292870c7cb9d0ebb4cc96b4d447513a9644801de54606dc7aabf2b7d920", size = 5475288, upload-time = "2026-03-09T07:56:02.857Z" }, - { url = "https://files.pythonhosted.org/packages/2b/09/3c4abbc1dcd8010bf1a611d174c7aa689fc505585ec806111b4406f6f1b1/numpy-2.4.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:23b46bb6d8ecb68b58c09944483c135ae5f0e9b8d8858ece5e4ead783771d2a9", size = 6805253, upload-time = "2026-03-09T07:56:04.53Z" }, - { url = "https://files.pythonhosted.org/packages/21/bc/e7aa3f6817e40c3f517d407742337cbb8e6fc4b83ce0b55ab780c829243b/numpy-2.4.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a016db5c5dba78fa8fe9f5d80d6708f9c42ab087a739803c0ac83a43d686a470", size = 15969479, upload-time = "2026-03-09T07:56:06.638Z" }, - { url = "https://files.pythonhosted.org/packages/78/51/9f5d7a41f0b51649ddf2f2320595e15e122a40610b233d51928dd6c92353/numpy-2.4.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:715de7f82e192e8cae5a507a347d97ad17598f8e026152ca97233e3666daaa71", size = 16901035, upload-time = "2026-03-09T07:56:09.405Z" }, - { url = "https://files.pythonhosted.org/packages/64/6e/b221dd847d7181bc5ee4857bfb026182ef69499f9305eb1371cbb1aea626/numpy-2.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2ddb7919366ee468342b91dea2352824c25b55814a987847b6c52003a7c97f15", size = 17325657, upload-time = "2026-03-09T07:56:12.067Z" }, - { url = "https://files.pythonhosted.org/packages/eb/b8/8f3fd2da596e1063964b758b5e3c970aed1949a05200d7e3d46a9d46d643/numpy-2.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a315e5234d88067f2d97e1f2ef670a7569df445d55400f1e33d117418d008d52", size = 18635512, upload-time = "2026-03-09T07:56:14.629Z" }, - { url = "https://files.pythonhosted.org/packages/5c/24/2993b775c37e39d2f8ab4125b44337ab0b2ba106c100980b7c274a22bee7/numpy-2.4.3-cp311-cp311-win32.whl", hash = "sha256:2b3f8d2c4589b1a2028d2a770b0fc4d1f332fb5e01521f4de3199a896d158ddd", size = 6238100, upload-time = "2026-03-09T07:56:17.243Z" }, - { url = "https://files.pythonhosted.org/packages/76/1d/edccf27adedb754db7c4511d5eac8b83f004ae948fe2d3509e8b78097d4c/numpy-2.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:77e76d932c49a75617c6d13464e41203cd410956614d0a0e999b25e9e8d27eec", size = 12609816, upload-time = "2026-03-09T07:56:19.089Z" }, - { url = "https://files.pythonhosted.org/packages/92/82/190b99153480076c8dce85f4cfe7d53ea84444145ffa54cb58dcd460d66b/numpy-2.4.3-cp311-cp311-win_arm64.whl", hash = "sha256:eb610595dd91560905c132c709412b512135a60f1851ccbd2c959e136431ff67", size = 10485757, upload-time = "2026-03-09T07:56:21.753Z" }, - { url = "https://files.pythonhosted.org/packages/a9/ed/6388632536f9788cea23a3a1b629f25b43eaacd7d7377e5d6bc7b9deb69b/numpy-2.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:61b0cbabbb6126c8df63b9a3a0c4b1f44ebca5e12ff6997b80fcf267fb3150ef", size = 16669628, upload-time = "2026-03-09T07:56:24.252Z" }, - { url = "https://files.pythonhosted.org/packages/74/1b/ee2abfc68e1ce728b2958b6ba831d65c62e1b13ce3017c13943f8f9b5b2e/numpy-2.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7395e69ff32526710748f92cd8c9849b361830968ea3e24a676f272653e8983e", size = 14696872, upload-time = "2026-03-09T07:56:26.991Z" }, - { url = "https://files.pythonhosted.org/packages/ba/d1/780400e915ff5638166f11ca9dc2c5815189f3d7cf6f8759a1685e586413/numpy-2.4.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:abdce0f71dcb4a00e4e77f3faf05e4616ceccfe72ccaa07f47ee79cda3b7b0f4", size = 5203489, upload-time = "2026-03-09T07:56:29.414Z" }, - { url = "https://files.pythonhosted.org/packages/0b/bb/baffa907e9da4cc34a6e556d6d90e032f6d7a75ea47968ea92b4858826c4/numpy-2.4.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:48da3a4ee1336454b07497ff7ec83903efa5505792c4e6d9bf83d99dc07a1e18", size = 6550814, upload-time = "2026-03-09T07:56:32.225Z" }, - { url = "https://files.pythonhosted.org/packages/7b/12/8c9f0c6c95f76aeb20fc4a699c33e9f827fa0d0f857747c73bb7b17af945/numpy-2.4.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32e3bef222ad6b052280311d1d60db8e259e4947052c3ae7dd6817451fc8a4c5", size = 15666601, upload-time = "2026-03-09T07:56:34.461Z" }, - { url = "https://files.pythonhosted.org/packages/bd/79/cc665495e4d57d0aa6fbcc0aa57aa82671dfc78fbf95fe733ed86d98f52a/numpy-2.4.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7dd01a46700b1967487141a66ac1a3cf0dd8ebf1f08db37d46389401512ca97", size = 16621358, upload-time = "2026-03-09T07:56:36.852Z" }, - { url = "https://files.pythonhosted.org/packages/a8/40/b4ecb7224af1065c3539f5ecfff879d090de09608ad1008f02c05c770cb3/numpy-2.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:76f0f283506c28b12bba319c0fab98217e9f9b54e6160e9c79e9f7348ba32e9c", size = 17016135, upload-time = "2026-03-09T07:56:39.337Z" }, - { url = "https://files.pythonhosted.org/packages/f7/b1/6a88e888052eed951afed7a142dcdf3b149a030ca59b4c71eef085858e43/numpy-2.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:737f630a337364665aba3b5a77e56a68cc42d350edd010c345d65a3efa3addcc", size = 18345816, upload-time = "2026-03-09T07:56:42.31Z" }, - { url = "https://files.pythonhosted.org/packages/f3/8f/103a60c5f8c3d7fc678c19cd7b2476110da689ccb80bc18050efbaeae183/numpy-2.4.3-cp312-cp312-win32.whl", hash = "sha256:26952e18d82a1dbbc2f008d402021baa8d6fc8e84347a2072a25e08b46d698b9", size = 5960132, upload-time = "2026-03-09T07:56:44.851Z" }, - { url = "https://files.pythonhosted.org/packages/d7/7c/f5ee1bf6ed888494978046a809df2882aad35d414b622893322df7286879/numpy-2.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:65f3c2455188f09678355f5cae1f959a06b778bc66d535da07bf2ef20cd319d5", size = 12316144, upload-time = "2026-03-09T07:56:47.057Z" }, - { url = "https://files.pythonhosted.org/packages/71/46/8d1cb3f7a00f2fb6394140e7e6623696e54c6318a9d9691bb4904672cf42/numpy-2.4.3-cp312-cp312-win_arm64.whl", hash = "sha256:2abad5c7fef172b3377502bde47892439bae394a71bc329f31df0fd829b41a9e", size = 10220364, upload-time = "2026-03-09T07:56:49.849Z" }, - { url = "https://files.pythonhosted.org/packages/b6/d0/1fe47a98ce0df229238b77611340aff92d52691bcbc10583303181abf7fc/numpy-2.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b346845443716c8e542d54112966383b448f4a3ba5c66409771b8c0889485dd3", size = 16665297, upload-time = "2026-03-09T07:56:52.296Z" }, - { url = "https://files.pythonhosted.org/packages/27/d9/4e7c3f0e68dfa91f21c6fb6cf839bc829ec920688b1ce7ec722b1a6202fb/numpy-2.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2629289168f4897a3c4e23dc98d6f1731f0fc0fe52fb9db19f974041e4cc12b9", size = 14691853, upload-time = "2026-03-09T07:56:54.992Z" }, - { url = "https://files.pythonhosted.org/packages/3a/66/bd096b13a87549683812b53ab211e6d413497f84e794fb3c39191948da97/numpy-2.4.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:bb2e3cf95854233799013779216c57e153c1ee67a0bf92138acca0e429aefaee", size = 5198435, upload-time = "2026-03-09T07:56:57.184Z" }, - { url = "https://files.pythonhosted.org/packages/a2/2f/687722910b5a5601de2135c891108f51dfc873d8e43c8ed9f4ebb440b4a2/numpy-2.4.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:7f3408ff897f8ab07a07fbe2823d7aee6ff644c097cc1f90382511fe982f647f", size = 6546347, upload-time = "2026-03-09T07:56:59.531Z" }, - { url = "https://files.pythonhosted.org/packages/bf/ec/7971c4e98d86c564750393fab8d7d83d0a9432a9d78bb8a163a6dc59967a/numpy-2.4.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:decb0eb8a53c3b009b0962378065589685d66b23467ef5dac16cbe818afde27f", size = 15664626, upload-time = "2026-03-09T07:57:01.385Z" }, - { url = "https://files.pythonhosted.org/packages/7e/eb/7daecbea84ec935b7fc732e18f532073064a3816f0932a40a17f3349185f/numpy-2.4.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5f51900414fc9204a0e0da158ba2ac52b75656e7dce7e77fb9f84bfa343b4cc", size = 16608916, upload-time = "2026-03-09T07:57:04.008Z" }, - { url = "https://files.pythonhosted.org/packages/df/58/2a2b4a817ffd7472dca4421d9f0776898b364154e30c95f42195041dc03b/numpy-2.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6bd06731541f89cdc01b261ba2c9e037f1543df7472517836b78dfb15bd6e476", size = 17015824, upload-time = "2026-03-09T07:57:06.347Z" }, - { url = "https://files.pythonhosted.org/packages/4a/ca/627a828d44e78a418c55f82dd4caea8ea4a8ef24e5144d9e71016e52fb40/numpy-2.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22654fe6be0e5206f553a9250762c653d3698e46686eee53b399ab90da59bd92", size = 18334581, upload-time = "2026-03-09T07:57:09.114Z" }, - { url = "https://files.pythonhosted.org/packages/cd/c0/76f93962fc79955fcba30a429b62304332345f22d4daec1cb33653425643/numpy-2.4.3-cp313-cp313-win32.whl", hash = "sha256:d71e379452a2f670ccb689ec801b1218cd3983e253105d6e83780967e899d687", size = 5958618, upload-time = "2026-03-09T07:57:11.432Z" }, - { url = "https://files.pythonhosted.org/packages/b1/3c/88af0040119209b9b5cb59485fa48b76f372c73068dbf9254784b975ac53/numpy-2.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:0a60e17a14d640f49146cb38e3f105f571318db7826d9b6fef7e4dce758faecd", size = 12312824, upload-time = "2026-03-09T07:57:13.586Z" }, - { url = "https://files.pythonhosted.org/packages/58/ce/3d07743aced3d173f877c3ef6a454c2174ba42b584ab0b7e6d99374f51ed/numpy-2.4.3-cp313-cp313-win_arm64.whl", hash = "sha256:c9619741e9da2059cd9c3f206110b97583c7152c1dc9f8aafd4beb450ac1c89d", size = 10221218, upload-time = "2026-03-09T07:57:16.183Z" }, - { url = "https://files.pythonhosted.org/packages/62/09/d96b02a91d09e9d97862f4fc8bfebf5400f567d8eb1fe4b0cc4795679c15/numpy-2.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7aa4e54f6469300ebca1d9eb80acd5253cdfa36f2c03d79a35883687da430875", size = 14819570, upload-time = "2026-03-09T07:57:18.564Z" }, - { url = "https://files.pythonhosted.org/packages/b5/ca/0b1aba3905fdfa3373d523b2b15b19029f4f3031c87f4066bd9d20ef6c6b/numpy-2.4.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d1b90d840b25874cf5cd20c219af10bac3667db3876d9a495609273ebe679070", size = 5326113, upload-time = "2026-03-09T07:57:21.052Z" }, - { url = "https://files.pythonhosted.org/packages/c0/63/406e0fd32fcaeb94180fd6a4c41e55736d676c54346b7efbce548b94a914/numpy-2.4.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a749547700de0a20a6718293396ec237bb38218049cfce788e08fcb716e8cf73", size = 6646370, upload-time = "2026-03-09T07:57:22.804Z" }, - { url = "https://files.pythonhosted.org/packages/b6/d0/10f7dc157d4b37af92720a196be6f54f889e90dcd30dce9dc657ed92c257/numpy-2.4.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f3c4a151a2e529adf49c1d54f0f57ff8f9b233ee4d44af623a81553ab86368", size = 15723499, upload-time = "2026-03-09T07:57:24.693Z" }, - { url = "https://files.pythonhosted.org/packages/66/f1/d1c2bf1161396629701bc284d958dc1efa3a5a542aab83cf11ee6eb4cba5/numpy-2.4.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22c31dc07025123aedf7f2db9e91783df13f1776dc52c6b22c620870dc0fab22", size = 16657164, upload-time = "2026-03-09T07:57:27.676Z" }, - { url = "https://files.pythonhosted.org/packages/1a/be/cca19230b740af199ac47331a21c71e7a3d0ba59661350483c1600d28c37/numpy-2.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:148d59127ac95979d6f07e4d460f934ebdd6eed641db9c0db6c73026f2b2101a", size = 17081544, upload-time = "2026-03-09T07:57:30.664Z" }, - { url = "https://files.pythonhosted.org/packages/b9/c5/9602b0cbb703a0936fb40f8a95407e8171935b15846de2f0776e08af04c7/numpy-2.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a97cbf7e905c435865c2d939af3d93f99d18eaaa3cabe4256f4304fb51604349", size = 18380290, upload-time = "2026-03-09T07:57:33.763Z" }, - { url = "https://files.pythonhosted.org/packages/ed/81/9f24708953cd30be9ee36ec4778f4b112b45165812f2ada4cc5ea1c1f254/numpy-2.4.3-cp313-cp313t-win32.whl", hash = "sha256:be3b8487d725a77acccc9924f65fd8bce9af7fac8c9820df1049424a2115af6c", size = 6082814, upload-time = "2026-03-09T07:57:36.491Z" }, - { url = "https://files.pythonhosted.org/packages/e2/9e/52f6eaa13e1a799f0ab79066c17f7016a4a8ae0c1aefa58c82b4dab690b4/numpy-2.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1ec84fd7c8e652b0f4aaaf2e6e9cc8eaa9b1b80a537e06b2e3a2fb176eedcb26", size = 12452673, upload-time = "2026-03-09T07:57:38.281Z" }, - { url = "https://files.pythonhosted.org/packages/c4/04/b8cece6ead0b30c9fbd99bb835ad7ea0112ac5f39f069788c5558e3b1ab2/numpy-2.4.3-cp313-cp313t-win_arm64.whl", hash = "sha256:120df8c0a81ebbf5b9020c91439fccd85f5e018a927a39f624845be194a2be02", size = 10290907, upload-time = "2026-03-09T07:57:40.747Z" }, - { url = "https://files.pythonhosted.org/packages/70/ae/3936f79adebf8caf81bd7a599b90a561334a658be4dcc7b6329ebf4ee8de/numpy-2.4.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5884ce5c7acfae1e4e1b6fde43797d10aa506074d25b531b4f54bde33c0c31d4", size = 16664563, upload-time = "2026-03-09T07:57:43.817Z" }, - { url = "https://files.pythonhosted.org/packages/9b/62/760f2b55866b496bb1fa7da2a6db076bef908110e568b02fcfc1422e2a3a/numpy-2.4.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:297837823f5bc572c5f9379b0c9f3a3365f08492cbdc33bcc3af174372ebb168", size = 14702161, upload-time = "2026-03-09T07:57:46.169Z" }, - { url = "https://files.pythonhosted.org/packages/32/af/a7a39464e2c0a21526fb4fb76e346fb172ebc92f6d1c7a07c2c139cc17b1/numpy-2.4.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:a111698b4a3f8dcbe54c64a7708f049355abd603e619013c346553c1fd4ca90b", size = 5208738, upload-time = "2026-03-09T07:57:48.506Z" }, - { url = "https://files.pythonhosted.org/packages/29/8c/2a0cf86a59558fa078d83805589c2de490f29ed4fb336c14313a161d358a/numpy-2.4.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:4bd4741a6a676770e0e97fe9ab2e51de01183df3dcbcec591d26d331a40de950", size = 6543618, upload-time = "2026-03-09T07:57:50.591Z" }, - { url = "https://files.pythonhosted.org/packages/aa/b8/612ce010c0728b1c363fa4ea3aa4c22fe1c5da1de008486f8c2f5cb92fae/numpy-2.4.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:54f29b877279d51e210e0c80709ee14ccbbad647810e8f3d375561c45ef613dd", size = 15680676, upload-time = "2026-03-09T07:57:52.34Z" }, - { url = "https://files.pythonhosted.org/packages/a9/7e/4f120ecc54ba26ddf3dc348eeb9eb063f421de65c05fc961941798feea18/numpy-2.4.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:679f2a834bae9020f81534671c56fd0cc76dd7e5182f57131478e23d0dc59e24", size = 16613492, upload-time = "2026-03-09T07:57:54.91Z" }, - { url = "https://files.pythonhosted.org/packages/2c/86/1b6020db73be330c4b45d5c6ee4295d59cfeef0e3ea323959d053e5a6909/numpy-2.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d84f0f881cb2225c2dfd7f78a10a5645d487a496c6668d6cc39f0f114164f3d0", size = 17031789, upload-time = "2026-03-09T07:57:57.641Z" }, - { url = "https://files.pythonhosted.org/packages/07/3a/3b90463bf41ebc21d1b7e06079f03070334374208c0f9a1f05e4ae8455e7/numpy-2.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d213c7e6e8d211888cc359bab7199670a00f5b82c0978b9d1c75baf1eddbeac0", size = 18339941, upload-time = "2026-03-09T07:58:00.577Z" }, - { url = "https://files.pythonhosted.org/packages/a8/74/6d736c4cd962259fd8bae9be27363eb4883a2f9069763747347544c2a487/numpy-2.4.3-cp314-cp314-win32.whl", hash = "sha256:52077feedeff7c76ed7c9f1a0428558e50825347b7545bbb8523da2cd55c547a", size = 6007503, upload-time = "2026-03-09T07:58:03.331Z" }, - { url = "https://files.pythonhosted.org/packages/48/39/c56ef87af669364356bb011922ef0734fc49dad51964568634c72a009488/numpy-2.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:0448e7f9caefb34b4b7dd2b77f21e8906e5d6f0365ad525f9f4f530b13df2afc", size = 12444915, upload-time = "2026-03-09T07:58:06.353Z" }, - { url = "https://files.pythonhosted.org/packages/9d/1f/ab8528e38d295fd349310807496fabb7cf9fe2e1f70b97bc20a483ea9d4a/numpy-2.4.3-cp314-cp314-win_arm64.whl", hash = "sha256:b44fd60341c4d9783039598efadd03617fa28d041fc37d22b62d08f2027fa0e7", size = 10494875, upload-time = "2026-03-09T07:58:08.734Z" }, - { url = "https://files.pythonhosted.org/packages/e6/ef/b7c35e4d5ef141b836658ab21a66d1a573e15b335b1d111d31f26c8ef80f/numpy-2.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0a195f4216be9305a73c0e91c9b026a35f2161237cf1c6de9b681637772ea657", size = 14822225, upload-time = "2026-03-09T07:58:11.034Z" }, - { url = "https://files.pythonhosted.org/packages/cd/8d/7730fa9278cf6648639946cc816e7cc89f0d891602584697923375f801ed/numpy-2.4.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:cd32fbacb9fd1bf041bf8e89e4576b6f00b895f06d00914820ae06a616bdfef7", size = 5328769, upload-time = "2026-03-09T07:58:13.67Z" }, - { url = "https://files.pythonhosted.org/packages/47/01/d2a137317c958b074d338807c1b6a383406cdf8b8e53b075d804cc3d211d/numpy-2.4.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:2e03c05abaee1f672e9d67bc858f300b5ccba1c21397211e8d77d98350972093", size = 6649461, upload-time = "2026-03-09T07:58:15.912Z" }, - { url = "https://files.pythonhosted.org/packages/5c/34/812ce12bc0f00272a4b0ec0d713cd237cb390666eb6206323d1cc9cedbb2/numpy-2.4.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d1ce23cce91fcea443320a9d0ece9b9305d4368875bab09538f7a5b4131938a", size = 15725809, upload-time = "2026-03-09T07:58:17.787Z" }, - { url = "https://files.pythonhosted.org/packages/25/c0/2aed473a4823e905e765fee3dc2cbf504bd3e68ccb1150fbdabd5c39f527/numpy-2.4.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c59020932feb24ed49ffd03704fbab89f22aa9c0d4b180ff45542fe8918f5611", size = 16655242, upload-time = "2026-03-09T07:58:20.476Z" }, - { url = "https://files.pythonhosted.org/packages/f2/c8/7e052b2fc87aa0e86de23f20e2c42bd261c624748aa8efd2c78f7bb8d8c6/numpy-2.4.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9684823a78a6cd6ad7511fc5e25b07947d1d5b5e2812c93fe99d7d4195130720", size = 17080660, upload-time = "2026-03-09T07:58:23.067Z" }, - { url = "https://files.pythonhosted.org/packages/f3/3d/0876746044db2adcb11549f214d104f2e1be00f07a67edbb4e2812094847/numpy-2.4.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0200b25c687033316fb39f0ff4e3e690e8957a2c3c8d22499891ec58c37a3eb5", size = 18380384, upload-time = "2026-03-09T07:58:25.839Z" }, - { url = "https://files.pythonhosted.org/packages/07/12/8160bea39da3335737b10308df4f484235fd297f556745f13092aa039d3b/numpy-2.4.3-cp314-cp314t-win32.whl", hash = "sha256:5e10da9e93247e554bb1d22f8edc51847ddd7dde52d85ce31024c1b4312bfba0", size = 6154547, upload-time = "2026-03-09T07:58:28.289Z" }, - { url = "https://files.pythonhosted.org/packages/42/f3/76534f61f80d74cc9cdf2e570d3d4eeb92c2280a27c39b0aaf471eda7b48/numpy-2.4.3-cp314-cp314t-win_amd64.whl", hash = "sha256:45f003dbdffb997a03da2d1d0cb41fbd24a87507fb41605c0420a3db5bd4667b", size = 12633645, upload-time = "2026-03-09T07:58:30.384Z" }, - { url = "https://files.pythonhosted.org/packages/1f/b6/7c0d4334c15983cec7f92a69e8ce9b1e6f31857e5ee3a413ac424e6bd63d/numpy-2.4.3-cp314-cp314t-win_arm64.whl", hash = "sha256:4d382735cecd7bcf090172489a525cd7d4087bc331f7df9f60ddc9a296cf208e", size = 10565454, upload-time = "2026-03-09T07:58:33.031Z" }, - { url = "https://files.pythonhosted.org/packages/64/e4/4dab9fb43c83719c29241c535d9e07be73bea4bc0c6686c5816d8e1b6689/numpy-2.4.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c6b124bfcafb9e8d3ed09130dbee44848c20b3e758b6bbf006e641778927c028", size = 16834892, upload-time = "2026-03-09T07:58:35.334Z" }, - { url = "https://files.pythonhosted.org/packages/c9/29/f8b6d4af90fed3dfda84ebc0df06c9833d38880c79ce954e5b661758aa31/numpy-2.4.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:76dbb9d4e43c16cf9aa711fcd8de1e2eeb27539dcefb60a1d5e9f12fae1d1ed8", size = 14893070, upload-time = "2026-03-09T07:58:37.7Z" }, - { url = "https://files.pythonhosted.org/packages/9a/04/a19b3c91dbec0a49269407f15d5753673a09832daed40c45e8150e6fa558/numpy-2.4.3-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:29363fbfa6f8ee855d7569c96ce524845e3d726d6c19b29eceec7dd555dab152", size = 5399609, upload-time = "2026-03-09T07:58:39.853Z" }, - { url = "https://files.pythonhosted.org/packages/79/34/4d73603f5420eab89ea8a67097b31364bf7c30f811d4dd84b1659c7476d9/numpy-2.4.3-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:bc71942c789ef415a37f0d4eab90341425a00d538cd0642445d30b41023d3395", size = 6714355, upload-time = "2026-03-09T07:58:42.365Z" }, - { url = "https://files.pythonhosted.org/packages/58/ad/1100d7229bb248394939a12a8074d485b655e8ed44207d328fdd7fcebc7b/numpy-2.4.3-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e58765ad74dcebd3ef0208a5078fba32dc8ec3578fe84a604432950cd043d79", size = 15800434, upload-time = "2026-03-09T07:58:44.837Z" }, - { url = "https://files.pythonhosted.org/packages/0c/fd/16d710c085d28ba4feaf29ac60c936c9d662e390344f94a6beaa2ac9899b/numpy-2.4.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e236dbda4e1d319d681afcbb136c0c4a8e0f1a5c58ceec2adebb547357fe857", size = 16729409, upload-time = "2026-03-09T07:58:47.972Z" }, - { url = "https://files.pythonhosted.org/packages/57/a7/b35835e278c18b85206834b3aa3abe68e77a98769c59233d1f6300284781/numpy-2.4.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:4b42639cdde6d24e732ff823a3fa5b701d8acad89c4142bc1d0bd6dc85200ba5", size = 12504685, upload-time = "2026-03-09T07:58:50.525Z" }, +version = "2.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/44/71852273146957899753e69986246d6a176061ea183407e95418c2aa4d9a/numpy-2.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7e88598032542bd49af7c4747541422884219056c268823ef6e5e89851c8825", size = 16955478, upload-time = "2026-01-31T23:10:25.623Z" }, + { url = "https://files.pythonhosted.org/packages/74/41/5d17d4058bd0cd96bcbd4d9ff0fb2e21f52702aab9a72e4a594efa18692f/numpy-2.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7edc794af8b36ca37ef5fcb5e0d128c7e0595c7b96a2318d1badb6fcd8ee86b1", size = 14965467, upload-time = "2026-01-31T23:10:28.186Z" }, + { url = "https://files.pythonhosted.org/packages/49/48/fb1ce8136c19452ed15f033f8aee91d5defe515094e330ce368a0647846f/numpy-2.4.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6e9f61981ace1360e42737e2bae58b27bf28a1b27e781721047d84bd754d32e7", size = 5475172, upload-time = "2026-01-31T23:10:30.848Z" }, + { url = "https://files.pythonhosted.org/packages/40/a9/3feb49f17bbd1300dd2570432961f5c8a4ffeff1db6f02c7273bd020a4c9/numpy-2.4.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cb7bbb88aa74908950d979eeaa24dbdf1a865e3c7e45ff0121d8f70387b55f73", size = 6805145, upload-time = "2026-01-31T23:10:32.352Z" }, + { url = "https://files.pythonhosted.org/packages/3f/39/fdf35cbd6d6e2fcad42fcf85ac04a85a0d0fbfbf34b30721c98d602fd70a/numpy-2.4.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f069069931240b3fc703f1e23df63443dbd6390614c8c44a87d96cd0ec81eb1", size = 15966084, upload-time = "2026-01-31T23:10:34.502Z" }, + { url = "https://files.pythonhosted.org/packages/1b/46/6fa4ea94f1ddf969b2ee941290cca6f1bfac92b53c76ae5f44afe17ceb69/numpy-2.4.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c02ef4401a506fb60b411467ad501e1429a3487abca4664871d9ae0b46c8ba32", size = 16899477, upload-time = "2026-01-31T23:10:37.075Z" }, + { url = "https://files.pythonhosted.org/packages/09/a1/2a424e162b1a14a5bd860a464ab4e07513916a64ab1683fae262f735ccd2/numpy-2.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2653de5c24910e49c2b106499803124dde62a5a1fe0eedeaecf4309a5f639390", size = 17323429, upload-time = "2026-01-31T23:10:39.704Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a2/73014149ff250628df72c58204822ac01d768697913881aacf839ff78680/numpy-2.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1ae241bbfc6ae276f94a170b14785e561cb5e7f626b6688cf076af4110887413", size = 18635109, upload-time = "2026-01-31T23:10:41.924Z" }, + { url = "https://files.pythonhosted.org/packages/6c/0c/73e8be2f1accd56df74abc1c5e18527822067dced5ec0861b5bb882c2ce0/numpy-2.4.2-cp311-cp311-win32.whl", hash = "sha256:df1b10187212b198dd45fa943d8985a3c8cf854aed4923796e0e019e113a1bda", size = 6237915, upload-time = "2026-01-31T23:10:45.26Z" }, + { url = "https://files.pythonhosted.org/packages/76/ae/e0265e0163cf127c24c3969d29f1c4c64551a1e375d95a13d32eab25d364/numpy-2.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:b9c618d56a29c9cb1c4da979e9899be7578d2e0b3c24d52079c166324c9e8695", size = 12607972, upload-time = "2026-01-31T23:10:47.021Z" }, + { url = "https://files.pythonhosted.org/packages/29/a5/c43029af9b8014d6ea157f192652c50042e8911f4300f8f6ed3336bf437f/numpy-2.4.2-cp311-cp311-win_arm64.whl", hash = "sha256:47c5a6ed21d9452b10227e5e8a0e1c22979811cad7dcc19d8e3e2fb8fa03f1a3", size = 10485763, upload-time = "2026-01-31T23:10:50.087Z" }, + { url = "https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a", size = 16667963, upload-time = "2026-01-31T23:10:52.147Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1", size = 14693571, upload-time = "2026-01-31T23:10:54.789Z" }, + { url = "https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e", size = 5203469, upload-time = "2026-01-31T23:10:57.343Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27", size = 6550820, upload-time = "2026-01-31T23:10:59.429Z" }, + { url = "https://files.pythonhosted.org/packages/b1/91/b97fdfd12dc75b02c44e26c6638241cc004d4079a0321a69c62f51470c4c/numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548", size = 15663067, upload-time = "2026-01-31T23:11:01.291Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c6/a18e59f3f0b8071cc85cbc8d80cd02d68aa9710170b2553a117203d46936/numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f", size = 16619782, upload-time = "2026-01-31T23:11:03.669Z" }, + { url = "https://files.pythonhosted.org/packages/b7/83/9751502164601a79e18847309f5ceec0b1446d7b6aa12305759b72cf98b2/numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460", size = 17013128, upload-time = "2026-01-31T23:11:05.913Z" }, + { url = "https://files.pythonhosted.org/packages/61/c4/c4066322256ec740acc1c8923a10047818691d2f8aec254798f3dd90f5f2/numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba", size = 18345324, upload-time = "2026-01-31T23:11:08.248Z" }, + { url = "https://files.pythonhosted.org/packages/ab/af/6157aa6da728fa4525a755bfad486ae7e3f76d4c1864138003eb84328497/numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f", size = 5960282, upload-time = "2026-01-31T23:11:10.497Z" }, + { url = "https://files.pythonhosted.org/packages/92/0f/7ceaaeaacb40567071e94dbf2c9480c0ae453d5bb4f52bea3892c39dc83c/numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85", size = 12314210, upload-time = "2026-01-31T23:11:12.176Z" }, + { url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z" }, + { url = "https://files.pythonhosted.org/packages/a1/22/815b9fe25d1d7ae7d492152adbc7226d3eff731dffc38fe970589fcaaa38/numpy-2.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c", size = 16663696, upload-time = "2026-01-31T23:11:17.516Z" }, + { url = "https://files.pythonhosted.org/packages/09/f0/817d03a03f93ba9c6c8993de509277d84e69f9453601915e4a69554102a1/numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979", size = 14688322, upload-time = "2026-01-31T23:11:19.883Z" }, + { url = "https://files.pythonhosted.org/packages/da/b4/f805ab79293c728b9a99438775ce51885fd4f31b76178767cfc718701a39/numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98", size = 5198157, upload-time = "2026-01-31T23:11:22.375Z" }, + { url = "https://files.pythonhosted.org/packages/74/09/826e4289844eccdcd64aac27d13b0fd3f32039915dd5b9ba01baae1f436c/numpy-2.4.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef", size = 6546330, upload-time = "2026-01-31T23:11:23.958Z" }, + { url = "https://files.pythonhosted.org/packages/19/fb/cbfdbfa3057a10aea5422c558ac57538e6acc87ec1669e666d32ac198da7/numpy-2.4.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7", size = 15660968, upload-time = "2026-01-31T23:11:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/04/dc/46066ce18d01645541f0186877377b9371b8fa8017fa8262002b4ef22612/numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499", size = 16607311, upload-time = "2026-01-31T23:11:28.117Z" }, + { url = "https://files.pythonhosted.org/packages/14/d9/4b5adfc39a43fa6bf918c6d544bc60c05236cc2f6339847fc5b35e6cb5b0/numpy-2.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb", size = 17012850, upload-time = "2026-01-31T23:11:30.888Z" }, + { url = "https://files.pythonhosted.org/packages/b7/20/adb6e6adde6d0130046e6fdfb7675cc62bc2f6b7b02239a09eb58435753d/numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7", size = 18334210, upload-time = "2026-01-31T23:11:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/78/0e/0a73b3dff26803a8c02baa76398015ea2a5434d9b8265a7898a6028c1591/numpy-2.4.2-cp313-cp313-win32.whl", hash = "sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110", size = 5958199, upload-time = "2026-01-31T23:11:35.385Z" }, + { url = "https://files.pythonhosted.org/packages/43/bc/6352f343522fcb2c04dbaf94cb30cca6fd32c1a750c06ad6231b4293708c/numpy-2.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622", size = 12310848, upload-time = "2026-01-31T23:11:38.001Z" }, + { url = "https://files.pythonhosted.org/packages/6e/8d/6da186483e308da5da1cc6918ce913dcfe14ffde98e710bfeff2a6158d4e/numpy-2.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71", size = 10221082, upload-time = "2026-01-31T23:11:40.392Z" }, + { url = "https://files.pythonhosted.org/packages/25/a1/9510aa43555b44781968935c7548a8926274f815de42ad3997e9e83680dd/numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262", size = 14815866, upload-time = "2026-01-31T23:11:42.495Z" }, + { url = "https://files.pythonhosted.org/packages/36/30/6bbb5e76631a5ae46e7923dd16ca9d3f1c93cfa8d4ed79a129814a9d8db3/numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913", size = 5325631, upload-time = "2026-01-31T23:11:44.7Z" }, + { url = "https://files.pythonhosted.org/packages/46/00/3a490938800c1923b567b3a15cd17896e68052e2145d8662aaf3e1ffc58f/numpy-2.4.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab", size = 6646254, upload-time = "2026-01-31T23:11:46.341Z" }, + { url = "https://files.pythonhosted.org/packages/d3/e9/fac0890149898a9b609caa5af7455a948b544746e4b8fe7c212c8edd71f8/numpy-2.4.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82", size = 15720138, upload-time = "2026-01-31T23:11:48.082Z" }, + { url = "https://files.pythonhosted.org/packages/ea/5c/08887c54e68e1e28df53709f1893ce92932cc6f01f7c3d4dc952f61ffd4e/numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f", size = 16655398, upload-time = "2026-01-31T23:11:50.293Z" }, + { url = "https://files.pythonhosted.org/packages/4d/89/253db0fa0e66e9129c745e4ef25631dc37d5f1314dad2b53e907b8538e6d/numpy-2.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554", size = 17079064, upload-time = "2026-01-31T23:11:52.927Z" }, + { url = "https://files.pythonhosted.org/packages/2a/d5/cbade46ce97c59c6c3da525e8d95b7abe8a42974a1dc5c1d489c10433e88/numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257", size = 18379680, upload-time = "2026-01-31T23:11:55.22Z" }, + { url = "https://files.pythonhosted.org/packages/40/62/48f99ae172a4b63d981babe683685030e8a3df4f246c893ea5c6ef99f018/numpy-2.4.2-cp313-cp313t-win32.whl", hash = "sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657", size = 6082433, upload-time = "2026-01-31T23:11:58.096Z" }, + { url = "https://files.pythonhosted.org/packages/07/38/e054a61cfe48ad9f1ed0d188e78b7e26859d0b60ef21cd9de4897cdb5326/numpy-2.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b", size = 12451181, upload-time = "2026-01-31T23:11:59.782Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a4/a05c3a6418575e185dd84d0b9680b6bb2e2dc3e4202f036b7b4e22d6e9dc/numpy-2.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1", size = 10290756, upload-time = "2026-01-31T23:12:02.438Z" }, + { url = "https://files.pythonhosted.org/packages/18/88/b7df6050bf18fdcfb7046286c6535cabbdd2064a3440fca3f069d319c16e/numpy-2.4.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:444be170853f1f9d528428eceb55f12918e4fda5d8805480f36a002f1415e09b", size = 16663092, upload-time = "2026-01-31T23:12:04.521Z" }, + { url = "https://files.pythonhosted.org/packages/25/7a/1fee4329abc705a469a4afe6e69b1ef7e915117747886327104a8493a955/numpy-2.4.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d1240d50adff70c2a88217698ca844723068533f3f5c5fa6ee2e3220e3bdb000", size = 14698770, upload-time = "2026-01-31T23:12:06.96Z" }, + { url = "https://files.pythonhosted.org/packages/fb/0b/f9e49ba6c923678ad5bc38181c08ac5e53b7a5754dbca8e581aa1a56b1ff/numpy-2.4.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:7cdde6de52fb6664b00b056341265441192d1291c130e99183ec0d4b110ff8b1", size = 5208562, upload-time = "2026-01-31T23:12:09.632Z" }, + { url = "https://files.pythonhosted.org/packages/7d/12/d7de8f6f53f9bb76997e5e4c069eda2051e3fe134e9181671c4391677bb2/numpy-2.4.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:cda077c2e5b780200b6b3e09d0b42205a3d1c68f30c6dceb90401c13bff8fe74", size = 6543710, upload-time = "2026-01-31T23:12:11.969Z" }, + { url = "https://files.pythonhosted.org/packages/09/63/c66418c2e0268a31a4cf8a8b512685748200f8e8e8ec6c507ce14e773529/numpy-2.4.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d30291931c915b2ab5717c2974bb95ee891a1cf22ebc16a8006bd59cd210d40a", size = 15677205, upload-time = "2026-01-31T23:12:14.33Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6c/7f237821c9642fb2a04d2f1e88b4295677144ca93285fd76eff3bcba858d/numpy-2.4.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bba37bc29d4d85761deed3954a1bc62be7cf462b9510b51d367b769a8c8df325", size = 16611738, upload-time = "2026-01-31T23:12:16.525Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a7/39c4cdda9f019b609b5c473899d87abff092fc908cfe4d1ecb2fcff453b0/numpy-2.4.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b2f0073ed0868db1dcd86e052d37279eef185b9c8db5bf61f30f46adac63c909", size = 17028888, upload-time = "2026-01-31T23:12:19.306Z" }, + { url = "https://files.pythonhosted.org/packages/da/b3/e84bb64bdfea967cc10950d71090ec2d84b49bc691df0025dddb7c26e8e3/numpy-2.4.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f54844851cdb630ceb623dcec4db3240d1ac13d4990532446761baede94996a", size = 18339556, upload-time = "2026-01-31T23:12:21.816Z" }, + { url = "https://files.pythonhosted.org/packages/88/f5/954a291bc1192a27081706862ac62bb5920fbecfbaa302f64682aa90beed/numpy-2.4.2-cp314-cp314-win32.whl", hash = "sha256:12e26134a0331d8dbd9351620f037ec470b7c75929cb8a1537f6bfe411152a1a", size = 6006899, upload-time = "2026-01-31T23:12:24.14Z" }, + { url = "https://files.pythonhosted.org/packages/05/cb/eff72a91b2efdd1bc98b3b8759f6a1654aa87612fc86e3d87d6fe4f948c4/numpy-2.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:068cdb2d0d644cdb45670810894f6a0600797a69c05f1ac478e8d31670b8ee75", size = 12443072, upload-time = "2026-01-31T23:12:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/37/75/62726948db36a56428fce4ba80a115716dc4fad6a3a4352487f8bb950966/numpy-2.4.2-cp314-cp314-win_arm64.whl", hash = "sha256:6ed0be1ee58eef41231a5c943d7d1375f093142702d5723ca2eb07db9b934b05", size = 10494886, upload-time = "2026-01-31T23:12:28.488Z" }, + { url = "https://files.pythonhosted.org/packages/36/2f/ee93744f1e0661dc267e4b21940870cabfae187c092e1433b77b09b50ac4/numpy-2.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:98f16a80e917003a12c0580f97b5f875853ebc33e2eaa4bccfc8201ac6869308", size = 14818567, upload-time = "2026-01-31T23:12:30.709Z" }, + { url = "https://files.pythonhosted.org/packages/a7/24/6535212add7d76ff938d8bdc654f53f88d35cddedf807a599e180dcb8e66/numpy-2.4.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:20abd069b9cda45874498b245c8015b18ace6de8546bf50dfa8cea1696ed06ef", size = 5328372, upload-time = "2026-01-31T23:12:32.962Z" }, + { url = "https://files.pythonhosted.org/packages/5e/9d/c48f0a035725f925634bf6b8994253b43f2047f6778a54147d7e213bc5a7/numpy-2.4.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e98c97502435b53741540a5717a6749ac2ada901056c7db951d33e11c885cc7d", size = 6649306, upload-time = "2026-01-31T23:12:34.797Z" }, + { url = "https://files.pythonhosted.org/packages/81/05/7c73a9574cd4a53a25907bad38b59ac83919c0ddc8234ec157f344d57d9a/numpy-2.4.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da6cad4e82cb893db4b69105c604d805e0c3ce11501a55b5e9f9083b47d2ffe8", size = 15722394, upload-time = "2026-01-31T23:12:36.565Z" }, + { url = "https://files.pythonhosted.org/packages/35/fa/4de10089f21fc7d18442c4a767ab156b25c2a6eaf187c0db6d9ecdaeb43f/numpy-2.4.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4424677ce4b47fe73c8b5556d876571f7c6945d264201180db2dc34f676ab5", size = 16653343, upload-time = "2026-01-31T23:12:39.188Z" }, + { url = "https://files.pythonhosted.org/packages/b8/f9/d33e4ffc857f3763a57aa85650f2e82486832d7492280ac21ba9efda80da/numpy-2.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2b8f157c8a6f20eb657e240f8985cc135598b2b46985c5bccbde7616dc9c6b1e", size = 17078045, upload-time = "2026-01-31T23:12:42.041Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b8/54bdb43b6225badbea6389fa038c4ef868c44f5890f95dd530a218706da3/numpy-2.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5daf6f3914a733336dab21a05cdec343144600e964d2fcdabaac0c0269874b2a", size = 18380024, upload-time = "2026-01-31T23:12:44.331Z" }, + { url = "https://files.pythonhosted.org/packages/a5/55/6e1a61ded7af8df04016d81b5b02daa59f2ea9252ee0397cb9f631efe9e5/numpy-2.4.2-cp314-cp314t-win32.whl", hash = "sha256:8c50dd1fc8826f5b26a5ee4d77ca55d88a895f4e4819c7ecc2a9f5905047a443", size = 6153937, upload-time = "2026-01-31T23:12:47.229Z" }, + { url = "https://files.pythonhosted.org/packages/45/aa/fa6118d1ed6d776b0983f3ceac9b1a5558e80df9365b1c3aa6d42bf9eee4/numpy-2.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fcf92bee92742edd401ba41135185866f7026c502617f422eb432cfeca4fe236", size = 12631844, upload-time = "2026-01-31T23:12:48.997Z" }, + { url = "https://files.pythonhosted.org/packages/32/0a/2ec5deea6dcd158f254a7b372fb09cfba5719419c8d66343bab35237b3fb/numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181", size = 10565379, upload-time = "2026-01-31T23:12:51.345Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f8/50e14d36d915ef64d8f8bc4a087fc8264d82c785eda6711f80ab7e620335/numpy-2.4.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:89f7268c009bc492f506abd6f5265defa7cb3f7487dc21d357c3d290add45082", size = 16833179, upload-time = "2026-01-31T23:12:53.5Z" }, + { url = "https://files.pythonhosted.org/packages/17/17/809b5cad63812058a8189e91a1e2d55a5a18fd04611dbad244e8aeae465c/numpy-2.4.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6dee3bb76aa4009d5a912180bf5b2de012532998d094acee25d9cb8dee3e44a", size = 14889755, upload-time = "2026-01-31T23:12:55.933Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ea/181b9bcf7627fc8371720316c24db888dcb9829b1c0270abf3d288b2e29b/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:cd2bd2bbed13e213d6b55dc1d035a4f91748a7d3edc9480c13898b0353708920", size = 5399500, upload-time = "2026-01-31T23:12:58.671Z" }, + { url = "https://files.pythonhosted.org/packages/33/9f/413adf3fc955541ff5536b78fcf0754680b3c6d95103230252a2c9408d23/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:cf28c0c1d4c4bf00f509fa7eb02c58d7caf221b50b467bcb0d9bbf1584d5c821", size = 6714252, upload-time = "2026-01-31T23:13:00.518Z" }, + { url = "https://files.pythonhosted.org/packages/91/da/643aad274e29ccbdf42ecd94dafe524b81c87bcb56b83872d54827f10543/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e04ae107ac591763a47398bb45b568fc38f02dbc4aa44c063f67a131f99346cb", size = 15797142, upload-time = "2026-01-31T23:13:02.219Z" }, + { url = "https://files.pythonhosted.org/packages/66/27/965b8525e9cb5dc16481b30a1b3c21e50c7ebf6e9dbd48d0c4d0d5089c7e/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:602f65afdef699cda27ec0b9224ae5dc43e328f4c24c689deaf77133dbee74d0", size = 16727979, upload-time = "2026-01-31T23:13:04.62Z" }, + { url = "https://files.pythonhosted.org/packages/de/e5/b7d20451657664b07986c2f6e3be564433f5dcaf3482d68eaecd79afaf03/numpy-2.4.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be71bf1edb48ebbbf7f6337b5bfd2f895d1902f6335a5830b20141fc126ffba0", size = 12502577, upload-time = "2026-01-31T23:13:07.08Z" }, ] [[package]] -name = "nvidia-cublas" -version = "13.1.0.3" +name = "nvidia-cublas-cu12" +version = "12.8.4.1" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/a5/fce49e2ae977e0ccc084e5adafceb4f0ac0c8333cb6863501618a7277f67/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:c86fc7f7ae36d7528288c5d88098edcb7b02c633d262e7ddbb86b0ad91be5df2", size = 542851226 }, - { url = "https://files.pythonhosted.org/packages/e7/44/423ac00af4dd95a5aeb27207e2c0d9b7118702149bf4704c3ddb55bb7429/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:ee8722c1f0145ab246bccb9e452153b5e0515fd094c3678df50b2a0888b8b171", size = 423133236 }, + { url = "https://files.pythonhosted.org/packages/dc/61/e24b560ab2e2eaeb3c839129175fb330dfcfc29e5203196e5541a4c44682/nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:8ac4e771d5a348c551b2a426eda6193c19aa630236b418086020df5ba9667142", size = 594346921, upload-time = "2025-03-07T01:44:31.254Z" }, ] [[package]] -name = "nvidia-cuda-cupti" -version = "13.0.85" +name = "nvidia-cuda-cupti-cu12" +version = "12.8.90" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/2a/80353b103fc20ce05ef51e928daed4b6015db4aaa9162ed0997090fe2250/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_aarch64.whl", hash = "sha256:796bd679890ee55fb14a94629b698b6db54bcfd833d391d5e94017dd9d7d3151", size = 10310827 }, - { url = "https://files.pythonhosted.org/packages/33/6d/737d164b4837a9bbd202f5ae3078975f0525a55730fe871d8ed4e3b952b0/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_x86_64.whl", hash = "sha256:4eb01c08e859bf924d222250d2e8f8b8ff6d3db4721288cf35d14252a4d933c8", size = 10715597 }, + { url = "https://files.pythonhosted.org/packages/f8/02/2adcaa145158bf1a8295d83591d22e4103dbfd821bcaf6f3f53151ca4ffa/nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea0cb07ebda26bb9b29ba82cda34849e73c166c18162d3913575b0c9db9a6182", size = 10248621, upload-time = "2025-03-07T01:40:21.213Z" }, ] [[package]] -name = "nvidia-cuda-nvrtc" -version = "13.0.88" +name = "nvidia-cuda-nvrtc-cu12" +version = "12.8.93" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/68/483a78f5e8f31b08fb1bb671559968c0ca3a065ac7acabfc7cee55214fd6/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:ad9b6d2ead2435f11cbb6868809d2adeeee302e9bb94bcf0539c7a40d80e8575", size = 90215200 }, - { url = "https://files.pythonhosted.org/packages/b7/dc/6bb80850e0b7edd6588d560758f17e0550893a1feaf436807d64d2da040f/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d27f20a0ca67a4bb34268a5e951033496c5b74870b868bacd046b1b8e0c3267b", size = 43015449 }, + { url = "https://files.pythonhosted.org/packages/05/6b/32f747947df2da6994e999492ab306a903659555dddc0fbdeb9d71f75e52/nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:a7756528852ef889772a84c6cd89d41dfa74667e24cca16bb31f8f061e3e9994", size = 88040029, upload-time = "2025-03-07T01:42:13.562Z" }, ] [[package]] -name = "nvidia-cuda-runtime" -version = "13.0.96" +name = "nvidia-cuda-runtime-cu12" +version = "12.8.90" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/4f/17d7b9b8e285199c58ce28e31b5c5bbaa4d8271af06a89b6405258245de2/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef9bcbe90493a2b9d810e43d249adb3d02e98dd30200d86607d8d02687c43f55", size = 2261060 }, - { url = "https://files.pythonhosted.org/packages/2e/24/d1558f3b68b1d26e706813b1d10aa1d785e4698c425af8db8edc3dced472/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f82250d7782aa23b6cfe765ecc7db554bd3c2870c43f3d1821f1d18aebf0548", size = 2243632 }, + { url = "https://files.pythonhosted.org/packages/0d/9b/a997b638fcd068ad6e4d53b8551a7d30fe8b404d6f1804abf1df69838932/nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adade8dcbd0edf427b7204d480d6066d33902cab2a4707dcfc48a2d0fd44ab90", size = 954765, upload-time = "2025-03-07T01:40:01.615Z" }, ] [[package]] -name = "nvidia-cudnn-cu13" -version = "9.19.0.56" +name = "nvidia-cudnn-cu12" +version = "9.10.2.21" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas" }, + { name = "nvidia-cublas-cu12" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/84/26025437c1e6b61a707442184fa0c03d083b661adf3a3eecfd6d21677740/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:6ed29ffaee1176c612daf442e4dd6cfeb6a0caa43ddcbeb59da94953030b1be4", size = 433781201 }, - { url = "https://files.pythonhosted.org/packages/a3/22/0b4b932655d17a6da1b92fa92ab12844b053bb2ac2475e179ba6f043da1e/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:d20e1734305e9d68889a96e3f35094d733ff1f83932ebe462753973e53a572bf", size = 366066321 }, + { url = "https://files.pythonhosted.org/packages/ba/51/e123d997aa098c61d029f76663dedbfb9bc8dcf8c60cbd6adbe42f76d049/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8", size = 706758467, upload-time = "2025-06-06T21:54:08.597Z" }, ] [[package]] -name = "nvidia-cufft" -version = "12.0.0.61" +name = "nvidia-cufft-cu12" +version = "11.3.3.83" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink" }, + { name = "nvidia-nvjitlink-cu12" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554 }, - { url = "https://files.pythonhosted.org/packages/a8/2f/7b57e29836ea8714f81e9898409196f47d772d5ddedddf1592eadb8ab743/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c44f692dce8fd5ffd3e3df134b6cdb9c2f72d99cf40b62c32dde45eea9ddad3", size = 214085489 }, + { url = "https://files.pythonhosted.org/packages/1f/13/ee4e00f30e676b66ae65b4f08cb5bcbb8392c03f54f2d5413ea99a5d1c80/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74", size = 193118695, upload-time = "2025-03-07T01:45:27.821Z" }, ] [[package]] -name = "nvidia-cufile" -version = "1.15.1.6" +name = "nvidia-cufile-cu12" +version = "1.13.1.3" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/70/4f193de89a48b71714e74602ee14d04e4019ad36a5a9f20c425776e72cd6/nvidia_cufile-1.15.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08a3ecefae5a01c7f5117351c64f17c7c62efa5fffdbe24fc7d298da19cd0b44", size = 1223672 }, - { url = "https://files.pythonhosted.org/packages/ab/73/cc4a14c9813a8a0d509417cf5f4bdaba76e924d58beb9864f5a7baceefbf/nvidia_cufile-1.15.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:bdc0deedc61f548bddf7733bdc216456c2fdb101d020e1ab4b88d232d5e2f6d1", size = 1136992 }, + { url = "https://files.pythonhosted.org/packages/bb/fe/1bcba1dfbfb8d01be8d93f07bfc502c93fa23afa6fd5ab3fc7c1df71038a/nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d069003be650e131b21c932ec3d8969c1715379251f8d23a1860554b1cb24fc", size = 1197834, upload-time = "2025-03-07T01:45:50.723Z" }, ] [[package]] -name = "nvidia-curand" -version = "10.4.0.35" +name = "nvidia-curand-cu12" +version = "10.3.9.90" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/72/7c2ae24fb6b63a32e6ae5d241cc65263ea18d08802aaae087d9f013335a2/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:133df5a7509c3e292aaa2b477afd0194f06ce4ea24d714d616ff36439cee349a", size = 61962106 }, - { url = "https://files.pythonhosted.org/packages/a5/9f/be0a41ca4a4917abf5cb9ae0daff1a6060cc5de950aec0396de9f3b52bc5/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:1aee33a5da6e1db083fe2b90082def8915f30f3248d5896bcec36a579d941bfc", size = 59544258 }, + { url = "https://files.pythonhosted.org/packages/fb/aa/6584b56dc84ebe9cf93226a5cde4d99080c8e90ab40f0c27bda7a0f29aa1/nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:b32331d4f4df5d6eefa0554c565b626c7216f87a06a4f56fab27c3b68a830ec9", size = 63619976, upload-time = "2025-03-07T01:46:23.323Z" }, ] [[package]] -name = "nvidia-cusolver" -version = "12.0.4.66" +name = "nvidia-cusolver-cu12" +version = "11.7.3.90" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas" }, - { name = "nvidia-cusparse" }, - { name = "nvidia-nvjitlink" }, + { name = "nvidia-cublas-cu12" }, + { name = "nvidia-cusparse-cu12" }, + { name = "nvidia-nvjitlink-cu12" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760 }, - { url = "https://files.pythonhosted.org/packages/5f/67/cba3777620cdacb99102da4042883709c41c709f4b6323c10781a9c3aa34/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0a759da5dea5c0ea10fd307de75cdeb59e7ea4fcb8add0924859b944babf1112", size = 200941980 }, + { url = "https://files.pythonhosted.org/packages/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450", size = 267506905, upload-time = "2025-03-07T01:47:16.273Z" }, ] [[package]] -name = "nvidia-cusparse" -version = "12.6.3.3" +name = "nvidia-cusparse-cu12" +version = "12.5.8.93" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink" }, + { name = "nvidia-nvjitlink-cu12" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568 }, - { url = "https://files.pythonhosted.org/packages/fa/18/623c77619c31d62efd55302939756966f3ecc8d724a14dab2b75f1508850/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b3c89c88d01ee0e477cb7f82ef60a11a4bcd57b6b87c33f789350b59759360b", size = 145942937 }, + { url = "https://files.pythonhosted.org/packages/c2/f5/e1854cb2f2bcd4280c44736c93550cc300ff4b8c95ebe370d0aa7d2b473d/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b", size = 288216466, upload-time = "2025-03-07T01:48:13.779Z" }, ] [[package]] -name = "nvidia-cusparselt-cu13" -version = "0.8.0" +name = "nvidia-cusparselt-cu12" +version = "0.7.1" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/10/8dcd1175260706a2fc92a16a52e306b71d4c1ea0b0cc4a9484183399818a/nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:400c6ed1cf6780fc6efedd64ec9f1345871767e6a1a0a552a1ea0578117ea77c", size = 220791277 }, - { url = "https://files.pythonhosted.org/packages/fd/53/43b0d71f4e702fa9733f8b4571fdca50a8813f1e450b656c239beff12315/nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:25e30a8a7323935d4ad0340b95a0b69926eee755767e8e0b1cf8dd85b197d3fd", size = 169884119 }, + { url = "https://files.pythonhosted.org/packages/56/79/12978b96bd44274fe38b5dde5cfb660b1d114f70a65ef962bcbbed99b549/nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f1bb701d6b930d5a7cea44c19ceb973311500847f81b634d802b7b539dc55623", size = 287193691, upload-time = "2025-02-26T00:15:44.104Z" }, ] [[package]] -name = "nvidia-nccl-cu13" -version = "2.28.9" +name = "nvidia-nccl-cu12" +version = "2.27.5" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/55/1920646a2e43ffd4fc958536b276197ed740e9e0c54105b4bb3521591fc7/nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:01c873ba1626b54caa12272ed228dc5b2781545e0ae8ba3f432a8ef1c6d78643", size = 196561677 }, - { url = "https://files.pythonhosted.org/packages/b0/b4/878fefaad5b2bcc6fcf8d474a25e3e3774bc5133e4b58adff4d0bca238bc/nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:e4553a30f34195f3fa1da02a6da3d6337d28f2003943aa0a3d247bbc25fefc42", size = 196493177 }, + { url = "https://files.pythonhosted.org/packages/6e/89/f7a07dc961b60645dbbf42e80f2bc85ade7feb9a491b11a1e973aa00071f/nvidia_nccl_cu12-2.27.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ad730cf15cb5d25fe849c6e6ca9eb5b76db16a80f13f425ac68d8e2e55624457", size = 322348229, upload-time = "2025-06-26T04:11:28.385Z" }, ] [[package]] -name = "nvidia-nvjitlink" -version = "13.0.88" +name = "nvidia-nvjitlink-cu12" +version = "12.8.93" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/7a/123e033aaff487c77107195fa5a2b8686795ca537935a24efae476c41f05/nvidia_nvjitlink-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:13a74f429e23b921c1109976abefacc69835f2f433ebd323d3946e11d804e47b", size = 40713933 }, - { url = "https://files.pythonhosted.org/packages/ab/2c/93c5250e64df4f894f1cbb397c6fd71f79813f9fd79d7cd61de3f97b3c2d/nvidia_nvjitlink-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e931536ccc7d467a98ba1d8b89ff7fa7f1fa3b13f2b0069118cd7f47bff07d0c", size = 38768748 }, + { url = "https://files.pythonhosted.org/packages/f6/74/86a07f1d0f42998ca31312f998bd3b9a7eff7f52378f4f270c8679c77fb9/nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:81ff63371a7ebd6e6451970684f916be2eab07321b73c9d244dc2b4da7f73b88", size = 39254836, upload-time = "2025-03-07T01:49:55.661Z" }, ] [[package]] -name = "nvidia-nvshmem-cu13" +name = "nvidia-nvshmem-cu12" version = "3.4.5" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/0f/05cc9c720236dcd2db9c1ab97fff629e96821be2e63103569da0c9b72f19/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dc2a197f38e5d0376ad52cd1a2a3617d3cdc150fd5966f4aee9bcebb1d68fe9", size = 60215947 }, - { url = "https://files.pythonhosted.org/packages/3c/35/a9bf80a609e74e3b000fef598933235c908fcefcef9026042b8e6dfde2a9/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:290f0a2ee94c9f3687a02502f3b9299a9f9fe826e6d0287ee18482e78d495b80", size = 60412546 }, + { url = "https://files.pythonhosted.org/packages/b5/09/6ea3ea725f82e1e76684f0708bbedd871fc96da89945adeba65c3835a64c/nvidia_nvshmem_cu12-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:042f2500f24c021db8a06c5eec2539027d57460e1c1a762055a6554f72c369bd", size = 139103095, upload-time = "2025-09-06T00:32:31.266Z" }, ] [[package]] -name = "nvidia-nvtx" -version = "13.0.85" +name = "nvidia-nvtx-cu12" +version = "12.8.90" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/f3/d86c845465a2723ad7e1e5c36dcd75ddb82898b3f53be47ebd429fb2fa5d/nvidia_nvtx-13.0.85-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4936d1d6780fbe68db454f5e72a42ff64d1fd6397df9f363ae786930fd5c1cd4", size = 148047 }, - { url = "https://files.pythonhosted.org/packages/a8/64/3708a90d1ebe202ffdeb7185f878a3c84d15c2b2c31858da2ce0583e2def/nvidia_nvtx-13.0.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb7780edb6b14107373c835bf8b72e7a178bac7367e23da7acb108f973f157a6", size = 148878 }, + { url = "https://files.pythonhosted.org/packages/a2/eb/86626c1bbc2edb86323022371c39aa48df6fd8b0a1647bc274577f72e90b/nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b17e2001cc0d751a5bc2c6ec6d26ad95913324a4adb86788c944f8ce9ba441f", size = 89954, upload-time = "2025-03-07T01:42:44.131Z" }, ] [[package]] name = "optuna" -version = "4.8.0" +version = "4.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alembic" }, @@ -2241,45 +2147,45 @@ dependencies = [ { name = "sqlalchemy" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bf/9b/62f120fb2ecbc4338bee70c5a3671c8e561714f3aa1a046b897ff142050e/optuna-4.8.0.tar.gz", hash = "sha256:6f7043e9f8ecb5e607af86a7eb00fb5ec2be26c3b08c201209a73d36aff37a38", size = 482603, upload-time = "2026-03-16T04:59:58.659Z" } +sdist = { url = "https://files.pythonhosted.org/packages/58/b2/b5e12de7b4486556fe2257611b55dbabf30d0300bdb031831aa943ad20e4/optuna-4.7.0.tar.gz", hash = "sha256:d91817e2079825557bd2e97de2e8c9ae260bfc99b32712502aef8a5095b2d2c0", size = 479740, upload-time = "2026-01-19T05:45:52.604Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/24/7c731839566d30dc70556d9824ef17692d896c15e3df627bce8c16f753e1/optuna-4.8.0-py3-none-any.whl", hash = "sha256:c57a7682679c36bfc9bca0da430698179e513874074b71bebedb0334964ab930", size = 419456, upload-time = "2026-03-16T04:59:56.977Z" }, + { url = "https://files.pythonhosted.org/packages/75/d1/6c8a4fbb38a9e3565f5c36b871262a85ecab3da48120af036b1e4937a15c/optuna-4.7.0-py3-none-any.whl", hash = "sha256:e41ec84018cecc10eabf28143573b1f0bde0ba56dba8151631a590ecbebc1186", size = 413894, upload-time = "2026-01-19T05:45:50.815Z" }, ] [[package]] name = "overrides" version = "7.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812 } +sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812, upload-time = "2024-01-27T21:01:33.423Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832 }, + { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832, upload-time = "2024-01-27T21:01:31.393Z" }, ] [[package]] name = "packaging" version = "26.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416 } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366 }, + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] [[package]] name = "pandocfilters" version = "1.5.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/70/6f/3dd4940bbe001c06a65f88e36bad298bc7a0de5036115639926b0c5c0458/pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e", size = 8454 } +sdist = { url = "https://files.pythonhosted.org/packages/70/6f/3dd4940bbe001c06a65f88e36bad298bc7a0de5036115639926b0c5c0458/pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e", size = 8454, upload-time = "2024-01-18T20:08:13.726Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc", size = 8663 }, + { url = "https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc", size = 8663, upload-time = "2024-01-18T20:08:11.28Z" }, ] [[package]] name = "parso" version = "0.8.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/81/76/a1e769043c0c0c9fe391b702539d594731a4362334cdf4dc25d0c09761e7/parso-0.8.6.tar.gz", hash = "sha256:2b9a0332696df97d454fa67b81618fd69c35a7b90327cbe6ba5c92d2c68a7bfd", size = 401621 } +sdist = { url = "https://files.pythonhosted.org/packages/81/76/a1e769043c0c0c9fe391b702539d594731a4362334cdf4dc25d0c09761e7/parso-0.8.6.tar.gz", hash = "sha256:2b9a0332696df97d454fa67b81618fd69c35a7b90327cbe6ba5c92d2c68a7bfd", size = 401621, upload-time = "2026-02-09T15:45:24.425Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl", hash = "sha256:2c549f800b70a5c4952197248825584cb00f033b29c692671d3bf08bf380baff", size = 106894 }, + { url = "https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl", hash = "sha256:2c549f800b70a5c4952197248825584cb00f033b29c692671d3bf08bf380baff", size = 106894, upload-time = "2026-02-09T15:45:21.391Z" }, ] [[package]] @@ -2290,9 +2196,9 @@ dependencies = [ { name = "locket" }, { name = "toolz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b2/3a/3f06f34820a31257ddcabdfafc2672c5816be79c7e353b02c1f318daa7d4/partd-1.4.2.tar.gz", hash = "sha256:d022c33afbdc8405c226621b015e8067888173d85f7f5ecebb3cafed9a20f02c", size = 21029 } +sdist = { url = "https://files.pythonhosted.org/packages/b2/3a/3f06f34820a31257ddcabdfafc2672c5816be79c7e353b02c1f318daa7d4/partd-1.4.2.tar.gz", hash = "sha256:d022c33afbdc8405c226621b015e8067888173d85f7f5ecebb3cafed9a20f02c", size = 21029, upload-time = "2024-05-06T19:51:41.945Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl", hash = "sha256:978e4ac767ec4ba5b86c6eaa52e5a2a3bc748a2ca839e8cc798f1cc6ce6efb0f", size = 18905 }, + { url = "https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl", hash = "sha256:978e4ac767ec4ba5b86c6eaa52e5a2a3bc748a2ca839e8cc798f1cc6ce6efb0f", size = 18905, upload-time = "2024-05-06T19:51:39.271Z" }, ] [[package]] @@ -2302,101 +2208,101 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ptyprocess" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450 } +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772 }, + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, ] [[package]] name = "pillow" version = "12.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/46/5da1ec4a5171ee7bf1a0efa064aba70ba3d6e0788ce3f5acd1375d23c8c0/pillow-12.1.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e879bb6cd5c73848ef3b2b48b8af9ff08c5b71ecda8048b7dd22d8a33f60be32", size = 5304084 }, - { url = "https://files.pythonhosted.org/packages/78/93/a29e9bc02d1cf557a834da780ceccd54e02421627200696fcf805ebdc3fb/pillow-12.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:365b10bb9417dd4498c0e3b128018c4a624dc11c7b97d8cc54effe3b096f4c38", size = 4657866 }, - { url = "https://files.pythonhosted.org/packages/13/84/583a4558d492a179d31e4aae32eadce94b9acf49c0337c4ce0b70e0a01f2/pillow-12.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d4ce8e329c93845720cd2014659ca67eac35f6433fd3050393d85f3ecef0dad5", size = 6232148 }, - { url = "https://files.pythonhosted.org/packages/d5/e2/53c43334bbbb2d3b938978532fbda8e62bb6e0b23a26ce8592f36bcc4987/pillow-12.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc354a04072b765eccf2204f588a7a532c9511e8b9c7f900e1b64e3e33487090", size = 8038007 }, - { url = "https://files.pythonhosted.org/packages/b8/a6/3d0e79c8a9d58150dd98e199d7c1c56861027f3829a3a60b3c2784190180/pillow-12.1.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e7976bf1910a8116b523b9f9f58bf410f3e8aa330cd9a2bb2953f9266ab49af", size = 6345418 }, - { url = "https://files.pythonhosted.org/packages/a2/c8/46dfeac5825e600579157eea177be43e2f7ff4a99da9d0d0a49533509ac5/pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:597bd9c8419bc7c6af5604e55847789b69123bbe25d65cc6ad3012b4f3c98d8b", size = 7034590 }, - { url = "https://files.pythonhosted.org/packages/af/bf/e6f65d3db8a8bbfeaf9e13cc0417813f6319863a73de934f14b2229ada18/pillow-12.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2c1fc0f2ca5f96a3c8407e41cca26a16e46b21060fe6d5b099d2cb01412222f5", size = 6458655 }, - { url = "https://files.pythonhosted.org/packages/f9/c2/66091f3f34a25894ca129362e510b956ef26f8fb67a0e6417bc5744e56f1/pillow-12.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:578510d88c6229d735855e1f278aa305270438d36a05031dfaae5067cc8eb04d", size = 7159286 }, - { url = "https://files.pythonhosted.org/packages/7b/5a/24bc8eb526a22f957d0cec6243146744966d40857e3d8deb68f7902ca6c1/pillow-12.1.1-cp311-cp311-win32.whl", hash = "sha256:7311c0a0dcadb89b36b7025dfd8326ecfa36964e29913074d47382706e516a7c", size = 6328663 }, - { url = "https://files.pythonhosted.org/packages/31/03/bef822e4f2d8f9d7448c133d0a18185d3cce3e70472774fffefe8b0ed562/pillow-12.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:fbfa2a7c10cc2623f412753cddf391c7f971c52ca40a3f65dc5039b2939e8563", size = 7031448 }, - { url = "https://files.pythonhosted.org/packages/49/70/f76296f53610bd17b2e7d31728b8b7825e3ac3b5b3688b51f52eab7c0818/pillow-12.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:b81b5e3511211631b3f672a595e3221252c90af017e399056d0faabb9538aa80", size = 2453651 }, - { url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803 }, - { url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601 }, - { url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995 }, - { url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012 }, - { url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638 }, - { url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540 }, - { url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613 }, - { url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745 }, - { url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823 }, - { url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367 }, - { url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811 }, - { url = "https://files.pythonhosted.org/packages/d5/11/6db24d4bd7685583caeae54b7009584e38da3c3d4488ed4cd25b439de486/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e", size = 4062689 }, - { url = "https://files.pythonhosted.org/packages/33/c0/ce6d3b1fe190f0021203e0d9b5b99e57843e345f15f9ef22fcd43842fd21/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", size = 4138535 }, - { url = "https://files.pythonhosted.org/packages/a0/c6/d5eb6a4fb32a3f9c21a8c7613ec706534ea1cf9f4b3663e99f0d83f6fca8/pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6", size = 3601364 }, - { url = "https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60", size = 5262561 }, - { url = "https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2", size = 4657460 }, - { url = "https://files.pythonhosted.org/packages/9e/1b/f1a4ea9a895b5732152789326202a82464d5254759fbacae4deea3069334/pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850", size = 6232698 }, - { url = "https://files.pythonhosted.org/packages/95/f4/86f51b8745070daf21fd2e5b1fe0eb35d4db9ca26e6d58366562fb56a743/pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289", size = 8041706 }, - { url = "https://files.pythonhosted.org/packages/29/9b/d6ecd956bb1266dd1045e995cce9b8d77759e740953a1c9aad9502a0461e/pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e", size = 6346621 }, - { url = "https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717", size = 7038069 }, - { url = "https://files.pythonhosted.org/packages/94/0e/58cb1a6bc48f746bc4cb3adb8cabff73e2742c92b3bf7a220b7cf69b9177/pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a", size = 6460040 }, - { url = "https://files.pythonhosted.org/packages/6c/57/9045cb3ff11eeb6c1adce3b2d60d7d299d7b273a2e6c8381a524abfdc474/pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029", size = 7164523 }, - { url = "https://files.pythonhosted.org/packages/73/f2/9be9cb99f2175f0d4dbadd6616ce1bf068ee54a28277ea1bf1fbf729c250/pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b", size = 6332552 }, - { url = "https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1", size = 7040108 }, - { url = "https://files.pythonhosted.org/packages/d5/7d/fc09634e2aabdd0feabaff4a32f4a7d97789223e7c2042fd805ea4b4d2c2/pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a", size = 2453712 }, - { url = "https://files.pythonhosted.org/packages/19/2a/b9d62794fc8a0dd14c1943df68347badbd5511103e0d04c035ffe5cf2255/pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da", size = 5264880 }, - { url = "https://files.pythonhosted.org/packages/26/9d/e03d857d1347fa5ed9247e123fcd2a97b6220e15e9cb73ca0a8d91702c6e/pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc", size = 4660616 }, - { url = "https://files.pythonhosted.org/packages/f7/ec/8a6d22afd02570d30954e043f09c32772bfe143ba9285e2fdb11284952cd/pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c", size = 6269008 }, - { url = "https://files.pythonhosted.org/packages/3d/1d/6d875422c9f28a4a361f495a5f68d9de4a66941dc2c619103ca335fa6446/pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8", size = 8073226 }, - { url = "https://files.pythonhosted.org/packages/a1/cd/134b0b6ee5eda6dc09e25e24b40fdafe11a520bc725c1d0bbaa5e00bf95b/pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20", size = 6380136 }, - { url = "https://files.pythonhosted.org/packages/7a/a9/7628f013f18f001c1b98d8fffe3452f306a70dc6aba7d931019e0492f45e/pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13", size = 7067129 }, - { url = "https://files.pythonhosted.org/packages/1e/f8/66ab30a2193b277785601e82ee2d49f68ea575d9637e5e234faaa98efa4c/pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf", size = 6491807 }, - { url = "https://files.pythonhosted.org/packages/da/0b/a877a6627dc8318fdb84e357c5e1a758c0941ab1ddffdafd231983788579/pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524", size = 7190954 }, - { url = "https://files.pythonhosted.org/packages/83/43/6f732ff85743cf746b1361b91665d9f5155e1483817f693f8d57ea93147f/pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986", size = 6336441 }, - { url = "https://files.pythonhosted.org/packages/3b/44/e865ef3986611bb75bfabdf94a590016ea327833f434558801122979cd0e/pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c", size = 7045383 }, - { url = "https://files.pythonhosted.org/packages/a8/c6/f4fb24268d0c6908b9f04143697ea18b0379490cb74ba9e8d41b898bd005/pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3", size = 2456104 }, - { url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652 }, - { url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823 }, - { url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143 }, - { url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254 }, - { url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499 }, - { url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137 }, - { url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721 }, - { url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798 }, - { url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315 }, - { url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360 }, - { url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438 }, - { url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503 }, - { url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748 }, - { url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314 }, - { url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612 }, - { url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567 }, - { url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951 }, - { url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769 }, - { url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358 }, - { url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558 }, - { url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028 }, - { url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940 }, - { url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736 }, - { url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894 }, - { url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446 }, - { url = "https://files.pythonhosted.org/packages/56/11/5d43209aa4cb58e0cc80127956ff1796a68b928e6324bbf06ef4db34367b/pillow-12.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:600fd103672b925fe62ed08e0d874ea34d692474df6f4bf7ebe148b30f89f39f", size = 5228606 }, - { url = "https://files.pythonhosted.org/packages/5f/d5/3b005b4e4fda6698b371fa6c21b097d4707585d7db99e98d9b0b87ac612a/pillow-12.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:665e1b916b043cef294bc54d47bf02d87e13f769bc4bc5fa225a24b3a6c5aca9", size = 4622321 }, - { url = "https://files.pythonhosted.org/packages/df/36/ed3ea2d594356fd8037e5a01f6156c74bc8d92dbb0fa60746cc96cabb6e8/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:495c302af3aad1ca67420ddd5c7bd480c8867ad173528767d906428057a11f0e", size = 5247579 }, - { url = "https://files.pythonhosted.org/packages/54/9a/9cc3e029683cf6d20ae5085da0dafc63148e3252c2f13328e553aaa13cfb/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8fd420ef0c52c88b5a035a0886f367748c72147b2b8f384c9d12656678dfdfa9", size = 6989094 }, - { url = "https://files.pythonhosted.org/packages/00/98/fc53ab36da80b88df0967896b6c4b4cd948a0dc5aa40a754266aa3ae48b3/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f975aa7ef9684ce7e2c18a3aa8f8e2106ce1e46b94ab713d156b2898811651d3", size = 5313850 }, - { url = "https://files.pythonhosted.org/packages/30/02/00fa585abfd9fe9d73e5f6e554dc36cc2b842898cbfc46d70353dae227f8/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8089c852a56c2966cf18835db62d9b34fef7ba74c726ad943928d494fa7f4735", size = 5963343 }, - { url = "https://files.pythonhosted.org/packages/f2/26/c56ce33ca856e358d27fda9676c055395abddb82c35ac0f593877ed4562e/pillow-12.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:cb9bb857b2d057c6dfc72ac5f3b44836924ba15721882ef103cecb40d002d80e", size = 7029880 }, +sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/46/5da1ec4a5171ee7bf1a0efa064aba70ba3d6e0788ce3f5acd1375d23c8c0/pillow-12.1.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e879bb6cd5c73848ef3b2b48b8af9ff08c5b71ecda8048b7dd22d8a33f60be32", size = 5304084, upload-time = "2026-02-11T04:20:27.501Z" }, + { url = "https://files.pythonhosted.org/packages/78/93/a29e9bc02d1cf557a834da780ceccd54e02421627200696fcf805ebdc3fb/pillow-12.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:365b10bb9417dd4498c0e3b128018c4a624dc11c7b97d8cc54effe3b096f4c38", size = 4657866, upload-time = "2026-02-11T04:20:29.827Z" }, + { url = "https://files.pythonhosted.org/packages/13/84/583a4558d492a179d31e4aae32eadce94b9acf49c0337c4ce0b70e0a01f2/pillow-12.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d4ce8e329c93845720cd2014659ca67eac35f6433fd3050393d85f3ecef0dad5", size = 6232148, upload-time = "2026-02-11T04:20:31.329Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e2/53c43334bbbb2d3b938978532fbda8e62bb6e0b23a26ce8592f36bcc4987/pillow-12.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc354a04072b765eccf2204f588a7a532c9511e8b9c7f900e1b64e3e33487090", size = 8038007, upload-time = "2026-02-11T04:20:34.225Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a6/3d0e79c8a9d58150dd98e199d7c1c56861027f3829a3a60b3c2784190180/pillow-12.1.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e7976bf1910a8116b523b9f9f58bf410f3e8aa330cd9a2bb2953f9266ab49af", size = 6345418, upload-time = "2026-02-11T04:20:35.858Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c8/46dfeac5825e600579157eea177be43e2f7ff4a99da9d0d0a49533509ac5/pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:597bd9c8419bc7c6af5604e55847789b69123bbe25d65cc6ad3012b4f3c98d8b", size = 7034590, upload-time = "2026-02-11T04:20:37.91Z" }, + { url = "https://files.pythonhosted.org/packages/af/bf/e6f65d3db8a8bbfeaf9e13cc0417813f6319863a73de934f14b2229ada18/pillow-12.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2c1fc0f2ca5f96a3c8407e41cca26a16e46b21060fe6d5b099d2cb01412222f5", size = 6458655, upload-time = "2026-02-11T04:20:39.496Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c2/66091f3f34a25894ca129362e510b956ef26f8fb67a0e6417bc5744e56f1/pillow-12.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:578510d88c6229d735855e1f278aa305270438d36a05031dfaae5067cc8eb04d", size = 7159286, upload-time = "2026-02-11T04:20:41.139Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5a/24bc8eb526a22f957d0cec6243146744966d40857e3d8deb68f7902ca6c1/pillow-12.1.1-cp311-cp311-win32.whl", hash = "sha256:7311c0a0dcadb89b36b7025dfd8326ecfa36964e29913074d47382706e516a7c", size = 6328663, upload-time = "2026-02-11T04:20:43.184Z" }, + { url = "https://files.pythonhosted.org/packages/31/03/bef822e4f2d8f9d7448c133d0a18185d3cce3e70472774fffefe8b0ed562/pillow-12.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:fbfa2a7c10cc2623f412753cddf391c7f971c52ca40a3f65dc5039b2939e8563", size = 7031448, upload-time = "2026-02-11T04:20:44.696Z" }, + { url = "https://files.pythonhosted.org/packages/49/70/f76296f53610bd17b2e7d31728b8b7825e3ac3b5b3688b51f52eab7c0818/pillow-12.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:b81b5e3511211631b3f672a595e3221252c90af017e399056d0faabb9538aa80", size = 2453651, upload-time = "2026-02-11T04:20:46.243Z" }, + { url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803, upload-time = "2026-02-11T04:20:47.653Z" }, + { url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601, upload-time = "2026-02-11T04:20:49.328Z" }, + { url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995, upload-time = "2026-02-11T04:20:51.032Z" }, + { url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012, upload-time = "2026-02-11T04:20:52.882Z" }, + { url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638, upload-time = "2026-02-11T04:20:54.444Z" }, + { url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540, upload-time = "2026-02-11T04:20:55.97Z" }, + { url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613, upload-time = "2026-02-11T04:20:57.542Z" }, + { url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745, upload-time = "2026-02-11T04:20:59.196Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823, upload-time = "2026-02-11T04:21:01.385Z" }, + { url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367, upload-time = "2026-02-11T04:21:03.536Z" }, + { url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811, upload-time = "2026-02-11T04:21:05.116Z" }, + { url = "https://files.pythonhosted.org/packages/d5/11/6db24d4bd7685583caeae54b7009584e38da3c3d4488ed4cd25b439de486/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e", size = 4062689, upload-time = "2026-02-11T04:21:06.804Z" }, + { url = "https://files.pythonhosted.org/packages/33/c0/ce6d3b1fe190f0021203e0d9b5b99e57843e345f15f9ef22fcd43842fd21/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", size = 4138535, upload-time = "2026-02-11T04:21:08.452Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c6/d5eb6a4fb32a3f9c21a8c7613ec706534ea1cf9f4b3663e99f0d83f6fca8/pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6", size = 3601364, upload-time = "2026-02-11T04:21:10.194Z" }, + { url = "https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60", size = 5262561, upload-time = "2026-02-11T04:21:11.742Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2", size = 4657460, upload-time = "2026-02-11T04:21:13.786Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1b/f1a4ea9a895b5732152789326202a82464d5254759fbacae4deea3069334/pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850", size = 6232698, upload-time = "2026-02-11T04:21:15.949Z" }, + { url = "https://files.pythonhosted.org/packages/95/f4/86f51b8745070daf21fd2e5b1fe0eb35d4db9ca26e6d58366562fb56a743/pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289", size = 8041706, upload-time = "2026-02-11T04:21:17.723Z" }, + { url = "https://files.pythonhosted.org/packages/29/9b/d6ecd956bb1266dd1045e995cce9b8d77759e740953a1c9aad9502a0461e/pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e", size = 6346621, upload-time = "2026-02-11T04:21:19.547Z" }, + { url = "https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717", size = 7038069, upload-time = "2026-02-11T04:21:21.378Z" }, + { url = "https://files.pythonhosted.org/packages/94/0e/58cb1a6bc48f746bc4cb3adb8cabff73e2742c92b3bf7a220b7cf69b9177/pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a", size = 6460040, upload-time = "2026-02-11T04:21:23.148Z" }, + { url = "https://files.pythonhosted.org/packages/6c/57/9045cb3ff11eeb6c1adce3b2d60d7d299d7b273a2e6c8381a524abfdc474/pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029", size = 7164523, upload-time = "2026-02-11T04:21:25.01Z" }, + { url = "https://files.pythonhosted.org/packages/73/f2/9be9cb99f2175f0d4dbadd6616ce1bf068ee54a28277ea1bf1fbf729c250/pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b", size = 6332552, upload-time = "2026-02-11T04:21:27.238Z" }, + { url = "https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1", size = 7040108, upload-time = "2026-02-11T04:21:29.462Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7d/fc09634e2aabdd0feabaff4a32f4a7d97789223e7c2042fd805ea4b4d2c2/pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a", size = 2453712, upload-time = "2026-02-11T04:21:31.072Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/b9d62794fc8a0dd14c1943df68347badbd5511103e0d04c035ffe5cf2255/pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da", size = 5264880, upload-time = "2026-02-11T04:21:32.865Z" }, + { url = "https://files.pythonhosted.org/packages/26/9d/e03d857d1347fa5ed9247e123fcd2a97b6220e15e9cb73ca0a8d91702c6e/pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc", size = 4660616, upload-time = "2026-02-11T04:21:34.97Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ec/8a6d22afd02570d30954e043f09c32772bfe143ba9285e2fdb11284952cd/pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c", size = 6269008, upload-time = "2026-02-11T04:21:36.623Z" }, + { url = "https://files.pythonhosted.org/packages/3d/1d/6d875422c9f28a4a361f495a5f68d9de4a66941dc2c619103ca335fa6446/pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8", size = 8073226, upload-time = "2026-02-11T04:21:38.585Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cd/134b0b6ee5eda6dc09e25e24b40fdafe11a520bc725c1d0bbaa5e00bf95b/pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20", size = 6380136, upload-time = "2026-02-11T04:21:40.562Z" }, + { url = "https://files.pythonhosted.org/packages/7a/a9/7628f013f18f001c1b98d8fffe3452f306a70dc6aba7d931019e0492f45e/pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13", size = 7067129, upload-time = "2026-02-11T04:21:42.521Z" }, + { url = "https://files.pythonhosted.org/packages/1e/f8/66ab30a2193b277785601e82ee2d49f68ea575d9637e5e234faaa98efa4c/pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf", size = 6491807, upload-time = "2026-02-11T04:21:44.22Z" }, + { url = "https://files.pythonhosted.org/packages/da/0b/a877a6627dc8318fdb84e357c5e1a758c0941ab1ddffdafd231983788579/pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524", size = 7190954, upload-time = "2026-02-11T04:21:46.114Z" }, + { url = "https://files.pythonhosted.org/packages/83/43/6f732ff85743cf746b1361b91665d9f5155e1483817f693f8d57ea93147f/pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986", size = 6336441, upload-time = "2026-02-11T04:21:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/3b/44/e865ef3986611bb75bfabdf94a590016ea327833f434558801122979cd0e/pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c", size = 7045383, upload-time = "2026-02-11T04:21:50.015Z" }, + { url = "https://files.pythonhosted.org/packages/a8/c6/f4fb24268d0c6908b9f04143697ea18b0379490cb74ba9e8d41b898bd005/pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3", size = 2456104, upload-time = "2026-02-11T04:21:51.633Z" }, + { url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652, upload-time = "2026-02-11T04:21:53.19Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823, upload-time = "2026-02-11T04:22:03.088Z" }, + { url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143, upload-time = "2026-02-11T04:22:04.909Z" }, + { url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254, upload-time = "2026-02-11T04:22:07.656Z" }, + { url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499, upload-time = "2026-02-11T04:22:09.613Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137, upload-time = "2026-02-11T04:22:11.434Z" }, + { url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721, upload-time = "2026-02-11T04:22:13.321Z" }, + { url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798, upload-time = "2026-02-11T04:22:15.449Z" }, + { url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315, upload-time = "2026-02-11T04:22:17.24Z" }, + { url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360, upload-time = "2026-02-11T04:22:19.111Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438, upload-time = "2026-02-11T04:22:21.041Z" }, + { url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503, upload-time = "2026-02-11T04:22:22.833Z" }, + { url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748, upload-time = "2026-02-11T04:22:24.64Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314, upload-time = "2026-02-11T04:22:26.685Z" }, + { url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612, upload-time = "2026-02-11T04:22:29.884Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567, upload-time = "2026-02-11T04:22:31.799Z" }, + { url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951, upload-time = "2026-02-11T04:22:33.921Z" }, + { url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769, upload-time = "2026-02-11T04:22:35.877Z" }, + { url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358, upload-time = "2026-02-11T04:22:37.698Z" }, + { url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558, upload-time = "2026-02-11T04:22:39.597Z" }, + { url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028, upload-time = "2026-02-11T04:22:42.73Z" }, + { url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940, upload-time = "2026-02-11T04:22:44.543Z" }, + { url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736, upload-time = "2026-02-11T04:22:46.347Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894, upload-time = "2026-02-11T04:22:48.114Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446, upload-time = "2026-02-11T04:22:50.342Z" }, + { url = "https://files.pythonhosted.org/packages/56/11/5d43209aa4cb58e0cc80127956ff1796a68b928e6324bbf06ef4db34367b/pillow-12.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:600fd103672b925fe62ed08e0d874ea34d692474df6f4bf7ebe148b30f89f39f", size = 5228606, upload-time = "2026-02-11T04:22:52.106Z" }, + { url = "https://files.pythonhosted.org/packages/5f/d5/3b005b4e4fda6698b371fa6c21b097d4707585d7db99e98d9b0b87ac612a/pillow-12.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:665e1b916b043cef294bc54d47bf02d87e13f769bc4bc5fa225a24b3a6c5aca9", size = 4622321, upload-time = "2026-02-11T04:22:53.827Z" }, + { url = "https://files.pythonhosted.org/packages/df/36/ed3ea2d594356fd8037e5a01f6156c74bc8d92dbb0fa60746cc96cabb6e8/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:495c302af3aad1ca67420ddd5c7bd480c8867ad173528767d906428057a11f0e", size = 5247579, upload-time = "2026-02-11T04:22:56.094Z" }, + { url = "https://files.pythonhosted.org/packages/54/9a/9cc3e029683cf6d20ae5085da0dafc63148e3252c2f13328e553aaa13cfb/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8fd420ef0c52c88b5a035a0886f367748c72147b2b8f384c9d12656678dfdfa9", size = 6989094, upload-time = "2026-02-11T04:22:58.288Z" }, + { url = "https://files.pythonhosted.org/packages/00/98/fc53ab36da80b88df0967896b6c4b4cd948a0dc5aa40a754266aa3ae48b3/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f975aa7ef9684ce7e2c18a3aa8f8e2106ce1e46b94ab713d156b2898811651d3", size = 5313850, upload-time = "2026-02-11T04:23:00.554Z" }, + { url = "https://files.pythonhosted.org/packages/30/02/00fa585abfd9fe9d73e5f6e554dc36cc2b842898cbfc46d70353dae227f8/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8089c852a56c2966cf18835db62d9b34fef7ba74c726ad943928d494fa7f4735", size = 5963343, upload-time = "2026-02-11T04:23:02.934Z" }, + { url = "https://files.pythonhosted.org/packages/f2/26/c56ce33ca856e358d27fda9676c055395abddb82c35ac0f593877ed4562e/pillow-12.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:cb9bb857b2d057c6dfc72ac5f3b44836924ba15721882ef103cecb40d002d80e", size = 7029880, upload-time = "2026-02-11T04:23:04.783Z" }, ] [[package]] name = "pint" -version = "0.25.3" +version = "0.25.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "flexcache" }, @@ -2404,27 +2310,27 @@ dependencies = [ { name = "platformdirs" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/52/9d/b1379cdbd33a49d17d627bc24e2b63cca06a1c5343b38072d2889499e82e/pint-0.25.3.tar.gz", hash = "sha256:f8f5df6cf65314d74da1ade1bf96f8e3e4d0c41b51577ac53c49e7d44ca5acee", size = 255106, upload-time = "2026-03-19T21:57:08.72Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/74/bc3f671997158aef171194c3c4041e549946f4784b8690baa0626a0a164b/pint-0.25.2.tar.gz", hash = "sha256:85a45d1da8fe9c9f7477fed8aef59ad2b939af3d6611507e1a9cbdacdcd3450a", size = 254467, upload-time = "2025-11-06T22:08:09.184Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/dd/a9fe6a0a09512da23951c68bf36466aeecd89def3183dc095edbc807ddc5/pint-0.25.3-py3-none-any.whl", hash = "sha256:27eb25143bd5de9fcc4d5a4b484f16faf6b4615aa93ece6b3373a8c1a3c1b97d", size = 307488, upload-time = "2026-03-19T21:57:07.022Z" }, + { url = "https://files.pythonhosted.org/packages/ab/88/550d41e81e6d43335603a960cd9c75c1d88f9cf01bc9d4ee8e86290aba7d/pint-0.25.2-py3-none-any.whl", hash = "sha256:ca35ab1d8eeeb6f7d9942b3cb5f34ca42b61cdd5fb3eae79531553dcca04dda7", size = 306762, upload-time = "2025-11-06T22:08:07.745Z" }, ] [[package]] name = "platformdirs" -version = "4.9.4" +version = "4.9.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/56/8d4c30c8a1d07013911a8fdbd8f89440ef9f08d07a1b50ab8ca8be5a20f9/platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934", size = 28737, upload-time = "2026-03-05T18:34:13.271Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394, upload-time = "2026-02-16T03:56:10.574Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868", size = 21216, upload-time = "2026-03-05T18:34:12.172Z" }, + { url = "https://files.pythonhosted.org/packages/48/31/05e764397056194206169869b50cf2fee4dbbbc71b344705b9c0d878d4d8/platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd", size = 21168, upload-time = "2026-02-16T03:56:08.891Z" }, ] [[package]] name = "pluggy" version = "1.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] [[package]] @@ -2438,18 +2344,18 @@ dependencies = [ { name = "pyyaml" }, { name = "virtualenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232 } +sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437 }, + { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" }, ] [[package]] name = "prometheus-client" version = "0.24.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f0/58/a794d23feb6b00fc0c72787d7e87d872a6730dd9ed7c7b3e954637d8f280/prometheus_client-0.24.1.tar.gz", hash = "sha256:7e0ced7fbbd40f7b84962d5d2ab6f17ef88a72504dcf7c0b40737b43b2a461f9", size = 85616 } +sdist = { url = "https://files.pythonhosted.org/packages/f0/58/a794d23feb6b00fc0c72787d7e87d872a6730dd9ed7c7b3e954637d8f280/prometheus_client-0.24.1.tar.gz", hash = "sha256:7e0ced7fbbd40f7b84962d5d2ab6f17ef88a72504dcf7c0b40737b43b2a461f9", size = 85616, upload-time = "2026-01-14T15:26:26.965Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.1-py3-none-any.whl", hash = "sha256:150db128af71a5c2482b36e588fc8a6b95e498750da4b17065947c16070f4055", size = 64057 }, + { url = "https://files.pythonhosted.org/packages/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.1-py3-none-any.whl", hash = "sha256:150db128af71a5c2482b36e588fc8a6b95e498750da4b17065947c16070f4055", size = 64057, upload-time = "2026-01-14T15:26:24.42Z" }, ] [[package]] @@ -2459,126 +2365,126 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "wcwidth" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198 } +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431 }, + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, ] [[package]] name = "protobuf" -version = "7.34.1" +version = "7.34.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6b/6b/a0e95cad1ad7cc3f2c6821fcab91671bd5b78bd42afb357bb4765f29bc41/protobuf-7.34.1.tar.gz", hash = "sha256:9ce42245e704cc5027be797c1db1eb93184d44d1cdd71811fb2d9b25ad541280", size = 454708, upload-time = "2026-03-20T17:34:47.036Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/00/04a2ab36b70a52d0356852979e08b44edde0435f2115dc66e25f2100f3ab/protobuf-7.34.0.tar.gz", hash = "sha256:3871a3df67c710aaf7bb8d214cc997342e63ceebd940c8c7fc65c9b3d697591a", size = 454726, upload-time = "2026-02-27T00:30:25.421Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/11/3325d41e6ee15bf1125654301211247b042563bcc898784351252549a8ad/protobuf-7.34.1-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:d8b2cc79c4d8f62b293ad9b11ec3aebce9af481fa73e64556969f7345ebf9fc7", size = 429247, upload-time = "2026-03-20T17:34:37.024Z" }, - { url = "https://files.pythonhosted.org/packages/eb/9d/aa69df2724ff63efa6f72307b483ce0827f4347cc6d6df24b59e26659fef/protobuf-7.34.1-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:5185e0e948d07abe94bb76ec9b8416b604cfe5da6f871d67aad30cbf24c3110b", size = 325753, upload-time = "2026-03-20T17:34:38.751Z" }, - { url = "https://files.pythonhosted.org/packages/92/e8/d174c91fd48e50101943f042b09af9029064810b734e4160bbe282fa1caa/protobuf-7.34.1-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:403b093a6e28a960372b44e5eb081775c9b056e816a8029c61231743d63f881a", size = 340198, upload-time = "2026-03-20T17:34:39.871Z" }, - { url = "https://files.pythonhosted.org/packages/53/1b/3b431694a4dc6d37b9f653f0c64b0a0d9ec074ee810710c0c3da21d67ba7/protobuf-7.34.1-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:8ff40ce8cd688f7265326b38d5a1bed9bfdf5e6723d49961432f83e21d5713e4", size = 324267, upload-time = "2026-03-20T17:34:41.1Z" }, - { url = "https://files.pythonhosted.org/packages/85/29/64de04a0ac142fb685fd09999bc3d337943fb386f3a0ec57f92fd8203f97/protobuf-7.34.1-cp310-abi3-win32.whl", hash = "sha256:34b84ce27680df7cca9f231043ada0daa55d0c44a2ddfaa58ec1d0d89d8bf60a", size = 426628, upload-time = "2026-03-20T17:34:42.536Z" }, - { url = "https://files.pythonhosted.org/packages/4d/87/cb5e585192a22b8bd457df5a2c16a75ea0db9674c3a0a39fc9347d84e075/protobuf-7.34.1-cp310-abi3-win_amd64.whl", hash = "sha256:e97b55646e6ce5cbb0954a8c28cd39a5869b59090dfaa7df4598a7fba869468c", size = 437901, upload-time = "2026-03-20T17:34:44.112Z" }, - { url = "https://files.pythonhosted.org/packages/88/95/608f665226bca68b736b79e457fded9a2a38c4f4379a4a7614303d9db3bc/protobuf-7.34.1-py3-none-any.whl", hash = "sha256:bb3812cd53aefea2b028ef42bd780f5b96407247f20c6ef7c679807e9d188f11", size = 170715, upload-time = "2026-03-20T17:34:45.384Z" }, + { url = "https://files.pythonhosted.org/packages/13/c4/6322ab5c8f279c4c358bc14eb8aefc0550b97222a39f04eb3c1af7a830fa/protobuf-7.34.0-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e329966799f2c271d5e05e236459fe1cbfdb8755aaa3b0914fa60947ddea408", size = 429248, upload-time = "2026-02-27T00:30:14.924Z" }, + { url = "https://files.pythonhosted.org/packages/45/99/b029bbbc61e8937545da5b79aa405ab2d9cf307a728f8c9459ad60d7a481/protobuf-7.34.0-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:9d7a5005fb96f3c1e64f397f91500b0eb371b28da81296ae73a6b08a5b76cdd6", size = 325753, upload-time = "2026-02-27T00:30:17.247Z" }, + { url = "https://files.pythonhosted.org/packages/cc/79/09f02671eb75b251c5550a1c48e7b3d4b0623efd7c95a15a50f6f9fc1e2e/protobuf-7.34.0-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:4a72a8ec94e7a9f7ef7fe818ed26d073305f347f8b3b5ba31e22f81fd85fca02", size = 340200, upload-time = "2026-02-27T00:30:18.672Z" }, + { url = "https://files.pythonhosted.org/packages/b5/57/89727baef7578897af5ed166735ceb315819f1c184da8c3441271dbcfde7/protobuf-7.34.0-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:964cf977e07f479c0697964e83deda72bcbc75c3badab506fb061b352d991b01", size = 324268, upload-time = "2026-02-27T00:30:20.088Z" }, + { url = "https://files.pythonhosted.org/packages/1f/3e/38ff2ddee5cc946f575c9d8cc822e34bde205cf61acf8099ad88ef19d7d2/protobuf-7.34.0-cp310-abi3-win32.whl", hash = "sha256:f791ec509707a1d91bd02e07df157e75e4fb9fbdad12a81b7396201ec244e2e3", size = 426628, upload-time = "2026-02-27T00:30:21.555Z" }, + { url = "https://files.pythonhosted.org/packages/cb/71/7c32eaf34a61a1bae1b62a2ac4ffe09b8d1bb0cf93ad505f42040023db89/protobuf-7.34.0-cp310-abi3-win_amd64.whl", hash = "sha256:9f9079f1dde4e32342ecbd1c118d76367090d4aaa19da78230c38101c5b3dd40", size = 437901, upload-time = "2026-02-27T00:30:22.836Z" }, + { url = "https://files.pythonhosted.org/packages/a4/e7/14dc9366696dcb53a413449881743426ed289d687bcf3d5aee4726c32ebb/protobuf-7.34.0-py3-none-any.whl", hash = "sha256:e3b914dd77fa33fa06ab2baa97937746ab25695f389869afdf03e81f34e45dc7", size = 170716, upload-time = "2026-02-27T00:30:23.994Z" }, ] [[package]] name = "psutil" version = "7.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595 }, - { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082 }, - { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476 }, - { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062 }, - { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893 }, - { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589 }, - { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664 }, - { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087 }, - { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383 }, - { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210 }, - { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228 }, - { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284 }, - { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090 }, - { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859 }, - { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560 }, - { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997 }, - { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972 }, - { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266 }, - { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737 }, - { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617 }, +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, ] [[package]] name = "psygnal" version = "0.15.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4e/79/20c3e23e75272e9ddf018097cf872ab088bccba978888472656629efa4a3/psygnal-0.15.1.tar.gz", hash = "sha256:f64f62dee2306fc1c22050a59b6c6cdad126e04b0cf50e393ff858a1da719096", size = 123147 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/a7/69495410025cc4298765545ce3b8c635cd4c8d3a362b7fbbc15b80e9fc8f/psygnal-0.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1adc41515f648696990964433f1e25d8dfd306813a3645366c85e01986ba57a0", size = 581002 }, - { url = "https://files.pythonhosted.org/packages/75/1f/19a8126ccf3cd3974ba5d08a435a049b666961d90f5848ba83599d7a29de/psygnal-0.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:38ff18455b2ac73d4e8eea82ef298ce904b52e4dfdc603a24380c9c440e37519", size = 567775 }, - { url = "https://files.pythonhosted.org/packages/54/c5/b1348880d603edb82128a721397a1ddcf3dfcf5384fe5689db6e471118ae/psygnal-0.15.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c923c322eeefb1140886927cfe7bda7c32341087e290e812b9c69a624ab72d54", size = 855961 }, - { url = "https://files.pythonhosted.org/packages/e6/42/3da2d6f3583bd1a849f7faa2fd3492b14bfda05012519ceaea5992658af0/psygnal-0.15.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2714ddaa41ea3134c0ee91cebd5fb11a88f254ea1d5948806ab0ad5f8be603d5", size = 862721 }, - { url = "https://files.pythonhosted.org/packages/4d/14/6fc7e97fdecf7e8c5c105684bab784920312a3259800d8b53e3cf8783f42/psygnal-0.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:877516056a5a383427a647fff2fad5179eaa3e12de2c083c273e748435414aef", size = 415696 }, - { url = "https://files.pythonhosted.org/packages/76/65/b7bbca96bc477aa9ac2264e5907b2f4ccfcd1319f776dd1f35eec06cc2f4/psygnal-0.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8d56f0f35eaf4a21f660de76885222faf9e8c7112454528d3394d464f3d4d1a3", size = 598340 }, - { url = "https://files.pythonhosted.org/packages/40/f2/56577465a1b42a5e6780bb5fab53fb68f8bfd72f0131ed397576529af724/psygnal-0.15.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0febcf757a1323d9b8bd75735ee3569213d8110012a7bf0f478e85c5ab459fc6", size = 575311 }, - { url = "https://files.pythonhosted.org/packages/79/81/f642ac08104049383076f83480ed412c9626e068769a1c34873c595bec0e/psygnal-0.15.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b5e4837dfbfa4974dabe0795e32be9aadcd87603adf734738ce1114f72238a05", size = 889770 }, - { url = "https://files.pythonhosted.org/packages/de/43/e571fa40b72780abed080ef829e5ad98017b6fe48d28c15a2404e006b676/psygnal-0.15.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:07b4c4e03bbf4e8cad7e25f4fbc1ba9575fb9c3d14991bc7edfeb8b09c8d6d54", size = 881105 }, - { url = "https://files.pythonhosted.org/packages/e3/26/ef3ab825eb08eaecbbceeeb56383694fe64ce399dbfd1d0767bb85688785/psygnal-0.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:4f0ce91b9c18e92281bf2c3fc4bb4e808d90f0b023d0a37b302d354188520338", size = 418969 }, - { url = "https://files.pythonhosted.org/packages/46/21/5a142165d27063abf5921807d3c3d973f5d44ab414a13b210839a43ead4d/psygnal-0.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2087aadc9404f007f79c2899e329932869e362c50de58b90631c5f49b4768cc5", size = 596768 }, - { url = "https://files.pythonhosted.org/packages/e1/25/c1712931d61c118691e73daf29ef708c679ea9ba187c797dd5deee360411/psygnal-0.15.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0f3bf68ca42569dfdce20c6cf915d34b78b9e3ddddacb9f78728224fda6946b4", size = 574808 }, - { url = "https://files.pythonhosted.org/packages/2d/4f/3593e5adb88a188c798604aed95fbc1479f30230e7f51e8f2c770e6a3832/psygnal-0.15.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e9fca977f5335deea39aed22e31d9795983e4f243e59a7d3c4105793adb7693d", size = 885616 }, - { url = "https://files.pythonhosted.org/packages/58/4c/14779ed4c3a1d71fa1a9a87ecfb184ad3335dd64681067f77c1c47b14ae9/psygnal-0.15.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c85b7d05b92ccbec47c75ab8a5545eda462e81a492c82424aba5ab81a3ad89d", size = 876516 }, - { url = "https://files.pythonhosted.org/packages/3e/bc/4f771e3cdcde4db4023dbf36d6f0aab44e02b9de719353c22954b655e2ff/psygnal-0.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:ac0e693b29e0a429e97315a52313321855bef6140e9975b7ae78b4d93c8fbb42", size = 419172 }, - { url = "https://files.pythonhosted.org/packages/f4/2e/975bd61727578d88df62797f78390965ca7905780cf01eb59cb095a13638/psygnal-0.15.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:803fc33c4280c822c6f4b22e6c3ea7c4483e190f3cc69e69350098b3799476f3", size = 595706 }, - { url = "https://files.pythonhosted.org/packages/b8/55/e487f1d91497eb75e86c3fdfef69a21b1cab24d023383dd7648b08797d6a/psygnal-0.15.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4f53b4b83355b0a785b745987fd04e59bbf169a9028ed81a68ca7e05fb76d458", size = 575133 }, - { url = "https://files.pythonhosted.org/packages/bf/2f/f286355accd0e68d3eef52e63c8b9ab6ba33ec3107177719a036b3319657/psygnal-0.15.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bcbca12190f5aa65c1f8fb04a81fa6f4463c5f5dde25cd74c3a56ceff6f37b02", size = 889565 }, - { url = "https://files.pythonhosted.org/packages/fc/dc/40c6026c88d7f9220ecc913afe0501045a512c9b82f9b7e036bb089dc287/psygnal-0.15.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1ac399566852fe4354ce26a1acbe12319232e8c2b615fe5ad1e114c547095cf6", size = 880863 }, - { url = "https://files.pythonhosted.org/packages/b7/85/b4f45ec3057c473b5622fc002b3a636a698c34d3a0917a064ff5247f1984/psygnal-0.15.1-cp314-cp314-win_amd64.whl", hash = "sha256:d3a03055f331ce91d44581c71edb79938ccc133a94af2ce7ad3a18fa57ac7be5", size = 423654 }, - { url = "https://files.pythonhosted.org/packages/46/49/7742544684bee728ec123515d2694cee859aa2a705951a461230b00f18cc/psygnal-0.15.1-py3-none-any.whl", hash = "sha256:4221140e633e45b076953c64bcb9b41a744833527f9a037c1ca98bc270798cbf", size = 90638 }, +sdist = { url = "https://files.pythonhosted.org/packages/4e/79/20c3e23e75272e9ddf018097cf872ab088bccba978888472656629efa4a3/psygnal-0.15.1.tar.gz", hash = "sha256:f64f62dee2306fc1c22050a59b6c6cdad126e04b0cf50e393ff858a1da719096", size = 123147, upload-time = "2026-01-04T16:38:41.959Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/a7/69495410025cc4298765545ce3b8c635cd4c8d3a362b7fbbc15b80e9fc8f/psygnal-0.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1adc41515f648696990964433f1e25d8dfd306813a3645366c85e01986ba57a0", size = 581002, upload-time = "2026-01-04T16:38:12.753Z" }, + { url = "https://files.pythonhosted.org/packages/75/1f/19a8126ccf3cd3974ba5d08a435a049b666961d90f5848ba83599d7a29de/psygnal-0.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:38ff18455b2ac73d4e8eea82ef298ce904b52e4dfdc603a24380c9c440e37519", size = 567775, upload-time = "2026-01-04T16:38:14.04Z" }, + { url = "https://files.pythonhosted.org/packages/54/c5/b1348880d603edb82128a721397a1ddcf3dfcf5384fe5689db6e471118ae/psygnal-0.15.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c923c322eeefb1140886927cfe7bda7c32341087e290e812b9c69a624ab72d54", size = 855961, upload-time = "2026-01-04T16:38:15.612Z" }, + { url = "https://files.pythonhosted.org/packages/e6/42/3da2d6f3583bd1a849f7faa2fd3492b14bfda05012519ceaea5992658af0/psygnal-0.15.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2714ddaa41ea3134c0ee91cebd5fb11a88f254ea1d5948806ab0ad5f8be603d5", size = 862721, upload-time = "2026-01-04T16:38:17.059Z" }, + { url = "https://files.pythonhosted.org/packages/4d/14/6fc7e97fdecf7e8c5c105684bab784920312a3259800d8b53e3cf8783f42/psygnal-0.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:877516056a5a383427a647fff2fad5179eaa3e12de2c083c273e748435414aef", size = 415696, upload-time = "2026-01-04T16:38:18.355Z" }, + { url = "https://files.pythonhosted.org/packages/76/65/b7bbca96bc477aa9ac2264e5907b2f4ccfcd1319f776dd1f35eec06cc2f4/psygnal-0.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8d56f0f35eaf4a21f660de76885222faf9e8c7112454528d3394d464f3d4d1a3", size = 598340, upload-time = "2026-01-04T16:38:19.752Z" }, + { url = "https://files.pythonhosted.org/packages/40/f2/56577465a1b42a5e6780bb5fab53fb68f8bfd72f0131ed397576529af724/psygnal-0.15.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0febcf757a1323d9b8bd75735ee3569213d8110012a7bf0f478e85c5ab459fc6", size = 575311, upload-time = "2026-01-04T16:38:21.137Z" }, + { url = "https://files.pythonhosted.org/packages/79/81/f642ac08104049383076f83480ed412c9626e068769a1c34873c595bec0e/psygnal-0.15.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b5e4837dfbfa4974dabe0795e32be9aadcd87603adf734738ce1114f72238a05", size = 889770, upload-time = "2026-01-04T16:38:22.629Z" }, + { url = "https://files.pythonhosted.org/packages/de/43/e571fa40b72780abed080ef829e5ad98017b6fe48d28c15a2404e006b676/psygnal-0.15.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:07b4c4e03bbf4e8cad7e25f4fbc1ba9575fb9c3d14991bc7edfeb8b09c8d6d54", size = 881105, upload-time = "2026-01-04T16:38:23.896Z" }, + { url = "https://files.pythonhosted.org/packages/e3/26/ef3ab825eb08eaecbbceeeb56383694fe64ce399dbfd1d0767bb85688785/psygnal-0.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:4f0ce91b9c18e92281bf2c3fc4bb4e808d90f0b023d0a37b302d354188520338", size = 418969, upload-time = "2026-01-04T16:38:25.731Z" }, + { url = "https://files.pythonhosted.org/packages/46/21/5a142165d27063abf5921807d3c3d973f5d44ab414a13b210839a43ead4d/psygnal-0.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2087aadc9404f007f79c2899e329932869e362c50de58b90631c5f49b4768cc5", size = 596768, upload-time = "2026-01-04T16:38:27.053Z" }, + { url = "https://files.pythonhosted.org/packages/e1/25/c1712931d61c118691e73daf29ef708c679ea9ba187c797dd5deee360411/psygnal-0.15.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0f3bf68ca42569dfdce20c6cf915d34b78b9e3ddddacb9f78728224fda6946b4", size = 574808, upload-time = "2026-01-04T16:38:28.779Z" }, + { url = "https://files.pythonhosted.org/packages/2d/4f/3593e5adb88a188c798604aed95fbc1479f30230e7f51e8f2c770e6a3832/psygnal-0.15.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e9fca977f5335deea39aed22e31d9795983e4f243e59a7d3c4105793adb7693d", size = 885616, upload-time = "2026-01-04T16:38:30.081Z" }, + { url = "https://files.pythonhosted.org/packages/58/4c/14779ed4c3a1d71fa1a9a87ecfb184ad3335dd64681067f77c1c47b14ae9/psygnal-0.15.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c85b7d05b92ccbec47c75ab8a5545eda462e81a492c82424aba5ab81a3ad89d", size = 876516, upload-time = "2026-01-04T16:38:31.422Z" }, + { url = "https://files.pythonhosted.org/packages/3e/bc/4f771e3cdcde4db4023dbf36d6f0aab44e02b9de719353c22954b655e2ff/psygnal-0.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:ac0e693b29e0a429e97315a52313321855bef6140e9975b7ae78b4d93c8fbb42", size = 419172, upload-time = "2026-01-04T16:38:32.82Z" }, + { url = "https://files.pythonhosted.org/packages/f4/2e/975bd61727578d88df62797f78390965ca7905780cf01eb59cb095a13638/psygnal-0.15.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:803fc33c4280c822c6f4b22e6c3ea7c4483e190f3cc69e69350098b3799476f3", size = 595706, upload-time = "2026-01-04T16:38:34.139Z" }, + { url = "https://files.pythonhosted.org/packages/b8/55/e487f1d91497eb75e86c3fdfef69a21b1cab24d023383dd7648b08797d6a/psygnal-0.15.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4f53b4b83355b0a785b745987fd04e59bbf169a9028ed81a68ca7e05fb76d458", size = 575133, upload-time = "2026-01-04T16:38:35.448Z" }, + { url = "https://files.pythonhosted.org/packages/bf/2f/f286355accd0e68d3eef52e63c8b9ab6ba33ec3107177719a036b3319657/psygnal-0.15.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bcbca12190f5aa65c1f8fb04a81fa6f4463c5f5dde25cd74c3a56ceff6f37b02", size = 889565, upload-time = "2026-01-04T16:38:37.003Z" }, + { url = "https://files.pythonhosted.org/packages/fc/dc/40c6026c88d7f9220ecc913afe0501045a512c9b82f9b7e036bb089dc287/psygnal-0.15.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1ac399566852fe4354ce26a1acbe12319232e8c2b615fe5ad1e114c547095cf6", size = 880863, upload-time = "2026-01-04T16:38:38.381Z" }, + { url = "https://files.pythonhosted.org/packages/b7/85/b4f45ec3057c473b5622fc002b3a636a698c34d3a0917a064ff5247f1984/psygnal-0.15.1-cp314-cp314-win_amd64.whl", hash = "sha256:d3a03055f331ce91d44581c71edb79938ccc133a94af2ce7ad3a18fa57ac7be5", size = 423654, upload-time = "2026-01-04T16:38:39.7Z" }, + { url = "https://files.pythonhosted.org/packages/46/49/7742544684bee728ec123515d2694cee859aa2a705951a461230b00f18cc/psygnal-0.15.1-py3-none-any.whl", hash = "sha256:4221140e633e45b076953c64bcb9b41a744833527f9a037c1ca98bc270798cbf", size = 90638, upload-time = "2026-01-04T16:38:40.841Z" }, ] [[package]] name = "ptyprocess" version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762 } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993 }, + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, ] [[package]] name = "pure-eval" version = "0.2.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752 } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842 }, + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, ] [[package]] name = "pycparser" version = "3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492 } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172 }, + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] [[package]] name = "pygments" version = "2.19.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631 } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 }, + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] [[package]] name = "pyparsing" version = "3.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574 } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781 }, + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, ] [[package]] @@ -2592,43 +2498,43 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901 } +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801 }, + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] [[package]] name = "pytest-cov" -version = "7.1.0" +version = "7.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage", extra = ["toml"] }, { name = "pluggy" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, + { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, ] [[package]] name = "python-box" version = "7.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/0f/34e7ee0a72f1464b4c7a2e8bafb389f230477256af586bc82bcfad85295a/python_box-7.4.1.tar.gz", hash = "sha256:e412e36c25fca8223560516d53ef6c7993591c3b0ec8bb4ec582bf7defdd79f0", size = 49859 } +sdist = { url = "https://files.pythonhosted.org/packages/0f/0f/34e7ee0a72f1464b4c7a2e8bafb389f230477256af586bc82bcfad85295a/python_box-7.4.1.tar.gz", hash = "sha256:e412e36c25fca8223560516d53ef6c7993591c3b0ec8bb4ec582bf7defdd79f0", size = 49859, upload-time = "2026-02-21T16:21:16.008Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/a8/c8bcd3ff0905ec549273ea3485e6b9f2039f57baab419123fb18f964f829/python_box-7.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3f76dad8be9d57d65a3edc792b952f7afe3991515aa6eba616cf5efb2fbb2e0c", size = 1870869 }, - { url = "https://files.pythonhosted.org/packages/0f/bc/9382766d388e258363a18a094e251d2624e3c524614c733d1afa989d9770/python_box-7.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c66582f41a94d46cb0896d468b0efebf9bc4c3a5634cd15373d871767c2e741d", size = 4494287 }, - { url = "https://files.pythonhosted.org/packages/8c/cf/b9d1d4550615f69f6f9c72767f026543442739e5c56adf6f844b50d88251/python_box-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:43c62f66d694eb6410f51eb2eb5726f9b466e6f685e5dc90b5cd11f7b3047362", size = 1321085 }, - { url = "https://files.pythonhosted.org/packages/4d/d9/d05f317b38b42253422d8483f5d7dc16d382c99ddc253e426639a0f2f235/python_box-7.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dfb91effff00d9e23486c4f0db3b19e03d602ebb7c9e20fc6a287c704fad2552", size = 1849441 }, - { url = "https://files.pythonhosted.org/packages/ba/a3/383eb3d658f36c6e531c8cf1e348ccb4b5031231df4aeb7742bb159a3166/python_box-7.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f977f00e715b030cee6ffef2322ff8ce100ffbf1dbcc4ef91099c75752d5f8", size = 4485153 }, - { url = "https://files.pythonhosted.org/packages/65/f9/5de3c18415dd6f5286f00e6539c0ae3cceb1c6aaf28d1d5f17b0b568c97f/python_box-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ca9a18fd15326bc267e9cc7e0e6e3a0cb78d11507940f43f687adf7e156d882", size = 1295520 }, - { url = "https://files.pythonhosted.org/packages/ec/e9/48d1b1eb21efc3f82a31b037b6903c9139018f686d96d251faa4cb0d593a/python_box-7.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:85db37b43094bf6c4884b931fb149a7850db5ce331f6e191edf98b453e6cf2d6", size = 1845195 }, - { url = "https://files.pythonhosted.org/packages/da/79/48d38c855f277223caf3aa79518476f95abc07f04386940855b7bd3d95f6/python_box-7.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bb204822c7638bd2dbed5c55d6ab264c6903c37d18dee5c45bdbda58b2e1e17a", size = 4468245 }, - { url = "https://files.pythonhosted.org/packages/17/1d/7a1e04f37674399e0f3076cfe1fa358f6a51540ae98299a06f2c0424c471/python_box-7.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:615da3fafd41572aec1b905832555c0ea08b6fbc27cc917356e257a9a5721af7", size = 1295564 }, - { url = "https://files.pythonhosted.org/packages/94/a2/771b5e526bba2214ac2d30e321209a66680c40788616a45cf01005e95204/python_box-7.4.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:33c6701faa51fd87f0dcc538873c0fad2b3a1cc3750eab85835cd071cadf1948", size = 1875508 }, - { url = "https://files.pythonhosted.org/packages/a6/5f/0e7ea7640ba60ff459ce37e340d816ac5e91b7a9a7c3c161f9dabe622be6/python_box-7.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:ae8c540a0457f52350211d24690211251912018e1e0c1857f50792729d6f562c", size = 1314304 }, - { url = "https://files.pythonhosted.org/packages/06/a6/5d3f3abf46b37aa44b1f6788d287c8b4f2319b55013191dddf25b9e6d62c/python_box-7.4.1-py3-none-any.whl", hash = "sha256:a3b0d84d003882fb6abe505b1b883b3a5dcbf226b0fe168d24bc5ff75d9826e5", size = 30402 }, + { url = "https://files.pythonhosted.org/packages/f8/a8/c8bcd3ff0905ec549273ea3485e6b9f2039f57baab419123fb18f964f829/python_box-7.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3f76dad8be9d57d65a3edc792b952f7afe3991515aa6eba616cf5efb2fbb2e0c", size = 1870869, upload-time = "2026-02-21T16:21:34.16Z" }, + { url = "https://files.pythonhosted.org/packages/0f/bc/9382766d388e258363a18a094e251d2624e3c524614c733d1afa989d9770/python_box-7.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c66582f41a94d46cb0896d468b0efebf9bc4c3a5634cd15373d871767c2e741d", size = 4494287, upload-time = "2026-02-21T16:26:03.131Z" }, + { url = "https://files.pythonhosted.org/packages/8c/cf/b9d1d4550615f69f6f9c72767f026543442739e5c56adf6f844b50d88251/python_box-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:43c62f66d694eb6410f51eb2eb5726f9b466e6f685e5dc90b5cd11f7b3047362", size = 1321085, upload-time = "2026-02-21T16:22:01.093Z" }, + { url = "https://files.pythonhosted.org/packages/4d/d9/d05f317b38b42253422d8483f5d7dc16d382c99ddc253e426639a0f2f235/python_box-7.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dfb91effff00d9e23486c4f0db3b19e03d602ebb7c9e20fc6a287c704fad2552", size = 1849441, upload-time = "2026-02-21T16:21:37.314Z" }, + { url = "https://files.pythonhosted.org/packages/ba/a3/383eb3d658f36c6e531c8cf1e348ccb4b5031231df4aeb7742bb159a3166/python_box-7.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f977f00e715b030cee6ffef2322ff8ce100ffbf1dbcc4ef91099c75752d5f8", size = 4485153, upload-time = "2026-02-21T16:26:04.507Z" }, + { url = "https://files.pythonhosted.org/packages/65/f9/5de3c18415dd6f5286f00e6539c0ae3cceb1c6aaf28d1d5f17b0b568c97f/python_box-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ca9a18fd15326bc267e9cc7e0e6e3a0cb78d11507940f43f687adf7e156d882", size = 1295520, upload-time = "2026-02-21T16:22:26.192Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e9/48d1b1eb21efc3f82a31b037b6903c9139018f686d96d251faa4cb0d593a/python_box-7.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:85db37b43094bf6c4884b931fb149a7850db5ce331f6e191edf98b453e6cf2d6", size = 1845195, upload-time = "2026-02-21T16:21:46.235Z" }, + { url = "https://files.pythonhosted.org/packages/da/79/48d38c855f277223caf3aa79518476f95abc07f04386940855b7bd3d95f6/python_box-7.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bb204822c7638bd2dbed5c55d6ab264c6903c37d18dee5c45bdbda58b2e1e17a", size = 4468245, upload-time = "2026-02-21T16:26:05.701Z" }, + { url = "https://files.pythonhosted.org/packages/17/1d/7a1e04f37674399e0f3076cfe1fa358f6a51540ae98299a06f2c0424c471/python_box-7.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:615da3fafd41572aec1b905832555c0ea08b6fbc27cc917356e257a9a5721af7", size = 1295564, upload-time = "2026-02-21T16:22:36.547Z" }, + { url = "https://files.pythonhosted.org/packages/94/a2/771b5e526bba2214ac2d30e321209a66680c40788616a45cf01005e95204/python_box-7.4.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:33c6701faa51fd87f0dcc538873c0fad2b3a1cc3750eab85835cd071cadf1948", size = 1875508, upload-time = "2026-02-21T16:21:37.432Z" }, + { url = "https://files.pythonhosted.org/packages/a6/5f/0e7ea7640ba60ff459ce37e340d816ac5e91b7a9a7c3c161f9dabe622be6/python_box-7.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:ae8c540a0457f52350211d24690211251912018e1e0c1857f50792729d6f562c", size = 1314304, upload-time = "2026-02-21T16:22:22.173Z" }, + { url = "https://files.pythonhosted.org/packages/06/a6/5d3f3abf46b37aa44b1f6788d287c8b4f2319b55013191dddf25b9e6d62c/python_box-7.4.1-py3-none-any.whl", hash = "sha256:a3b0d84d003882fb6abe505b1b883b3a5dcbf226b0fe168d24bc5ff75d9826e5", size = 30402, upload-time = "2026-02-21T16:21:14.78Z" }, ] [[package]] @@ -2638,106 +2544,106 @@ 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 } +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] [[package]] name = "python-discovery" -version = "1.2.0" +version = "1.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9c/90/bcce6b46823c9bec1757c964dc37ed332579be512e17a30e9698095dcae4/python_discovery-1.2.0.tar.gz", hash = "sha256:7d33e350704818b09e3da2bd419d37e21e7c30db6e0977bb438916e06b41b5b1", size = 58055, upload-time = "2026-03-19T01:43:08.248Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/bb/93a3e83bdf9322c7e21cafd092e56a4a17c4d8ef4277b6eb01af1a540a6f/python_discovery-1.1.0.tar.gz", hash = "sha256:447941ba1aed8cc2ab7ee3cb91be5fc137c5bdbb05b7e6ea62fbdcb66e50b268", size = 55674, upload-time = "2026-02-26T09:42:49.668Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/3c/2005227cb951df502412de2fa781f800663cccbef8d90ec6f1b371ac2c0d/python_discovery-1.2.0-py3-none-any.whl", hash = "sha256:1e108f1bbe2ed0ef089823d28805d5ad32be8e734b86a5f212bf89b71c266e4a", size = 31524, upload-time = "2026-03-19T01:43:07.045Z" }, + { url = "https://files.pythonhosted.org/packages/06/54/82a6e2ef37f0f23dccac604b9585bdcbd0698604feb64807dcb72853693e/python_discovery-1.1.0-py3-none-any.whl", hash = "sha256:a162893b8809727f54594a99ad2179d2ede4bf953e12d4c7abc3cc9cdbd1437b", size = 30687, upload-time = "2026-02-26T09:42:48.548Z" }, ] [[package]] name = "python-json-logger" version = "4.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/29/bf/eca6a3d43db1dae7070f70e160ab20b807627ba953663ba07928cdd3dc58/python_json_logger-4.0.0.tar.gz", hash = "sha256:f58e68eb46e1faed27e0f574a55a0455eecd7b8a5b88b85a784519ba3cff047f", size = 17683 } +sdist = { url = "https://files.pythonhosted.org/packages/29/bf/eca6a3d43db1dae7070f70e160ab20b807627ba953663ba07928cdd3dc58/python_json_logger-4.0.0.tar.gz", hash = "sha256:f58e68eb46e1faed27e0f574a55a0455eecd7b8a5b88b85a784519ba3cff047f", size = 17683, upload-time = "2025-10-06T04:15:18.984Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl", hash = "sha256:af09c9daf6a813aa4cc7180395f50f2a9e5fa056034c9953aec92e381c5ba1e2", size = 15548 }, + { url = "https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl", hash = "sha256:af09c9daf6a813aa4cc7180395f50f2a9e5fa056034c9953aec92e381c5ba1e2", size = 15548, upload-time = "2025-10-06T04:15:17.553Z" }, ] [[package]] name = "pywinpty" version = "3.0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f7/54/37c7370ba91f579235049dc26cd2c5e657d2a943e01820844ffc81f32176/pywinpty-3.0.3.tar.gz", hash = "sha256:523441dc34d231fb361b4b00f8c99d3f16de02f5005fd544a0183112bcc22412", size = 31309 } +sdist = { url = "https://files.pythonhosted.org/packages/f7/54/37c7370ba91f579235049dc26cd2c5e657d2a943e01820844ffc81f32176/pywinpty-3.0.3.tar.gz", hash = "sha256:523441dc34d231fb361b4b00f8c99d3f16de02f5005fd544a0183112bcc22412", size = 31309, upload-time = "2026-02-04T21:51:09.524Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/c3/3e75075c7f71735f22b66fab0481f2c98e3a4d58cba55cb50ba29114bcf6/pywinpty-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:dff25a9a6435f527d7c65608a7e62783fc12076e7d44487a4911ee91be5a8ac8", size = 2114430 }, - { url = "https://files.pythonhosted.org/packages/8d/1e/8a54166a8c5e4f5cb516514bdf4090be4d51a71e8d9f6d98c0aa00fe45d4/pywinpty-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:fbc1e230e5b193eef4431cba3f39996a288f9958f9c9f092c8a961d930ee8f68", size = 236191 }, - { url = "https://files.pythonhosted.org/packages/7c/d4/aeb5e1784d2c5bff6e189138a9ca91a090117459cea0c30378e1f2db3d54/pywinpty-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:c9081df0e49ffa86d15db4a6ba61530630e48707f987df42c9d3313537e81fc0", size = 2113098 }, - { url = "https://files.pythonhosted.org/packages/b9/53/7278223c493ccfe4883239cf06c823c56460a8010e0fc778eef67858dc14/pywinpty-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:15e79d870e18b678fb8a5a6105fd38496b55697c66e6fc0378236026bc4d59e9", size = 234901 }, - { url = "https://files.pythonhosted.org/packages/e5/cb/58d6ed3fd429c96a90ef01ac9a617af10a6d41469219c25e7dc162abbb71/pywinpty-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9c91dbb026050c77bdcef964e63a4f10f01a639113c4d3658332614544c467ab", size = 2112686 }, - { url = "https://files.pythonhosted.org/packages/fd/50/724ed5c38c504d4e58a88a072776a1e880d970789deaeb2b9f7bd9a5141a/pywinpty-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:fe1f7911805127c94cf51f89ab14096c6f91ffdcacf993d2da6082b2142a2523", size = 234591 }, - { url = "https://files.pythonhosted.org/packages/f7/ad/90a110538696b12b39fd8758a06d70ded899308198ad2305ac68e361126e/pywinpty-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:3f07a6cf1c1d470d284e614733c3d0f726d2c85e78508ea10a403140c3c0c18a", size = 2112360 }, - { url = "https://files.pythonhosted.org/packages/44/0f/7ffa221757a220402bc79fda44044c3f2cc57338d878ab7d622add6f4581/pywinpty-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:15c7c0b6f8e9d87aabbaff76468dabf6e6121332c40fc1d83548d02a9d6a3759", size = 233107 }, - { url = "https://files.pythonhosted.org/packages/28/88/2ff917caff61e55f38bcdb27de06ee30597881b2cae44fbba7627be015c4/pywinpty-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:d4b6b7b0fe0cdcd02e956bd57cfe9f4e5a06514eecf3b5ae174da4f951b58be9", size = 2113282 }, - { url = "https://files.pythonhosted.org/packages/63/32/40a775343ace542cc43ece3f1d1fce454021521ecac41c4c4573081c2336/pywinpty-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:34789d685fc0d547ce0c8a65e5a70e56f77d732fa6e03c8f74fefb8cbb252019", size = 234207 }, - { url = "https://files.pythonhosted.org/packages/8d/54/5d5e52f4cb75028104ca6faf36c10f9692389b1986d34471663b4ebebd6d/pywinpty-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:0c37e224a47a971d1a6e08649a1714dac4f63c11920780977829ed5c8cadead1", size = 2112910 }, - { url = "https://files.pythonhosted.org/packages/0a/44/dcd184824e21d4620b06c7db9fbb15c3ad0a0f1fa2e6de79969fb82647ec/pywinpty-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:c4e9c3dff7d86ba81937438d5819f19f385a39d8f592d4e8af67148ceb4f6ab5", size = 233425 }, + { url = "https://files.pythonhosted.org/packages/79/c3/3e75075c7f71735f22b66fab0481f2c98e3a4d58cba55cb50ba29114bcf6/pywinpty-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:dff25a9a6435f527d7c65608a7e62783fc12076e7d44487a4911ee91be5a8ac8", size = 2114430, upload-time = "2026-02-04T21:54:19.485Z" }, + { url = "https://files.pythonhosted.org/packages/8d/1e/8a54166a8c5e4f5cb516514bdf4090be4d51a71e8d9f6d98c0aa00fe45d4/pywinpty-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:fbc1e230e5b193eef4431cba3f39996a288f9958f9c9f092c8a961d930ee8f68", size = 236191, upload-time = "2026-02-04T21:50:36.239Z" }, + { url = "https://files.pythonhosted.org/packages/7c/d4/aeb5e1784d2c5bff6e189138a9ca91a090117459cea0c30378e1f2db3d54/pywinpty-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:c9081df0e49ffa86d15db4a6ba61530630e48707f987df42c9d3313537e81fc0", size = 2113098, upload-time = "2026-02-04T21:54:37.711Z" }, + { url = "https://files.pythonhosted.org/packages/b9/53/7278223c493ccfe4883239cf06c823c56460a8010e0fc778eef67858dc14/pywinpty-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:15e79d870e18b678fb8a5a6105fd38496b55697c66e6fc0378236026bc4d59e9", size = 234901, upload-time = "2026-02-04T21:53:31.35Z" }, + { url = "https://files.pythonhosted.org/packages/e5/cb/58d6ed3fd429c96a90ef01ac9a617af10a6d41469219c25e7dc162abbb71/pywinpty-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9c91dbb026050c77bdcef964e63a4f10f01a639113c4d3658332614544c467ab", size = 2112686, upload-time = "2026-02-04T21:52:03.035Z" }, + { url = "https://files.pythonhosted.org/packages/fd/50/724ed5c38c504d4e58a88a072776a1e880d970789deaeb2b9f7bd9a5141a/pywinpty-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:fe1f7911805127c94cf51f89ab14096c6f91ffdcacf993d2da6082b2142a2523", size = 234591, upload-time = "2026-02-04T21:52:29.821Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ad/90a110538696b12b39fd8758a06d70ded899308198ad2305ac68e361126e/pywinpty-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:3f07a6cf1c1d470d284e614733c3d0f726d2c85e78508ea10a403140c3c0c18a", size = 2112360, upload-time = "2026-02-04T21:55:33.397Z" }, + { url = "https://files.pythonhosted.org/packages/44/0f/7ffa221757a220402bc79fda44044c3f2cc57338d878ab7d622add6f4581/pywinpty-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:15c7c0b6f8e9d87aabbaff76468dabf6e6121332c40fc1d83548d02a9d6a3759", size = 233107, upload-time = "2026-02-04T21:51:45.455Z" }, + { url = "https://files.pythonhosted.org/packages/28/88/2ff917caff61e55f38bcdb27de06ee30597881b2cae44fbba7627be015c4/pywinpty-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:d4b6b7b0fe0cdcd02e956bd57cfe9f4e5a06514eecf3b5ae174da4f951b58be9", size = 2113282, upload-time = "2026-02-04T21:52:08.188Z" }, + { url = "https://files.pythonhosted.org/packages/63/32/40a775343ace542cc43ece3f1d1fce454021521ecac41c4c4573081c2336/pywinpty-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:34789d685fc0d547ce0c8a65e5a70e56f77d732fa6e03c8f74fefb8cbb252019", size = 234207, upload-time = "2026-02-04T21:51:58.687Z" }, + { url = "https://files.pythonhosted.org/packages/8d/54/5d5e52f4cb75028104ca6faf36c10f9692389b1986d34471663b4ebebd6d/pywinpty-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:0c37e224a47a971d1a6e08649a1714dac4f63c11920780977829ed5c8cadead1", size = 2112910, upload-time = "2026-02-04T21:52:30.976Z" }, + { url = "https://files.pythonhosted.org/packages/0a/44/dcd184824e21d4620b06c7db9fbb15c3ad0a0f1fa2e6de79969fb82647ec/pywinpty-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:c4e9c3dff7d86ba81937438d5819f19f385a39d8f592d4e8af67148ceb4f6ab5", size = 233425, upload-time = "2026-02-04T21:51:56.754Z" }, ] [[package]] name = "pyyaml" version = "6.0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826 }, - { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577 }, - { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556 }, - { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114 }, - { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638 }, - { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463 }, - { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986 }, - { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543 }, - { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763 }, - { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063 }, - { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973 }, - { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116 }, - { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011 }, - { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870 }, - { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089 }, - { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181 }, - { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658 }, - { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003 }, - { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344 }, - { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669 }, - { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252 }, - { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081 }, - { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159 }, - { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626 }, - { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613 }, - { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115 }, - { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427 }, - { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090 }, - { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246 }, - { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814 }, - { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809 }, - { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454 }, - { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355 }, - { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175 }, - { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228 }, - { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194 }, - { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429 }, - { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912 }, - { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108 }, - { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641 }, - { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901 }, - { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132 }, - { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261 }, - { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272 }, - { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923 }, - { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062 }, - { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341 }, +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, ] [[package]] @@ -2747,55 +2653,55 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "implementation_name == 'pypy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86", size = 1333328 }, - { url = "https://files.pythonhosted.org/packages/bd/a0/fc7e78a23748ad5443ac3275943457e8452da67fda347e05260261108cbc/pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581", size = 908803 }, - { url = "https://files.pythonhosted.org/packages/7e/22/37d15eb05f3bdfa4abea6f6d96eb3bb58585fbd3e4e0ded4e743bc650c97/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f", size = 668836 }, - { url = "https://files.pythonhosted.org/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e", size = 857038 }, - { url = "https://files.pythonhosted.org/packages/cb/eb/bfdcb41d0db9cd233d6fb22dc131583774135505ada800ebf14dfb0a7c40/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15c8bd0fe0dabf808e2d7a681398c4e5ded70a551ab47482067a572c054c8e2e", size = 1657531 }, - { url = "https://files.pythonhosted.org/packages/ab/21/e3180ca269ed4a0de5c34417dfe71a8ae80421198be83ee619a8a485b0c7/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bafcb3dd171b4ae9f19ee6380dfc71ce0390fefaf26b504c0e5f628d7c8c54f2", size = 2034786 }, - { url = "https://files.pythonhosted.org/packages/3b/b1/5e21d0b517434b7f33588ff76c177c5a167858cc38ef740608898cd329f2/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e829529fcaa09937189178115c49c504e69289abd39967cd8a4c215761373394", size = 1894220 }, - { url = "https://files.pythonhosted.org/packages/03/f2/44913a6ff6941905efc24a1acf3d3cb6146b636c546c7406c38c49c403d4/pyzmq-27.1.0-cp311-cp311-win32.whl", hash = "sha256:6df079c47d5902af6db298ec92151db82ecb557af663098b92f2508c398bb54f", size = 567155 }, - { url = "https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97", size = 633428 }, - { url = "https://files.pythonhosted.org/packages/ae/14/01afebc96c5abbbd713ecfc7469cfb1bc801c819a74ed5c9fad9a48801cb/pyzmq-27.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:eca6b47df11a132d1745eb3b5b5e557a7dae2c303277aa0e69c6ba91b8736e07", size = 559497 }, - { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279 }, - { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645 }, - { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574 }, - { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995 }, - { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070 }, - { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121 }, - { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550 }, - { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184 }, - { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480 }, - { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993 }, - { url = "https://files.pythonhosted.org/packages/60/cb/84a13459c51da6cec1b7b1dc1a47e6db6da50b77ad7fd9c145842750a011/pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5", size = 1122436 }, - { url = "https://files.pythonhosted.org/packages/dc/b6/94414759a69a26c3dd674570a81813c46a078767d931a6c70ad29fc585cb/pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6", size = 1156301 }, - { url = "https://files.pythonhosted.org/packages/a5/ad/15906493fd40c316377fd8a8f6b1f93104f97a752667763c9b9c1b71d42d/pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7", size = 1341197 }, - { url = "https://files.pythonhosted.org/packages/14/1d/d343f3ce13db53a54cb8946594e567410b2125394dafcc0268d8dda027e0/pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05", size = 897275 }, - { url = "https://files.pythonhosted.org/packages/69/2d/d83dd6d7ca929a2fc67d2c3005415cdf322af7751d773524809f9e585129/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9", size = 660469 }, - { url = "https://files.pythonhosted.org/packages/3e/cd/9822a7af117f4bc0f1952dbe9ef8358eb50a24928efd5edf54210b850259/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128", size = 847961 }, - { url = "https://files.pythonhosted.org/packages/9a/12/f003e824a19ed73be15542f172fd0ec4ad0b60cf37436652c93b9df7c585/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39", size = 1650282 }, - { url = "https://files.pythonhosted.org/packages/d5/4a/e82d788ed58e9a23995cee70dbc20c9aded3d13a92d30d57ec2291f1e8a3/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97", size = 2024468 }, - { url = "https://files.pythonhosted.org/packages/d9/94/2da0a60841f757481e402b34bf4c8bf57fa54a5466b965de791b1e6f747d/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db", size = 1885394 }, - { url = "https://files.pythonhosted.org/packages/4f/6f/55c10e2e49ad52d080dc24e37adb215e5b0d64990b57598abc2e3f01725b/pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c", size = 574964 }, - { url = "https://files.pythonhosted.org/packages/87/4d/2534970ba63dd7c522d8ca80fb92777f362c0f321900667c615e2067cb29/pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2", size = 641029 }, - { url = "https://files.pythonhosted.org/packages/f6/fa/f8aea7a28b0641f31d40dea42d7ef003fded31e184ef47db696bc74cd610/pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e", size = 561541 }, - { url = "https://files.pythonhosted.org/packages/87/45/19efbb3000956e82d0331bafca5d9ac19ea2857722fa2caacefb6042f39d/pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a", size = 1341197 }, - { url = "https://files.pythonhosted.org/packages/48/43/d72ccdbf0d73d1343936296665826350cb1e825f92f2db9db3e61c2162a2/pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea", size = 897175 }, - { url = "https://files.pythonhosted.org/packages/2f/2e/a483f73a10b65a9ef0161e817321d39a770b2acf8bcf3004a28d90d14a94/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96", size = 660427 }, - { url = "https://files.pythonhosted.org/packages/f5/d2/5f36552c2d3e5685abe60dfa56f91169f7a2d99bbaf67c5271022ab40863/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d", size = 847929 }, - { url = "https://files.pythonhosted.org/packages/c4/2a/404b331f2b7bf3198e9945f75c4c521f0c6a3a23b51f7a4a401b94a13833/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146", size = 1650193 }, - { url = "https://files.pythonhosted.org/packages/1c/0b/f4107e33f62a5acf60e3ded67ed33d79b4ce18de432625ce2fc5093d6388/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd", size = 2024388 }, - { url = "https://files.pythonhosted.org/packages/0d/01/add31fe76512642fd6e40e3a3bd21f4b47e242c8ba33efb6809e37076d9b/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a", size = 1885316 }, - { url = "https://files.pythonhosted.org/packages/c4/59/a5f38970f9bf07cee96128de79590bb354917914a9be11272cfc7ff26af0/pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92", size = 587472 }, - { url = "https://files.pythonhosted.org/packages/70/d8/78b1bad170f93fcf5e3536e70e8fadac55030002275c9a29e8f5719185de/pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0", size = 661401 }, - { url = "https://files.pythonhosted.org/packages/81/d6/4bfbb40c9a0b42fc53c7cf442f6385db70b40f74a783130c5d0a5aa62228/pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7", size = 575170 }, - { url = "https://files.pythonhosted.org/packages/4c/c6/c4dcdecdbaa70969ee1fdced6d7b8f60cfabe64d25361f27ac4665a70620/pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066", size = 836265 }, - { url = "https://files.pythonhosted.org/packages/3e/79/f38c92eeaeb03a2ccc2ba9866f0439593bb08c5e3b714ac1d553e5c96e25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604", size = 800208 }, - { url = "https://files.pythonhosted.org/packages/49/0e/3f0d0d335c6b3abb9b7b723776d0b21fa7f3a6c819a0db6097059aada160/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c", size = 567747 }, - { url = "https://files.pythonhosted.org/packages/a1/cf/f2b3784d536250ffd4be70e049f3b60981235d70c6e8ce7e3ef21e1adb25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f605d884e7c8be8fe1aa94e0a783bf3f591b84c24e4bc4f3e7564c82ac25e271", size = 747371 }, - { url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862 }, +sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86", size = 1333328, upload-time = "2025-09-08T23:07:45.946Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a0/fc7e78a23748ad5443ac3275943457e8452da67fda347e05260261108cbc/pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581", size = 908803, upload-time = "2025-09-08T23:07:47.551Z" }, + { url = "https://files.pythonhosted.org/packages/7e/22/37d15eb05f3bdfa4abea6f6d96eb3bb58585fbd3e4e0ded4e743bc650c97/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f", size = 668836, upload-time = "2025-09-08T23:07:49.436Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e", size = 857038, upload-time = "2025-09-08T23:07:51.234Z" }, + { url = "https://files.pythonhosted.org/packages/cb/eb/bfdcb41d0db9cd233d6fb22dc131583774135505ada800ebf14dfb0a7c40/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15c8bd0fe0dabf808e2d7a681398c4e5ded70a551ab47482067a572c054c8e2e", size = 1657531, upload-time = "2025-09-08T23:07:52.795Z" }, + { url = "https://files.pythonhosted.org/packages/ab/21/e3180ca269ed4a0de5c34417dfe71a8ae80421198be83ee619a8a485b0c7/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bafcb3dd171b4ae9f19ee6380dfc71ce0390fefaf26b504c0e5f628d7c8c54f2", size = 2034786, upload-time = "2025-09-08T23:07:55.047Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b1/5e21d0b517434b7f33588ff76c177c5a167858cc38ef740608898cd329f2/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e829529fcaa09937189178115c49c504e69289abd39967cd8a4c215761373394", size = 1894220, upload-time = "2025-09-08T23:07:57.172Z" }, + { url = "https://files.pythonhosted.org/packages/03/f2/44913a6ff6941905efc24a1acf3d3cb6146b636c546c7406c38c49c403d4/pyzmq-27.1.0-cp311-cp311-win32.whl", hash = "sha256:6df079c47d5902af6db298ec92151db82ecb557af663098b92f2508c398bb54f", size = 567155, upload-time = "2025-09-08T23:07:59.05Z" }, + { url = "https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97", size = 633428, upload-time = "2025-09-08T23:08:00.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/14/01afebc96c5abbbd713ecfc7469cfb1bc801c819a74ed5c9fad9a48801cb/pyzmq-27.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:eca6b47df11a132d1745eb3b5b5e557a7dae2c303277aa0e69c6ba91b8736e07", size = 559497, upload-time = "2025-09-08T23:08:02.15Z" }, + { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" }, + { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" }, + { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" }, + { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" }, + { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, + { url = "https://files.pythonhosted.org/packages/60/cb/84a13459c51da6cec1b7b1dc1a47e6db6da50b77ad7fd9c145842750a011/pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5", size = 1122436, upload-time = "2025-09-08T23:08:20.801Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b6/94414759a69a26c3dd674570a81813c46a078767d931a6c70ad29fc585cb/pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6", size = 1156301, upload-time = "2025-09-08T23:08:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ad/15906493fd40c316377fd8a8f6b1f93104f97a752667763c9b9c1b71d42d/pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7", size = 1341197, upload-time = "2025-09-08T23:08:24.286Z" }, + { url = "https://files.pythonhosted.org/packages/14/1d/d343f3ce13db53a54cb8946594e567410b2125394dafcc0268d8dda027e0/pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05", size = 897275, upload-time = "2025-09-08T23:08:26.063Z" }, + { url = "https://files.pythonhosted.org/packages/69/2d/d83dd6d7ca929a2fc67d2c3005415cdf322af7751d773524809f9e585129/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9", size = 660469, upload-time = "2025-09-08T23:08:27.623Z" }, + { url = "https://files.pythonhosted.org/packages/3e/cd/9822a7af117f4bc0f1952dbe9ef8358eb50a24928efd5edf54210b850259/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128", size = 847961, upload-time = "2025-09-08T23:08:29.672Z" }, + { url = "https://files.pythonhosted.org/packages/9a/12/f003e824a19ed73be15542f172fd0ec4ad0b60cf37436652c93b9df7c585/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39", size = 1650282, upload-time = "2025-09-08T23:08:31.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4a/e82d788ed58e9a23995cee70dbc20c9aded3d13a92d30d57ec2291f1e8a3/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97", size = 2024468, upload-time = "2025-09-08T23:08:33.543Z" }, + { url = "https://files.pythonhosted.org/packages/d9/94/2da0a60841f757481e402b34bf4c8bf57fa54a5466b965de791b1e6f747d/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db", size = 1885394, upload-time = "2025-09-08T23:08:35.51Z" }, + { url = "https://files.pythonhosted.org/packages/4f/6f/55c10e2e49ad52d080dc24e37adb215e5b0d64990b57598abc2e3f01725b/pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c", size = 574964, upload-time = "2025-09-08T23:08:37.178Z" }, + { url = "https://files.pythonhosted.org/packages/87/4d/2534970ba63dd7c522d8ca80fb92777f362c0f321900667c615e2067cb29/pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2", size = 641029, upload-time = "2025-09-08T23:08:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/f6/fa/f8aea7a28b0641f31d40dea42d7ef003fded31e184ef47db696bc74cd610/pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e", size = 561541, upload-time = "2025-09-08T23:08:42.668Z" }, + { url = "https://files.pythonhosted.org/packages/87/45/19efbb3000956e82d0331bafca5d9ac19ea2857722fa2caacefb6042f39d/pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a", size = 1341197, upload-time = "2025-09-08T23:08:44.973Z" }, + { url = "https://files.pythonhosted.org/packages/48/43/d72ccdbf0d73d1343936296665826350cb1e825f92f2db9db3e61c2162a2/pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea", size = 897175, upload-time = "2025-09-08T23:08:46.601Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2e/a483f73a10b65a9ef0161e817321d39a770b2acf8bcf3004a28d90d14a94/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96", size = 660427, upload-time = "2025-09-08T23:08:48.187Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d2/5f36552c2d3e5685abe60dfa56f91169f7a2d99bbaf67c5271022ab40863/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d", size = 847929, upload-time = "2025-09-08T23:08:49.76Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2a/404b331f2b7bf3198e9945f75c4c521f0c6a3a23b51f7a4a401b94a13833/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146", size = 1650193, upload-time = "2025-09-08T23:08:51.7Z" }, + { url = "https://files.pythonhosted.org/packages/1c/0b/f4107e33f62a5acf60e3ded67ed33d79b4ce18de432625ce2fc5093d6388/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd", size = 2024388, upload-time = "2025-09-08T23:08:53.393Z" }, + { url = "https://files.pythonhosted.org/packages/0d/01/add31fe76512642fd6e40e3a3bd21f4b47e242c8ba33efb6809e37076d9b/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a", size = 1885316, upload-time = "2025-09-08T23:08:55.702Z" }, + { url = "https://files.pythonhosted.org/packages/c4/59/a5f38970f9bf07cee96128de79590bb354917914a9be11272cfc7ff26af0/pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92", size = 587472, upload-time = "2025-09-08T23:08:58.18Z" }, + { url = "https://files.pythonhosted.org/packages/70/d8/78b1bad170f93fcf5e3536e70e8fadac55030002275c9a29e8f5719185de/pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0", size = 661401, upload-time = "2025-09-08T23:08:59.802Z" }, + { url = "https://files.pythonhosted.org/packages/81/d6/4bfbb40c9a0b42fc53c7cf442f6385db70b40f74a783130c5d0a5aa62228/pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7", size = 575170, upload-time = "2025-09-08T23:09:01.418Z" }, + { url = "https://files.pythonhosted.org/packages/4c/c6/c4dcdecdbaa70969ee1fdced6d7b8f60cfabe64d25361f27ac4665a70620/pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066", size = 836265, upload-time = "2025-09-08T23:09:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/3e/79/f38c92eeaeb03a2ccc2ba9866f0439593bb08c5e3b714ac1d553e5c96e25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604", size = 800208, upload-time = "2025-09-08T23:09:51.073Z" }, + { url = "https://files.pythonhosted.org/packages/49/0e/3f0d0d335c6b3abb9b7b723776d0b21fa7f3a6c819a0db6097059aada160/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c", size = 567747, upload-time = "2025-09-08T23:09:52.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cf/f2b3784d536250ffd4be70e049f3b60981235d70c6e8ce7e3ef21e1adb25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f605d884e7c8be8fe1aa94e0a783bf3f591b84c24e4bc4f3e7564c82ac25e271", size = 747371, upload-time = "2025-09-08T23:09:54.563Z" }, + { url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" }, ] [[package]] @@ -2902,9 +2808,9 @@ dependencies = [ { name = "rpds-py" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036 } +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766 }, + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, ] [[package]] @@ -2917,9 +2823,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517 } +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738 }, + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] [[package]] @@ -2929,18 +2835,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513 } +sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513, upload-time = "2021-05-12T16:37:54.178Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490 }, + { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490, upload-time = "2021-05-12T16:37:52.536Z" }, ] [[package]] name = "rfc3986-validator" version = "0.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/da/88/f270de456dd7d11dcc808abfa291ecdd3f45ff44e3b549ffa01b126464d0/rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055", size = 6760 } +sdist = { url = "https://files.pythonhosted.org/packages/da/88/f270de456dd7d11dcc808abfa291ecdd3f45ff44e3b549ffa01b126464d0/rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055", size = 6760, upload-time = "2019-10-28T16:00:19.144Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9", size = 4242 }, + { url = "https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9", size = 4242, upload-time = "2019-10-28T16:00:13.976Z" }, ] [[package]] @@ -2950,9 +2856,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "lark" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/06/37c1a5557acf449e8e406a830a05bf885ac47d33270aec454ef78675008d/rfc3987_syntax-1.1.0.tar.gz", hash = "sha256:717a62cbf33cffdd16dfa3a497d81ce48a660ea691b1ddd7be710c22f00b4a0d", size = 14239 } +sdist = { url = "https://files.pythonhosted.org/packages/2c/06/37c1a5557acf449e8e406a830a05bf885ac47d33270aec454ef78675008d/rfc3987_syntax-1.1.0.tar.gz", hash = "sha256:717a62cbf33cffdd16dfa3a497d81ce48a660ea691b1ddd7be710c22f00b4a0d", size = 14239, upload-time = "2025-07-18T01:05:05.015Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl", hash = "sha256:6c3d97604e4c5ce9f714898e05401a0445a641cfa276432b0a648c80856f6a3f", size = 8046 }, + { url = "https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl", hash = "sha256:6c3d97604e4c5ce9f714898e05401a0445a641cfa276432b0a648c80856f6a3f", size = 8046, upload-time = "2025-07-18T01:05:03.843Z" }, ] [[package]] @@ -2967,178 +2873,178 @@ dependencies = [ { name = "python-dateutil" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/2f/cbcd0a75fce3bf955fb3317dae037a60bcdbe848c98105afaa25c31d1475/rosettasciio-0.12.0.tar.gz", hash = "sha256:e02575d451e9c3d301fcb68c319ce0728175df9940feff5b09b855e18945a093", size = 1180824 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/00/34/0b9aa833f2dfdb8f22ad6fb337b71b638f9a937abb2796f8112bc507195f/rosettasciio-0.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c529232347e251abd07dd8b9f92f618994cdc289aca687a1df21e2846936a3d3", size = 817077 }, - { url = "https://files.pythonhosted.org/packages/15/2b/caac69f14e9ee9486901bca609fe66fb2d21ad144ccaa6e562f696115bc3/rosettasciio-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:33bc1382770b74a1b4c5c9605656cf2f3a63e0409903bce3620a1d5d8401d270", size = 816215 }, - { url = "https://files.pythonhosted.org/packages/b0/9b/345d28ce5a802d82048de418a427b3239e4f9ff44553709eef7a3a2b5134/rosettasciio-0.12.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:931d5251c25f4db537573671723fddcb1938684ed3047425f2f35cc9c89bab2b", size = 1271765 }, - { url = "https://files.pythonhosted.org/packages/c9/62/de7bd49e68760d4deda46d58f089e936e1a6bcd14dbd4e7acb4d9c7c1769/rosettasciio-0.12.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:754345908d083083d54aa24572290eb44632fa9ef0c6fca7d547a9e6754a2e88", size = 1276286 }, - { url = "https://files.pythonhosted.org/packages/9b/5c/d1f62362790d601b0749089ab30f6193e0a328af523b0569f36361deeda2/rosettasciio-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a57699c95226d6f71714629d23e58045c0ee3378e0aa00e8fe5fb0da88aff2ba", size = 805836 }, - { url = "https://files.pythonhosted.org/packages/3c/75/f6f81c945da3c3b8d67c14d4ca26dda0ac3421ad9074bc54196019cfe119/rosettasciio-0.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:e1203f9960a2026122d2900062db1ea48700aaec4fa427ed731cb7f8b0433740", size = 794372 }, - { url = "https://files.pythonhosted.org/packages/8b/42/48eebee13ace6e0c714433111ef7854b023f63ced48ccb38c26c4a325e8b/rosettasciio-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:18608de5b37a197d15cf4236d045adfb398dbcd2bc0917a8a193e9a5c87dc871", size = 818054 }, - { url = "https://files.pythonhosted.org/packages/90/d6/0971527544fc27f59856700c6220aee2199bd631c51c4eb05bf3edd1e58d/rosettasciio-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2567a490f4be1ce7402551f21ed32f5f34dc5956286fa819594bdc899999d32f", size = 816403 }, - { url = "https://files.pythonhosted.org/packages/87/c9/81e6fceaeba01f6d3bf36de9e43145590acba43063f2566d45f11d9a0999/rosettasciio-0.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0bf7ce36dbed455554892c8b84732ec25f3026da8374c56e1d491aac6efddf35", size = 1274114 }, - { url = "https://files.pythonhosted.org/packages/88/cb/325acfd3255132574c6fa975362b03bf286db9b8dce0f57eaef9d4d2bf9c/rosettasciio-0.12.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:45f74c2414704cba896c13c09102a960b1624b610f7a2e7a177d70ebea6bb2a6", size = 1278900 }, - { url = "https://files.pythonhosted.org/packages/24/4e/b0bcce83693873e26fc7d104708b8b14f710562e6b414c28a8b35c9030d7/rosettasciio-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:7e7bfc6f23604ae5133db33241053c265d35b0649d85029e85b2c261796bd865", size = 806447 }, - { url = "https://files.pythonhosted.org/packages/c1/ac/aa09ee2a13608bdbed8049183a9f6983146de38f67d20fc91819f90fd7fc/rosettasciio-0.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:f15b9ab80c2ebd468c554b026bddd62cee02b9caa3d5ef20e354214c321e3ac0", size = 794087 }, - { url = "https://files.pythonhosted.org/packages/72/ee/99d31a8514f82b47cebf3a4adad44c04e96c7d05d6821aaba5b737d6f819/rosettasciio-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0289c5f7bbb065880041f1fa1e777ddaf0dea37cc667c0da5d05cfbec6885a1c", size = 817296 }, - { url = "https://files.pythonhosted.org/packages/61/26/83a280dfcd703cd2300e7ae4586dc0df2d24d2ed7a42693a355c9f3f875e/rosettasciio-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:35d6d8e8b947277fa6e5a829e0a367cdc1c8fbf29dccdfc667cec12dd1da8d3d", size = 815608 }, - { url = "https://files.pythonhosted.org/packages/0b/8e/87e07335406125cff5095d72cffd0360a9fe4c76067659c5ccac5ee712e7/rosettasciio-0.12.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f88523c38eba7c991d711668667aac5c146737c613bbf4e1325f3b0d174eedf", size = 1267905 }, - { url = "https://files.pythonhosted.org/packages/cb/7b/f10c0d7d8110cf14a51c47ddeba0fcd32633f87eefdd77d667da1e44c16d/rosettasciio-0.12.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:97727411c3c7560d4349d39582e1888b7e5bda1dcda21d48e30e2047e2f6e45c", size = 1277855 }, - { url = "https://files.pythonhosted.org/packages/30/0f/daaa1b14c9fb2b295ff52be0b13604d59d7ae4968cb5779abfd414d61ddb/rosettasciio-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:7769f2caa80c761d7f30adaf5aa11da2dadf4149a0df8f0c939400e2ad924a1c", size = 806235 }, - { url = "https://files.pythonhosted.org/packages/ef/ce/89729079e3527cf7cd2bb5e90c0d466d0852ee5cc7267a7f45cc8cdc5482/rosettasciio-0.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:5f307f3abfc061499535313d55b6d630cead7e450040d72276923a98342df930", size = 793943 }, - { url = "https://files.pythonhosted.org/packages/33/5e/049b670a32e09cc788e085b503d083ab81f341837c2f1e46bd7ab4714d98/rosettasciio-0.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7ae3e37730b81cfb9b894a0e562693befa2ea44471c8c469ef82caf15c8ca42d", size = 821718 }, - { url = "https://files.pythonhosted.org/packages/cf/a1/4884bcb0e187246ec24aee3175d1b2294cfd97b42afee8ccd7ec33a4c8b2/rosettasciio-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:07288b70421dc7ca91ccf59c91f7d9675d425582eae499662f6c3a27f1562e7f", size = 821588 }, - { url = "https://files.pythonhosted.org/packages/9b/e8/b223f9b63614982b09f1c29b118466102580b95c518705db792866edb645/rosettasciio-0.12.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c810177218abfc74729f175856bf082bc435ad964ca5ceb1158a87fd23dbe992", size = 1281610 }, - { url = "https://files.pythonhosted.org/packages/a4/53/f779609a7f0a887ea97233a00482b3034b262c982549e81337fdcc26bd8c/rosettasciio-0.12.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f9ab696534dc4cc5fd3b3eeac3758ab9c5260b09b4aea0cd94ea52decbede97", size = 1268743 }, - { url = "https://files.pythonhosted.org/packages/34/b4/4ec99972f93b605a662ce09e56b2cc562051900492c8516dd935c0db3087/rosettasciio-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c6aecd74a7a0248843efc392a689a8dc68044f7183e2b8b9252c91335ee2f16f", size = 816540 }, - { url = "https://files.pythonhosted.org/packages/73/0d/d1d99c3b9614a7525c082737f11c140671d690bdd9fc6a6085f1282bc713/rosettasciio-0.12.0-cp313-cp313t-win_arm64.whl", hash = "sha256:30fe143cd4aede2260cdff388a5686ad4725a27207d095f0c35b6e1fa09e0663", size = 798650 }, - { url = "https://files.pythonhosted.org/packages/36/9f/98eaa0d086d6f569b40b8ec13b62d99c2ab6c60bf7a8a5d6999c45a18e6b/rosettasciio-0.12.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:268697caf043823c69e7c6c397db364a798abc1e3c31e56d48c4e1142cb85af7", size = 817569 }, - { url = "https://files.pythonhosted.org/packages/18/60/09cb9b438107032251f49c07fad46539814adddfc58eece4790d3ab65a9f/rosettasciio-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fc66c00a4e3c4a564741e77296c120135c0a3fbc4b56640bc812298ca83b5a3c", size = 816443 }, - { url = "https://files.pythonhosted.org/packages/61/90/be7c89b63f1cd5c702f23f0247b1a93040c867e37b3817004b89ec2b89a6/rosettasciio-0.12.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fa6023d4f206c37d0e2a4ef12070a0ef03a44ca5a6fbc25ae5ee58be72f236da", size = 1265935 }, - { url = "https://files.pythonhosted.org/packages/c5/08/6b9a0a11686d8e10f43da14262129b4b4eb02a7f0a6e8d681b8f6e308fa8/rosettasciio-0.12.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84adcd658b7be1b03b1f8f36b3f5690c43d87b20b8cc8723db561fbfe05510d1", size = 1273714 }, - { url = "https://files.pythonhosted.org/packages/f0/a5/50517ea5c856c64e6405ea9c550acd4cdea4b4f595144d73ad344bdca6cf/rosettasciio-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:59b51d45a888ee9361b5b5fb459adf7f5c0ac0e62f20610331e8af94ae5b695b", size = 805318 }, - { url = "https://files.pythonhosted.org/packages/48/23/90534c33567d1473dbf59efcb4ba27699d5471824e0036937cb699bbb0cf/rosettasciio-0.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:4623e8263af07174c3fbe02981d15920910ce2d7eaf989ddc100a74bc162b2f6", size = 793074 }, - { url = "https://files.pythonhosted.org/packages/62/ca/d6e04ac4b41e8f92e7754bc02f115a4bfa70a782815aacc2d5cad1a21b25/rosettasciio-0.12.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8e445003a48000f6dd462c180b8cea7a54a0e5ce17fe0706228fac619b80cce8", size = 822221 }, - { url = "https://files.pythonhosted.org/packages/c4/a5/078548b2760ba2c529de3b4e677d48030d41092d011b4034cb0e2c23310b/rosettasciio-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:608aa659d42654dcd28eeb7076761a736fbbcb59876f8907d5160f9f2d7c5f5d", size = 821917 }, - { url = "https://files.pythonhosted.org/packages/bd/ee/88dcea135add00ef59d03138b0b8378028153b00f4c0374ebd86cd19fe58/rosettasciio-0.12.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d5bccd360790e1c17bca31223f4e946eb4b19b55b9c1aae937365ed45839df0", size = 1282089 }, - { url = "https://files.pythonhosted.org/packages/05/83/b4e55db04a54cd50ce85ad44604cef619e5a0e72b1eedf1f9d71a77fc0aa/rosettasciio-0.12.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04fa825a951b6fe11c4fca422e30e2d03156d04b197d0a75649db8562f4c7dcd", size = 1269179 }, - { url = "https://files.pythonhosted.org/packages/5b/c1/d3ce8ada006d8e0e608859c335c83069cbc1dbce548a07ab870d3d336ee9/rosettasciio-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:3f89eeea6a624179fc11924a904ad7f4699a332a1d7b13176e3e9c2ca116e00a", size = 817913 }, - { url = "https://files.pythonhosted.org/packages/4c/08/d41bf9dbbfe60463ab68268c5458d644fcb2d7cab9453c958a53b577b9ce/rosettasciio-0.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:38aec6a42a0810bee065502467f941d563a51d236af762432c65e212bac27ccf", size = 797652 }, - { url = "https://files.pythonhosted.org/packages/56/00/772baf64631f9ec440b3291b708a04a00442aad796625cc0f0c6f8a1c0c4/rosettasciio-0.12.0-py3-none-any.whl", hash = "sha256:3974e75b1502b4e974c227f0b80d35240011c445b6d63394482fd9e6afc5c9fb", size = 567619 }, +sdist = { url = "https://files.pythonhosted.org/packages/2e/2f/cbcd0a75fce3bf955fb3317dae037a60bcdbe848c98105afaa25c31d1475/rosettasciio-0.12.0.tar.gz", hash = "sha256:e02575d451e9c3d301fcb68c319ce0728175df9940feff5b09b855e18945a093", size = 1180824, upload-time = "2025-12-29T17:33:06.459Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/34/0b9aa833f2dfdb8f22ad6fb337b71b638f9a937abb2796f8112bc507195f/rosettasciio-0.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c529232347e251abd07dd8b9f92f618994cdc289aca687a1df21e2846936a3d3", size = 817077, upload-time = "2025-12-29T17:32:03.968Z" }, + { url = "https://files.pythonhosted.org/packages/15/2b/caac69f14e9ee9486901bca609fe66fb2d21ad144ccaa6e562f696115bc3/rosettasciio-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:33bc1382770b74a1b4c5c9605656cf2f3a63e0409903bce3620a1d5d8401d270", size = 816215, upload-time = "2025-12-29T17:32:05.776Z" }, + { url = "https://files.pythonhosted.org/packages/b0/9b/345d28ce5a802d82048de418a427b3239e4f9ff44553709eef7a3a2b5134/rosettasciio-0.12.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:931d5251c25f4db537573671723fddcb1938684ed3047425f2f35cc9c89bab2b", size = 1271765, upload-time = "2025-12-29T17:32:07.928Z" }, + { url = "https://files.pythonhosted.org/packages/c9/62/de7bd49e68760d4deda46d58f089e936e1a6bcd14dbd4e7acb4d9c7c1769/rosettasciio-0.12.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:754345908d083083d54aa24572290eb44632fa9ef0c6fca7d547a9e6754a2e88", size = 1276286, upload-time = "2025-12-29T17:32:09.831Z" }, + { url = "https://files.pythonhosted.org/packages/9b/5c/d1f62362790d601b0749089ab30f6193e0a328af523b0569f36361deeda2/rosettasciio-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a57699c95226d6f71714629d23e58045c0ee3378e0aa00e8fe5fb0da88aff2ba", size = 805836, upload-time = "2025-12-29T17:32:12.107Z" }, + { url = "https://files.pythonhosted.org/packages/3c/75/f6f81c945da3c3b8d67c14d4ca26dda0ac3421ad9074bc54196019cfe119/rosettasciio-0.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:e1203f9960a2026122d2900062db1ea48700aaec4fa427ed731cb7f8b0433740", size = 794372, upload-time = "2025-12-29T17:32:13.931Z" }, + { url = "https://files.pythonhosted.org/packages/8b/42/48eebee13ace6e0c714433111ef7854b023f63ced48ccb38c26c4a325e8b/rosettasciio-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:18608de5b37a197d15cf4236d045adfb398dbcd2bc0917a8a193e9a5c87dc871", size = 818054, upload-time = "2025-12-29T17:32:15.366Z" }, + { url = "https://files.pythonhosted.org/packages/90/d6/0971527544fc27f59856700c6220aee2199bd631c51c4eb05bf3edd1e58d/rosettasciio-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2567a490f4be1ce7402551f21ed32f5f34dc5956286fa819594bdc899999d32f", size = 816403, upload-time = "2025-12-29T17:32:16.85Z" }, + { url = "https://files.pythonhosted.org/packages/87/c9/81e6fceaeba01f6d3bf36de9e43145590acba43063f2566d45f11d9a0999/rosettasciio-0.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0bf7ce36dbed455554892c8b84732ec25f3026da8374c56e1d491aac6efddf35", size = 1274114, upload-time = "2025-12-29T17:32:18.488Z" }, + { url = "https://files.pythonhosted.org/packages/88/cb/325acfd3255132574c6fa975362b03bf286db9b8dce0f57eaef9d4d2bf9c/rosettasciio-0.12.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:45f74c2414704cba896c13c09102a960b1624b610f7a2e7a177d70ebea6bb2a6", size = 1278900, upload-time = "2025-12-29T17:32:19.97Z" }, + { url = "https://files.pythonhosted.org/packages/24/4e/b0bcce83693873e26fc7d104708b8b14f710562e6b414c28a8b35c9030d7/rosettasciio-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:7e7bfc6f23604ae5133db33241053c265d35b0649d85029e85b2c261796bd865", size = 806447, upload-time = "2025-12-29T17:32:21.349Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ac/aa09ee2a13608bdbed8049183a9f6983146de38f67d20fc91819f90fd7fc/rosettasciio-0.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:f15b9ab80c2ebd468c554b026bddd62cee02b9caa3d5ef20e354214c321e3ac0", size = 794087, upload-time = "2025-12-29T17:32:22.783Z" }, + { url = "https://files.pythonhosted.org/packages/72/ee/99d31a8514f82b47cebf3a4adad44c04e96c7d05d6821aaba5b737d6f819/rosettasciio-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0289c5f7bbb065880041f1fa1e777ddaf0dea37cc667c0da5d05cfbec6885a1c", size = 817296, upload-time = "2025-12-29T17:32:24.206Z" }, + { url = "https://files.pythonhosted.org/packages/61/26/83a280dfcd703cd2300e7ae4586dc0df2d24d2ed7a42693a355c9f3f875e/rosettasciio-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:35d6d8e8b947277fa6e5a829e0a367cdc1c8fbf29dccdfc667cec12dd1da8d3d", size = 815608, upload-time = "2025-12-29T17:32:26.191Z" }, + { url = "https://files.pythonhosted.org/packages/0b/8e/87e07335406125cff5095d72cffd0360a9fe4c76067659c5ccac5ee712e7/rosettasciio-0.12.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f88523c38eba7c991d711668667aac5c146737c613bbf4e1325f3b0d174eedf", size = 1267905, upload-time = "2025-12-29T17:32:27.612Z" }, + { url = "https://files.pythonhosted.org/packages/cb/7b/f10c0d7d8110cf14a51c47ddeba0fcd32633f87eefdd77d667da1e44c16d/rosettasciio-0.12.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:97727411c3c7560d4349d39582e1888b7e5bda1dcda21d48e30e2047e2f6e45c", size = 1277855, upload-time = "2025-12-29T17:32:29.694Z" }, + { url = "https://files.pythonhosted.org/packages/30/0f/daaa1b14c9fb2b295ff52be0b13604d59d7ae4968cb5779abfd414d61ddb/rosettasciio-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:7769f2caa80c761d7f30adaf5aa11da2dadf4149a0df8f0c939400e2ad924a1c", size = 806235, upload-time = "2025-12-29T17:32:31.571Z" }, + { url = "https://files.pythonhosted.org/packages/ef/ce/89729079e3527cf7cd2bb5e90c0d466d0852ee5cc7267a7f45cc8cdc5482/rosettasciio-0.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:5f307f3abfc061499535313d55b6d630cead7e450040d72276923a98342df930", size = 793943, upload-time = "2025-12-29T17:32:32.989Z" }, + { url = "https://files.pythonhosted.org/packages/33/5e/049b670a32e09cc788e085b503d083ab81f341837c2f1e46bd7ab4714d98/rosettasciio-0.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7ae3e37730b81cfb9b894a0e562693befa2ea44471c8c469ef82caf15c8ca42d", size = 821718, upload-time = "2025-12-29T17:32:35.3Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a1/4884bcb0e187246ec24aee3175d1b2294cfd97b42afee8ccd7ec33a4c8b2/rosettasciio-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:07288b70421dc7ca91ccf59c91f7d9675d425582eae499662f6c3a27f1562e7f", size = 821588, upload-time = "2025-12-29T17:32:37.094Z" }, + { url = "https://files.pythonhosted.org/packages/9b/e8/b223f9b63614982b09f1c29b118466102580b95c518705db792866edb645/rosettasciio-0.12.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c810177218abfc74729f175856bf082bc435ad964ca5ceb1158a87fd23dbe992", size = 1281610, upload-time = "2025-12-29T17:32:38.621Z" }, + { url = "https://files.pythonhosted.org/packages/a4/53/f779609a7f0a887ea97233a00482b3034b262c982549e81337fdcc26bd8c/rosettasciio-0.12.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f9ab696534dc4cc5fd3b3eeac3758ab9c5260b09b4aea0cd94ea52decbede97", size = 1268743, upload-time = "2025-12-29T17:32:40.257Z" }, + { url = "https://files.pythonhosted.org/packages/34/b4/4ec99972f93b605a662ce09e56b2cc562051900492c8516dd935c0db3087/rosettasciio-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c6aecd74a7a0248843efc392a689a8dc68044f7183e2b8b9252c91335ee2f16f", size = 816540, upload-time = "2025-12-29T17:32:41.644Z" }, + { url = "https://files.pythonhosted.org/packages/73/0d/d1d99c3b9614a7525c082737f11c140671d690bdd9fc6a6085f1282bc713/rosettasciio-0.12.0-cp313-cp313t-win_arm64.whl", hash = "sha256:30fe143cd4aede2260cdff388a5686ad4725a27207d095f0c35b6e1fa09e0663", size = 798650, upload-time = "2025-12-29T17:32:43.09Z" }, + { url = "https://files.pythonhosted.org/packages/36/9f/98eaa0d086d6f569b40b8ec13b62d99c2ab6c60bf7a8a5d6999c45a18e6b/rosettasciio-0.12.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:268697caf043823c69e7c6c397db364a798abc1e3c31e56d48c4e1142cb85af7", size = 817569, upload-time = "2025-12-29T17:32:44.6Z" }, + { url = "https://files.pythonhosted.org/packages/18/60/09cb9b438107032251f49c07fad46539814adddfc58eece4790d3ab65a9f/rosettasciio-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fc66c00a4e3c4a564741e77296c120135c0a3fbc4b56640bc812298ca83b5a3c", size = 816443, upload-time = "2025-12-29T17:32:46.279Z" }, + { url = "https://files.pythonhosted.org/packages/61/90/be7c89b63f1cd5c702f23f0247b1a93040c867e37b3817004b89ec2b89a6/rosettasciio-0.12.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fa6023d4f206c37d0e2a4ef12070a0ef03a44ca5a6fbc25ae5ee58be72f236da", size = 1265935, upload-time = "2025-12-29T17:32:47.788Z" }, + { url = "https://files.pythonhosted.org/packages/c5/08/6b9a0a11686d8e10f43da14262129b4b4eb02a7f0a6e8d681b8f6e308fa8/rosettasciio-0.12.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84adcd658b7be1b03b1f8f36b3f5690c43d87b20b8cc8723db561fbfe05510d1", size = 1273714, upload-time = "2025-12-29T17:32:49.394Z" }, + { url = "https://files.pythonhosted.org/packages/f0/a5/50517ea5c856c64e6405ea9c550acd4cdea4b4f595144d73ad344bdca6cf/rosettasciio-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:59b51d45a888ee9361b5b5fb459adf7f5c0ac0e62f20610331e8af94ae5b695b", size = 805318, upload-time = "2025-12-29T17:32:50.861Z" }, + { url = "https://files.pythonhosted.org/packages/48/23/90534c33567d1473dbf59efcb4ba27699d5471824e0036937cb699bbb0cf/rosettasciio-0.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:4623e8263af07174c3fbe02981d15920910ce2d7eaf989ddc100a74bc162b2f6", size = 793074, upload-time = "2025-12-29T17:32:52.227Z" }, + { url = "https://files.pythonhosted.org/packages/62/ca/d6e04ac4b41e8f92e7754bc02f115a4bfa70a782815aacc2d5cad1a21b25/rosettasciio-0.12.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8e445003a48000f6dd462c180b8cea7a54a0e5ce17fe0706228fac619b80cce8", size = 822221, upload-time = "2025-12-29T17:32:54.074Z" }, + { url = "https://files.pythonhosted.org/packages/c4/a5/078548b2760ba2c529de3b4e677d48030d41092d011b4034cb0e2c23310b/rosettasciio-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:608aa659d42654dcd28eeb7076761a736fbbcb59876f8907d5160f9f2d7c5f5d", size = 821917, upload-time = "2025-12-29T17:32:57.044Z" }, + { url = "https://files.pythonhosted.org/packages/bd/ee/88dcea135add00ef59d03138b0b8378028153b00f4c0374ebd86cd19fe58/rosettasciio-0.12.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d5bccd360790e1c17bca31223f4e946eb4b19b55b9c1aae937365ed45839df0", size = 1282089, upload-time = "2025-12-29T17:32:58.535Z" }, + { url = "https://files.pythonhosted.org/packages/05/83/b4e55db04a54cd50ce85ad44604cef619e5a0e72b1eedf1f9d71a77fc0aa/rosettasciio-0.12.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04fa825a951b6fe11c4fca422e30e2d03156d04b197d0a75649db8562f4c7dcd", size = 1269179, upload-time = "2025-12-29T17:33:00.169Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c1/d3ce8ada006d8e0e608859c335c83069cbc1dbce548a07ab870d3d336ee9/rosettasciio-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:3f89eeea6a624179fc11924a904ad7f4699a332a1d7b13176e3e9c2ca116e00a", size = 817913, upload-time = "2025-12-29T17:33:01.779Z" }, + { url = "https://files.pythonhosted.org/packages/4c/08/d41bf9dbbfe60463ab68268c5458d644fcb2d7cab9453c958a53b577b9ce/rosettasciio-0.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:38aec6a42a0810bee065502467f941d563a51d236af762432c65e212bac27ccf", size = 797652, upload-time = "2025-12-29T17:33:03.331Z" }, + { url = "https://files.pythonhosted.org/packages/56/00/772baf64631f9ec440b3291b708a04a00442aad796625cc0f0c6f8a1c0c4/rosettasciio-0.12.0-py3-none-any.whl", hash = "sha256:3974e75b1502b4e974c227f0b80d35240011c445b6d63394482fd9e6afc5c9fb", size = 567619, upload-time = "2025-12-29T17:33:04.939Z" }, ] [[package]] name = "rpds-py" version = "0.30.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157 }, - { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676 }, - { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938 }, - { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932 }, - { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830 }, - { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033 }, - { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828 }, - { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683 }, - { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583 }, - { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496 }, - { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669 }, - { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011 }, - { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406 }, - { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024 }, - { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069 }, - { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086 }, - { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053 }, - { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763 }, - { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951 }, - { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622 }, - { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492 }, - { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080 }, - { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680 }, - { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589 }, - { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289 }, - { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737 }, - { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120 }, - { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782 }, - { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463 }, - { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868 }, - { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887 }, - { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904 }, - { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945 }, - { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783 }, - { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021 }, - { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589 }, - { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025 }, - { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895 }, - { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799 }, - { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731 }, - { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027 }, - { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020 }, - { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139 }, - { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224 }, - { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645 }, - { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443 }, - { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375 }, - { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850 }, - { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812 }, - { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841 }, - { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149 }, - { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843 }, - { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507 }, - { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949 }, - { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790 }, - { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217 }, - { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806 }, - { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341 }, - { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768 }, - { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099 }, - { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192 }, - { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080 }, - { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841 }, - { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670 }, - { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005 }, - { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112 }, - { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049 }, - { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661 }, - { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606 }, - { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126 }, - { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371 }, - { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298 }, - { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604 }, - { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391 }, - { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868 }, - { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747 }, - { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795 }, - { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330 }, - { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194 }, - { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340 }, - { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765 }, - { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834 }, - { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470 }, - { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630 }, - { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148 }, - { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030 }, - { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570 }, - { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532 }, - { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292 }, - { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128 }, - { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542 }, - { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004 }, - { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063 }, - { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099 }, - { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177 }, - { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015 }, - { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736 }, - { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981 }, - { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782 }, - { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191 }, +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157, upload-time = "2025-11-30T20:21:53.789Z" }, + { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676, upload-time = "2025-11-30T20:21:55.475Z" }, + { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938, upload-time = "2025-11-30T20:21:57.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932, upload-time = "2025-11-30T20:21:58.47Z" }, + { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830, upload-time = "2025-11-30T20:21:59.699Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033, upload-time = "2025-11-30T20:22:00.991Z" }, + { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828, upload-time = "2025-11-30T20:22:02.723Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683, upload-time = "2025-11-30T20:22:04.367Z" }, + { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583, upload-time = "2025-11-30T20:22:05.814Z" }, + { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496, upload-time = "2025-11-30T20:22:07.713Z" }, + { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669, upload-time = "2025-11-30T20:22:09.312Z" }, + { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011, upload-time = "2025-11-30T20:22:11.309Z" }, + { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406, upload-time = "2025-11-30T20:22:13.101Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024, upload-time = "2025-11-30T20:22:14.853Z" }, + { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069, upload-time = "2025-11-30T20:22:16.577Z" }, + { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, + { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, + { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, + { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, + { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, + { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, + { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, + { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, + { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, + { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, + { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, + { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, + { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, + { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, + { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, + { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, + { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, + { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, + { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, + { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, + { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, + { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, + { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, + { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, + { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, + { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, + { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, + { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, + { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, + { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, + { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, + { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, + { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, + { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, + { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, + { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, + { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, + { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, + { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, + { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, + { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, + { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, + { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, + { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292, upload-time = "2025-11-30T20:24:16.537Z" }, + { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128, upload-time = "2025-11-30T20:24:18.086Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542, upload-time = "2025-11-30T20:24:20.092Z" }, + { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004, upload-time = "2025-11-30T20:24:22.231Z" }, + { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063, upload-time = "2025-11-30T20:24:24.302Z" }, + { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099, upload-time = "2025-11-30T20:24:25.916Z" }, + { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177, upload-time = "2025-11-30T20:24:27.834Z" }, + { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015, upload-time = "2025-11-30T20:24:29.457Z" }, + { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736, upload-time = "2025-11-30T20:24:31.22Z" }, + { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981, upload-time = "2025-11-30T20:24:32.934Z" }, + { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782, upload-time = "2025-11-30T20:24:35.169Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" }, ] [[package]] name = "ruff" -version = "0.15.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/22/9e4f66ee588588dc6c9af6a994e12d26e19efbe874d1a909d09a6dac7a59/ruff-0.15.7.tar.gz", hash = "sha256:04f1ae61fc20fe0b148617c324d9d009b5f63412c0b16474f3d5f1a1a665f7ac", size = 4601277, upload-time = "2026-03-19T16:26:22.605Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/41/2f/0b08ced94412af091807b6119ca03755d651d3d93a242682bf020189db94/ruff-0.15.7-py3-none-linux_armv6l.whl", hash = "sha256:a81cc5b6910fb7dfc7c32d20652e50fa05963f6e13ead3c5915c41ac5d16668e", size = 10489037, upload-time = "2026-03-19T16:26:32.47Z" }, - { url = "https://files.pythonhosted.org/packages/91/4a/82e0fa632e5c8b1eba5ee86ecd929e8ff327bbdbfb3c6ac5d81631bef605/ruff-0.15.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:722d165bd52403f3bdabc0ce9e41fc47070ac56d7a91b4e0d097b516a53a3477", size = 10955433, upload-time = "2026-03-19T16:27:00.205Z" }, - { url = "https://files.pythonhosted.org/packages/ab/10/12586735d0ff42526ad78c049bf51d7428618c8b5c467e72508c694119df/ruff-0.15.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7fbc2448094262552146cbe1b9643a92f66559d3761f1ad0656d4991491af49e", size = 10269302, upload-time = "2026-03-19T16:26:26.183Z" }, - { url = "https://files.pythonhosted.org/packages/eb/5d/32b5c44ccf149a26623671df49cbfbd0a0ae511ff3df9d9d2426966a8d57/ruff-0.15.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b39329b60eba44156d138275323cc726bbfbddcec3063da57caa8a8b1d50adf", size = 10607625, upload-time = "2026-03-19T16:27:03.263Z" }, - { url = "https://files.pythonhosted.org/packages/5d/f1/f0001cabe86173aaacb6eb9bb734aa0605f9a6aa6fa7d43cb49cbc4af9c9/ruff-0.15.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87768c151808505f2bfc93ae44e5f9e7c8518943e5074f76ac21558ef5627c85", size = 10324743, upload-time = "2026-03-19T16:27:09.791Z" }, - { url = "https://files.pythonhosted.org/packages/7a/87/b8a8f3d56b8d848008559e7c9d8bf367934d5367f6d932ba779456e2f73b/ruff-0.15.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb0511670002c6c529ec66c0e30641c976c8963de26a113f3a30456b702468b0", size = 11138536, upload-time = "2026-03-19T16:27:06.101Z" }, - { url = "https://files.pythonhosted.org/packages/e4/f2/4fd0d05aab0c5934b2e1464784f85ba2eab9d54bffc53fb5430d1ed8b829/ruff-0.15.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0d19644f801849229db8345180a71bee5407b429dd217f853ec515e968a6912", size = 11994292, upload-time = "2026-03-19T16:26:48.718Z" }, - { url = "https://files.pythonhosted.org/packages/64/22/fc4483871e767e5e95d1622ad83dad5ebb830f762ed0420fde7dfa9d9b08/ruff-0.15.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4806d8e09ef5e84eb19ba833d0442f7e300b23fe3f0981cae159a248a10f0036", size = 11398981, upload-time = "2026-03-19T16:26:54.513Z" }, - { url = "https://files.pythonhosted.org/packages/b0/99/66f0343176d5eab02c3f7fcd2de7a8e0dd7a41f0d982bee56cd1c24db62b/ruff-0.15.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dce0896488562f09a27b9c91b1f58a097457143931f3c4d519690dea54e624c5", size = 11242422, upload-time = "2026-03-19T16:26:29.277Z" }, - { url = "https://files.pythonhosted.org/packages/5d/3a/a7060f145bfdcce4c987ea27788b30c60e2c81d6e9a65157ca8afe646328/ruff-0.15.7-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:1852ce241d2bc89e5dc823e03cff4ce73d816b5c6cdadd27dbfe7b03217d2a12", size = 11232158, upload-time = "2026-03-19T16:26:42.321Z" }, - { url = "https://files.pythonhosted.org/packages/a7/53/90fbb9e08b29c048c403558d3cdd0adf2668b02ce9d50602452e187cd4af/ruff-0.15.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5f3e4b221fb4bd293f79912fc5e93a9063ebd6d0dcbd528f91b89172a9b8436c", size = 10577861, upload-time = "2026-03-19T16:26:57.459Z" }, - { url = "https://files.pythonhosted.org/packages/2f/aa/5f486226538fe4d0f0439e2da1716e1acf895e2a232b26f2459c55f8ddad/ruff-0.15.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b15e48602c9c1d9bdc504b472e90b90c97dc7d46c7028011ae67f3861ceba7b4", size = 10327310, upload-time = "2026-03-19T16:26:35.909Z" }, - { url = "https://files.pythonhosted.org/packages/99/9e/271afdffb81fe7bfc8c43ba079e9d96238f674380099457a74ccb3863857/ruff-0.15.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b4705e0e85cedc74b0a23cf6a179dbb3df184cb227761979cc76c0440b5ab0d", size = 10840752, upload-time = "2026-03-19T16:26:45.723Z" }, - { url = "https://files.pythonhosted.org/packages/bf/29/a4ae78394f76c7759953c47884eb44de271b03a66634148d9f7d11e721bd/ruff-0.15.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:112c1fa316a558bb34319282c1200a8bf0495f1b735aeb78bfcb2991e6087580", size = 11336961, upload-time = "2026-03-19T16:26:39.076Z" }, - { url = "https://files.pythonhosted.org/packages/26/6b/8786ba5736562220d588a2f6653e6c17e90c59ced34a2d7b512ef8956103/ruff-0.15.7-py3-none-win32.whl", hash = "sha256:6d39e2d3505b082323352f733599f28169d12e891f7dd407f2d4f54b4c2886de", size = 10582538, upload-time = "2026-03-19T16:26:15.992Z" }, - { url = "https://files.pythonhosted.org/packages/2b/e9/346d4d3fffc6871125e877dae8d9a1966b254fbd92a50f8561078b88b099/ruff-0.15.7-py3-none-win_amd64.whl", hash = "sha256:4d53d712ddebcd7dace1bc395367aec12c057aacfe9adbb6d832302575f4d3a1", size = 11755839, upload-time = "2026-03-19T16:26:19.897Z" }, - { url = "https://files.pythonhosted.org/packages/8f/e8/726643a3ea68c727da31570bde48c7a10f1aa60eddd628d94078fec586ff/ruff-0.15.7-py3-none-win_arm64.whl", hash = "sha256:18e8d73f1c3fdf27931497972250340f92e8c861722161a9caeb89a58ead6ed2", size = 11023304, upload-time = "2026-03-19T16:26:51.669Z" }, +version = "0.15.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/31/d6e536cdebb6568ae75a7f00e4b4819ae0ad2640c3604c305a0428680b0c/ruff-0.15.4.tar.gz", hash = "sha256:3412195319e42d634470cc97aa9803d07e9d5c9223b99bcb1518f0c725f26ae1", size = 4569550, upload-time = "2026-02-26T20:04:14.959Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/82/c11a03cfec3a4d26a0ea1e571f0f44be5993b923f905eeddfc397c13d360/ruff-0.15.4-py3-none-linux_armv6l.whl", hash = "sha256:a1810931c41606c686bae8b5b9a8072adac2f611bb433c0ba476acba17a332e0", size = 10453333, upload-time = "2026-02-26T20:04:20.093Z" }, + { url = "https://files.pythonhosted.org/packages/ce/5d/6a1f271f6e31dffb31855996493641edc3eef8077b883eaf007a2f1c2976/ruff-0.15.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5a1632c66672b8b4d3e1d1782859e98d6e0b4e70829530666644286600a33992", size = 10853356, upload-time = "2026-02-26T20:04:05.808Z" }, + { url = "https://files.pythonhosted.org/packages/b1/d8/0fab9f8842b83b1a9c2bf81b85063f65e93fb512e60effa95b0be49bfc54/ruff-0.15.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a4386ba2cd6c0f4ff75252845906acc7c7c8e1ac567b7bc3d373686ac8c222ba", size = 10187434, upload-time = "2026-02-26T20:03:54.656Z" }, + { url = "https://files.pythonhosted.org/packages/85/cc/cc220fd9394eff5db8d94dec199eec56dd6c9f3651d8869d024867a91030/ruff-0.15.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2496488bdfd3732747558b6f95ae427ff066d1fcd054daf75f5a50674411e75", size = 10535456, upload-time = "2026-02-26T20:03:52.738Z" }, + { url = "https://files.pythonhosted.org/packages/fa/0f/bced38fa5cf24373ec767713c8e4cadc90247f3863605fb030e597878661/ruff-0.15.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3f1c4893841ff2d54cbda1b2860fa3260173df5ddd7b95d370186f8a5e66a4ac", size = 10287772, upload-time = "2026-02-26T20:04:08.138Z" }, + { url = "https://files.pythonhosted.org/packages/2b/90/58a1802d84fed15f8f281925b21ab3cecd813bde52a8ca033a4de8ab0e7a/ruff-0.15.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:820b8766bd65503b6c30aaa6331e8ef3a6e564f7999c844e9a547c40179e440a", size = 11049051, upload-time = "2026-02-26T20:04:03.53Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ac/b7ad36703c35f3866584564dc15f12f91cb1a26a897dc2fd13d7cb3ae1af/ruff-0.15.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9fb74bab47139c1751f900f857fa503987253c3ef89129b24ed375e72873e85", size = 11890494, upload-time = "2026-02-26T20:04:10.497Z" }, + { url = "https://files.pythonhosted.org/packages/93/3d/3eb2f47a39a8b0da99faf9c54d3eb24720add1e886a5309d4d1be73a6380/ruff-0.15.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f80c98765949c518142b3a50a5db89343aa90f2c2bf7799de9986498ae6176db", size = 11326221, upload-time = "2026-02-26T20:04:12.84Z" }, + { url = "https://files.pythonhosted.org/packages/ff/90/bf134f4c1e5243e62690e09d63c55df948a74084c8ac3e48a88468314da6/ruff-0.15.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:451a2e224151729b3b6c9ffb36aed9091b2996fe4bdbd11f47e27d8f2e8888ec", size = 11168459, upload-time = "2026-02-26T20:04:00.969Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e5/a64d27688789b06b5d55162aafc32059bb8c989c61a5139a36e1368285eb/ruff-0.15.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a8f157f2e583c513c4f5f896163a93198297371f34c04220daf40d133fdd4f7f", size = 11104366, upload-time = "2026-02-26T20:03:48.099Z" }, + { url = "https://files.pythonhosted.org/packages/f1/f6/32d1dcb66a2559763fc3027bdd65836cad9eb09d90f2ed6a63d8e9252b02/ruff-0.15.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:917cc68503357021f541e69b35361c99387cdbbf99bd0ea4aa6f28ca99ff5338", size = 10510887, upload-time = "2026-02-26T20:03:45.771Z" }, + { url = "https://files.pythonhosted.org/packages/ff/92/22d1ced50971c5b6433aed166fcef8c9343f567a94cf2b9d9089f6aa80fe/ruff-0.15.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e9737c8161da79fd7cfec19f1e35620375bd8b2a50c3e77fa3d2c16f574105cc", size = 10285939, upload-time = "2026-02-26T20:04:22.42Z" }, + { url = "https://files.pythonhosted.org/packages/e6/f4/7c20aec3143837641a02509a4668fb146a642fd1211846634edc17eb5563/ruff-0.15.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:291258c917539e18f6ba40482fe31d6f5ac023994ee11d7bdafd716f2aab8a68", size = 10765471, upload-time = "2026-02-26T20:03:58.924Z" }, + { url = "https://files.pythonhosted.org/packages/d0/09/6d2f7586f09a16120aebdff8f64d962d7c4348313c77ebb29c566cefc357/ruff-0.15.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3f83c45911da6f2cd5936c436cf86b9f09f09165f033a99dcf7477e34041cbc3", size = 11263382, upload-time = "2026-02-26T20:04:24.424Z" }, + { url = "https://files.pythonhosted.org/packages/1b/fa/2ef715a1cd329ef47c1a050e10dee91a9054b7ce2fcfdd6a06d139afb7ec/ruff-0.15.4-py3-none-win32.whl", hash = "sha256:65594a2d557d4ee9f02834fcdf0a28daa8b3b9f6cb2cb93846025a36db47ef22", size = 10506664, upload-time = "2026-02-26T20:03:50.56Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a8/c688ef7e29983976820d18710f955751d9f4d4eb69df658af3d006e2ba3e/ruff-0.15.4-py3-none-win_amd64.whl", hash = "sha256:04196ad44f0df220c2ece5b0e959c2f37c777375ec744397d21d15b50a75264f", size = 11651048, upload-time = "2026-02-26T20:04:17.191Z" }, + { url = "https://files.pythonhosted.org/packages/3e/0a/9e1be9035b37448ce2e68c978f0591da94389ade5a5abafa4cf99985d1b2/ruff-0.15.4-py3-none-win_arm64.whl", hash = "sha256:60d5177e8cfc70e51b9c5fad936c634872a74209f934c1e79107d11787ad5453", size = 10966776, upload-time = "2026-02-26T20:03:56.908Z" }, ] [[package]] @@ -3155,56 +3061,56 @@ dependencies = [ { name = "scipy" }, { name = "tifffile" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/b4/2528bb43c67d48053a7a649a9666432dc307d66ba02e3a6d5c40f46655df/scikit_image-0.26.0.tar.gz", hash = "sha256:f5f970ab04efad85c24714321fcc91613fcb64ef2a892a13167df2f3e59199fa", size = 22729739 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/16/8a407688b607f86f81f8c649bf0d68a2a6d67375f18c2d660aba20f5b648/scikit_image-0.26.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b1ede33a0fb3731457eaf53af6361e73dd510f449dac437ab54573b26788baf0", size = 12355510 }, - { url = "https://files.pythonhosted.org/packages/6b/f9/7efc088ececb6f6868fd4475e16cfafc11f242ce9ab5fc3557d78b5da0d4/scikit_image-0.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7af7aa331c6846bd03fa28b164c18d0c3fd419dbb888fb05e958ac4257a78fdd", size = 12056334 }, - { url = "https://files.pythonhosted.org/packages/9f/1e/bc7fb91fb5ff65ef42346c8b7ee8b09b04eabf89235ab7dbfdfd96cbd1ea/scikit_image-0.26.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ea6207d9e9d21c3f464efe733121c0504e494dbdc7728649ff3e23c3c5a4953", size = 13297768 }, - { url = "https://files.pythonhosted.org/packages/a5/2a/e71c1a7d90e70da67b88ccc609bd6ae54798d5847369b15d3a8052232f9d/scikit_image-0.26.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74aa5518ccea28121f57a95374581d3b979839adc25bb03f289b1bc9b99c58af", size = 13711217 }, - { url = "https://files.pythonhosted.org/packages/d4/59/9637ee12c23726266b91296791465218973ce1ad3e4c56fc81e4d8e7d6e1/scikit_image-0.26.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d5c244656de905e195a904e36dbc18585e06ecf67d90f0482cbde63d7f9ad59d", size = 14337782 }, - { url = "https://files.pythonhosted.org/packages/e7/5c/a3e1e0860f9294663f540c117e4bf83d55e5b47c281d475cc06227e88411/scikit_image-0.26.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21a818ee6ca2f2131b9e04d8eb7637b5c18773ebe7b399ad23dcc5afaa226d2d", size = 14805997 }, - { url = "https://files.pythonhosted.org/packages/d3/c6/2eeacf173da041a9e388975f54e5c49df750757fcfc3ee293cdbbae1ea0a/scikit_image-0.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:9490360c8d3f9a7e85c8de87daf7c0c66507960cf4947bb9610d1751928721c7", size = 11878486 }, - { url = "https://files.pythonhosted.org/packages/c3/a4/a852c4949b9058d585e762a66bf7e9a2cd3be4795cd940413dfbfbb0ce79/scikit_image-0.26.0-cp311-cp311-win_arm64.whl", hash = "sha256:0baa0108d2d027f34d748e84e592b78acc23e965a5de0e4bb03cf371de5c0581", size = 11346518 }, - { url = "https://files.pythonhosted.org/packages/99/e8/e13757982264b33a1621628f86b587e9a73a13f5256dad49b19ba7dc9083/scikit_image-0.26.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d454b93a6fa770ac5ae2d33570f8e7a321bb80d29511ce4b6b78058ebe176e8c", size = 12376452 }, - { url = "https://files.pythonhosted.org/packages/e3/be/f8dd17d0510f9911f9f17ba301f7455328bf13dae416560126d428de9568/scikit_image-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3409e89d66eff5734cd2b672d1c48d2759360057e714e1d92a11df82c87cba37", size = 12061567 }, - { url = "https://files.pythonhosted.org/packages/b3/2b/c70120a6880579fb42b91567ad79feb4772f7be72e8d52fec403a3dde0c6/scikit_image-0.26.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c717490cec9e276afb0438dd165b7c3072d6c416709cc0f9f5a4c1070d23a44", size = 13084214 }, - { url = "https://files.pythonhosted.org/packages/f4/a2/70401a107d6d7466d64b466927e6b96fcefa99d57494b972608e2f8be50f/scikit_image-0.26.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7df650e79031634ac90b11e64a9eedaf5a5e06fcd09bcd03a34be01745744466", size = 13561683 }, - { url = "https://files.pythonhosted.org/packages/13/a5/48bdfd92794c5002d664e0910a349d0a1504671ef5ad358150f21643c79a/scikit_image-0.26.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cefd85033e66d4ea35b525bb0937d7f42d4cdcfed2d1888e1570d5ce450d3932", size = 14112147 }, - { url = "https://files.pythonhosted.org/packages/ee/b5/ac71694da92f5def5953ca99f18a10fe98eac2dd0a34079389b70b4d0394/scikit_image-0.26.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3f5bf622d7c0435884e1e141ebbe4b2804e16b2dd23ae4c6183e2ea99233be70", size = 14661625 }, - { url = "https://files.pythonhosted.org/packages/23/4d/a3cc1e96f080e253dad2251bfae7587cf2b7912bcd76fd43fd366ff35a87/scikit_image-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:abed017474593cd3056ae0fe948d07d0747b27a085e92df5474f4955dd65aec0", size = 11911059 }, - { url = "https://files.pythonhosted.org/packages/35/8a/d1b8055f584acc937478abf4550d122936f420352422a1a625eef2c605d8/scikit_image-0.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:4d57e39ef67a95d26860c8caf9b14b8fb130f83b34c6656a77f191fa6d1d04d8", size = 11348740 }, - { url = "https://files.pythonhosted.org/packages/4f/48/02357ffb2cca35640f33f2cfe054a4d6d5d7a229b88880a64f1e45c11f4e/scikit_image-0.26.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a2e852eccf41d2d322b8e60144e124802873a92b8d43a6f96331aa42888491c7", size = 12346329 }, - { url = "https://files.pythonhosted.org/packages/67/b9/b792c577cea2c1e94cda83b135a656924fc57c428e8a6d302cd69aac1b60/scikit_image-0.26.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:98329aab3bc87db352b9887f64ce8cdb8e75f7c2daa19927f2e121b797b678d5", size = 12031726 }, - { url = "https://files.pythonhosted.org/packages/07/a9/9564250dfd65cb20404a611016db52afc6268b2b371cd19c7538ea47580f/scikit_image-0.26.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:915bb3ba66455cf8adac00dc8fdf18a4cd29656aec7ddd38cb4dda90289a6f21", size = 13094910 }, - { url = "https://files.pythonhosted.org/packages/a3/b8/0d8eeb5a9fd7d34ba84f8a55753a0a3e2b5b51b2a5a0ade648a8db4a62f7/scikit_image-0.26.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b36ab5e778bf50af5ff386c3ac508027dc3aaeccf2161bdf96bde6848f44d21b", size = 13660939 }, - { url = "https://files.pythonhosted.org/packages/2f/d6/91d8973584d4793d4c1a847d388e34ef1218d835eeddecfc9108d735b467/scikit_image-0.26.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:09bad6a5d5949c7896c8347424c4cca899f1d11668030e5548813ab9c2865dcb", size = 14138938 }, - { url = "https://files.pythonhosted.org/packages/39/9a/7e15d8dc10d6bbf212195fb39bdeb7f226c46dd53f9c63c312e111e2e175/scikit_image-0.26.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:aeb14db1ed09ad4bee4ceb9e635547a8d5f3549be67fc6c768c7f923e027e6cd", size = 14752243 }, - { url = "https://files.pythonhosted.org/packages/8f/58/2b11b933097bc427e42b4a8b15f7de8f24f2bac1fd2779d2aea1431b2c31/scikit_image-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:ac529eb9dbd5954f9aaa2e3fe9a3fd9661bfe24e134c688587d811a0233127f1", size = 11906770 }, - { url = "https://files.pythonhosted.org/packages/ad/ec/96941474a18a04b69b6f6562a5bd79bd68049fa3728d3b350976eccb8b93/scikit_image-0.26.0-cp313-cp313-win_arm64.whl", hash = "sha256:a2d211bc355f59725efdcae699b93b30348a19416cc9e017f7b2fb599faf7219", size = 11342506 }, - { url = "https://files.pythonhosted.org/packages/03/e5/c1a9962b0cf1952f42d32b4a2e48eed520320dbc4d2ff0b981c6fa508b6b/scikit_image-0.26.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9eefb4adad066da408a7601c4c24b07af3b472d90e08c3e7483d4e9e829d8c49", size = 12663278 }, - { url = "https://files.pythonhosted.org/packages/ae/97/c1a276a59ce8e4e24482d65c1a3940d69c6b3873279193b7ebd04e5ee56b/scikit_image-0.26.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6caec76e16c970c528d15d1c757363334d5cb3069f9cea93d2bead31820511f3", size = 12405142 }, - { url = "https://files.pythonhosted.org/packages/d4/4a/f1cbd1357caef6c7993f7efd514d6e53d8fd6f7fe01c4714d51614c53289/scikit_image-0.26.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a07200fe09b9d99fcdab959859fe0f7db8df6333d6204344425d476850ce3604", size = 12942086 }, - { url = "https://files.pythonhosted.org/packages/5b/6f/74d9fb87c5655bd64cf00b0c44dc3d6206d9002e5f6ba1c9aeb13236f6bf/scikit_image-0.26.0-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92242351bccf391fc5df2d1529d15470019496d2498d615beb68da85fe7fdf37", size = 13265667 }, - { url = "https://files.pythonhosted.org/packages/a7/73/faddc2413ae98d863f6fa2e3e14da4467dd38e788e1c23346cf1a2b06b97/scikit_image-0.26.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:52c496f75a7e45844d951557f13c08c81487c6a1da2e3c9c8a39fcde958e02cc", size = 14001966 }, - { url = "https://files.pythonhosted.org/packages/02/94/9f46966fa042b5d57c8cd641045372b4e0df0047dd400e77ea9952674110/scikit_image-0.26.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:20ef4a155e2e78b8ab973998e04d8a361d49d719e65412405f4dadd9155a61d9", size = 14359526 }, - { url = "https://files.pythonhosted.org/packages/5d/b4/2840fe38f10057f40b1c9f8fb98a187a370936bf144a4ac23452c5ef1baf/scikit_image-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c9087cf7d0e7f33ab5c46d2068d86d785e70b05400a891f73a13400f1e1faf6a", size = 12287629 }, - { url = "https://files.pythonhosted.org/packages/22/ba/73b6ca70796e71f83ab222690e35a79612f0117e5aaf167151b7d46f5f2c/scikit_image-0.26.0-cp313-cp313t-win_arm64.whl", hash = "sha256:27d58bc8b2acd351f972c6508c1b557cfed80299826080a4d803dd29c51b707e", size = 11647755 }, - { url = "https://files.pythonhosted.org/packages/51/44/6b744f92b37ae2833fd423cce8f806d2368859ec325a699dc30389e090b9/scikit_image-0.26.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:63af3d3a26125f796f01052052f86806da5b5e54c6abef152edb752683075a9c", size = 12365810 }, - { url = "https://files.pythonhosted.org/packages/40/f5/83590d9355191f86ac663420fec741b82cc547a4afe7c4c1d986bf46e4db/scikit_image-0.26.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ce00600cd70d4562ed59f80523e18cdcc1fae0e10676498a01f73c255774aefd", size = 12075717 }, - { url = "https://files.pythonhosted.org/packages/72/48/253e7cf5aee6190459fe136c614e2cbccc562deceb4af96e0863f1b8ee29/scikit_image-0.26.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6381edf972b32e4f54085449afde64365a57316637496c1325a736987083e2ab", size = 13161520 }, - { url = "https://files.pythonhosted.org/packages/73/c3/cec6a3cbaadfdcc02bd6ff02f3abfe09eaa7f4d4e0a525a1e3a3f4bce49c/scikit_image-0.26.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6624a76c6085218248154cc7e1500e6b488edcd9499004dd0d35040607d7505", size = 13684340 }, - { url = "https://files.pythonhosted.org/packages/d4/0d/39a776f675d24164b3a267aa0db9f677a4cb20127660d8bf4fd7fef66817/scikit_image-0.26.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f775f0e420faac9c2aa6757135f4eb468fb7b70e0b67fa77a5e79be3c30ee331", size = 14203839 }, - { url = "https://files.pythonhosted.org/packages/ee/25/2514df226bbcedfe9b2caafa1ba7bc87231a0c339066981b182b08340e06/scikit_image-0.26.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede4d6d255cc5da9faeb2f9ba7fedbc990abbc652db429f40a16b22e770bb578", size = 14770021 }, - { url = "https://files.pythonhosted.org/packages/8d/5b/0671dc91c0c79340c3fe202f0549c7d3681eb7640fe34ab68a5f090a7c7f/scikit_image-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:0660b83968c15293fd9135e8d860053ee19500d52bf55ca4fb09de595a1af650", size = 12023490 }, - { url = "https://files.pythonhosted.org/packages/65/08/7c4cb59f91721f3de07719085212a0b3962e3e3f2d1818cbac4eeb1ea53e/scikit_image-0.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:b8d14d3181c21c11170477a42542c1addc7072a90b986675a71266ad17abc37f", size = 11473782 }, - { url = "https://files.pythonhosted.org/packages/49/41/65c4258137acef3d73cb561ac55512eacd7b30bb4f4a11474cad526bc5db/scikit_image-0.26.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:cde0bbd57e6795eba83cb10f71a677f7239271121dc950bc060482834a668ad1", size = 12686060 }, - { url = "https://files.pythonhosted.org/packages/e7/32/76971f8727b87f1420a962406388a50e26667c31756126444baf6668f559/scikit_image-0.26.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:163e9afb5b879562b9aeda0dd45208a35316f26cc7a3aed54fd601604e5cf46f", size = 12422628 }, - { url = "https://files.pythonhosted.org/packages/37/0d/996febd39f757c40ee7b01cdb861867327e5c8e5f595a634e8201462d958/scikit_image-0.26.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724f79fd9b6cb6f4a37864fe09f81f9f5d5b9646b6868109e1b100d1a7019e59", size = 12962369 }, - { url = "https://files.pythonhosted.org/packages/48/b4/612d354f946c9600e7dea012723c11d47e8d455384e530f6daaaeb9bf62c/scikit_image-0.26.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3268f13310e6857508bd87202620df996199a016a1d281b309441d227c822394", size = 13272431 }, - { url = "https://files.pythonhosted.org/packages/0a/6e/26c00b466e06055a086de2c6e2145fe189ccdc9a1d11ccc7de020f2591ad/scikit_image-0.26.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fac96a1f9b06cd771cbbb3cd96c5332f36d4efd839b1d8b053f79e5887acde62", size = 14016362 }, - { url = "https://files.pythonhosted.org/packages/47/88/00a90402e1775634043c2a0af8a3c76ad450866d9fa444efcc43b553ba2d/scikit_image-0.26.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2c1e7bd342f43e7a97e571b3f03ba4c1293ea1a35c3f13f41efdc8a81c1dc8f2", size = 14364151 }, - { url = "https://files.pythonhosted.org/packages/da/ca/918d8d306bd43beacff3b835c6d96fac0ae64c0857092f068b88db531a7c/scikit_image-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b702c3bb115e1dcf4abf5297429b5c90f2189655888cbed14921f3d26f81d3a4", size = 12413484 }, - { url = "https://files.pythonhosted.org/packages/dc/cd/4da01329b5a8d47ff7ec3c99a2b02465a8017b186027590dc7425cee0b56/scikit_image-0.26.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0608aa4a9ec39e0843de10d60edb2785a30c1c47819b67866dd223ebd149acaf", size = 11769501 }, +sdist = { url = "https://files.pythonhosted.org/packages/a1/b4/2528bb43c67d48053a7a649a9666432dc307d66ba02e3a6d5c40f46655df/scikit_image-0.26.0.tar.gz", hash = "sha256:f5f970ab04efad85c24714321fcc91613fcb64ef2a892a13167df2f3e59199fa", size = 22729739, upload-time = "2025-12-20T17:12:21.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/16/8a407688b607f86f81f8c649bf0d68a2a6d67375f18c2d660aba20f5b648/scikit_image-0.26.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b1ede33a0fb3731457eaf53af6361e73dd510f449dac437ab54573b26788baf0", size = 12355510, upload-time = "2025-12-20T17:10:31.628Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f9/7efc088ececb6f6868fd4475e16cfafc11f242ce9ab5fc3557d78b5da0d4/scikit_image-0.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7af7aa331c6846bd03fa28b164c18d0c3fd419dbb888fb05e958ac4257a78fdd", size = 12056334, upload-time = "2025-12-20T17:10:34.559Z" }, + { url = "https://files.pythonhosted.org/packages/9f/1e/bc7fb91fb5ff65ef42346c8b7ee8b09b04eabf89235ab7dbfdfd96cbd1ea/scikit_image-0.26.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ea6207d9e9d21c3f464efe733121c0504e494dbdc7728649ff3e23c3c5a4953", size = 13297768, upload-time = "2025-12-20T17:10:37.733Z" }, + { url = "https://files.pythonhosted.org/packages/a5/2a/e71c1a7d90e70da67b88ccc609bd6ae54798d5847369b15d3a8052232f9d/scikit_image-0.26.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74aa5518ccea28121f57a95374581d3b979839adc25bb03f289b1bc9b99c58af", size = 13711217, upload-time = "2025-12-20T17:10:40.935Z" }, + { url = "https://files.pythonhosted.org/packages/d4/59/9637ee12c23726266b91296791465218973ce1ad3e4c56fc81e4d8e7d6e1/scikit_image-0.26.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d5c244656de905e195a904e36dbc18585e06ecf67d90f0482cbde63d7f9ad59d", size = 14337782, upload-time = "2025-12-20T17:10:43.452Z" }, + { url = "https://files.pythonhosted.org/packages/e7/5c/a3e1e0860f9294663f540c117e4bf83d55e5b47c281d475cc06227e88411/scikit_image-0.26.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21a818ee6ca2f2131b9e04d8eb7637b5c18773ebe7b399ad23dcc5afaa226d2d", size = 14805997, upload-time = "2025-12-20T17:10:45.93Z" }, + { url = "https://files.pythonhosted.org/packages/d3/c6/2eeacf173da041a9e388975f54e5c49df750757fcfc3ee293cdbbae1ea0a/scikit_image-0.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:9490360c8d3f9a7e85c8de87daf7c0c66507960cf4947bb9610d1751928721c7", size = 11878486, upload-time = "2025-12-20T17:10:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/c3/a4/a852c4949b9058d585e762a66bf7e9a2cd3be4795cd940413dfbfbb0ce79/scikit_image-0.26.0-cp311-cp311-win_arm64.whl", hash = "sha256:0baa0108d2d027f34d748e84e592b78acc23e965a5de0e4bb03cf371de5c0581", size = 11346518, upload-time = "2025-12-20T17:10:50.575Z" }, + { url = "https://files.pythonhosted.org/packages/99/e8/e13757982264b33a1621628f86b587e9a73a13f5256dad49b19ba7dc9083/scikit_image-0.26.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d454b93a6fa770ac5ae2d33570f8e7a321bb80d29511ce4b6b78058ebe176e8c", size = 12376452, upload-time = "2025-12-20T17:10:52.796Z" }, + { url = "https://files.pythonhosted.org/packages/e3/be/f8dd17d0510f9911f9f17ba301f7455328bf13dae416560126d428de9568/scikit_image-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3409e89d66eff5734cd2b672d1c48d2759360057e714e1d92a11df82c87cba37", size = 12061567, upload-time = "2025-12-20T17:10:55.207Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/c70120a6880579fb42b91567ad79feb4772f7be72e8d52fec403a3dde0c6/scikit_image-0.26.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c717490cec9e276afb0438dd165b7c3072d6c416709cc0f9f5a4c1070d23a44", size = 13084214, upload-time = "2025-12-20T17:10:57.468Z" }, + { url = "https://files.pythonhosted.org/packages/f4/a2/70401a107d6d7466d64b466927e6b96fcefa99d57494b972608e2f8be50f/scikit_image-0.26.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7df650e79031634ac90b11e64a9eedaf5a5e06fcd09bcd03a34be01745744466", size = 13561683, upload-time = "2025-12-20T17:10:59.49Z" }, + { url = "https://files.pythonhosted.org/packages/13/a5/48bdfd92794c5002d664e0910a349d0a1504671ef5ad358150f21643c79a/scikit_image-0.26.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cefd85033e66d4ea35b525bb0937d7f42d4cdcfed2d1888e1570d5ce450d3932", size = 14112147, upload-time = "2025-12-20T17:11:02.083Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b5/ac71694da92f5def5953ca99f18a10fe98eac2dd0a34079389b70b4d0394/scikit_image-0.26.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3f5bf622d7c0435884e1e141ebbe4b2804e16b2dd23ae4c6183e2ea99233be70", size = 14661625, upload-time = "2025-12-20T17:11:04.528Z" }, + { url = "https://files.pythonhosted.org/packages/23/4d/a3cc1e96f080e253dad2251bfae7587cf2b7912bcd76fd43fd366ff35a87/scikit_image-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:abed017474593cd3056ae0fe948d07d0747b27a085e92df5474f4955dd65aec0", size = 11911059, upload-time = "2025-12-20T17:11:06.61Z" }, + { url = "https://files.pythonhosted.org/packages/35/8a/d1b8055f584acc937478abf4550d122936f420352422a1a625eef2c605d8/scikit_image-0.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:4d57e39ef67a95d26860c8caf9b14b8fb130f83b34c6656a77f191fa6d1d04d8", size = 11348740, upload-time = "2025-12-20T17:11:09.118Z" }, + { url = "https://files.pythonhosted.org/packages/4f/48/02357ffb2cca35640f33f2cfe054a4d6d5d7a229b88880a64f1e45c11f4e/scikit_image-0.26.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a2e852eccf41d2d322b8e60144e124802873a92b8d43a6f96331aa42888491c7", size = 12346329, upload-time = "2025-12-20T17:11:11.599Z" }, + { url = "https://files.pythonhosted.org/packages/67/b9/b792c577cea2c1e94cda83b135a656924fc57c428e8a6d302cd69aac1b60/scikit_image-0.26.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:98329aab3bc87db352b9887f64ce8cdb8e75f7c2daa19927f2e121b797b678d5", size = 12031726, upload-time = "2025-12-20T17:11:13.871Z" }, + { url = "https://files.pythonhosted.org/packages/07/a9/9564250dfd65cb20404a611016db52afc6268b2b371cd19c7538ea47580f/scikit_image-0.26.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:915bb3ba66455cf8adac00dc8fdf18a4cd29656aec7ddd38cb4dda90289a6f21", size = 13094910, upload-time = "2025-12-20T17:11:16.2Z" }, + { url = "https://files.pythonhosted.org/packages/a3/b8/0d8eeb5a9fd7d34ba84f8a55753a0a3e2b5b51b2a5a0ade648a8db4a62f7/scikit_image-0.26.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b36ab5e778bf50af5ff386c3ac508027dc3aaeccf2161bdf96bde6848f44d21b", size = 13660939, upload-time = "2025-12-20T17:11:18.464Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d6/91d8973584d4793d4c1a847d388e34ef1218d835eeddecfc9108d735b467/scikit_image-0.26.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:09bad6a5d5949c7896c8347424c4cca899f1d11668030e5548813ab9c2865dcb", size = 14138938, upload-time = "2025-12-20T17:11:20.919Z" }, + { url = "https://files.pythonhosted.org/packages/39/9a/7e15d8dc10d6bbf212195fb39bdeb7f226c46dd53f9c63c312e111e2e175/scikit_image-0.26.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:aeb14db1ed09ad4bee4ceb9e635547a8d5f3549be67fc6c768c7f923e027e6cd", size = 14752243, upload-time = "2025-12-20T17:11:23.347Z" }, + { url = "https://files.pythonhosted.org/packages/8f/58/2b11b933097bc427e42b4a8b15f7de8f24f2bac1fd2779d2aea1431b2c31/scikit_image-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:ac529eb9dbd5954f9aaa2e3fe9a3fd9661bfe24e134c688587d811a0233127f1", size = 11906770, upload-time = "2025-12-20T17:11:25.297Z" }, + { url = "https://files.pythonhosted.org/packages/ad/ec/96941474a18a04b69b6f6562a5bd79bd68049fa3728d3b350976eccb8b93/scikit_image-0.26.0-cp313-cp313-win_arm64.whl", hash = "sha256:a2d211bc355f59725efdcae699b93b30348a19416cc9e017f7b2fb599faf7219", size = 11342506, upload-time = "2025-12-20T17:11:27.399Z" }, + { url = "https://files.pythonhosted.org/packages/03/e5/c1a9962b0cf1952f42d32b4a2e48eed520320dbc4d2ff0b981c6fa508b6b/scikit_image-0.26.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9eefb4adad066da408a7601c4c24b07af3b472d90e08c3e7483d4e9e829d8c49", size = 12663278, upload-time = "2025-12-20T17:11:29.358Z" }, + { url = "https://files.pythonhosted.org/packages/ae/97/c1a276a59ce8e4e24482d65c1a3940d69c6b3873279193b7ebd04e5ee56b/scikit_image-0.26.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6caec76e16c970c528d15d1c757363334d5cb3069f9cea93d2bead31820511f3", size = 12405142, upload-time = "2025-12-20T17:11:31.282Z" }, + { url = "https://files.pythonhosted.org/packages/d4/4a/f1cbd1357caef6c7993f7efd514d6e53d8fd6f7fe01c4714d51614c53289/scikit_image-0.26.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a07200fe09b9d99fcdab959859fe0f7db8df6333d6204344425d476850ce3604", size = 12942086, upload-time = "2025-12-20T17:11:33.683Z" }, + { url = "https://files.pythonhosted.org/packages/5b/6f/74d9fb87c5655bd64cf00b0c44dc3d6206d9002e5f6ba1c9aeb13236f6bf/scikit_image-0.26.0-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92242351bccf391fc5df2d1529d15470019496d2498d615beb68da85fe7fdf37", size = 13265667, upload-time = "2025-12-20T17:11:36.11Z" }, + { url = "https://files.pythonhosted.org/packages/a7/73/faddc2413ae98d863f6fa2e3e14da4467dd38e788e1c23346cf1a2b06b97/scikit_image-0.26.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:52c496f75a7e45844d951557f13c08c81487c6a1da2e3c9c8a39fcde958e02cc", size = 14001966, upload-time = "2025-12-20T17:11:38.55Z" }, + { url = "https://files.pythonhosted.org/packages/02/94/9f46966fa042b5d57c8cd641045372b4e0df0047dd400e77ea9952674110/scikit_image-0.26.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:20ef4a155e2e78b8ab973998e04d8a361d49d719e65412405f4dadd9155a61d9", size = 14359526, upload-time = "2025-12-20T17:11:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/5d/b4/2840fe38f10057f40b1c9f8fb98a187a370936bf144a4ac23452c5ef1baf/scikit_image-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c9087cf7d0e7f33ab5c46d2068d86d785e70b05400a891f73a13400f1e1faf6a", size = 12287629, upload-time = "2025-12-20T17:11:43.11Z" }, + { url = "https://files.pythonhosted.org/packages/22/ba/73b6ca70796e71f83ab222690e35a79612f0117e5aaf167151b7d46f5f2c/scikit_image-0.26.0-cp313-cp313t-win_arm64.whl", hash = "sha256:27d58bc8b2acd351f972c6508c1b557cfed80299826080a4d803dd29c51b707e", size = 11647755, upload-time = "2025-12-20T17:11:45.279Z" }, + { url = "https://files.pythonhosted.org/packages/51/44/6b744f92b37ae2833fd423cce8f806d2368859ec325a699dc30389e090b9/scikit_image-0.26.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:63af3d3a26125f796f01052052f86806da5b5e54c6abef152edb752683075a9c", size = 12365810, upload-time = "2025-12-20T17:11:47.357Z" }, + { url = "https://files.pythonhosted.org/packages/40/f5/83590d9355191f86ac663420fec741b82cc547a4afe7c4c1d986bf46e4db/scikit_image-0.26.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ce00600cd70d4562ed59f80523e18cdcc1fae0e10676498a01f73c255774aefd", size = 12075717, upload-time = "2025-12-20T17:11:49.483Z" }, + { url = "https://files.pythonhosted.org/packages/72/48/253e7cf5aee6190459fe136c614e2cbccc562deceb4af96e0863f1b8ee29/scikit_image-0.26.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6381edf972b32e4f54085449afde64365a57316637496c1325a736987083e2ab", size = 13161520, upload-time = "2025-12-20T17:11:51.58Z" }, + { url = "https://files.pythonhosted.org/packages/73/c3/cec6a3cbaadfdcc02bd6ff02f3abfe09eaa7f4d4e0a525a1e3a3f4bce49c/scikit_image-0.26.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6624a76c6085218248154cc7e1500e6b488edcd9499004dd0d35040607d7505", size = 13684340, upload-time = "2025-12-20T17:11:53.708Z" }, + { url = "https://files.pythonhosted.org/packages/d4/0d/39a776f675d24164b3a267aa0db9f677a4cb20127660d8bf4fd7fef66817/scikit_image-0.26.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f775f0e420faac9c2aa6757135f4eb468fb7b70e0b67fa77a5e79be3c30ee331", size = 14203839, upload-time = "2025-12-20T17:11:55.89Z" }, + { url = "https://files.pythonhosted.org/packages/ee/25/2514df226bbcedfe9b2caafa1ba7bc87231a0c339066981b182b08340e06/scikit_image-0.26.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede4d6d255cc5da9faeb2f9ba7fedbc990abbc652db429f40a16b22e770bb578", size = 14770021, upload-time = "2025-12-20T17:11:58.014Z" }, + { url = "https://files.pythonhosted.org/packages/8d/5b/0671dc91c0c79340c3fe202f0549c7d3681eb7640fe34ab68a5f090a7c7f/scikit_image-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:0660b83968c15293fd9135e8d860053ee19500d52bf55ca4fb09de595a1af650", size = 12023490, upload-time = "2025-12-20T17:12:00.013Z" }, + { url = "https://files.pythonhosted.org/packages/65/08/7c4cb59f91721f3de07719085212a0b3962e3e3f2d1818cbac4eeb1ea53e/scikit_image-0.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:b8d14d3181c21c11170477a42542c1addc7072a90b986675a71266ad17abc37f", size = 11473782, upload-time = "2025-12-20T17:12:01.983Z" }, + { url = "https://files.pythonhosted.org/packages/49/41/65c4258137acef3d73cb561ac55512eacd7b30bb4f4a11474cad526bc5db/scikit_image-0.26.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:cde0bbd57e6795eba83cb10f71a677f7239271121dc950bc060482834a668ad1", size = 12686060, upload-time = "2025-12-20T17:12:03.886Z" }, + { url = "https://files.pythonhosted.org/packages/e7/32/76971f8727b87f1420a962406388a50e26667c31756126444baf6668f559/scikit_image-0.26.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:163e9afb5b879562b9aeda0dd45208a35316f26cc7a3aed54fd601604e5cf46f", size = 12422628, upload-time = "2025-12-20T17:12:05.921Z" }, + { url = "https://files.pythonhosted.org/packages/37/0d/996febd39f757c40ee7b01cdb861867327e5c8e5f595a634e8201462d958/scikit_image-0.26.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724f79fd9b6cb6f4a37864fe09f81f9f5d5b9646b6868109e1b100d1a7019e59", size = 12962369, upload-time = "2025-12-20T17:12:07.912Z" }, + { url = "https://files.pythonhosted.org/packages/48/b4/612d354f946c9600e7dea012723c11d47e8d455384e530f6daaaeb9bf62c/scikit_image-0.26.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3268f13310e6857508bd87202620df996199a016a1d281b309441d227c822394", size = 13272431, upload-time = "2025-12-20T17:12:10.255Z" }, + { url = "https://files.pythonhosted.org/packages/0a/6e/26c00b466e06055a086de2c6e2145fe189ccdc9a1d11ccc7de020f2591ad/scikit_image-0.26.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fac96a1f9b06cd771cbbb3cd96c5332f36d4efd839b1d8b053f79e5887acde62", size = 14016362, upload-time = "2025-12-20T17:12:12.793Z" }, + { url = "https://files.pythonhosted.org/packages/47/88/00a90402e1775634043c2a0af8a3c76ad450866d9fa444efcc43b553ba2d/scikit_image-0.26.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2c1e7bd342f43e7a97e571b3f03ba4c1293ea1a35c3f13f41efdc8a81c1dc8f2", size = 14364151, upload-time = "2025-12-20T17:12:14.909Z" }, + { url = "https://files.pythonhosted.org/packages/da/ca/918d8d306bd43beacff3b835c6d96fac0ae64c0857092f068b88db531a7c/scikit_image-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b702c3bb115e1dcf4abf5297429b5c90f2189655888cbed14921f3d26f81d3a4", size = 12413484, upload-time = "2025-12-20T17:12:17.046Z" }, + { url = "https://files.pythonhosted.org/packages/dc/cd/4da01329b5a8d47ff7ec3c99a2b02465a8017b186027590dc7425cee0b56/scikit_image-0.26.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0608aa4a9ec39e0843de10d60edb2785a30c1c47819b67866dd223ebd149acaf", size = 11769501, upload-time = "2025-12-20T17:12:19.339Z" }, ] [[package]] @@ -3214,157 +3120,157 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:1f95b894f13729334fb990162e911c9e5dc1ab390c58aa6cbecb389c5b5e28ec", size = 31613675 }, - { url = "https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:e18f12c6b0bc5a592ed23d3f7b891f68fd7f8241d69b7883769eb5d5dfb52696", size = 28162057 }, - { url = "https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee", size = 20334032 }, - { url = "https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:766e0dc5a616d026a3a1cffa379af959671729083882f50307e18175797b3dfd", size = 22709533 }, - { url = "https://files.pythonhosted.org/packages/4d/60/8804678875fc59362b0fb759ab3ecce1f09c10a735680318ac30da8cd76b/scipy-1.17.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:744b2bf3640d907b79f3fd7874efe432d1cf171ee721243e350f55234b4cec4c", size = 33062057 }, - { url = "https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43af8d1f3bea642559019edfe64e9b11192a8978efbd1539d7bc2aaa23d92de4", size = 35349300 }, - { url = "https://files.pythonhosted.org/packages/b4/3d/7ccbbdcbb54c8fdc20d3b6930137c782a163fa626f0aef920349873421ba/scipy-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd96a1898c0a47be4520327e01f874acfd61fb48a9420f8aa9f6483412ffa444", size = 35127333 }, - { url = "https://files.pythonhosted.org/packages/e8/19/f926cb11c42b15ba08e3a71e376d816ac08614f769b4f47e06c3580c836a/scipy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4eb6c25dd62ee8d5edf68a8e1c171dd71c292fdae95d8aeb3dd7d7de4c364082", size = 37741314 }, - { url = "https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff", size = 36607512 }, - { url = "https://files.pythonhosted.org/packages/68/7f/bdd79ceaad24b671543ffe0ef61ed8e659440eb683b66f033454dcee90eb/scipy-1.17.1-cp311-cp311-win_arm64.whl", hash = "sha256:9ecb4efb1cd6e8c4afea0daa91a87fbddbce1b99d2895d151596716c0b2e859d", size = 24599248 }, - { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954 }, - { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662 }, - { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366 }, - { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017 }, - { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842 }, - { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890 }, - { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557 }, - { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856 }, - { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682 }, - { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340 }, - { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199 }, - { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001 }, - { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719 }, - { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595 }, - { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429 }, - { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952 }, - { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063 }, - { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449 }, - { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943 }, - { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621 }, - { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708 }, - { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135 }, - { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977 }, - { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601 }, - { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667 }, - { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159 }, - { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771 }, - { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910 }, - { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980 }, - { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543 }, - { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510 }, - { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131 }, - { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032 }, - { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766 }, - { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007 }, - { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333 }, - { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066 }, - { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763 }, - { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984 }, - { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877 }, - { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750 }, - { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858 }, - { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723 }, - { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098 }, - { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397 }, - { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163 }, - { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291 }, - { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317 }, - { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327 }, - { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165 }, +sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:1f95b894f13729334fb990162e911c9e5dc1ab390c58aa6cbecb389c5b5e28ec", size = 31613675, upload-time = "2026-02-23T00:16:00.13Z" }, + { url = "https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:e18f12c6b0bc5a592ed23d3f7b891f68fd7f8241d69b7883769eb5d5dfb52696", size = 28162057, upload-time = "2026-02-23T00:16:09.456Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee", size = 20334032, upload-time = "2026-02-23T00:16:17.358Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:766e0dc5a616d026a3a1cffa379af959671729083882f50307e18175797b3dfd", size = 22709533, upload-time = "2026-02-23T00:16:25.791Z" }, + { url = "https://files.pythonhosted.org/packages/4d/60/8804678875fc59362b0fb759ab3ecce1f09c10a735680318ac30da8cd76b/scipy-1.17.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:744b2bf3640d907b79f3fd7874efe432d1cf171ee721243e350f55234b4cec4c", size = 33062057, upload-time = "2026-02-23T00:16:36.931Z" }, + { url = "https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43af8d1f3bea642559019edfe64e9b11192a8978efbd1539d7bc2aaa23d92de4", size = 35349300, upload-time = "2026-02-23T00:16:49.108Z" }, + { url = "https://files.pythonhosted.org/packages/b4/3d/7ccbbdcbb54c8fdc20d3b6930137c782a163fa626f0aef920349873421ba/scipy-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd96a1898c0a47be4520327e01f874acfd61fb48a9420f8aa9f6483412ffa444", size = 35127333, upload-time = "2026-02-23T00:17:01.293Z" }, + { url = "https://files.pythonhosted.org/packages/e8/19/f926cb11c42b15ba08e3a71e376d816ac08614f769b4f47e06c3580c836a/scipy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4eb6c25dd62ee8d5edf68a8e1c171dd71c292fdae95d8aeb3dd7d7de4c364082", size = 37741314, upload-time = "2026-02-23T00:17:12.576Z" }, + { url = "https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff", size = 36607512, upload-time = "2026-02-23T00:17:23.424Z" }, + { url = "https://files.pythonhosted.org/packages/68/7f/bdd79ceaad24b671543ffe0ef61ed8e659440eb683b66f033454dcee90eb/scipy-1.17.1-cp311-cp311-win_arm64.whl", hash = "sha256:9ecb4efb1cd6e8c4afea0daa91a87fbddbce1b99d2895d151596716c0b2e859d", size = 24599248, upload-time = "2026-02-23T00:17:34.561Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, + { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, + { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, + { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, + { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, + { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z" }, + { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z" }, + { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z" }, + { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z" }, + { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z" }, + { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z" }, + { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z" }, + { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z" }, + { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z" }, + { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z" }, + { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510, upload-time = "2026-02-23T00:21:01.015Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131, upload-time = "2026-02-23T00:21:05.888Z" }, + { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032, upload-time = "2026-02-23T00:21:09.904Z" }, + { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766, upload-time = "2026-02-23T00:21:14.313Z" }, + { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007, upload-time = "2026-02-23T00:21:19.663Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333, upload-time = "2026-02-23T00:21:25.278Z" }, + { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066, upload-time = "2026-02-23T00:21:31.358Z" }, + { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763, upload-time = "2026-02-23T00:21:37.247Z" }, + { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984, upload-time = "2026-02-23T00:22:35.023Z" }, + { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877, upload-time = "2026-02-23T00:22:39.798Z" }, + { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750, upload-time = "2026-02-23T00:21:42.289Z" }, + { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858, upload-time = "2026-02-23T00:21:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723, upload-time = "2026-02-23T00:21:52.039Z" }, + { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098, upload-time = "2026-02-23T00:21:56.185Z" }, + { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397, upload-time = "2026-02-23T00:22:01.404Z" }, + { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163, upload-time = "2026-02-23T00:22:07.024Z" }, + { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291, upload-time = "2026-02-23T00:22:12.585Z" }, + { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317, upload-time = "2026-02-23T00:22:18.513Z" }, + { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327, upload-time = "2026-02-23T00:22:24.442Z" }, + { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, ] [[package]] name = "send2trash" version = "2.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c5/f0/184b4b5f8d00f2a92cf96eec8967a3d550b52cf94362dad1100df9e48d57/send2trash-2.1.0.tar.gz", hash = "sha256:1c72b39f09457db3c05ce1d19158c2cbef4c32b8bedd02c155e49282b7ea7459", size = 17255 } +sdist = { url = "https://files.pythonhosted.org/packages/c5/f0/184b4b5f8d00f2a92cf96eec8967a3d550b52cf94362dad1100df9e48d57/send2trash-2.1.0.tar.gz", hash = "sha256:1c72b39f09457db3c05ce1d19158c2cbef4c32b8bedd02c155e49282b7ea7459", size = 17255, upload-time = "2026-01-14T06:27:36.056Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/78/504fdd027da3b84ff1aecd9f6957e65f35134534ccc6da8628eb71e76d3f/send2trash-2.1.0-py3-none-any.whl", hash = "sha256:0da2f112e6d6bb22de6aa6daa7e144831a4febf2a87261451c4ad849fe9a873c", size = 17610 }, + { url = "https://files.pythonhosted.org/packages/1c/78/504fdd027da3b84ff1aecd9f6957e65f35134534ccc6da8628eb71e76d3f/send2trash-2.1.0-py3-none-any.whl", hash = "sha256:0da2f112e6d6bb22de6aa6daa7e144831a4febf2a87261451c4ad849fe9a873c", size = 17610, upload-time = "2026-01-14T06:27:35.218Z" }, ] [[package]] name = "setuptools" -version = "82.0.1" +version = "82.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/f3/748f4d6f65d1756b9ae577f329c951cda23fb900e4de9f70900ced962085/setuptools-82.0.0.tar.gz", hash = "sha256:22e0a2d69474c6ae4feb01951cb69d515ed23728cf96d05513d36e42b62b37cb", size = 1144893, upload-time = "2026-02-08T15:08:40.206Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" }, + { url = "https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl", hash = "sha256:70b18734b607bd1da571d097d236cfcfacaf01de45717d59e6e04b96877532e0", size = 1003468, upload-time = "2026-02-08T15:08:38.723Z" }, ] [[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 } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] [[package]] name = "soupsieve" version = "2.8.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627 } +sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016 }, + { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, ] [[package]] name = "sqlalchemy" -version = "2.0.48" +version = "2.0.47" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1f/73/b4a9737255583b5fa858e0bb8e116eb94b88c910164ed2ed719147bde3de/sqlalchemy-2.0.48.tar.gz", hash = "sha256:5ca74f37f3369b45e1f6b7b06afb182af1fd5dde009e4ffd831830d98cbe5fe7", size = 9886075, upload-time = "2026-03-02T15:28:51.474Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/6d/b8b78b5b80f3c3ab3f7fa90faa195ec3401f6d884b60221260fd4d51864c/sqlalchemy-2.0.48-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b4c575df7368b3b13e0cebf01d4679f9a28ed2ae6c1cd0b1d5beffb6b2007dc", size = 2157184, upload-time = "2026-03-02T15:38:28.161Z" }, - { url = "https://files.pythonhosted.org/packages/21/4b/4f3d4a43743ab58b95b9ddf5580a265b593d017693df9e08bd55780af5bb/sqlalchemy-2.0.48-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e83e3f959aaa1c9df95c22c528096d94848a1bc819f5d0ebf7ee3df0ca63db6c", size = 3313555, upload-time = "2026-03-02T15:58:57.21Z" }, - { url = "https://files.pythonhosted.org/packages/21/dd/3b7c53f1dbbf736fd27041aee68f8ac52226b610f914085b1652c2323442/sqlalchemy-2.0.48-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f7b7243850edd0b8b97043f04748f31de50cf426e939def5c16bedb540698f7", size = 3313057, upload-time = "2026-03-02T15:52:29.366Z" }, - { url = "https://files.pythonhosted.org/packages/d9/cc/3e600a90ae64047f33313d7d32e5ad025417f09d2ded487e8284b5e21a15/sqlalchemy-2.0.48-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:82745b03b4043e04600a6b665cb98697c4339b24e34d74b0a2ac0a2488b6f94d", size = 3265431, upload-time = "2026-03-02T15:58:59.096Z" }, - { url = "https://files.pythonhosted.org/packages/8b/19/780138dacfe3f5024f4cf96e4005e91edf6653d53d3673be4844578faf1d/sqlalchemy-2.0.48-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e5e088bf43f6ee6fec7dbf1ef7ff7774a616c236b5c0cb3e00662dd71a56b571", size = 3287646, upload-time = "2026-03-02T15:52:31.569Z" }, - { url = "https://files.pythonhosted.org/packages/40/fd/f32ced124f01a23151f4777e4c705f3a470adc7bd241d9f36a7c941a33bf/sqlalchemy-2.0.48-cp311-cp311-win32.whl", hash = "sha256:9c7d0a77e36b5f4b01ca398482230ab792061d243d715299b44a0b55c89fe617", size = 2116956, upload-time = "2026-03-02T15:46:54.535Z" }, - { url = "https://files.pythonhosted.org/packages/58/d5/dd767277f6feef12d05651538f280277e661698f617fa4d086cce6055416/sqlalchemy-2.0.48-cp311-cp311-win_amd64.whl", hash = "sha256:583849c743e0e3c9bb7446f5b5addeacedc168d657a69b418063dfdb2d90081c", size = 2141627, upload-time = "2026-03-02T15:46:55.849Z" }, - { url = "https://files.pythonhosted.org/packages/ef/91/a42ae716f8925e9659df2da21ba941f158686856107a61cc97a95e7647a3/sqlalchemy-2.0.48-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:348174f228b99f33ca1f773e85510e08927620caa59ffe7803b37170df30332b", size = 2155737, upload-time = "2026-03-02T15:49:13.207Z" }, - { url = "https://files.pythonhosted.org/packages/b9/52/f75f516a1f3888f027c1cfb5d22d4376f4b46236f2e8669dcb0cddc60275/sqlalchemy-2.0.48-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53667b5f668991e279d21f94ccfa6e45b4e3f4500e7591ae59a8012d0f010dcb", size = 3337020, upload-time = "2026-03-02T15:50:34.547Z" }, - { url = "https://files.pythonhosted.org/packages/37/9a/0c28b6371e0cdcb14f8f1930778cb3123acfcbd2c95bb9cf6b4a2ba0cce3/sqlalchemy-2.0.48-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34634e196f620c7a61d18d5cf7dc841ca6daa7961aed75d532b7e58b309ac894", size = 3349983, upload-time = "2026-03-02T15:53:25.542Z" }, - { url = "https://files.pythonhosted.org/packages/1c/46/0aee8f3ff20b1dcbceb46ca2d87fcc3d48b407925a383ff668218509d132/sqlalchemy-2.0.48-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:546572a1793cc35857a2ffa1fe0e58571af1779bcc1ffa7c9fb0839885ed69a9", size = 3279690, upload-time = "2026-03-02T15:50:36.277Z" }, - { url = "https://files.pythonhosted.org/packages/ce/8c/a957bc91293b49181350bfd55e6dfc6e30b7f7d83dc6792d72043274a390/sqlalchemy-2.0.48-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:07edba08061bc277bfdc772dd2a1a43978f5a45994dd3ede26391b405c15221e", size = 3314738, upload-time = "2026-03-02T15:53:27.519Z" }, - { url = "https://files.pythonhosted.org/packages/4b/44/1d257d9f9556661e7bdc83667cc414ba210acfc110c82938cb3611eea58f/sqlalchemy-2.0.48-cp312-cp312-win32.whl", hash = "sha256:908a3fa6908716f803b86896a09a2c4dde5f5ce2bb07aacc71ffebb57986ce99", size = 2115546, upload-time = "2026-03-02T15:54:31.591Z" }, - { url = "https://files.pythonhosted.org/packages/f2/af/c3c7e1f3a2b383155a16454df62ae8c62a30dd238e42e68c24cebebbfae6/sqlalchemy-2.0.48-cp312-cp312-win_amd64.whl", hash = "sha256:68549c403f79a8e25984376480959975212a670405e3913830614432b5daa07a", size = 2142484, upload-time = "2026-03-02T15:54:34.072Z" }, - { url = "https://files.pythonhosted.org/packages/d1/c6/569dc8bf3cd375abc5907e82235923e986799f301cd79a903f784b996fca/sqlalchemy-2.0.48-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e3070c03701037aa418b55d36532ecb8f8446ed0135acb71c678dbdf12f5b6e4", size = 2152599, upload-time = "2026-03-02T15:49:14.41Z" }, - { url = "https://files.pythonhosted.org/packages/6d/ff/f4e04a4bd5a24304f38cb0d4aa2ad4c0fb34999f8b884c656535e1b2b74c/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2645b7d8a738763b664a12a1542c89c940daa55196e8d73e55b169cc5c99f65f", size = 3278825, upload-time = "2026-03-02T15:50:38.269Z" }, - { url = "https://files.pythonhosted.org/packages/fe/88/cb59509e4668d8001818d7355d9995be90c321313078c912420603a7cb95/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b19151e76620a412c2ac1c6f977ab1b9fa7ad43140178345136456d5265b32ed", size = 3295200, upload-time = "2026-03-02T15:53:29.366Z" }, - { url = "https://files.pythonhosted.org/packages/87/dc/1609a4442aefd750ea2f32629559394ec92e89ac1d621a7f462b70f736ff/sqlalchemy-2.0.48-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b193a7e29fd9fa56e502920dca47dffe60f97c863494946bd698c6058a55658", size = 3226876, upload-time = "2026-03-02T15:50:39.802Z" }, - { url = "https://files.pythonhosted.org/packages/37/c3/6ae2ab5ea2fa989fbac4e674de01224b7a9d744becaf59bb967d62e99bed/sqlalchemy-2.0.48-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:36ac4ddc3d33e852da9cb00ffb08cea62ca05c39711dc67062ca2bb1fae35fd8", size = 3265045, upload-time = "2026-03-02T15:53:31.421Z" }, - { url = "https://files.pythonhosted.org/packages/6f/82/ea4665d1bb98c50c19666e672f21b81356bd6077c4574e3d2bbb84541f53/sqlalchemy-2.0.48-cp313-cp313-win32.whl", hash = "sha256:389b984139278f97757ea9b08993e7b9d1142912e046ab7d82b3fbaeb0209131", size = 2113700, upload-time = "2026-03-02T15:54:35.825Z" }, - { url = "https://files.pythonhosted.org/packages/b7/2b/b9040bec58c58225f073f5b0c1870defe1940835549dafec680cbd58c3c3/sqlalchemy-2.0.48-cp313-cp313-win_amd64.whl", hash = "sha256:d612c976cbc2d17edfcc4c006874b764e85e990c29ce9bd411f926bbfb02b9a2", size = 2139487, upload-time = "2026-03-02T15:54:37.079Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f4/7b17bd50244b78a49d22cc63c969d71dc4de54567dc152a9b46f6fae40ce/sqlalchemy-2.0.48-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69f5bc24904d3bc3640961cddd2523e361257ef68585d6e364166dfbe8c78fae", size = 3558851, upload-time = "2026-03-02T15:57:48.607Z" }, - { url = "https://files.pythonhosted.org/packages/20/0d/213668e9aca61d370f7d2a6449ea4ec699747fac67d4bda1bb3d129025be/sqlalchemy-2.0.48-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd08b90d211c086181caed76931ecfa2bdfc83eea3cfccdb0f82abc6c4b876cb", size = 3525525, upload-time = "2026-03-02T16:04:38.058Z" }, - { url = "https://files.pythonhosted.org/packages/85/d7/a84edf412979e7d59c69b89a5871f90a49228360594680e667cb2c46a828/sqlalchemy-2.0.48-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1ccd42229aaac2df431562117ac7e667d702e8e44afdb6cf0e50fa3f18160f0b", size = 3466611, upload-time = "2026-03-02T15:57:50.759Z" }, - { url = "https://files.pythonhosted.org/packages/86/55/42404ce5770f6be26a2b0607e7866c31b9a4176c819e9a7a5e0a055770be/sqlalchemy-2.0.48-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0dcbc588cd5b725162c076eb9119342f6579c7f7f55057bb7e3c6ff27e13121", size = 3475812, upload-time = "2026-03-02T16:04:40.092Z" }, - { url = "https://files.pythonhosted.org/packages/ae/ae/29b87775fadc43e627cf582fe3bda4d02e300f6b8f2747c764950d13784c/sqlalchemy-2.0.48-cp313-cp313t-win32.whl", hash = "sha256:9764014ef5e58aab76220c5664abb5d47d5bc858d9debf821e55cfdd0f128485", size = 2141335, upload-time = "2026-03-02T15:52:51.518Z" }, - { url = "https://files.pythonhosted.org/packages/91/44/f39d063c90f2443e5b46ec4819abd3d8de653893aae92df42a5c4f5843de/sqlalchemy-2.0.48-cp313-cp313t-win_amd64.whl", hash = "sha256:e2f35b4cccd9ed286ad62e0a3c3ac21e06c02abc60e20aa51a3e305a30f5fa79", size = 2173095, upload-time = "2026-03-02T15:52:52.79Z" }, - { url = "https://files.pythonhosted.org/packages/f7/b3/f437eaa1cf028bb3c927172c7272366393e73ccd104dcf5b6963f4ab5318/sqlalchemy-2.0.48-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e2d0d88686e3d35a76f3e15a34e8c12d73fc94c1dea1cd55782e695cc14086dd", size = 2154401, upload-time = "2026-03-02T15:49:17.24Z" }, - { url = "https://files.pythonhosted.org/packages/6c/1c/b3abdf0f402aa3f60f0df6ea53d92a162b458fca2321d8f1f00278506402/sqlalchemy-2.0.48-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49b7bddc1eebf011ea5ab722fdbe67a401caa34a350d278cc7733c0e88fecb1f", size = 3274528, upload-time = "2026-03-02T15:50:41.489Z" }, - { url = "https://files.pythonhosted.org/packages/f2/5e/327428a034407651a048f5e624361adf3f9fbac9d0fa98e981e9c6ff2f5e/sqlalchemy-2.0.48-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:426c5ca86415d9b8945c7073597e10de9644802e2ff502b8e1f11a7a2642856b", size = 3279523, upload-time = "2026-03-02T15:53:32.962Z" }, - { url = "https://files.pythonhosted.org/packages/2a/ca/ece73c81a918add0965b76b868b7b5359e068380b90ef1656ee995940c02/sqlalchemy-2.0.48-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:288937433bd44e3990e7da2402fabc44a3c6c25d3704da066b85b89a85474ae0", size = 3224312, upload-time = "2026-03-02T15:50:42.996Z" }, - { url = "https://files.pythonhosted.org/packages/88/11/fbaf1ae91fa4ee43f4fe79661cead6358644824419c26adb004941bdce7c/sqlalchemy-2.0.48-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8183dc57ae7d9edc1346e007e840a9f3d6aa7b7f165203a99e16f447150140d2", size = 3246304, upload-time = "2026-03-02T15:53:34.937Z" }, - { url = "https://files.pythonhosted.org/packages/fa/a8/5fb0deb13930b4f2f698c5541ae076c18981173e27dd00376dbaea7a9c82/sqlalchemy-2.0.48-cp314-cp314-win32.whl", hash = "sha256:1182437cb2d97988cfea04cf6cdc0b0bb9c74f4d56ec3d08b81e23d621a28cc6", size = 2116565, upload-time = "2026-03-02T15:54:38.321Z" }, - { url = "https://files.pythonhosted.org/packages/95/7e/e83615cb63f80047f18e61e31e8e32257d39458426c23006deeaf48f463b/sqlalchemy-2.0.48-cp314-cp314-win_amd64.whl", hash = "sha256:144921da96c08feb9e2b052c5c5c1d0d151a292c6135623c6b2c041f2a45f9e0", size = 2142205, upload-time = "2026-03-02T15:54:39.831Z" }, - { url = "https://files.pythonhosted.org/packages/83/e3/69d8711b3f2c5135e9cde5f063bc1605860f0b2c53086d40c04017eb1f77/sqlalchemy-2.0.48-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5aee45fd2c6c0f2b9cdddf48c48535e7471e42d6fb81adfde801da0bd5b93241", size = 3563519, upload-time = "2026-03-02T15:57:52.387Z" }, - { url = "https://files.pythonhosted.org/packages/f8/4f/a7cce98facca73c149ea4578981594aaa5fd841e956834931de503359336/sqlalchemy-2.0.48-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7cddca31edf8b0653090cbb54562ca027c421c58ddde2c0685f49ff56a1690e0", size = 3528611, upload-time = "2026-03-02T16:04:42.097Z" }, - { url = "https://files.pythonhosted.org/packages/cd/7d/5936c7a03a0b0cb0fa0cc425998821c6029756b0855a8f7ee70fba1de955/sqlalchemy-2.0.48-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7a936f1bb23d370b7c8cc079d5fce4c7d18da87a33c6744e51a93b0f9e97e9b3", size = 3472326, upload-time = "2026-03-02T15:57:54.423Z" }, - { url = "https://files.pythonhosted.org/packages/f4/33/cea7dfc31b52904efe3dcdc169eb4514078887dff1f5ae28a7f4c5d54b3c/sqlalchemy-2.0.48-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e004aa9248e8cb0a5f9b96d003ca7c1c0a5da8decd1066e7b53f59eb8ce7c62b", size = 3478453, upload-time = "2026-03-02T16:04:44.584Z" }, - { url = "https://files.pythonhosted.org/packages/c8/95/32107c4d13be077a9cae61e9ae49966a35dc4bf442a8852dd871db31f62e/sqlalchemy-2.0.48-cp314-cp314t-win32.whl", hash = "sha256:b8438ec5594980d405251451c5b7ea9aa58dda38eb7ac35fb7e4c696712ee24f", size = 2147209, upload-time = "2026-03-02T15:52:54.274Z" }, - { url = "https://files.pythonhosted.org/packages/d2/d7/1e073da7a4bc645eb83c76067284a0374e643bc4be57f14cc6414656f92c/sqlalchemy-2.0.48-cp314-cp314t-win_amd64.whl", hash = "sha256:d854b3970067297f3a7fbd7a4683587134aa9b3877ee15aa29eea478dc68f933", size = 2182198, upload-time = "2026-03-02T15:52:55.606Z" }, - { url = "https://files.pythonhosted.org/packages/46/2c/9664130905f03db57961b8980b05cab624afd114bf2be2576628a9f22da4/sqlalchemy-2.0.48-py3-none-any.whl", hash = "sha256:a66fe406437dd65cacd96a72689a3aaaecaebbcd62d81c5ac1c0fdbeac835096", size = 1940202, upload-time = "2026-03-02T15:52:43.285Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/cd/4b/1e00561093fe2cd8eef09d406da003c8a118ff02d6548498c1ae677d68d9/sqlalchemy-2.0.47.tar.gz", hash = "sha256:e3e7feb57b267fe897e492b9721ae46d5c7de6f9e8dee58aacf105dc4e154f3d", size = 9886323, upload-time = "2026-02-24T16:34:27.947Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/13/886338d3e8ab5ddcfe84d54302c749b1793e16c4bba63d7004e3f7baa8ec/sqlalchemy-2.0.47-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a1dbf0913879c443617d6b64403cf2801c941651db8c60e96d204ed9388d6b0", size = 2157124, upload-time = "2026-02-24T16:43:54.706Z" }, + { url = "https://files.pythonhosted.org/packages/b6/bb/a897f6a66c9986aa9f27f5cf8550637d8a5ea368fd7fb42f6dac3105b4dc/sqlalchemy-2.0.47-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:775effbb97ea3b00c4dd3aeaf3ba8acba6e3e2b4b41d17d67a27e696843dbc95", size = 3313513, upload-time = "2026-02-24T17:29:00.527Z" }, + { url = "https://files.pythonhosted.org/packages/59/fb/69bfae022b681507565ab0d34f0c80aa1e9f954a5a7cbfb0ed054966ac8d/sqlalchemy-2.0.47-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56cc834a3ffac34270cc2a41875e0f40e97aa651f4f3ca1cfbbf421c044cb62b", size = 3313014, upload-time = "2026-02-24T17:27:11.679Z" }, + { url = "https://files.pythonhosted.org/packages/04/f3/0eba329f7c182d53205a228c4fd24651b95489b431ea2bd830887b4c13c4/sqlalchemy-2.0.47-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:49b5e0c7244262f39e767c018e4fdb5e5dbc23cd54c5ddac8eea8f0ba32ef890", size = 3265389, upload-time = "2026-02-24T17:29:02.497Z" }, + { url = "https://files.pythonhosted.org/packages/5c/06/654edc084b3b46ac79e04200d7c46467ae80c759c4ee41c897f9272b036f/sqlalchemy-2.0.47-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:15cd822a3f1f6f77b5b841a30c1a07a07f7dee3385f17e638e1722de9ab683be", size = 3287604, upload-time = "2026-02-24T17:27:13.295Z" }, + { url = "https://files.pythonhosted.org/packages/78/33/c18c8f63b61981219d3aa12321bb7ccee605034d195e868ed94f9727b27c/sqlalchemy-2.0.47-cp311-cp311-win32.whl", hash = "sha256:9847a19548cd283a65e1ce0afd54016598d55ff72682d6fd3e493af6fc044064", size = 2116916, upload-time = "2026-02-24T17:14:37.392Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c6/a59e3f9796fff844e16afbd821db9abfd6e12698db9441a231a96193a100/sqlalchemy-2.0.47-cp311-cp311-win_amd64.whl", hash = "sha256:722abf1c82aeca46a1a0803711244a48a298279eeaec9e02f7bfee9e064182e5", size = 2141587, upload-time = "2026-02-24T17:14:39.746Z" }, + { url = "https://files.pythonhosted.org/packages/80/88/74eb470223ff88ea6572a132c0b8de8c1d8ed7b843d3b44a8a3c77f31d39/sqlalchemy-2.0.47-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4fa91b19d6b9821c04cc8f7aa2476429cc8887b9687c762815aa629f5c0edec1", size = 2155687, upload-time = "2026-02-24T17:05:46.451Z" }, + { url = "https://files.pythonhosted.org/packages/ef/ba/1447d3d558971b036cb93b557595cb5dcdfe728f1c7ac4dec16505ef5756/sqlalchemy-2.0.47-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c5bbbd14eff577c8c79cbfe39a0771eecd20f430f3678533476f0087138f356", size = 3336978, upload-time = "2026-02-24T17:18:04.597Z" }, + { url = "https://files.pythonhosted.org/packages/8a/07/b47472d2ffd0776826f17ccf0b4d01b224c99fbd1904aeb103dffbb4b1cc/sqlalchemy-2.0.47-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5a6c555da8d4280a3c4c78c5b7a3f990cee2b2884e5f934f87a226191682ff7", size = 3349939, upload-time = "2026-02-24T17:27:18.937Z" }, + { url = "https://files.pythonhosted.org/packages/bb/c6/95fa32b79b57769da3e16f054cf658d90940317b5ca0ec20eac84aa19c4f/sqlalchemy-2.0.47-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ed48a1701d24dff3bb49a5bce94d6bc84cbe33d98af2aa2d3cdcce3dea1709ec", size = 3279648, upload-time = "2026-02-24T17:18:07.038Z" }, + { url = "https://files.pythonhosted.org/packages/bb/c8/3d07e7c73928dc59a0bed40961ca4e313e797bce650b088e8d5fdd3ad939/sqlalchemy-2.0.47-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4f3178c920ad98158f0b6309382194df04b14808fa6052ae07099fdde29d5602", size = 3314695, upload-time = "2026-02-24T17:27:20.93Z" }, + { url = "https://files.pythonhosted.org/packages/6b/d2/ed32b1611c1e19fdb028eee1adc5a9aa138c2952d09ae11f1670170f80ae/sqlalchemy-2.0.47-cp312-cp312-win32.whl", hash = "sha256:b9c11ac9934dd59ece9619fe42780a08abe2faab7b0543bb00d5eabea4f421b9", size = 2115502, upload-time = "2026-02-24T17:22:52.546Z" }, + { url = "https://files.pythonhosted.org/packages/fd/52/9de590356a4dd8e9ef5a881dbba64b2bbc4cbc71bf02bc68e775fb9b1899/sqlalchemy-2.0.47-cp312-cp312-win_amd64.whl", hash = "sha256:db43b72cf8274a99e089755c9c1e0b947159b71adbc2c83c3de2e38d5d607acb", size = 2142435, upload-time = "2026-02-24T17:22:54.268Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e5/0af64ce7d8f60ec5328c10084e2f449e7912a9b8bdbefdcfb44454a25f49/sqlalchemy-2.0.47-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:456a135b790da5d3c6b53d0ef71ac7b7d280b7f41eb0c438986352bf03ca7143", size = 2152551, upload-time = "2026-02-24T17:05:47.675Z" }, + { url = "https://files.pythonhosted.org/packages/63/79/746b8d15f6940e2ac469ce22d7aa5b1124b1ab820bad9b046eb3000c88a6/sqlalchemy-2.0.47-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09a2f7698e44b3135433387da5d8846cf7cc7c10e5425af7c05fee609df978b6", size = 3278782, upload-time = "2026-02-24T17:18:10.012Z" }, + { url = "https://files.pythonhosted.org/packages/91/b1/bd793ddb34345d1ed43b13ab2d88c95d7d4eb2e28f5b5a99128b9cc2bca2/sqlalchemy-2.0.47-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0bbc72e6a177c78d724f9106aaddc0d26a2ada89c6332b5935414eccf04cbd5", size = 3295155, upload-time = "2026-02-24T17:27:22.827Z" }, + { url = "https://files.pythonhosted.org/packages/97/84/7213def33f94e5ca6f5718d259bc9f29de0363134648425aa218d4356b23/sqlalchemy-2.0.47-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:75460456b043b78b6006e41bdf5b86747ee42eafaf7fffa3b24a6e9a456a2092", size = 3226834, upload-time = "2026-02-24T17:18:11.465Z" }, + { url = "https://files.pythonhosted.org/packages/ef/06/456810204f4dc29b5f025b1b0a03b4bd6b600ebf3c1040aebd90a257fa33/sqlalchemy-2.0.47-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5d9adaa616c3bc7d80f9ded57cd84b51d6617cad6a5456621d858c9f23aaee01", size = 3265001, upload-time = "2026-02-24T17:27:24.813Z" }, + { url = "https://files.pythonhosted.org/packages/fb/20/df3920a4b2217dbd7390a5bd277c1902e0393f42baaf49f49b3c935e7328/sqlalchemy-2.0.47-cp313-cp313-win32.whl", hash = "sha256:76e09f974382a496a5ed985db9343628b1cb1ac911f27342e4cc46a8bac10476", size = 2113647, upload-time = "2026-02-24T17:22:55.747Z" }, + { url = "https://files.pythonhosted.org/packages/46/06/7873ddf69918efbfabd7211829f4bd8019739d0a719253112d305d3ba51d/sqlalchemy-2.0.47-cp313-cp313-win_amd64.whl", hash = "sha256:0664089b0bf6724a0bfb49a0cf4d4da24868a0a5c8e937cd7db356d5dcdf2c66", size = 2139425, upload-time = "2026-02-24T17:22:57.033Z" }, + { url = "https://files.pythonhosted.org/packages/54/fa/61ad9731370c90ac7ea5bf8f5eaa12c48bb4beec41c0fa0360becf4ac10d/sqlalchemy-2.0.47-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed0c967c701ae13da98eb220f9ddab3044ab63504c1ba24ad6a59b26826ad003", size = 3558809, upload-time = "2026-02-24T17:12:15.232Z" }, + { url = "https://files.pythonhosted.org/packages/33/d5/221fac96f0529391fe374875633804c866f2b21a9c6d3a6ca57d9c12cfd7/sqlalchemy-2.0.47-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3537943a61fd25b241e976426a0c6814434b93cf9b09d39e8e78f3c9eb9a487", size = 3525480, upload-time = "2026-02-24T17:27:59.602Z" }, + { url = "https://files.pythonhosted.org/packages/ec/55/8247d53998c3673e4a8d1958eba75c6f5cc3b39082029d400bb1f2a911ae/sqlalchemy-2.0.47-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:57f7e336a64a0dba686c66392d46b9bc7af2c57d55ce6dc1697b4ef32b043ceb", size = 3466569, upload-time = "2026-02-24T17:12:16.94Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b5/c1f0eea1bac6790845f71420a7fe2f2a0566203aa57543117d4af3b77d1c/sqlalchemy-2.0.47-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dff735a621858680217cb5142b779bad40ef7322ddbb7c12062190db6879772e", size = 3475770, upload-time = "2026-02-24T17:28:02.034Z" }, + { url = "https://files.pythonhosted.org/packages/c5/ed/2f43f92474ea0c43c204657dc47d9d002cd738b96ca2af8e6d29a9b5e42d/sqlalchemy-2.0.47-cp313-cp313t-win32.whl", hash = "sha256:3893dc096bb3cca9608ea3487372ffcea3ae9b162f40e4d3c51dd49db1d1b2dc", size = 2141300, upload-time = "2026-02-24T17:14:37.024Z" }, + { url = "https://files.pythonhosted.org/packages/cc/a9/8b73f9f1695b6e92f7aaf1711135a1e3bbeb78bca9eded35cb79180d3c6d/sqlalchemy-2.0.47-cp313-cp313t-win_amd64.whl", hash = "sha256:b5103427466f4b3e61f04833ae01f9a914b1280a2a8bcde3a9d7ab11f3755b42", size = 2173053, upload-time = "2026-02-24T17:14:38.688Z" }, + { url = "https://files.pythonhosted.org/packages/c1/30/98243209aae58ed80e090ea988d5182244ca7ab3ff59e6d850c3dfc7651e/sqlalchemy-2.0.47-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b03010a5a5dfe71676bc83f2473ebe082478e32d77e6f082c8fe15a31c3b42a6", size = 2154355, upload-time = "2026-02-24T17:05:48.959Z" }, + { url = "https://files.pythonhosted.org/packages/ab/62/12ca6ea92055fe486d6558a2a4efe93e194ff597463849c01f88e5adb99d/sqlalchemy-2.0.47-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8e3371aa9024520883a415a09cc20c33cfd3eeccf9e0f4f4c367f940b9cbd44", size = 3274486, upload-time = "2026-02-24T17:18:13.659Z" }, + { url = "https://files.pythonhosted.org/packages/97/88/7dfbdeaa8d42b1584e65d6cc713e9d33b6fa563e0d546d5cb87e545bb0e5/sqlalchemy-2.0.47-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9449f747e50d518c6e1b40cc379e48bfc796453c47b15e627ea901c201e48a6", size = 3279481, upload-time = "2026-02-24T17:27:26.491Z" }, + { url = "https://files.pythonhosted.org/packages/d0/b7/75e1c1970616a9dd64a8a6fd788248da2ddaf81c95f4875f2a1e8aee4128/sqlalchemy-2.0.47-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:21410f60d5cac1d6bfe360e05bd91b179be4fa0aa6eea6be46054971d277608f", size = 3224269, upload-time = "2026-02-24T17:18:15.078Z" }, + { url = "https://files.pythonhosted.org/packages/31/ac/eec1a13b891df9a8bc203334caf6e6aac60b02f61b018ef3b4124b8c4120/sqlalchemy-2.0.47-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:819841dd5bb4324c284c09e2874cf96fe6338bfb57a64548d9b81a4e39c9871f", size = 3246262, upload-time = "2026-02-24T17:27:27.986Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b0/661b0245b06421058610da39f8ceb34abcc90b49f90f256380968d761dbe/sqlalchemy-2.0.47-cp314-cp314-win32.whl", hash = "sha256:e255ee44821a7ef45649c43064cf94e74f81f61b4df70547304b97a351e9b7db", size = 2116528, upload-time = "2026-02-24T17:22:59.363Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ef/1035a90d899e61810791c052004958be622a2cf3eb3df71c3fe20778c5d0/sqlalchemy-2.0.47-cp314-cp314-win_amd64.whl", hash = "sha256:209467ff73ea1518fe1a5aaed9ba75bb9e33b2666e2553af9ccd13387bf192cb", size = 2142181, upload-time = "2026-02-24T17:23:01.001Z" }, + { url = "https://files.pythonhosted.org/packages/76/bb/17a1dd09cbba91258218ceb582225f14b5364d2683f9f5a274f72f2d764f/sqlalchemy-2.0.47-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e78fd9186946afaa287f8a1fe147ead06e5d566b08c0afcb601226e9c7322a64", size = 3563477, upload-time = "2026-02-24T17:12:18.46Z" }, + { url = "https://files.pythonhosted.org/packages/66/8f/1a03d24c40cc321ef2f2231f05420d140bb06a84f7047eaa7eaa21d230ba/sqlalchemy-2.0.47-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5740e2f31b5987ed9619d6912ae5b750c03637f2078850da3002934c9532f172", size = 3528568, upload-time = "2026-02-24T17:28:03.732Z" }, + { url = "https://files.pythonhosted.org/packages/fd/53/d56a213055d6b038a5384f0db5ece7343334aca230ff3f0fa1561106f22c/sqlalchemy-2.0.47-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb9ac00d03de93acb210e8ec7243fefe3e012515bf5fd2f0898c8dff38bc77a4", size = 3472284, upload-time = "2026-02-24T17:12:20.319Z" }, + { url = "https://files.pythonhosted.org/packages/ff/19/c235d81b9cfdd6130bf63143b7bade0dc4afa46c4b634d5d6b2a96bea233/sqlalchemy-2.0.47-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c72a0b9eb2672d70d112cb149fbaf172d466bc691014c496aaac594f1988e706", size = 3478410, upload-time = "2026-02-24T17:28:05.892Z" }, + { url = "https://files.pythonhosted.org/packages/0e/db/cafdeca5ecdaa3bb0811ba5449501da677ce0d83be8d05c5822da72d2e86/sqlalchemy-2.0.47-cp314-cp314t-win32.whl", hash = "sha256:c200db1128d72a71dc3c31c24b42eb9fd85b2b3e5a3c9ba1e751c11ac31250ff", size = 2147164, upload-time = "2026-02-24T17:14:40.783Z" }, + { url = "https://files.pythonhosted.org/packages/fc/5e/ff41a010e9e0f76418b02ad352060a4341bb15f0af66cedc924ab376c7c6/sqlalchemy-2.0.47-cp314-cp314t-win_amd64.whl", hash = "sha256:669837759b84e575407355dcff912835892058aea9b80bd1cb76d6a151cf37f7", size = 2182154, upload-time = "2026-02-24T17:14:43.205Z" }, + { url = "https://files.pythonhosted.org/packages/15/9f/7c378406b592fcf1fc157248607b495a40e3202ba4a6f1372a2ba6447717/sqlalchemy-2.0.47-py3-none-any.whl", hash = "sha256:e2647043599297a1ef10e720cf310846b7f31b6c841fee093d2b09d81215eb93", size = 1940159, upload-time = "2026-02-24T17:15:07.158Z" }, ] [[package]] @@ -3376,9 +3282,9 @@ dependencies = [ { name = "executing" }, { name = "pure-eval" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707 } +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521 }, + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, ] [[package]] @@ -3388,9 +3294,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mpmath" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921 } +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353 }, + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, ] [[package]] @@ -3410,7 +3316,7 @@ dependencies = [ { name = "werkzeug" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/d9/a5db55f88f258ac669a92858b70a714bbbd5acd993820b41ec4a96a4d77f/tensorboard-2.20.0-py3-none-any.whl", hash = "sha256:9dc9f978cb84c0723acf9a345d96c184f0293d18f166bb8d59ee098e6cfaaba6", size = 5525680 }, + { url = "https://files.pythonhosted.org/packages/9c/d9/a5db55f88f258ac669a92858b70a714bbbd5acd993820b41ec4a96a4d77f/tensorboard-2.20.0-py3-none-any.whl", hash = "sha256:9dc9f978cb84c0723acf9a345d96c184f0293d18f166bb8d59ee098e6cfaaba6", size = 5525680, upload-time = "2025-07-17T19:20:49.638Z" }, ] [[package]] @@ -3418,9 +3324,9 @@ name = "tensorboard-data-server" version = "0.7.2" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/13/e503968fefabd4c6b2650af21e110aa8466fe21432cd7c43a84577a89438/tensorboard_data_server-0.7.2-py3-none-any.whl", hash = "sha256:7e0610d205889588983836ec05dc098e80f97b7e7bbff7e994ebb78f578d0ddb", size = 2356 }, - { url = "https://files.pythonhosted.org/packages/b7/85/dabeaf902892922777492e1d253bb7e1264cadce3cea932f7ff599e53fea/tensorboard_data_server-0.7.2-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:9fe5d24221b29625dbc7328b0436ca7fc1c23de4acf4d272f1180856e32f9f60", size = 4823598 }, - { url = "https://files.pythonhosted.org/packages/73/c6/825dab04195756cf8ff2e12698f22513b3db2f64925bdd41671bfb33aaa5/tensorboard_data_server-0.7.2-py3-none-manylinux_2_31_x86_64.whl", hash = "sha256:ef687163c24185ae9754ed5650eb5bc4d84ff257aabdc33f0cc6f74d8ba54530", size = 6590363 }, + { url = "https://files.pythonhosted.org/packages/7a/13/e503968fefabd4c6b2650af21e110aa8466fe21432cd7c43a84577a89438/tensorboard_data_server-0.7.2-py3-none-any.whl", hash = "sha256:7e0610d205889588983836ec05dc098e80f97b7e7bbff7e994ebb78f578d0ddb", size = 2356, upload-time = "2023-10-23T21:23:32.16Z" }, + { url = "https://files.pythonhosted.org/packages/b7/85/dabeaf902892922777492e1d253bb7e1264cadce3cea932f7ff599e53fea/tensorboard_data_server-0.7.2-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:9fe5d24221b29625dbc7328b0436ca7fc1c23de4acf4d272f1180856e32f9f60", size = 4823598, upload-time = "2023-10-23T21:23:33.714Z" }, + { url = "https://files.pythonhosted.org/packages/73/c6/825dab04195756cf8ff2e12698f22513b3db2f64925bdd41671bfb33aaa5/tensorboard_data_server-0.7.2-py3-none-manylinux_2_31_x86_64.whl", hash = "sha256:ef687163c24185ae9754ed5650eb5bc4d84ff257aabdc33f0cc6f74d8ba54530", size = 6590363, upload-time = "2023-10-23T21:23:35.583Z" }, ] [[package]] @@ -3432,21 +3338,21 @@ dependencies = [ { name = "pywinpty", marker = "os_name == 'nt'" }, { name = "tornado" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8a/11/965c6fd8e5cc254f1fe142d547387da17a8ebfd75a3455f637c663fb38a0/terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e", size = 32701 } +sdist = { url = "https://files.pythonhosted.org/packages/8a/11/965c6fd8e5cc254f1fe142d547387da17a8ebfd75a3455f637c663fb38a0/terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e", size = 32701, upload-time = "2024-03-12T14:34:39.026Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0", size = 14154 }, + { url = "https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0", size = 14154, upload-time = "2024-03-12T14:34:36.569Z" }, ] [[package]] name = "tifffile" -version = "2026.3.3" +version = "2026.2.24" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c5/cb/2f6d79c7576e22c116352a801f4c3c8ace5957e9aced862012430b62e14f/tifffile-2026.3.3.tar.gz", hash = "sha256:d9a1266bed6f2ee1dd0abde2018a38b4f8b2935cb843df381d70ac4eac5458b7", size = 388745, upload-time = "2026-03-03T19:14:38.134Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/1c/19fc653e2b05ec0defae511b03b330ca60c95f2c47fcaaf21c52c6e84aa8/tifffile-2026.2.24.tar.gz", hash = "sha256:d73cfa6d7a8f5775a1e3c9f3bfca77c992946639fb41a5bbe888878cb6964dc6", size = 387373, upload-time = "2026-02-24T23:59:11.706Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/e4/e804505f87627cd8cdae9c010c47c4485fd8c1ce31a7dd0ab7fcc4707377/tifffile-2026.3.3-py3-none-any.whl", hash = "sha256:e8be15c94273113d31ecb7aa3a39822189dd11c4967e3cc88c178f1ad2fd1170", size = 243960, upload-time = "2026-03-03T19:14:35.808Z" }, + { url = "https://files.pythonhosted.org/packages/ee/fe/80250dc06cd4a3a5afe7059875a8d53e97a78528c5dd9ea8c3f981fb897a/tifffile-2026.2.24-py3-none-any.whl", hash = "sha256:38ef6258c2bd8dd3551c7480c6d75a36c041616262e6cd55a50dd16046b71863", size = 243223, upload-time = "2026-02-24T23:59:10.131Z" }, ] [[package]] @@ -3456,104 +3362,108 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "webencodings" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085 } +sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085, upload-time = "2024-10-24T14:58:29.895Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610 }, + { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610, upload-time = "2024-10-24T14:58:28.029Z" }, ] [[package]] name = "tomli" version = "2.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663 }, - { url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469 }, - { url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039 }, - { url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007 }, - { url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875 }, - { url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271 }, - { url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770 }, - { url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626 }, - { url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842 }, - { url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894 }, - { url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053 }, - { url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481 }, - { url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720 }, - { url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014 }, - { url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820 }, - { url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712 }, - { url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296 }, - { url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553 }, - { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915 }, - { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038 }, - { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245 }, - { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335 }, - { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962 }, - { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396 }, - { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530 }, - { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227 }, - { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748 }, - { url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725 }, - { url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901 }, - { url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375 }, - { url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639 }, - { url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897 }, - { url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697 }, - { url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567 }, - { url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556 }, - { url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014 }, - { url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339 }, - { url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490 }, - { url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398 }, - { url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515 }, - { url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806 }, - { url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340 }, - { url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106 }, - { url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504 }, - { url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561 }, - { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477 }, +sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" }, + { url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" }, + { url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" }, + { url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" }, + { url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" }, + { url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" }, + { url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" }, + { url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" }, + { url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" }, + { url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" }, + { url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" }, + { url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" }, + { url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" }, + { url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" }, + { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" }, + { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" }, + { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" }, + { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" }, + { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" }, + { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" }, + { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" }, + { url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" }, + { url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" }, + { url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" }, + { url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" }, + { url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" }, + { url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" }, + { url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" }, + { url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" }, + { url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" }, + { url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" }, + { url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" }, + { url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" }, + { url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" }, + { url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" }, + { url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" }, + { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" }, ] [[package]] name = "toolz" version = "1.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/11/d6/114b492226588d6ff54579d95847662fc69196bdeec318eb45393b24c192/toolz-1.1.0.tar.gz", hash = "sha256:27a5c770d068c110d9ed9323f24f1543e83b2f300a687b7891c1a6d56b697b5b", size = 52613 } +sdist = { url = "https://files.pythonhosted.org/packages/11/d6/114b492226588d6ff54579d95847662fc69196bdeec318eb45393b24c192/toolz-1.1.0.tar.gz", hash = "sha256:27a5c770d068c110d9ed9323f24f1543e83b2f300a687b7891c1a6d56b697b5b", size = 52613, upload-time = "2025-10-17T04:03:21.661Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl", hash = "sha256:15ccc861ac51c53696de0a5d6d4607f99c210739caf987b5d2054f3efed429d8", size = 58093 }, + { url = "https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl", hash = "sha256:15ccc861ac51c53696de0a5d6d4607f99c210739caf987b5d2054f3efed429d8", size = 58093, upload-time = "2025-10-17T04:03:20.435Z" }, ] [[package]] name = "torch" -version = "2.11.0" +version = "2.10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cuda-bindings", marker = "sys_platform == 'linux'" }, - { name = "cuda-toolkit", extra = ["cublas", "cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"], marker = "sys_platform == 'linux'" }, + { name = "cuda-bindings", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "filelock" }, { name = "fsspec" }, { name = "jinja2" }, { name = "networkx" }, - { name = "nvidia-cudnn-cu13", marker = "sys_platform == 'linux'" }, - { name = "nvidia-cusparselt-cu13", marker = "sys_platform == 'linux'" }, - { name = "nvidia-nccl-cu13", marker = "sys_platform == 'linux'" }, - { name = "nvidia-nvshmem-cu13", marker = "sys_platform == 'linux'" }, - { name = "setuptools" }, + { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cufile-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusparselt-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nvshmem-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "setuptools", marker = "python_full_version >= '3.12'" }, { name = "sympy" }, - { name = "triton", marker = "sys_platform == 'linux'" }, + { name = "triton", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "typing-extensions" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/0f/8b/4b61d6e13f7108f36910df9ab4b58fd389cc2520d54d81b88660804aad99/torch-2.10.0-2-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:418997cb02d0a0f1497cf6a09f63166f9f5df9f3e16c8a716ab76a72127c714f", size = 79423467, upload-time = "2026-02-10T21:44:48.711Z" }, { url = "https://files.pythonhosted.org/packages/d3/54/a2ba279afcca44bbd320d4e73675b282fcee3d81400ea1b53934efca6462/torch-2.10.0-2-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:13ec4add8c3faaed8d13e0574f5cd4a323c11655546f91fbe6afa77b57423574", size = 79498202, upload-time = "2026-02-10T21:44:52.603Z" }, { url = "https://files.pythonhosted.org/packages/ec/23/2c9fe0c9c27f7f6cb865abcea8a4568f29f00acaeadfc6a37f6801f84cb4/torch-2.10.0-2-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:e521c9f030a3774ed770a9c011751fb47c4d12029a3d6522116e48431f2ff89e", size = 79498254, upload-time = "2026-02-10T21:44:44.095Z" }, - { url = "https://files.pythonhosted.org/packages/36/ab/7b562f1808d3f65414cd80a4f7d4bb00979d9355616c034c171249e1a303/torch-2.10.0-3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:ac5bdcbb074384c66fa160c15b1ead77839e3fe7ed117d667249afce0acabfac", size = 915518691, upload-time = "2026-03-11T14:15:43.147Z" }, - { url = "https://files.pythonhosted.org/packages/b3/7a/abada41517ce0011775f0f4eacc79659bc9bc6c361e6bfe6f7052a6b9363/torch-2.10.0-3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:98c01b8bb5e3240426dcde1446eed6f40c778091c8544767ef1168fc663a05a6", size = 915622781, upload-time = "2026-03-11T14:17:11.354Z" }, - { url = "https://files.pythonhosted.org/packages/ab/c6/4dfe238342ffdcec5aef1c96c457548762d33c40b45a1ab7033bb26d2ff2/torch-2.10.0-3-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:80b1b5bfe38eb0e9f5ff09f206dcac0a87aadd084230d4a36eea5ec5232c115b", size = 915627275, upload-time = "2026-03-11T14:16:11.325Z" }, - { url = "https://files.pythonhosted.org/packages/d8/f0/72bf18847f58f877a6a8acf60614b14935e2f156d942483af1ffc081aea0/torch-2.10.0-3-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:46b3574d93a2a8134b3f5475cfb98e2eb46771794c57015f6ad1fb795ec25e49", size = 915523474, upload-time = "2026-03-11T14:17:44.422Z" }, - { url = "https://files.pythonhosted.org/packages/f4/39/590742415c3030551944edc2ddc273ea1fdfe8ffb2780992e824f1ebee98/torch-2.10.0-3-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:b1d5e2aba4eb7f8e87fbe04f86442887f9167a35f092afe4c237dfcaaef6e328", size = 915632474, upload-time = "2026-03-11T14:15:13.666Z" }, - { url = "https://files.pythonhosted.org/packages/b6/8e/34949484f764dde5b222b7fe3fede43e4a6f0da9d7f8c370bb617d629ee2/torch-2.10.0-3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:0228d20b06701c05a8f978357f657817a4a63984b0c90745def81c18aedfa591", size = 915523882, upload-time = "2026-03-11T14:14:46.311Z" }, { url = "https://files.pythonhosted.org/packages/78/89/f5554b13ebd71e05c0b002f95148033e730d3f7067f67423026cc9c69410/torch-2.10.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:3282d9febd1e4e476630a099692b44fdc214ee9bf8ee5377732d9d9dfe5712e4", size = 145992610, upload-time = "2026-01-21T16:25:26.327Z" }, { url = "https://files.pythonhosted.org/packages/ae/30/a3a2120621bf9c17779b169fc17e3dc29b230c29d0f8222f499f5e159aa8/torch-2.10.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a2f9edd8dbc99f62bc4dfb78af7bf89499bca3d753423ac1b4e06592e467b763", size = 915607863, upload-time = "2026-01-21T16:25:06.696Z" }, { url = "https://files.pythonhosted.org/packages/6f/3d/c87b33c5f260a2a8ad68da7147e105f05868c281c63d65ed85aa4da98c66/torch-2.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:29b7009dba4b7a1c960260fc8ac85022c784250af43af9fb0ebafc9883782ebd", size = 113723116, upload-time = "2026-01-21T16:25:21.916Z" }, @@ -3584,14 +3494,14 @@ wheels = [ name = "torchinfo" version = "1.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/d9/2b811d1c0812e9ef23e6cf2dbe022becbe6c5ab065e33fd80ee05c0cd996/torchinfo-1.8.0.tar.gz", hash = "sha256:72e94b0e9a3e64dc583a8e5b7940b8938a1ac0f033f795457f27e6f4e7afa2e9", size = 25880 } +sdist = { url = "https://files.pythonhosted.org/packages/53/d9/2b811d1c0812e9ef23e6cf2dbe022becbe6c5ab065e33fd80ee05c0cd996/torchinfo-1.8.0.tar.gz", hash = "sha256:72e94b0e9a3e64dc583a8e5b7940b8938a1ac0f033f795457f27e6f4e7afa2e9", size = 25880, upload-time = "2023-05-14T19:23:26.377Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/72/25/973bd6128381951b23cdcd8a9870c6dcfc5606cb864df8eabd82e529f9c1/torchinfo-1.8.0-py3-none-any.whl", hash = "sha256:2e911c2918603f945c26ff21a3a838d12709223dc4ccf243407bce8b6e897b46", size = 23377 }, + { url = "https://files.pythonhosted.org/packages/72/25/973bd6128381951b23cdcd8a9870c6dcfc5606cb864df8eabd82e529f9c1/torchinfo-1.8.0-py3-none-any.whl", hash = "sha256:2e911c2918603f945c26ff21a3a838d12709223dc4ccf243407bce8b6e897b46", size = 23377, upload-time = "2023-05-14T19:23:24.141Z" }, ] [[package]] name = "torchmetrics" -version = "1.9.0" +version = "1.8.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "lightning-utilities" }, @@ -3599,14 +3509,14 @@ dependencies = [ { name = "packaging" }, { name = "torch" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/81/34/39b8b749333db56c0585d7a11fa62a283c087bb1dfc897d69fb8cedbefb1/torchmetrics-1.9.0.tar.gz", hash = "sha256:a488609948600df52d3db4fcdab02e62aab2a85ef34da67037dc3e65b8512faa", size = 581765, upload-time = "2026-03-09T17:41:22.443Z" } +sdist = { url = "https://files.pythonhosted.org/packages/85/2e/48a887a59ecc4a10ce9e8b35b3e3c5cef29d902c4eac143378526e7485cb/torchmetrics-1.8.2.tar.gz", hash = "sha256:cf64a901036bf107f17a524009eea7781c9c5315d130713aeca5747a686fe7a5", size = 580679, upload-time = "2025-09-03T14:00:54.077Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/a2/c7f6ebf546f8f644edf0f999aa98ece106986a77a7b922316bf6414ff825/torchmetrics-1.9.0-py3-none-any.whl", hash = "sha256:bfdcbff3dd1d96b3374bb2496eb39f23c4b28b8a845b6a18c313688e0d2d9ca1", size = 983384, upload-time = "2026-03-09T17:41:19.756Z" }, + { url = "https://files.pythonhosted.org/packages/02/21/aa0f434434c48490f91b65962b1ce863fdcce63febc166ca9fe9d706c2b6/torchmetrics-1.8.2-py3-none-any.whl", hash = "sha256:08382fd96b923e39e904c4d570f3d49e2cc71ccabd2a94e0f895d1f0dac86242", size = 983161, upload-time = "2025-09-03T14:00:51.921Z" }, ] [[package]] name = "torchvision" -version = "0.26.0" +version = "0.25.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, @@ -3614,47 +3524,49 @@ dependencies = [ { name = "torch" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/bd/d552a2521bade3295b2c6e7a4a0d1022261cab7ca7011f4e2a330dbb3caa/torchvision-0.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55bd6ad4ae77be01ba67a410b05b51f53b0d0ee45f146eb6a0dfb9007e70ab3c", size = 1863499 }, - { url = "https://files.pythonhosted.org/packages/33/bf/21b899792b08cae7a298551c68398a79e333697479ed311b3b067aab4bdc/torchvision-0.26.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:1c55dc8affbcc0eb2060fbabbe996ae9e5839b24bb6419777f17848945a411b1", size = 7767527 }, - { url = "https://files.pythonhosted.org/packages/9a/45/57bbf9e216850d065e66dd31a50f57424b607f1d878ab8956e56a1f4e36b/torchvision-0.26.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:fd10b5f994c210f4f6d6761cf686f82d748554adf486cb0979770c3252868c8f", size = 7519925 }, - { url = "https://files.pythonhosted.org/packages/10/58/ed8f7754299f3e91d6414b6dc09f62b3fa7c6e5d63dfe48d69ab81498a37/torchvision-0.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:de6424b12887ad884f39a0ee446994ae3cd3b6a00a9cafe1bead85a031132af0", size = 3983834 }, - { url = "https://files.pythonhosted.org/packages/ae/e7/56b47cc3b132aea90ccce22bcb8975dec688b002150012acc842846039d0/torchvision-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c409e1c3fdebec7a3834465086dbda8bf7680eff79abf7fd2f10c6b59520a7a4", size = 1863502 }, - { url = "https://files.pythonhosted.org/packages/f4/ec/5c31c92c08b65662fe9604a4067ae8232582805949f11ddc042cebe818ed/torchvision-0.26.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:406557718e62fdf10f5706e88d8a5ec000f872da913bf629aab9297622585547", size = 7767944 }, - { url = "https://files.pythonhosted.org/packages/f5/d8/cb6ccda1a1f35a6597645818641701207b3e8e13553e75fce5d86bac74b2/torchvision-0.26.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d61a5abb6b42a0c0c311996c2ac4b83a94418a97182c83b055a2a4ae985e05aa", size = 7522205 }, - { url = "https://files.pythonhosted.org/packages/1c/a9/c272623a0f735c35f0f6cd6dc74784d4f970e800cf063bb76687895a2ab9/torchvision-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:7993c01648e7c61d191b018e84d38fe0825c8fcb2720cd0f37caf7ba14404aa1", size = 4255155 }, - { url = "https://files.pythonhosted.org/packages/da/80/0762f77f53605d10c9477be39bb47722cc8e383bbbc2531471ce0e396c07/torchvision-0.26.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5d63dd43162691258b1b3529b9041bac7d54caa37eae0925f997108268cbf7c4", size = 1860809 }, - { url = "https://files.pythonhosted.org/packages/e6/81/0b3e58d1478c660a5af4268713486b2df7203f35abd9195fea87348a5178/torchvision-0.26.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a39c7a26538c41fda453f9a9692b5ff9b35a5437db1d94f3027f6f509c160eac", size = 7727494 }, - { url = "https://files.pythonhosted.org/packages/b6/dc/d9ab5d29115aa05e12e30f1397a3eeae1d88a511241dc3bce48dc4342675/torchvision-0.26.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:b7e6213620bbf97742e5f79832f9e9d769e6cf0f744c5b53dad80b76db633691", size = 7521747 }, - { url = "https://files.pythonhosted.org/packages/a9/1b/f1bc86a918c5f6feab1eeff11982e2060f4704332e96185463d27855bdf5/torchvision-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:4280c35ec8cba1fcc8294fb87e136924708726864c379e4c54494797d86bc474", size = 4319880 }, - { url = "https://files.pythonhosted.org/packages/66/28/b4ad0a723ed95b003454caffcc41894b34bd8379df340848cae2c33871de/torchvision-0.26.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:358fc4726d0c08615b6d83b3149854f11efb2a564ed1acb6fce882e151412d23", size = 1951973 }, - { url = "https://files.pythonhosted.org/packages/71/e2/7a89096e6cf2f3336353b5338ba925e0addf9d8601920340e6bdf47e8eb3/torchvision-0.26.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:3daf9cc149cf3cdcbd4df9c59dae69ffca86c6823250442c3bbfd63fc2e26c61", size = 7728679 }, - { url = "https://files.pythonhosted.org/packages/69/1d/4e1eebc17d18ce080a11dcf3df3f8f717f0efdfa00983f06e8ba79259f61/torchvision-0.26.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:82c3965eca27e86a316e31e4c3e5a16d353e0bcbe0ef8efa2e66502c54493c4b", size = 7609138 }, - { url = "https://files.pythonhosted.org/packages/f3/a4/f1155e943ae5b32400d7000adc81c79bb0392b16ceb33bcf13e02e48cced/torchvision-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ebc043cc5a4f0bf22e7680806dbba37ffb19e70f6953bbb44ed1a90aeb5c9bea", size = 4248202 }, - { url = "https://files.pythonhosted.org/packages/7f/c8/9bffa9c7f7bdf95b2a0a2dc535c290b9f1cc580c3fb3033ab1246ffffdeb/torchvision-0.26.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:eb61804eb9dbe88c5a2a6c4da8dec1d80d2d0a6f18c999c524e32266cb1ebcd3", size = 1860813 }, - { url = "https://files.pythonhosted.org/packages/7b/ac/48f28ffd227991f2e14f4392dde7e8dc14352bb9428c1ef4a4bbf5f7ed85/torchvision-0.26.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:9a904f2131cbfadab4df828088a9f66291ad33f49ff853872aed1f86848ef776", size = 7727777 }, - { url = "https://files.pythonhosted.org/packages/a4/21/a2266f7f1b0e58e624ff15fd6f01041f59182c49551ece0db9a183071329/torchvision-0.26.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:0f3e572efe62ad645017ea847e0b5e4f2f638d4e39f05bc011d1eb9ac68d4806", size = 7522174 }, - { url = "https://files.pythonhosted.org/packages/fc/ba/1666f90bc0bdd77aaa11dcc42bb9f621a9c3668819c32430452e3d404730/torchvision-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:114bec0c0e98aa4ba446f63e2fe7a2cbca37b39ac933987ee4804f65de121800", size = 4348469 }, - { url = "https://files.pythonhosted.org/packages/45/8f/1f0402ac55c2ae15651ff831957d083fe70b2d12282e72612a30ba601512/torchvision-0.26.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:b7d3e295624a28b3b1769228ce1345d94cf4d390dd31136766f76f2d20f718da", size = 1860826 }, - { url = "https://files.pythonhosted.org/packages/d2/6a/18a582fe3c5ee26f49b5c9fb21ad8016b4d1c06d10178894a58653946fda/torchvision-0.26.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:7058c5878262937e876f20c25867b33724586aa4499e2853b2d52b99a5e51953", size = 7729089 }, - { url = "https://files.pythonhosted.org/packages/c5/9b/f7e119b59499edc00c55c03adc9ec3bd96144d9b81c46852c431f9c64a9a/torchvision-0.26.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:8008474855623c6ba52876589dc52df0aa66e518c25eca841445348e5f79844c", size = 7522704 }, - { url = "https://files.pythonhosted.org/packages/d0/6a/09f3844c10643f6c0de5d95abc863420cfaf194c88c7dffd0ac523e2015f/torchvision-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e9d0e022c19a78552fb055d0414d47fecb4a649309b9968573daea160ba6869c", size = 4454275 }, + { url = "https://files.pythonhosted.org/packages/3e/be/c704bceaf11c4f6b19d64337a34a877fcdfe3bd68160a8c9ae9bea4a35a3/torchvision-0.25.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:db74a551946b75d19f9996c419a799ffdf6a223ecf17c656f90da011f1d75b20", size = 1874923, upload-time = "2026-01-21T16:27:46.574Z" }, + { url = "https://files.pythonhosted.org/packages/ae/e9/f143cd71232430de1f547ceab840f68c55e127d72558b1061a71d0b193cd/torchvision-0.25.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:f49964f96644dbac2506dffe1a0a7ec0f2bf8cf7a588c3319fed26e6329ffdf3", size = 2344808, upload-time = "2026-01-21T16:27:43.191Z" }, + { url = "https://files.pythonhosted.org/packages/43/ae/ad5d6165797de234c9658752acb4fce65b78a6a18d82efdf8367c940d8da/torchvision-0.25.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:153c0d2cbc34b7cf2da19d73450f24ba36d2b75ec9211b9962b5022fb9e4ecee", size = 8070752, upload-time = "2026-01-21T16:27:33.748Z" }, + { url = "https://files.pythonhosted.org/packages/23/19/55b28aecdc7f38df57b8eb55eb0b14a62b470ed8efeb22cdc74224df1d6a/torchvision-0.25.0-cp311-cp311-win_amd64.whl", hash = "sha256:ea580ffd6094cc01914ad32f8c8118174f18974629af905cea08cb6d5d48c7b7", size = 4038722, upload-time = "2026-01-21T16:27:41.355Z" }, + { url = "https://files.pythonhosted.org/packages/56/3a/6ea0d73f49a9bef38a1b3a92e8dd455cea58470985d25635beab93841748/torchvision-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c2abe430c90b1d5e552680037d68da4eb80a5852ebb1c811b2b89d299b10573b", size = 1874920, upload-time = "2026-01-21T16:27:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/51/f8/c0e1ef27c66e15406fece94930e7d6feee4cb6374bbc02d945a630d6426e/torchvision-0.25.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:b75deafa2dfea3e2c2a525559b04783515e3463f6e830cb71de0fb7ea36fe233", size = 2344556, upload-time = "2026-01-21T16:27:40.125Z" }, + { url = "https://files.pythonhosted.org/packages/68/2f/f24b039169db474e8688f649377de082a965fbf85daf4e46c44412f1d15a/torchvision-0.25.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f25aa9e380865b11ea6e9d99d84df86b9cc959f1a007cd966fc6f1ab2ed0e248", size = 8072351, upload-time = "2026-01-21T16:27:21.074Z" }, + { url = "https://files.pythonhosted.org/packages/ad/16/8f650c2e288977cf0f8f85184b90ee56ed170a4919347fc74ee99286ed6f/torchvision-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:f9c55ae8d673ab493325d1267cbd285bb94d56f99626c00ac4644de32a59ede3", size = 4303059, upload-time = "2026-01-21T16:27:11.08Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5b/1562a04a6a5a4cf8cf40016a0cdeda91ede75d6962cff7f809a85ae966a5/torchvision-0.25.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:24e11199e4d84ba9c5ee7825ebdf1cd37ce8deec225117f10243cae984ced3ec", size = 1874918, upload-time = "2026-01-21T16:27:39.02Z" }, + { url = "https://files.pythonhosted.org/packages/36/b1/3d6c42f62c272ce34fcce609bb8939bdf873dab5f1b798fd4e880255f129/torchvision-0.25.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:5f271136d2d2c0b7a24c5671795c6e4fd8da4e0ea98aeb1041f62bc04c4370ef", size = 2309106, upload-time = "2026-01-21T16:27:30.624Z" }, + { url = "https://files.pythonhosted.org/packages/c7/60/59bb9c8b67cce356daeed4cb96a717caa4f69c9822f72e223a0eae7a9bd9/torchvision-0.25.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:855c0dc6d37f462482da7531c6788518baedca1e0847f3df42a911713acdfe52", size = 8071522, upload-time = "2026-01-21T16:27:29.392Z" }, + { url = "https://files.pythonhosted.org/packages/32/a5/9a9b1de0720f884ea50dbf9acb22cbe5312e51d7b8c4ac6ba9b51efd9bba/torchvision-0.25.0-cp313-cp313-win_amd64.whl", hash = "sha256:cef0196be31be421f6f462d1e9da1101be7332d91984caa6f8022e6c78a5877f", size = 4321911, upload-time = "2026-01-21T16:27:35.195Z" }, + { url = "https://files.pythonhosted.org/packages/52/99/dca81ed21ebaeff2b67cc9f815a20fdaa418b69f5f9ea4c6ed71721470db/torchvision-0.25.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a8f8061284395ce31bcd460f2169013382ccf411148ceb2ee38e718e9860f5a7", size = 1896209, upload-time = "2026-01-21T16:27:32.159Z" }, + { url = "https://files.pythonhosted.org/packages/28/cc/2103149761fdb4eaed58a53e8437b2d716d48f05174fab1d9fcf1e2a2244/torchvision-0.25.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:146d02c9876858420adf41f3189fe90e3d6a409cbfa65454c09f25fb33bf7266", size = 2310735, upload-time = "2026-01-21T16:27:22.327Z" }, + { url = "https://files.pythonhosted.org/packages/76/ad/f4c985ad52ddd3b22711c588501be1b330adaeaf6850317f66751711b78c/torchvision-0.25.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:c4d395cb2c4a2712f6eb93a34476cdf7aae74bb6ea2ea1917f858e96344b00aa", size = 8089557, upload-time = "2026-01-21T16:27:27.666Z" }, + { url = "https://files.pythonhosted.org/packages/63/cc/0ea68b5802e5e3c31f44b307e74947bad5a38cc655231d845534ed50ddb8/torchvision-0.25.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5e6b449e9fa7d642142c0e27c41e5a43b508d57ed8e79b7c0a0c28652da8678c", size = 4344260, upload-time = "2026-01-21T16:27:17.018Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1f/fa839532660e2602b7e704d65010787c5bb296258b44fa8b9c1cd6175e7d/torchvision-0.25.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:620a236288d594dcec7634c754484542dc0a5c1b0e0b83a34bda5e91e9b7c3a1", size = 1896193, upload-time = "2026-01-21T16:27:24.785Z" }, + { url = "https://files.pythonhosted.org/packages/80/ed/d51889da7ceaf5ff7a0574fb28f9b6b223df19667265395891f81b364ab3/torchvision-0.25.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:0b5e7f50002a8145a98c5694a018e738c50e2972608310c7e88e1bd4c058f6ce", size = 2309331, upload-time = "2026-01-21T16:27:19.97Z" }, + { url = "https://files.pythonhosted.org/packages/90/a5/f93fcffaddd8f12f9e812256830ec9c9ca65abbf1bc369379f9c364d1ff4/torchvision-0.25.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:632db02300e83793812eee4f61ae6a2686dab10b4cfd628b620dc47747aa9d03", size = 8088713, upload-time = "2026-01-21T16:27:15.281Z" }, + { url = "https://files.pythonhosted.org/packages/1f/eb/d0096eed5690d962853213f2ee00d91478dfcb586b62dbbb449fb8abc3a6/torchvision-0.25.0-cp314-cp314-win_amd64.whl", hash = "sha256:d1abd5ed030c708f5dbf4812ad5f6fbe9384b63c40d6bd79f8df41a4a759a917", size = 4325058, upload-time = "2026-01-21T16:27:26.165Z" }, + { url = "https://files.pythonhosted.org/packages/97/36/96374a4c7ab50dea9787ce987815614ccfe988a42e10ac1a2e3e5b60319a/torchvision-0.25.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ad9a8a5877782944d99186e4502a614770fe906626d76e9cd32446a0ac3075f2", size = 1896207, upload-time = "2026-01-21T16:27:23.383Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e2/7abb10a867db79b226b41da419b63b69c0bd5b82438c4a4ed50e084c552f/torchvision-0.25.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:40a122c3cf4d14b651f095e0f672b688dde78632783fc5cd3d4d5e4f6a828563", size = 2310741, upload-time = "2026-01-21T16:27:18.712Z" }, + { url = "https://files.pythonhosted.org/packages/08/e6/0927784e6ffc340b6676befde1c60260bd51641c9c574b9298d791a9cda4/torchvision-0.25.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:846890161b825b38aa85fc37fb3ba5eea74e7091ff28bab378287111483b6443", size = 8089772, upload-time = "2026-01-21T16:27:14.048Z" }, + { url = "https://files.pythonhosted.org/packages/b6/37/e7ca4ec820d434c0f23f824eb29f0676a0c3e7a118f1514f5b949c3356da/torchvision-0.25.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f07f01d27375ad89d72aa2b3f2180f07da95dd9d2e4c758e015c0acb2da72977", size = 4425879, upload-time = "2026-01-21T16:27:12.579Z" }, ] [[package]] name = "tornado" -version = "6.5.5" +version = "6.5.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/f1/3173dfa4a18db4a9b03e5d55325559dab51ee653763bb8745a75af491286/tornado-6.5.5.tar.gz", hash = "sha256:192b8f3ea91bd7f1f50c06955416ed76c6b72f96779b962f07f911b91e8d30e9", size = 516006, upload-time = "2026-03-10T21:31:02.067Z" } +sdist = { url = "https://files.pythonhosted.org/packages/37/1d/0a336abf618272d53f62ebe274f712e213f5a03c0b2339575430b8362ef2/tornado-6.5.4.tar.gz", hash = "sha256:a22fa9047405d03260b483980635f0b041989d8bcc9a313f8fe18b411d84b1d7", size = 513632, upload-time = "2025-12-15T19:21:03.836Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/59/8c/77f5097695f4dd8255ecbd08b2a1ed8ba8b953d337804dd7080f199e12bf/tornado-6.5.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:487dc9cc380e29f58c7ab88f9e27cdeef04b2140862e5076a66fb6bb68bb1bfa", size = 445983, upload-time = "2026-03-10T21:30:44.28Z" }, - { url = "https://files.pythonhosted.org/packages/ab/5e/7625b76cd10f98f1516c36ce0346de62061156352353ef2da44e5c21523c/tornado-6.5.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:65a7f1d46d4bb41df1ac99f5fcb685fb25c7e61613742d5108b010975a9a6521", size = 444246, upload-time = "2026-03-10T21:30:46.571Z" }, - { url = "https://files.pythonhosted.org/packages/b2/04/7b5705d5b3c0fab088f434f9c83edac1573830ca49ccf29fb83bf7178eec/tornado-6.5.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e74c92e8e65086b338fd56333fb9a68b9f6f2fe7ad532645a290a464bcf46be5", size = 447229, upload-time = "2026-03-10T21:30:48.273Z" }, - { url = "https://files.pythonhosted.org/packages/34/01/74e034a30ef59afb4097ef8659515e96a39d910b712a89af76f5e4e1f93c/tornado-6.5.5-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:435319e9e340276428bbdb4e7fa732c2d399386d1de5686cb331ec8eee754f07", size = 448192, upload-time = "2026-03-10T21:30:51.22Z" }, - { url = "https://files.pythonhosted.org/packages/be/00/fe9e02c5a96429fce1a1d15a517f5d8444f9c412e0bb9eadfbe3b0fc55bf/tornado-6.5.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3f54aa540bdbfee7b9eb268ead60e7d199de5021facd276819c193c0fb28ea4e", size = 448039, upload-time = "2026-03-10T21:30:53.52Z" }, - { url = "https://files.pythonhosted.org/packages/82/9e/656ee4cec0398b1d18d0f1eb6372c41c6b889722641d84948351ae19556d/tornado-6.5.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:36abed1754faeb80fbd6e64db2758091e1320f6bba74a4cf8c09cd18ccce8aca", size = 447445, upload-time = "2026-03-10T21:30:55.541Z" }, - { url = "https://files.pythonhosted.org/packages/5a/76/4921c00511f88af86a33de770d64141170f1cfd9c00311aea689949e274e/tornado-6.5.5-cp39-abi3-win32.whl", hash = "sha256:dd3eafaaeec1c7f2f8fdcd5f964e8907ad788fe8a5a32c4426fbbdda621223b7", size = 448582, upload-time = "2026-03-10T21:30:57.142Z" }, - { url = "https://files.pythonhosted.org/packages/2c/23/f6c6112a04d28eed765e374435fb1a9198f73e1ec4b4024184f21faeb1ad/tornado-6.5.5-cp39-abi3-win_amd64.whl", hash = "sha256:6443a794ba961a9f619b1ae926a2e900ac20c34483eea67be4ed8f1e58d3ef7b", size = 448990, upload-time = "2026-03-10T21:30:58.857Z" }, - { url = "https://files.pythonhosted.org/packages/b7/c8/876602cbc96469911f0939f703453c1157b0c826ecb05bdd32e023397d4e/tornado-6.5.5-cp39-abi3-win_arm64.whl", hash = "sha256:2c9a876e094109333f888539ddb2de4361743e5d21eece20688e3e351e4990a6", size = 448016, upload-time = "2026-03-10T21:31:00.43Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d6241c1a16b1c9e4cc28148b1cda97dd1c6cb4fb7068ac1bedc610768dff0ba9", size = 443909, upload-time = "2025-12-15T19:20:48.382Z" }, + { url = "https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2d50f63dda1d2cac3ae1fa23d254e16b5e38153758470e9956cbc3d813d40843", size = 442163, upload-time = "2025-12-15T19:20:49.791Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b5/206f82d51e1bfa940ba366a8d2f83904b15942c45a78dd978b599870ab44/tornado-6.5.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1cf66105dc6acb5af613c054955b8137e34a03698aa53272dbda4afe252be17", size = 445746, upload-time = "2025-12-15T19:20:51.491Z" }, + { url = "https://files.pythonhosted.org/packages/8e/9d/1a3338e0bd30ada6ad4356c13a0a6c35fbc859063fa7eddb309183364ac1/tornado-6.5.4-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50ff0a58b0dc97939d29da29cd624da010e7f804746621c78d14b80238669335", size = 445083, upload-time = "2025-12-15T19:20:52.778Z" }, + { url = "https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fb5e04efa54cf0baabdd10061eb4148e0be137166146fff835745f59ab9f7f", size = 445315, upload-time = "2025-12-15T19:20:53.996Z" }, + { url = "https://files.pythonhosted.org/packages/27/07/2273972f69ca63dbc139694a3fc4684edec3ea3f9efabf77ed32483b875c/tornado-6.5.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9c86b1643b33a4cd415f8d0fe53045f913bf07b4a3ef646b735a6a86047dda84", size = 446003, upload-time = "2025-12-15T19:20:56.101Z" }, + { url = "https://files.pythonhosted.org/packages/d1/83/41c52e47502bf7260044413b6770d1a48dda2f0246f95ee1384a3cd9c44a/tornado-6.5.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:6eb82872335a53dd063a4f10917b3efd28270b56a33db69009606a0312660a6f", size = 445412, upload-time = "2025-12-15T19:20:57.398Z" }, + { url = "https://files.pythonhosted.org/packages/10/c7/bc96917f06cbee182d44735d4ecde9c432e25b84f4c2086143013e7b9e52/tornado-6.5.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6076d5dda368c9328ff41ab5d9dd3608e695e8225d1cd0fd1e006f05da3635a8", size = 445392, upload-time = "2025-12-15T19:20:58.692Z" }, + { url = "https://files.pythonhosted.org/packages/0c/1a/d7592328d037d36f2d2462f4bc1fbb383eec9278bc786c1b111cbbd44cfa/tornado-6.5.4-cp39-abi3-win32.whl", hash = "sha256:1768110f2411d5cd281bac0a090f707223ce77fd110424361092859e089b38d1", size = 446481, upload-time = "2025-12-15T19:21:00.008Z" }, + { url = "https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl", hash = "sha256:fa07d31e0cd85c60713f2b995da613588aa03e1303d75705dca6af8babc18ddc", size = 446886, upload-time = "2025-12-15T19:21:01.287Z" }, + { url = "https://files.pythonhosted.org/packages/50/49/8dc3fd90902f70084bd2cd059d576ddb4f8bb44c2c7c0e33a11422acb17e/tornado-6.5.4-cp39-abi3-win_arm64.whl", hash = "sha256:053e6e16701eb6cbe641f308f4c1a9541f91b6261991160391bfc342e8a551a1", size = 445910, upload-time = "2025-12-15T19:21:02.571Z" }, ] [[package]] @@ -3664,18 +3576,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598 } +sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374 }, + { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, ] [[package]] name = "traitlets" version = "5.14.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621 } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359 }, + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, ] [[package]] @@ -3683,59 +3595,53 @@ name = "triton" version = "3.6.0" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/2c/96f92f3c60387e14cc45aed49487f3486f89ea27106c1b1376913c62abe4/triton-3.6.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49df5ef37379c0c2b5c0012286f80174fcf0e073e5ade1ca9a86c36814553651", size = 176081190 }, - { url = "https://files.pythonhosted.org/packages/e0/12/b05ba554d2c623bffa59922b94b0775673de251f468a9609bc9e45de95e9/triton-3.6.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8e323d608e3a9bfcc2d9efcc90ceefb764a82b99dea12a86d643c72539ad5d3", size = 188214640 }, - { url = "https://files.pythonhosted.org/packages/17/5d/08201db32823bdf77a0e2b9039540080b2e5c23a20706ddba942924ebcd6/triton-3.6.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:374f52c11a711fd062b4bfbb201fd9ac0a5febd28a96fb41b4a0f51dde3157f4", size = 176128243 }, - { url = "https://files.pythonhosted.org/packages/ab/a8/cdf8b3e4c98132f965f88c2313a4b493266832ad47fb52f23d14d4f86bb5/triton-3.6.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74caf5e34b66d9f3a429af689c1c7128daba1d8208df60e81106b115c00d6fca", size = 188266850 }, - { url = "https://files.pythonhosted.org/packages/3c/12/34d71b350e89a204c2c7777a9bba0dcf2f19a5bfdd70b57c4dbc5ffd7154/triton-3.6.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448e02fe6dc898e9e5aa89cf0ee5c371e99df5aa5e8ad976a80b93334f3494fd", size = 176133521 }, - { url = "https://files.pythonhosted.org/packages/f9/0b/37d991d8c130ce81a8728ae3c25b6e60935838e9be1b58791f5997b24a54/triton-3.6.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10c7f76c6e72d2ef08df639e3d0d30729112f47a56b0c81672edc05ee5116ac9", size = 188289450 }, - { url = "https://files.pythonhosted.org/packages/ce/4e/41b0c8033b503fd3cfcd12392cdd256945026a91ff02452bef40ec34bee7/triton-3.6.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1722e172d34e32abc3eb7711d0025bb69d7959ebea84e3b7f7a341cd7ed694d6", size = 176276087 }, - { url = "https://files.pythonhosted.org/packages/35/f8/9c66bfc55361ec6d0e4040a0337fb5924ceb23de4648b8a81ae9d33b2b38/triton-3.6.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d002e07d7180fd65e622134fbd980c9a3d4211fb85224b56a0a0efbd422ab72f", size = 188400296 }, - { url = "https://files.pythonhosted.org/packages/49/55/5ecf0dcaa0f2fbbd4420f7ef227ee3cb172e91e5fede9d0ecaddc43363b4/triton-3.6.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5523241e7d1abca00f1d240949eebdd7c673b005edbbce0aca95b8191f1d43", size = 176138577 }, - { url = "https://files.pythonhosted.org/packages/df/3d/9e7eee57b37c80cec63322c0231bb6da3cfe535a91d7a4d64896fcb89357/triton-3.6.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a17a5d5985f0ac494ed8a8e54568f092f7057ef60e1b0fa09d3fd1512064e803", size = 188273063 }, - { url = "https://files.pythonhosted.org/packages/48/db/56ee649cab5eaff4757541325aca81f52d02d4a7cd3506776cad2451e060/triton-3.6.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b3a97e8ed304dfa9bd23bb41ca04cdf6b2e617d5e782a8653d616037a5d537d", size = 176274804 }, - { url = "https://files.pythonhosted.org/packages/f6/56/6113c23ff46c00aae423333eb58b3e60bdfe9179d542781955a5e1514cb3/triton-3.6.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46bd1c1af4b6704e554cad2eeb3b0a6513a980d470ccfa63189737340c7746a7", size = 188397994 }, + { url = "https://files.pythonhosted.org/packages/e0/12/b05ba554d2c623bffa59922b94b0775673de251f468a9609bc9e45de95e9/triton-3.6.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8e323d608e3a9bfcc2d9efcc90ceefb764a82b99dea12a86d643c72539ad5d3", size = 188214640, upload-time = "2026-01-20T16:00:35.869Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a8/cdf8b3e4c98132f965f88c2313a4b493266832ad47fb52f23d14d4f86bb5/triton-3.6.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74caf5e34b66d9f3a429af689c1c7128daba1d8208df60e81106b115c00d6fca", size = 188266850, upload-time = "2026-01-20T16:00:43.041Z" }, + { url = "https://files.pythonhosted.org/packages/f9/0b/37d991d8c130ce81a8728ae3c25b6e60935838e9be1b58791f5997b24a54/triton-3.6.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10c7f76c6e72d2ef08df639e3d0d30729112f47a56b0c81672edc05ee5116ac9", size = 188289450, upload-time = "2026-01-20T16:00:49.136Z" }, + { url = "https://files.pythonhosted.org/packages/35/f8/9c66bfc55361ec6d0e4040a0337fb5924ceb23de4648b8a81ae9d33b2b38/triton-3.6.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d002e07d7180fd65e622134fbd980c9a3d4211fb85224b56a0a0efbd422ab72f", size = 188400296, upload-time = "2026-01-20T16:00:56.042Z" }, + { url = "https://files.pythonhosted.org/packages/df/3d/9e7eee57b37c80cec63322c0231bb6da3cfe535a91d7a4d64896fcb89357/triton-3.6.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a17a5d5985f0ac494ed8a8e54568f092f7057ef60e1b0fa09d3fd1512064e803", size = 188273063, upload-time = "2026-01-20T16:01:07.278Z" }, + { url = "https://files.pythonhosted.org/packages/f6/56/6113c23ff46c00aae423333eb58b3e60bdfe9179d542781955a5e1514cb3/triton-3.6.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46bd1c1af4b6704e554cad2eeb3b0a6513a980d470ccfa63189737340c7746a7", size = 188397994, upload-time = "2026-01-20T16:01:14.236Z" }, ] [[package]] name = "typing-extensions" version = "4.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391 } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614 }, + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] [[package]] name = "tzdata" version = "2025.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772 } +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521 }, + { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, ] [[package]] name = "uri-template" version = "1.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/31/c7/0336f2bd0bcbada6ccef7aaa25e443c118a704f828a0620c6fa0207c1b64/uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7", size = 21678 } +sdist = { url = "https://files.pythonhosted.org/packages/31/c7/0336f2bd0bcbada6ccef7aaa25e443c118a704f828a0620c6fa0207c1b64/uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7", size = 21678, upload-time = "2023-06-21T01:49:05.374Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363", size = 11140 }, + { url = "https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363", size = 11140, upload-time = "2023-06-21T01:49:03.467Z" }, ] [[package]] name = "urllib3" version = "2.6.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556 } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584 }, + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, ] [[package]] name = "virtualenv" -version = "21.2.0" +version = "21.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -3743,45 +3649,45 @@ dependencies = [ { name = "platformdirs" }, { name = "python-discovery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/aa/92/58199fe10049f9703c2666e809c4f686c54ef0a68b0f6afccf518c0b1eb9/virtualenv-21.2.0.tar.gz", hash = "sha256:1720dc3a62ef5b443092e3f499228599045d7fea4c79199770499df8becf9098", size = 5840618, upload-time = "2026-03-09T17:24:38.013Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/c9/18d4b36606d6091844daa3bd93cf7dc78e6f5da21d9f21d06c221104b684/virtualenv-21.1.0.tar.gz", hash = "sha256:1990a0188c8f16b6b9cf65c9183049007375b26aad415514d377ccacf1e4fb44", size = 5840471, upload-time = "2026-02-27T08:49:29.702Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-py3-none-any.whl", hash = "sha256:1bd755b504931164a5a496d217c014d098426cddc79363ad66ac78125f9d908f", size = 5825084, upload-time = "2026-03-09T17:24:35.378Z" }, + { url = "https://files.pythonhosted.org/packages/78/55/896b06bf93a49bec0f4ae2a6f1ed12bd05c8860744ac3a70eda041064e4d/virtualenv-21.1.0-py3-none-any.whl", hash = "sha256:164f5e14c5587d170cf98e60378eb91ea35bf037be313811905d3a24ea33cc07", size = 5825072, upload-time = "2026-02-27T08:49:27.516Z" }, ] [[package]] name = "wcwidth" version = "0.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/35/a2/8e3becb46433538a38726c948d3399905a4c7cabd0df578ede5dc51f0ec2/wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159", size = 159684 } +sdist = { url = "https://files.pythonhosted.org/packages/35/a2/8e3becb46433538a38726c948d3399905a4c7cabd0df578ede5dc51f0ec2/wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159", size = 159684, upload-time = "2026-02-06T19:19:40.919Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189 }, + { url = "https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189, upload-time = "2026-02-06T19:19:39.646Z" }, ] [[package]] name = "webcolors" version = "25.10.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/7a/eb316761ec35664ea5174709a68bbd3389de60d4a1ebab8808bfc264ed67/webcolors-25.10.0.tar.gz", hash = "sha256:62abae86504f66d0f6364c2a8520de4a0c47b80c03fc3a5f1815fedbef7c19bf", size = 53491 } +sdist = { url = "https://files.pythonhosted.org/packages/1d/7a/eb316761ec35664ea5174709a68bbd3389de60d4a1ebab8808bfc264ed67/webcolors-25.10.0.tar.gz", hash = "sha256:62abae86504f66d0f6364c2a8520de4a0c47b80c03fc3a5f1815fedbef7c19bf", size = 53491, upload-time = "2025-10-31T07:51:03.977Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl", hash = "sha256:032c727334856fc0b968f63daa252a1ac93d33db2f5267756623c210e57a4f1d", size = 14905 }, + { url = "https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl", hash = "sha256:032c727334856fc0b968f63daa252a1ac93d33db2f5267756623c210e57a4f1d", size = 14905, upload-time = "2025-10-31T07:51:01.778Z" }, ] [[package]] name = "webencodings" version = "0.5.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721 } +sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721, upload-time = "2017-04-05T20:21:34.189Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774 }, + { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" }, ] [[package]] name = "websocket-client" version = "1.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576 } +sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576, upload-time = "2025-10-07T21:16:36.495Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616 }, + { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616, upload-time = "2025-10-07T21:16:34.951Z" }, ] [[package]] @@ -3791,18 +3697,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/61/f1/ee81806690a87dab5f5653c1f146c92bc066d7f4cebc603ef88eb9e13957/werkzeug-3.1.6.tar.gz", hash = "sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25", size = 864736 } +sdist = { url = "https://files.pythonhosted.org/packages/61/f1/ee81806690a87dab5f5653c1f146c92bc066d7f4cebc603ef88eb9e13957/werkzeug-3.1.6.tar.gz", hash = "sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25", size = 864736, upload-time = "2026-02-19T15:17:18.884Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/ec/d58832f89ede95652fd01f4f24236af7d32b70cab2196dfcc2d2fd13c5c2/werkzeug-3.1.6-py3-none-any.whl", hash = "sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131", size = 225166 }, + { url = "https://files.pythonhosted.org/packages/4d/ec/d58832f89ede95652fd01f4f24236af7d32b70cab2196dfcc2d2fd13c5c2/werkzeug-3.1.6-py3-none-any.whl", hash = "sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131", size = 225166, upload-time = "2026-02-19T15:17:17.475Z" }, ] [[package]] name = "widgetsnbextension" version = "4.0.15" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bd/f4/c67440c7fb409a71b7404b7aefcd7569a9c0d6bd071299bf4198ae7a5d95/widgetsnbextension-4.0.15.tar.gz", hash = "sha256:de8610639996f1567952d763a5a41af8af37f2575a41f9852a38f947eb82a3b9", size = 1097402 } +sdist = { url = "https://files.pythonhosted.org/packages/bd/f4/c67440c7fb409a71b7404b7aefcd7569a9c0d6bd071299bf4198ae7a5d95/widgetsnbextension-4.0.15.tar.gz", hash = "sha256:de8610639996f1567952d763a5a41af8af37f2575a41f9852a38f947eb82a3b9", size = 1097402, upload-time = "2025-11-01T21:15:55.178Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl", hash = "sha256:8156704e4346a571d9ce73b84bee86a29906c9abfd7223b7228a28899ccf3366", size = 2196503 }, + { url = "https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl", hash = "sha256:8156704e4346a571d9ce73b84bee86a29906c9abfd7223b7228a28899ccf3366", size = 2196503, upload-time = "2025-11-01T21:15:53.565Z" }, ] [[package]] @@ -3817,16 +3723,16 @@ dependencies = [ { name = "packaging" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/76/7fa87f57c112c7b9c82f0a730f8b6f333e792574812872e2cd45ab604199/zarr-3.1.5.tar.gz", hash = "sha256:fbe0c79675a40c996de7ca08e80a1c0a20537bd4a9f43418b6d101395c0bba2b", size = 366825 } +sdist = { url = "https://files.pythonhosted.org/packages/fc/76/7fa87f57c112c7b9c82f0a730f8b6f333e792574812872e2cd45ab604199/zarr-3.1.5.tar.gz", hash = "sha256:fbe0c79675a40c996de7ca08e80a1c0a20537bd4a9f43418b6d101395c0bba2b", size = 366825, upload-time = "2025-11-21T14:06:01.492Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/44/15/bb13b4913ef95ad5448490821eee4671d0e67673342e4d4070854e5fe081/zarr-3.1.5-py3-none-any.whl", hash = "sha256:29cd905afb6235b94c09decda4258c888fcb79bb6c862ef7c0b8fe009b5c8563", size = 284067 }, + { url = "https://files.pythonhosted.org/packages/44/15/bb13b4913ef95ad5448490821eee4671d0e67673342e4d4070854e5fe081/zarr-3.1.5-py3-none-any.whl", hash = "sha256:29cd905afb6235b94c09decda4258c888fcb79bb6c862ef7c0b8fe009b5c8563", size = 284067, upload-time = "2025-11-21T14:05:59.235Z" }, ] [[package]] name = "zipp" version = "3.23.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547 } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276 }, -] + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, +] \ No newline at end of file From b922cbb458dfa53f5c8f5ce823efcc4943cccf22 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Mar 2026 19:56:05 -0700 Subject: [PATCH 201/335] num_samples_per_ray schedule provided set to only global_rank 0 print --- src/quantem/tomography/tomography.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index ea990169..6463a621 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -160,7 +160,8 @@ def reconstruct( raise ValueError( "num_samples_per_ray schedule must have the same length as num_iter" ) - print("num_samples_per_ray schedule provided.") + if self.global_rank == 0: + print("num_samples_per_ray schedule provided.") loss_func = get_loss_module(name=loss_type, dtype=self.obj_model.dtype, **loss_func_kwargs) From c81b2b5f5fff307477a58555ea947853599d4b5d Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Mar 2026 19:57:21 -0700 Subject: [PATCH 202/335] Type-hinting ignore fix in inr.py --- src/quantem/core/ml/inr.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/quantem/core/ml/inr.py b/src/quantem/core/ml/inr.py index a721c008..08b1732d 100644 --- a/src/quantem/core/ml/inr.py +++ b/src/quantem/core/ml/inr.py @@ -118,10 +118,10 @@ def _build(self) -> None: rng = torch.Generator() rng.manual_seed(42) with torch.no_grad(): - self.net[0].linear.weight += ( # pyright: ignore[reportAttributeAccessIssue] + self.net[0].linear.weight += ( # type: ignore[reportAttributeAccessIssue] torch.randn_like(self.net[0].linear.weight) * 5 / self.first_omega_0 # type:ignore ) - self.net[1].linear.weight += ( # pyright: ignore[reportAttributeAccessIssue] + self.net[1].linear.weight += ( # type: ignore[reportAttributeAccessIssue] torch.randn_like(self.net[1].linear.weight) * 0.1 / self.hidden_omega_0 # type:ignore ) From 62616d706700730c25768ef280a7dc7e7c599a57 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Mar 2026 19:59:52 -0700 Subject: [PATCH 203/335] to_numpy() added in tomography_utils.py, and edited background_subtract --- src/quantem/core/utils/tomography_utils.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/quantem/core/utils/tomography_utils.py b/src/quantem/core/utils/tomography_utils.py index 241687bc..3aad74cb 100644 --- a/src/quantem/core/utils/tomography_utils.py +++ b/src/quantem/core/utils/tomography_utils.py @@ -10,22 +10,13 @@ from tqdm.auto import tqdm from quantem.core.utils.imaging_utils import cross_correlation_shift +from quantem.core.utils.utils import to_numpy from quantem.core.visualization import show_2d ImageType = NDArray[Any] BoolArray = NDArray[np.bool_] -def _as_array(x: ImageType) -> NDArray[Any]: - return ( - x.array - if isinstance( - x, - ) - else np.asarray(x) - ) - - def _bernstein_basis_1d(n: int, t: NDArray[Any]) -> NDArray[Any]: k = np.arange(n + 1, dtype=int) return ( @@ -66,7 +57,7 @@ def background_subtract( - If `True`: (ImageType, numpy.ndarray, numpy.ndarray[bool]) where background and mask are always NumPy. """ - im = _as_array(image).astype(float, copy=True) + im = to_numpy(image).astype(float, copy=True) if im.ndim != 2: raise ValueError("`image` must be 2D") From 3567b4e30a3d65ebdfea275df98857ac414e3a02 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Mar 2026 20:01:14 -0700 Subject: [PATCH 204/335] tomography_utils.py diff_shift_2d updated docstring --- src/quantem/core/utils/tomography_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quantem/core/utils/tomography_utils.py b/src/quantem/core/utils/tomography_utils.py index 3aad74cb..2f9cebca 100644 --- a/src/quantem/core/utils/tomography_utils.py +++ b/src/quantem/core/utils/tomography_utils.py @@ -230,7 +230,7 @@ def centering_com_alignment(image_stack): def differentiable_shift_2d(image, shift_x, shift_y, sampling_rate): """ - Shifts a 2D image using grid_sample in a differentiable manner. + Shifts a 2D image using grid_sample in a differentiable manner with boundary conditions applied. Args: image: Tensor of shape [H, W] From 5923624a7a275ba39411eacbb0bf888b4d1da8fd Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Mar 2026 20:04:21 -0700 Subject: [PATCH 205/335] plot_losses() function implemented both for conventional and INR algorithms --- src/quantem/tomography/tomography.py | 60 +++++++++++++++------------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 6463a621..7e9854ba 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -324,26 +324,7 @@ def reconstruct( f"Reconstruction Epoch {self.num_epochs} | Loss: {total_loss:.5e}, Consistency Loss: {consistency_loss:.5e}, Soft Constraint Loss: {epoch_soft_constraint_loss:.5e}" ) if show_metrics and self.world_size == 1: - fig, ax = plt.subplots(figsize=(10, 4), ncols=2) - - ax[0].plot(self._epoch_losses, label="Total Training Loss") - if len(self._val_losses) > 0: - ax[0].plot(self._val_losses, label="Validation Loss") - ax[0].legend() - - for key, value in self._lrs.items(): - ax[1].plot(value, label=key) - - ax[1].legend() - ax[0].legend() - ax[0].set_yscale("log") - ax[1].set_yscale("log") - ax[0].set_xlabel("Epoch") - ax[1].set_xlabel("Epoch") - ax[0].set_ylabel("Loss") - ax[1].set_ylabel("Learning Rate") - - fig.tight_layout() + self.plot_losses() # --- Helper Functions --- @@ -412,6 +393,26 @@ def save( compression_level=compression_level, ) + def plot_losses(self): + fig, ax = plt.subplots(figsize=(10, 4), ncols=2) + + ax[0].plot(self._epoch_losses, label="Total Training Loss") + if len(self._val_losses) > 0: + ax[0].plot(self._val_losses, label="Validation Loss") + ax[0].legend() + + for key, value in self._lrs.items(): + ax[1].plot(value, label=key) + + ax[1].legend() + ax[0].legend() + ax[0].set_yscale("log") + ax[1].set_yscale("log") + ax[0].set_xlabel("Epoch") + ax[1].set_xlabel("Epoch") + ax[0].set_ylabel("Loss") + ax[1].set_ylabel("Learning Rate") + class TomographyConventional(TomographyBase): """ @@ -498,13 +499,7 @@ def reconstruct( break if show_metrics: - fig, ax = plt.subplots() - ax.plot(self._epoch_losses) - ax.set_xlabel("Iteration") - ax.set_ylabel("Loss") - ax.set_title("Reconstruction Loss") - ax.set_yscale("log") - plt.show() + self.plot_losses() # --- Conventional reconstruction method --- def _adaptive_relaxation(self, n_power_iter: int = 10) -> float: @@ -585,3 +580,14 @@ def _reconstruction_epoch( loss = torch.mean(torch.abs(error)) return proj_forward, loss + + # --- Helper Functions --- + + def plot_losses(self): + fig, ax = plt.subplots() + ax.plot(self._epoch_losses) + ax.set_xlabel("Iteration") + ax.set_ylabel("Loss") + ax.set_title("Reconstruction Loss") + ax.set_yscale("log") + plt.show() From 0ec02dfc4f021032485525fda48a826834212c1b Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Mar 2026 20:06:08 -0700 Subject: [PATCH 206/335] Quick fix on object_models DDP instantiation, global_rank is not defined yet --- src/quantem/tomography/tomography_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quantem/tomography/tomography_base.py b/src/quantem/tomography/tomography_base.py index 7e2670fb..743a6f6a 100644 --- a/src/quantem/tomography/tomography_base.py +++ b/src/quantem/tomography/tomography_base.py @@ -52,9 +52,9 @@ def __init__( self._lrs: dict[str, float] = {} # DDP Initialization if isinstance(obj_model, ObjectINR): + self.setup_distributed(device=device) if self.global_rank == 0: print("Setting up DDP for obj_model") - self.setup_distributed(device=device) self.dset = dset self.dset.to(device) From 8afb02854d98f4bbd5592309395279f456674a7d Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Mar 2026 20:07:37 -0700 Subject: [PATCH 207/335] logger_tomography fixed linting --- src/quantem/tomography/logger_tomography.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quantem/tomography/logger_tomography.py b/src/quantem/tomography/logger_tomography.py index 821f1814..9798214d 100644 --- a/src/quantem/tomography/logger_tomography.py +++ b/src/quantem/tomography/logger_tomography.py @@ -15,7 +15,7 @@ def __init__( self, log_dir: str, run_prefix: str, - run_suffix: str = None, + run_suffix: str | None = None, log_images_every: int = 10, ): super().__init__(log_dir, run_prefix, run_suffix, log_images_every) From 4ebb1d9df4ee892afe862634838fa622e3632395 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Mar 2026 20:10:35 -0700 Subject: [PATCH 208/335] TomographyDataset quantile linter error fixed --- src/quantem/tomography/dataset_models.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index c1e362da..8c58f1c0 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -2,7 +2,6 @@ from dataclasses import dataclass from typing import Any -import numpy as np import torch import torch.nn as nn from numpy.typing import NDArray @@ -185,12 +184,11 @@ def __init__( "The number of tilt projections should be in the first dimension of the dataset." ) - # TODO: Maybe have the validation in here too. - max_val = np.quantile(tilt_stack, 0.95) if type(tilt_stack) is not torch.Tensor: tilt_stack = torch.from_numpy(tilt_stack) if type(tilt_angles) is not torch.Tensor: tilt_angles = torch.from_numpy(tilt_angles) + max_val = torch.quantile(tilt_stack, 0.95) # Tilt stack normalization tilt_stack = tilt_stack / max_val From 435e623ed5198640449587b71a4733104bd4470a Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Mar 2026 20:11:20 -0700 Subject: [PATCH 209/335] Linter error in .reconstruct --- src/quantem/tomography/tomography.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 7e9854ba..f8337986 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -64,7 +64,7 @@ def reconstruct( optimizer_params: dict | None = None, scheduler_params: dict | None = None, obj_constraints: dict | ObjConstraintsType | None = None, - dset_constraints: dict | DatasetConstraintsType = None, + dset_constraints: dict | DatasetConstraintsType | None = None, num_samples_per_ray: int | list[tuple[int, int]] | None = None, profiling_mode: bool = False, val_fraction: float = 0.0, From 5c10509bd602aedd60d1dbc9658f211897d6344e Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Mar 2026 20:34:44 -0700 Subject: [PATCH 210/335] Fixed all linter issues in top-level tomography.py --- src/quantem/tomography/logger_tomography.py | 3 +- src/quantem/tomography/object_models.py | 11 ++++++ src/quantem/tomography/radon/radon.py | 7 +++- src/quantem/tomography/tomography.py | 39 ++++++++++++++------- 4 files changed, 46 insertions(+), 14 deletions(-) diff --git a/src/quantem/tomography/logger_tomography.py b/src/quantem/tomography/logger_tomography.py index 9798214d..d148a0f5 100644 --- a/src/quantem/tomography/logger_tomography.py +++ b/src/quantem/tomography/logger_tomography.py @@ -1,4 +1,5 @@ import matplotlib.pyplot as plt +import numpy as np import torch from quantem.core.ml.logger import LoggerBase @@ -46,7 +47,7 @@ def log_iter( def log_iter_images( self, - pred_volume: torch.Tensor, + pred_volume: np.ndarray, dataset_model: DatasetModelType, iter: int, logger_cmap: str = "turbo", diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index dfedd0a1..c5eb2406 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -209,6 +209,13 @@ def obj(self) -> torch.Tensor: """ raise NotImplementedError + @property + def model(self) -> nn.Module: + """ + Returns the model, should be implemented in subclasses. + """ + raise NotImplementedError + @abstractmethod def dtype(self) -> torch.dtype: """ @@ -476,6 +483,10 @@ def model(self) -> nn.Module | nn.parallel.DistributedDataParallel: def obj(self) -> torch.Tensor: return self._obj + @obj.setter + def obj(self, obj: torch.Tensor): + self._obj = obj + @property def obj_view(self) -> np.ndarray: """ diff --git a/src/quantem/tomography/radon/radon.py b/src/quantem/tomography/radon/radon.py index 75c66b6f..8c2913d8 100644 --- a/src/quantem/tomography/radon/radon.py +++ b/src/quantem/tomography/radon/radon.py @@ -80,7 +80,12 @@ def radon_torch(images, theta=None, device=None): def iradon_torch( - sinograms, theta=None, output_size=None, filter_name="ramp", circle=True, device=None + sinograms, + theta=None, + output_size=None, + filter_name: str | None = "ramp", + circle=True, + device=None, ): if sinograms.ndim == 2: sinograms = sinograms.unsqueeze(0) diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index f8337986..907ad994 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -16,6 +16,8 @@ DatasetConstraintParams, DatasetConstraintsType, DatasetModelType, + TomographyINRDataset, + TomographyPixDataset, ) from quantem.tomography.logger_tomography import LoggerTomography from quantem.tomography.object_models import ( @@ -148,6 +150,13 @@ def reconstruct( val_fraction=val_fraction, ) ) + + # Type check for INR-based reconstruction + if not isinstance(self.dset, TomographyINRDataset): + raise NotImplementedError( + "Only TomographyINRDataset is supported for this reconstruction method." + ) + N = max(self.obj_model.shape) if num_samples_per_ray is None: @@ -167,10 +176,15 @@ def reconstruct( pbar = tqdm(range(num_iter), disable=not self.verbose) for a0 in pbar: - consistency_loss = 0.0 - total_loss = 0.0 - epoch_soft_constraint_loss = 0.0 - self.obj_model.model.train() + consistency_loss = torch.tensor(0.0, device=self.device) + total_loss = torch.tensor(0.0, device=self.device) + epoch_soft_constraint_loss = torch.tensor(0.0, device=self.device) + if isinstance(self.obj_model, ObjectINR): + self.obj_model.model.train() + else: + raise NotImplementedError( + "AD Pixelated reconstruction is not yet implemented. Use ObjectINR instead." + ) self.dset.train() # self._reset_iter_constraints() @@ -200,10 +214,9 @@ def reconstruct( ) pred = integrated_densities.float() + soft_constraints_loss = 0.0 if self.num_epochs > 0: soft_constraints_loss = self.obj_model.apply_soft_constraints(all_coords, pred) - else: - soft_constraints_loss = 0.0 target = batch["target_value"].to(self.device, non_blocking=True).float() @@ -234,12 +247,13 @@ def reconstruct( self.step_schedulers(loss=total_loss) # TODO: Maybe reorganize the losses so that the order makes sense lol. + avg_val_loss = None if self.val_dataloader is not None: print("Validating...") self.obj_model.model.eval() self.dset.eval() with torch.no_grad(): - val_loss = 0.0 + val_loss = torch.tensor(0.0, device=self.device) for batch in self.val_dataloader: with torch.autocast( @@ -265,7 +279,7 @@ def reconstruct( integrated_densities, target ) - val_loss += batch_val_loss.detach() + soft_constraints_loss.detach() + val_loss += batch_val_loss.detach() avg_val_loss = val_loss.item() / len(self.val_dataloader) @@ -286,7 +300,7 @@ def reconstruct( self._consistency_losses.append(consistency_loss) self.append_learning_rates(self.get_current_lrs()) self.obj_model._soft_constraint_losses.append(epoch_soft_constraint_loss) - if self.val_dataloader is not None: + if avg_val_loss is not None: self._val_losses.append(avg_val_loss) if self.logger is not None: @@ -294,7 +308,7 @@ def reconstruct( self.logger.log_images_every > 0 and self.num_epochs % self.logger.log_images_every == 0 ): - pred_full = self.obj_model.create_volume(return_vol=True) + pred_full = self.obj_model.obj_view if self.global_rank == 0: self.logger.log_iter_images( @@ -423,7 +437,7 @@ class TomographyConventional(TomographyBase): @classmethod def from_models( cls, - dset: DatasetModelType, + dset: TomographyPixDataset, obj_model: ObjectPixelated, logger: LoggerTomography | None = None, device: str = "cuda", @@ -516,13 +530,14 @@ def _reconstruction_epoch( gaussian_kernel: torch.Tensor | None = None, ): loss = 0 + if relaxation == 0.0: relaxation = self._adaptive_relaxation() print(f"Adaptive relaxation: {relaxation}") if inline_alignment: for ind in range(len(self.dset.tilt_angles)): im_proj = proj_forward[:, ind, :] - im_meas = self.dset.forward(ind).target + im_meas = self.dset.forward(ind).target # type: ignore shift = torch_phase_cross_correlation(im_proj, im_meas) if torch.linalg.norm(shift) <= 32: shifted = torch.fft.ifft2( From fc0c675521195eef62caa03cf1a60c71813c3304 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Mar 2026 20:35:31 -0700 Subject: [PATCH 211/335] logger_tomography.py type-hinting fix --- src/quantem/tomography/logger_tomography.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quantem/tomography/logger_tomography.py b/src/quantem/tomography/logger_tomography.py index d148a0f5..e07072f7 100644 --- a/src/quantem/tomography/logger_tomography.py +++ b/src/quantem/tomography/logger_tomography.py @@ -16,7 +16,7 @@ def __init__( self, log_dir: str, run_prefix: str, - run_suffix: str | None = None, + run_suffix: str = "", log_images_every: int = 10, ): super().__init__(log_dir, run_prefix, run_suffix, log_images_every) From 462e7a04e5647511f7275371e5f62f7e0d3f519f Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Mar 2026 20:39:53 -0700 Subject: [PATCH 212/335] dataset_models.py type-hinting --- src/quantem/tomography/dataset_models.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index 8c58f1c0..f6343e4e 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -342,7 +342,7 @@ def device(self, device: torch.device | str): # --- Helper Functions --- @abstractmethod - def to(self, device: torch.device | str): + def to(self, device: torch.device | str): # type: ignore """ Moves the dataset to the device, and also insantiates the aux params to the device. """ @@ -375,8 +375,11 @@ def apply_soft_constraints(self) -> torch.Tensor: soft_loss += tv_loss_shifts return soft_loss - def apply_hard_constraints(self): - pass + def apply_hard_constraints(self) -> torch.Tensor: + """ + No hard constraints have been implemented yet. + """ + return torch.tensor(0.0) class TomographyPixDataset(TomographyDatasetConstraints): @@ -402,7 +405,7 @@ def __init__( _token=_token, ) - def forward( + def forward( # type:ignore self, proj_idx: int, ) -> DatasetValue: @@ -413,7 +416,7 @@ def forward( return DatasetValue( target=self.tilt_stack[proj_idx], - tilt_angle=self.tilt_angles[proj_idx], + tilt_angle=self.tilt_angles[proj_idx].item(), pixel_loc=None, ) From 238bffdac323b4d1dea3bca7ce339dbba031a287 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Mar 2026 20:40:26 -0700 Subject: [PATCH 213/335] radon.py type-hinting fix --- src/quantem/tomography/radon/radon.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/quantem/tomography/radon/radon.py b/src/quantem/tomography/radon/radon.py index 8c2913d8..8510e570 100644 --- a/src/quantem/tomography/radon/radon.py +++ b/src/quantem/tomography/radon/radon.py @@ -149,7 +149,9 @@ def iradon_torch( return recon.squeeze(0) if B == 1 else recon -def get_fourier_filter_torch(size, filter_name="ramp", device=None, dtype=torch.float32): +def get_fourier_filter_torch( + size, filter_name: str | None = "ramp", device=None, dtype=torch.float32 +): """ Construct the Fourier filter in PyTorch. """ From 1b3b0cd81f9049fc287ac571a6aa9749fba87924 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Mar 2026 20:43:22 -0700 Subject: [PATCH 214/335] tomography_opt.py linting errors fixed by Claude --- src/quantem/tomography/tomography_opt.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/quantem/tomography/tomography_opt.py b/src/quantem/tomography/tomography_opt.py index 1dd406ab..16c846ab 100644 --- a/src/quantem/tomography/tomography_opt.py +++ b/src/quantem/tomography/tomography_opt.py @@ -1,6 +1,8 @@ +from collections.abc import Mapping + import torch -from quantem.core.ml.optimizer_mixin import OptimizerParams, OptimizerType +from quantem.core.ml.optimizer_mixin import OptimizerParams, OptimizerType, SchedulerType from quantem.tomography.tomography_base import TomographyBase @@ -25,7 +27,7 @@ def _get_default_lr(self, key: str) -> float: raise ValueError(f"Unknown optimization key: {key}") @property - def optimizer_params(self) -> dict[str, dict]: + def optimizer_params(self) -> dict[str, OptimizerType]: return { key: params for key, params in [ @@ -57,10 +59,14 @@ def optimizer_params(self, d: dict[str, OptimizerType] | dict[str, dict]): @property def optimizers(self) -> dict[str, torch.optim.Optimizer]: - return { - "object": self.obj_model.optimizer, - "pose": self.dset.optimizer, - } + optimizers = {} + + if self.obj_model.optimizer is not None: + optimizers["object"] = self.obj_model.optimizer + if self.dset.optimizer is not None: + optimizers["pose"] = self.dset.optimizer + + return optimizers def set_optimizers(self): for key, params in self.optimizer_params.items(): @@ -94,7 +100,7 @@ def remove_optimizer(self, key: str): raise ValueError(f"Unknown optimization key: {key}") @property - def scheduler_params(self) -> dict[str, dict]: + def scheduler_params(self) -> dict[str, SchedulerType]: """Returns the parameters used to set the schedulers.""" return { "object": self.obj_model.scheduler_params, @@ -129,7 +135,9 @@ def schedulers(self) -> dict[str, torch.optim.lr_scheduler._LRScheduler]: return schedulers - def set_schedulers(self, params: dict[str, dict], num_iter: int | None = None): + def set_schedulers( + self, params: Mapping[str, SchedulerType | dict], num_iter: int | None = None + ): for key, scheduler_params in params.items(): if key == "object": self.obj_model.set_scheduler(scheduler_params, num_iter=num_iter) From ab444888d646d657a09cadaf6f5489aa62604065 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Mar 2026 22:24:29 -0700 Subject: [PATCH 215/335] Removed tomography_old and put forkserver back in instead of spawn. --- src/quantem/core/ml/ddp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/quantem/core/ml/ddp.py b/src/quantem/core/ml/ddp.py index dedd90dd..351d703d 100644 --- a/src/quantem/core/ml/ddp.py +++ b/src/quantem/core/ml/ddp.py @@ -102,7 +102,7 @@ def setup_dataloader( pin_memory=pin_mem, drop_last=True, persistent_workers=persist, - multiprocessing_context="spawn", + multiprocessing_context="forkserver", worker_init_fn=worker_init_fn, ) @@ -116,7 +116,7 @@ def setup_dataloader( pin_memory=pin_mem, drop_last=False, persistent_workers=persist, - multiprocessing_context="spawn", + multiprocessing_context="forkserver", worker_init_fn=worker_init_fn, ) val_dataloader = val_dataloader From 8d85f30b228b0a09231d25961738dad5ac50355d Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Mon, 23 Mar 2026 22:25:34 -0700 Subject: [PATCH 216/335] Nevermind back to spawn, DataLoader doesn't like to be in forkserver --- src/quantem/core/ml/ddp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/quantem/core/ml/ddp.py b/src/quantem/core/ml/ddp.py index 351d703d..dedd90dd 100644 --- a/src/quantem/core/ml/ddp.py +++ b/src/quantem/core/ml/ddp.py @@ -102,7 +102,7 @@ def setup_dataloader( pin_memory=pin_mem, drop_last=True, persistent_workers=persist, - multiprocessing_context="forkserver", + multiprocessing_context="spawn", worker_init_fn=worker_init_fn, ) @@ -116,7 +116,7 @@ def setup_dataloader( pin_memory=pin_mem, drop_last=False, persistent_workers=persist, - multiprocessing_context="forkserver", + multiprocessing_context="spawn", worker_init_fn=worker_init_fn, ) val_dataloader = val_dataloader From 96c1665cad674dcf9224805219b97da76ba57725 Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Tue, 24 Mar 2026 10:27:27 -0700 Subject: [PATCH 217/335] forgot to include the new file whoops --- src/quantem/core/visualization/show_params.py | 189 ++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 src/quantem/core/visualization/show_params.py diff --git a/src/quantem/core/visualization/show_params.py b/src/quantem/core/visualization/show_params.py new file mode 100644 index 00000000..cef2ec5a --- /dev/null +++ b/src/quantem/core/visualization/show_params.py @@ -0,0 +1,189 @@ +from dataclasses import dataclass, fields +from typing import Literal, Optional + +from quantem.core.visualization.custom_normalizations import NormalizationConfig +from quantem.core.visualization.visualization_utils import ScalebarConfig + + +class ShowParams: + """ + Container for ``show_2d`` parameter dataclasses. + + Nested classes + -------------- + Norm + Normalization configuration (interval + stretch). + Scalebar + Scale bar overlay configuration. + + Examples + -------- + >>> show_2d(img, norm=ShowParams.Norm(power=0.5)) + >>> show_2d(img, scalebar=ShowParams.Scalebar(sampling=0.5, units="Å")) + >>> show_2d(dp, norm=ShowParams.Norm.log_auto(), cbar=True, cmap="turbo") + """ + + @dataclass + class Norm: + """ + Normalization parameters for ``show_2d``. + + Controls how pixel values are mapped to the [0, 1] display range via + an *interval* (which values to keep) and a *stretch* (non-linear + transfer function). + + If ``vmin`` or ``vmax`` is set and ``interval_type`` is left as the + default ``"quantile"``, it is automatically changed to ``"manual"``. + Likewise, setting ``vcenter`` to a non-zero value or providing + ``half_range`` auto-selects ``"centered"``. + + Parameters + ---------- + interval_type : ``"quantile"`` | ``"manual"`` | ``"centered"`` + How to determine the data range. + stretch_type : ``"linear"`` | ``"power"`` | ``"logarithmic"`` | ``"asinh"`` + Transfer function applied after interval mapping. + lower_quantile : float + Lower quantile for ``"quantile"`` interval. Default 0.02. + upper_quantile : float + Upper quantile for ``"quantile"`` interval. Default 0.98. + vmin : float or None + Explicit minimum for ``"manual"`` interval. + vmax : float or None + Explicit maximum for ``"manual"`` interval. + vcenter : float + Centre value for ``"centered"`` interval. Default 0.0. + half_range : float or None + Symmetric half-range for ``"centered"`` interval. + power : float + Exponent for ``"power"`` stretch (e.g. 0.5 = sqrt). Default 1.0. + logarithmic_index : float + Index *a* for ``"logarithmic"`` stretch: ``log(a*x+1)/log(a+1)``. + Default 1000. + asinh_linear_range : float + Transition parameter *a* for ``"asinh"`` stretch. Default 0.1. + + Examples + -------- + >>> ShowParams.Norm() # quantile + linear (default) + >>> ShowParams.Norm(power=0.5) # quantile + sqrt stretch + >>> ShowParams.Norm(vmin=0, vmax=1000) # auto → manual range + >>> ShowParams.Norm.log_auto() # quantile + log stretch + >>> ShowParams.Norm.centered(half_range=5) # centered ± 5, linear + """ + + interval_type: Literal["quantile", "manual", "centered"] = "quantile" + stretch_type: Literal["linear", "power", "logarithmic", "asinh"] = "linear" + lower_quantile: float = 0.02 + upper_quantile: float = 0.98 + vmin: Optional[float] = None + vmax: Optional[float] = None + vcenter: float = 0.0 + half_range: Optional[float] = None + power: float = 1.0 + logarithmic_index: float = 1000.0 + asinh_linear_range: float = 0.1 + + def __post_init__(self) -> None: + if self.interval_type != "quantile": + return + if self.vmin is not None or self.vmax is not None: + self.interval_type = "manual" + elif self.vcenter != 0.0 or self.half_range is not None: + self.interval_type = "centered" + + def to_config(self) -> NormalizationConfig: + """Convert to a ``NormalizationConfig``.""" + return NormalizationConfig(**{f.name: getattr(self, f.name) for f in fields(self)}) + + # ---- presets (mirror NORMALIZATION_PRESETS) ---- + + @classmethod + def linear_auto(cls, **kw) -> "ShowParams.Norm": + """Quantile interval + linear stretch (the default).""" + return cls(**kw) + + @classmethod + def minmax(cls, **kw) -> "ShowParams.Norm": + """Full min/max interval + linear stretch.""" + return cls(interval_type="manual", **kw) + + @classmethod + def centered( + cls, vcenter: float = 0.0, half_range: Optional[float] = None, **kw + ) -> "ShowParams.Norm": + """Centered interval + linear stretch.""" + return cls(interval_type="centered", vcenter=vcenter, half_range=half_range, **kw) + + @classmethod + def log_auto(cls, **kw) -> "ShowParams.Norm": + """Quantile interval + logarithmic stretch.""" + return cls(stretch_type="logarithmic", **kw) + + @classmethod + def log_minmax(cls, **kw) -> "ShowParams.Norm": + """Full min/max interval + logarithmic stretch.""" + return cls(interval_type="manual", stretch_type="logarithmic", **kw) + + @classmethod + def power_sqrt(cls, **kw) -> "ShowParams.Norm": + """Quantile interval + square-root (power=0.5) stretch.""" + return cls(stretch_type="power", power=0.5, **kw) + + @classmethod + def power_squared(cls, **kw) -> "ShowParams.Norm": + """Quantile interval + squared (power=2.0) stretch.""" + return cls(stretch_type="power", power=2.0, **kw) + + @classmethod + def asinh_centered(cls, vcenter: float = 0.0, **kw) -> "ShowParams.Norm": + """Centered interval + asinh stretch.""" + return cls(interval_type="centered", stretch_type="asinh", vcenter=vcenter, **kw) + + @dataclass + class Scalebar: + """ + Scale bar parameters for ``show_2d``. + + Parameters + ---------- + sampling : float + Physical units per pixel. Default 1.0. + units : str + Unit label displayed on the scale bar (e.g. ``"Å"``, ``"nm"``, + ``"1/Å"``). Default ``"pixels"``. + length : float or None + Fixed scale bar length in physical units. ``None`` auto-estimates + a "nice" length. + width_px : float + Thickness of the bar in image pixels. Default 1. + pad_px : float + Padding between bar and plot edge in image pixels. Default 0.5. + color : str + Bar and label colour. Default ``"white"``. + loc : ``"lower right"`` | ``"lower left"`` | ``"upper right"`` | ``"upper left"`` + Anchor location. Default ``"lower right"``. + fontsize : int + Font size of the scale bar label in points. Default 12. + bold : bool + Whether to render the label in bold. Default True. + + Examples + -------- + >>> ShowParams.Scalebar(sampling=0.5, units="Å") + >>> ShowParams.Scalebar(sampling=0.02, units="1/Å", color="black", fontsize=16) + """ + + sampling: float = 1.0 + units: str = "pixels" + length: Optional[float] = None + width_px: float = 1 + pad_px: float = 0.5 + color: str = "white" + loc: Literal["lower right", "lower left", "upper right", "upper left"] = "lower right" + fontsize: int = 12 + bold: bool = False + + def to_config(self) -> ScalebarConfig: + """Convert to a ``ScalebarConfig``.""" + return ScalebarConfig(**{f.name: getattr(self, f.name) for f in fields(self)}) From c60a1302609314c1fa01ebe5af2442b091a9dce0 Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Tue, 24 Mar 2026 10:45:49 -0700 Subject: [PATCH 218/335] adding checks for incompatible arguments and validators --- src/quantem/core/utils/validators.py | 17 ++- src/quantem/core/visualization/show_params.py | 102 ++++++++++++++++-- 2 files changed, 110 insertions(+), 9 deletions(-) diff --git a/src/quantem/core/utils/validators.py b/src/quantem/core/utils/validators.py index 02f8a935..50361e25 100644 --- a/src/quantem/core/utils/validators.py +++ b/src/quantem/core/utils/validators.py @@ -10,8 +10,9 @@ from quantem.core.utils import array_funcs as af if TYPE_CHECKING: - import cupy as cp + import cupy as cp # type: ignore import torch + TensorLike: TypeAlias = ArrayLike | torch.Tensor else: TensorLike: TypeAlias = ArrayLike @@ -20,9 +21,10 @@ if config.get("has_cupy"): import cupy as cp + # --- Dataset Validation Functions --- def ensure_valid_array( - array: "np.ndarray | cp.ndarray", dtype: DTypeLike = None, ndim: int | None = None + array: "np.ndarray | cp.ndarray", dtype: DTypeLike | None = None, ndim: int | None = None ) -> Union[np.ndarray, "cp.ndarray"]: """ Ensure input is a numpy array or cupy array (if available), converting if necessary. @@ -359,6 +361,17 @@ def validate_gt( return value +def validate_lt( + value: float | int, cutoff: float | int, name: str, leq: bool = False +) -> float | int: + if leq: # less than or equal to + if value > cutoff: + raise ValueError(f"{name} must be less than or equal to {cutoff}, got {value}") + elif value >= cutoff: + raise ValueError(f"{name} must be less than {cutoff}, got {value}") + return value + + def validate_int(value: int | float, name: str) -> int: try: return int(round(value)) diff --git a/src/quantem/core/visualization/show_params.py b/src/quantem/core/visualization/show_params.py index cef2ec5a..ed7a21df 100644 --- a/src/quantem/core/visualization/show_params.py +++ b/src/quantem/core/visualization/show_params.py @@ -1,6 +1,8 @@ +import warnings from dataclasses import dataclass, fields from typing import Literal, Optional +from quantem.core.utils.validators import validate_gt, validate_lt from quantem.core.visualization.custom_normalizations import NormalizationConfig from quantem.core.visualization.visualization_utils import ScalebarConfig @@ -72,7 +74,7 @@ class Norm: >>> ShowParams.Norm.centered(half_range=5) # centered ± 5, linear """ - interval_type: Literal["quantile", "manual", "centered"] = "quantile" + interval_type: Optional[Literal["quantile", "manual", "centered"]] = None stretch_type: Literal["linear", "power", "logarithmic", "asinh"] = "linear" lower_quantile: float = 0.02 upper_quantile: float = 0.98 @@ -85,12 +87,91 @@ class Norm: asinh_linear_range: float = 0.1 def __post_init__(self) -> None: - if self.interval_type != "quantile": - return - if self.vmin is not None or self.vmax is not None: - self.interval_type = "manual" - elif self.vcenter != 0.0 or self.half_range is not None: - self.interval_type = "centered" + manual_set = self.vmin is not None or self.vmax is not None + centered_set = self.vcenter != 0.0 or self.half_range is not None + quantile_set = self.lower_quantile != 0.02 or self.upper_quantile != 0.98 + user_chose = self.interval_type is not None + + # --- auto-infer interval_type when not explicitly provided --- + if not user_chose: + if manual_set and centered_set: + warnings.warn( + "Both vmin/vmax and vcenter/half_range were set; " + "defaulting to interval_type='manual'.", + stacklevel=2, + ) + self.interval_type = "manual" + elif manual_set: + self.interval_type = "manual" + elif centered_set: + self.interval_type = "centered" + else: + self.interval_type = "quantile" + + # --- warn about ignored interval fields --- + if self.interval_type == "manual" and quantile_set: + warnings.warn( + "lower_quantile/upper_quantile are ignored when interval_type='manual'.", + stacklevel=2, + ) + if self.interval_type == "manual" and centered_set: + warnings.warn( + "vcenter/half_range are ignored when interval_type='manual'.", + stacklevel=2, + ) + if self.interval_type == "quantile" and manual_set: + warnings.warn( + "vmin/vmax are ignored when interval_type='quantile'.", + stacklevel=2, + ) + if self.interval_type == "quantile" and centered_set: + warnings.warn( + "vcenter/half_range are ignored when interval_type='quantile'.", + stacklevel=2, + ) + if self.interval_type == "centered" and manual_set: + warnings.warn( + "vmin/vmax are ignored when interval_type='centered'.", + stacklevel=2, + ) + if self.interval_type == "centered" and quantile_set: + warnings.warn( + "lower_quantile/upper_quantile are ignored when interval_type='centered'.", + stacklevel=2, + ) + + # --- warn about ignored stretch fields --- + if self.power != 1.0 and self.stretch_type not in ("power", "linear"): + warnings.warn( + f"power={self.power} is ignored when stretch_type='{self.stretch_type}'.", + stacklevel=2, + ) + if self.logarithmic_index != 1000.0 and self.stretch_type != "logarithmic": + warnings.warn( + f"logarithmic_index={self.logarithmic_index} is ignored " + f"when stretch_type='{self.stretch_type}'.", + stacklevel=2, + ) + if self.asinh_linear_range != 0.1 and self.stretch_type != "asinh": + warnings.warn( + f"asinh_linear_range={self.asinh_linear_range} is ignored " + f"when stretch_type='{self.stretch_type}'.", + stacklevel=2, + ) + + # --- invalid value checks --- + if self.vmin is not None and self.vmax is not None: + validate_gt(self.vmax, self.vmin, "vmax", geq=False) + validate_gt(self.lower_quantile, 0, "lower_quantile", geq=True) + validate_gt(self.upper_quantile, self.lower_quantile, "upper_quantile") + validate_lt(self.upper_quantile, 1.0, "upper_quantile", leq=True) + if self.upper_quantile > 1.0: + raise ValueError(f"upper_quantile must be <= 1, got {self.upper_quantile}.") + if self.half_range is not None: + validate_gt(self.half_range, 0, "half_range", geq=True) + validate_gt(self.power, 0, "power") + validate_gt(self.logarithmic_index, 0, "logarithmic_index") + validate_gt(self.asinh_linear_range, 0, "asinh_linear_range") def to_config(self) -> NormalizationConfig: """Convert to a ``NormalizationConfig``.""" @@ -184,6 +265,13 @@ class Scalebar: fontsize: int = 12 bold: bool = False + def __post_init__(self) -> None: + validate_gt(self.sampling, 0, "sampling") + if self.length is not None: + validate_gt(self.length, 0, "length") + validate_gt(self.width_px, 0, "width_px") + validate_gt(self.fontsize, 0, "fontsize") + def to_config(self) -> ScalebarConfig: """Convert to a ``ScalebarConfig``.""" return ScalebarConfig(**{f.name: getattr(self, f.name) for f in fields(self)}) From ccaf70c3e602ed914659fcf996dee4434f1ac33d Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Tue, 24 Mar 2026 10:59:48 -0700 Subject: [PATCH 219/335] updating type hinting to 3.11+ --- src/quantem/core/visualization/show_params.py | 14 ++++---- .../core/visualization/visualization.py | 30 ++++++++--------- .../core/visualization/visualization_utils.py | 33 ++++++++++--------- 3 files changed, 38 insertions(+), 39 deletions(-) diff --git a/src/quantem/core/visualization/show_params.py b/src/quantem/core/visualization/show_params.py index ed7a21df..d0399db9 100644 --- a/src/quantem/core/visualization/show_params.py +++ b/src/quantem/core/visualization/show_params.py @@ -1,6 +1,6 @@ import warnings from dataclasses import dataclass, fields -from typing import Literal, Optional +from typing import Literal from quantem.core.utils.validators import validate_gt, validate_lt from quantem.core.visualization.custom_normalizations import NormalizationConfig @@ -74,14 +74,14 @@ class Norm: >>> ShowParams.Norm.centered(half_range=5) # centered ± 5, linear """ - interval_type: Optional[Literal["quantile", "manual", "centered"]] = None + interval_type: Literal["quantile", "manual", "centered"] | None = None stretch_type: Literal["linear", "power", "logarithmic", "asinh"] = "linear" lower_quantile: float = 0.02 upper_quantile: float = 0.98 - vmin: Optional[float] = None - vmax: Optional[float] = None + vmin: float | None = None + vmax: float | None = None vcenter: float = 0.0 - half_range: Optional[float] = None + half_range: float | None = None power: float = 1.0 logarithmic_index: float = 1000.0 asinh_linear_range: float = 0.1 @@ -191,7 +191,7 @@ def minmax(cls, **kw) -> "ShowParams.Norm": @classmethod def centered( - cls, vcenter: float = 0.0, half_range: Optional[float] = None, **kw + cls, vcenter: float = 0.0, half_range: float | None = None, **kw ) -> "ShowParams.Norm": """Centered interval + linear stretch.""" return cls(interval_type="centered", vcenter=vcenter, half_range=half_range, **kw) @@ -257,7 +257,7 @@ class Scalebar: sampling: float = 1.0 units: str = "pixels" - length: Optional[float] = None + length: float | None = None width_px: float = 1 pad_px: float = 0.5 color: str = "white" diff --git a/src/quantem/core/visualization/visualization.py b/src/quantem/core/visualization/visualization.py index 72400527..6cb45e44 100644 --- a/src/quantem/core/visualization/visualization.py +++ b/src/quantem/core/visualization/visualization.py @@ -3,11 +3,12 @@ import os import warnings from collections.abc import Sequence -from typing import TYPE_CHECKING, Any, Optional, Union, cast +from typing import Any, cast import matplotlib as mpl import matplotlib.pyplot as plt import numpy as np +import torch from matplotlib import colors from mpl_toolkits.axes_grid1 import make_axes_locatable from numpy.typing import NDArray @@ -30,22 +31,19 @@ list_of_arrays_to_rgba, ) -if TYPE_CHECKING: - import torch - -ArrayLike = Union[NDArray, "torch.Tensor"] +ArrayLike = NDArray | torch.Tensor def _show_2d_array( array: NDArray, *, - norm: Optional[Union[NormalizationConfig, ShowParams.Norm, dict, str]] = None, - scalebar: Optional[Union[ScalebarConfig, ShowParams.Scalebar, dict, bool]] = None, - cmap: Union[str, colors.Colormap] = "gray", + norm: NormalizationConfig | ShowParams.Norm | dict | str | None = None, + scalebar: ScalebarConfig | ShowParams.Scalebar | dict | bool | None = None, + cmap: str | colors.Colormap = "gray", chroma_boost: float = 1.0, cbar: bool = False, - title: Optional[str] = None, - figax: Optional[tuple[Any, Any]] = None, + title: str | None = None, + figax: tuple[Any, Any] | None = None, figsize: tuple[int, int] = (8, 8), show_ticks: bool = False, **kwargs: Any, @@ -173,14 +171,14 @@ def _show_2d_array( def _show_2d_combined( list_of_arrays: Sequence[NDArray], *, - norm: Optional[Union[NormalizationConfig, ShowParams.Norm, dict, str]] = None, - scalebar: Optional[Union[ScalebarConfig, ShowParams.Scalebar, dict, bool]] = None, - cmap: Union[str, colors.Colormap] = "gray", + norm: NormalizationConfig | ShowParams.Norm | dict | str | None = None, + scalebar: ScalebarConfig | ShowParams.Scalebar | dict | bool | None = None, + cmap: str | colors.Colormap = "gray", chroma_boost: float = 1.0, cbar: bool = False, - figax: Optional[tuple[Any, Any]] = None, + figax: tuple[Any, Any] | None = None, figsize: tuple[int, int] = (8, 8), - title: Optional[str] = None, + title: str | None = None, show_ticks: bool = False, **kwargs: Any, ) -> tuple[Any, Any]: @@ -285,7 +283,7 @@ def _show_2d_combined( def _normalize_show_input_to_grid( - arrays: Any, # Union[NDArray, Sequence[NDArray], Sequence[Sequence[NDArray]]], + arrays: Any, # NDArray | Sequence[NDArray] | Sequence[Sequence[NDArray]] ) -> list[list[NDArray]]: """Convert various input formats to a consistent grid format for visualization. diff --git a/src/quantem/core/visualization/visualization_utils.py b/src/quantem/core/visualization/visualization_utils.py index d3111476..6b968708 100644 --- a/src/quantem/core/visualization/visualization_utils.py +++ b/src/quantem/core/visualization/visualization_utils.py @@ -1,7 +1,8 @@ from dataclasses import dataclass -from typing import Any, List, Optional, Sequence, Tuple, Union, cast +from typing import Any, cast import matplotlib as mpl +import matplotlib.pyplot as plt import numpy as np from colorspacious import cspace_convert from matplotlib import cm, colors, legend, ticker @@ -18,9 +19,9 @@ def array_to_rgba( scaled_amplitude: NDArray, - scaled_angle: Optional[NDArray] = None, + scaled_angle: NDArray | None = None, *, - cmap: Union[str, colors.Colormap] = "gray", + cmap: str | colors.Colormap = "gray", chroma_boost: float = 1, ) -> NDArray: """Convert amplitude and angle arrays to an RGBA color array. @@ -73,7 +74,7 @@ def array_to_rgba( def list_of_arrays_to_rgba( - list_of_arrays: List[NDArray], + list_of_arrays: list[NDArray], *, norm: CustomNormalization = CustomNormalization(), chroma_boost: float = 1, @@ -147,11 +148,11 @@ class ScalebarConfig: sampling: float = 1.0 units: str = "pixels" - length: Optional[float] = None + length: float | None = None width_px: float = 1 pad_px: float = 0.5 color: str = "white" - loc: Union[str, int] = "lower right" + loc: str | int = "lower right" fontsize: int = 12 bold: bool = False @@ -162,7 +163,7 @@ class ScalebarConfig: ] -def _resolve_scalebar(cfg: Any, **kwargs) -> Optional[ScalebarConfig]: +def _resolve_scalebar(cfg: Any, **kwargs) -> ScalebarConfig | None: """Resolve various input types to a ScalebarConfig object. Parameters @@ -204,7 +205,7 @@ def _resolve_scalebar(cfg: Any, **kwargs) -> Optional[ScalebarConfig]: ) -def estimate_scalebar_length(length: float, sampling: float) -> Tuple[float, float]: +def estimate_scalebar_length(length: float, sampling: float) -> tuple[float, float]: """Estimate an appropriate scale bar length based on data dimensions. This function calculates a "nice" scale bar length that is a multiple of @@ -277,12 +278,12 @@ def add_scalebar_to_ax( ax: Axes, array_size: float, sampling: float, - length_units: Optional[float], + length_units: float | None, units: str, width_px: float, pad_px: float, color: str, - loc: Union[str, int], + loc: str | int, fontsize: int = 12, bold: bool = True, ) -> None: @@ -444,7 +445,7 @@ def add_arg_cbar_to_ax( return cb_angle -def turbo_black(num_colors: int = 256, fade_len: Optional[int] = None) -> colors.ListedColormap: +def turbo_black(num_colors: int = 256, fade_len: int | None = None) -> colors.ListedColormap: """Create a modified version of the 'turbo' colormap that fades to black. This function creates a colormap based on the 'turbo' colormap but with @@ -483,12 +484,12 @@ def turbo_black(num_colors: int = 256, fade_len: Optional[int] = None) -> colors def bilinear_histogram_2d( - shape: Tuple[int, int], + shape: tuple[int, int], x: NDArray, y: NDArray, weight: NDArray, - origin: Tuple[float, float] = (0.0, 0.0), - sampling: Tuple[float, float] = (1.0, 1.0), + origin: tuple[float, float] = (0.0, 0.0), + sampling: tuple[float, float] = (1.0, 1.0), statistic: str = "sum", ) -> NDArray: """Create a 2D histogram with bilinear binning. @@ -533,7 +534,7 @@ def bilinear_histogram_2d( ) # Convert shape tuple to list for binned_statistic_2d - bins: Sequence[int] = [Nx, Ny] + bins: list[int] = [Nx, Ny] hist, _, _, _ = binned_statistic_2d( x, y, @@ -568,7 +569,7 @@ def axes_with_inset( - Fractional inset by default (relative to main axes size). - Only the inset axes background is set to black (main axes stays default). """ - fig, ax_main = mpl.pyplot.subplots(1, 1, figsize=axsize) + fig, ax_main = plt.subplots(1, 1, figsize=axsize) # lazy import here (some environments need it this way) from mpl_toolkits.axes_grid1.inset_locator import inset_axes From 390a81cb1fcd2f90784cea0864ddbd40bf76e62b Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Tue, 24 Mar 2026 11:39:10 -0700 Subject: [PATCH 220/335] bugfix vmax and refactor list to combine --- src/quantem/core/visualization/visualization.py | 6 +++--- .../core/visualization/visualization_utils.py | 2 +- tests/visualization/test_visualization_utils.py | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/quantem/core/visualization/visualization.py b/src/quantem/core/visualization/visualization.py index 6cb45e44..df559af4 100644 --- a/src/quantem/core/visualization/visualization.py +++ b/src/quantem/core/visualization/visualization.py @@ -28,7 +28,7 @@ add_cbar_to_ax, add_scalebar_to_ax, array_to_rgba, - list_of_arrays_to_rgba, + combine_arrays_to_rgba, ) ArrayLike = NDArray | torch.Tensor @@ -233,7 +233,7 @@ def _show_2d_combined( lower_quantile=norm_config.lower_quantile, upper_quantile=norm_config.upper_quantile, vmin=norm_config.vmin, - vmax=norm_config.vmin, + vmax=norm_config.vmax, vcenter=norm_config.vcenter, half_range=norm_config.half_range, power=norm_config.power, @@ -243,7 +243,7 @@ def _show_2d_combined( # Convert Sequence to list for list_of_arrays_to_rgba list_of_arrays_list = list(list_of_arrays) - rgba = list_of_arrays_to_rgba( + rgba = combine_arrays_to_rgba( list_of_arrays_list, norm=norm_obj, chroma_boost=chroma_boost, diff --git a/src/quantem/core/visualization/visualization_utils.py b/src/quantem/core/visualization/visualization_utils.py index 6b968708..4b8ebae5 100644 --- a/src/quantem/core/visualization/visualization_utils.py +++ b/src/quantem/core/visualization/visualization_utils.py @@ -73,7 +73,7 @@ def array_to_rgba( return rgba -def list_of_arrays_to_rgba( +def combine_arrays_to_rgba( list_of_arrays: list[NDArray], *, norm: CustomNormalization = CustomNormalization(), diff --git a/tests/visualization/test_visualization_utils.py b/tests/visualization/test_visualization_utils.py index ca114774..1617f0a4 100644 --- a/tests/visualization/test_visualization_utils.py +++ b/tests/visualization/test_visualization_utils.py @@ -14,8 +14,8 @@ add_scalebar_to_ax, array_to_rgba, bilinear_histogram_2d, + combine_arrays_to_rgba, estimate_scalebar_length, - list_of_arrays_to_rgba, turbo_black, ) @@ -85,14 +85,14 @@ def test_array_to_rgba_shape_mismatch(self, sample_array): array_to_rgba(sample_array, wrong_shape) -class TestListOfArraysToRGBA: - def test_list_of_arrays_to_rgba(self, sample_arrays): - rgba = list_of_arrays_to_rgba(sample_arrays) +class TestCombineArraysToRGBA: + def test_combine_arrays_to_rgba(self, sample_arrays): + rgba = combine_arrays_to_rgba(sample_arrays) assert rgba.shape == (*sample_arrays[0].shape, 4) assert np.all(rgba[..., 3] == 1) # alpha channel should be 1 - def test_list_of_arrays_to_rgba_with_chroma_boost(self, sample_arrays): - rgba = list_of_arrays_to_rgba(sample_arrays, chroma_boost=2.0) + def test_combine_arrays_to_rgba_with_chroma_boost(self, sample_arrays): + rgba = combine_arrays_to_rgba(sample_arrays, chroma_boost=2.0) assert rgba.shape == (*sample_arrays[0].shape, 4) From 7c2735fad1e4d01e612de18b839136131f3745c6 Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Tue, 24 Mar 2026 12:58:44 -0700 Subject: [PATCH 221/335] fixing type hinting and potential circular import --- src/quantem/core/visualization/__init__.py | 1 + src/quantem/core/visualization/visualization.py | 14 +++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/quantem/core/visualization/__init__.py b/src/quantem/core/visualization/__init__.py index d6f8dc95..125fb02e 100644 --- a/src/quantem/core/visualization/__init__.py +++ b/src/quantem/core/visualization/__init__.py @@ -9,3 +9,4 @@ turbo_black as turbo_black, axes_with_inset as axes_with_inset, ) +from quantem.core.visualization.line_scan import linescan as linescan diff --git a/src/quantem/core/visualization/visualization.py b/src/quantem/core/visualization/visualization.py index df559af4..c45e4daf 100644 --- a/src/quantem/core/visualization/visualization.py +++ b/src/quantem/core/visualization/visualization.py @@ -1,9 +1,9 @@ -from __future__ import annotations +# from __future__ import annotations import os import warnings from collections.abc import Sequence -from typing import Any, cast +from typing import TYPE_CHECKING, Any, TypeAlias, Union, cast import matplotlib as mpl import matplotlib.pyplot as plt @@ -31,7 +31,10 @@ combine_arrays_to_rgba, ) -ArrayLike = NDArray | torch.Tensor +if TYPE_CHECKING: + from quantem.core.datastructures import Dataset2d + +ArrayLike: TypeAlias = Union[NDArray, torch.Tensor, "Dataset2d"] # union required here def _show_2d_array( @@ -404,12 +407,14 @@ def _normalize_show_args_to_grid( | dict | str | Sequence[NormalizationConfig | ShowParams.Norm | dict | str] + | Sequence[Sequence[NormalizationConfig | ShowParams.Norm | dict | str]] | None = None, scalebar: ScalebarConfig | ShowParams.Scalebar | dict | bool | Sequence[ScalebarConfig | ShowParams.Scalebar | dict | bool | None] + | Sequence[Sequence[ScalebarConfig | ShowParams.Scalebar | dict | bool | None]] | None = None, cmap: str | colors.Colormap | Sequence[str] | Sequence[Sequence[str]] = "gray", cbar: bool | Sequence[bool] | Sequence[Sequence[bool]] = False, @@ -444,6 +449,7 @@ def _normalize_show_args_to_grid( return args +# the type hinting is a bit of a mess, but not sure how to improve it def show_2d( arrays: ArrayLike | Sequence[ArrayLike] | Sequence[Sequence[ArrayLike]], *, @@ -453,6 +459,7 @@ def show_2d( | dict | str | Sequence[NormalizationConfig | ShowParams.Norm | dict | str] + | Sequence[Sequence[NormalizationConfig | ShowParams.Norm | dict | str]] | None ) = None, scalebar: ScalebarConfig @@ -460,6 +467,7 @@ def show_2d( | dict | bool | Sequence[ScalebarConfig | ShowParams.Scalebar | dict | bool | None] + | Sequence[Sequence[ScalebarConfig | ShowParams.Scalebar | dict | bool | None]] | None = None, cmap: str | colors.Colormap | Sequence[str] | Sequence[Sequence[str]] = "gray", cbar: bool | Sequence[bool] | Sequence[Sequence[bool]] = False, From ee204ab3e38c9056bc559812e7a6d84dcddc7720 Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Tue, 24 Mar 2026 13:09:38 -0700 Subject: [PATCH 222/335] fixing arg passing to _show_combined --- .../core/visualization/visualization.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/quantem/core/visualization/visualization.py b/src/quantem/core/visualization/visualization.py index c45e4daf..844f0e25 100644 --- a/src/quantem/core/visualization/visualization.py +++ b/src/quantem/core/visualization/visualization.py @@ -176,7 +176,6 @@ def _show_2d_combined( *, norm: NormalizationConfig | ShowParams.Norm | dict | str | None = None, scalebar: ScalebarConfig | ShowParams.Scalebar | dict | bool | None = None, - cmap: str | colors.Colormap = "gray", chroma_boost: float = 1.0, cbar: bool = False, figax: tuple[Any, Any] | None = None, @@ -591,7 +590,22 @@ def show_2d( if kwargs.pop("combine_images", False): if nrows > 1: raise ValueError() - fig, axs = _show_2d_combined(grid[0], figax=figax, **kwargs) # TODO pass args here + if isinstance(norm, Sequence): # flatten norm + norm = norm[0] if not isinstance(norm[0], Sequence) else norm[0][0] + if isinstance(scalebar, Sequence): # flatten scalebar + scalebar = scalebar[0] if not isinstance(scalebar[0], Sequence) else scalebar[0][0] + if isinstance(title, Sequence) and not isinstance(title, str): # flatten title + title = title[0] if isinstance(title[0], str) else title[0][0] + + fig, axs = _show_2d_combined( + grid[0], + norm=norm, + scalebar=scalebar, + figax=figax, + title=title, + figsize=kwargs.pop("figsize", axsize), + **kwargs, + ) else: normalized_args = _normalize_show_args_to_grid( shape=(nrows, ncols), From b63997e11125adfb45396b9dd8e5e37df4a16398 Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Tue, 24 Mar 2026 14:53:53 -0700 Subject: [PATCH 223/335] adding inverse units picking --- .../core/visualization/visualization.py | 26 ++-- .../core/visualization/visualization_utils.py | 113 ++++++++++++++---- 2 files changed, 104 insertions(+), 35 deletions(-) diff --git a/src/quantem/core/visualization/visualization.py b/src/quantem/core/visualization/visualization.py index 844f0e25..bb32d480 100644 --- a/src/quantem/core/visualization/visualization.py +++ b/src/quantem/core/visualization/visualization.py @@ -131,10 +131,10 @@ def _show_2d_array( ax.imshow(rgba, interpolation=config.get("viz.interpolation")) - if show_ticks: - ax.set(title=title) - else: - ax.set(xticks=[], yticks=[], title=title) + if title is not None: + ax.set_title(title, fontsize=kwargs.get("title_fontsize", 12)) + if not show_ticks: + ax.set(xticks=[], yticks=[]) if cbar: divider = make_axes_locatable(ax) @@ -258,10 +258,10 @@ def _show_2d_combined( ax.imshow(rgba, interpolation=config.get("viz.interpolation")) - if show_ticks: - ax.set(title=title) - else: - ax.set(xticks=[], yticks=[], title=title) + if title is not None: + ax.set_title(title, fontsize=kwargs.get("title_fontsize", 12)) + if not show_ticks: + ax.set(xticks=[], yticks=[]) if cbar: raise NotImplementedError() @@ -415,7 +415,10 @@ def _normalize_show_args_to_grid( | Sequence[ScalebarConfig | ShowParams.Scalebar | dict | bool | None] | Sequence[Sequence[ScalebarConfig | ShowParams.Scalebar | dict | bool | None]] | None = None, - cmap: str | colors.Colormap | Sequence[str] | Sequence[Sequence[str]] = "gray", + cmap: str + | colors.Colormap + | Sequence[str | colors.Colormap] + | Sequence[Sequence[str | colors.Colormap]] = "gray", cbar: bool | Sequence[bool] | Sequence[Sequence[bool]] = False, title: str | Sequence[str] | Sequence[Sequence[str]] | None = None, chroma_boost: float | Sequence[float] = 1.0, @@ -468,7 +471,10 @@ def show_2d( | Sequence[ScalebarConfig | ShowParams.Scalebar | dict | bool | None] | Sequence[Sequence[ScalebarConfig | ShowParams.Scalebar | dict | bool | None]] | None = None, - cmap: str | colors.Colormap | Sequence[str] | Sequence[Sequence[str]] = "gray", + cmap: str + | colors.Colormap + | Sequence[str | colors.Colormap] + | Sequence[Sequence[str | colors.Colormap]] = "gray", cbar: bool | Sequence[bool] | Sequence[Sequence[bool]] = False, title: str | Sequence[str] | Sequence[Sequence[str]] | None = None, figax: tuple[Any, Any] | None = None, diff --git a/src/quantem/core/visualization/visualization_utils.py b/src/quantem/core/visualization/visualization_utils.py index 4b8ebae5..aa6687ab 100644 --- a/src/quantem/core/visualization/visualization_utils.py +++ b/src/quantem/core/visualization/visualization_utils.py @@ -247,31 +247,92 @@ def estimate_scalebar_length(length: float, sampling: float) -> tuple[float, flo def _normalize_length_units(length_units: float, units: str) -> tuple[float, str]: """ - pick intelligent units for the scalebar length + Pick intelligent units for the scalebar length. Handles both direct and inverse units. """ - if units in ["A", "Å", "angstrom", "Angstrom"]: - length_A = length_units - elif units in ["nm", "nanometer", "nanometre"]: - length_A = length_units * 10 - elif units in ["um", "μm", "micrometer", "micrometre"]: - length_A = length_units * 1e4 - elif units in ["mm", "millimeter", "millimetre"]: - length_A = length_units * 1e7 - elif units in ["cm", "centimeter", "centimetre"]: - length_A = length_units * 1e8 - else: - return length_units, units - - if length_A < 0.1: - return length_A * 100, "pm" - elif length_A < 10: - return length_A, "Å" - elif length_A < 3e4: - return length_A / 10, "nm" - elif length_A < 1e7: - return length_A / 1e4, "μm" - else: - return length_A / 1e7, "mm" + inverse_unit_map = { + "1/A": "$\\mathrm{Å}^{-1}$", + "1/Å": "$\\mathrm{Å}^{-1}$", + "A^-1": "$\\mathrm{Å}^{-1}$", + "1/angstrom": "$\\mathrm{Å}^{-1}$", + "1/Angstrom": "$\\mathrm{Å}^{-1}$", + "1/nm": "$\\mathrm{nm}^{-1}$", + "1/nanometer": "$\\mathrm{nm}^{-1}$", + "1/nanometre": "$\\mathrm{nm}^{-1}$", + } + direct_unit_map = { + "A": "Å", + "Å": "Å", + "angstrom": "Å", + "Angstrom": "Å", + "nm": "nm", + "nanometer": "nm", + "nanometre": "nm", + "um": "μm", + "μm": "μm", + "micrometer": "μm", + "micrometre": "μm", + "micron": "μm", + "mm": "mm", + "millimeter": "mm", + "millimetre": "mm", + "cm": "cm", + "centimeter": "cm", + "centimetre": "cm", + "m": "m", + "meter": "m", + "metre": "m", + } + + # Handle inverse units first + if units in inverse_unit_map: + # Convert everything to 1/Å then scale + units = inverse_unit_map[units] + if units == "$\\mathrm{Å}^{-1}$": + length_invA = length_units + else: # units == "$\\mathrm{nm}^{-1}$": + length_invA = length_units / 10 + + if length_invA < 0.1: + return length_invA * 10, "$\\mathrm{nm}^{-1}$" + elif length_invA < 100: + return length_invA, "$\\mathrm{Å}^{-1}$" + else: # length_invA >= 10: + return length_invA / 100, "$\\mathrm{pm}^{-1}$" + + # Handle direct metric units (distance) + if units in direct_unit_map: + # Everything to Å + if units in ["A", "Å", "angstrom", "Angstrom"]: + length_A = length_units + elif units in ["nm", "nanometer", "nanometre"]: + length_A = length_units * 10 + elif units in ["um", "μm", "micrometer", "micrometre", "micron"]: + length_A = length_units * 1e4 + elif units in ["mm", "millimeter", "millimetre"]: + length_A = length_units * 1e7 + elif units in ["cm", "centimeter", "centimetre"]: + length_A = length_units * 1e8 + elif units in ["m", "meter", "metre"]: + length_A = length_units * 1e10 + else: + # fallback, should not happen due to keys in direct_unit_map + return length_units, units + + if length_A <= 0.1: + return length_A * 100, "pm" + elif length_A < 10: + return length_A, "Å" + elif length_A < 1e4: + return length_A / 10, "nm" + elif length_A < 1e7: + return length_A / 1e4, "μm" + elif length_A < 1e10: + return length_A / 1e7, "mm" + else: + return length_A / 1e10, "m" + + # fallback: unknown unit, return as is + return length_units, units def add_scalebar_to_ax( @@ -336,6 +397,7 @@ def add_scalebar_to_ax( fontprops = FontProperties(size=fontsize, weight="bold" if bold else "normal") + label_top = loc[:3] == "low" bar = AnchoredSizeBar( ax.transData, length_px, @@ -344,9 +406,10 @@ def add_scalebar_to_ax( pad=pad_px, color=color, frameon=False, - label_top=loc[:3] == "low", + label_top=label_top, size_vertical=int(width_px), fontproperties=fontprops, + sep=2 if label_top else int(round(0.3 * fontsize)), ) ax.add_artist(bar) From d6e2aa8d2475e326255aee533bf17a9f99990b91 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Tue, 24 Mar 2026 15:15:59 -0700 Subject: [PATCH 224/335] Zero-pad docs fixed --- src/quantem/core/utils/tomography_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quantem/core/utils/tomography_utils.py b/src/quantem/core/utils/tomography_utils.py index 2f9cebca..9f5df102 100644 --- a/src/quantem/core/utils/tomography_utils.py +++ b/src/quantem/core/utils/tomography_utils.py @@ -230,7 +230,7 @@ def centering_com_alignment(image_stack): def differentiable_shift_2d(image, shift_x, shift_y, sampling_rate): """ - Shifts a 2D image using grid_sample in a differentiable manner with boundary conditions applied. + Shifts a 2D image using grid_sample in a differentiable manner with zero-pad boundary conditions applied. Args: image: Tensor of shape [H, W] From 56c44e51394474e66c8ceac08143b67df4d90c8e Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Tue, 24 Mar 2026 17:22:13 -0700 Subject: [PATCH 225/335] fixing linescan returns, improving complex cbar labels --- src/quantem/core/visualization/line_scan.py | 11 +++-------- src/quantem/core/visualization/visualization.py | 4 ++-- src/quantem/core/visualization/visualization_utils.py | 2 +- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/quantem/core/visualization/line_scan.py b/src/quantem/core/visualization/line_scan.py index 4200902e..6d22beb2 100644 --- a/src/quantem/core/visualization/line_scan.py +++ b/src/quantem/core/visualization/line_scan.py @@ -19,7 +19,7 @@ def linescan( sampling: tuple[float, float] | np.ndarray | None = None, sampling_units: str | None = None, **kwargs, -) -> tuple[np.ndarray, np.ndarray] | tuple[np.ndarray, np.ndarray, tuple[Any, Any]]: +) -> tuple[np.ndarray, np.ndarray]: """ Generate a line scan through an image. @@ -111,7 +111,7 @@ def linescan( sampling_units = "pixels" if show: - fig, axs = _show_linescan( + _fig, _axs = _show_linescan( scan_image, profile, positions, @@ -121,13 +121,8 @@ def linescan( sampling_units, **kwargs, ) - else: - fig, axs = None, None - if kwargs.get("return_fig", False) and show: - return positions, profile, (fig, axs) - else: - return positions, profile + return positions, profile def _calculate_line_endpoints( diff --git a/src/quantem/core/visualization/visualization.py b/src/quantem/core/visualization/visualization.py index bb32d480..9bccbf86 100644 --- a/src/quantem/core/visualization/visualization.py +++ b/src/quantem/core/visualization/visualization.py @@ -144,10 +144,10 @@ def _show_2d_array( cb_abs = add_cbar_to_ax(fig, ax_cb_abs, norm_obj, cmap_obj) if is_complex: - ax_cb_angle = divider.append_axes("right", size="5%", pad="10%") + ax_cb_angle = divider.append_axes("right", size="5%", pad="15%") add_arg_cbar_to_ax(fig, ax_cb_angle, chroma_boost=chroma_boost) cb_abs.set_label("abs", rotation=0, ha="center", va="bottom") - cb_abs.ax.yaxis.set_label_coords(0.5, -0.05) + cb_abs.ax.yaxis.set_label_coords(0.5, -0.07) if scalebar_config is not None: add_scalebar_to_ax( diff --git a/src/quantem/core/visualization/visualization_utils.py b/src/quantem/core/visualization/visualization_utils.py index aa6687ab..126cb096 100644 --- a/src/quantem/core/visualization/visualization_utils.py +++ b/src/quantem/core/visualization/visualization_utils.py @@ -499,7 +499,7 @@ def add_arg_cbar_to_ax( cb_angle = fig.colorbar(sm, cax=cax) cb_angle.set_label("arg", rotation=0, ha="center", va="bottom") - cb_angle.ax.yaxis.set_label_coords(0.5, -0.05) + cb_angle.ax.yaxis.set_label_coords(0.5, -0.07) cb_angle.set_ticks([-np.pi, -np.pi / 2, 0, np.pi / 2, np.pi]) cb_angle.set_ticklabels( [r"$-\pi$", r"$-\dfrac{\pi}{2}$", "$0$", r"$\dfrac{\pi}{2}$", r"$\pi$"] From 28021afbbf0df31c526eeb0f66b54ff2be0c2692 Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Tue, 24 Mar 2026 17:34:01 -0700 Subject: [PATCH 226/335] fixing couple typehints --- src/quantem/tomography/tomography_base.py | 4 ++-- src/quantem/tomography/tomography_lite.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/quantem/tomography/tomography_base.py b/src/quantem/tomography/tomography_base.py index 743a6f6a..58909bdf 100644 --- a/src/quantem/tomography/tomography_base.py +++ b/src/quantem/tomography/tomography_base.py @@ -49,7 +49,7 @@ def __init__( self._epoch_losses: list[float] = [] self._consistency_losses: list[float] = [] self._val_losses: list[float] = [] - self._lrs: dict[str, float] = {} + self._lrs: dict[str, list] = {} # DDP Initialization if isinstance(obj_model, ObjectINR): self.setup_distributed(device=device) @@ -128,7 +128,7 @@ def consistency_losses(self) -> NDArray: return np.array(self._consistency_losses) @property - def learning_rates(self) -> dict[str, float]: + def learning_rates(self) -> dict[str, list]: """ Returns the learning rates for each epoch ran. """ diff --git a/src/quantem/tomography/tomography_lite.py b/src/quantem/tomography/tomography_lite.py index dc3d918c..7bd9ad67 100644 --- a/src/quantem/tomography/tomography_lite.py +++ b/src/quantem/tomography/tomography_lite.py @@ -76,7 +76,7 @@ def from_dataset( return tomography - def reconstruct( + def reconstruct( # type:ignore[reportIncompatibleMethodOverride] ## easier than overloads self, num_iter: int = 10, reset: bool = False, From e482d76aedd1dc1ce71dd589e89045805da84fa2 Mon Sep 17 00:00:00 2001 From: quantem-bot Date: Mon, 30 Mar 2026 10:22:51 +0000 Subject: [PATCH 227/335] chore: update lock file --- uv.lock | 1749 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 936 insertions(+), 813 deletions(-) diff --git a/uv.lock b/uv.lock index 7d2b81dc..5972dd69 100644 --- a/uv.lock +++ b/uv.lock @@ -38,15 +38,15 @@ wheels = [ [[package]] name = "anyio" -version = "4.12.1" +version = "4.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, + { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, ] [[package]] @@ -139,20 +139,20 @@ wheels = [ [[package]] name = "async-lru" -version = "2.2.0" +version = "2.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/8a/ca724066c32a53fa75f59e0f21aa822fdaa8a0dffa112d223634e3caabf9/async_lru-2.2.0.tar.gz", hash = "sha256:80abae2a237dbc6c60861d621619af39f0d920aea306de34cb992c879e01370c", size = 14654, upload-time = "2026-02-20T19:11:43.848Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/1f/989ecfef8e64109a489fff357450cb73fa73a865a92bd8c272170a6922c2/async_lru-2.3.0.tar.gz", hash = "sha256:89bdb258a0140d7313cf8f4031d816a042202faa61d0ab310a0a538baa1c24b6", size = 16332, upload-time = "2026-03-19T01:04:32.413Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl", hash = "sha256:e2c1cf731eba202b59c5feedaef14ffd9d02ad0037fcda64938699f2c380eafe", size = 7890, upload-time = "2026-02-20T19:11:42.273Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl", hash = "sha256:eea27b01841909316f2cc739807acea1c623df2be8c5cfad7583286397bb8315", size = 8403, upload-time = "2026-03-19T01:04:30.883Z" }, ] [[package]] name = "attrs" -version = "25.4.0" +version = "26.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, ] [[package]] @@ -284,75 +284,91 @@ wheels = [ [[package]] name = "charset-normalizer" -version = "3.4.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, - { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, - { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, - { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, - { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, - { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, - { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, - { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, - { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, - { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, - { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, - { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, - { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, - { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, - { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, - { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, - { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, - { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, - { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, - { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, - { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, - { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, - { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, - { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, - { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, - { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, - { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, - { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, - { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, - { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, - { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, - { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, - { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, - { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, - { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, - { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, - { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, - { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, - { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, - { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, - { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, - { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, - { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, - { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, - { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, - { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, - { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, - { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, - { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, - { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, - { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, - { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, - { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, - { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, - { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, - { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, - { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, - { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, - { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, - { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, - { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, - { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, - { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, - { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +version = "3.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/60/e3bec1881450851b087e301bedc3daa9377a4d45f1c26aa90b0b235e38aa/charset_normalizer-3.4.6.tar.gz", hash = "sha256:1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6", size = 143363, upload-time = "2026-03-15T18:53:25.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/28/ff6f234e628a2de61c458be2779cb182bc03f6eec12200d4a525bbfc9741/charset_normalizer-3.4.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:82060f995ab5003a2d6e0f4ad29065b7672b6593c8c63559beefe5b443242c3e", size = 293582, upload-time = "2026-03-15T18:50:25.454Z" }, + { url = "https://files.pythonhosted.org/packages/1c/b7/b1a117e5385cbdb3205f6055403c2a2a220c5ea80b8716c324eaf75c5c95/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60c74963d8350241a79cb8feea80e54d518f72c26db618862a8f53e5023deaf9", size = 197240, upload-time = "2026-03-15T18:50:27.196Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5f/2574f0f09f3c3bc1b2f992e20bce6546cb1f17e111c5be07308dc5427956/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6e4333fb15c83f7d1482a76d45a0818897b3d33f00efd215528ff7c51b8e35d", size = 217363, upload-time = "2026-03-15T18:50:28.601Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d1/0ae20ad77bc949ddd39b51bf383b6ca932f2916074c95cad34ae465ab71f/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bc72863f4d9aba2e8fd9085e63548a324ba706d2ea2c83b260da08a59b9482de", size = 212994, upload-time = "2026-03-15T18:50:30.102Z" }, + { url = "https://files.pythonhosted.org/packages/60/ac/3233d262a310c1b12633536a07cde5ddd16985e6e7e238e9f3f9423d8eb9/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9cc4fc6c196d6a8b76629a70ddfcd4635a6898756e2d9cac5565cf0654605d73", size = 204697, upload-time = "2026-03-15T18:50:31.654Z" }, + { url = "https://files.pythonhosted.org/packages/25/3c/8a18fc411f085b82303cfb7154eed5bd49c77035eb7608d049468b53f87c/charset_normalizer-3.4.6-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:0c173ce3a681f309f31b87125fecec7a5d1347261ea11ebbb856fa6006b23c8c", size = 191673, upload-time = "2026-03-15T18:50:33.433Z" }, + { url = "https://files.pythonhosted.org/packages/ff/a7/11cfe61d6c5c5c7438d6ba40919d0306ed83c9ab957f3d4da2277ff67836/charset_normalizer-3.4.6-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c907cdc8109f6c619e6254212e794d6548373cc40e1ec75e6e3823d9135d29cc", size = 201120, upload-time = "2026-03-15T18:50:35.105Z" }, + { url = "https://files.pythonhosted.org/packages/b5/10/cf491fa1abd47c02f69687046b896c950b92b6cd7337a27e6548adbec8e4/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:404a1e552cf5b675a87f0651f8b79f5f1e6fd100ee88dc612f89aa16abd4486f", size = 200911, upload-time = "2026-03-15T18:50:36.819Z" }, + { url = "https://files.pythonhosted.org/packages/28/70/039796160b48b18ed466fde0af84c1b090c4e288fae26cd674ad04a2d703/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e3c701e954abf6fc03a49f7c579cc80c2c6cc52525340ca3186c41d3f33482ef", size = 192516, upload-time = "2026-03-15T18:50:38.228Z" }, + { url = "https://files.pythonhosted.org/packages/ff/34/c56f3223393d6ff3124b9e78f7de738047c2d6bc40a4f16ac0c9d7a1cb3c/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7a6967aaf043bceabab5412ed6bd6bd26603dae84d5cb75bf8d9a74a4959d398", size = 218795, upload-time = "2026-03-15T18:50:39.664Z" }, + { url = "https://files.pythonhosted.org/packages/e8/3b/ce2d4f86c5282191a041fdc5a4ce18f1c6bd40a5bd1f74cf8625f08d51c1/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5feb91325bbceade6afab43eb3b508c63ee53579fe896c77137ded51c6b6958e", size = 201833, upload-time = "2026-03-15T18:50:41.552Z" }, + { url = "https://files.pythonhosted.org/packages/3b/9b/b6a9f76b0fd7c5b5ec58b228ff7e85095370282150f0bd50b3126f5506d6/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f820f24b09e3e779fe84c3c456cb4108a7aa639b0d1f02c28046e11bfcd088ed", size = 213920, upload-time = "2026-03-15T18:50:43.33Z" }, + { url = "https://files.pythonhosted.org/packages/ae/98/7bc23513a33d8172365ed30ee3a3b3fe1ece14a395e5fc94129541fc6003/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b35b200d6a71b9839a46b9b7fff66b6638bb52fc9658aa58796b0326595d3021", size = 206951, upload-time = "2026-03-15T18:50:44.789Z" }, + { url = "https://files.pythonhosted.org/packages/32/73/c0b86f3d1458468e11aec870e6b3feac931facbe105a894b552b0e518e79/charset_normalizer-3.4.6-cp311-cp311-win32.whl", hash = "sha256:9ca4c0b502ab399ef89248a2c84c54954f77a070f28e546a85e91da627d1301e", size = 143703, upload-time = "2026-03-15T18:50:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/c6/e3/76f2facfe8eddee0bbd38d2594e709033338eae44ebf1738bcefe0a06185/charset_normalizer-3.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:a9e68c9d88823b274cf1e72f28cb5dc89c990edf430b0bfd3e2fb0785bfeabf4", size = 153857, upload-time = "2026-03-15T18:50:47.563Z" }, + { url = "https://files.pythonhosted.org/packages/e2/dc/9abe19c9b27e6cd3636036b9d1b387b78c40dedbf0b47f9366737684b4b0/charset_normalizer-3.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:97d0235baafca5f2b09cf332cc275f021e694e8362c6bb9c96fc9a0eb74fc316", size = 142751, upload-time = "2026-03-15T18:50:49.234Z" }, + { url = "https://files.pythonhosted.org/packages/e5/62/c0815c992c9545347aeea7859b50dc9044d147e2e7278329c6e02ac9a616/charset_normalizer-3.4.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ef7fedc7a6ecbe99969cd09632516738a97eeb8bd7258bf8a0f23114c057dab", size = 295154, upload-time = "2026-03-15T18:50:50.88Z" }, + { url = "https://files.pythonhosted.org/packages/a8/37/bdca6613c2e3c58c7421891d80cc3efa1d32e882f7c4a7ee6039c3fc951a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4ea868bc28109052790eb2b52a9ab33f3aa7adc02f96673526ff47419490e21", size = 199191, upload-time = "2026-03-15T18:50:52.658Z" }, + { url = "https://files.pythonhosted.org/packages/6c/92/9934d1bbd69f7f398b38c5dae1cbf9cc672e7c34a4adf7b17c0a9c17d15d/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:836ab36280f21fc1a03c99cd05c6b7af70d2697e374c7af0b61ed271401a72a2", size = 218674, upload-time = "2026-03-15T18:50:54.102Z" }, + { url = "https://files.pythonhosted.org/packages/af/90/25f6ab406659286be929fd89ab0e78e38aa183fc374e03aa3c12d730af8a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f1ce721c8a7dfec21fcbdfe04e8f68174183cf4e8188e0645e92aa23985c57ff", size = 215259, upload-time = "2026-03-15T18:50:55.616Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ef/79a463eb0fff7f96afa04c1d4c51f8fc85426f918db467854bfb6a569ce3/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e28d62a8fc7a1fa411c43bd65e346f3bce9716dc51b897fbe930c5987b402d5", size = 207276, upload-time = "2026-03-15T18:50:57.054Z" }, + { url = "https://files.pythonhosted.org/packages/f7/72/d0426afec4b71dc159fa6b4e68f868cd5a3ecd918fec5813a15d292a7d10/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:530d548084c4a9f7a16ed4a294d459b4f229db50df689bfe92027452452943a0", size = 195161, upload-time = "2026-03-15T18:50:58.686Z" }, + { url = "https://files.pythonhosted.org/packages/bf/18/c82b06a68bfcb6ce55e508225d210c7e6a4ea122bfc0748892f3dc4e8e11/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30f445ae60aad5e1f8bdbb3108e39f6fbc09f4ea16c815c66578878325f8f15a", size = 203452, upload-time = "2026-03-15T18:51:00.196Z" }, + { url = "https://files.pythonhosted.org/packages/44/d6/0c25979b92f8adafdbb946160348d8d44aa60ce99afdc27df524379875cb/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ac2393c73378fea4e52aa56285a3d64be50f1a12395afef9cce47772f60334c2", size = 202272, upload-time = "2026-03-15T18:51:01.703Z" }, + { url = "https://files.pythonhosted.org/packages/2e/3d/7fea3e8fe84136bebbac715dd1221cc25c173c57a699c030ab9b8900cbb7/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:90ca27cd8da8118b18a52d5f547859cc1f8354a00cd1e8e5120df3e30d6279e5", size = 195622, upload-time = "2026-03-15T18:51:03.526Z" }, + { url = "https://files.pythonhosted.org/packages/57/8a/d6f7fd5cb96c58ef2f681424fbca01264461336d2a7fc875e4446b1f1346/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e5a94886bedca0f9b78fecd6afb6629142fd2605aa70a125d49f4edc6037ee6", size = 220056, upload-time = "2026-03-15T18:51:05.269Z" }, + { url = "https://files.pythonhosted.org/packages/16/50/478cdda782c8c9c3fb5da3cc72dd7f331f031e7f1363a893cdd6ca0f8de0/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:695f5c2823691a25f17bc5d5ffe79fa90972cc34b002ac6c843bb8a1720e950d", size = 203751, upload-time = "2026-03-15T18:51:06.858Z" }, + { url = "https://files.pythonhosted.org/packages/75/fc/cc2fcac943939c8e4d8791abfa139f685e5150cae9f94b60f12520feaa9b/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:231d4da14bcd9301310faf492051bee27df11f2bc7549bc0bb41fef11b82daa2", size = 216563, upload-time = "2026-03-15T18:51:08.564Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b7/a4add1d9a5f68f3d037261aecca83abdb0ab15960a3591d340e829b37298/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a056d1ad2633548ca18ffa2f85c202cfb48b68615129143915b8dc72a806a923", size = 209265, upload-time = "2026-03-15T18:51:10.312Z" }, + { url = "https://files.pythonhosted.org/packages/6c/18/c094561b5d64a24277707698e54b7f67bd17a4f857bbfbb1072bba07c8bf/charset_normalizer-3.4.6-cp312-cp312-win32.whl", hash = "sha256:c2274ca724536f173122f36c98ce188fd24ce3dad886ec2b7af859518ce008a4", size = 144229, upload-time = "2026-03-15T18:51:11.694Z" }, + { url = "https://files.pythonhosted.org/packages/ab/20/0567efb3a8fd481b8f34f739ebddc098ed062a59fed41a8d193a61939e8f/charset_normalizer-3.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:c8ae56368f8cc97c7e40a7ee18e1cedaf8e780cd8bc5ed5ac8b81f238614facb", size = 154277, upload-time = "2026-03-15T18:51:13.004Z" }, + { url = "https://files.pythonhosted.org/packages/15/57/28d79b44b51933119e21f65479d0864a8d5893e494cf5daab15df0247c17/charset_normalizer-3.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:899d28f422116b08be5118ef350c292b36fc15ec2daeb9ea987c89281c7bb5c4", size = 142817, upload-time = "2026-03-15T18:51:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/1e/1d/4fdabeef4e231153b6ed7567602f3b68265ec4e5b76d6024cf647d43d981/charset_normalizer-3.4.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:11afb56037cbc4b1555a34dd69151e8e069bee82e613a73bef6e714ce733585f", size = 294823, upload-time = "2026-03-15T18:51:15.755Z" }, + { url = "https://files.pythonhosted.org/packages/47/7b/20e809b89c69d37be748d98e84dce6820bf663cf19cf6b942c951a3e8f41/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:423fb7e748a08f854a08a222b983f4df1912b1daedce51a72bd24fe8f26a1843", size = 198527, upload-time = "2026-03-15T18:51:17.177Z" }, + { url = "https://files.pythonhosted.org/packages/37/a6/4f8d27527d59c039dce6f7622593cdcd3d70a8504d87d09eb11e9fdc6062/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d73beaac5e90173ac3deb9928a74763a6d230f494e4bfb422c217a0ad8e629bf", size = 218388, upload-time = "2026-03-15T18:51:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/f6/9b/4770ccb3e491a9bacf1c46cc8b812214fe367c86a96353ccc6daf87b01ec/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d60377dce4511655582e300dc1e5a5f24ba0cb229005a1d5c8d0cb72bb758ab8", size = 214563, upload-time = "2026-03-15T18:51:20.374Z" }, + { url = "https://files.pythonhosted.org/packages/2b/58/a199d245894b12db0b957d627516c78e055adc3a0d978bc7f65ddaf7c399/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:530e8cebeea0d76bdcf93357aa5e41336f48c3dc709ac52da2bb167c5b8271d9", size = 206587, upload-time = "2026-03-15T18:51:21.807Z" }, + { url = "https://files.pythonhosted.org/packages/7e/70/3def227f1ec56f5c69dfc8392b8bd63b11a18ca8178d9211d7cc5e5e4f27/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:a26611d9987b230566f24a0a125f17fe0de6a6aff9f25c9f564aaa2721a5fb88", size = 194724, upload-time = "2026-03-15T18:51:23.508Z" }, + { url = "https://files.pythonhosted.org/packages/58/ab/9318352e220c05efd31c2779a23b50969dc94b985a2efa643ed9077bfca5/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:34315ff4fc374b285ad7f4a0bf7dcbfe769e1b104230d40f49f700d4ab6bbd84", size = 202956, upload-time = "2026-03-15T18:51:25.239Z" }, + { url = "https://files.pythonhosted.org/packages/75/13/f3550a3ac25b70f87ac98c40d3199a8503676c2f1620efbf8d42095cfc40/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ddd609f9e1af8c7bd6e2aca279c931aefecd148a14402d4e368f3171769fd", size = 201923, upload-time = "2026-03-15T18:51:26.682Z" }, + { url = "https://files.pythonhosted.org/packages/1b/db/c5c643b912740b45e8eec21de1bbab8e7fc085944d37e1e709d3dcd9d72f/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:80d0a5615143c0b3225e5e3ef22c8d5d51f3f72ce0ea6fb84c943546c7b25b6c", size = 195366, upload-time = "2026-03-15T18:51:28.129Z" }, + { url = "https://files.pythonhosted.org/packages/5a/67/3b1c62744f9b2448443e0eb160d8b001c849ec3fef591e012eda6484787c/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:92734d4d8d187a354a556626c221cd1a892a4e0802ccb2af432a1d85ec012194", size = 219752, upload-time = "2026-03-15T18:51:29.556Z" }, + { url = "https://files.pythonhosted.org/packages/f6/98/32ffbaf7f0366ffb0445930b87d103f6b406bc2c271563644bde8a2b1093/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:613f19aa6e082cf96e17e3ffd89383343d0d589abda756b7764cf78361fd41dc", size = 203296, upload-time = "2026-03-15T18:51:30.921Z" }, + { url = "https://files.pythonhosted.org/packages/41/12/5d308c1bbe60cabb0c5ef511574a647067e2a1f631bc8634fcafaccd8293/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2b1a63e8224e401cafe7739f77efd3f9e7f5f2026bda4aead8e59afab537784f", size = 215956, upload-time = "2026-03-15T18:51:32.399Z" }, + { url = "https://files.pythonhosted.org/packages/53/e9/5f85f6c5e20669dbe56b165c67b0260547dea97dba7e187938833d791687/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6cceb5473417d28edd20c6c984ab6fee6c6267d38d906823ebfe20b03d607dc2", size = 208652, upload-time = "2026-03-15T18:51:34.214Z" }, + { url = "https://files.pythonhosted.org/packages/f1/11/897052ea6af56df3eef3ca94edafee410ca699ca0c7b87960ad19932c55e/charset_normalizer-3.4.6-cp313-cp313-win32.whl", hash = "sha256:d7de2637729c67d67cf87614b566626057e95c303bc0a55ffe391f5205e7003d", size = 143940, upload-time = "2026-03-15T18:51:36.15Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5c/724b6b363603e419829f561c854b87ed7c7e31231a7908708ac086cdf3e2/charset_normalizer-3.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:572d7c822caf521f0525ba1bce1a622a0b85cf47ffbdae6c9c19e3b5ac3c4389", size = 154101, upload-time = "2026-03-15T18:51:37.876Z" }, + { url = "https://files.pythonhosted.org/packages/01/a5/7abf15b4c0968e47020f9ca0935fb3274deb87cb288cd187cad92e8cdffd/charset_normalizer-3.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a4474d924a47185a06411e0064b803c68be044be2d60e50e8bddcc2649957c1f", size = 143109, upload-time = "2026-03-15T18:51:39.565Z" }, + { url = "https://files.pythonhosted.org/packages/25/6f/ffe1e1259f384594063ea1869bfb6be5cdb8bc81020fc36c3636bc8302a1/charset_normalizer-3.4.6-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9cc6e6d9e571d2f863fa77700701dae73ed5f78881efc8b3f9a4398772ff53e8", size = 294458, upload-time = "2026-03-15T18:51:41.134Z" }, + { url = "https://files.pythonhosted.org/packages/56/60/09bb6c13a8c1016c2ed5c6a6488e4ffef506461aa5161662bd7636936fb1/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5960d965e67165d75b7c7ffc60a83ec5abfc5c11b764ec13ea54fbef8b4421", size = 199277, upload-time = "2026-03-15T18:51:42.953Z" }, + { url = "https://files.pythonhosted.org/packages/00/50/dcfbb72a5138bbefdc3332e8d81a23494bf67998b4b100703fd15fa52d81/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b3694e3f87f8ac7ce279d4355645b3c878d24d1424581b46282f24b92f5a4ae2", size = 218758, upload-time = "2026-03-15T18:51:44.339Z" }, + { url = "https://files.pythonhosted.org/packages/03/b3/d79a9a191bb75f5aa81f3aaaa387ef29ce7cb7a9e5074ba8ea095cc073c2/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5d11595abf8dd942a77883a39d81433739b287b6aa71620f15164f8096221b30", size = 215299, upload-time = "2026-03-15T18:51:45.871Z" }, + { url = "https://files.pythonhosted.org/packages/76/7e/bc8911719f7084f72fd545f647601ea3532363927f807d296a8c88a62c0d/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7bda6eebafd42133efdca535b04ccb338ab29467b3f7bf79569883676fc628db", size = 206811, upload-time = "2026-03-15T18:51:47.308Z" }, + { url = "https://files.pythonhosted.org/packages/e2/40/c430b969d41dda0c465aa36cc7c2c068afb67177bef50905ac371b28ccc7/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:bbc8c8650c6e51041ad1be191742b8b421d05bbd3410f43fa2a00c8db87678e8", size = 193706, upload-time = "2026-03-15T18:51:48.849Z" }, + { url = "https://files.pythonhosted.org/packages/48/15/e35e0590af254f7df984de1323640ef375df5761f615b6225ba8deb9799a/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22c6f0c2fbc31e76c3b8a86fba1a56eda6166e238c29cdd3d14befdb4a4e4815", size = 202706, upload-time = "2026-03-15T18:51:50.257Z" }, + { url = "https://files.pythonhosted.org/packages/5e/bd/f736f7b9cc5e93a18b794a50346bb16fbfd6b37f99e8f306f7951d27c17c/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7edbed096e4a4798710ed6bc75dcaa2a21b68b6c356553ac4823c3658d53743a", size = 202497, upload-time = "2026-03-15T18:51:52.012Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ba/2cc9e3e7dfdf7760a6ed8da7446d22536f3d0ce114ac63dee2a5a3599e62/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7f9019c9cb613f084481bd6a100b12e1547cf2efe362d873c2e31e4035a6fa43", size = 193511, upload-time = "2026-03-15T18:51:53.723Z" }, + { url = "https://files.pythonhosted.org/packages/9e/cb/5be49b5f776e5613be07298c80e1b02a2d900f7a7de807230595c85a8b2e/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:58c948d0d086229efc484fe2f30c2d382c86720f55cd9bc33591774348ad44e0", size = 220133, upload-time = "2026-03-15T18:51:55.333Z" }, + { url = "https://files.pythonhosted.org/packages/83/43/99f1b5dad345accb322c80c7821071554f791a95ee50c1c90041c157ae99/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:419a9d91bd238052642a51938af8ac05da5b3343becde08d5cdeab9046df9ee1", size = 203035, upload-time = "2026-03-15T18:51:56.736Z" }, + { url = "https://files.pythonhosted.org/packages/87/9a/62c2cb6a531483b55dddff1a68b3d891a8b498f3ca555fbcf2978e804d9d/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5273b9f0b5835ff0350c0828faea623c68bfa65b792720c453e22b25cc72930f", size = 216321, upload-time = "2026-03-15T18:51:58.17Z" }, + { url = "https://files.pythonhosted.org/packages/6e/79/94a010ff81e3aec7c293eb82c28f930918e517bc144c9906a060844462eb/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0e901eb1049fdb80f5bd11ed5ea1e498ec423102f7a9b9e4645d5b8204ff2815", size = 208973, upload-time = "2026-03-15T18:51:59.998Z" }, + { url = "https://files.pythonhosted.org/packages/2a/57/4ecff6d4ec8585342f0c71bc03efaa99cb7468f7c91a57b105bcd561cea8/charset_normalizer-3.4.6-cp314-cp314-win32.whl", hash = "sha256:b4ff1d35e8c5bd078be89349b6f3a845128e685e751b6ea1169cf2160b344c4d", size = 144610, upload-time = "2026-03-15T18:52:02.213Z" }, + { url = "https://files.pythonhosted.org/packages/80/94/8434a02d9d7f168c25767c64671fead8d599744a05d6a6c877144c754246/charset_normalizer-3.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:74119174722c4349af9708993118581686f343adc1c8c9c007d59be90d077f3f", size = 154962, upload-time = "2026-03-15T18:52:03.658Z" }, + { url = "https://files.pythonhosted.org/packages/46/4c/48f2cdbfd923026503dfd67ccea45c94fd8fe988d9056b468579c66ed62b/charset_normalizer-3.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:e5bcc1a1ae744e0bb59641171ae53743760130600da8db48cbb6e4918e186e4e", size = 143595, upload-time = "2026-03-15T18:52:05.123Z" }, + { url = "https://files.pythonhosted.org/packages/31/93/8878be7569f87b14f1d52032946131bcb6ebbd8af3e20446bc04053dc3f1/charset_normalizer-3.4.6-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ad8faf8df23f0378c6d527d8b0b15ea4a2e23c89376877c598c4870d1b2c7866", size = 314828, upload-time = "2026-03-15T18:52:06.831Z" }, + { url = "https://files.pythonhosted.org/packages/06/b6/fae511ca98aac69ecc35cde828b0a3d146325dd03d99655ad38fc2cc3293/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5ea69428fa1b49573eef0cc44a1d43bebd45ad0c611eb7d7eac760c7ae771bc", size = 208138, upload-time = "2026-03-15T18:52:08.239Z" }, + { url = "https://files.pythonhosted.org/packages/54/57/64caf6e1bf07274a1e0b7c160a55ee9e8c9ec32c46846ce59b9c333f7008/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:06a7e86163334edfc5d20fe104db92fcd666e5a5df0977cb5680a506fe26cc8e", size = 224679, upload-time = "2026-03-15T18:52:10.043Z" }, + { url = "https://files.pythonhosted.org/packages/aa/cb/9ff5a25b9273ef160861b41f6937f86fae18b0792fe0a8e75e06acb08f1d/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e1f6e2f00a6b8edb562826e4632e26d063ac10307e80f7461f7de3ad8ef3f077", size = 223475, upload-time = "2026-03-15T18:52:11.854Z" }, + { url = "https://files.pythonhosted.org/packages/fc/97/440635fc093b8d7347502a377031f9605a1039c958f3cd18dcacffb37743/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95b52c68d64c1878818687a473a10547b3292e82b6f6fe483808fb1468e2f52f", size = 215230, upload-time = "2026-03-15T18:52:13.325Z" }, + { url = "https://files.pythonhosted.org/packages/cd/24/afff630feb571a13f07c8539fbb502d2ab494019492aaffc78ef41f1d1d0/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:7504e9b7dc05f99a9bbb4525c67a2c155073b44d720470a148b34166a69c054e", size = 199045, upload-time = "2026-03-15T18:52:14.752Z" }, + { url = "https://files.pythonhosted.org/packages/e5/17/d1399ecdaf7e0498c327433e7eefdd862b41236a7e484355b8e0e5ebd64b/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:172985e4ff804a7ad08eebec0a1640ece87ba5041d565fff23c8f99c1f389484", size = 211658, upload-time = "2026-03-15T18:52:16.278Z" }, + { url = "https://files.pythonhosted.org/packages/b5/38/16baa0affb957b3d880e5ac2144caf3f9d7de7bc4a91842e447fbb5e8b67/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4be9f4830ba8741527693848403e2c457c16e499100963ec711b1c6f2049b7c7", size = 210769, upload-time = "2026-03-15T18:52:17.782Z" }, + { url = "https://files.pythonhosted.org/packages/05/34/c531bc6ac4c21da9ddfddb3107be2287188b3ea4b53b70fc58f2a77ac8d8/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:79090741d842f564b1b2827c0b82d846405b744d31e84f18d7a7b41c20e473ff", size = 201328, upload-time = "2026-03-15T18:52:19.553Z" }, + { url = "https://files.pythonhosted.org/packages/fa/73/a5a1e9ca5f234519c1953608a03fe109c306b97fdfb25f09182babad51a7/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:87725cfb1a4f1f8c2fc9890ae2f42094120f4b44db9360be5d99a4c6b0e03a9e", size = 225302, upload-time = "2026-03-15T18:52:21.043Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f6/cd782923d112d296294dea4bcc7af5a7ae0f86ab79f8fefbda5526b6cfc0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:fcce033e4021347d80ed9c66dcf1e7b1546319834b74445f561d2e2221de5659", size = 211127, upload-time = "2026-03-15T18:52:22.491Z" }, + { url = "https://files.pythonhosted.org/packages/0e/c5/0b6898950627af7d6103a449b22320372c24c6feda91aa24e201a478d161/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:ca0276464d148c72defa8bb4390cce01b4a0e425f3b50d1435aa6d7a18107602", size = 222840, upload-time = "2026-03-15T18:52:24.113Z" }, + { url = "https://files.pythonhosted.org/packages/7d/25/c4bba773bef442cbdc06111d40daa3de5050a676fa26e85090fc54dd12f0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:197c1a244a274bb016dd8b79204850144ef77fe81c5b797dc389327adb552407", size = 216890, upload-time = "2026-03-15T18:52:25.541Z" }, + { url = "https://files.pythonhosted.org/packages/35/1a/05dacadb0978da72ee287b0143097db12f2e7e8d3ffc4647da07a383b0b7/charset_normalizer-3.4.6-cp314-cp314t-win32.whl", hash = "sha256:2a24157fa36980478dd1770b585c0f30d19e18f4fb0c47c13aa568f871718579", size = 155379, upload-time = "2026-03-15T18:52:27.05Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7a/d269d834cb3a76291651256f3b9a5945e81d0a49ab9f4a498964e83c0416/charset_normalizer-3.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:cd5e2801c89992ed8c0a3f0293ae83c159a60d9a5d685005383ef4caca77f2c4", size = 169043, upload-time = "2026-03-15T18:52:28.502Z" }, + { url = "https://files.pythonhosted.org/packages/23/06/28b29fba521a37a8932c6a84192175c34d49f84a6d4773fa63d05f9aff22/charset_normalizer-3.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:47955475ac79cc504ef2704b192364e51d0d473ad452caedd0002605f780101c", size = 148523, upload-time = "2026-03-15T18:52:29.956Z" }, + { url = "https://files.pythonhosted.org/packages/2a/68/687187c7e26cb24ccbd88e5069f5ef00eba804d36dde11d99aad0838ab45/charset_normalizer-3.4.6-py3-none-any.whl", hash = "sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69", size = 61455, upload-time = "2026-03-15T18:53:23.833Z" }, ] [[package]] @@ -516,101 +532,101 @@ wheels = [ [[package]] name = "coverage" -version = "7.13.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/ad/b59e5b451cf7172b8d1043dc0fa718f23aab379bc1521ee13d4bd9bfa960/coverage-7.13.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d490ba50c3f35dd7c17953c68f3270e7ccd1c6642e2d2afe2d8e720b98f5a053", size = 219278, upload-time = "2026-02-09T12:56:31.673Z" }, - { url = "https://files.pythonhosted.org/packages/f1/17/0cb7ca3de72e5f4ef2ec2fa0089beafbcaaaead1844e8b8a63d35173d77d/coverage-7.13.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:19bc3c88078789f8ef36acb014d7241961dbf883fd2533d18cb1e7a5b4e28b11", size = 219783, upload-time = "2026-02-09T12:56:33.104Z" }, - { url = "https://files.pythonhosted.org/packages/ab/63/325d8e5b11e0eaf6d0f6a44fad444ae58820929a9b0de943fa377fe73e85/coverage-7.13.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3998e5a32e62fdf410c0dbd3115df86297995d6e3429af80b8798aad894ca7aa", size = 250200, upload-time = "2026-02-09T12:56:34.474Z" }, - { url = "https://files.pythonhosted.org/packages/76/53/c16972708cbb79f2942922571a687c52bd109a7bd51175aeb7558dff2236/coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7", size = 252114, upload-time = "2026-02-09T12:56:35.749Z" }, - { url = "https://files.pythonhosted.org/packages/eb/c2/7ab36d8b8cc412bec9ea2d07c83c48930eb4ba649634ba00cb7e4e0f9017/coverage-7.13.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3aa4e7b9e416774b21797365b358a6e827ffadaaca81b69ee02946852449f00", size = 254220, upload-time = "2026-02-09T12:56:37.796Z" }, - { url = "https://files.pythonhosted.org/packages/d6/4d/cf52c9a3322c89a0e6febdfbc83bb45c0ed3c64ad14081b9503adee702e7/coverage-7.13.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:71ca20079dd8f27fcf808817e281e90220475cd75115162218d0e27549f95fef", size = 256164, upload-time = "2026-02-09T12:56:39.016Z" }, - { url = "https://files.pythonhosted.org/packages/78/e9/eb1dd17bd6de8289df3580e967e78294f352a5df8a57ff4671ee5fc3dcd0/coverage-7.13.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e2f25215f1a359ab17320b47bcdaca3e6e6356652e8256f2441e4ef972052903", size = 250325, upload-time = "2026-02-09T12:56:40.668Z" }, - { url = "https://files.pythonhosted.org/packages/71/07/8c1542aa873728f72267c07278c5cc0ec91356daf974df21335ccdb46368/coverage-7.13.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d65b2d373032411e86960604dc4edac91fdfb5dca539461cf2cbe78327d1e64f", size = 251913, upload-time = "2026-02-09T12:56:41.97Z" }, - { url = "https://files.pythonhosted.org/packages/74/d7/c62e2c5e4483a748e27868e4c32ad3daa9bdddbba58e1bc7a15e252baa74/coverage-7.13.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94eb63f9b363180aff17de3e7c8760c3ba94664ea2695c52f10111244d16a299", size = 249974, upload-time = "2026-02-09T12:56:43.323Z" }, - { url = "https://files.pythonhosted.org/packages/98/9f/4c5c015a6e98ced54efd0f5cf8d31b88e5504ecb6857585fc0161bb1e600/coverage-7.13.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e856bf6616714c3a9fbc270ab54103f4e685ba236fa98c054e8f87f266c93505", size = 253741, upload-time = "2026-02-09T12:56:45.155Z" }, - { url = "https://files.pythonhosted.org/packages/bd/59/0f4eef89b9f0fcd9633b5d350016f54126ab49426a70ff4c4e87446cabdc/coverage-7.13.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:65dfcbe305c3dfe658492df2d85259e0d79ead4177f9ae724b6fb245198f55d6", size = 249695, upload-time = "2026-02-09T12:56:46.636Z" }, - { url = "https://files.pythonhosted.org/packages/b5/2c/b7476f938deb07166f3eb281a385c262675d688ff4659ad56c6c6b8e2e70/coverage-7.13.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b507778ae8a4c915436ed5c2e05b4a6cecfa70f734e19c22a005152a11c7b6a9", size = 250599, upload-time = "2026-02-09T12:56:48.13Z" }, - { url = "https://files.pythonhosted.org/packages/b8/34/c3420709d9846ee3785b9f2831b4d94f276f38884032dca1457fa83f7476/coverage-7.13.4-cp311-cp311-win32.whl", hash = "sha256:784fc3cf8be001197b652d51d3fd259b1e2262888693a4636e18879f613a62a9", size = 221780, upload-time = "2026-02-09T12:56:50.479Z" }, - { url = "https://files.pythonhosted.org/packages/61/08/3d9c8613079d2b11c185b865de9a4c1a68850cfda2b357fae365cf609f29/coverage-7.13.4-cp311-cp311-win_amd64.whl", hash = "sha256:2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f", size = 222715, upload-time = "2026-02-09T12:56:51.815Z" }, - { url = "https://files.pythonhosted.org/packages/18/1a/54c3c80b2f056164cc0a6cdcb040733760c7c4be9d780fe655f356f433e4/coverage-7.13.4-cp311-cp311-win_arm64.whl", hash = "sha256:79e73a76b854d9c6088fe5d8b2ebe745f8681c55f7397c3c0a016192d681045f", size = 221385, upload-time = "2026-02-09T12:56:53.194Z" }, - { url = "https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459", size = 219449, upload-time = "2026-02-09T12:56:54.889Z" }, - { url = "https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3", size = 219810, upload-time = "2026-02-09T12:56:56.33Z" }, - { url = "https://files.pythonhosted.org/packages/78/72/2f372b726d433c9c35e56377cf1d513b4c16fe51841060d826b95caacec1/coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634", size = 251308, upload-time = "2026-02-09T12:56:57.858Z" }, - { url = "https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3", size = 254052, upload-time = "2026-02-09T12:56:59.754Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ac/45dc2e19a1939098d783c846e130b8f862fbb50d09e0af663988f2f21973/coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa", size = 255165, upload-time = "2026-02-09T12:57:01.287Z" }, - { url = "https://files.pythonhosted.org/packages/2d/4d/26d236ff35abc3b5e63540d3386e4c3b192168c1d96da5cb2f43c640970f/coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3", size = 257432, upload-time = "2026-02-09T12:57:02.637Z" }, - { url = "https://files.pythonhosted.org/packages/ec/55/14a966c757d1348b2e19caf699415a2a4c4f7feaa4bbc6326a51f5c7dd1b/coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a", size = 251716, upload-time = "2026-02-09T12:57:04.056Z" }, - { url = "https://files.pythonhosted.org/packages/77/33/50116647905837c66d28b2af1321b845d5f5d19be9655cb84d4a0ea806b4/coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7", size = 253089, upload-time = "2026-02-09T12:57:05.503Z" }, - { url = "https://files.pythonhosted.org/packages/c2/b4/8efb11a46e3665d92635a56e4f2d4529de6d33f2cb38afd47d779d15fc99/coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc", size = 251232, upload-time = "2026-02-09T12:57:06.879Z" }, - { url = "https://files.pythonhosted.org/packages/51/24/8cd73dd399b812cc76bb0ac260e671c4163093441847ffe058ac9fda1e32/coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47", size = 255299, upload-time = "2026-02-09T12:57:08.245Z" }, - { url = "https://files.pythonhosted.org/packages/03/94/0a4b12f1d0e029ce1ccc1c800944a9984cbe7d678e470bb6d3c6bc38a0da/coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985", size = 250796, upload-time = "2026-02-09T12:57:10.142Z" }, - { url = "https://files.pythonhosted.org/packages/73/44/6002fbf88f6698ca034360ce474c406be6d5a985b3fdb3401128031eef6b/coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0", size = 252673, upload-time = "2026-02-09T12:57:12.197Z" }, - { url = "https://files.pythonhosted.org/packages/de/c6/a0279f7c00e786be75a749a5674e6fa267bcbd8209cd10c9a450c655dfa7/coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246", size = 221990, upload-time = "2026-02-09T12:57:14.085Z" }, - { url = "https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126", size = 222800, upload-time = "2026-02-09T12:57:15.944Z" }, - { url = "https://files.pythonhosted.org/packages/47/ac/92da44ad9a6f4e3a7debd178949d6f3769bedca33830ce9b1dcdab589a37/coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d", size = 221415, upload-time = "2026-02-09T12:57:17.497Z" }, - { url = "https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9", size = 219474, upload-time = "2026-02-09T12:57:19.332Z" }, - { url = "https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac", size = 219844, upload-time = "2026-02-09T12:57:20.66Z" }, - { url = "https://files.pythonhosted.org/packages/97/fd/7e859f8fab324cef6c4ad7cff156ca7c489fef9179d5749b0c8d321281c2/coverage-7.13.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea", size = 250832, upload-time = "2026-02-09T12:57:22.007Z" }, - { url = "https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b", size = 253434, upload-time = "2026-02-09T12:57:23.339Z" }, - { url = "https://files.pythonhosted.org/packages/5a/88/6728a7ad17428b18d836540630487231f5470fb82454871149502f5e5aa2/coverage-7.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525", size = 254676, upload-time = "2026-02-09T12:57:24.774Z" }, - { url = "https://files.pythonhosted.org/packages/7c/bc/21244b1b8cedf0dff0a2b53b208015fe798d5f2a8d5348dbfece04224fff/coverage-7.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242", size = 256807, upload-time = "2026-02-09T12:57:26.125Z" }, - { url = "https://files.pythonhosted.org/packages/97/a0/ddba7ed3251cff51006737a727d84e05b61517d1784a9988a846ba508877/coverage-7.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148", size = 251058, upload-time = "2026-02-09T12:57:27.614Z" }, - { url = "https://files.pythonhosted.org/packages/9b/55/e289addf7ff54d3a540526f33751951bf0878f3809b47f6dfb3def69c6f7/coverage-7.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a", size = 252805, upload-time = "2026-02-09T12:57:29.066Z" }, - { url = "https://files.pythonhosted.org/packages/13/4e/cc276b1fa4a59be56d96f1dabddbdc30f4ba22e3b1cd42504c37b3313255/coverage-7.13.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23", size = 250766, upload-time = "2026-02-09T12:57:30.522Z" }, - { url = "https://files.pythonhosted.org/packages/94/44/1093b8f93018f8b41a8cf29636c9292502f05e4a113d4d107d14a3acd044/coverage-7.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80", size = 254923, upload-time = "2026-02-09T12:57:31.946Z" }, - { url = "https://files.pythonhosted.org/packages/8b/55/ea2796da2d42257f37dbea1aab239ba9263b31bd91d5527cdd6db5efe174/coverage-7.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea", size = 250591, upload-time = "2026-02-09T12:57:33.842Z" }, - { url = "https://files.pythonhosted.org/packages/d4/fa/7c4bb72aacf8af5020675aa633e59c1fbe296d22aed191b6a5b711eb2bc7/coverage-7.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a", size = 252364, upload-time = "2026-02-09T12:57:35.743Z" }, - { url = "https://files.pythonhosted.org/packages/5c/38/a8d2ec0146479c20bbaa7181b5b455a0c41101eed57f10dd19a78ab44c80/coverage-7.13.4-cp313-cp313-win32.whl", hash = "sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d", size = 222010, upload-time = "2026-02-09T12:57:37.25Z" }, - { url = "https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd", size = 222818, upload-time = "2026-02-09T12:57:38.734Z" }, - { url = "https://files.pythonhosted.org/packages/04/d1/934918a138c932c90d78301f45f677fb05c39a3112b96fd2c8e60503cdc7/coverage-7.13.4-cp313-cp313-win_arm64.whl", hash = "sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af", size = 221438, upload-time = "2026-02-09T12:57:40.223Z" }, - { url = "https://files.pythonhosted.org/packages/52/57/ee93ced533bcb3e6df961c0c6e42da2fc6addae53fb95b94a89b1e33ebd7/coverage-7.13.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d", size = 220165, upload-time = "2026-02-09T12:57:41.639Z" }, - { url = "https://files.pythonhosted.org/packages/c5/e0/969fc285a6fbdda49d91af278488d904dcd7651b2693872f0ff94e40e84a/coverage-7.13.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12", size = 220516, upload-time = "2026-02-09T12:57:44.215Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b8/9531944e16267e2735a30a9641ff49671f07e8138ecf1ca13db9fd2560c7/coverage-7.13.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b", size = 261804, upload-time = "2026-02-09T12:57:45.989Z" }, - { url = "https://files.pythonhosted.org/packages/8a/f3/e63df6d500314a2a60390d1989240d5f27318a7a68fa30ad3806e2a9323e/coverage-7.13.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9", size = 263885, upload-time = "2026-02-09T12:57:47.42Z" }, - { url = "https://files.pythonhosted.org/packages/f3/67/7654810de580e14b37670b60a09c599fa348e48312db5b216d730857ffe6/coverage-7.13.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092", size = 266308, upload-time = "2026-02-09T12:57:49.345Z" }, - { url = "https://files.pythonhosted.org/packages/37/6f/39d41eca0eab3cc82115953ad41c4e77935286c930e8fad15eaed1389d83/coverage-7.13.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9", size = 267452, upload-time = "2026-02-09T12:57:50.811Z" }, - { url = "https://files.pythonhosted.org/packages/50/6d/39c0fbb8fc5cd4d2090811e553c2108cf5112e882f82505ee7495349a6bf/coverage-7.13.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26", size = 261057, upload-time = "2026-02-09T12:57:52.447Z" }, - { url = "https://files.pythonhosted.org/packages/a4/a2/60010c669df5fa603bb5a97fb75407e191a846510da70ac657eb696b7fce/coverage-7.13.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2", size = 263875, upload-time = "2026-02-09T12:57:53.938Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d9/63b22a6bdbd17f1f96e9ed58604c2a6b0e72a9133e37d663bef185877cf6/coverage-7.13.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940", size = 261500, upload-time = "2026-02-09T12:57:56.012Z" }, - { url = "https://files.pythonhosted.org/packages/70/bf/69f86ba1ad85bc3ad240e4c0e57a2e620fbc0e1645a47b5c62f0e941ad7f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c", size = 265212, upload-time = "2026-02-09T12:57:57.5Z" }, - { url = "https://files.pythonhosted.org/packages/ae/f2/5f65a278a8c2148731831574c73e42f57204243d33bedaaf18fa79c5958f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0", size = 260398, upload-time = "2026-02-09T12:57:59.027Z" }, - { url = "https://files.pythonhosted.org/packages/ef/80/6e8280a350ee9fea92f14b8357448a242dcaa243cb2c72ab0ca591f66c8c/coverage-7.13.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b", size = 262584, upload-time = "2026-02-09T12:58:01.129Z" }, - { url = "https://files.pythonhosted.org/packages/22/63/01ff182fc95f260b539590fb12c11ad3e21332c15f9799cb5e2386f71d9f/coverage-7.13.4-cp313-cp313t-win32.whl", hash = "sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9", size = 222688, upload-time = "2026-02-09T12:58:02.736Z" }, - { url = "https://files.pythonhosted.org/packages/a9/43/89de4ef5d3cd53b886afa114065f7e9d3707bdb3e5efae13535b46ae483d/coverage-7.13.4-cp313-cp313t-win_amd64.whl", hash = "sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd", size = 223746, upload-time = "2026-02-09T12:58:05.362Z" }, - { url = "https://files.pythonhosted.org/packages/35/39/7cf0aa9a10d470a5309b38b289b9bb07ddeac5d61af9b664fe9775a4cb3e/coverage-7.13.4-cp313-cp313t-win_arm64.whl", hash = "sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997", size = 222003, upload-time = "2026-02-09T12:58:06.952Z" }, - { url = "https://files.pythonhosted.org/packages/92/11/a9cf762bb83386467737d32187756a42094927150c3e107df4cb078e8590/coverage-7.13.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:300deaee342f90696ed186e3a00c71b5b3d27bffe9e827677954f4ee56969601", size = 219522, upload-time = "2026-02-09T12:58:08.623Z" }, - { url = "https://files.pythonhosted.org/packages/d3/28/56e6d892b7b052236d67c95f1936b6a7cf7c3e2634bf27610b8cbd7f9c60/coverage-7.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29e3220258d682b6226a9b0925bc563ed9a1ebcff3cad30f043eceea7eaf2689", size = 219855, upload-time = "2026-02-09T12:58:10.176Z" }, - { url = "https://files.pythonhosted.org/packages/e5/69/233459ee9eb0c0d10fcc2fe425a029b3fa5ce0f040c966ebce851d030c70/coverage-7.13.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:391ee8f19bef69210978363ca930f7328081c6a0152f1166c91f0b5fdd2a773c", size = 250887, upload-time = "2026-02-09T12:58:12.503Z" }, - { url = "https://files.pythonhosted.org/packages/06/90/2cdab0974b9b5bbc1623f7876b73603aecac11b8d95b85b5b86b32de5eab/coverage-7.13.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0dd7ab8278f0d58a0128ba2fca25824321f05d059c1441800e934ff2efa52129", size = 253396, upload-time = "2026-02-09T12:58:14.615Z" }, - { url = "https://files.pythonhosted.org/packages/ac/15/ea4da0f85bf7d7b27635039e649e99deb8173fe551096ea15017f7053537/coverage-7.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78cdf0d578b15148b009ccf18c686aa4f719d887e76e6b40c38ffb61d264a552", size = 254745, upload-time = "2026-02-09T12:58:16.162Z" }, - { url = "https://files.pythonhosted.org/packages/99/11/bb356e86920c655ca4d61daee4e2bbc7258f0a37de0be32d233b561134ff/coverage-7.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:48685fee12c2eb3b27c62f2658e7ea21e9c3239cba5a8a242801a0a3f6a8c62a", size = 257055, upload-time = "2026-02-09T12:58:17.892Z" }, - { url = "https://files.pythonhosted.org/packages/c9/0f/9ae1f8cb17029e09da06ca4e28c9e1d5c1c0a511c7074592e37e0836c915/coverage-7.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4e83efc079eb39480e6346a15a1bcb3e9b04759c5202d157e1dd4303cd619356", size = 250911, upload-time = "2026-02-09T12:58:19.495Z" }, - { url = "https://files.pythonhosted.org/packages/89/3a/adfb68558fa815cbc29747b553bc833d2150228f251b127f1ce97e48547c/coverage-7.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecae9737b72408d6a950f7e525f30aca12d4bd8dd95e37342e5beb3a2a8c4f71", size = 252754, upload-time = "2026-02-09T12:58:21.064Z" }, - { url = "https://files.pythonhosted.org/packages/32/b1/540d0c27c4e748bd3cd0bd001076ee416eda993c2bae47a73b7cc9357931/coverage-7.13.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ae4578f8528569d3cf303fef2ea569c7f4c4059a38c8667ccef15c6e1f118aa5", size = 250720, upload-time = "2026-02-09T12:58:22.622Z" }, - { url = "https://files.pythonhosted.org/packages/c7/95/383609462b3ffb1fe133014a7c84fc0dd01ed55ac6140fa1093b5af7ebb1/coverage-7.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6fdef321fdfbb30a197efa02d48fcd9981f0d8ad2ae8903ac318adc653f5df98", size = 254994, upload-time = "2026-02-09T12:58:24.548Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ba/1761138e86c81680bfc3c49579d66312865457f9fe405b033184e5793cb3/coverage-7.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b0f6ccf3dbe577170bebfce1318707d0e8c3650003cb4b3a9dd744575daa8b5", size = 250531, upload-time = "2026-02-09T12:58:26.271Z" }, - { url = "https://files.pythonhosted.org/packages/f8/8e/05900df797a9c11837ab59c4d6fe94094e029582aab75c3309a93e6fb4e3/coverage-7.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75fcd519f2a5765db3f0e391eb3b7d150cce1a771bf4c9f861aeab86c767a3c0", size = 252189, upload-time = "2026-02-09T12:58:27.807Z" }, - { url = "https://files.pythonhosted.org/packages/00/bd/29c9f2db9ea4ed2738b8a9508c35626eb205d51af4ab7bf56a21a2e49926/coverage-7.13.4-cp314-cp314-win32.whl", hash = "sha256:8e798c266c378da2bd819b0677df41ab46d78065fb2a399558f3f6cae78b2fbb", size = 222258, upload-time = "2026-02-09T12:58:29.441Z" }, - { url = "https://files.pythonhosted.org/packages/a7/4d/1f8e723f6829977410efeb88f73673d794075091c8c7c18848d273dc9d73/coverage-7.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505", size = 223073, upload-time = "2026-02-09T12:58:31.026Z" }, - { url = "https://files.pythonhosted.org/packages/51/5b/84100025be913b44e082ea32abcf1afbf4e872f5120b7a1cab1d331b1e13/coverage-7.13.4-cp314-cp314-win_arm64.whl", hash = "sha256:ad27098a189e5838900ce4c2a99f2fe42a0bf0c2093c17c69b45a71579e8d4a2", size = 221638, upload-time = "2026-02-09T12:58:32.599Z" }, - { url = "https://files.pythonhosted.org/packages/a7/e4/c884a405d6ead1370433dad1e3720216b4f9fd8ef5b64bfd984a2a60a11a/coverage-7.13.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:85480adfb35ffc32d40918aad81b89c69c9cc5661a9b8a81476d3e645321a056", size = 220246, upload-time = "2026-02-09T12:58:34.181Z" }, - { url = "https://files.pythonhosted.org/packages/81/5c/4d7ed8b23b233b0fffbc9dfec53c232be2e695468523242ea9fd30f97ad2/coverage-7.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:79be69cf7f3bf9b0deeeb062eab7ac7f36cd4cc4c4dd694bd28921ba4d8596cc", size = 220514, upload-time = "2026-02-09T12:58:35.704Z" }, - { url = "https://files.pythonhosted.org/packages/2f/6f/3284d4203fd2f28edd73034968398cd2d4cb04ab192abc8cff007ea35679/coverage-7.13.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:caa421e2684e382c5d8973ac55e4f36bed6821a9bad5c953494de960c74595c9", size = 261877, upload-time = "2026-02-09T12:58:37.864Z" }, - { url = "https://files.pythonhosted.org/packages/09/aa/b672a647bbe1556a85337dc95bfd40d146e9965ead9cc2fe81bde1e5cbce/coverage-7.13.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14375934243ee05f56c45393fe2ce81fe5cc503c07cee2bdf1725fb8bef3ffaf", size = 264004, upload-time = "2026-02-09T12:58:39.492Z" }, - { url = "https://files.pythonhosted.org/packages/79/a1/aa384dbe9181f98bba87dd23dda436f0c6cf2e148aecbb4e50fc51c1a656/coverage-7.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25a41c3104d08edb094d9db0d905ca54d0cd41c928bb6be3c4c799a54753af55", size = 266408, upload-time = "2026-02-09T12:58:41.852Z" }, - { url = "https://files.pythonhosted.org/packages/53/5e/5150bf17b4019bc600799f376bb9606941e55bd5a775dc1e096b6ffea952/coverage-7.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f01afcff62bf9a08fb32b2c1d6e924236c0383c02c790732b6537269e466a72", size = 267544, upload-time = "2026-02-09T12:58:44.093Z" }, - { url = "https://files.pythonhosted.org/packages/e0/ed/f1de5c675987a4a7a672250d2c5c9d73d289dbf13410f00ed7181d8017dd/coverage-7.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eb9078108fbf0bcdde37c3f4779303673c2fa1fe8f7956e68d447d0dd426d38a", size = 260980, upload-time = "2026-02-09T12:58:45.721Z" }, - { url = "https://files.pythonhosted.org/packages/b3/e3/fe758d01850aa172419a6743fe76ba8b92c29d181d4f676ffe2dae2ba631/coverage-7.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e086334e8537ddd17e5f16a344777c1ab8194986ec533711cbe6c41cde841b6", size = 263871, upload-time = "2026-02-09T12:58:47.334Z" }, - { url = "https://files.pythonhosted.org/packages/b6/76/b829869d464115e22499541def9796b25312b8cf235d3bb00b39f1675395/coverage-7.13.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:725d985c5ab621268b2edb8e50dfe57633dc69bda071abc470fed55a14935fd3", size = 261472, upload-time = "2026-02-09T12:58:48.995Z" }, - { url = "https://files.pythonhosted.org/packages/14/9e/caedb1679e73e2f6ad240173f55218488bfe043e38da577c4ec977489915/coverage-7.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3c06f0f1337c667b971ca2f975523347e63ec5e500b9aa5882d91931cd3ef750", size = 265210, upload-time = "2026-02-09T12:58:51.178Z" }, - { url = "https://files.pythonhosted.org/packages/3a/10/0dd02cb009b16ede425b49ec344aba13a6ae1dc39600840ea6abcb085ac4/coverage-7.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:590c0ed4bf8e85f745e6b805b2e1c457b2e33d5255dd9729743165253bc9ad39", size = 260319, upload-time = "2026-02-09T12:58:53.081Z" }, - { url = "https://files.pythonhosted.org/packages/92/8e/234d2c927af27c6d7a5ffad5bd2cf31634c46a477b4c7adfbfa66baf7ebb/coverage-7.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eb30bf180de3f632cd043322dad5751390e5385108b2807368997d1a92a509d0", size = 262638, upload-time = "2026-02-09T12:58:55.258Z" }, - { url = "https://files.pythonhosted.org/packages/2f/64/e5547c8ff6964e5965c35a480855911b61509cce544f4d442caa759a0702/coverage-7.13.4-cp314-cp314t-win32.whl", hash = "sha256:c4240e7eded42d131a2d2c4dec70374b781b043ddc79a9de4d55ca71f8e98aea", size = 223040, upload-time = "2026-02-09T12:58:56.936Z" }, - { url = "https://files.pythonhosted.org/packages/c7/96/38086d58a181aac86d503dfa9c47eb20715a79c3e3acbdf786e92e5c09a8/coverage-7.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4c7d3cc01e7350f2f0f6f7036caaf5673fb56b6998889ccfe9e1c1fe75a9c932", size = 224148, upload-time = "2026-02-09T12:58:58.645Z" }, - { url = "https://files.pythonhosted.org/packages/ce/72/8d10abd3740a0beb98c305e0c3faf454366221c0f37a8bcf8f60020bb65a/coverage-7.13.4-cp314-cp314t-win_arm64.whl", hash = "sha256:23e3f687cf945070d1c90f85db66d11e3025665d8dafa831301a0e0038f3db9b", size = 222172, upload-time = "2026-02-09T12:59:00.396Z" }, - { url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" }, +version = "7.13.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967, upload-time = "2026-03-17T10:33:18.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/37/d24c8f8220ff07b839b2c043ea4903a33b0f455abe673ae3c03bbdb7f212/coverage-7.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66a80c616f80181f4d643b0f9e709d97bcea413ecd9631e1dedc7401c8e6695d", size = 219381, upload-time = "2026-03-17T10:30:14.68Z" }, + { url = "https://files.pythonhosted.org/packages/35/8b/cd129b0ca4afe886a6ce9d183c44d8301acbd4ef248622e7c49a23145605/coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:145ede53ccbafb297c1c9287f788d1bc3efd6c900da23bf6931b09eafc931587", size = 219880, upload-time = "2026-03-17T10:30:16.231Z" }, + { url = "https://files.pythonhosted.org/packages/55/2f/e0e5b237bffdb5d6c530ce87cc1d413a5b7d7dfd60fb067ad6d254c35c76/coverage-7.13.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0672854dc733c342fa3e957e0605256d2bf5934feeac328da9e0b5449634a642", size = 250303, upload-time = "2026-03-17T10:30:17.748Z" }, + { url = "https://files.pythonhosted.org/packages/92/be/b1afb692be85b947f3401375851484496134c5554e67e822c35f28bf2fbc/coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ec10e2a42b41c923c2209b846126c6582db5e43a33157e9870ba9fb70dc7854b", size = 252218, upload-time = "2026-03-17T10:30:19.804Z" }, + { url = "https://files.pythonhosted.org/packages/da/69/2f47bb6fa1b8d1e3e5d0c4be8ccb4313c63d742476a619418f85740d597b/coverage-7.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be3d4bbad9d4b037791794ddeedd7d64a56f5933a2c1373e18e9e568b9141686", size = 254326, upload-time = "2026-03-17T10:30:21.321Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d0/79db81da58965bd29dabc8f4ad2a2af70611a57cba9d1ec006f072f30a54/coverage-7.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d2afbc5cc54d286bfb54541aa50b64cdb07a718227168c87b9e2fb8f25e1743", size = 256267, upload-time = "2026-03-17T10:30:23.094Z" }, + { url = "https://files.pythonhosted.org/packages/e5/32/d0d7cc8168f91ddab44c0ce4806b969df5f5fdfdbb568eaca2dbc2a04936/coverage-7.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3ad050321264c49c2fa67bb599100456fc51d004b82534f379d16445da40fb75", size = 250430, upload-time = "2026-03-17T10:30:25.311Z" }, + { url = "https://files.pythonhosted.org/packages/4d/06/a055311d891ddbe231cd69fdd20ea4be6e3603ffebddf8704b8ca8e10a3c/coverage-7.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7300c8a6d13335b29bb76d7651c66af6bd8658517c43499f110ddc6717bfc209", size = 252017, upload-time = "2026-03-17T10:30:27.284Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f6/d0fd2d21e29a657b5f77a2fe7082e1568158340dceb941954f776dce1b7b/coverage-7.13.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:eb07647a5738b89baab047f14edd18ded523de60f3b30e75c2acc826f79c839a", size = 250080, upload-time = "2026-03-17T10:30:29.481Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ab/0d7fb2efc2e9a5eb7ddcc6e722f834a69b454b7e6e5888c3a8567ecffb31/coverage-7.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9adb6688e3b53adffefd4a52d72cbd8b02602bfb8f74dcd862337182fd4d1a4e", size = 253843, upload-time = "2026-03-17T10:30:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/ba/6f/7467b917bbf5408610178f62a49c0ed4377bb16c1657f689cc61470da8ce/coverage-7.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7c8d4bc913dd70b93488d6c496c77f3aff5ea99a07e36a18f865bca55adef8bd", size = 249802, upload-time = "2026-03-17T10:30:33.358Z" }, + { url = "https://files.pythonhosted.org/packages/75/2c/1172fb689df92135f5bfbbd69fc83017a76d24ea2e2f3a1154007e2fb9f8/coverage-7.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e3c426ffc4cd952f54ee9ffbdd10345709ecc78a3ecfd796a57236bfad0b9b8", size = 250707, upload-time = "2026-03-17T10:30:35.2Z" }, + { url = "https://files.pythonhosted.org/packages/67/21/9ac389377380a07884e3b48ba7a620fcd9dbfaf1d40565facdc6b36ec9ef/coverage-7.13.5-cp311-cp311-win32.whl", hash = "sha256:259b69bb83ad9894c4b25be2528139eecba9a82646ebdda2d9db1ba28424a6bf", size = 221880, upload-time = "2026-03-17T10:30:36.775Z" }, + { url = "https://files.pythonhosted.org/packages/af/7f/4cd8a92531253f9d7c1bbecd9fa1b472907fb54446ca768c59b531248dc5/coverage-7.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:258354455f4e86e3e9d0d17571d522e13b4e1e19bf0f8596bcf9476d61e7d8a9", size = 222816, upload-time = "2026-03-17T10:30:38.891Z" }, + { url = "https://files.pythonhosted.org/packages/12/a6/1d3f6155fb0010ca68eba7fe48ca6c9da7385058b77a95848710ecf189b1/coverage-7.13.5-cp311-cp311-win_arm64.whl", hash = "sha256:bff95879c33ec8da99fc9b6fe345ddb5be6414b41d6d1ad1c8f188d26f36e028", size = 221483, upload-time = "2026-03-17T10:30:40.463Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c3/a396306ba7db865bf96fc1fb3b7fd29bcbf3d829df642e77b13555163cd6/coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01", size = 219554, upload-time = "2026-03-17T10:30:42.208Z" }, + { url = "https://files.pythonhosted.org/packages/a6/16/a68a19e5384e93f811dccc51034b1fd0b865841c390e3c931dcc4699e035/coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422", size = 219908, upload-time = "2026-03-17T10:30:43.906Z" }, + { url = "https://files.pythonhosted.org/packages/29/72/20b917c6793af3a5ceb7fb9c50033f3ec7865f2911a1416b34a7cfa0813b/coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f", size = 251419, upload-time = "2026-03-17T10:30:45.545Z" }, + { url = "https://files.pythonhosted.org/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5", size = 254159, upload-time = "2026-03-17T10:30:47.204Z" }, + { url = "https://files.pythonhosted.org/packages/9d/00/7b0edcfe64e2ed4c0340dac14a52ad0f4c9bd0b8b5e531af7d55b703db7c/coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376", size = 255270, upload-time = "2026-03-17T10:30:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/7ffc4ba0f5d0a55c1e84ea7cee39c9fc06af7b170513d83fbf3bbefce280/coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256", size = 257538, upload-time = "2026-03-17T10:30:50.77Z" }, + { url = "https://files.pythonhosted.org/packages/81/bd/73ddf85f93f7e6fa83e77ccecb6162d9415c79007b4bc124008a4995e4a7/coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c", size = 251821, upload-time = "2026-03-17T10:30:52.5Z" }, + { url = "https://files.pythonhosted.org/packages/a0/81/278aff4e8dec4926a0bcb9486320752811f543a3ce5b602cc7a29978d073/coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5", size = 253191, upload-time = "2026-03-17T10:30:54.543Z" }, + { url = "https://files.pythonhosted.org/packages/70/ee/fe1621488e2e0a58d7e94c4800f0d96f79671553488d401a612bebae324b/coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09", size = 251337, upload-time = "2026-03-17T10:30:56.663Z" }, + { url = "https://files.pythonhosted.org/packages/37/a6/f79fb37aa104b562207cc23cb5711ab6793608e246cae1e93f26b2236ed9/coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9", size = 255404, upload-time = "2026-03-17T10:30:58.427Z" }, + { url = "https://files.pythonhosted.org/packages/75/f0/ed15262a58ec81ce457ceb717b7f78752a1713556b19081b76e90896e8d4/coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf", size = 250903, upload-time = "2026-03-17T10:31:00.093Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e9/9129958f20e7e9d4d56d51d42ccf708d15cac355ff4ac6e736e97a9393d2/coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c", size = 252780, upload-time = "2026-03-17T10:31:01.916Z" }, + { url = "https://files.pythonhosted.org/packages/a4/d7/0ad9b15812d81272db94379fe4c6df8fd17781cc7671fdfa30c76ba5ff7b/coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf", size = 222093, upload-time = "2026-03-17T10:31:03.642Z" }, + { url = "https://files.pythonhosted.org/packages/29/3d/821a9a5799fac2556bcf0bd37a70d1d11fa9e49784b6d22e92e8b2f85f18/coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810", size = 222900, upload-time = "2026-03-17T10:31:05.651Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/2238c2ad08e35cf4f020ea721f717e09ec3152aea75d191a7faf3ef009a8/coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de", size = 221515, upload-time = "2026-03-17T10:31:07.293Z" }, + { url = "https://files.pythonhosted.org/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1", size = 219576, upload-time = "2026-03-17T10:31:09.045Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3", size = 219942, upload-time = "2026-03-17T10:31:10.708Z" }, + { url = "https://files.pythonhosted.org/packages/5f/13/93419671cee82b780bab7ea96b67c8ef448f5f295f36bf5031154ec9a790/coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26", size = 250935, upload-time = "2026-03-17T10:31:12.392Z" }, + { url = "https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3", size = 253541, upload-time = "2026-03-17T10:31:14.247Z" }, + { url = "https://files.pythonhosted.org/packages/4e/5e/3ee3b835647be646dcf3c65a7c6c18f87c27326a858f72ab22c12730773d/coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b", size = 254780, upload-time = "2026-03-17T10:31:16.193Z" }, + { url = "https://files.pythonhosted.org/packages/44/b3/cb5bd1a04cfcc49ede6cd8409d80bee17661167686741e041abc7ee1b9a9/coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a", size = 256912, upload-time = "2026-03-17T10:31:17.89Z" }, + { url = "https://files.pythonhosted.org/packages/1b/66/c1dceb7b9714473800b075f5c8a84f4588f887a90eb8645282031676e242/coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969", size = 251165, upload-time = "2026-03-17T10:31:19.605Z" }, + { url = "https://files.pythonhosted.org/packages/b7/62/5502b73b97aa2e53ea22a39cf8649ff44827bef76d90bf638777daa27a9d/coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161", size = 252908, upload-time = "2026-03-17T10:31:21.312Z" }, + { url = "https://files.pythonhosted.org/packages/7d/37/7792c2d69854397ca77a55c4646e5897c467928b0e27f2d235d83b5d08c6/coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15", size = 250873, upload-time = "2026-03-17T10:31:23.565Z" }, + { url = "https://files.pythonhosted.org/packages/a3/23/bc866fb6163be52a8a9e5d708ba0d3b1283c12158cefca0a8bbb6e247a43/coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1", size = 255030, upload-time = "2026-03-17T10:31:25.58Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8b/ef67e1c222ef49860701d346b8bbb70881bef283bd5f6cbba68a39a086c7/coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6", size = 250694, upload-time = "2026-03-17T10:31:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/46/0d/866d1f74f0acddbb906db212e096dee77a8e2158ca5e6bb44729f9d93298/coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17", size = 252469, upload-time = "2026-03-17T10:31:29.472Z" }, + { url = "https://files.pythonhosted.org/packages/7a/f5/be742fec31118f02ce42b21c6af187ad6a344fed546b56ca60caacc6a9a0/coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85", size = 222112, upload-time = "2026-03-17T10:31:31.526Z" }, + { url = "https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b", size = 222923, upload-time = "2026-03-17T10:31:33.633Z" }, + { url = "https://files.pythonhosted.org/packages/48/af/fea819c12a095781f6ccd504890aaddaf88b8fab263c4940e82c7b770124/coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664", size = 221540, upload-time = "2026-03-17T10:31:35.445Z" }, + { url = "https://files.pythonhosted.org/packages/23/d2/17879af479df7fbbd44bd528a31692a48f6b25055d16482fdf5cdb633805/coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d", size = 220262, upload-time = "2026-03-17T10:31:37.184Z" }, + { url = "https://files.pythonhosted.org/packages/5b/4c/d20e554f988c8f91d6a02c5118f9abbbf73a8768a3048cb4962230d5743f/coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0", size = 220617, upload-time = "2026-03-17T10:31:39.245Z" }, + { url = "https://files.pythonhosted.org/packages/29/9c/f9f5277b95184f764b24e7231e166dfdb5780a46d408a2ac665969416d61/coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806", size = 261912, upload-time = "2026-03-17T10:31:41.324Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f6/7f1ab39393eeb50cfe4747ae8ef0e4fc564b989225aa1152e13a180d74f8/coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3", size = 263987, upload-time = "2026-03-17T10:31:43.724Z" }, + { url = "https://files.pythonhosted.org/packages/a0/d7/62c084fb489ed9c6fbdf57e006752e7c516ea46fd690e5ed8b8617c7d52e/coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9", size = 266416, upload-time = "2026-03-17T10:31:45.769Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f6/df63d8660e1a0bff6125947afda112a0502736f470d62ca68b288ea762d8/coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd", size = 267558, upload-time = "2026-03-17T10:31:48.293Z" }, + { url = "https://files.pythonhosted.org/packages/5b/02/353ca81d36779bd108f6d384425f7139ac3c58c750dcfaafe5d0bee6436b/coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606", size = 261163, upload-time = "2026-03-17T10:31:50.125Z" }, + { url = "https://files.pythonhosted.org/packages/2c/16/2e79106d5749bcaf3aee6d309123548e3276517cd7851faa8da213bc61bf/coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e", size = 263981, upload-time = "2026-03-17T10:31:51.961Z" }, + { url = "https://files.pythonhosted.org/packages/29/c7/c29e0c59ffa6942030ae6f50b88ae49988e7e8da06de7ecdbf49c6d4feae/coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0", size = 261604, upload-time = "2026-03-17T10:31:53.872Z" }, + { url = "https://files.pythonhosted.org/packages/40/48/097cdc3db342f34006a308ab41c3a7c11c3f0d84750d340f45d88a782e00/coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87", size = 265321, upload-time = "2026-03-17T10:31:55.997Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1f/4994af354689e14fd03a75f8ec85a9a68d94e0188bbdab3fc1516b55e512/coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479", size = 260502, upload-time = "2026-03-17T10:31:58.308Z" }, + { url = "https://files.pythonhosted.org/packages/22/c6/9bb9ef55903e628033560885f5c31aa227e46878118b63ab15dc7ba87797/coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2", size = 262688, upload-time = "2026-03-17T10:32:00.141Z" }, + { url = "https://files.pythonhosted.org/packages/14/4f/f5df9007e50b15e53e01edea486814783a7f019893733d9e4d6caad75557/coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a", size = 222788, upload-time = "2026-03-17T10:32:02.246Z" }, + { url = "https://files.pythonhosted.org/packages/e1/98/aa7fccaa97d0f3192bec013c4e6fd6d294a6ed44b640e6bb61f479e00ed5/coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819", size = 223851, upload-time = "2026-03-17T10:32:04.416Z" }, + { url = "https://files.pythonhosted.org/packages/3d/8b/e5c469f7352651e5f013198e9e21f97510b23de957dd06a84071683b4b60/coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911", size = 222104, upload-time = "2026-03-17T10:32:06.65Z" }, + { url = "https://files.pythonhosted.org/packages/8e/77/39703f0d1d4b478bfd30191d3c14f53caf596fac00efb3f8f6ee23646439/coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f", size = 219621, upload-time = "2026-03-17T10:32:08.589Z" }, + { url = "https://files.pythonhosted.org/packages/e2/3e/51dff36d99ae14639a133d9b164d63e628532e2974d8b1edb99dd1ebc733/coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e", size = 219953, upload-time = "2026-03-17T10:32:10.507Z" }, + { url = "https://files.pythonhosted.org/packages/6a/6c/1f1917b01eb647c2f2adc9962bd66c79eb978951cab61bdc1acab3290c07/coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a", size = 250992, upload-time = "2026-03-17T10:32:12.41Z" }, + { url = "https://files.pythonhosted.org/packages/22/e5/06b1f88f42a5a99df42ce61208bdec3bddb3d261412874280a19796fc09c/coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510", size = 253503, upload-time = "2026-03-17T10:32:14.449Z" }, + { url = "https://files.pythonhosted.org/packages/80/28/2a148a51e5907e504fa7b85490277734e6771d8844ebcc48764a15e28155/coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247", size = 254852, upload-time = "2026-03-17T10:32:16.56Z" }, + { url = "https://files.pythonhosted.org/packages/61/77/50e8d3d85cc0b7ebe09f30f151d670e302c7ff4a1bf6243f71dd8b0981fa/coverage-7.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6", size = 257161, upload-time = "2026-03-17T10:32:19.004Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c4/b5fd1d4b7bf8d0e75d997afd3925c59ba629fc8616f1b3aae7605132e256/coverage-7.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0", size = 251021, upload-time = "2026-03-17T10:32:21.344Z" }, + { url = "https://files.pythonhosted.org/packages/f8/66/6ea21f910e92d69ef0b1c3346ea5922a51bad4446c9126db2ae96ee24c4c/coverage-7.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882", size = 252858, upload-time = "2026-03-17T10:32:23.506Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ea/879c83cb5d61aa2a35fb80e72715e92672daef8191b84911a643f533840c/coverage-7.13.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740", size = 250823, upload-time = "2026-03-17T10:32:25.516Z" }, + { url = "https://files.pythonhosted.org/packages/8a/fb/616d95d3adb88b9803b275580bdeee8bd1b69a886d057652521f83d7322f/coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16", size = 255099, upload-time = "2026-03-17T10:32:27.944Z" }, + { url = "https://files.pythonhosted.org/packages/1c/93/25e6917c90ec1c9a56b0b26f6cad6408e5f13bb6b35d484a0d75c9cf000d/coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0", size = 250638, upload-time = "2026-03-17T10:32:29.914Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7b/dc1776b0464145a929deed214aef9fb1493f159b59ff3c7eeeedf91eddd0/coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0", size = 252295, upload-time = "2026-03-17T10:32:31.981Z" }, + { url = "https://files.pythonhosted.org/packages/ea/fb/99cbbc56a26e07762a2740713f3c8f9f3f3106e3a3dd8cc4474954bccd34/coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc", size = 222360, upload-time = "2026-03-17T10:32:34.233Z" }, + { url = "https://files.pythonhosted.org/packages/8d/b7/4758d4f73fb536347cc5e4ad63662f9d60ba9118cb6785e9616b2ce5d7fa/coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633", size = 223174, upload-time = "2026-03-17T10:32:36.369Z" }, + { url = "https://files.pythonhosted.org/packages/2c/f2/24d84e1dfe70f8ac9fdf30d338239860d0d1d5da0bda528959d0ebc9da28/coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8", size = 221739, upload-time = "2026-03-17T10:32:38.736Z" }, + { url = "https://files.pythonhosted.org/packages/60/5b/4a168591057b3668c2428bff25dd3ebc21b629d666d90bcdfa0217940e84/coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b", size = 220351, upload-time = "2026-03-17T10:32:41.196Z" }, + { url = "https://files.pythonhosted.org/packages/f5/21/1fd5c4dbfe4a58b6b99649125635df46decdfd4a784c3cd6d410d303e370/coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c", size = 220612, upload-time = "2026-03-17T10:32:43.204Z" }, + { url = "https://files.pythonhosted.org/packages/d6/fe/2a924b3055a5e7e4512655a9d4609781b0d62334fa0140c3e742926834e2/coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9", size = 261985, upload-time = "2026-03-17T10:32:45.514Z" }, + { url = "https://files.pythonhosted.org/packages/d7/0d/c8928f2bd518c45990fe1a2ab8db42e914ef9b726c975facc4282578c3eb/coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29", size = 264107, upload-time = "2026-03-17T10:32:47.971Z" }, + { url = "https://files.pythonhosted.org/packages/ef/ae/4ae35bbd9a0af9d820362751f0766582833c211224b38665c0f8de3d487f/coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607", size = 266513, upload-time = "2026-03-17T10:32:50.1Z" }, + { url = "https://files.pythonhosted.org/packages/9c/20/d326174c55af36f74eac6ae781612d9492f060ce8244b570bb9d50d9d609/coverage-7.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90", size = 267650, upload-time = "2026-03-17T10:32:52.391Z" }, + { url = "https://files.pythonhosted.org/packages/7a/5e/31484d62cbd0eabd3412e30d74386ece4a0837d4f6c3040a653878bfc019/coverage-7.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3", size = 261089, upload-time = "2026-03-17T10:32:54.544Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d8/49a72d6de146eebb0b7e48cc0f4bc2c0dd858e3d4790ab2b39a2872b62bd/coverage-7.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab", size = 263982, upload-time = "2026-03-17T10:32:56.803Z" }, + { url = "https://files.pythonhosted.org/packages/06/3b/0351f1bd566e6e4dd39e978efe7958bde1d32f879e85589de147654f57bb/coverage-7.13.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562", size = 261579, upload-time = "2026-03-17T10:32:59.466Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ce/796a2a2f4017f554d7810f5c573449b35b1e46788424a548d4d19201b222/coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2", size = 265316, upload-time = "2026-03-17T10:33:01.847Z" }, + { url = "https://files.pythonhosted.org/packages/3d/16/d5ae91455541d1a78bc90abf495be600588aff8f6db5c8b0dae739fa39c9/coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea", size = 260427, upload-time = "2026-03-17T10:33:03.945Z" }, + { url = "https://files.pythonhosted.org/packages/48/11/07f413dba62db21fb3fad5d0de013a50e073cc4e2dc4306e770360f6dfc8/coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a", size = 262745, upload-time = "2026-03-17T10:33:06.285Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/d792371332eb4663115becf4bad47e047d16234b1aff687b1b18c58d60ae/coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215", size = 223146, upload-time = "2026-03-17T10:33:08.756Z" }, + { url = "https://files.pythonhosted.org/packages/db/51/37221f59a111dca5e85be7dbf09696323b5b9f13ff65e0641d535ed06ea8/coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43", size = 224254, upload-time = "2026-03-17T10:33:11.174Z" }, + { url = "https://files.pythonhosted.org/packages/54/83/6acacc889de8987441aa7d5adfbdbf33d288dad28704a67e574f1df9bcbb/coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45", size = 222276, upload-time = "2026-03-17T10:33:13.466Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346, upload-time = "2026-03-17T10:33:15.691Z" }, ] [package.optional-dependencies] @@ -620,26 +636,73 @@ toml = [ [[package]] name = "cuda-bindings" -version = "12.9.4" +version = "13.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cuda-pathfinder" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/45/e7/b47792cc2d01c7e1d37c32402182524774dadd2d26339bd224e0e913832e/cuda_bindings-12.9.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c912a3d9e6b6651853eed8eed96d6800d69c08e94052c292fec3f282c5a817c9", size = 12210593, upload-time = "2025-10-21T14:51:36.574Z" }, - { url = "https://files.pythonhosted.org/packages/a9/c1/dabe88f52c3e3760d861401bb994df08f672ec893b8f7592dc91626adcf3/cuda_bindings-12.9.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fda147a344e8eaeca0c6ff113d2851ffca8f7dfc0a6c932374ee5c47caa649c8", size = 12151019, upload-time = "2025-10-21T14:51:43.167Z" }, - { url = "https://files.pythonhosted.org/packages/63/56/e465c31dc9111be3441a9ba7df1941fe98f4aa6e71e8788a3fb4534ce24d/cuda_bindings-12.9.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:32bdc5a76906be4c61eb98f546a6786c5773a881f3b166486449b5d141e4a39f", size = 11906628, upload-time = "2025-10-21T14:51:49.905Z" }, - { url = "https://files.pythonhosted.org/packages/a3/84/1e6be415e37478070aeeee5884c2022713c1ecc735e6d82d744de0252eee/cuda_bindings-12.9.4-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56e0043c457a99ac473ddc926fe0dc4046694d99caef633e92601ab52cbe17eb", size = 11925991, upload-time = "2025-10-21T14:51:56.535Z" }, - { url = "https://files.pythonhosted.org/packages/d1/af/6dfd8f2ed90b1d4719bc053ff8940e494640fe4212dc3dd72f383e4992da/cuda_bindings-12.9.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8b72ee72a9cc1b531db31eebaaee5c69a8ec3500e32c6933f2d3b15297b53686", size = 11922703, upload-time = "2025-10-21T14:52:03.585Z" }, - { url = "https://files.pythonhosted.org/packages/6c/19/90ac264acc00f6df8a49378eedec9fd2db3061bf9263bf9f39fd3d8377c3/cuda_bindings-12.9.4-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d80bffc357df9988dca279734bc9674c3934a654cab10cadeed27ce17d8635ee", size = 11924658, upload-time = "2025-10-21T14:52:10.411Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a9/3a8241c6e19483ac1f1dcf5c10238205dcb8a6e9d0d4d4709240dff28ff4/cuda_bindings-13.2.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:721104c603f059780d287969be3d194a18d0cc3b713ed9049065a1107706759d", size = 5730273, upload-time = "2026-03-11T00:12:37.18Z" }, + { url = "https://files.pythonhosted.org/packages/e9/94/2748597f47bb1600cd466b20cab4159f1530a3a33fe7f70fee199b3abb9e/cuda_bindings-13.2.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1eba9504ac70667dd48313395fe05157518fd6371b532790e96fbb31bbb5a5e1", size = 6313924, upload-time = "2026-03-11T00:12:39.462Z" }, + { url = "https://files.pythonhosted.org/packages/52/c8/b2589d68acf7e3d63e2be330b84bc25712e97ed799affbca7edd7eae25d6/cuda_bindings-13.2.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e865447abfb83d6a98ad5130ed3c70b1fc295ae3eeee39fd07b4ddb0671b6788", size = 5722404, upload-time = "2026-03-11T00:12:44.041Z" }, + { url = "https://files.pythonhosted.org/packages/1f/92/f899f7bbb5617bb65ec52a6eac1e9a1447a86b916c4194f8a5001b8cde0c/cuda_bindings-13.2.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46d8776a55d6d5da9dd6e9858fba2efcda2abe6743871dee47dd06eb8cb6d955", size = 6320619, upload-time = "2026-03-11T00:12:45.939Z" }, + { url = "https://files.pythonhosted.org/packages/df/93/eef988860a3ca985f82c4f3174fc0cdd94e07331ba9a92e8e064c260337f/cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6629ca2df6f795b784752409bcaedbd22a7a651b74b56a165ebc0c9dcbd504d0", size = 5614610, upload-time = "2026-03-11T00:12:50.337Z" }, + { url = "https://files.pythonhosted.org/packages/18/23/6db3aba46864aee357ab2415135b3fe3da7e9f1fa0221fa2a86a5968099c/cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7dca0da053d3b4cc4869eff49c61c03f3c5dbaa0bcd712317a358d5b8f3f385d", size = 6149914, upload-time = "2026-03-11T00:12:52.374Z" }, + { url = "https://files.pythonhosted.org/packages/c0/87/87a014f045b77c6de5c8527b0757fe644417b184e5367db977236a141602/cuda_bindings-13.2.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6464b30f46692d6c7f65d4a0e0450d81dd29de3afc1bb515653973d01c2cd6e", size = 5685673, upload-time = "2026-03-11T00:12:56.371Z" }, + { url = "https://files.pythonhosted.org/packages/ee/5e/c0fe77a73aaefd3fff25ffaccaac69c5a63eafdf8b9a4c476626ef0ac703/cuda_bindings-13.2.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4af9f3e1be603fa12d5ad6cfca7844c9d230befa9792b5abdf7dd79979c3626", size = 6191386, upload-time = "2026-03-11T00:12:58.965Z" }, + { url = "https://files.pythonhosted.org/packages/5f/58/ed2c3b39c8dd5f96aa7a4abef0d47a73932c7a988e30f5fa428f00ed0da1/cuda_bindings-13.2.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df850a1ff8ce1b3385257b08e47b70e959932f5f432d0a4e46a355962b4e4771", size = 5507469, upload-time = "2026-03-11T00:13:04.063Z" }, + { url = "https://files.pythonhosted.org/packages/1f/01/0c941b112ceeb21439b05895eace78ca1aa2eaaf695c8521a068fd9b4c00/cuda_bindings-13.2.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8a16384c6494e5485f39314b0b4afb04bee48d49edb16d5d8593fd35bbd231b", size = 6059693, upload-time = "2026-03-11T00:13:06.003Z" }, ] [[package]] name = "cuda-pathfinder" -version = "1.4.0" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/66/0c02bd330e7d976f83fa68583d6198d76f23581bcbb5c0e98a6148f326e5/cuda_pathfinder-1.5.0-py3-none-any.whl", hash = "sha256:498f90a9e9de36044a7924742aecce11c50c49f735f1bc53e05aa46de9ea4110", size = 49739, upload-time = "2026-03-24T21:14:30.869Z" }, +] + +[[package]] +name = "cuda-toolkit" +version = "13.0.2" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/60/d8f1dbfb7f06b94c662e98c95189e6f39b817da638bc8fcea0d003f89e5d/cuda_pathfinder-1.4.0-py3-none-any.whl", hash = "sha256:437079ca59e7b61ae439ecc501d69ed87b3accc34d58153ef1e54815e2c2e118", size = 38406, upload-time = "2026-02-25T22:13:00.807Z" }, + { url = "https://files.pythonhosted.org/packages/57/b2/453099f5f3b698d7d0eab38916aac44c7f76229f451709e2eb9db6615dcd/cuda_toolkit-13.0.2-py2.py3-none-any.whl", hash = "sha256:b198824cf2f54003f50d64ada3a0f184b42ca0846c1c94192fa269ecd97a66eb", size = 2364, upload-time = "2025-12-19T23:24:07.328Z" }, +] + +[package.optional-dependencies] +cublas = [ + { name = "nvidia-cublas", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +cudart = [ + { name = "nvidia-cuda-runtime", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +cufft = [ + { name = "nvidia-cufft", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +cufile = [ + { name = "nvidia-cufile", marker = "sys_platform == 'linux'" }, +] +cupti = [ + { name = "nvidia-cuda-cupti", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +curand = [ + { name = "nvidia-curand", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +cusolver = [ + { name = "nvidia-cusolver", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +cusparse = [ + { name = "nvidia-cusparse", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +nvjitlink = [ + { name = "nvidia-nvjitlink", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +nvrtc = [ + { name = "nvidia-cuda-nvrtc", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +nvtx = [ + { name = "nvidia-nvtx", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, ] [[package]] @@ -653,7 +716,7 @@ wheels = [ [[package]] name = "dask" -version = "2026.1.2" +version = "2026.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -665,9 +728,9 @@ dependencies = [ { name = "pyyaml" }, { name = "toolz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bd/52/b0f9172b22778def907db1ff173249e4eb41f054b46a9c83b1528aaf811f/dask-2026.1.2.tar.gz", hash = "sha256:1136683de2750d98ea792670f7434e6c1cfce90cab2cc2f2495a9e60fd25a4fc", size = 10997838, upload-time = "2026-01-30T21:04:20.54Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/2a/5d8cc1579590af86576dde890254440e478c7174b93a02095ecfc2e6ba38/dask-2026.3.0.tar.gz", hash = "sha256:f7d96c8274e8a900d217c1ff6ea8d1bbf0b4c2c21e74a409644498d925eb8f85", size = 11000710, upload-time = "2026-03-18T07:10:14.945Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl", hash = "sha256:46a0cf3b8d87f78a3d2e6b145aea4418a6d6d606fe6a16c79bd8ca2bb862bc91", size = 1482084, upload-time = "2026-01-30T21:04:18.363Z" }, + { url = "https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl", hash = "sha256:be614b9242b0b38288060fb2d7696125946469c98a1c30e174883fd199e0428d", size = 1485630, upload-time = "2026-03-18T07:10:12.832Z" }, ] [package.optional-dependencies] @@ -768,11 +831,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.25.0" +version = "3.25.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/77/18/a1fd2231c679dcb9726204645721b12498aeac28e1ad0601038f94b42556/filelock-3.25.0.tar.gz", hash = "sha256:8f00faf3abf9dc730a1ffe9c354ae5c04e079ab7d3a683b7c32da5dd05f26af3", size = 40158, upload-time = "2026-03-01T15:08:45.916Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/b8/00651a0f559862f3bb7d6f7477b192afe3f583cc5e26403b44e59a55ab34/filelock-3.25.2.tar.gz", hash = "sha256:b64ece2b38f4ca29dd3e810287aa8c48182bbecd1ae6e9ae126c9b35f1382694", size = 40480, upload-time = "2026-03-11T20:45:38.487Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/0b/de6f54d4a8bedfe8645c41497f3c18d749f0bd3218170c667bf4b81d0cdd/filelock-3.25.0-py3-none-any.whl", hash = "sha256:5ccf8069f7948f494968fc0713c10e5c182a9c9d9eef3a636307a20c2490f047", size = 26427, upload-time = "2026-03-01T15:08:44.593Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl", hash = "sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70", size = 26759, upload-time = "2026-03-11T20:45:37.437Z" }, ] [[package]] @@ -801,51 +864,51 @@ wheels = [ [[package]] name = "fonttools" -version = "4.61.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69", size = 3565756, upload-time = "2025-12-12T17:31:24.246Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/69/12/bf9f4eaa2fad039356cc627587e30ed008c03f1cebd3034376b5ee8d1d44/fonttools-4.61.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09", size = 2852213, upload-time = "2025-12-12T17:29:46.675Z" }, - { url = "https://files.pythonhosted.org/packages/ac/49/4138d1acb6261499bedde1c07f8c2605d1d8f9d77a151e5507fd3ef084b6/fonttools-4.61.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37", size = 2401689, upload-time = "2025-12-12T17:29:48.769Z" }, - { url = "https://files.pythonhosted.org/packages/e5/fe/e6ce0fe20a40e03aef906af60aa87668696f9e4802fa283627d0b5ed777f/fonttools-4.61.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77efb033d8d7ff233385f30c62c7c79271c8885d5c9657d967ede124671bbdfb", size = 5058809, upload-time = "2025-12-12T17:29:51.701Z" }, - { url = "https://files.pythonhosted.org/packages/79/61/1ca198af22f7dd22c17ab86e9024ed3c06299cfdb08170640e9996d501a0/fonttools-4.61.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9", size = 5036039, upload-time = "2025-12-12T17:29:53.659Z" }, - { url = "https://files.pythonhosted.org/packages/99/cc/fa1801e408586b5fce4da9f5455af8d770f4fc57391cd5da7256bb364d38/fonttools-4.61.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0de30bfe7745c0d1ffa2b0b7048fb7123ad0d71107e10ee090fa0b16b9452e87", size = 5034714, upload-time = "2025-12-12T17:29:55.592Z" }, - { url = "https://files.pythonhosted.org/packages/bf/aa/b7aeafe65adb1b0a925f8f25725e09f078c635bc22754f3fecb7456955b0/fonttools-4.61.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58b0ee0ab5b1fc9921eccfe11d1435added19d6494dde14e323f25ad2bc30c56", size = 5158648, upload-time = "2025-12-12T17:29:57.861Z" }, - { url = "https://files.pythonhosted.org/packages/99/f9/08ea7a38663328881384c6e7777bbefc46fd7d282adfd87a7d2b84ec9d50/fonttools-4.61.1-cp311-cp311-win32.whl", hash = "sha256:f79b168428351d11e10c5aeb61a74e1851ec221081299f4cf56036a95431c43a", size = 2280681, upload-time = "2025-12-12T17:29:59.943Z" }, - { url = "https://files.pythonhosted.org/packages/07/ad/37dd1ae5fa6e01612a1fbb954f0927681f282925a86e86198ccd7b15d515/fonttools-4.61.1-cp311-cp311-win_amd64.whl", hash = "sha256:fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7", size = 2331951, upload-time = "2025-12-12T17:30:02.254Z" }, - { url = "https://files.pythonhosted.org/packages/6f/16/7decaa24a1bd3a70c607b2e29f0adc6159f36a7e40eaba59846414765fd4/fonttools-4.61.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e", size = 2851593, upload-time = "2025-12-12T17:30:04.225Z" }, - { url = "https://files.pythonhosted.org/packages/94/98/3c4cb97c64713a8cf499b3245c3bf9a2b8fd16a3e375feff2aed78f96259/fonttools-4.61.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2", size = 2400231, upload-time = "2025-12-12T17:30:06.47Z" }, - { url = "https://files.pythonhosted.org/packages/b7/37/82dbef0f6342eb01f54bca073ac1498433d6ce71e50c3c3282b655733b31/fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796", size = 4954103, upload-time = "2025-12-12T17:30:08.432Z" }, - { url = "https://files.pythonhosted.org/packages/6c/44/f3aeac0fa98e7ad527f479e161aca6c3a1e47bb6996b053d45226fe37bf2/fonttools-4.61.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d", size = 5004295, upload-time = "2025-12-12T17:30:10.56Z" }, - { url = "https://files.pythonhosted.org/packages/14/e8/7424ced75473983b964d09f6747fa09f054a6d656f60e9ac9324cf40c743/fonttools-4.61.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8", size = 4944109, upload-time = "2025-12-12T17:30:12.874Z" }, - { url = "https://files.pythonhosted.org/packages/c8/8b/6391b257fa3d0b553d73e778f953a2f0154292a7a7a085e2374b111e5410/fonttools-4.61.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0", size = 5093598, upload-time = "2025-12-12T17:30:15.79Z" }, - { url = "https://files.pythonhosted.org/packages/d9/71/fd2ea96cdc512d92da5678a1c98c267ddd4d8c5130b76d0f7a80f9a9fde8/fonttools-4.61.1-cp312-cp312-win32.whl", hash = "sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261", size = 2269060, upload-time = "2025-12-12T17:30:18.058Z" }, - { url = "https://files.pythonhosted.org/packages/80/3b/a3e81b71aed5a688e89dfe0e2694b26b78c7d7f39a5ffd8a7d75f54a12a8/fonttools-4.61.1-cp312-cp312-win_amd64.whl", hash = "sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9", size = 2319078, upload-time = "2025-12-12T17:30:22.862Z" }, - { url = "https://files.pythonhosted.org/packages/4b/cf/00ba28b0990982530addb8dc3e9e6f2fa9cb5c20df2abdda7baa755e8fe1/fonttools-4.61.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c56c488ab471628ff3bfa80964372fc13504ece601e0d97a78ee74126b2045c", size = 2846454, upload-time = "2025-12-12T17:30:24.938Z" }, - { url = "https://files.pythonhosted.org/packages/5a/ca/468c9a8446a2103ae645d14fee3f610567b7042aba85031c1c65e3ef7471/fonttools-4.61.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc492779501fa723b04d0ab1f5be046797fee17d27700476edc7ee9ae535a61e", size = 2398191, upload-time = "2025-12-12T17:30:27.343Z" }, - { url = "https://files.pythonhosted.org/packages/a3/4b/d67eedaed19def5967fade3297fed8161b25ba94699efc124b14fb68cdbc/fonttools-4.61.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:64102ca87e84261419c3747a0d20f396eb024bdbeb04c2bfb37e2891f5fadcb5", size = 4928410, upload-time = "2025-12-12T17:30:29.771Z" }, - { url = "https://files.pythonhosted.org/packages/b0/8d/6fb3494dfe61a46258cd93d979cf4725ded4eb46c2a4ca35e4490d84daea/fonttools-4.61.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c1b526c8d3f615a7b1867f38a9410849c8f4aef078535742198e942fba0e9bd", size = 4984460, upload-time = "2025-12-12T17:30:32.073Z" }, - { url = "https://files.pythonhosted.org/packages/f7/f1/a47f1d30b3dc00d75e7af762652d4cbc3dff5c2697a0dbd5203c81afd9c3/fonttools-4.61.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:41ed4b5ec103bd306bb68f81dc166e77409e5209443e5773cb4ed837bcc9b0d3", size = 4925800, upload-time = "2025-12-12T17:30:34.339Z" }, - { url = "https://files.pythonhosted.org/packages/a7/01/e6ae64a0981076e8a66906fab01539799546181e32a37a0257b77e4aa88b/fonttools-4.61.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b501c862d4901792adaec7c25b1ecc749e2662543f68bb194c42ba18d6eec98d", size = 5067859, upload-time = "2025-12-12T17:30:36.593Z" }, - { url = "https://files.pythonhosted.org/packages/73/aa/28e40b8d6809a9b5075350a86779163f074d2b617c15d22343fce81918db/fonttools-4.61.1-cp313-cp313-win32.whl", hash = "sha256:4d7092bb38c53bbc78e9255a59158b150bcdc115a1e3b3ce0b5f267dc35dd63c", size = 2267821, upload-time = "2025-12-12T17:30:38.478Z" }, - { url = "https://files.pythonhosted.org/packages/1a/59/453c06d1d83dc0951b69ef692d6b9f1846680342927df54e9a1ca91c6f90/fonttools-4.61.1-cp313-cp313-win_amd64.whl", hash = "sha256:21e7c8d76f62ab13c9472ccf74515ca5b9a761d1bde3265152a6dc58700d895b", size = 2318169, upload-time = "2025-12-12T17:30:40.951Z" }, - { url = "https://files.pythonhosted.org/packages/32/8f/4e7bf82c0cbb738d3c2206c920ca34ca74ef9dabde779030145d28665104/fonttools-4.61.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fff4f534200a04b4a36e7ae3cb74493afe807b517a09e99cb4faa89a34ed6ecd", size = 2846094, upload-time = "2025-12-12T17:30:43.511Z" }, - { url = "https://files.pythonhosted.org/packages/71/09/d44e45d0a4f3a651f23a1e9d42de43bc643cce2971b19e784cc67d823676/fonttools-4.61.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d9203500f7c63545b4ce3799319fe4d9feb1a1b89b28d3cb5abd11b9dd64147e", size = 2396589, upload-time = "2025-12-12T17:30:45.681Z" }, - { url = "https://files.pythonhosted.org/packages/89/18/58c64cafcf8eb677a99ef593121f719e6dcbdb7d1c594ae5a10d4997ca8a/fonttools-4.61.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa646ecec9528bef693415c79a86e733c70a4965dd938e9a226b0fc64c9d2e6c", size = 4877892, upload-time = "2025-12-12T17:30:47.709Z" }, - { url = "https://files.pythonhosted.org/packages/8a/ec/9e6b38c7ba1e09eb51db849d5450f4c05b7e78481f662c3b79dbde6f3d04/fonttools-4.61.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f35ad7805edba3aac1a3710d104592df59f4b957e30108ae0ba6c10b11dd75", size = 4972884, upload-time = "2025-12-12T17:30:49.656Z" }, - { url = "https://files.pythonhosted.org/packages/5e/87/b5339da8e0256734ba0dbbf5b6cdebb1dd79b01dc8c270989b7bcd465541/fonttools-4.61.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b931ae8f62db78861b0ff1ac017851764602288575d65b8e8ff1963fed419063", size = 4924405, upload-time = "2025-12-12T17:30:51.735Z" }, - { url = "https://files.pythonhosted.org/packages/0b/47/e3409f1e1e69c073a3a6fd8cb886eb18c0bae0ee13db2c8d5e7f8495e8b7/fonttools-4.61.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b148b56f5de675ee16d45e769e69f87623a4944f7443850bf9a9376e628a89d2", size = 5035553, upload-time = "2025-12-12T17:30:54.823Z" }, - { url = "https://files.pythonhosted.org/packages/bf/b6/1f6600161b1073a984294c6c031e1a56ebf95b6164249eecf30012bb2e38/fonttools-4.61.1-cp314-cp314-win32.whl", hash = "sha256:9b666a475a65f4e839d3d10473fad6d47e0a9db14a2f4a224029c5bfde58ad2c", size = 2271915, upload-time = "2025-12-12T17:30:57.913Z" }, - { url = "https://files.pythonhosted.org/packages/52/7b/91e7b01e37cc8eb0e1f770d08305b3655e4f002fc160fb82b3390eabacf5/fonttools-4.61.1-cp314-cp314-win_amd64.whl", hash = "sha256:4f5686e1fe5fce75d82d93c47a438a25bf0d1319d2843a926f741140b2b16e0c", size = 2323487, upload-time = "2025-12-12T17:30:59.804Z" }, - { url = "https://files.pythonhosted.org/packages/39/5c/908ad78e46c61c3e3ed70c3b58ff82ab48437faf84ec84f109592cabbd9f/fonttools-4.61.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e76ce097e3c57c4bcb67c5aa24a0ecdbd9f74ea9219997a707a4061fbe2707aa", size = 2929571, upload-time = "2025-12-12T17:31:02.574Z" }, - { url = "https://files.pythonhosted.org/packages/bd/41/975804132c6dea64cdbfbaa59f3518a21c137a10cccf962805b301ac6ab2/fonttools-4.61.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9cfef3ab326780c04d6646f68d4b4742aae222e8b8ea1d627c74e38afcbc9d91", size = 2435317, upload-time = "2025-12-12T17:31:04.974Z" }, - { url = "https://files.pythonhosted.org/packages/b0/5a/aef2a0a8daf1ebaae4cfd83f84186d4a72ee08fd6a8451289fcd03ffa8a4/fonttools-4.61.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a75c301f96db737e1c5ed5fd7d77d9c34466de16095a266509e13da09751bd19", size = 4882124, upload-time = "2025-12-12T17:31:07.456Z" }, - { url = "https://files.pythonhosted.org/packages/80/33/d6db3485b645b81cea538c9d1c9219d5805f0877fda18777add4671c5240/fonttools-4.61.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91669ccac46bbc1d09e9273546181919064e8df73488ea087dcac3e2968df9ba", size = 5100391, upload-time = "2025-12-12T17:31:09.732Z" }, - { url = "https://files.pythonhosted.org/packages/6c/d6/675ba631454043c75fcf76f0ca5463eac8eb0666ea1d7badae5fea001155/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c33ab3ca9d3ccd581d58e989d67554e42d8d4ded94ab3ade3508455fe70e65f7", size = 4978800, upload-time = "2025-12-12T17:31:11.681Z" }, - { url = "https://files.pythonhosted.org/packages/7f/33/d3ec753d547a8d2bdaedd390d4a814e8d5b45a093d558f025c6b990b554c/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:664c5a68ec406f6b1547946683008576ef8b38275608e1cee6c061828171c118", size = 5006426, upload-time = "2025-12-12T17:31:13.764Z" }, - { url = "https://files.pythonhosted.org/packages/b4/40/cc11f378b561a67bea850ab50063366a0d1dd3f6d0a30ce0f874b0ad5664/fonttools-4.61.1-cp314-cp314t-win32.whl", hash = "sha256:aed04cabe26f30c1647ef0e8fbb207516fd40fe9472e9439695f5c6998e60ac5", size = 2335377, upload-time = "2025-12-12T17:31:16.49Z" }, - { url = "https://files.pythonhosted.org/packages/e4/ff/c9a2b66b39f8628531ea58b320d66d951267c98c6a38684daa8f50fb02f8/fonttools-4.61.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2180f14c141d2f0f3da43f3a81bc8aa4684860f6b0e6f9e165a4831f24e6a23b", size = 2400613, upload-time = "2025-12-12T17:31:18.769Z" }, - { url = "https://files.pythonhosted.org/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371", size = 1148996, upload-time = "2025-12-12T17:31:21.03Z" }, +version = "4.62.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/08/7012b00a9a5874311b639c3920270c36ee0c445b69d9989a85e5c92ebcb0/fonttools-4.62.1.tar.gz", hash = "sha256:e54c75fd6041f1122476776880f7c3c3295ffa31962dc6ebe2543c00dca58b5d", size = 3580737, upload-time = "2026-03-13T13:54:25.52Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/39/23ff32561ec8d45a4d48578b4d241369d9270dc50926c017570e60893701/fonttools-4.62.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:40975849bac44fb0b9253d77420c6d8b523ac4dcdcefeff6e4d706838a5b80f7", size = 2871039, upload-time = "2026-03-13T13:52:33.127Z" }, + { url = "https://files.pythonhosted.org/packages/24/7f/66d3f8a9338a9b67fe6e1739f47e1cd5cee78bd3bc1206ef9b0b982289a5/fonttools-4.62.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9dde91633f77fa576879a0c76b1d89de373cae751a98ddf0109d54e173b40f14", size = 2416346, upload-time = "2026-03-13T13:52:35.676Z" }, + { url = "https://files.pythonhosted.org/packages/aa/53/5276ceba7bff95da7793a07c5284e1da901cf00341ce5e2f3273056c0cca/fonttools-4.62.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6acb4109f8bee00fec985c8c7afb02299e35e9c94b57287f3ea542f28bd0b0a7", size = 5100897, upload-time = "2026-03-13T13:52:38.102Z" }, + { url = "https://files.pythonhosted.org/packages/cc/a1/40a5c4d8e28b0851d53a8eeeb46fbd73c325a2a9a165f290a5ed90e6c597/fonttools-4.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1c5c25671ce8805e0d080e2ffdeca7f1e86778c5cbfbeae86d7f866d8830517b", size = 5071078, upload-time = "2026-03-13T13:52:41.305Z" }, + { url = "https://files.pythonhosted.org/packages/e3/be/d378fca4c65ea1956fee6d90ace6e861776809cbbc5af22388a090c3c092/fonttools-4.62.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a5d8825e1140f04e6c99bb7d37a9e31c172f3bc208afbe02175339e699c710e1", size = 5076908, upload-time = "2026-03-13T13:52:44.122Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d9/ae6a1d0693a4185a84605679c8a1f719a55df87b9c6e8e817bfdd9ef5936/fonttools-4.62.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:268abb1cb221e66c014acc234e872b7870d8b5d4657a83a8f4205094c32d2416", size = 5202275, upload-time = "2026-03-13T13:52:46.591Z" }, + { url = "https://files.pythonhosted.org/packages/54/6c/af95d9c4efb15cabff22642b608342f2bd67137eea6107202d91b5b03184/fonttools-4.62.1-cp311-cp311-win32.whl", hash = "sha256:942b03094d7edbb99bdf1ae7e9090898cad7bf9030b3d21f33d7072dbcb51a53", size = 2293075, upload-time = "2026-03-13T13:52:48.711Z" }, + { url = "https://files.pythonhosted.org/packages/d3/97/bf54c5b3f2be34e1f143e6db838dfdc54f2ffa3e68c738934c82f3b2a08d/fonttools-4.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:e8514f4924375f77084e81467e63238b095abda5107620f49421c368a6017ed2", size = 2344593, upload-time = "2026-03-13T13:52:50.725Z" }, + { url = "https://files.pythonhosted.org/packages/47/d4/dbacced3953544b9a93088cc10ef2b596d348c983d5c67a404fa41ec51ba/fonttools-4.62.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:90365821debbd7db678809c7491ca4acd1e0779b9624cdc6ddaf1f31992bf974", size = 2870219, upload-time = "2026-03-13T13:52:53.664Z" }, + { url = "https://files.pythonhosted.org/packages/66/9e/a769c8e99b81e5a87ab7e5e7236684de4e96246aae17274e5347d11ebd78/fonttools-4.62.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12859ff0b47dd20f110804c3e0d0970f7b832f561630cd879969011541a464a9", size = 2414891, upload-time = "2026-03-13T13:52:56.493Z" }, + { url = "https://files.pythonhosted.org/packages/69/64/f19a9e3911968c37e1e620e14dfc5778299e1474f72f4e57c5ec771d9489/fonttools-4.62.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c125ffa00c3d9003cdaaf7f2c79e6e535628093e14b5de1dccb08859b680936", size = 5033197, upload-time = "2026-03-13T13:52:59.179Z" }, + { url = "https://files.pythonhosted.org/packages/9b/8a/99c8b3c3888c5c474c08dbfd7c8899786de9604b727fcefb055b42c84bba/fonttools-4.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:149f7d84afca659d1a97e39a4778794a2f83bf344c5ee5134e09995086cc2392", size = 4988768, upload-time = "2026-03-13T13:53:02.761Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c6/0f904540d3e6ab463c1243a0d803504826a11604c72dd58c2949796a1762/fonttools-4.62.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0aa72c43a601cfa9273bb1ae0518f1acadc01ee181a6fc60cd758d7fdadffc04", size = 4971512, upload-time = "2026-03-13T13:53:05.678Z" }, + { url = "https://files.pythonhosted.org/packages/29/0b/5cbef6588dc9bd6b5c9ad6a4d5a8ca384d0cea089da31711bbeb4f9654a6/fonttools-4.62.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:19177c8d96c7c36359266e571c5173bcee9157b59cfc8cb0153c5673dc5a3a7d", size = 5122723, upload-time = "2026-03-13T13:53:08.662Z" }, + { url = "https://files.pythonhosted.org/packages/4a/47/b3a5342d381595ef439adec67848bed561ab7fdb1019fa522e82101b7d9c/fonttools-4.62.1-cp312-cp312-win32.whl", hash = "sha256:a24decd24d60744ee8b4679d38e88b8303d86772053afc29b19d23bb8207803c", size = 2281278, upload-time = "2026-03-13T13:53:10.998Z" }, + { url = "https://files.pythonhosted.org/packages/28/b1/0c2ab56a16f409c6c8a68816e6af707827ad5d629634691ff60a52879792/fonttools-4.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:9e7863e10b3de72376280b515d35b14f5eeed639d1aa7824f4cf06779ec65e42", size = 2331414, upload-time = "2026-03-13T13:53:13.992Z" }, + { url = "https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c22b1014017111c401469e3acc5433e6acf6ebcc6aa9efb538a533c800971c79", size = 2865155, upload-time = "2026-03-13T13:53:16.132Z" }, + { url = "https://files.pythonhosted.org/packages/03/c5/0e3966edd5ec668d41dfe418787726752bc07e2f5fd8c8f208615e61fa89/fonttools-4.62.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68959f5fc58ed4599b44aad161c2837477d7f35f5f79402d97439974faebfebe", size = 2412802, upload-time = "2026-03-13T13:53:18.878Z" }, + { url = "https://files.pythonhosted.org/packages/52/94/e6ac4b44026de7786fe46e3bfa0c87e51d5d70a841054065d49cd62bb909/fonttools-4.62.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef46db46c9447103b8f3ff91e8ba009d5fe181b1920a83757a5762551e32bb68", size = 5013926, upload-time = "2026-03-13T13:53:21.379Z" }, + { url = "https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6706d1cb1d5e6251a97ad3c1b9347505c5615c112e66047abbef0f8545fa30d1", size = 4964575, upload-time = "2026-03-13T13:53:23.857Z" }, + { url = "https://files.pythonhosted.org/packages/46/76/7d051671e938b1881670528fec69cc4044315edd71a229c7fd712eaa5119/fonttools-4.62.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e7abd2b1e11736f58c1de27819e1955a53267c21732e78243fa2fa2e5c1e069", size = 4953693, upload-time = "2026-03-13T13:53:26.569Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ae/b41f8628ec0be3c1b934fc12b84f4576a5c646119db4d3bdd76a217c90b5/fonttools-4.62.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:403d28ce06ebfc547fbcb0cb8b7f7cc2f7a2d3e1a67ba9a34b14632df9e080f9", size = 5094920, upload-time = "2026-03-13T13:53:29.329Z" }, + { url = "https://files.pythonhosted.org/packages/f2/f6/53a1e9469331a23dcc400970a27a4caa3d9f6edbf5baab0260285238b884/fonttools-4.62.1-cp313-cp313-win32.whl", hash = "sha256:93c316e0f5301b2adbe6a5f658634307c096fd5aae60a5b3412e4f3e1728ab24", size = 2279928, upload-time = "2026-03-13T13:53:32.352Z" }, + { url = "https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl", hash = "sha256:7aa21ff53e28a9c2157acbc44e5b401149d3c9178107130e82d74ceb500e5056", size = 2330514, upload-time = "2026-03-13T13:53:34.991Z" }, + { url = "https://files.pythonhosted.org/packages/36/f0/2888cdac391807d68d90dcb16ef858ddc1b5309bfc6966195a459dd326e2/fonttools-4.62.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fa1d16210b6b10a826d71bed68dd9ec24a9e218d5a5e2797f37c573e7ec215ca", size = 2864442, upload-time = "2026-03-13T13:53:37.509Z" }, + { url = "https://files.pythonhosted.org/packages/4b/b2/e521803081f8dc35990816b82da6360fa668a21b44da4b53fc9e77efcd62/fonttools-4.62.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:aa69d10ed420d8121118e628ad47d86e4caa79ba37f968597b958f6cceab7eca", size = 2410901, upload-time = "2026-03-13T13:53:40.55Z" }, + { url = "https://files.pythonhosted.org/packages/00/a4/8c3511ff06e53110039358dbbdc1a65d72157a054638387aa2ada300a8b8/fonttools-4.62.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd13b7999d59c5eb1c2b442eb2d0c427cb517a0b7a1f5798fc5c9e003f5ff782", size = 4999608, upload-time = "2026-03-13T13:53:42.798Z" }, + { url = "https://files.pythonhosted.org/packages/28/63/cd0c3b26afe60995a5295f37c246a93d454023726c3261cfbb3559969bb9/fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8d337fdd49a79b0d51c4da87bc38169d21c3abbf0c1aa9367eff5c6656fb6dae", size = 4912726, upload-time = "2026-03-13T13:53:45.405Z" }, + { url = "https://files.pythonhosted.org/packages/70/b9/ac677cb07c24c685cf34f64e140617d58789d67a3dd524164b63648c6114/fonttools-4.62.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d241cdc4a67b5431c6d7f115fdf63335222414995e3a1df1a41e1182acd4bcc7", size = 4951422, upload-time = "2026-03-13T13:53:48.326Z" }, + { url = "https://files.pythonhosted.org/packages/e6/10/11c08419a14b85b7ca9a9faca321accccc8842dd9e0b1c8a72908de05945/fonttools-4.62.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c05557a78f8fa514da0f869556eeda40887a8abc77c76ee3f74cf241778afd5a", size = 5060979, upload-time = "2026-03-13T13:53:51.366Z" }, + { url = "https://files.pythonhosted.org/packages/4e/3c/12eea4a4cf054e7ab058ed5ceada43b46809fce2bf319017c4d63ae55bb4/fonttools-4.62.1-cp314-cp314-win32.whl", hash = "sha256:49a445d2f544ce4a69338694cad575ba97b9a75fff02720da0882d1a73f12800", size = 2283733, upload-time = "2026-03-13T13:53:53.606Z" }, + { url = "https://files.pythonhosted.org/packages/6b/67/74b070029043186b5dd13462c958cb7c7f811be0d2e634309d9a1ffb1505/fonttools-4.62.1-cp314-cp314-win_amd64.whl", hash = "sha256:1eecc128c86c552fb963fe846ca4e011b1be053728f798185a1687502f6d398e", size = 2335663, upload-time = "2026-03-13T13:53:56.23Z" }, + { url = "https://files.pythonhosted.org/packages/42/c5/4d2ed3ca6e33617fc5624467da353337f06e7f637707478903c785bd8e20/fonttools-4.62.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1596aeaddf7f78e21e68293c011316a25267b3effdaccaf4d59bc9159d681b82", size = 2947288, upload-time = "2026-03-13T13:53:59.397Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e9/7ab11ddfda48ed0f89b13380e5595ba572619c27077be0b2c447a63ff351/fonttools-4.62.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8f8fca95d3bb3208f59626a4b0ea6e526ee51f5a8ad5d91821c165903e8d9260", size = 2449023, upload-time = "2026-03-13T13:54:01.642Z" }, + { url = "https://files.pythonhosted.org/packages/b2/10/a800fa090b5e8819942e54e19b55fc7c21fe14a08757c3aa3ca8db358939/fonttools-4.62.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee91628c08e76f77b533d65feb3fbe6d9dad699f95be51cf0d022db94089cdc4", size = 5137599, upload-time = "2026-03-13T13:54:04.495Z" }, + { url = "https://files.pythonhosted.org/packages/37/dc/8ccd45033fffd74deb6912fa1ca524643f584b94c87a16036855b498a1ed/fonttools-4.62.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f37df1cac61d906e7b836abe356bc2f34c99d4477467755c216b72aa3dc748b", size = 4920933, upload-time = "2026-03-13T13:54:07.557Z" }, + { url = "https://files.pythonhosted.org/packages/99/eb/e618adefb839598d25ac8136cd577925d6c513dc0d931d93b8af956210f0/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:92bb00a947e666169c99b43753c4305fc95a890a60ef3aeb2a6963e07902cc87", size = 5016232, upload-time = "2026-03-13T13:54:10.611Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5f/9b5c9bfaa8ec82def8d8168c4f13615990d6ce5996fe52bd49bfb5e05134/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bdfe592802ef939a0e33106ea4a318eeb17822c7ee168c290273cbd5fabd746c", size = 5042987, upload-time = "2026-03-13T13:54:13.569Z" }, + { url = "https://files.pythonhosted.org/packages/90/aa/dfbbe24c6a6afc5c203d90cc0343e24bcbb09e76d67c4d6eef8c2558d7ba/fonttools-4.62.1-cp314-cp314t-win32.whl", hash = "sha256:b820fcb92d4655513d8402d5b219f94481c4443d825b4372c75a2072aa4b357a", size = 2348021, upload-time = "2026-03-13T13:54:16.98Z" }, + { url = "https://files.pythonhosted.org/packages/13/6f/ae9c4e4dd417948407b680855c2c7790efb52add6009aaecff1e3bc50e8e/fonttools-4.62.1-cp314-cp314t-win_amd64.whl", hash = "sha256:59b372b4f0e113d3746b88985f1c796e7bf830dd54b28374cd85c2b8acd7583e", size = 2414147, upload-time = "2026-03-13T13:54:19.416Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ba/56147c165442cc5ba7e82ecf301c9a68353cede498185869e6e02b4c264f/fonttools-4.62.1-py3-none-any.whl", hash = "sha256:7487782e2113861f4ddcc07c3436450659e3caa5e470b27dc2177cade2d8e7fd", size = 1152647, upload-time = "2026-03-13T13:54:22.735Z" }, ] [[package]] @@ -859,11 +922,11 @@ wheels = [ [[package]] name = "fsspec" -version = "2026.2.0" +version = "2026.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/51/7c/f60c259dcbf4f0c47cc4ddb8f7720d2dcdc8888c8e5ad84c73ea4531cc5b/fsspec-2026.2.0.tar.gz", hash = "sha256:6544e34b16869f5aacd5b90bdf1a71acb37792ea3ddf6125ee69a22a53fb8bff", size = 313441, upload-time = "2026-02-05T21:50:53.743Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/cf/b50ddf667c15276a9ab15a70ef5f257564de271957933ffea49d2cdbcdfb/fsspec-2026.3.0.tar.gz", hash = "sha256:1ee6a0e28677557f8c2f994e3eea77db6392b4de9cd1f5d7a9e87a0ae9d01b41", size = 313547, upload-time = "2026-03-27T19:11:14.892Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl", hash = "sha256:98de475b5cb3bd66bedd5c4679e87b4fdfe1a3bf4d707b151b3c07e58c9a2437", size = 202505, upload-time = "2026-02-05T21:50:51.819Z" }, + { url = "https://files.pythonhosted.org/packages/d5/1f/5f4a3cd9e4440e9d9bc78ad0a91a1c8d46b4d429d5239ebe6793c9fe5c41/fsspec-2026.3.0-py3-none-any.whl", hash = "sha256:d2ceafaad1b3457968ed14efa28798162f1638dbb5d2a6868a2db002a5ee39a4", size = 202595, upload-time = "2026-03-27T19:11:13.595Z" }, ] [[package]] @@ -945,53 +1008,53 @@ wheels = [ [[package]] name = "grpcio" -version = "1.78.0" +version = "1.80.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/8a/3d098f35c143a89520e568e6539cc098fcd294495910e359889ce8741c84/grpcio-1.78.0.tar.gz", hash = "sha256:7382b95189546f375c174f53a5fa873cef91c4b8005faa05cc5b3beea9c4f1c5", size = 12852416, upload-time = "2026-02-06T09:57:18.093Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/86/c7/d0b780a29b0837bf4ca9580904dfb275c1fc321ded7897d620af7047ec57/grpcio-1.78.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:2777b783f6c13b92bd7b716667452c329eefd646bfb3f2e9dabea2e05dbd34f6", size = 5951525, upload-time = "2026-02-06T09:55:01.989Z" }, - { url = "https://files.pythonhosted.org/packages/c5/b1/96920bf2ee61df85a9503cb6f733fe711c0ff321a5a697d791b075673281/grpcio-1.78.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:9dca934f24c732750389ce49d638069c3892ad065df86cb465b3fa3012b70c9e", size = 11830418, upload-time = "2026-02-06T09:55:04.462Z" }, - { url = "https://files.pythonhosted.org/packages/83/0c/7c1528f098aeb75a97de2bae18c530f56959fb7ad6c882db45d9884d6edc/grpcio-1.78.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:459ab414b35f4496138d0ecd735fed26f1318af5e52cb1efbc82a09f0d5aa911", size = 6524477, upload-time = "2026-02-06T09:55:07.111Z" }, - { url = "https://files.pythonhosted.org/packages/8d/52/e7c1f3688f949058e19a011c4e0dec973da3d0ae5e033909677f967ae1f4/grpcio-1.78.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:082653eecbdf290e6e3e2c276ab2c54b9e7c299e07f4221872380312d8cf395e", size = 7198266, upload-time = "2026-02-06T09:55:10.016Z" }, - { url = "https://files.pythonhosted.org/packages/e5/61/8ac32517c1e856677282c34f2e7812d6c328fa02b8f4067ab80e77fdc9c9/grpcio-1.78.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85f93781028ec63f383f6bc90db785a016319c561cc11151fbb7b34e0d012303", size = 6730552, upload-time = "2026-02-06T09:55:12.207Z" }, - { url = "https://files.pythonhosted.org/packages/bd/98/b8ee0158199250220734f620b12e4a345955ac7329cfd908d0bf0fda77f0/grpcio-1.78.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f12857d24d98441af6a1d5c87442d624411db486f7ba12550b07788f74b67b04", size = 7304296, upload-time = "2026-02-06T09:55:15.044Z" }, - { url = "https://files.pythonhosted.org/packages/bd/0f/7b72762e0d8840b58032a56fdbd02b78fc645b9fa993d71abf04edbc54f4/grpcio-1.78.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5397fff416b79e4b284959642a4e95ac4b0f1ece82c9993658e0e477d40551ec", size = 8288298, upload-time = "2026-02-06T09:55:17.276Z" }, - { url = "https://files.pythonhosted.org/packages/24/ae/ae4ce56bc5bb5caa3a486d60f5f6083ac3469228faa734362487176c15c5/grpcio-1.78.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fbe6e89c7ffb48518384068321621b2a69cab509f58e40e4399fdd378fa6d074", size = 7730953, upload-time = "2026-02-06T09:55:19.545Z" }, - { url = "https://files.pythonhosted.org/packages/b5/6e/8052e3a28eb6a820c372b2eb4b5e32d195c661e137d3eca94d534a4cfd8a/grpcio-1.78.0-cp311-cp311-win32.whl", hash = "sha256:6092beabe1966a3229f599d7088b38dfc8ffa1608b5b5cdda31e591e6500f856", size = 4076503, upload-time = "2026-02-06T09:55:21.521Z" }, - { url = "https://files.pythonhosted.org/packages/08/62/f22c98c5265dfad327251fa2f840b591b1df5f5e15d88b19c18c86965b27/grpcio-1.78.0-cp311-cp311-win_amd64.whl", hash = "sha256:1afa62af6e23f88629f2b29ec9e52ec7c65a7176c1e0a83292b93c76ca882558", size = 4799767, upload-time = "2026-02-06T09:55:24.107Z" }, - { url = "https://files.pythonhosted.org/packages/4e/f4/7384ed0178203d6074446b3c4f46c90a22ddf7ae0b3aee521627f54cfc2a/grpcio-1.78.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:f9ab915a267fc47c7e88c387a3a28325b58c898e23d4995f765728f4e3dedb97", size = 5913985, upload-time = "2026-02-06T09:55:26.832Z" }, - { url = "https://files.pythonhosted.org/packages/81/ed/be1caa25f06594463f685b3790b320f18aea49b33166f4141bfdc2bfb236/grpcio-1.78.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3f8904a8165ab21e07e58bf3e30a73f4dffc7a1e0dbc32d51c61b5360d26f43e", size = 11811853, upload-time = "2026-02-06T09:55:29.224Z" }, - { url = "https://files.pythonhosted.org/packages/24/a7/f06d151afc4e64b7e3cc3e872d331d011c279aaab02831e40a81c691fb65/grpcio-1.78.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:859b13906ce098c0b493af92142ad051bf64c7870fa58a123911c88606714996", size = 6475766, upload-time = "2026-02-06T09:55:31.825Z" }, - { url = "https://files.pythonhosted.org/packages/8a/a8/4482922da832ec0082d0f2cc3a10976d84a7424707f25780b82814aafc0a/grpcio-1.78.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b2342d87af32790f934a79c3112641e7b27d63c261b8b4395350dad43eff1dc7", size = 7170027, upload-time = "2026-02-06T09:55:34.7Z" }, - { url = "https://files.pythonhosted.org/packages/54/bf/f4a3b9693e35d25b24b0b39fa46d7d8a3c439e0a3036c3451764678fec20/grpcio-1.78.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12a771591ae40bc65ba67048fa52ef4f0e6db8279e595fd349f9dfddeef571f9", size = 6690766, upload-time = "2026-02-06T09:55:36.902Z" }, - { url = "https://files.pythonhosted.org/packages/c7/b9/521875265cc99fe5ad4c5a17010018085cae2810a928bf15ebe7d8bcd9cc/grpcio-1.78.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:185dea0d5260cbb2d224c507bf2a5444d5abbb1fa3594c1ed7e4c709d5eb8383", size = 7266161, upload-time = "2026-02-06T09:55:39.824Z" }, - { url = "https://files.pythonhosted.org/packages/05/86/296a82844fd40a4ad4a95f100b55044b4f817dece732bf686aea1a284147/grpcio-1.78.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51b13f9aed9d59ee389ad666b8c2214cc87b5de258fa712f9ab05f922e3896c6", size = 8253303, upload-time = "2026-02-06T09:55:42.353Z" }, - { url = "https://files.pythonhosted.org/packages/f3/e4/ea3c0caf5468537f27ad5aab92b681ed7cc0ef5f8c9196d3fd42c8c2286b/grpcio-1.78.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd5f135b1bd58ab088930b3c613455796dfa0393626a6972663ccdda5b4ac6ce", size = 7698222, upload-time = "2026-02-06T09:55:44.629Z" }, - { url = "https://files.pythonhosted.org/packages/d7/47/7f05f81e4bb6b831e93271fb12fd52ba7b319b5402cbc101d588f435df00/grpcio-1.78.0-cp312-cp312-win32.whl", hash = "sha256:94309f498bcc07e5a7d16089ab984d42ad96af1d94b5a4eb966a266d9fcabf68", size = 4066123, upload-time = "2026-02-06T09:55:47.644Z" }, - { url = "https://files.pythonhosted.org/packages/ad/e7/d6914822c88aa2974dbbd10903d801a28a19ce9cd8bad7e694cbbcf61528/grpcio-1.78.0-cp312-cp312-win_amd64.whl", hash = "sha256:9566fe4ababbb2610c39190791e5b829869351d14369603702e890ef3ad2d06e", size = 4797657, upload-time = "2026-02-06T09:55:49.86Z" }, - { url = "https://files.pythonhosted.org/packages/05/a9/8f75894993895f361ed8636cd9237f4ab39ef87fd30db17467235ed1c045/grpcio-1.78.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:ce3a90455492bf8bfa38e56fbbe1dbd4f872a3d8eeaf7337dc3b1c8aa28c271b", size = 5920143, upload-time = "2026-02-06T09:55:52.035Z" }, - { url = "https://files.pythonhosted.org/packages/55/06/0b78408e938ac424100100fd081189451b472236e8a3a1f6500390dc4954/grpcio-1.78.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:2bf5e2e163b356978b23652c4818ce4759d40f4712ee9ec5a83c4be6f8c23a3a", size = 11803926, upload-time = "2026-02-06T09:55:55.494Z" }, - { url = "https://files.pythonhosted.org/packages/88/93/b59fe7832ff6ae3c78b813ea43dac60e295fa03606d14d89d2e0ec29f4f3/grpcio-1.78.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8f2ac84905d12918e4e55a16da17939eb63e433dc11b677267c35568aa63fc84", size = 6478628, upload-time = "2026-02-06T09:55:58.533Z" }, - { url = "https://files.pythonhosted.org/packages/ed/df/e67e3734527f9926b7d9c0dde6cd998d1d26850c3ed8eeec81297967ac67/grpcio-1.78.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b58f37edab4a3881bc6c9bca52670610e0c9ca14e2ea3cf9debf185b870457fb", size = 7173574, upload-time = "2026-02-06T09:56:01.786Z" }, - { url = "https://files.pythonhosted.org/packages/a6/62/cc03fffb07bfba982a9ec097b164e8835546980aec25ecfa5f9c1a47e022/grpcio-1.78.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:735e38e176a88ce41840c21bb49098ab66177c64c82426e24e0082500cc68af5", size = 6692639, upload-time = "2026-02-06T09:56:04.529Z" }, - { url = "https://files.pythonhosted.org/packages/bf/9a/289c32e301b85bdb67d7ec68b752155e674ee3ba2173a1858f118e399ef3/grpcio-1.78.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2045397e63a7a0ee7957c25f7dbb36ddc110e0cfb418403d110c0a7a68a844e9", size = 7268838, upload-time = "2026-02-06T09:56:08.397Z" }, - { url = "https://files.pythonhosted.org/packages/0e/79/1be93f32add280461fa4773880196572563e9c8510861ac2da0ea0f892b6/grpcio-1.78.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a9f136fbafe7ccf4ac7e8e0c28b31066e810be52d6e344ef954a3a70234e1702", size = 8251878, upload-time = "2026-02-06T09:56:10.914Z" }, - { url = "https://files.pythonhosted.org/packages/65/65/793f8e95296ab92e4164593674ae6291b204bb5f67f9d4a711489cd30ffa/grpcio-1.78.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:748b6138585379c737adc08aeffd21222abbda1a86a0dca2a39682feb9196c20", size = 7695412, upload-time = "2026-02-06T09:56:13.593Z" }, - { url = "https://files.pythonhosted.org/packages/1c/9f/1e233fe697ecc82845942c2822ed06bb522e70d6771c28d5528e4c50f6a4/grpcio-1.78.0-cp313-cp313-win32.whl", hash = "sha256:271c73e6e5676afe4fc52907686670c7cea22ab2310b76a59b678403ed40d670", size = 4064899, upload-time = "2026-02-06T09:56:15.601Z" }, - { url = "https://files.pythonhosted.org/packages/4d/27/d86b89e36de8a951501fb06a0f38df19853210f341d0b28f83f4aa0ffa08/grpcio-1.78.0-cp313-cp313-win_amd64.whl", hash = "sha256:f2d4e43ee362adfc05994ed479334d5a451ab7bc3f3fee1b796b8ca66895acb4", size = 4797393, upload-time = "2026-02-06T09:56:17.882Z" }, - { url = "https://files.pythonhosted.org/packages/29/f2/b56e43e3c968bfe822fa6ce5bca10d5c723aa40875b48791ce1029bb78c7/grpcio-1.78.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:e87cbc002b6f440482b3519e36e1313eb5443e9e9e73d6a52d43bd2004fcfd8e", size = 5920591, upload-time = "2026-02-06T09:56:20.758Z" }, - { url = "https://files.pythonhosted.org/packages/5d/81/1f3b65bd30c334167bfa8b0d23300a44e2725ce39bba5b76a2460d85f745/grpcio-1.78.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:c41bc64626db62e72afec66b0c8a0da76491510015417c127bfc53b2fe6d7f7f", size = 11813685, upload-time = "2026-02-06T09:56:24.315Z" }, - { url = "https://files.pythonhosted.org/packages/0e/1c/bbe2f8216a5bd3036119c544d63c2e592bdf4a8ec6e4a1867592f4586b26/grpcio-1.78.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8dfffba826efcf366b1e3ccc37e67afe676f290e13a3b48d31a46739f80a8724", size = 6487803, upload-time = "2026-02-06T09:56:27.367Z" }, - { url = "https://files.pythonhosted.org/packages/16/5c/a6b2419723ea7ddce6308259a55e8e7593d88464ce8db9f4aa857aba96fa/grpcio-1.78.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:74be1268d1439eaaf552c698cdb11cd594f0c49295ae6bb72c34ee31abbe611b", size = 7173206, upload-time = "2026-02-06T09:56:29.876Z" }, - { url = "https://files.pythonhosted.org/packages/df/1e/b8801345629a415ea7e26c83d75eb5dbe91b07ffe5210cc517348a8d4218/grpcio-1.78.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be63c88b32e6c0f1429f1398ca5c09bc64b0d80950c8bb7807d7d7fb36fb84c7", size = 6693826, upload-time = "2026-02-06T09:56:32.305Z" }, - { url = "https://files.pythonhosted.org/packages/34/84/0de28eac0377742679a510784f049738a80424b17287739fc47d63c2439e/grpcio-1.78.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3c586ac70e855c721bda8f548d38c3ca66ac791dc49b66a8281a1f99db85e452", size = 7277897, upload-time = "2026-02-06T09:56:34.915Z" }, - { url = "https://files.pythonhosted.org/packages/ca/9c/ad8685cfe20559a9edb66f735afdcb2b7d3de69b13666fdfc542e1916ebd/grpcio-1.78.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:35eb275bf1751d2ffbd8f57cdbc46058e857cf3971041521b78b7db94bdaf127", size = 8252404, upload-time = "2026-02-06T09:56:37.553Z" }, - { url = "https://files.pythonhosted.org/packages/3c/05/33a7a4985586f27e1de4803887c417ec7ced145ebd069bc38a9607059e2b/grpcio-1.78.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:207db540302c884b8848036b80db352a832b99dfdf41db1eb554c2c2c7800f65", size = 7696837, upload-time = "2026-02-06T09:56:40.173Z" }, - { url = "https://files.pythonhosted.org/packages/73/77/7382241caf88729b106e49e7d18e3116216c778e6a7e833826eb96de22f7/grpcio-1.78.0-cp314-cp314-win32.whl", hash = "sha256:57bab6deef2f4f1ca76cc04565df38dc5713ae6c17de690721bdf30cb1e0545c", size = 4142439, upload-time = "2026-02-06T09:56:43.258Z" }, - { url = "https://files.pythonhosted.org/packages/48/b2/b096ccce418882fbfda4f7496f9357aaa9a5af1896a9a7f60d9f2b275a06/grpcio-1.78.0-cp314-cp314-win_amd64.whl", hash = "sha256:dce09d6116df20a96acfdbf85e4866258c3758180e8c49845d6ba8248b6d0bbb", size = 4929852, upload-time = "2026-02-06T09:56:45.885Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/b7/48/af6173dbca4454f4637a4678b67f52ca7e0c1ed7d5894d89d434fecede05/grpcio-1.80.0.tar.gz", hash = "sha256:29aca15edd0688c22ba01d7cc01cb000d72b2033f4a3c72a81a19b56fd143257", size = 12978905, upload-time = "2026-03-30T08:49:10.502Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/db/1d56e5f5823257b291962d6c0ce106146c6447f405b60b234c4f222a7cde/grpcio-1.80.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:dfab85db094068ff42e2a3563f60ab3dddcc9d6488a35abf0132daec13209c8a", size = 6055009, upload-time = "2026-03-30T08:46:46.265Z" }, + { url = "https://files.pythonhosted.org/packages/6e/18/c83f3cad64c5ca63bca7e91e5e46b0d026afc5af9d0a9972472ceba294b3/grpcio-1.80.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:5c07e82e822e1161354e32da2662f741a4944ea955f9f580ec8fb409dd6f6060", size = 12035295, upload-time = "2026-03-30T08:46:49.099Z" }, + { url = "https://files.pythonhosted.org/packages/0f/8e/e14966b435be2dda99fbe89db9525ea436edc79780431a1c2875a3582644/grpcio-1.80.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba0915d51fd4ced2db5ff719f84e270afe0e2d4c45a7bdb1e8d036e4502928c2", size = 6610297, upload-time = "2026-03-30T08:46:52.123Z" }, + { url = "https://files.pythonhosted.org/packages/cc/26/d5eb38f42ce0e3fdc8174ea4d52036ef8d58cc4426cb800f2610f625dd75/grpcio-1.80.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3cb8130ba457d2aa09fa6b7c3ed6b6e4e6a2685fce63cb803d479576c4d80e21", size = 7300208, upload-time = "2026-03-30T08:46:54.859Z" }, + { url = "https://files.pythonhosted.org/packages/25/51/bd267c989f85a17a5b3eea65a6feb4ff672af41ca614e5a0279cc0ea381c/grpcio-1.80.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:09e5e478b3d14afd23f12e49e8b44c8684ac3c5f08561c43a5b9691c54d136ab", size = 6813442, upload-time = "2026-03-30T08:46:57.056Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d9/d80eef735b19e9169e30164bbf889b46f9df9127598a83d174eb13a48b26/grpcio-1.80.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:00168469238b022500e486c1c33916acf2f2a9b2c022202cf8a1885d2e3073c1", size = 7414743, upload-time = "2026-03-30T08:46:59.682Z" }, + { url = "https://files.pythonhosted.org/packages/de/f2/567f5bd5054398ed6b0509b9a30900376dcf2786bd936812098808b49d8d/grpcio-1.80.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8502122a3cc1714038e39a0b071acb1207ca7844208d5ea0d091317555ee7106", size = 8426046, upload-time = "2026-03-30T08:47:02.474Z" }, + { url = "https://files.pythonhosted.org/packages/62/29/73ef0141b4732ff5eacd68430ff2512a65c004696997f70476a83e548e7e/grpcio-1.80.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ce1794f4ea6cc3ca29463f42d665c32ba1b964b48958a66497917fe9069f26e6", size = 7851641, upload-time = "2026-03-30T08:47:05.462Z" }, + { url = "https://files.pythonhosted.org/packages/46/69/abbfa360eb229a8623bab5f5a4f8105e445bd38ce81a89514ba55d281ad0/grpcio-1.80.0-cp311-cp311-win32.whl", hash = "sha256:51b4a7189b0bef2aa30adce3c78f09c83526cf3dddb24c6a96555e3b97340440", size = 4154368, upload-time = "2026-03-30T08:47:08.027Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d4/ae92206d01183b08613e846076115f5ac5991bae358d2a749fa864da5699/grpcio-1.80.0-cp311-cp311-win_amd64.whl", hash = "sha256:02e64bb0bb2da14d947a49e6f120a75e947250aebe65f9629b62bb1f5c14e6e9", size = 4894235, upload-time = "2026-03-30T08:47:10.839Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e8/a2b749265eb3415abc94f2e619bbd9e9707bebdda787e61c593004ec927a/grpcio-1.80.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:c624cc9f1008361014378c9d776de7182b11fe8b2e5a81bc69f23a295f2a1ad0", size = 6015616, upload-time = "2026-03-30T08:47:13.428Z" }, + { url = "https://files.pythonhosted.org/packages/3e/97/b1282161a15d699d1e90c360df18d19165a045ce1c343c7f313f5e8a0b77/grpcio-1.80.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:f49eddcac43c3bf350c0385366a58f36bed8cc2c0ec35ef7b74b49e56552c0c2", size = 12014204, upload-time = "2026-03-30T08:47:15.873Z" }, + { url = "https://files.pythonhosted.org/packages/6e/5e/d319c6e997b50c155ac5a8cb12f5173d5b42677510e886d250d50264949d/grpcio-1.80.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d334591df610ab94714048e0d5b4f3dd5ad1bee74dfec11eee344220077a79de", size = 6563866, upload-time = "2026-03-30T08:47:18.588Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f6/fdd975a2cb4d78eb67769a7b3b3830970bfa2e919f1decf724ae4445f42c/grpcio-1.80.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0cb517eb1d0d0aaf1d87af7cc5b801d686557c1d88b2619f5e31fab3c2315921", size = 7273060, upload-time = "2026-03-30T08:47:21.113Z" }, + { url = "https://files.pythonhosted.org/packages/db/f0/a3deb5feba60d9538a962913e37bd2e69a195f1c3376a3dd44fe0427e996/grpcio-1.80.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4e78c4ac0d97dc2e569b2f4bcbbb447491167cb358d1a389fc4af71ab6f70411", size = 6782121, upload-time = "2026-03-30T08:47:23.827Z" }, + { url = "https://files.pythonhosted.org/packages/ca/84/36c6dcfddc093e108141f757c407902a05085e0c328007cb090d56646cdf/grpcio-1.80.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2ed770b4c06984f3b47eb0517b1c69ad0b84ef3f40128f51448433be904634cd", size = 7383811, upload-time = "2026-03-30T08:47:26.517Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ef/f3a77e3dc5b471a0ec86c564c98d6adfa3510d38f8ee99010410858d591e/grpcio-1.80.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:256507e2f524092f1473071a05e65a5b10d84b82e3ff24c5b571513cfaa61e2f", size = 8393860, upload-time = "2026-03-30T08:47:29.439Z" }, + { url = "https://files.pythonhosted.org/packages/9b/8d/9d4d27ed7f33d109c50d6b5ce578a9914aa68edab75d65869a17e630a8d1/grpcio-1.80.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a6284a5d907c37db53350645567c522be314bac859a64a7a5ca63b77bb7958f", size = 7830132, upload-time = "2026-03-30T08:47:33.254Z" }, + { url = "https://files.pythonhosted.org/packages/14/e4/9990b41c6d7a44e1e9dee8ac11d7a9802ba1378b40d77468a7761d1ad288/grpcio-1.80.0-cp312-cp312-win32.whl", hash = "sha256:c71309cfce2f22be26aa4a847357c502db6c621f1a49825ae98aa0907595b193", size = 4140904, upload-time = "2026-03-30T08:47:35.319Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2c/296f6138caca1f4b92a31ace4ae1b87dab692fc16a7a3417af3bb3c805bf/grpcio-1.80.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe648599c0e37594c4809d81a9e77bd138cc82eb8baa71b6a86af65426723ff", size = 4880944, upload-time = "2026-03-30T08:47:37.831Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/7c3c25789e3f069e581dc342e03613c5b1cb012c4e8c7d9d5cf960a75856/grpcio-1.80.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:e9e408fc016dffd20661f0126c53d8a31c2821b5c13c5d67a0f5ed5de93319ad", size = 6017243, upload-time = "2026-03-30T08:47:40.075Z" }, + { url = "https://files.pythonhosted.org/packages/04/19/21a9806eb8240e174fd1ab0cd5b9aa948bb0e05c2f2f55f9d5d7405e6d08/grpcio-1.80.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:92d787312e613754d4d8b9ca6d3297e69994a7912a32fa38c4c4e01c272974b0", size = 12010840, upload-time = "2026-03-30T08:47:43.11Z" }, + { url = "https://files.pythonhosted.org/packages/18/3a/23347d35f76f639e807fb7a36fad3068aed100996849a33809591f26eca6/grpcio-1.80.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8ac393b58aa16991a2f1144ec578084d544038c12242da3a215966b512904d0f", size = 6567644, upload-time = "2026-03-30T08:47:46.806Z" }, + { url = "https://files.pythonhosted.org/packages/ff/40/96e07ecb604a6a67ae6ab151e3e35b132875d98bc68ec65f3e5ab3e781d7/grpcio-1.80.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:68e5851ac4b9afe07e7f84483803ad167852570d65326b34d54ca560bfa53fb6", size = 7277830, upload-time = "2026-03-30T08:47:49.643Z" }, + { url = "https://files.pythonhosted.org/packages/9b/e2/da1506ecea1f34a5e365964644b35edef53803052b763ca214ba3870c856/grpcio-1.80.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:873ff5d17d68992ef6605330127425d2fc4e77e612fa3c3e0ed4e668685e3140", size = 6783216, upload-time = "2026-03-30T08:47:52.817Z" }, + { url = "https://files.pythonhosted.org/packages/44/83/3b20ff58d0c3b7f6caaa3af9a4174d4023701df40a3f39f7f1c8e7c48f9d/grpcio-1.80.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2bea16af2750fd0a899bf1abd9022244418b55d1f37da2202249ba4ba673838d", size = 7385866, upload-time = "2026-03-30T08:47:55.687Z" }, + { url = "https://files.pythonhosted.org/packages/47/45/55c507599c5520416de5eefecc927d6a0d7af55e91cfffb2e410607e5744/grpcio-1.80.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba0db34f7e1d803a878284cd70e4c63cb6ae2510ba51937bf8f45ba997cefcf7", size = 8391602, upload-time = "2026-03-30T08:47:58.303Z" }, + { url = "https://files.pythonhosted.org/packages/10/bb/dd06f4c24c01db9cf11341b547d0a016b2c90ed7dbbb086a5710df7dd1d7/grpcio-1.80.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8eb613f02d34721f1acf3626dfdb3545bd3c8505b0e52bf8b5710a28d02e8aa7", size = 7826752, upload-time = "2026-03-30T08:48:01.311Z" }, + { url = "https://files.pythonhosted.org/packages/f9/1e/9d67992ba23371fd63d4527096eb8c6b76d74d52b500df992a3343fd7251/grpcio-1.80.0-cp313-cp313-win32.whl", hash = "sha256:93b6f823810720912fd131f561f91f5fed0fda372b6b7028a2681b8194d5d294", size = 4142310, upload-time = "2026-03-30T08:48:04.594Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e6/283326a27da9e2c3038bc93eeea36fb118ce0b2d03922a9cda6688f53c5b/grpcio-1.80.0-cp313-cp313-win_amd64.whl", hash = "sha256:e172cf795a3ba5246d3529e4d34c53db70e888fa582a8ffebd2e6e48bc0cba50", size = 4882833, upload-time = "2026-03-30T08:48:07.363Z" }, + { url = "https://files.pythonhosted.org/packages/c5/6d/e65307ce20f5a09244ba9e9d8476e99fb039de7154f37fb85f26978b59c3/grpcio-1.80.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:3d4147a97c8344d065d01bbf8b6acec2cf86fb0400d40696c8bdad34a64ffc0e", size = 6017376, upload-time = "2026-03-30T08:48:10.005Z" }, + { url = "https://files.pythonhosted.org/packages/69/10/9cef5d9650c72625a699c549940f0abb3c4bfdb5ed45a5ce431f92f31806/grpcio-1.80.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d8e11f167935b3eb089ac9038e1a063e6d7dbe995c0bb4a661e614583352e76f", size = 12018133, upload-time = "2026-03-30T08:48:12.927Z" }, + { url = "https://files.pythonhosted.org/packages/04/82/983aabaad82ba26113caceeb9091706a0696b25da004fe3defb5b346e15b/grpcio-1.80.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f14b618fc30de822681ee986cfdcc2d9327229dc4c98aed16896761cacd468b9", size = 6574748, upload-time = "2026-03-30T08:48:16.386Z" }, + { url = "https://files.pythonhosted.org/packages/07/d7/031666ef155aa0bf399ed7e19439656c38bbd143779ae0861b038ce82abd/grpcio-1.80.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4ed39fbdcf9b87370f6e8df4e39ca7b38b3e5e9d1b0013c7b6be9639d6578d14", size = 7277711, upload-time = "2026-03-30T08:48:19.627Z" }, + { url = "https://files.pythonhosted.org/packages/e8/43/f437a78f7f4f1d311804189e8f11fb311a01049b2e08557c1068d470cb2e/grpcio-1.80.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2dcc70e9f0ba987526e8e8603a610fb4f460e42899e74e7a518bf3c68fe1bf05", size = 6785372, upload-time = "2026-03-30T08:48:22.373Z" }, + { url = "https://files.pythonhosted.org/packages/93/3d/f6558e9c6296cb4227faa5c43c54a34c68d32654b829f53288313d16a86e/grpcio-1.80.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:448c884b668b868562b1bda833c5fce6272d26e1926ec46747cda05741d302c1", size = 7395268, upload-time = "2026-03-30T08:48:25.638Z" }, + { url = "https://files.pythonhosted.org/packages/06/21/0fdd77e84720b08843c371a2efa6f2e19dbebf56adc72df73d891f5506f0/grpcio-1.80.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a1dc80fe55685b4a543555e6eef975303b36c8db1023b1599b094b92aa77965f", size = 8392000, upload-time = "2026-03-30T08:48:28.974Z" }, + { url = "https://files.pythonhosted.org/packages/f5/68/67f4947ed55d2e69f2cc199ab9fd85e0a0034d813bbeef84df6d2ba4d4b7/grpcio-1.80.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:31b9ac4ad1aa28ffee5503821fafd09e4da0a261ce1c1281c6c8da0423c83b6e", size = 7828477, upload-time = "2026-03-30T08:48:32.054Z" }, + { url = "https://files.pythonhosted.org/packages/44/b6/8d4096691b2e385e8271911a0de4f35f0a6c7d05aff7098e296c3de86939/grpcio-1.80.0-cp314-cp314-win32.whl", hash = "sha256:367ce30ba67d05e0592470428f0ec1c31714cab9ef19b8f2e37be1f4c7d32fae", size = 4218563, upload-time = "2026-03-30T08:48:34.538Z" }, + { url = "https://files.pythonhosted.org/packages/e5/8c/bbe6baf2557262834f2070cf668515fa308b2d38a4bbf771f8f7872a7036/grpcio-1.80.0-cp314-cp314-win_amd64.whl", hash = "sha256:3b01e1f5464c583d2f567b2e46ff0d516ef979978f72091fd81f5ab7fa6e2e7f", size = 5019457, upload-time = "2026-03-30T08:48:37.308Z" }, ] [[package]] @@ -1005,45 +1068,53 @@ wheels = [ [[package]] name = "h5py" -version = "3.15.1" +version = "3.16.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4d/6a/0d79de0b025aa85dc8864de8e97659c94cf3d23148394a954dc5ca52f8c8/h5py-3.15.1.tar.gz", hash = "sha256:c86e3ed45c4473564de55aa83b6fc9e5ead86578773dfbd93047380042e26b69", size = 426236, upload-time = "2025-10-16T10:35:27.404Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/41/fd/8349b48b15b47768042cff06ad6e1c229f0a4bd89225bf6b6894fea27e6d/h5py-3.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5aaa330bcbf2830150c50897ea5dcbed30b5b6d56897289846ac5b9e529ec243", size = 3434135, upload-time = "2025-10-16T10:33:47.954Z" }, - { url = "https://files.pythonhosted.org/packages/c1/b0/1c628e26a0b95858f54aba17e1599e7f6cd241727596cc2580b72cb0a9bf/h5py-3.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c970fb80001fffabb0109eaf95116c8e7c0d3ca2de854e0901e8a04c1f098509", size = 2870958, upload-time = "2025-10-16T10:33:50.907Z" }, - { url = "https://files.pythonhosted.org/packages/f9/e3/c255cafc9b85e6ea04e2ad1bba1416baa1d7f57fc98a214be1144087690c/h5py-3.15.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80e5bb5b9508d5d9da09f81fd00abbb3f85da8143e56b1585d59bc8ceb1dba8b", size = 4504770, upload-time = "2025-10-16T10:33:54.357Z" }, - { url = "https://files.pythonhosted.org/packages/8b/23/4ab1108e87851ccc69694b03b817d92e142966a6c4abd99e17db77f2c066/h5py-3.15.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b849ba619a066196169763c33f9f0f02e381156d61c03e000bb0100f9950faf", size = 4700329, upload-time = "2025-10-16T10:33:57.616Z" }, - { url = "https://files.pythonhosted.org/packages/a4/e4/932a3a8516e4e475b90969bf250b1924dbe3612a02b897e426613aed68f4/h5py-3.15.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e7f6c841efd4e6e5b7e82222eaf90819927b6d256ab0f3aca29675601f654f3c", size = 4152456, upload-time = "2025-10-16T10:34:00.843Z" }, - { url = "https://files.pythonhosted.org/packages/2a/0a/f74d589883b13737021b2049ac796328f188dbb60c2ed35b101f5b95a3fc/h5py-3.15.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ca8a3a22458956ee7b40d8e39c9a9dc01f82933e4c030c964f8b875592f4d831", size = 4617295, upload-time = "2025-10-16T10:34:04.154Z" }, - { url = "https://files.pythonhosted.org/packages/23/95/499b4e56452ef8b6c95a271af0dde08dac4ddb70515a75f346d4f400579b/h5py-3.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:550e51131376889656feec4aff2170efc054a7fe79eb1da3bb92e1625d1ac878", size = 2882129, upload-time = "2025-10-16T10:34:06.886Z" }, - { url = "https://files.pythonhosted.org/packages/ce/bb/cfcc70b8a42222ba3ad4478bcef1791181ea908e2adbd7d53c66395edad5/h5py-3.15.1-cp311-cp311-win_arm64.whl", hash = "sha256:b39239947cb36a819147fc19e86b618dcb0953d1cd969f5ed71fc0de60392427", size = 2477121, upload-time = "2025-10-16T10:34:09.579Z" }, - { url = "https://files.pythonhosted.org/packages/62/b8/c0d9aa013ecfa8b7057946c080c0c07f6fa41e231d2e9bd306a2f8110bdc/h5py-3.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:316dd0f119734f324ca7ed10b5627a2de4ea42cc4dfbcedbee026aaa361c238c", size = 3399089, upload-time = "2025-10-16T10:34:12.135Z" }, - { url = "https://files.pythonhosted.org/packages/a4/5e/3c6f6e0430813c7aefe784d00c6711166f46225f5d229546eb53032c3707/h5py-3.15.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b51469890e58e85d5242e43aab29f5e9c7e526b951caab354f3ded4ac88e7b76", size = 2847803, upload-time = "2025-10-16T10:34:14.564Z" }, - { url = "https://files.pythonhosted.org/packages/00/69/ba36273b888a4a48d78f9268d2aee05787e4438557450a8442946ab8f3ec/h5py-3.15.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a33bfd5dfcea037196f7778534b1ff7e36a7f40a89e648c8f2967292eb6898e", size = 4914884, upload-time = "2025-10-16T10:34:18.452Z" }, - { url = "https://files.pythonhosted.org/packages/3a/30/d1c94066343a98bb2cea40120873193a4fed68c4ad7f8935c11caf74c681/h5py-3.15.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25c8843fec43b2cc368aa15afa1cdf83fc5e17b1c4e10cd3771ef6c39b72e5ce", size = 5109965, upload-time = "2025-10-16T10:34:21.853Z" }, - { url = "https://files.pythonhosted.org/packages/81/3d/d28172116eafc3bc9f5991b3cb3fd2c8a95f5984f50880adfdf991de9087/h5py-3.15.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a308fd8681a864c04423c0324527237a0484e2611e3441f8089fd00ed56a8171", size = 4561870, upload-time = "2025-10-16T10:34:26.69Z" }, - { url = "https://files.pythonhosted.org/packages/a5/83/393a7226024238b0f51965a7156004eaae1fcf84aa4bfecf7e582676271b/h5py-3.15.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f4a016df3f4a8a14d573b496e4d1964deb380e26031fc85fb40e417e9131888a", size = 5037161, upload-time = "2025-10-16T10:34:30.383Z" }, - { url = "https://files.pythonhosted.org/packages/cf/51/329e7436bf87ca6b0fe06dd0a3795c34bebe4ed8d6c44450a20565d57832/h5py-3.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:59b25cf02411bf12e14f803fef0b80886444c7fe21a5ad17c6a28d3f08098a1e", size = 2874165, upload-time = "2025-10-16T10:34:33.461Z" }, - { url = "https://files.pythonhosted.org/packages/09/a8/2d02b10a66747c54446e932171dd89b8b4126c0111b440e6bc05a7c852ec/h5py-3.15.1-cp312-cp312-win_arm64.whl", hash = "sha256:61d5a58a9851e01ee61c932bbbb1c98fe20aba0a5674776600fb9a361c0aa652", size = 2458214, upload-time = "2025-10-16T10:34:35.733Z" }, - { url = "https://files.pythonhosted.org/packages/88/b3/40207e0192415cbff7ea1d37b9f24b33f6d38a5a2f5d18a678de78f967ae/h5py-3.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8440fd8bee9500c235ecb7aa1917a0389a2adb80c209fa1cc485bd70e0d94a5", size = 3376511, upload-time = "2025-10-16T10:34:38.596Z" }, - { url = "https://files.pythonhosted.org/packages/31/96/ba99a003c763998035b0de4c299598125df5fc6c9ccf834f152ddd60e0fb/h5py-3.15.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ab2219dbc6fcdb6932f76b548e2b16f34a1f52b7666e998157a4dfc02e2c4123", size = 2826143, upload-time = "2025-10-16T10:34:41.342Z" }, - { url = "https://files.pythonhosted.org/packages/6a/c2/fc6375d07ea3962df7afad7d863fe4bde18bb88530678c20d4c90c18de1d/h5py-3.15.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8cb02c3a96255149ed3ac811eeea25b655d959c6dd5ce702c9a95ff11859eb5", size = 4908316, upload-time = "2025-10-16T10:34:44.619Z" }, - { url = "https://files.pythonhosted.org/packages/d9/69/4402ea66272dacc10b298cca18ed73e1c0791ff2ae9ed218d3859f9698ac/h5py-3.15.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:121b2b7a4c1915d63737483b7bff14ef253020f617c2fb2811f67a4bed9ac5e8", size = 5103710, upload-time = "2025-10-16T10:34:48.639Z" }, - { url = "https://files.pythonhosted.org/packages/e0/f6/11f1e2432d57d71322c02a97a5567829a75f223a8c821764a0e71a65cde8/h5py-3.15.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59b0d63b318bf3cc06687def2b45afd75926bbc006f7b8cd2b1a231299fc8599", size = 4556042, upload-time = "2025-10-16T10:34:51.841Z" }, - { url = "https://files.pythonhosted.org/packages/18/88/3eda3ef16bfe7a7dbc3d8d6836bbaa7986feb5ff091395e140dc13927bcc/h5py-3.15.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e02fe77a03f652500d8bff288cbf3675f742fc0411f5a628fa37116507dc7cc0", size = 5030639, upload-time = "2025-10-16T10:34:55.257Z" }, - { url = "https://files.pythonhosted.org/packages/e5/ea/fbb258a98863f99befb10ed727152b4ae659f322e1d9c0576f8a62754e81/h5py-3.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:dea78b092fd80a083563ed79a3171258d4a4d307492e7cf8b2313d464c82ba52", size = 2864363, upload-time = "2025-10-16T10:34:58.099Z" }, - { url = "https://files.pythonhosted.org/packages/5d/c9/35021cc9cd2b2915a7da3026e3d77a05bed1144a414ff840953b33937fb9/h5py-3.15.1-cp313-cp313-win_arm64.whl", hash = "sha256:c256254a8a81e2bddc0d376e23e2a6d2dc8a1e8a2261835ed8c1281a0744cd97", size = 2449570, upload-time = "2025-10-16T10:35:00.473Z" }, - { url = "https://files.pythonhosted.org/packages/a0/2c/926eba1514e4d2e47d0e9eb16c784e717d8b066398ccfca9b283917b1bfb/h5py-3.15.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5f4fb0567eb8517c3ecd6b3c02c4f4e9da220c8932604960fd04e24ee1254763", size = 3380368, upload-time = "2025-10-16T10:35:03.117Z" }, - { url = "https://files.pythonhosted.org/packages/65/4b/d715ed454d3baa5f6ae1d30b7eca4c7a1c1084f6a2edead9e801a1541d62/h5py-3.15.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:954e480433e82d3872503104f9b285d369048c3a788b2b1a00e53d1c47c98dd2", size = 2833793, upload-time = "2025-10-16T10:35:05.623Z" }, - { url = "https://files.pythonhosted.org/packages/ef/d4/ef386c28e4579314610a8bffebbee3b69295b0237bc967340b7c653c6c10/h5py-3.15.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd125c131889ebbef0849f4a0e29cf363b48aba42f228d08b4079913b576bb3a", size = 4903199, upload-time = "2025-10-16T10:35:08.972Z" }, - { url = "https://files.pythonhosted.org/packages/33/5d/65c619e195e0b5e54ea5a95c1bb600c8ff8715e0d09676e4cce56d89f492/h5py-3.15.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28a20e1a4082a479b3d7db2169f3a5034af010b90842e75ebbf2e9e49eb4183e", size = 5097224, upload-time = "2025-10-16T10:35:12.808Z" }, - { url = "https://files.pythonhosted.org/packages/30/30/5273218400bf2da01609e1292f562c94b461fcb73c7a9e27fdadd43abc0a/h5py-3.15.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa8df5267f545b4946df8ca0d93d23382191018e4cda2deda4c2cedf9a010e13", size = 4551207, upload-time = "2025-10-16T10:35:16.24Z" }, - { url = "https://files.pythonhosted.org/packages/d3/39/a7ef948ddf4d1c556b0b2b9559534777bccc318543b3f5a1efdf6b556c9c/h5py-3.15.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99d374a21f7321a4c6ab327c4ab23bd925ad69821aeb53a1e75dd809d19f67fa", size = 5025426, upload-time = "2025-10-16T10:35:19.831Z" }, - { url = "https://files.pythonhosted.org/packages/b6/d8/7368679b8df6925b8415f9dcc9ab1dab01ddc384d2b2c24aac9191bd9ceb/h5py-3.15.1-cp314-cp314-win_amd64.whl", hash = "sha256:9c73d1d7cdb97d5b17ae385153472ce118bed607e43be11e9a9deefaa54e0734", size = 2865704, upload-time = "2025-10-16T10:35:22.658Z" }, - { url = "https://files.pythonhosted.org/packages/d3/b7/4a806f85d62c20157e62e58e03b27513dc9c55499768530acc4f4c5ce4be/h5py-3.15.1-cp314-cp314-win_arm64.whl", hash = "sha256:a6d8c5a05a76aca9a494b4c53ce8a9c29023b7f64f625c6ce1841e92a362ccdf", size = 2465544, upload-time = "2025-10-16T10:35:25.695Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/db/33/acd0ce6863b6c0d7735007df01815403f5589a21ff8c2e1ee2587a38f548/h5py-3.16.0.tar.gz", hash = "sha256:a0dbaad796840ccaa67a4c144a0d0c8080073c34c76d5a6941d6818678ef2738", size = 446526, upload-time = "2026-03-06T13:49:08.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/95/a825894f3e45cbac7554c4e97314ce886b233a20033787eda755ca8fecc7/h5py-3.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:719439d14b83f74eeb080e9650a6c7aa6d0d9ea0ca7f804347b05fac6fbf18af", size = 3721663, upload-time = "2026-03-06T13:47:49.599Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3b/38ff88b347c3e346cda1d3fc1b65a7aa75d40632228d8b8a5d7b58508c24/h5py-3.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c3f0a0e136f2e95dd0b67146abb6668af4f1a69c81ef8651a2d316e8e01de447", size = 3087630, upload-time = "2026-03-06T13:47:51.249Z" }, + { url = "https://files.pythonhosted.org/packages/98/a8/2594cef906aee761601eff842c7dc598bea2b394a3e1c00966832b8eeb7c/h5py-3.16.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:a6fbc5367d4046801f9b7db9191b31895f22f1c6df1f9987d667854cac493538", size = 4823472, upload-time = "2026-03-06T13:47:53.085Z" }, + { url = "https://files.pythonhosted.org/packages/52/a0/c1f604538ff6db22a0690be2dc44ab59178e115f63c917794e529356ab23/h5py-3.16.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:fb1720028d99040792bb2fb31facb8da44a6f29df7697e0b84f0d79aff2e9bd3", size = 5027150, upload-time = "2026-03-06T13:47:55.043Z" }, + { url = "https://files.pythonhosted.org/packages/2e/fd/301739083c2fc4fd89950f9bcfce75d6e14b40b0ca3d40e48a8993d1722c/h5py-3.16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:314b6054fe0b1051c2b0cb2df5cbdab15622fb05e80f202e3b6a5eee0d6fe365", size = 4814544, upload-time = "2026-03-06T13:47:56.893Z" }, + { url = "https://files.pythonhosted.org/packages/4c/42/2193ed41ccee78baba8fcc0cff2c925b8b9ee3793305b23e1f22c20bf4c7/h5py-3.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ffbab2fedd6581f6aa31cf1639ca2cb86e02779de525667892ebf4cc9fd26434", size = 5034013, upload-time = "2026-03-06T13:47:59.01Z" }, + { url = "https://files.pythonhosted.org/packages/f7/20/e6c0ff62ca2ad1a396a34f4380bafccaaf8791ff8fccf3d995a1fc12d417/h5py-3.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:17d1f1630f92ad74494a9a7392ab25982ce2b469fc62da6074c0ce48366a2999", size = 3191673, upload-time = "2026-03-06T13:48:00.626Z" }, + { url = "https://files.pythonhosted.org/packages/f2/48/239cbe352ac4f2b8243a8e620fa1a2034635f633731493a7ff1ed71e8658/h5py-3.16.0-cp311-cp311-win_arm64.whl", hash = "sha256:85b9c49dd58dc44cf70af944784e2c2038b6f799665d0dcbbc812a26e0faa859", size = 2673834, upload-time = "2026-03-06T13:48:02.579Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c0/5d4119dba94093bbafede500d3defd2f5eab7897732998c04b54021e530b/h5py-3.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c5313566f4643121a78503a473f0fb1e6dcc541d5115c44f05e037609c565c4d", size = 3685604, upload-time = "2026-03-06T13:48:04.198Z" }, + { url = "https://files.pythonhosted.org/packages/b0/42/c84efcc1d4caebafb1ecd8be4643f39c85c47a80fe254d92b8b43b1eadaf/h5py-3.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:42b012933a83e1a558c673176676a10ce2fd3759976a0fedee1e672d1e04fc9d", size = 3061940, upload-time = "2026-03-06T13:48:05.783Z" }, + { url = "https://files.pythonhosted.org/packages/89/84/06281c82d4d1686fde1ac6b0f307c50918f1c0151062445ab3b6fa5a921d/h5py-3.16.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:ff24039e2573297787c3063df64b60aab0591980ac898329a08b0320e0cf2527", size = 5198852, upload-time = "2026-03-06T13:48:07.482Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e9/1a19e42cd43cc1365e127db6aae85e1c671da1d9a5d746f4d34a50edb577/h5py-3.16.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:dfc21898ff025f1e8e67e194965a95a8d4754f452f83454538f98f8a3fcb207e", size = 5405250, upload-time = "2026-03-06T13:48:09.628Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8e/9790c1655eabeb85b92b1ecab7d7e62a2069e53baefd58c98f0909c7a948/h5py-3.16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:698dd69291272642ffda44a0ecd6cd3bda5faf9621452d255f57ce91487b9794", size = 5190108, upload-time = "2026-03-06T13:48:11.26Z" }, + { url = "https://files.pythonhosted.org/packages/51/d7/ab693274f1bd7e8c5f9fdd6c7003a88d59bedeaf8752716a55f532924fbb/h5py-3.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2b2c02b0a160faed5fb33f1ba8a264a37ee240b22e049ecc827345d0d9043074", size = 5419216, upload-time = "2026-03-06T13:48:13.322Z" }, + { url = "https://files.pythonhosted.org/packages/03/c1/0976b235cf29ead553e22f2fb6385a8252b533715e00d0ae52ed7b900582/h5py-3.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:96b422019a1c8975c2d5dadcf61d4ba6f01c31f92bbde6e4649607885fe502d6", size = 3182868, upload-time = "2026-03-06T13:48:15.759Z" }, + { url = "https://files.pythonhosted.org/packages/14/d9/866b7e570b39070f92d47b0ff1800f0f8239b6f9e45f02363d7112336c1f/h5py-3.16.0-cp312-cp312-win_arm64.whl", hash = "sha256:39c2838fb1e8d97bcf1755e60ad1f3dd76a7b2a475928dc321672752678b96db", size = 2653286, upload-time = "2026-03-06T13:48:17.279Z" }, + { url = "https://files.pythonhosted.org/packages/0f/9e/6142ebfda0cb6e9349c091eae73c2e01a770b7659255248d637bec54a88b/h5py-3.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:370a845f432c2c9619db8eed334d1e610c6015796122b0e57aa46312c22617d9", size = 3671808, upload-time = "2026-03-06T13:48:19.737Z" }, + { url = "https://files.pythonhosted.org/packages/b0/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42108e93326c50c2810025aade9eac9d6827524cdccc7d4b75a546e5ab308edb", size = 3045837, upload-time = "2026-03-06T13:48:21.854Z" }, + { url = "https://files.pythonhosted.org/packages/da/1e/6172269e18cc5a484e2913ced33339aad588e02ba407fafd00d369e22ef3/h5py-3.16.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:099f2525c9dcf28de366970a5fb34879aab20491589fa89ce2863a84218bb524", size = 5193860, upload-time = "2026-03-06T13:48:24.071Z" }, + { url = "https://files.pythonhosted.org/packages/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:9300ad32dea9dfc5171f94d5f6948e159ed93e4701280b0f508773b3f582f402", size = 5400417, upload-time = "2026-03-06T13:48:25.728Z" }, + { url = "https://files.pythonhosted.org/packages/bc/81/5b62d760039eed64348c98129d17061fdfc7839fc9c04eaaad6dee1004e4/h5py-3.16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:171038f23bccddfc23f344cadabdfc9917ff554db6a0d417180d2747fe4c75a7", size = 5185214, upload-time = "2026-03-06T13:48:27.436Z" }, + { url = "https://files.pythonhosted.org/packages/28/c4/532123bcd9080e250696779c927f2cb906c8bf3447df98f5ceb8dcded539/h5py-3.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7e420b539fb6023a259a1b14d4c9f6df8cf50d7268f48e161169987a57b737ff", size = 5414598, upload-time = "2026-03-06T13:48:29.49Z" }, + { url = "https://files.pythonhosted.org/packages/c3/d9/a27997f84341fc0dfcdd1fe4179b6ba6c32a7aa880fdb8c514d4dad6fba3/h5py-3.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:18f2bbcd545e6991412253b98727374c356d67caa920e68dc79eab36bf5fedad", size = 3175509, upload-time = "2026-03-06T13:48:31.131Z" }, + { url = "https://files.pythonhosted.org/packages/a5/23/bb8647521d4fd770c30a76cfc6cb6a2f5495868904054e92f2394c5a78ff/h5py-3.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:656f00e4d903199a1d58df06b711cf3ca632b874b4207b7dbec86185b5c8c7d4", size = 2647362, upload-time = "2026-03-06T13:48:33.411Z" }, + { url = "https://files.pythonhosted.org/packages/48/3c/7fcd9b4c9eed82e91fb15568992561019ae7a829d1f696b2c844355d95dd/h5py-3.16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9c9d307c0ef862d1cd5714f72ecfafe0a5d7529c44845afa8de9f46e5ba8bd65", size = 3678608, upload-time = "2026-03-06T13:48:35.183Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b7/9366ed44ced9b7ef357ab48c94205280276db9d7f064aa3012a97227e966/h5py-3.16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8c1eff849cdd53cbc73c214c30ebdb6f1bb8b64790b4b4fc36acdb5e43570210", size = 3054773, upload-time = "2026-03-06T13:48:37.139Z" }, + { url = "https://files.pythonhosted.org/packages/58/a5/4964bc0e91e86340c2bbda83420225b2f770dcf1eb8a39464871ad769436/h5py-3.16.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:e2c04d129f180019e216ee5f9c40b78a418634091c8782e1f723a6ca3658b965", size = 5198886, upload-time = "2026-03-06T13:48:38.879Z" }, + { url = "https://files.pythonhosted.org/packages/f1/16/d905e7f53e661ce2c24686c38048d8e2b750ffc4350009d41c4e6c6c9826/h5py-3.16.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:e4360f15875a532bc7b98196c7592ed4fc92672a57c0a621355961cafb17a6dd", size = 5404883, upload-time = "2026-03-06T13:48:41.324Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f2/58f34cb74af46d39f4cd18ea20909a8514960c5a3e5b92fd06a28161e0a8/h5py-3.16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3fae9197390c325e62e0a1aa977f2f62d994aa87aab182abbea85479b791197c", size = 5192039, upload-time = "2026-03-06T13:48:43.117Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ca/934a39c24ce2e2db017268c08da0537c20fa0be7e1549be3e977313fc8f5/h5py-3.16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:43259303989ac8adacc9986695b31e35dba6fd1e297ff9c6a04b7da5542139cc", size = 5421526, upload-time = "2026-03-06T13:48:44.838Z" }, + { url = "https://files.pythonhosted.org/packages/3e/14/615a450205e1b56d16c6783f5ccd116cde05550faad70ae077c955654a75/h5py-3.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:fa48993a0b799737ba7fd21e2350fa0a60701e58180fae9f2de834bc39a147ab", size = 3183263, upload-time = "2026-03-06T13:48:47.117Z" }, + { url = "https://files.pythonhosted.org/packages/7b/48/a6faef5ed632cae0c65ac6b214a6614a0b510c3183532c521bdb0055e117/h5py-3.16.0-cp314-cp314-win_arm64.whl", hash = "sha256:1897a771a7f40d05c262fc8f37376ec37873218544b70216872876c627640f63", size = 2663450, upload-time = "2026-03-06T13:48:48.707Z" }, + { url = "https://files.pythonhosted.org/packages/5d/32/0c8bb8aedb62c772cf7c1d427c7d1951477e8c2835f872bc0a13d1f85f86/h5py-3.16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:15922e485844f77c0b9d275396d435db3baa58292a9c2176a386e072e0cf2491", size = 3760693, upload-time = "2026-03-06T13:48:50.453Z" }, + { url = "https://files.pythonhosted.org/packages/1d/1f/fcc5977d32d6387c5c9a694afee716a5e20658ac08b3ff24fdec79fb05f2/h5py-3.16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:df02dd29bd247f98674634dfe41f89fd7c16ba3d7de8695ec958f58404a4e618", size = 3181305, upload-time = "2026-03-06T13:48:52.221Z" }, + { url = "https://files.pythonhosted.org/packages/f5/a1/af87f64b9f986889884243643621ebbd4ac72472ba8ec8cec891ac8e2ca1/h5py-3.16.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:0f456f556e4e2cebeebd9d66adf8dc321770a42593494a0b6f0af54a7567b242", size = 5074061, upload-time = "2026-03-06T13:48:54.089Z" }, + { url = "https://files.pythonhosted.org/packages/cc/d0/146f5eaff3dc246a9c7f6e5e4f42bd45cc613bce16693bcd4d1f7c958bf5/h5py-3.16.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:3e6cb3387c756de6a9492d601553dffea3fe11b5f22b443aac708c69f3f55e16", size = 5279216, upload-time = "2026-03-06T13:48:56.75Z" }, + { url = "https://files.pythonhosted.org/packages/a1/9d/12a13424f1e604fc7df9497b73c0356fb78c2fb206abd7465ce47226e8fd/h5py-3.16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8389e13a1fd745ad2856873e8187fd10268b2d9677877bb667b41aebd771d8b7", size = 5070068, upload-time = "2026-03-06T13:48:59.169Z" }, + { url = "https://files.pythonhosted.org/packages/41/8c/bbe98f813722b4873818a8db3e15aa3e625b59278566905ac439725e8070/h5py-3.16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:346df559a0f7dcb31cf8e44805319e2ab24b8957c45e7708ce503b2ec79ba725", size = 5300253, upload-time = "2026-03-06T13:49:02.033Z" }, + { url = "https://files.pythonhosted.org/packages/32/9e/87e6705b4d6890e7cecdf876e2a7d3e40654a2ae37482d79a6f1b87f7b92/h5py-3.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4c6ab014ab704b4feaa719ae783b86522ed0bf1f82184704ed3c9e4e3228796e", size = 3381671, upload-time = "2026-03-06T13:49:04.351Z" }, + { url = "https://files.pythonhosted.org/packages/96/91/9fad90cfc5f9b2489c7c26ad897157bce82f0e9534a986a221b99760b23b/h5py-3.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:faca8fb4e4319c09d83337adc80b2ca7d5c5a343c2d6f1b6388f32cfecca13c1", size = 2740706, upload-time = "2026-03-06T13:49:06.347Z" }, ] [[package]] @@ -1092,11 +1163,11 @@ wheels = [ [[package]] name = "identify" -version = "2.6.17" +version = "2.6.18" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/84/376a3b96e5a8d33a7aa2c5b3b31a4b3c364117184bf0b17418055f6ace66/identify-2.6.17.tar.gz", hash = "sha256:f816b0b596b204c9fdf076ded172322f2723cf958d02f9c3587504834c8ff04d", size = 99579, upload-time = "2026-03-01T20:04:12.702Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/c4/7fb4db12296cdb11893d61c92048fe617ee853f8523b9b296ac03b43757e/identify-2.6.18.tar.gz", hash = "sha256:873ac56a5e3fd63e7438a7ecbc4d91aca692eb3fefa4534db2b7913f3fc352fd", size = 99580, upload-time = "2026-03-15T18:39:50.319Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl", hash = "sha256:be5f8412d5ed4b20f2bd41a65f920990bdccaa6a4a18a08f1eefdcd0bdd885f0", size = 99382, upload-time = "2026-03-01T20:04:11.439Z" }, + { url = "https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl", hash = "sha256:8db9d3c8ea9079db92cafb0ebf97abdc09d52e97f4dcf773a2e694048b7cd737", size = 99394, upload-time = "2026-03-15T18:39:48.915Z" }, ] [[package]] @@ -1110,27 +1181,27 @@ wheels = [ [[package]] name = "imageio" -version = "2.37.2" +version = "2.37.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, { name = "pillow" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/6f/606be632e37bf8d05b253e8626c2291d74c691ddc7bcdf7d6aaf33b32f6a/imageio-2.37.2.tar.gz", hash = "sha256:0212ef2727ac9caa5ca4b2c75ae89454312f440a756fcfc8ef1993e718f50f8a", size = 389600, upload-time = "2025-11-04T14:29:39.898Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/84/93bcd1300216ea50811cee96873b84a1bebf8d0489ffaf7f2a3756bab866/imageio-2.37.3.tar.gz", hash = "sha256:bbb37efbfc4c400fcd534b367b91fcd66d5da639aaa138034431a1c5e0a41451", size = 389673, upload-time = "2026-03-09T11:31:12.573Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/fe/301e0936b79bcab4cacc7548bf2853fc28dced0a578bab1f7ef53c9aa75b/imageio-2.37.2-py3-none-any.whl", hash = "sha256:ad9adfb20335d718c03de457358ed69f141021a333c40a53e57273d8a5bd0b9b", size = 317646, upload-time = "2025-11-04T14:29:37.948Z" }, + { url = "https://files.pythonhosted.org/packages/49/fa/391e437a34e55095173dca5f24070d89cbc233ff85bf1c29c93248c6588d/imageio-2.37.3-py3-none-any.whl", hash = "sha256:46f5bb8522cd421c0f5ae104d8268f569d856b29eb1a13b92829d1970f32c9f0", size = 317646, upload-time = "2026-03-09T11:31:10.771Z" }, ] [[package]] name = "importlib-metadata" -version = "8.7.1" +version = "9.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "zipp", marker = "python_full_version < '3.12'" }, + { name = "zipp" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/01/15bb152d77b21318514a96f43af312635eb2500c96b55398d020c93d86ea/importlib_metadata-9.0.0.tar.gz", hash = "sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc", size = 56405, upload-time = "2026-03-20T06:42:56.999Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, + { url = "https://files.pythonhosted.org/packages/38/3d/2d244233ac4f76e38533cfcb2991c9eb4c7bf688ae0a036d30725b8faafe/importlib_metadata-9.0.0-py3-none-any.whl", hash = "sha256:2d21d1cc5a017bd0559e36150c21c830ab1dc304dedd1b7ea85d20f45ef3edd7", size = 27789, upload-time = "2026-03-20T06:42:55.665Z" }, ] [[package]] @@ -1150,7 +1221,8 @@ dependencies = [ { name = "appnope", marker = "sys_platform == 'darwin'" }, { name = "comm" }, { name = "debugpy" }, - { name = "ipython" }, + { name = "ipython", version = "9.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "ipython", version = "9.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "jupyter-client" }, { name = "jupyter-core" }, { name = "matplotlib-inline" }, @@ -1168,24 +1240,52 @@ wheels = [ [[package]] name = "ipython" -version = "9.10.0" +version = "9.10.1" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.12'", +] dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "decorator" }, - { name = "ipython-pygments-lexers" }, - { name = "jedi" }, - { name = "matplotlib-inline" }, - { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit" }, - { name = "pygments" }, - { name = "stack-data" }, - { name = "traitlets" }, + { name = "colorama", marker = "python_full_version < '3.12' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version < '3.12'" }, + { name = "ipython-pygments-lexers", marker = "python_full_version < '3.12'" }, + { name = "jedi", marker = "python_full_version < '3.12'" }, + { name = "matplotlib-inline", marker = "python_full_version < '3.12'" }, + { name = "pexpect", marker = "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version < '3.12'" }, + { name = "pygments", marker = "python_full_version < '3.12'" }, + { name = "stack-data", marker = "python_full_version < '3.12'" }, + { name = "traitlets", marker = "python_full_version < '3.12'" }, { name = "typing-extensions", marker = "python_full_version < '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a6/60/2111715ea11f39b1535bed6024b7dec7918b71e5e5d30855a5b503056b50/ipython-9.10.0.tar.gz", hash = "sha256:cd9e656be97618a0676d058134cd44e6dc7012c0e5cb36a9ce96a8c904adaf77", size = 4426526, upload-time = "2026-02-02T10:00:33.594Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/25/daae0e764047b0a2480c7bbb25d48f4f509b5818636562eeac145d06dfee/ipython-9.10.1.tar.gz", hash = "sha256:e170e9b2a44312484415bdb750492699bf329233b03f2557a9692cce6466ada4", size = 4426663, upload-time = "2026-03-27T09:53:26.244Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/09/ba70f8d662d5671687da55ad2cc0064cf795b15e1eea70907532202e7c97/ipython-9.10.1-py3-none-any.whl", hash = "sha256:82d18ae9fb9164ded080c71ef92a182ee35ee7db2395f67616034bebb020a232", size = 622827, upload-time = "2026-03-27T09:53:24.566Z" }, +] + +[[package]] +name = "ipython" +version = "9.12.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version >= '3.12' and python_full_version < '3.14'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version >= '3.12'" }, + { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.12'" }, + { name = "jedi", marker = "python_full_version >= '3.12'" }, + { name = "matplotlib-inline", marker = "python_full_version >= '3.12'" }, + { name = "pexpect", marker = "python_full_version >= '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version >= '3.12'" }, + { name = "pygments", marker = "python_full_version >= '3.12'" }, + { name = "stack-data", marker = "python_full_version >= '3.12'" }, + { name = "traitlets", marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3a/73/7114f80a8f9cabdb13c27732dce24af945b2923dcab80723602f7c8bc2d8/ipython-9.12.0.tar.gz", hash = "sha256:01daa83f504b693ba523b5a407246cabde4eb4513285a3c6acaff11a66735ee4", size = 4428879, upload-time = "2026-03-27T09:42:45.312Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/aa/898dec789a05731cd5a9f50605b7b44a72bd198fd0d4528e11fc610177cc/ipython-9.10.0-py3-none-any.whl", hash = "sha256:c6ab68cc23bba8c7e18e9b932797014cc61ea7fd6f19de180ab9ba73e65ee58d", size = 622774, upload-time = "2026-02-02T10:00:31.503Z" }, + { url = "https://files.pythonhosted.org/packages/59/22/906c8108974c673ebef6356c506cebb6870d48cedea3c41e949e2dd556bb/ipython-9.12.0-py3-none-any.whl", hash = "sha256:0f2701e8ee86e117e37f50563205d36feaa259d2e08d4a6bc6b6d74b18ce128d", size = 625661, upload-time = "2026-03-27T09:42:42.831Z" }, ] [[package]] @@ -1206,7 +1306,8 @@ version = "8.1.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "comm" }, - { name = "ipython" }, + { name = "ipython", version = "9.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "ipython", version = "9.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "jupyterlab-widgets" }, { name = "traitlets" }, { name = "widgetsnbextension" }, @@ -1254,20 +1355,20 @@ wheels = [ [[package]] name = "json5" -version = "0.13.0" +version = "0.14.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/77/e8/a3f261a66e4663f22700bc8a17c08cb83e91fbf086726e7a228398968981/json5-0.13.0.tar.gz", hash = "sha256:b1edf8d487721c0bf64d83c28e91280781f6e21f4a797d3261c7c828d4c165bf", size = 52441, upload-time = "2026-01-01T19:42:14.99Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/4b/6f8906aaf67d501e259b0adab4d312945bb7211e8b8d4dcc77c92320edaa/json5-0.14.0.tar.gz", hash = "sha256:b3f492fad9f6cdbced8b7d40b28b9b1c9701c5f561bef0d33b81c2ff433fefcb", size = 52656, upload-time = "2026-03-27T22:50:48.108Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl", hash = "sha256:9a08e1dd65f6a4d4c6fa82d216cf2477349ec2346a38fd70cc11d2557499fbcc", size = 36163, upload-time = "2026-01-01T19:42:13.962Z" }, + { url = "https://files.pythonhosted.org/packages/b8/42/cf027b4ac873b076189d935b135397675dac80cb29acb13e1ab86ad6c631/json5-0.14.0-py3-none-any.whl", hash = "sha256:56cf861bab076b1178eb8c92e1311d273a9b9acea2ccc82c276abf839ebaef3a", size = 36271, upload-time = "2026-03-27T22:50:47.073Z" }, ] [[package]] name = "jsonpointer" -version = "3.0.0" +version = "3.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114, upload-time = "2024-06-10T19:24:42.462Z" } +sdist = { url = "https://files.pythonhosted.org/packages/18/c7/af399a2e7a67fd18d63c40c5e62d3af4e67b836a2107468b6a5ea24c4304/jsonpointer-3.1.1.tar.gz", hash = "sha256:0b801c7db33a904024f6004d526dcc53bbb8a4a0f4e32bfd10beadf60adf1900", size = 9068, upload-time = "2026-03-23T22:32:32.458Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl", hash = "sha256:8ff8b95779d071ba472cf5bc913028df06031797532f08a7d5b602d8b2a488ca", size = 7659, upload-time = "2026-03-23T22:32:31.568Z" }, ] [[package]] @@ -1415,7 +1516,7 @@ wheels = [ [[package]] name = "jupyterlab" -version = "4.5.5" +version = "4.5.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "async-lru" }, @@ -1432,9 +1533,9 @@ dependencies = [ { name = "tornado" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6e/2d/953a5612a34a3c799a62566a548e711d103f631672fd49650e0f2de80870/jupyterlab-4.5.5.tar.gz", hash = "sha256:eac620698c59eb810e1729909be418d9373d18137cac66637141abba613b3fda", size = 23968441, upload-time = "2026-02-23T18:57:34.339Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/d5/730628e03fff2e8a8e8ccdaedde1489ab1309f9a4fa2536248884e30b7c7/jupyterlab-4.5.6.tar.gz", hash = "sha256:642fe2cfe7f0f5922a8a558ba7a0d246c7bc133b708dfe43f7b3a826d163cf42", size = 23970670, upload-time = "2026-03-11T14:17:04.531Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl", hash = "sha256:a35694a40a8e7f2e82f387472af24e61b22adcce87b5a8ab97a5d9c486202a6d", size = 12446824, upload-time = "2026-02-23T18:57:30.398Z" }, + { url = "https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl", hash = "sha256:d6b3dac883aa4d9993348e0f8e95b24624f75099aed64eab6a4351a9cdd1e580", size = 12447124, upload-time = "2026-03-11T14:17:00.229Z" }, ] [[package]] @@ -1475,92 +1576,108 @@ wheels = [ [[package]] name = "kiwisolver" -version = "1.4.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/ab/c80b0d5a9d8a1a65f4f815f2afff9798b12c3b9f31f1d304dd233dd920e2/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16", size = 124167, upload-time = "2025-08-10T21:25:53.403Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c0/27fe1a68a39cf62472a300e2879ffc13c0538546c359b86f149cc19f6ac3/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089", size = 66579, upload-time = "2025-08-10T21:25:54.79Z" }, - { url = "https://files.pythonhosted.org/packages/31/a2/a12a503ac1fd4943c50f9822678e8015a790a13b5490354c68afb8489814/kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543", size = 65309, upload-time = "2025-08-10T21:25:55.76Z" }, - { url = "https://files.pythonhosted.org/packages/66/e1/e533435c0be77c3f64040d68d7a657771194a63c279f55573188161e81ca/kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61", size = 1435596, upload-time = "2025-08-10T21:25:56.861Z" }, - { url = "https://files.pythonhosted.org/packages/67/1e/51b73c7347f9aabdc7215aa79e8b15299097dc2f8e67dee2b095faca9cb0/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1", size = 1246548, upload-time = "2025-08-10T21:25:58.246Z" }, - { url = "https://files.pythonhosted.org/packages/21/aa/72a1c5d1e430294f2d32adb9542719cfb441b5da368d09d268c7757af46c/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872", size = 1263618, upload-time = "2025-08-10T21:25:59.857Z" }, - { url = "https://files.pythonhosted.org/packages/a3/af/db1509a9e79dbf4c260ce0cfa3903ea8945f6240e9e59d1e4deb731b1a40/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26", size = 1317437, upload-time = "2025-08-10T21:26:01.105Z" }, - { url = "https://files.pythonhosted.org/packages/e0/f2/3ea5ee5d52abacdd12013a94130436e19969fa183faa1e7c7fbc89e9a42f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028", size = 2195742, upload-time = "2025-08-10T21:26:02.675Z" }, - { url = "https://files.pythonhosted.org/packages/6f/9b/1efdd3013c2d9a2566aa6a337e9923a00590c516add9a1e89a768a3eb2fc/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771", size = 2290810, upload-time = "2025-08-10T21:26:04.009Z" }, - { url = "https://files.pythonhosted.org/packages/fb/e5/cfdc36109ae4e67361f9bc5b41323648cb24a01b9ade18784657e022e65f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a", size = 2461579, upload-time = "2025-08-10T21:26:05.317Z" }, - { url = "https://files.pythonhosted.org/packages/62/86/b589e5e86c7610842213994cdea5add00960076bef4ae290c5fa68589cac/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464", size = 2268071, upload-time = "2025-08-10T21:26:06.686Z" }, - { url = "https://files.pythonhosted.org/packages/3b/c6/f8df8509fd1eee6c622febe54384a96cfaf4d43bf2ccec7a0cc17e4715c9/kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2", size = 73840, upload-time = "2025-08-10T21:26:07.94Z" }, - { url = "https://files.pythonhosted.org/packages/e2/2d/16e0581daafd147bc11ac53f032a2b45eabac897f42a338d0a13c1e5c436/kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7", size = 65159, upload-time = "2025-08-10T21:26:09.048Z" }, - { url = "https://files.pythonhosted.org/packages/86/c9/13573a747838aeb1c76e3267620daa054f4152444d1f3d1a2324b78255b5/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", size = 123686, upload-time = "2025-08-10T21:26:10.034Z" }, - { url = "https://files.pythonhosted.org/packages/51/ea/2ecf727927f103ffd1739271ca19c424d0e65ea473fbaeea1c014aea93f6/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2", size = 66460, upload-time = "2025-08-10T21:26:11.083Z" }, - { url = "https://files.pythonhosted.org/packages/5b/5a/51f5464373ce2aeb5194508298a508b6f21d3867f499556263c64c621914/kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14", size = 64952, upload-time = "2025-08-10T21:26:12.058Z" }, - { url = "https://files.pythonhosted.org/packages/70/90/6d240beb0f24b74371762873e9b7f499f1e02166a2d9c5801f4dbf8fa12e/kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04", size = 1474756, upload-time = "2025-08-10T21:26:13.096Z" }, - { url = "https://files.pythonhosted.org/packages/12/42/f36816eaf465220f683fb711efdd1bbf7a7005a2473d0e4ed421389bd26c/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752", size = 1276404, upload-time = "2025-08-10T21:26:14.457Z" }, - { url = "https://files.pythonhosted.org/packages/2e/64/bc2de94800adc830c476dce44e9b40fd0809cddeef1fde9fcf0f73da301f/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77", size = 1294410, upload-time = "2025-08-10T21:26:15.73Z" }, - { url = "https://files.pythonhosted.org/packages/5f/42/2dc82330a70aa8e55b6d395b11018045e58d0bb00834502bf11509f79091/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198", size = 1343631, upload-time = "2025-08-10T21:26:17.045Z" }, - { url = "https://files.pythonhosted.org/packages/22/fd/f4c67a6ed1aab149ec5a8a401c323cee7a1cbe364381bb6c9c0d564e0e20/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d", size = 2224963, upload-time = "2025-08-10T21:26:18.737Z" }, - { url = "https://files.pythonhosted.org/packages/45/aa/76720bd4cb3713314677d9ec94dcc21ced3f1baf4830adde5bb9b2430a5f/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab", size = 2321295, upload-time = "2025-08-10T21:26:20.11Z" }, - { url = "https://files.pythonhosted.org/packages/80/19/d3ec0d9ab711242f56ae0dc2fc5d70e298bb4a1f9dfab44c027668c673a1/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2", size = 2487987, upload-time = "2025-08-10T21:26:21.49Z" }, - { url = "https://files.pythonhosted.org/packages/39/e9/61e4813b2c97e86b6fdbd4dd824bf72d28bcd8d4849b8084a357bc0dd64d/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145", size = 2291817, upload-time = "2025-08-10T21:26:22.812Z" }, - { url = "https://files.pythonhosted.org/packages/a0/41/85d82b0291db7504da3c2defe35c9a8a5c9803a730f297bd823d11d5fb77/kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54", size = 73895, upload-time = "2025-08-10T21:26:24.37Z" }, - { url = "https://files.pythonhosted.org/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992, upload-time = "2025-08-10T21:26:25.732Z" }, - { url = "https://files.pythonhosted.org/packages/31/c1/c2686cda909742ab66c7388e9a1a8521a59eb89f8bcfbee28fc980d07e24/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8", size = 123681, upload-time = "2025-08-10T21:26:26.725Z" }, - { url = "https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2", size = 66464, upload-time = "2025-08-10T21:26:27.733Z" }, - { url = "https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f", size = 64961, upload-time = "2025-08-10T21:26:28.729Z" }, - { url = "https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098", size = 1474607, upload-time = "2025-08-10T21:26:29.798Z" }, - { url = "https://files.pythonhosted.org/packages/d9/28/aac26d4c882f14de59041636292bc838db8961373825df23b8eeb807e198/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed", size = 1276546, upload-time = "2025-08-10T21:26:31.401Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ad/8bfc1c93d4cc565e5069162f610ba2f48ff39b7de4b5b8d93f69f30c4bed/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525", size = 1294482, upload-time = "2025-08-10T21:26:32.721Z" }, - { url = "https://files.pythonhosted.org/packages/da/f1/6aca55ff798901d8ce403206d00e033191f63d82dd708a186e0ed2067e9c/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78", size = 1343720, upload-time = "2025-08-10T21:26:34.032Z" }, - { url = "https://files.pythonhosted.org/packages/d1/91/eed031876c595c81d90d0f6fc681ece250e14bf6998c3d7c419466b523b7/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b", size = 2224907, upload-time = "2025-08-10T21:26:35.824Z" }, - { url = "https://files.pythonhosted.org/packages/e9/ec/4d1925f2e49617b9cca9c34bfa11adefad49d00db038e692a559454dfb2e/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799", size = 2321334, upload-time = "2025-08-10T21:26:37.534Z" }, - { url = "https://files.pythonhosted.org/packages/43/cb/450cd4499356f68802750c6ddc18647b8ea01ffa28f50d20598e0befe6e9/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3", size = 2488313, upload-time = "2025-08-10T21:26:39.191Z" }, - { url = "https://files.pythonhosted.org/packages/71/67/fc76242bd99f885651128a5d4fa6083e5524694b7c88b489b1b55fdc491d/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c", size = 2291970, upload-time = "2025-08-10T21:26:40.828Z" }, - { url = "https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d", size = 73894, upload-time = "2025-08-10T21:26:42.33Z" }, - { url = "https://files.pythonhosted.org/packages/95/38/dce480814d25b99a391abbddadc78f7c117c6da34be68ca8b02d5848b424/kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2", size = 64995, upload-time = "2025-08-10T21:26:43.889Z" }, - { url = "https://files.pythonhosted.org/packages/e2/37/7d218ce5d92dadc5ebdd9070d903e0c7cf7edfe03f179433ac4d13ce659c/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1", size = 126510, upload-time = "2025-08-10T21:26:44.915Z" }, - { url = "https://files.pythonhosted.org/packages/23/b0/e85a2b48233daef4b648fb657ebbb6f8367696a2d9548a00b4ee0eb67803/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1", size = 67903, upload-time = "2025-08-10T21:26:45.934Z" }, - { url = "https://files.pythonhosted.org/packages/44/98/f2425bc0113ad7de24da6bb4dae1343476e95e1d738be7c04d31a5d037fd/kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11", size = 66402, upload-time = "2025-08-10T21:26:47.101Z" }, - { url = "https://files.pythonhosted.org/packages/98/d8/594657886df9f34c4177cc353cc28ca7e6e5eb562d37ccc233bff43bbe2a/kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c", size = 1582135, upload-time = "2025-08-10T21:26:48.665Z" }, - { url = "https://files.pythonhosted.org/packages/5c/c6/38a115b7170f8b306fc929e166340c24958347308ea3012c2b44e7e295db/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197", size = 1389409, upload-time = "2025-08-10T21:26:50.335Z" }, - { url = "https://files.pythonhosted.org/packages/bf/3b/e04883dace81f24a568bcee6eb3001da4ba05114afa622ec9b6fafdc1f5e/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c", size = 1401763, upload-time = "2025-08-10T21:26:51.867Z" }, - { url = "https://files.pythonhosted.org/packages/9f/80/20ace48e33408947af49d7d15c341eaee69e4e0304aab4b7660e234d6288/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185", size = 1453643, upload-time = "2025-08-10T21:26:53.592Z" }, - { url = "https://files.pythonhosted.org/packages/64/31/6ce4380a4cd1f515bdda976a1e90e547ccd47b67a1546d63884463c92ca9/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748", size = 2330818, upload-time = "2025-08-10T21:26:55.051Z" }, - { url = "https://files.pythonhosted.org/packages/fa/e9/3f3fcba3bcc7432c795b82646306e822f3fd74df0ee81f0fa067a1f95668/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64", size = 2419963, upload-time = "2025-08-10T21:26:56.421Z" }, - { url = "https://files.pythonhosted.org/packages/99/43/7320c50e4133575c66e9f7dadead35ab22d7c012a3b09bb35647792b2a6d/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff", size = 2594639, upload-time = "2025-08-10T21:26:57.882Z" }, - { url = "https://files.pythonhosted.org/packages/65/d6/17ae4a270d4a987ef8a385b906d2bdfc9fce502d6dc0d3aea865b47f548c/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07", size = 2391741, upload-time = "2025-08-10T21:26:59.237Z" }, - { url = "https://files.pythonhosted.org/packages/2a/8f/8f6f491d595a9e5912971f3f863d81baddccc8a4d0c3749d6a0dd9ffc9df/kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c", size = 68646, upload-time = "2025-08-10T21:27:00.52Z" }, - { url = "https://files.pythonhosted.org/packages/6b/32/6cc0fbc9c54d06c2969faa9c1d29f5751a2e51809dd55c69055e62d9b426/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386", size = 123806, upload-time = "2025-08-10T21:27:01.537Z" }, - { url = "https://files.pythonhosted.org/packages/b2/dd/2bfb1d4a4823d92e8cbb420fe024b8d2167f72079b3bb941207c42570bdf/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552", size = 66605, upload-time = "2025-08-10T21:27:03.335Z" }, - { url = "https://files.pythonhosted.org/packages/f7/69/00aafdb4e4509c2ca6064646cba9cd4b37933898f426756adb2cb92ebbed/kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3", size = 64925, upload-time = "2025-08-10T21:27:04.339Z" }, - { url = "https://files.pythonhosted.org/packages/43/dc/51acc6791aa14e5cb6d8a2e28cefb0dc2886d8862795449d021334c0df20/kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58", size = 1472414, upload-time = "2025-08-10T21:27:05.437Z" }, - { url = "https://files.pythonhosted.org/packages/3d/bb/93fa64a81db304ac8a246f834d5094fae4b13baf53c839d6bb6e81177129/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4", size = 1281272, upload-time = "2025-08-10T21:27:07.063Z" }, - { url = "https://files.pythonhosted.org/packages/70/e6/6df102916960fb8d05069d4bd92d6d9a8202d5a3e2444494e7cd50f65b7a/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df", size = 1298578, upload-time = "2025-08-10T21:27:08.452Z" }, - { url = "https://files.pythonhosted.org/packages/7c/47/e142aaa612f5343736b087864dbaebc53ea8831453fb47e7521fa8658f30/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6", size = 1345607, upload-time = "2025-08-10T21:27:10.125Z" }, - { url = "https://files.pythonhosted.org/packages/54/89/d641a746194a0f4d1a3670fb900d0dbaa786fb98341056814bc3f058fa52/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5", size = 2230150, upload-time = "2025-08-10T21:27:11.484Z" }, - { url = "https://files.pythonhosted.org/packages/aa/6b/5ee1207198febdf16ac11f78c5ae40861b809cbe0e6d2a8d5b0b3044b199/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf", size = 2325979, upload-time = "2025-08-10T21:27:12.917Z" }, - { url = "https://files.pythonhosted.org/packages/fc/ff/b269eefd90f4ae14dcc74973d5a0f6d28d3b9bb1afd8c0340513afe6b39a/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5", size = 2491456, upload-time = "2025-08-10T21:27:14.353Z" }, - { url = "https://files.pythonhosted.org/packages/fc/d4/10303190bd4d30de547534601e259a4fbf014eed94aae3e5521129215086/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce", size = 2294621, upload-time = "2025-08-10T21:27:15.808Z" }, - { url = "https://files.pythonhosted.org/packages/28/e0/a9a90416fce5c0be25742729c2ea52105d62eda6c4be4d803c2a7be1fa50/kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7", size = 75417, upload-time = "2025-08-10T21:27:17.436Z" }, - { url = "https://files.pythonhosted.org/packages/1f/10/6949958215b7a9a264299a7db195564e87900f709db9245e4ebdd3c70779/kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c", size = 66582, upload-time = "2025-08-10T21:27:18.436Z" }, - { url = "https://files.pythonhosted.org/packages/ec/79/60e53067903d3bc5469b369fe0dfc6b3482e2133e85dae9daa9527535991/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548", size = 126514, upload-time = "2025-08-10T21:27:19.465Z" }, - { url = "https://files.pythonhosted.org/packages/25/d1/4843d3e8d46b072c12a38c97c57fab4608d36e13fe47d47ee96b4d61ba6f/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d", size = 67905, upload-time = "2025-08-10T21:27:20.51Z" }, - { url = "https://files.pythonhosted.org/packages/8c/ae/29ffcbd239aea8b93108de1278271ae764dfc0d803a5693914975f200596/kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c", size = 66399, upload-time = "2025-08-10T21:27:21.496Z" }, - { url = "https://files.pythonhosted.org/packages/a1/ae/d7ba902aa604152c2ceba5d352d7b62106bedbccc8e95c3934d94472bfa3/kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122", size = 1582197, upload-time = "2025-08-10T21:27:22.604Z" }, - { url = "https://files.pythonhosted.org/packages/f2/41/27c70d427eddb8bc7e4f16420a20fefc6f480312122a59a959fdfe0445ad/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64", size = 1390125, upload-time = "2025-08-10T21:27:24.036Z" }, - { url = "https://files.pythonhosted.org/packages/41/42/b3799a12bafc76d962ad69083f8b43b12bf4fe78b097b12e105d75c9b8f1/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134", size = 1402612, upload-time = "2025-08-10T21:27:25.773Z" }, - { url = "https://files.pythonhosted.org/packages/d2/b5/a210ea073ea1cfaca1bb5c55a62307d8252f531beb364e18aa1e0888b5a0/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370", size = 1453990, upload-time = "2025-08-10T21:27:27.089Z" }, - { url = "https://files.pythonhosted.org/packages/5f/ce/a829eb8c033e977d7ea03ed32fb3c1781b4fa0433fbadfff29e39c676f32/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21", size = 2331601, upload-time = "2025-08-10T21:27:29.343Z" }, - { url = "https://files.pythonhosted.org/packages/e0/4b/b5e97eb142eb9cd0072dacfcdcd31b1c66dc7352b0f7c7255d339c0edf00/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a", size = 2422041, upload-time = "2025-08-10T21:27:30.754Z" }, - { url = "https://files.pythonhosted.org/packages/40/be/8eb4cd53e1b85ba4edc3a9321666f12b83113a178845593307a3e7891f44/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f", size = 2594897, upload-time = "2025-08-10T21:27:32.803Z" }, - { url = "https://files.pythonhosted.org/packages/99/dd/841e9a66c4715477ea0abc78da039832fbb09dac5c35c58dc4c41a407b8a/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369", size = 2391835, upload-time = "2025-08-10T21:27:34.23Z" }, - { url = "https://files.pythonhosted.org/packages/0c/28/4b2e5c47a0da96896fdfdb006340ade064afa1e63675d01ea5ac222b6d52/kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891", size = 79988, upload-time = "2025-08-10T21:27:35.587Z" }, - { url = "https://files.pythonhosted.org/packages/80/be/3578e8afd18c88cdf9cb4cffde75a96d2be38c5a903f1ed0ceec061bd09e/kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32", size = 70260, upload-time = "2025-08-10T21:27:36.606Z" }, - { url = "https://files.pythonhosted.org/packages/a3/0f/36d89194b5a32c054ce93e586d4049b6c2c22887b0eb229c61c68afd3078/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5", size = 60104, upload-time = "2025-08-10T21:27:43.287Z" }, - { url = "https://files.pythonhosted.org/packages/52/ba/4ed75f59e4658fd21fe7dde1fee0ac397c678ec3befba3fe6482d987af87/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa", size = 58592, upload-time = "2025-08-10T21:27:44.314Z" }, - { url = "https://files.pythonhosted.org/packages/33/01/a8ea7c5ea32a9b45ceeaee051a04c8ed4320f5add3c51bfa20879b765b70/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2", size = 80281, upload-time = "2025-08-10T21:27:45.369Z" }, - { url = "https://files.pythonhosted.org/packages/da/e3/dbd2ecdce306f1d07a1aaf324817ee993aab7aee9db47ceac757deabafbe/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f", size = 78009, upload-time = "2025-08-10T21:27:46.376Z" }, - { url = "https://files.pythonhosted.org/packages/da/e9/0d4add7873a73e462aeb45c036a2dead2562b825aa46ba326727b3f31016/kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1", size = 73929, upload-time = "2025-08-10T21:27:48.236Z" }, +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/67/9c61eccb13f0bdca9307614e782fec49ffdde0f7a2314935d489fa93cd9c/kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a", size = 103482, upload-time = "2026-03-09T13:15:53.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/dd/a495a9c104be1c476f0386e714252caf2b7eca883915422a64c50b88c6f5/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9eed0f7edbb274413b6ee781cca50541c8c0facd3d6fd289779e494340a2b85c", size = 122798, upload-time = "2026-03-09T13:12:58.963Z" }, + { url = "https://files.pythonhosted.org/packages/11/60/37b4047a2af0cf5ef6d8b4b26e91829ae6fc6a2d1f74524bcb0e7cd28a32/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c4923e404d6bcd91b6779c009542e5647fef32e4a5d75e115e3bbac6f2335eb", size = 66216, upload-time = "2026-03-09T13:13:00.155Z" }, + { url = "https://files.pythonhosted.org/packages/0a/aa/510dc933d87767584abfe03efa445889996c70c2990f6f87c3ebaa0a18c5/kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0df54df7e686afa55e6f21fb86195224a6d9beb71d637e8d7920c95cf0f89aac", size = 63911, upload-time = "2026-03-09T13:13:01.671Z" }, + { url = "https://files.pythonhosted.org/packages/80/46/bddc13df6c2a40741e0cc7865bb1c9ed4796b6760bd04ce5fae3928ef917/kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2517e24d7315eb51c10664cdb865195df38ab74456c677df67bb47f12d088a27", size = 1438209, upload-time = "2026-03-09T13:13:03.385Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d6/76621246f5165e5372f02f5e6f3f48ea336a8f9e96e43997d45b240ed8cd/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff710414307fefa903e0d9bdf300972f892c23477829f49504e59834f4195398", size = 1248888, upload-time = "2026-03-09T13:13:05.231Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c1/31559ec6fb39a5b48035ce29bb63ade628f321785f38c384dee3e2c08bc1/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6176c1811d9d5a04fa391c490cc44f451e240697a16977f11c6f722efb9041db", size = 1266304, upload-time = "2026-03-09T13:13:06.743Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ef/1cb8276f2d29cc6a41e0a042f27946ca347d3a4a75acf85d0a16aa6dcc82/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50847dca5d197fcbd389c805aa1a1cf32f25d2e7273dc47ab181a517666b68cc", size = 1319650, upload-time = "2026-03-09T13:13:08.607Z" }, + { url = "https://files.pythonhosted.org/packages/4c/e4/5ba3cecd7ce6236ae4a80f67e5d5531287337d0e1f076ca87a5abe4cd5d0/kiwisolver-1.5.0-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:01808c6d15f4c3e8559595d6d1fe6411c68e4a3822b4b9972b44473b24f4e679", size = 970949, upload-time = "2026-03-09T13:13:10.299Z" }, + { url = "https://files.pythonhosted.org/packages/5a/69/dc61f7ae9a2f071f26004ced87f078235b5507ab6e5acd78f40365655034/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1f9f4121ec58628c96baa3de1a55a4e3a333c5102c8e94b64e23bf7b2083309", size = 2199125, upload-time = "2026-03-09T13:13:11.841Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7b/abbe0f1b5afa85f8d084b73e90e5f801c0939eba16ac2e49af7c61a6c28d/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b7d335370ae48a780c6e6a6bbfa97342f563744c39c35562f3f367665f5c1de2", size = 2293783, upload-time = "2026-03-09T13:13:14.399Z" }, + { url = "https://files.pythonhosted.org/packages/8a/80/5908ae149d96d81580d604c7f8aefd0e98f4fd728cf172f477e9f2a81744/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:800ee55980c18545af444d93fdd60c56b580db5cc54867d8cbf8a1dc0829938c", size = 1960726, upload-time = "2026-03-09T13:13:16.047Z" }, + { url = "https://files.pythonhosted.org/packages/84/08/a78cb776f8c085b7143142ce479859cfec086bd09ee638a317040b6ef420/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c438f6ca858697c9ab67eb28246c92508af972e114cac34e57a6d4ba17a3ac08", size = 2464738, upload-time = "2026-03-09T13:13:17.897Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e1/65584da5356ed6cb12c63791a10b208860ac40a83de165cb6a6751a686e3/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8c63c91f95173f9c2a67c7c526b2cea976828a0e7fced9cdcead2802dc10f8a4", size = 2270718, upload-time = "2026-03-09T13:13:19.421Z" }, + { url = "https://files.pythonhosted.org/packages/be/6c/28f17390b62b8f2f520e2915095b3c94d88681ecf0041e75389d9667f202/kiwisolver-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:beb7f344487cdcb9e1efe4b7a29681b74d34c08f0043a327a74da852a6749e7b", size = 73480, upload-time = "2026-03-09T13:13:20.818Z" }, + { url = "https://files.pythonhosted.org/packages/d8/0e/2ee5debc4f77a625778fec5501ff3e8036fe361b7ee28ae402a485bb9694/kiwisolver-1.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:ad4ae4ffd1ee9cd11357b4c66b612da9888f4f4daf2f36995eda64bd45370cac", size = 64930, upload-time = "2026-03-09T13:13:21.997Z" }, + { url = "https://files.pythonhosted.org/packages/4d/b2/818b74ebea34dabe6d0c51cb1c572e046730e64844da6ed646d5298c40ce/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9", size = 123158, upload-time = "2026-03-09T13:13:23.127Z" }, + { url = "https://files.pythonhosted.org/packages/bf/d9/405320f8077e8e1c5c4bd6adc45e1e6edf6d727b6da7f2e2533cf58bff71/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588", size = 66388, upload-time = "2026-03-09T13:13:24.765Z" }, + { url = "https://files.pythonhosted.org/packages/99/9f/795fedf35634f746151ca8839d05681ceb6287fbed6cc1c9bf235f7887c2/kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819", size = 64068, upload-time = "2026-03-09T13:13:25.878Z" }, + { url = "https://files.pythonhosted.org/packages/c4/13/680c54afe3e65767bed7ec1a15571e1a2f1257128733851ade24abcefbcc/kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f", size = 1477934, upload-time = "2026-03-09T13:13:27.166Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2f/cebfcdb60fd6a9b0f6b47a9337198bcbad6fbe15e68189b7011fd914911f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf", size = 1278537, upload-time = "2026-03-09T13:13:28.707Z" }, + { url = "https://files.pythonhosted.org/packages/f2/0d/9b782923aada3fafb1d6b84e13121954515c669b18af0c26e7d21f579855/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d", size = 1296685, upload-time = "2026-03-09T13:13:30.528Z" }, + { url = "https://files.pythonhosted.org/packages/27/70/83241b6634b04fe44e892688d5208332bde130f38e610c0418f9ede47ded/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083", size = 1346024, upload-time = "2026-03-09T13:13:32.818Z" }, + { url = "https://files.pythonhosted.org/packages/e4/db/30ed226fb271ae1a6431fc0fe0edffb2efe23cadb01e798caeb9f2ceae8f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6", size = 987241, upload-time = "2026-03-09T13:13:34.435Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bd/c314595208e4c9587652d50959ead9e461995389664e490f4dce7ff0f782/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1", size = 2227742, upload-time = "2026-03-09T13:13:36.4Z" }, + { url = "https://files.pythonhosted.org/packages/c1/43/0499cec932d935229b5543d073c2b87c9c22846aab48881e9d8d6e742a2d/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0", size = 2323966, upload-time = "2026-03-09T13:13:38.204Z" }, + { url = "https://files.pythonhosted.org/packages/3d/6f/79b0d760907965acfd9d61826a3d41f8f093c538f55cd2633d3f0db269f6/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15", size = 1977417, upload-time = "2026-03-09T13:13:39.966Z" }, + { url = "https://files.pythonhosted.org/packages/ab/31/01d0537c41cb75a551a438c3c7a80d0c60d60b81f694dac83dd436aec0d0/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314", size = 2491238, upload-time = "2026-03-09T13:13:41.698Z" }, + { url = "https://files.pythonhosted.org/packages/e4/34/8aefdd0be9cfd00a44509251ba864f5caf2991e36772e61c408007e7f417/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9", size = 2294947, upload-time = "2026-03-09T13:13:43.343Z" }, + { url = "https://files.pythonhosted.org/packages/ad/cf/0348374369ca588f8fe9c338fae49fa4e16eeb10ffb3d012f23a54578a9e/kiwisolver-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384", size = 73569, upload-time = "2026-03-09T13:13:45.792Z" }, + { url = "https://files.pythonhosted.org/packages/28/26/192b26196e2316e2bd29deef67e37cdf9870d9af8e085e521afff0fed526/kiwisolver-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7", size = 64997, upload-time = "2026-03-09T13:13:46.878Z" }, + { url = "https://files.pythonhosted.org/packages/9d/69/024d6711d5ba575aa65d5538042e99964104e97fa153a9f10bc369182bc2/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fd40bb9cd0891c4c3cb1ddf83f8bbfa15731a248fdc8162669405451e2724b09", size = 123166, upload-time = "2026-03-09T13:13:48.032Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/adbb40df306f587054a348831220812b9b1d787aff714cfbc8556e38fccd/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0e1403fd7c26d77c1f03e096dc58a5c726503fa0db0456678b8668f76f521e3", size = 66395, upload-time = "2026-03-09T13:13:49.365Z" }, + { url = "https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd", size = 64065, upload-time = "2026-03-09T13:13:50.562Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3", size = 1477903, upload-time = "2026-03-09T13:13:52.084Z" }, + { url = "https://files.pythonhosted.org/packages/18/d8/55638d89ffd27799d5cc3d8aa28e12f4ce7a64d67b285114dbedc8ea4136/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c50b89ffd3e1a911c69a1dd3de7173c0cd10b130f56222e57898683841e4f96", size = 1278751, upload-time = "2026-03-09T13:13:54.673Z" }, + { url = "https://files.pythonhosted.org/packages/b8/97/b4c8d0d18421ecceba20ad8701358453b88e32414e6f6950b5a4bad54e65/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4db576bb8c3ef9365f8b40fe0f671644de6736ae2c27a2c62d7d8a1b4329f099", size = 1296793, upload-time = "2026-03-09T13:13:56.287Z" }, + { url = "https://files.pythonhosted.org/packages/c4/10/f862f94b6389d8957448ec9df59450b81bec4abb318805375c401a1e6892/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b85aad90cea8ac6797a53b5d5f2e967334fa4d1149f031c4537569972596cb8", size = 1346041, upload-time = "2026-03-09T13:13:58.269Z" }, + { url = "https://files.pythonhosted.org/packages/a3/6a/f1650af35821eaf09de398ec0bc2aefc8f211f0cda50204c9f1673741ba9/kiwisolver-1.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:d36ca54cb4c6c4686f7cbb7b817f66f5911c12ddb519450bbe86707155028f87", size = 987292, upload-time = "2026-03-09T13:13:59.871Z" }, + { url = "https://files.pythonhosted.org/packages/de/19/d7fb82984b9238115fe629c915007be608ebd23dc8629703d917dbfaffd4/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:38f4a703656f493b0ad185211ccfca7f0386120f022066b018eb5296d8613e23", size = 2227865, upload-time = "2026-03-09T13:14:01.401Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b9/46b7f386589fd222dac9e9de9c956ce5bcefe2ee73b4e79891381dda8654/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ac2360e93cb41be81121755c6462cff3beaa9967188c866e5fce5cf13170859", size = 2324369, upload-time = "2026-03-09T13:14:02.972Z" }, + { url = "https://files.pythonhosted.org/packages/92/8b/95e237cf3d9c642960153c769ddcbe278f182c8affb20cecc1cc983e7cc5/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c95cab08d1965db3d84a121f1c7ce7479bdd4072c9b3dafd8fecce48a2e6b902", size = 1977989, upload-time = "2026-03-09T13:14:04.503Z" }, + { url = "https://files.pythonhosted.org/packages/1b/95/980c9df53501892784997820136c01f62bc1865e31b82b9560f980c0e649/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc20894c3d21194d8041a28b65622d5b86db786da6e3cfe73f0c762951a61167", size = 2491645, upload-time = "2026-03-09T13:14:06.106Z" }, + { url = "https://files.pythonhosted.org/packages/cb/32/900647fd0840abebe1561792c6b31e6a7c0e278fc3973d30572a965ca14c/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a32f72973f0f950c1920475d5c5ea3d971b81b6f0ec53b8d0a956cc965f22e0", size = 2295237, upload-time = "2026-03-09T13:14:08.891Z" }, + { url = "https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276", size = 73573, upload-time = "2026-03-09T13:14:12.327Z" }, + { url = "https://files.pythonhosted.org/packages/4d/d2/64be2e429eb4fca7f7e1c52a91b12663aeaf25de3895e5cca0f47ef2a8d0/kiwisolver-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa8eb9ecdb7efb0b226acec134e0d709e87a909fa4971a54c0c4f6e88635484c", size = 64998, upload-time = "2026-03-09T13:14:13.469Z" }, + { url = "https://files.pythonhosted.org/packages/b0/69/ce68dd0c85755ae2de490bf015b62f2cea5f6b14ff00a463f9d0774449ff/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db485b3847d182b908b483b2ed133c66d88d49cacf98fd278fadafe11b4478d1", size = 125700, upload-time = "2026-03-09T13:14:14.636Z" }, + { url = "https://files.pythonhosted.org/packages/74/aa/937aac021cf9d4349990d47eb319309a51355ed1dbdc9c077cdc9224cb11/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:be12f931839a3bdfe28b584db0e640a65a8bcbc24560ae3fdb025a449b3d754e", size = 67537, upload-time = "2026-03-09T13:14:15.808Z" }, + { url = "https://files.pythonhosted.org/packages/ee/20/3a87fbece2c40ad0f6f0aefa93542559159c5f99831d596050e8afae7a9f/kiwisolver-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:16b85d37c2cbb3253226d26e64663f755d88a03439a9c47df6246b35defbdfb7", size = 65514, upload-time = "2026-03-09T13:14:18.035Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7f/f943879cda9007c45e1f7dba216d705c3a18d6b35830e488b6c6a4e7cdf0/kiwisolver-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4432b835675f0ea7414aab3d37d119f7226d24869b7a829caeab49ebda407b0c", size = 1584848, upload-time = "2026-03-09T13:14:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/37/f8/4d4f85cc1870c127c88d950913370dd76138482161cd07eabbc450deff01/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b0feb50971481a2cc44d94e88bdb02cdd497618252ae226b8eb1201b957e368", size = 1391542, upload-time = "2026-03-09T13:14:21.54Z" }, + { url = "https://files.pythonhosted.org/packages/04/0b/65dd2916c84d252b244bd405303220f729e7c17c9d7d33dca6feeff9ffc4/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56fa888f10d0f367155e76ce849fa1166fc9730d13bd2d65a2aa13b6f5424489", size = 1404447, upload-time = "2026-03-09T13:14:23.205Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/2606a373247babce9b1d056c03a04b65f3cf5290a8eac5d7bdead0a17e21/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:940dda65d5e764406b9fb92761cbf462e4e63f712ab60ed98f70552e496f3bf1", size = 1455918, upload-time = "2026-03-09T13:14:24.74Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d1/c6078b5756670658e9192a2ef11e939c92918833d2745f85cd14a6004bdf/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:89fc958c702ee9a745e4700378f5d23fddbc46ff89e8fdbf5395c24d5c1452a3", size = 1072856, upload-time = "2026-03-09T13:14:26.597Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c8/7def6ddf16eb2b3741d8b172bdaa9af882b03c78e9b0772975408801fa63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9027d773c4ff81487181a925945743413f6069634d0b122d0b37684ccf4f1e18", size = 2333580, upload-time = "2026-03-09T13:14:28.237Z" }, + { url = "https://files.pythonhosted.org/packages/9e/87/2ac1fce0eb1e616fcd3c35caa23e665e9b1948bb984f4764790924594128/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5b233ea3e165e43e35dba1d2b8ecc21cf070b45b65ae17dd2747d2713d942021", size = 2423018, upload-time = "2026-03-09T13:14:30.018Z" }, + { url = "https://files.pythonhosted.org/packages/67/13/c6700ccc6cc218716bfcda4935e4b2997039869b4ad8a94f364c5a3b8e63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ce9bf03dad3b46408c08649c6fbd6ca28a9fce0eb32fdfffa6775a13103b5310", size = 2062804, upload-time = "2026-03-09T13:14:32.888Z" }, + { url = "https://files.pythonhosted.org/packages/1b/bd/877056304626943ff0f1f44c08f584300c199b887cb3176cd7e34f1515f1/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:fc4d3f1fb9ca0ae9f97b095963bc6326f1dbfd3779d6679a1e016b9baaa153d3", size = 2597482, upload-time = "2026-03-09T13:14:34.971Z" }, + { url = "https://files.pythonhosted.org/packages/75/19/c60626c47bf0f8ac5dcf72c6c98e266d714f2fbbfd50cf6dab5ede3aaa50/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f443b4825c50a51ee68585522ab4a1d1257fac65896f282b4c6763337ac9f5d2", size = 2394328, upload-time = "2026-03-09T13:14:36.816Z" }, + { url = "https://files.pythonhosted.org/packages/47/84/6a6d5e5bb8273756c27b7d810d47f7ef2f1f9b9fd23c9ee9a3f8c75c9cef/kiwisolver-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:893ff3a711d1b515ba9da14ee090519bad4610ed1962fbe298a434e8c5f8db53", size = 68410, upload-time = "2026-03-09T13:14:38.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/060f45052f2a01ad5762c8fdecd6d7a752b43400dc29ff75cd47225a40fd/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8df31fe574b8b3993cc61764f40941111b25c2d9fea13d3ce24a49907cd2d615", size = 123231, upload-time = "2026-03-09T13:14:41.323Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a7/78da680eadd06ff35edef6ef68a1ad273bad3e2a0936c9a885103230aece/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1d49a49ac4cbfb7c1375301cd1ec90169dfeae55ff84710d782260ce77a75a02", size = 66489, upload-time = "2026-03-09T13:14:42.534Z" }, + { url = "https://files.pythonhosted.org/packages/49/b2/97980f3ad4fae37dd7fe31626e2bf75fbf8bdf5d303950ec1fab39a12da8/kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0cbe94b69b819209a62cb27bdfa5dc2a8977d8de2f89dfd97ba4f53ed3af754e", size = 64063, upload-time = "2026-03-09T13:14:44.759Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f9/b06c934a6aa8bc91f566bd2a214fd04c30506c2d9e2b6b171953216a65b6/kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80aa065ffd378ff784822a6d7c3212f2d5f5e9c3589614b5c228b311fd3063ac", size = 1475913, upload-time = "2026-03-09T13:14:46.247Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f0/f768ae564a710135630672981231320bc403cf9152b5596ec5289de0f106/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e7f886f47ab881692f278ae901039a234e4025a68e6dfab514263a0b1c4ae05", size = 1282782, upload-time = "2026-03-09T13:14:48.458Z" }, + { url = "https://files.pythonhosted.org/packages/e2/9f/1de7aad00697325f05238a5f2eafbd487fb637cc27a558b5367a5f37fb7f/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5060731cc3ed12ca3a8b57acd4aeca5bbc2f49216dd0bec1650a1acd89486bcd", size = 1300815, upload-time = "2026-03-09T13:14:50.721Z" }, + { url = "https://files.pythonhosted.org/packages/5a/c2/297f25141d2e468e0ce7f7a7b92e0cf8918143a0cbd3422c1ad627e85a06/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a4aa69609f40fce3cbc3f87b2061f042eee32f94b8f11db707b66a26461591a", size = 1347925, upload-time = "2026-03-09T13:14:52.304Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d3/f4c73a02eb41520c47610207b21afa8cdd18fdbf64ffd94674ae21c4812d/kiwisolver-1.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:d168fda2dbff7b9b5f38e693182d792a938c31db4dac3a80a4888de603c99554", size = 991322, upload-time = "2026-03-09T13:14:54.637Z" }, + { url = "https://files.pythonhosted.org/packages/7b/46/d3f2efef7732fcda98d22bf4ad5d3d71d545167a852ca710a494f4c15343/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:413b820229730d358efd838ecbab79902fe97094565fdc80ddb6b0a18c18a581", size = 2232857, upload-time = "2026-03-09T13:14:56.471Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ec/2d9756bf2b6d26ae4349b8d3662fb3993f16d80c1f971c179ce862b9dbae/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5124d1ea754509b09e53738ec185584cc609aae4a3b510aaf4ed6aa047ef9303", size = 2329376, upload-time = "2026-03-09T13:14:58.072Z" }, + { url = "https://files.pythonhosted.org/packages/8f/9f/876a0a0f2260f1bde92e002b3019a5fabc35e0939c7d945e0fa66185eb20/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e4415a8db000bf49a6dd1c478bf70062eaacff0f462b92b0ba68791a905861f9", size = 1982549, upload-time = "2026-03-09T13:14:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4f/ba3624dfac23a64d54ac4179832860cb537c1b0af06024936e82ca4154a0/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d618fd27420381a4f6044faa71f46d8bfd911bd077c555f7138ed88729bfbe79", size = 2494680, upload-time = "2026-03-09T13:15:01.364Z" }, + { url = "https://files.pythonhosted.org/packages/39/b7/97716b190ab98911b20d10bf92eca469121ec483b8ce0edd314f51bc85af/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5092eb5b1172947f57d6ea7d89b2f29650414e4293c47707eb499ec07a0ac796", size = 2297905, upload-time = "2026-03-09T13:15:03.925Z" }, + { url = "https://files.pythonhosted.org/packages/a3/36/4e551e8aa55c9188bca9abb5096805edbf7431072b76e2298e34fd3a3008/kiwisolver-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e", size = 75086, upload-time = "2026-03-09T13:15:07.775Z" }, + { url = "https://files.pythonhosted.org/packages/70/15/9b90f7df0e31a003c71649cf66ef61c3c1b862f48c81007fa2383c8bd8d7/kiwisolver-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df", size = 66577, upload-time = "2026-03-09T13:15:09.139Z" }, + { url = "https://files.pythonhosted.org/packages/17/01/7dc8c5443ff42b38e72731643ed7cf1ed9bf01691ae5cdca98501999ed83/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d1ffeb80b5676463d7a7d56acbe8e37a20ce725570e09549fe738e02ca6b7e1e", size = 125794, upload-time = "2026-03-09T13:15:10.525Z" }, + { url = "https://files.pythonhosted.org/packages/46/8a/b4ebe46ebaac6a303417fab10c2e165c557ddaff558f9699d302b256bc53/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc4d8e252f532ab46a1de9349e2d27b91fce46736a9eedaa37beaca66f574ed4", size = 67646, upload-time = "2026-03-09T13:15:12.016Z" }, + { url = "https://files.pythonhosted.org/packages/60/35/10a844afc5f19d6f567359bf4789e26661755a2f36200d5d1ed8ad0126e5/kiwisolver-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6783e069732715ad0c3ce96dbf21dbc2235ab0593f2baf6338101f70371f4028", size = 65511, upload-time = "2026-03-09T13:15:13.311Z" }, + { url = "https://files.pythonhosted.org/packages/f8/8a/685b297052dd041dcebce8e8787b58923b6e78acc6115a0dc9189011c44b/kiwisolver-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7c4c09a490dc4d4a7f8cbee56c606a320f9dc28cf92a7157a39d1ce7676a657", size = 1584858, upload-time = "2026-03-09T13:15:15.103Z" }, + { url = "https://files.pythonhosted.org/packages/9e/80/04865e3d4638ac5bddec28908916df4a3075b8c6cc101786a96803188b96/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a075bd7bd19c70cf67c8badfa36cf7c5d8de3c9ddb8420c51e10d9c50e94920", size = 1392539, upload-time = "2026-03-09T13:15:16.661Z" }, + { url = "https://files.pythonhosted.org/packages/ba/01/77a19cacc0893fa13fafa46d1bba06fb4dc2360b3292baf4b56d8e067b24/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bdd3e53429ff02aa319ba59dfe4ceeec345bf46cf180ec2cf6fd5b942e7975e9", size = 1405310, upload-time = "2026-03-09T13:15:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/53/39/bcaf5d0cca50e604cfa9b4e3ae1d64b50ca1ae5b754122396084599ef903/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cdcb35dc9d807259c981a85531048ede628eabcffb3239adf3d17463518992d", size = 1456244, upload-time = "2026-03-09T13:15:20.444Z" }, + { url = "https://files.pythonhosted.org/packages/d0/7a/72c187abc6975f6978c3e39b7cf67aeb8b3c0a8f9790aa7fd412855e9e1f/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:70d593af6a6ca332d1df73d519fddb5148edb15cd90d5f0155e3746a6d4fcc65", size = 1073154, upload-time = "2026-03-09T13:15:22.039Z" }, + { url = "https://files.pythonhosted.org/packages/c7/ca/cf5b25783ebbd59143b4371ed0c8428a278abe68d6d0104b01865b1bbd0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:377815a8616074cabbf3f53354e1d040c35815a134e01d7614b7692e4bf8acfa", size = 2334377, upload-time = "2026-03-09T13:15:23.741Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e5/b1f492adc516796e88751282276745340e2a72dcd0d36cf7173e0daf3210/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0255a027391d52944eae1dbb5d4cc5903f57092f3674e8e544cdd2622826b3f0", size = 2425288, upload-time = "2026-03-09T13:15:25.789Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e5/9b21fbe91a61b8f409d74a26498706e97a48008bfcd1864373d32a6ba31c/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9", size = 2063158, upload-time = "2026-03-09T13:15:27.63Z" }, + { url = "https://files.pythonhosted.org/packages/b1/02/83f47986138310f95ea95531f851b2a62227c11cbc3e690ae1374fe49f0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e3aafb33aed7479377e5e9a82e9d4bf87063741fc99fc7ae48b0f16e32bdd6f", size = 2597260, upload-time = "2026-03-09T13:15:29.421Z" }, + { url = "https://files.pythonhosted.org/packages/07/18/43a5f24608d8c313dd189cf838c8e68d75b115567c6279de7796197cfb6a/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7a116ae737f0000343218c4edf5bd45893bfeaff0993c0b215d7124c9f77646", size = 2394403, upload-time = "2026-03-09T13:15:31.517Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b5/98222136d839b8afabcaa943b09bd05888c2d36355b7e448550211d1fca4/kiwisolver-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681", size = 79687, upload-time = "2026-03-09T13:15:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/99/a2/ca7dc962848040befed12732dff6acae7fb3c4f6fc4272b3f6c9a30b8713/kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57", size = 70032, upload-time = "2026-03-09T13:15:34.411Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fa/2910df836372d8761bb6eff7d8bdcb1613b5c2e03f260efe7abe34d388a7/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797", size = 130262, upload-time = "2026-03-09T13:15:35.629Z" }, + { url = "https://files.pythonhosted.org/packages/0f/41/c5f71f9f00aabcc71fee8b7475e3f64747282580c2fe748961ba29b18385/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f6764a4ccab3078db14a632420930f6186058750df066b8ea2a7106df91d3203", size = 138036, upload-time = "2026-03-09T13:15:36.894Z" }, + { url = "https://files.pythonhosted.org/packages/fa/06/7399a607f434119c6e1fdc8ec89a8d51ccccadf3341dee4ead6bd14caaf5/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31c13da98624f957b0fb1b5bae5383b2333c2c3f6793d9825dd5ce79b525cb7", size = 194295, upload-time = "2026-03-09T13:15:38.22Z" }, + { url = "https://files.pythonhosted.org/packages/b5/91/53255615acd2a1eaca307ede3c90eb550bae9c94581f8c00081b6b1c8f44/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:1f1489f769582498610e015a8ef2d36f28f505ab3096d0e16b4858a9ec214f57", size = 75987, upload-time = "2026-03-09T13:15:39.65Z" }, + { url = "https://files.pythonhosted.org/packages/e9/eb/5fcbbbf9a0e2c3a35effb88831a483345326bbc3a030a3b5b69aee647f84/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ec4c85dc4b687c7f7f15f553ff26a98bfe8c58f5f7f0ac8905f0ba4c7be60232", size = 59532, upload-time = "2026-03-09T13:15:47.047Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9b/e17104555bb4db148fd52327feea1e96be4b88e8e008b029002c281a21ab/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:12e91c215a96e39f57989c8912ae761286ac5a9584d04030ceb3368a357f017a", size = 57420, upload-time = "2026-03-09T13:15:48.199Z" }, + { url = "https://files.pythonhosted.org/packages/48/44/2b5b95b7aa39fb2d8d9d956e0f3d5d45aef2ae1d942d4c3ffac2f9cfed1a/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be4a51a55833dc29ab5d7503e7bcb3b3af3402d266018137127450005cdfe737", size = 79892, upload-time = "2026-03-09T13:15:49.694Z" }, + { url = "https://files.pythonhosted.org/packages/52/7d/7157f9bba6b455cfb4632ed411e199fc8b8977642c2b12082e1bd9e6d173/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daae526907e262de627d8f70058a0f64acc9e2641c164c99c8f594b34a799a16", size = 77603, upload-time = "2026-03-09T13:15:50.945Z" }, + { url = "https://files.pythonhosted.org/packages/0a/dd/8050c947d435c8d4bc94e3252f4d8bb8a76cfb424f043a8680be637a57f1/kiwisolver-1.5.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:59cd8683f575d96df5bb48f6add94afc055012c29e28124fcae2b63661b9efb1", size = 73558, upload-time = "2026-03-09T13:15:52.112Z" }, ] [[package]] @@ -1574,14 +1691,14 @@ wheels = [ [[package]] name = "lazy-loader" -version = "0.4" +version = "0.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6f/6b/c875b30a1ba490860c93da4cabf479e03f584eba06fe5963f6f6644653d8/lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1", size = 15431, upload-time = "2024-04-05T13:03:12.261Z" } +sdist = { url = "https://files.pythonhosted.org/packages/49/ac/21a1f8aa3777f5658576777ea76bfb124b702c520bbe90edf4ae9915eafa/lazy_loader-0.5.tar.gz", hash = "sha256:717f9179a0dbed357012ddad50a5ad3d5e4d9a0b8712680d4e687f5e6e6ed9b3", size = 15294, upload-time = "2026-03-06T15:45:09.054Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc", size = 12097, upload-time = "2024-04-05T13:03:10.514Z" }, + { url = "https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl", hash = "sha256:ab0ea149e9c554d4ffeeb21105ac60bed7f3b4fd69b1d2360a4add51b170b005", size = 8044, upload-time = "2026-03-06T15:45:07.668Z" }, ] [[package]] @@ -1923,220 +2040,235 @@ wheels = [ [[package]] name = "numpy" -version = "2.4.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/44/71852273146957899753e69986246d6a176061ea183407e95418c2aa4d9a/numpy-2.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7e88598032542bd49af7c4747541422884219056c268823ef6e5e89851c8825", size = 16955478, upload-time = "2026-01-31T23:10:25.623Z" }, - { url = "https://files.pythonhosted.org/packages/74/41/5d17d4058bd0cd96bcbd4d9ff0fb2e21f52702aab9a72e4a594efa18692f/numpy-2.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7edc794af8b36ca37ef5fcb5e0d128c7e0595c7b96a2318d1badb6fcd8ee86b1", size = 14965467, upload-time = "2026-01-31T23:10:28.186Z" }, - { url = "https://files.pythonhosted.org/packages/49/48/fb1ce8136c19452ed15f033f8aee91d5defe515094e330ce368a0647846f/numpy-2.4.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6e9f61981ace1360e42737e2bae58b27bf28a1b27e781721047d84bd754d32e7", size = 5475172, upload-time = "2026-01-31T23:10:30.848Z" }, - { url = "https://files.pythonhosted.org/packages/40/a9/3feb49f17bbd1300dd2570432961f5c8a4ffeff1db6f02c7273bd020a4c9/numpy-2.4.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cb7bbb88aa74908950d979eeaa24dbdf1a865e3c7e45ff0121d8f70387b55f73", size = 6805145, upload-time = "2026-01-31T23:10:32.352Z" }, - { url = "https://files.pythonhosted.org/packages/3f/39/fdf35cbd6d6e2fcad42fcf85ac04a85a0d0fbfbf34b30721c98d602fd70a/numpy-2.4.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f069069931240b3fc703f1e23df63443dbd6390614c8c44a87d96cd0ec81eb1", size = 15966084, upload-time = "2026-01-31T23:10:34.502Z" }, - { url = "https://files.pythonhosted.org/packages/1b/46/6fa4ea94f1ddf969b2ee941290cca6f1bfac92b53c76ae5f44afe17ceb69/numpy-2.4.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c02ef4401a506fb60b411467ad501e1429a3487abca4664871d9ae0b46c8ba32", size = 16899477, upload-time = "2026-01-31T23:10:37.075Z" }, - { url = "https://files.pythonhosted.org/packages/09/a1/2a424e162b1a14a5bd860a464ab4e07513916a64ab1683fae262f735ccd2/numpy-2.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2653de5c24910e49c2b106499803124dde62a5a1fe0eedeaecf4309a5f639390", size = 17323429, upload-time = "2026-01-31T23:10:39.704Z" }, - { url = "https://files.pythonhosted.org/packages/ce/a2/73014149ff250628df72c58204822ac01d768697913881aacf839ff78680/numpy-2.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1ae241bbfc6ae276f94a170b14785e561cb5e7f626b6688cf076af4110887413", size = 18635109, upload-time = "2026-01-31T23:10:41.924Z" }, - { url = "https://files.pythonhosted.org/packages/6c/0c/73e8be2f1accd56df74abc1c5e18527822067dced5ec0861b5bb882c2ce0/numpy-2.4.2-cp311-cp311-win32.whl", hash = "sha256:df1b10187212b198dd45fa943d8985a3c8cf854aed4923796e0e019e113a1bda", size = 6237915, upload-time = "2026-01-31T23:10:45.26Z" }, - { url = "https://files.pythonhosted.org/packages/76/ae/e0265e0163cf127c24c3969d29f1c4c64551a1e375d95a13d32eab25d364/numpy-2.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:b9c618d56a29c9cb1c4da979e9899be7578d2e0b3c24d52079c166324c9e8695", size = 12607972, upload-time = "2026-01-31T23:10:47.021Z" }, - { url = "https://files.pythonhosted.org/packages/29/a5/c43029af9b8014d6ea157f192652c50042e8911f4300f8f6ed3336bf437f/numpy-2.4.2-cp311-cp311-win_arm64.whl", hash = "sha256:47c5a6ed21d9452b10227e5e8a0e1c22979811cad7dcc19d8e3e2fb8fa03f1a3", size = 10485763, upload-time = "2026-01-31T23:10:50.087Z" }, - { url = "https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a", size = 16667963, upload-time = "2026-01-31T23:10:52.147Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1", size = 14693571, upload-time = "2026-01-31T23:10:54.789Z" }, - { url = "https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e", size = 5203469, upload-time = "2026-01-31T23:10:57.343Z" }, - { url = "https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27", size = 6550820, upload-time = "2026-01-31T23:10:59.429Z" }, - { url = "https://files.pythonhosted.org/packages/b1/91/b97fdfd12dc75b02c44e26c6638241cc004d4079a0321a69c62f51470c4c/numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548", size = 15663067, upload-time = "2026-01-31T23:11:01.291Z" }, - { url = "https://files.pythonhosted.org/packages/f5/c6/a18e59f3f0b8071cc85cbc8d80cd02d68aa9710170b2553a117203d46936/numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f", size = 16619782, upload-time = "2026-01-31T23:11:03.669Z" }, - { url = "https://files.pythonhosted.org/packages/b7/83/9751502164601a79e18847309f5ceec0b1446d7b6aa12305759b72cf98b2/numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460", size = 17013128, upload-time = "2026-01-31T23:11:05.913Z" }, - { url = "https://files.pythonhosted.org/packages/61/c4/c4066322256ec740acc1c8923a10047818691d2f8aec254798f3dd90f5f2/numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba", size = 18345324, upload-time = "2026-01-31T23:11:08.248Z" }, - { url = "https://files.pythonhosted.org/packages/ab/af/6157aa6da728fa4525a755bfad486ae7e3f76d4c1864138003eb84328497/numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f", size = 5960282, upload-time = "2026-01-31T23:11:10.497Z" }, - { url = "https://files.pythonhosted.org/packages/92/0f/7ceaaeaacb40567071e94dbf2c9480c0ae453d5bb4f52bea3892c39dc83c/numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85", size = 12314210, upload-time = "2026-01-31T23:11:12.176Z" }, - { url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z" }, - { url = "https://files.pythonhosted.org/packages/a1/22/815b9fe25d1d7ae7d492152adbc7226d3eff731dffc38fe970589fcaaa38/numpy-2.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c", size = 16663696, upload-time = "2026-01-31T23:11:17.516Z" }, - { url = "https://files.pythonhosted.org/packages/09/f0/817d03a03f93ba9c6c8993de509277d84e69f9453601915e4a69554102a1/numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979", size = 14688322, upload-time = "2026-01-31T23:11:19.883Z" }, - { url = "https://files.pythonhosted.org/packages/da/b4/f805ab79293c728b9a99438775ce51885fd4f31b76178767cfc718701a39/numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98", size = 5198157, upload-time = "2026-01-31T23:11:22.375Z" }, - { url = "https://files.pythonhosted.org/packages/74/09/826e4289844eccdcd64aac27d13b0fd3f32039915dd5b9ba01baae1f436c/numpy-2.4.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef", size = 6546330, upload-time = "2026-01-31T23:11:23.958Z" }, - { url = "https://files.pythonhosted.org/packages/19/fb/cbfdbfa3057a10aea5422c558ac57538e6acc87ec1669e666d32ac198da7/numpy-2.4.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7", size = 15660968, upload-time = "2026-01-31T23:11:25.713Z" }, - { url = "https://files.pythonhosted.org/packages/04/dc/46066ce18d01645541f0186877377b9371b8fa8017fa8262002b4ef22612/numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499", size = 16607311, upload-time = "2026-01-31T23:11:28.117Z" }, - { url = "https://files.pythonhosted.org/packages/14/d9/4b5adfc39a43fa6bf918c6d544bc60c05236cc2f6339847fc5b35e6cb5b0/numpy-2.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb", size = 17012850, upload-time = "2026-01-31T23:11:30.888Z" }, - { url = "https://files.pythonhosted.org/packages/b7/20/adb6e6adde6d0130046e6fdfb7675cc62bc2f6b7b02239a09eb58435753d/numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7", size = 18334210, upload-time = "2026-01-31T23:11:33.214Z" }, - { url = "https://files.pythonhosted.org/packages/78/0e/0a73b3dff26803a8c02baa76398015ea2a5434d9b8265a7898a6028c1591/numpy-2.4.2-cp313-cp313-win32.whl", hash = "sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110", size = 5958199, upload-time = "2026-01-31T23:11:35.385Z" }, - { url = "https://files.pythonhosted.org/packages/43/bc/6352f343522fcb2c04dbaf94cb30cca6fd32c1a750c06ad6231b4293708c/numpy-2.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622", size = 12310848, upload-time = "2026-01-31T23:11:38.001Z" }, - { url = "https://files.pythonhosted.org/packages/6e/8d/6da186483e308da5da1cc6918ce913dcfe14ffde98e710bfeff2a6158d4e/numpy-2.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71", size = 10221082, upload-time = "2026-01-31T23:11:40.392Z" }, - { url = "https://files.pythonhosted.org/packages/25/a1/9510aa43555b44781968935c7548a8926274f815de42ad3997e9e83680dd/numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262", size = 14815866, upload-time = "2026-01-31T23:11:42.495Z" }, - { url = "https://files.pythonhosted.org/packages/36/30/6bbb5e76631a5ae46e7923dd16ca9d3f1c93cfa8d4ed79a129814a9d8db3/numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913", size = 5325631, upload-time = "2026-01-31T23:11:44.7Z" }, - { url = "https://files.pythonhosted.org/packages/46/00/3a490938800c1923b567b3a15cd17896e68052e2145d8662aaf3e1ffc58f/numpy-2.4.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab", size = 6646254, upload-time = "2026-01-31T23:11:46.341Z" }, - { url = "https://files.pythonhosted.org/packages/d3/e9/fac0890149898a9b609caa5af7455a948b544746e4b8fe7c212c8edd71f8/numpy-2.4.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82", size = 15720138, upload-time = "2026-01-31T23:11:48.082Z" }, - { url = "https://files.pythonhosted.org/packages/ea/5c/08887c54e68e1e28df53709f1893ce92932cc6f01f7c3d4dc952f61ffd4e/numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f", size = 16655398, upload-time = "2026-01-31T23:11:50.293Z" }, - { url = "https://files.pythonhosted.org/packages/4d/89/253db0fa0e66e9129c745e4ef25631dc37d5f1314dad2b53e907b8538e6d/numpy-2.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554", size = 17079064, upload-time = "2026-01-31T23:11:52.927Z" }, - { url = "https://files.pythonhosted.org/packages/2a/d5/cbade46ce97c59c6c3da525e8d95b7abe8a42974a1dc5c1d489c10433e88/numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257", size = 18379680, upload-time = "2026-01-31T23:11:55.22Z" }, - { url = "https://files.pythonhosted.org/packages/40/62/48f99ae172a4b63d981babe683685030e8a3df4f246c893ea5c6ef99f018/numpy-2.4.2-cp313-cp313t-win32.whl", hash = "sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657", size = 6082433, upload-time = "2026-01-31T23:11:58.096Z" }, - { url = "https://files.pythonhosted.org/packages/07/38/e054a61cfe48ad9f1ed0d188e78b7e26859d0b60ef21cd9de4897cdb5326/numpy-2.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b", size = 12451181, upload-time = "2026-01-31T23:11:59.782Z" }, - { url = "https://files.pythonhosted.org/packages/6e/a4/a05c3a6418575e185dd84d0b9680b6bb2e2dc3e4202f036b7b4e22d6e9dc/numpy-2.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1", size = 10290756, upload-time = "2026-01-31T23:12:02.438Z" }, - { url = "https://files.pythonhosted.org/packages/18/88/b7df6050bf18fdcfb7046286c6535cabbdd2064a3440fca3f069d319c16e/numpy-2.4.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:444be170853f1f9d528428eceb55f12918e4fda5d8805480f36a002f1415e09b", size = 16663092, upload-time = "2026-01-31T23:12:04.521Z" }, - { url = "https://files.pythonhosted.org/packages/25/7a/1fee4329abc705a469a4afe6e69b1ef7e915117747886327104a8493a955/numpy-2.4.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d1240d50adff70c2a88217698ca844723068533f3f5c5fa6ee2e3220e3bdb000", size = 14698770, upload-time = "2026-01-31T23:12:06.96Z" }, - { url = "https://files.pythonhosted.org/packages/fb/0b/f9e49ba6c923678ad5bc38181c08ac5e53b7a5754dbca8e581aa1a56b1ff/numpy-2.4.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:7cdde6de52fb6664b00b056341265441192d1291c130e99183ec0d4b110ff8b1", size = 5208562, upload-time = "2026-01-31T23:12:09.632Z" }, - { url = "https://files.pythonhosted.org/packages/7d/12/d7de8f6f53f9bb76997e5e4c069eda2051e3fe134e9181671c4391677bb2/numpy-2.4.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:cda077c2e5b780200b6b3e09d0b42205a3d1c68f30c6dceb90401c13bff8fe74", size = 6543710, upload-time = "2026-01-31T23:12:11.969Z" }, - { url = "https://files.pythonhosted.org/packages/09/63/c66418c2e0268a31a4cf8a8b512685748200f8e8e8ec6c507ce14e773529/numpy-2.4.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d30291931c915b2ab5717c2974bb95ee891a1cf22ebc16a8006bd59cd210d40a", size = 15677205, upload-time = "2026-01-31T23:12:14.33Z" }, - { url = "https://files.pythonhosted.org/packages/5d/6c/7f237821c9642fb2a04d2f1e88b4295677144ca93285fd76eff3bcba858d/numpy-2.4.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bba37bc29d4d85761deed3954a1bc62be7cf462b9510b51d367b769a8c8df325", size = 16611738, upload-time = "2026-01-31T23:12:16.525Z" }, - { url = "https://files.pythonhosted.org/packages/c2/a7/39c4cdda9f019b609b5c473899d87abff092fc908cfe4d1ecb2fcff453b0/numpy-2.4.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b2f0073ed0868db1dcd86e052d37279eef185b9c8db5bf61f30f46adac63c909", size = 17028888, upload-time = "2026-01-31T23:12:19.306Z" }, - { url = "https://files.pythonhosted.org/packages/da/b3/e84bb64bdfea967cc10950d71090ec2d84b49bc691df0025dddb7c26e8e3/numpy-2.4.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f54844851cdb630ceb623dcec4db3240d1ac13d4990532446761baede94996a", size = 18339556, upload-time = "2026-01-31T23:12:21.816Z" }, - { url = "https://files.pythonhosted.org/packages/88/f5/954a291bc1192a27081706862ac62bb5920fbecfbaa302f64682aa90beed/numpy-2.4.2-cp314-cp314-win32.whl", hash = "sha256:12e26134a0331d8dbd9351620f037ec470b7c75929cb8a1537f6bfe411152a1a", size = 6006899, upload-time = "2026-01-31T23:12:24.14Z" }, - { url = "https://files.pythonhosted.org/packages/05/cb/eff72a91b2efdd1bc98b3b8759f6a1654aa87612fc86e3d87d6fe4f948c4/numpy-2.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:068cdb2d0d644cdb45670810894f6a0600797a69c05f1ac478e8d31670b8ee75", size = 12443072, upload-time = "2026-01-31T23:12:26.33Z" }, - { url = "https://files.pythonhosted.org/packages/37/75/62726948db36a56428fce4ba80a115716dc4fad6a3a4352487f8bb950966/numpy-2.4.2-cp314-cp314-win_arm64.whl", hash = "sha256:6ed0be1ee58eef41231a5c943d7d1375f093142702d5723ca2eb07db9b934b05", size = 10494886, upload-time = "2026-01-31T23:12:28.488Z" }, - { url = "https://files.pythonhosted.org/packages/36/2f/ee93744f1e0661dc267e4b21940870cabfae187c092e1433b77b09b50ac4/numpy-2.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:98f16a80e917003a12c0580f97b5f875853ebc33e2eaa4bccfc8201ac6869308", size = 14818567, upload-time = "2026-01-31T23:12:30.709Z" }, - { url = "https://files.pythonhosted.org/packages/a7/24/6535212add7d76ff938d8bdc654f53f88d35cddedf807a599e180dcb8e66/numpy-2.4.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:20abd069b9cda45874498b245c8015b18ace6de8546bf50dfa8cea1696ed06ef", size = 5328372, upload-time = "2026-01-31T23:12:32.962Z" }, - { url = "https://files.pythonhosted.org/packages/5e/9d/c48f0a035725f925634bf6b8994253b43f2047f6778a54147d7e213bc5a7/numpy-2.4.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e98c97502435b53741540a5717a6749ac2ada901056c7db951d33e11c885cc7d", size = 6649306, upload-time = "2026-01-31T23:12:34.797Z" }, - { url = "https://files.pythonhosted.org/packages/81/05/7c73a9574cd4a53a25907bad38b59ac83919c0ddc8234ec157f344d57d9a/numpy-2.4.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da6cad4e82cb893db4b69105c604d805e0c3ce11501a55b5e9f9083b47d2ffe8", size = 15722394, upload-time = "2026-01-31T23:12:36.565Z" }, - { url = "https://files.pythonhosted.org/packages/35/fa/4de10089f21fc7d18442c4a767ab156b25c2a6eaf187c0db6d9ecdaeb43f/numpy-2.4.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4424677ce4b47fe73c8b5556d876571f7c6945d264201180db2dc34f676ab5", size = 16653343, upload-time = "2026-01-31T23:12:39.188Z" }, - { url = "https://files.pythonhosted.org/packages/b8/f9/d33e4ffc857f3763a57aa85650f2e82486832d7492280ac21ba9efda80da/numpy-2.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2b8f157c8a6f20eb657e240f8985cc135598b2b46985c5bccbde7616dc9c6b1e", size = 17078045, upload-time = "2026-01-31T23:12:42.041Z" }, - { url = "https://files.pythonhosted.org/packages/c8/b8/54bdb43b6225badbea6389fa038c4ef868c44f5890f95dd530a218706da3/numpy-2.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5daf6f3914a733336dab21a05cdec343144600e964d2fcdabaac0c0269874b2a", size = 18380024, upload-time = "2026-01-31T23:12:44.331Z" }, - { url = "https://files.pythonhosted.org/packages/a5/55/6e1a61ded7af8df04016d81b5b02daa59f2ea9252ee0397cb9f631efe9e5/numpy-2.4.2-cp314-cp314t-win32.whl", hash = "sha256:8c50dd1fc8826f5b26a5ee4d77ca55d88a895f4e4819c7ecc2a9f5905047a443", size = 6153937, upload-time = "2026-01-31T23:12:47.229Z" }, - { url = "https://files.pythonhosted.org/packages/45/aa/fa6118d1ed6d776b0983f3ceac9b1a5558e80df9365b1c3aa6d42bf9eee4/numpy-2.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fcf92bee92742edd401ba41135185866f7026c502617f422eb432cfeca4fe236", size = 12631844, upload-time = "2026-01-31T23:12:48.997Z" }, - { url = "https://files.pythonhosted.org/packages/32/0a/2ec5deea6dcd158f254a7b372fb09cfba5719419c8d66343bab35237b3fb/numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181", size = 10565379, upload-time = "2026-01-31T23:12:51.345Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f8/50e14d36d915ef64d8f8bc4a087fc8264d82c785eda6711f80ab7e620335/numpy-2.4.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:89f7268c009bc492f506abd6f5265defa7cb3f7487dc21d357c3d290add45082", size = 16833179, upload-time = "2026-01-31T23:12:53.5Z" }, - { url = "https://files.pythonhosted.org/packages/17/17/809b5cad63812058a8189e91a1e2d55a5a18fd04611dbad244e8aeae465c/numpy-2.4.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6dee3bb76aa4009d5a912180bf5b2de012532998d094acee25d9cb8dee3e44a", size = 14889755, upload-time = "2026-01-31T23:12:55.933Z" }, - { url = "https://files.pythonhosted.org/packages/3e/ea/181b9bcf7627fc8371720316c24db888dcb9829b1c0270abf3d288b2e29b/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:cd2bd2bbed13e213d6b55dc1d035a4f91748a7d3edc9480c13898b0353708920", size = 5399500, upload-time = "2026-01-31T23:12:58.671Z" }, - { url = "https://files.pythonhosted.org/packages/33/9f/413adf3fc955541ff5536b78fcf0754680b3c6d95103230252a2c9408d23/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:cf28c0c1d4c4bf00f509fa7eb02c58d7caf221b50b467bcb0d9bbf1584d5c821", size = 6714252, upload-time = "2026-01-31T23:13:00.518Z" }, - { url = "https://files.pythonhosted.org/packages/91/da/643aad274e29ccbdf42ecd94dafe524b81c87bcb56b83872d54827f10543/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e04ae107ac591763a47398bb45b568fc38f02dbc4aa44c063f67a131f99346cb", size = 15797142, upload-time = "2026-01-31T23:13:02.219Z" }, - { url = "https://files.pythonhosted.org/packages/66/27/965b8525e9cb5dc16481b30a1b3c21e50c7ebf6e9dbd48d0c4d0d5089c7e/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:602f65afdef699cda27ec0b9224ae5dc43e328f4c24c689deaf77133dbee74d0", size = 16727979, upload-time = "2026-01-31T23:13:04.62Z" }, - { url = "https://files.pythonhosted.org/packages/de/e5/b7d20451657664b07986c2f6e3be564433f5dcaf3482d68eaecd79afaf03/numpy-2.4.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be71bf1edb48ebbbf7f6337b5bfd2f895d1902f6335a5830b20141fc126ffba0", size = 12502577, upload-time = "2026-01-31T23:13:07.08Z" }, +version = "2.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/9f/b8cef5bffa569759033adda9481211426f12f53299629b410340795c2514/numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0", size = 20731587, upload-time = "2026-03-29T13:22:01.298Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/c6/4218570d8c8ecc9704b5157a3348e486e84ef4be0ed3e38218ab473c83d2/numpy-2.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f983334aea213c99992053ede6168500e5f086ce74fbc4acc3f2b00f5762e9db", size = 16976799, upload-time = "2026-03-29T13:18:15.438Z" }, + { url = "https://files.pythonhosted.org/packages/dd/92/b4d922c4a5f5dab9ed44e6153908a5c665b71acf183a83b93b690996e39b/numpy-2.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72944b19f2324114e9dc86a159787333b77874143efcf89a5167ef83cfee8af0", size = 14971552, upload-time = "2026-03-29T13:18:18.606Z" }, + { url = "https://files.pythonhosted.org/packages/8a/dc/df98c095978fa6ee7b9a9387d1d58cbb3d232d0e69ad169a4ce784bde4fd/numpy-2.4.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:86b6f55f5a352b48d7fbfd2dbc3d5b780b2d79f4d3c121f33eb6efb22e9a2015", size = 5476566, upload-time = "2026-03-29T13:18:21.532Z" }, + { url = "https://files.pythonhosted.org/packages/28/34/b3fdcec6e725409223dd27356bdf5a3c2cc2282e428218ecc9cb7acc9763/numpy-2.4.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:ba1f4fc670ed79f876f70082eff4f9583c15fb9a4b89d6188412de4d18ae2f40", size = 6806482, upload-time = "2026-03-29T13:18:23.634Z" }, + { url = "https://files.pythonhosted.org/packages/68/62/63417c13aa35d57bee1337c67446761dc25ea6543130cf868eace6e8157b/numpy-2.4.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a87ec22c87be071b6bdbd27920b129b94f2fc964358ce38f3822635a3e2e03d", size = 15973376, upload-time = "2026-03-29T13:18:26.677Z" }, + { url = "https://files.pythonhosted.org/packages/cf/c5/9fcb7e0e69cef59cf10c746b84f7d58b08bc66a6b7d459783c5a4f6101a6/numpy-2.4.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df3775294accfdd75f32c74ae39fcba920c9a378a2fc18a12b6820aa8c1fb502", size = 16925137, upload-time = "2026-03-29T13:18:30.14Z" }, + { url = "https://files.pythonhosted.org/packages/7e/43/80020edacb3f84b9efdd1591120a4296462c23fd8db0dde1666f6ef66f13/numpy-2.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d4e437e295f18ec29bc79daf55e8a47a9113df44d66f702f02a293d93a2d6dd", size = 17329414, upload-time = "2026-03-29T13:18:33.733Z" }, + { url = "https://files.pythonhosted.org/packages/fd/06/af0658593b18a5f73532d377188b964f239eb0894e664a6c12f484472f97/numpy-2.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6aa3236c78803afbcb255045fbef97a9e25a1f6c9888357d205ddc42f4d6eba5", size = 18658397, upload-time = "2026-03-29T13:18:37.511Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ce/13a09ed65f5d0ce5c7dd0669250374c6e379910f97af2c08c57b0608eee4/numpy-2.4.4-cp311-cp311-win32.whl", hash = "sha256:30caa73029a225b2d40d9fae193e008e24b2026b7ee1a867b7ee8d96ca1a448e", size = 6239499, upload-time = "2026-03-29T13:18:40.372Z" }, + { url = "https://files.pythonhosted.org/packages/bd/63/05d193dbb4b5eec1eca73822d80da98b511f8328ad4ae3ca4caf0f4db91d/numpy-2.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:6bbe4eb67390b0a0265a2c25458f6b90a409d5d069f1041e6aff1e27e3d9a79e", size = 12614257, upload-time = "2026-03-29T13:18:42.95Z" }, + { url = "https://files.pythonhosted.org/packages/87/c5/8168052f080c26fa984c413305012be54741c9d0d74abd7fbeeccae3889f/numpy-2.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:fcfe2045fd2e8f3cb0ce9d4ba6dba6333b8fa05bb8a4939c908cd43322d14c7e", size = 10486775, upload-time = "2026-03-29T13:18:45.835Z" }, + { url = "https://files.pythonhosted.org/packages/28/05/32396bec30fb2263770ee910142f49c1476d08e8ad41abf8403806b520ce/numpy-2.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15716cfef24d3a9762e3acdf87e27f58dc823d1348f765bbea6bef8c639bfa1b", size = 16689272, upload-time = "2026-03-29T13:18:49.223Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f3/a983d28637bfcd763a9c7aafdb6d5c0ebf3d487d1e1459ffdb57e2f01117/numpy-2.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23cbfd4c17357c81021f21540da84ee282b9c8fba38a03b7b9d09ba6b951421e", size = 14699573, upload-time = "2026-03-29T13:18:52.629Z" }, + { url = "https://files.pythonhosted.org/packages/9b/fd/e5ecca1e78c05106d98028114f5c00d3eddb41207686b2b7de3e477b0e22/numpy-2.4.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b3b60bb7cba2c8c81837661c488637eee696f59a877788a396d33150c35d842", size = 5204782, upload-time = "2026-03-29T13:18:55.579Z" }, + { url = "https://files.pythonhosted.org/packages/de/2f/702a4594413c1a8632092beae8aba00f1d67947389369b3777aed783fdca/numpy-2.4.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e4a010c27ff6f210ff4c6ef34394cd61470d01014439b192ec22552ee867f2a8", size = 6552038, upload-time = "2026-03-29T13:18:57.769Z" }, + { url = "https://files.pythonhosted.org/packages/7f/37/eed308a8f56cba4d1fdf467a4fc67ef4ff4bf1c888f5fc980481890104b1/numpy-2.4.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9e75681b59ddaa5e659898085ae0eaea229d054f2ac0c7e563a62205a700121", size = 15670666, upload-time = "2026-03-29T13:19:00.341Z" }, + { url = "https://files.pythonhosted.org/packages/0a/0d/0e3ecece05b7a7e87ab9fb587855548da437a061326fff64a223b6dcb78a/numpy-2.4.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81f4a14bee47aec54f883e0cad2d73986640c1590eb9bfaaba7ad17394481e6e", size = 16645480, upload-time = "2026-03-29T13:19:03.63Z" }, + { url = "https://files.pythonhosted.org/packages/34/49/f2312c154b82a286758ee2f1743336d50651f8b5195db18cdb63675ff649/numpy-2.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62d6b0f03b694173f9fcb1fb317f7222fd0b0b103e784c6549f5e53a27718c44", size = 17020036, upload-time = "2026-03-29T13:19:07.428Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e9/736d17bd77f1b0ec4f9901aaec129c00d59f5d84d5e79bba540ef12c2330/numpy-2.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbc356aae7adf9e6336d336b9c8111d390a05df88f1805573ebb0807bd06fd1d", size = 18368643, upload-time = "2026-03-29T13:19:10.775Z" }, + { url = "https://files.pythonhosted.org/packages/63/f6/d417977c5f519b17c8a5c3bc9e8304b0908b0e21136fe43bf628a1343914/numpy-2.4.4-cp312-cp312-win32.whl", hash = "sha256:0d35aea54ad1d420c812bfa0385c71cd7cc5bcf7c65fed95fc2cd02fe8c79827", size = 5961117, upload-time = "2026-03-29T13:19:13.464Z" }, + { url = "https://files.pythonhosted.org/packages/2d/5b/e1deebf88ff431b01b7406ca3583ab2bbb90972bbe1c568732e49c844f7e/numpy-2.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:b5f0362dc928a6ecd9db58868fca5e48485205e3855957bdedea308f8672ea4a", size = 12320584, upload-time = "2026-03-29T13:19:16.155Z" }, + { url = "https://files.pythonhosted.org/packages/58/89/e4e856ac82a68c3ed64486a544977d0e7bdd18b8da75b78a577ca31c4395/numpy-2.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:846300f379b5b12cc769334464656bc882e0735d27d9726568bc932fdc49d5ec", size = 10221450, upload-time = "2026-03-29T13:19:18.994Z" }, + { url = "https://files.pythonhosted.org/packages/14/1d/d0a583ce4fefcc3308806a749a536c201ed6b5ad6e1322e227ee4848979d/numpy-2.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:08f2e31ed5e6f04b118e49821397f12767934cfdd12a1ce86a058f91e004ee50", size = 16684933, upload-time = "2026-03-29T13:19:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/c1/62/2b7a48fbb745d344742c0277f01286dead15f3f68e4f359fbfcf7b48f70f/numpy-2.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e823b8b6edc81e747526f70f71a9c0a07ac4e7ad13020aa736bb7c9d67196115", size = 14694532, upload-time = "2026-03-29T13:19:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af", size = 5199661, upload-time = "2026-03-29T13:19:28.31Z" }, + { url = "https://files.pythonhosted.org/packages/cd/da/464d551604320d1491bc345efed99b4b7034143a85787aab78d5691d5a0e/numpy-2.4.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d2a8490669bfe99a233298348acc2d824d496dee0e66e31b66a6022c2ad74a5c", size = 6547539, upload-time = "2026-03-29T13:19:30.97Z" }, + { url = "https://files.pythonhosted.org/packages/7d/90/8d23e3b0dafd024bf31bdec225b3bb5c2dbfa6912f8a53b8659f21216cbf/numpy-2.4.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45dbed2ab436a9e826e302fcdcbe9133f9b0006e5af7168afb8963a6520da103", size = 15668806, upload-time = "2026-03-29T13:19:33.887Z" }, + { url = "https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83", size = 16632682, upload-time = "2026-03-29T13:19:37.336Z" }, + { url = "https://files.pythonhosted.org/packages/34/fb/14570d65c3bde4e202a031210475ae9cde9b7686a2e7dc97ee67d2833b35/numpy-2.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:99d838547ace2c4aace6c4f76e879ddfe02bb58a80c1549928477862b7a6d6ed", size = 17019810, upload-time = "2026-03-29T13:19:40.963Z" }, + { url = "https://files.pythonhosted.org/packages/8a/77/2ba9d87081fd41f6d640c83f26fb7351e536b7ce6dd9061b6af5904e8e46/numpy-2.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0aec54fd785890ecca25a6003fd9a5aed47ad607bbac5cd64f836ad8666f4959", size = 18357394, upload-time = "2026-03-29T13:19:44.859Z" }, + { url = "https://files.pythonhosted.org/packages/a2/23/52666c9a41708b0853fa3b1a12c90da38c507a3074883823126d4e9d5b30/numpy-2.4.4-cp313-cp313-win32.whl", hash = "sha256:07077278157d02f65c43b1b26a3886bce886f95d20aabd11f87932750dfb14ed", size = 5959556, upload-time = "2026-03-29T13:19:47.661Z" }, + { url = "https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf", size = 12317311, upload-time = "2026-03-29T13:19:50.67Z" }, + { url = "https://files.pythonhosted.org/packages/ba/d8/11490cddd564eb4de97b4579ef6bfe6a736cc07e94c1598590ae25415e01/numpy-2.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:ef4059d6e5152fa1a39f888e344c73fdc926e1b2dd58c771d67b0acfbf2aa67d", size = 10222060, upload-time = "2026-03-29T13:19:54.229Z" }, + { url = "https://files.pythonhosted.org/packages/99/5d/dab4339177a905aad3e2221c915b35202f1ec30d750dd2e5e9d9a72b804b/numpy-2.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4bbc7f303d125971f60ec0aaad5e12c62d0d2c925f0ab1273debd0e4ba37aba5", size = 14822302, upload-time = "2026-03-29T13:19:57.585Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e4/0564a65e7d3d97562ed6f9b0fd0fb0a6f559ee444092f105938b50043876/numpy-2.4.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:4d6d57903571f86180eb98f8f0c839fa9ebbfb031356d87f1361be91e433f5b7", size = 5327407, upload-time = "2026-03-29T13:20:00.601Z" }, + { url = "https://files.pythonhosted.org/packages/29/8d/35a3a6ce5ad371afa58b4700f1c820f8f279948cca32524e0a695b0ded83/numpy-2.4.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:4636de7fd195197b7535f231b5de9e4b36d2c440b6e566d2e4e4746e6af0ca93", size = 6647631, upload-time = "2026-03-29T13:20:02.855Z" }, + { url = "https://files.pythonhosted.org/packages/f4/da/477731acbd5a58a946c736edfdabb2ac5b34c3d08d1ba1a7b437fa0884df/numpy-2.4.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad2e2ef14e0b04e544ea2fa0a36463f847f113d314aa02e5b402fdf910ef309e", size = 15727691, upload-time = "2026-03-29T13:20:06.004Z" }, + { url = "https://files.pythonhosted.org/packages/e6/db/338535d9b152beabeb511579598418ba0212ce77cf9718edd70262cc4370/numpy-2.4.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a285b3b96f951841799528cd1f4f01cd70e7e0204b4abebac9463eecfcf2a40", size = 16681241, upload-time = "2026-03-29T13:20:09.417Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a9/ad248e8f58beb7a0219b413c9c7d8151c5d285f7f946c3e26695bdbbe2df/numpy-2.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f8474c4241bc18b750be2abea9d7a9ec84f46ef861dbacf86a4f6e043401f79e", size = 17085767, upload-time = "2026-03-29T13:20:13.126Z" }, + { url = "https://files.pythonhosted.org/packages/b5/1a/3b88ccd3694681356f70da841630e4725a7264d6a885c8d442a697e1146b/numpy-2.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4e874c976154687c1f71715b034739b45c7711bec81db01914770373d125e392", size = 18403169, upload-time = "2026-03-29T13:20:17.096Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c9/fcfd5d0639222c6eac7f304829b04892ef51c96a75d479214d77e3ce6e33/numpy-2.4.4-cp313-cp313t-win32.whl", hash = "sha256:9c585a1790d5436a5374bac930dad6ed244c046ed91b2b2a3634eb2971d21008", size = 6083477, upload-time = "2026-03-29T13:20:20.195Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e3/3938a61d1c538aaec8ed6fd6323f57b0c2d2d2219512434c5c878db76553/numpy-2.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:93e15038125dc1e5345d9b5b68aa7f996ec33b98118d18c6ca0d0b7d6198b7e8", size = 12457487, upload-time = "2026-03-29T13:20:22.946Z" }, + { url = "https://files.pythonhosted.org/packages/97/6a/7e345032cc60501721ef94e0e30b60f6b0bd601f9174ebd36389a2b86d40/numpy-2.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:0dfd3f9d3adbe2920b68b5cd3d51444e13a10792ec7154cd0a2f6e74d4ab3233", size = 10292002, upload-time = "2026-03-29T13:20:25.909Z" }, + { url = "https://files.pythonhosted.org/packages/6e/06/c54062f85f673dd5c04cbe2f14c3acb8c8b95e3384869bb8cc9bff8cb9df/numpy-2.4.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f169b9a863d34f5d11b8698ead99febeaa17a13ca044961aa8e2662a6c7766a0", size = 16684353, upload-time = "2026-03-29T13:20:29.504Z" }, + { url = "https://files.pythonhosted.org/packages/4c/39/8a320264a84404c74cc7e79715de85d6130fa07a0898f67fb5cd5bd79908/numpy-2.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2483e4584a1cb3092da4470b38866634bafb223cbcd551ee047633fd2584599a", size = 14704914, upload-time = "2026-03-29T13:20:33.547Z" }, + { url = "https://files.pythonhosted.org/packages/91/fb/287076b2614e1d1044235f50f03748f31fa287e3dbe6abeb35cdfa351eca/numpy-2.4.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:2d19e6e2095506d1736b7d80595e0f252d76b89f5e715c35e06e937679ea7d7a", size = 5210005, upload-time = "2026-03-29T13:20:36.45Z" }, + { url = "https://files.pythonhosted.org/packages/63/eb/fcc338595309910de6ecabfcef2419a9ce24399680bfb149421fa2df1280/numpy-2.4.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6a246d5914aa1c820c9443ddcee9c02bec3e203b0c080349533fae17727dfd1b", size = 6544974, upload-time = "2026-03-29T13:20:39.014Z" }, + { url = "https://files.pythonhosted.org/packages/44/5d/e7e9044032a716cdfaa3fba27a8e874bf1c5f1912a1ddd4ed071bf8a14a6/numpy-2.4.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:989824e9faf85f96ec9c7761cd8d29c531ad857bfa1daa930cba85baaecf1a9a", size = 15684591, upload-time = "2026-03-29T13:20:42.146Z" }, + { url = "https://files.pythonhosted.org/packages/98/7c/21252050676612625449b4807d6b695b9ce8a7c9e1c197ee6216c8a65c7c/numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d", size = 16637700, upload-time = "2026-03-29T13:20:46.204Z" }, + { url = "https://files.pythonhosted.org/packages/b1/29/56d2bbef9465db24ef25393383d761a1af4f446a1df9b8cded4fe3a5a5d7/numpy-2.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e44319a2953c738205bf3354537979eaa3998ed673395b964c1176083dd46252", size = 17035781, upload-time = "2026-03-29T13:20:50.242Z" }, + { url = "https://files.pythonhosted.org/packages/e3/2b/a35a6d7589d21f44cea7d0a98de5ddcbb3d421b2622a5c96b1edf18707c3/numpy-2.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e892aff75639bbef0d2a2cfd55535510df26ff92f63c92cd84ef8d4ba5a5557f", size = 18362959, upload-time = "2026-03-29T13:20:54.019Z" }, + { url = "https://files.pythonhosted.org/packages/64/c9/d52ec581f2390e0f5f85cbfd80fb83d965fc15e9f0e1aec2195faa142cde/numpy-2.4.4-cp314-cp314-win32.whl", hash = "sha256:1378871da56ca8943c2ba674530924bb8ca40cd228358a3b5f302ad60cf875fc", size = 6008768, upload-time = "2026-03-29T13:20:56.912Z" }, + { url = "https://files.pythonhosted.org/packages/fa/22/4cc31a62a6c7b74a8730e31a4274c5dc80e005751e277a2ce38e675e4923/numpy-2.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:715d1c092715954784bc79e1174fc2a90093dc4dc84ea15eb14dad8abdcdeb74", size = 12449181, upload-time = "2026-03-29T13:20:59.548Z" }, + { url = "https://files.pythonhosted.org/packages/70/2e/14cda6f4d8e396c612d1bf97f22958e92148801d7e4f110cabebdc0eef4b/numpy-2.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:2c194dd721e54ecad9ad387c1d35e63dce5c4450c6dc7dd5611283dda239aabb", size = 10496035, upload-time = "2026-03-29T13:21:02.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e8/8fed8c8d848d7ecea092dc3469643f9d10bc3a134a815a3b033da1d2039b/numpy-2.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2aa0613a5177c264ff5921051a5719d20095ea586ca88cc802c5c218d1c67d3e", size = 14824958, upload-time = "2026-03-29T13:21:05.671Z" }, + { url = "https://files.pythonhosted.org/packages/05/1a/d8007a5138c179c2bf33ef44503e83d70434d2642877ee8fbb230e7c0548/numpy-2.4.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:42c16925aa5a02362f986765f9ebabf20de75cdefdca827d14315c568dcab113", size = 5330020, upload-time = "2026-03-29T13:21:08.635Z" }, + { url = "https://files.pythonhosted.org/packages/99/64/ffb99ac6ae93faf117bcbd5c7ba48a7f45364a33e8e458545d3633615dda/numpy-2.4.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:874f200b2a981c647340f841730fc3a2b54c9d940566a3c4149099591e2c4c3d", size = 6650758, upload-time = "2026-03-29T13:21:10.949Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6e/795cc078b78a384052e73b2f6281ff7a700e9bf53bcce2ee579d4f6dd879/numpy-2.4.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b39d38a9bd2ae1becd7eac1303d031c5c110ad31f2b319c6e7d98b135c934d", size = 15729948, upload-time = "2026-03-29T13:21:14.047Z" }, + { url = "https://files.pythonhosted.org/packages/5f/86/2acbda8cc2af5f3d7bfc791192863b9e3e19674da7b5e533fded124d1299/numpy-2.4.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b268594bccac7d7cf5844c7732e3f20c50921d94e36d7ec9b79e9857694b1b2f", size = 16679325, upload-time = "2026-03-29T13:21:17.561Z" }, + { url = "https://files.pythonhosted.org/packages/bc/59/cafd83018f4aa55e0ac6fa92aa066c0a1877b77a615ceff1711c260ffae8/numpy-2.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac6b31e35612a26483e20750126d30d0941f949426974cace8e6b5c58a3657b0", size = 17084883, upload-time = "2026-03-29T13:21:21.106Z" }, + { url = "https://files.pythonhosted.org/packages/f0/85/a42548db84e65ece46ab2caea3d3f78b416a47af387fcbb47ec28e660dc2/numpy-2.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8e3ed142f2728df44263aaf5fb1f5b0b99f4070c553a0d7f033be65338329150", size = 18403474, upload-time = "2026-03-29T13:21:24.828Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ad/483d9e262f4b831000062e5d8a45e342166ec8aaa1195264982bca267e62/numpy-2.4.4-cp314-cp314t-win32.whl", hash = "sha256:dddbbd259598d7240b18c9d87c56a9d2fb3b02fe266f49a7c101532e78c1d871", size = 6155500, upload-time = "2026-03-29T13:21:28.205Z" }, + { url = "https://files.pythonhosted.org/packages/c7/03/2fc4e14c7bd4ff2964b74ba90ecb8552540b6315f201df70f137faa5c589/numpy-2.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a7164afb23be6e37ad90b2f10426149fd75aee07ca55653d2aa41e66c4ef697e", size = 12637755, upload-time = "2026-03-29T13:21:31.107Z" }, + { url = "https://files.pythonhosted.org/packages/58/78/548fb8e07b1a341746bfbecb32f2c268470f45fa028aacdbd10d9bc73aab/numpy-2.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:ba203255017337d39f89bdd58417f03c4426f12beed0440cfd933cb15f8669c7", size = 10566643, upload-time = "2026-03-29T13:21:34.339Z" }, + { url = "https://files.pythonhosted.org/packages/6b/33/8fae8f964a4f63ed528264ddf25d2b683d0b663e3cba26961eb838a7c1bd/numpy-2.4.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:58c8b5929fcb8287cbd6f0a3fae19c6e03a5c48402ae792962ac465224a629a4", size = 16854491, upload-time = "2026-03-29T13:21:38.03Z" }, + { url = "https://files.pythonhosted.org/packages/bc/d0/1aabee441380b981cf8cdda3ae7a46aa827d1b5a8cce84d14598bc94d6d9/numpy-2.4.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:eea7ac5d2dce4189771cedb559c738a71512768210dc4e4753b107a2048b3d0e", size = 14895830, upload-time = "2026-03-29T13:21:41.509Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b8/aafb0d1065416894fccf4df6b49ef22b8db045187949545bced89c034b8e/numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:51fc224f7ca4d92656d5a5eb315f12eb5fe2c97a66249aa7b5f562528a3be38c", size = 5400927, upload-time = "2026-03-29T13:21:44.747Z" }, + { url = "https://files.pythonhosted.org/packages/d6/77/063baa20b08b431038c7f9ff5435540c7b7265c78cf56012a483019ca72d/numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:28a650663f7314afc3e6ec620f44f333c386aad9f6fc472030865dc0ebb26ee3", size = 6715557, upload-time = "2026-03-29T13:21:47.406Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a8/379542d45a14f149444c5c4c4e7714707239ce9cc1de8c2803958889da14/numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19710a9ca9992d7174e9c52f643d4272dcd1558c5f7af7f6f8190f633bd651a7", size = 15804253, upload-time = "2026-03-29T13:21:50.753Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c8/f0a45426d6d21e7ea3310a15cf90c43a14d9232c31a837702dba437f3373/numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b2aec6af35c113b05695ebb5749a787acd63cafc83086a05771d1e1cd1e555f", size = 16753552, upload-time = "2026-03-29T13:21:54.344Z" }, + { url = "https://files.pythonhosted.org/packages/04/74/f4c001f4714c3ad9ce037e18cf2b9c64871a84951eaa0baf683a9ca9301c/numpy-2.4.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f2cf083b324a467e1ab358c105f6cad5ea950f50524668a80c486ff1db24e119", size = 12509075, upload-time = "2026-03-29T13:21:57.644Z" }, ] [[package]] -name = "nvidia-cublas-cu12" -version = "12.8.4.1" +name = "nvidia-cublas" +version = "13.1.0.3" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/61/e24b560ab2e2eaeb3c839129175fb330dfcfc29e5203196e5541a4c44682/nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:8ac4e771d5a348c551b2a426eda6193c19aa630236b418086020df5ba9667142", size = 594346921, upload-time = "2025-03-07T01:44:31.254Z" }, + { url = "https://files.pythonhosted.org/packages/e1/a5/fce49e2ae977e0ccc084e5adafceb4f0ac0c8333cb6863501618a7277f67/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:c86fc7f7ae36d7528288c5d88098edcb7b02c633d262e7ddbb86b0ad91be5df2", size = 542851226, upload-time = "2025-10-09T08:59:04.818Z" }, + { url = "https://files.pythonhosted.org/packages/e7/44/423ac00af4dd95a5aeb27207e2c0d9b7118702149bf4704c3ddb55bb7429/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:ee8722c1f0145ab246bccb9e452153b5e0515fd094c3678df50b2a0888b8b171", size = 423133236, upload-time = "2025-10-09T08:59:32.536Z" }, ] [[package]] -name = "nvidia-cuda-cupti-cu12" -version = "12.8.90" +name = "nvidia-cuda-cupti" +version = "13.0.85" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/02/2adcaa145158bf1a8295d83591d22e4103dbfd821bcaf6f3f53151ca4ffa/nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea0cb07ebda26bb9b29ba82cda34849e73c166c18162d3913575b0c9db9a6182", size = 10248621, upload-time = "2025-03-07T01:40:21.213Z" }, + { url = "https://files.pythonhosted.org/packages/2a/2a/80353b103fc20ce05ef51e928daed4b6015db4aaa9162ed0997090fe2250/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_aarch64.whl", hash = "sha256:796bd679890ee55fb14a94629b698b6db54bcfd833d391d5e94017dd9d7d3151", size = 10310827, upload-time = "2025-09-04T08:26:42.012Z" }, + { url = "https://files.pythonhosted.org/packages/33/6d/737d164b4837a9bbd202f5ae3078975f0525a55730fe871d8ed4e3b952b0/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_x86_64.whl", hash = "sha256:4eb01c08e859bf924d222250d2e8f8b8ff6d3db4721288cf35d14252a4d933c8", size = 10715597, upload-time = "2025-09-04T08:26:51.312Z" }, ] [[package]] -name = "nvidia-cuda-nvrtc-cu12" -version = "12.8.93" +name = "nvidia-cuda-nvrtc" +version = "13.0.88" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/6b/32f747947df2da6994e999492ab306a903659555dddc0fbdeb9d71f75e52/nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:a7756528852ef889772a84c6cd89d41dfa74667e24cca16bb31f8f061e3e9994", size = 88040029, upload-time = "2025-03-07T01:42:13.562Z" }, + { url = "https://files.pythonhosted.org/packages/c3/68/483a78f5e8f31b08fb1bb671559968c0ca3a065ac7acabfc7cee55214fd6/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:ad9b6d2ead2435f11cbb6868809d2adeeee302e9bb94bcf0539c7a40d80e8575", size = 90215200, upload-time = "2025-09-04T08:28:44.204Z" }, + { url = "https://files.pythonhosted.org/packages/b7/dc/6bb80850e0b7edd6588d560758f17e0550893a1feaf436807d64d2da040f/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d27f20a0ca67a4bb34268a5e951033496c5b74870b868bacd046b1b8e0c3267b", size = 43015449, upload-time = "2025-09-04T08:28:20.239Z" }, ] [[package]] -name = "nvidia-cuda-runtime-cu12" -version = "12.8.90" +name = "nvidia-cuda-runtime" +version = "13.0.96" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/9b/a997b638fcd068ad6e4d53b8551a7d30fe8b404d6f1804abf1df69838932/nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adade8dcbd0edf427b7204d480d6066d33902cab2a4707dcfc48a2d0fd44ab90", size = 954765, upload-time = "2025-03-07T01:40:01.615Z" }, + { url = "https://files.pythonhosted.org/packages/87/4f/17d7b9b8e285199c58ce28e31b5c5bbaa4d8271af06a89b6405258245de2/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef9bcbe90493a2b9d810e43d249adb3d02e98dd30200d86607d8d02687c43f55", size = 2261060, upload-time = "2025-10-09T08:55:15.78Z" }, + { url = "https://files.pythonhosted.org/packages/2e/24/d1558f3b68b1d26e706813b1d10aa1d785e4698c425af8db8edc3dced472/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f82250d7782aa23b6cfe765ecc7db554bd3c2870c43f3d1821f1d18aebf0548", size = 2243632, upload-time = "2025-10-09T08:55:36.117Z" }, ] [[package]] -name = "nvidia-cudnn-cu12" -version = "9.10.2.21" +name = "nvidia-cudnn-cu13" +version = "9.19.0.56" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas-cu12" }, + { name = "nvidia-cublas" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/51/e123d997aa098c61d029f76663dedbfb9bc8dcf8c60cbd6adbe42f76d049/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8", size = 706758467, upload-time = "2025-06-06T21:54:08.597Z" }, + { url = "https://files.pythonhosted.org/packages/f1/84/26025437c1e6b61a707442184fa0c03d083b661adf3a3eecfd6d21677740/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:6ed29ffaee1176c612daf442e4dd6cfeb6a0caa43ddcbeb59da94953030b1be4", size = 433781201, upload-time = "2026-02-03T20:40:53.805Z" }, + { url = "https://files.pythonhosted.org/packages/a3/22/0b4b932655d17a6da1b92fa92ab12844b053bb2ac2475e179ba6f043da1e/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:d20e1734305e9d68889a96e3f35094d733ff1f83932ebe462753973e53a572bf", size = 366066321, upload-time = "2026-02-03T20:44:52.837Z" }, ] [[package]] -name = "nvidia-cufft-cu12" -version = "11.3.3.83" +name = "nvidia-cufft" +version = "12.0.0.61" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink-cu12" }, + { name = "nvidia-nvjitlink" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/13/ee4e00f30e676b66ae65b4f08cb5bcbb8392c03f54f2d5413ea99a5d1c80/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74", size = 193118695, upload-time = "2025-03-07T01:45:27.821Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2f/7b57e29836ea8714f81e9898409196f47d772d5ddedddf1592eadb8ab743/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c44f692dce8fd5ffd3e3df134b6cdb9c2f72d99cf40b62c32dde45eea9ddad3", size = 214085489, upload-time = "2025-09-04T08:31:56.044Z" }, ] [[package]] -name = "nvidia-cufile-cu12" -version = "1.13.1.3" +name = "nvidia-cufile" +version = "1.15.1.6" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/fe/1bcba1dfbfb8d01be8d93f07bfc502c93fa23afa6fd5ab3fc7c1df71038a/nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d069003be650e131b21c932ec3d8969c1715379251f8d23a1860554b1cb24fc", size = 1197834, upload-time = "2025-03-07T01:45:50.723Z" }, + { url = "https://files.pythonhosted.org/packages/3f/70/4f193de89a48b71714e74602ee14d04e4019ad36a5a9f20c425776e72cd6/nvidia_cufile-1.15.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08a3ecefae5a01c7f5117351c64f17c7c62efa5fffdbe24fc7d298da19cd0b44", size = 1223672, upload-time = "2025-09-04T08:32:22.779Z" }, + { url = "https://files.pythonhosted.org/packages/ab/73/cc4a14c9813a8a0d509417cf5f4bdaba76e924d58beb9864f5a7baceefbf/nvidia_cufile-1.15.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:bdc0deedc61f548bddf7733bdc216456c2fdb101d020e1ab4b88d232d5e2f6d1", size = 1136992, upload-time = "2025-09-04T08:32:14.119Z" }, ] [[package]] -name = "nvidia-curand-cu12" -version = "10.3.9.90" +name = "nvidia-curand" +version = "10.4.0.35" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/aa/6584b56dc84ebe9cf93226a5cde4d99080c8e90ab40f0c27bda7a0f29aa1/nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:b32331d4f4df5d6eefa0554c565b626c7216f87a06a4f56fab27c3b68a830ec9", size = 63619976, upload-time = "2025-03-07T01:46:23.323Z" }, + { url = "https://files.pythonhosted.org/packages/1e/72/7c2ae24fb6b63a32e6ae5d241cc65263ea18d08802aaae087d9f013335a2/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:133df5a7509c3e292aaa2b477afd0194f06ce4ea24d714d616ff36439cee349a", size = 61962106, upload-time = "2025-08-04T10:21:41.128Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9f/be0a41ca4a4917abf5cb9ae0daff1a6060cc5de950aec0396de9f3b52bc5/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:1aee33a5da6e1db083fe2b90082def8915f30f3248d5896bcec36a579d941bfc", size = 59544258, upload-time = "2025-08-04T10:22:03.992Z" }, ] [[package]] -name = "nvidia-cusolver-cu12" -version = "11.7.3.90" +name = "nvidia-cusolver" +version = "12.0.4.66" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas-cu12" }, - { name = "nvidia-cusparse-cu12" }, - { name = "nvidia-nvjitlink-cu12" }, + { name = "nvidia-cublas" }, + { name = "nvidia-cusparse" }, + { name = "nvidia-nvjitlink" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450", size = 267506905, upload-time = "2025-03-07T01:47:16.273Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" }, + { url = "https://files.pythonhosted.org/packages/5f/67/cba3777620cdacb99102da4042883709c41c709f4b6323c10781a9c3aa34/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0a759da5dea5c0ea10fd307de75cdeb59e7ea4fcb8add0924859b944babf1112", size = 200941980, upload-time = "2025-09-04T08:33:22.767Z" }, ] [[package]] -name = "nvidia-cusparse-cu12" -version = "12.5.8.93" +name = "nvidia-cusparse" +version = "12.6.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink-cu12" }, + { name = "nvidia-nvjitlink" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/f5/e1854cb2f2bcd4280c44736c93550cc300ff4b8c95ebe370d0aa7d2b473d/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b", size = 288216466, upload-time = "2025-03-07T01:48:13.779Z" }, + { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" }, + { url = "https://files.pythonhosted.org/packages/fa/18/623c77619c31d62efd55302939756966f3ecc8d724a14dab2b75f1508850/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b3c89c88d01ee0e477cb7f82ef60a11a4bcd57b6b87c33f789350b59759360b", size = 145942937, upload-time = "2025-09-04T08:33:58.029Z" }, ] [[package]] -name = "nvidia-cusparselt-cu12" -version = "0.7.1" +name = "nvidia-cusparselt-cu13" +version = "0.8.0" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/79/12978b96bd44274fe38b5dde5cfb660b1d114f70a65ef962bcbbed99b549/nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f1bb701d6b930d5a7cea44c19ceb973311500847f81b634d802b7b539dc55623", size = 287193691, upload-time = "2025-02-26T00:15:44.104Z" }, + { url = "https://files.pythonhosted.org/packages/46/10/8dcd1175260706a2fc92a16a52e306b71d4c1ea0b0cc4a9484183399818a/nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:400c6ed1cf6780fc6efedd64ec9f1345871767e6a1a0a552a1ea0578117ea77c", size = 220791277, upload-time = "2025-08-13T19:22:40.982Z" }, + { url = "https://files.pythonhosted.org/packages/fd/53/43b0d71f4e702fa9733f8b4571fdca50a8813f1e450b656c239beff12315/nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:25e30a8a7323935d4ad0340b95a0b69926eee755767e8e0b1cf8dd85b197d3fd", size = 169884119, upload-time = "2025-08-13T19:23:41.967Z" }, ] [[package]] -name = "nvidia-nccl-cu12" -version = "2.27.5" +name = "nvidia-nccl-cu13" +version = "2.28.9" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/89/f7a07dc961b60645dbbf42e80f2bc85ade7feb9a491b11a1e973aa00071f/nvidia_nccl_cu12-2.27.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ad730cf15cb5d25fe849c6e6ca9eb5b76db16a80f13f425ac68d8e2e55624457", size = 322348229, upload-time = "2025-06-26T04:11:28.385Z" }, + { url = "https://files.pythonhosted.org/packages/39/55/1920646a2e43ffd4fc958536b276197ed740e9e0c54105b4bb3521591fc7/nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:01c873ba1626b54caa12272ed228dc5b2781545e0ae8ba3f432a8ef1c6d78643", size = 196561677, upload-time = "2025-11-18T05:49:03.45Z" }, + { url = "https://files.pythonhosted.org/packages/b0/b4/878fefaad5b2bcc6fcf8d474a25e3e3774bc5133e4b58adff4d0bca238bc/nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:e4553a30f34195f3fa1da02a6da3d6337d28f2003943aa0a3d247bbc25fefc42", size = 196493177, upload-time = "2025-11-18T05:49:17.677Z" }, ] [[package]] -name = "nvidia-nvjitlink-cu12" -version = "12.8.93" +name = "nvidia-nvjitlink" +version = "13.0.88" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/74/86a07f1d0f42998ca31312f998bd3b9a7eff7f52378f4f270c8679c77fb9/nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:81ff63371a7ebd6e6451970684f916be2eab07321b73c9d244dc2b4da7f73b88", size = 39254836, upload-time = "2025-03-07T01:49:55.661Z" }, + { url = "https://files.pythonhosted.org/packages/56/7a/123e033aaff487c77107195fa5a2b8686795ca537935a24efae476c41f05/nvidia_nvjitlink-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:13a74f429e23b921c1109976abefacc69835f2f433ebd323d3946e11d804e47b", size = 40713933, upload-time = "2025-09-04T08:35:43.553Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2c/93c5250e64df4f894f1cbb397c6fd71f79813f9fd79d7cd61de3f97b3c2d/nvidia_nvjitlink-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e931536ccc7d467a98ba1d8b89ff7fa7f1fa3b13f2b0069118cd7f47bff07d0c", size = 38768748, upload-time = "2025-09-04T08:35:20.008Z" }, ] [[package]] -name = "nvidia-nvshmem-cu12" +name = "nvidia-nvshmem-cu13" version = "3.4.5" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/09/6ea3ea725f82e1e76684f0708bbedd871fc96da89945adeba65c3835a64c/nvidia_nvshmem_cu12-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:042f2500f24c021db8a06c5eec2539027d57460e1c1a762055a6554f72c369bd", size = 139103095, upload-time = "2025-09-06T00:32:31.266Z" }, + { url = "https://files.pythonhosted.org/packages/dc/0f/05cc9c720236dcd2db9c1ab97fff629e96821be2e63103569da0c9b72f19/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dc2a197f38e5d0376ad52cd1a2a3617d3cdc150fd5966f4aee9bcebb1d68fe9", size = 60215947, upload-time = "2025-09-06T00:32:20.022Z" }, + { url = "https://files.pythonhosted.org/packages/3c/35/a9bf80a609e74e3b000fef598933235c908fcefcef9026042b8e6dfde2a9/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:290f0a2ee94c9f3687a02502f3b9299a9f9fe826e6d0287ee18482e78d495b80", size = 60412546, upload-time = "2025-09-06T00:32:41.564Z" }, ] [[package]] -name = "nvidia-nvtx-cu12" -version = "12.8.90" +name = "nvidia-nvtx" +version = "13.0.85" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/eb/86626c1bbc2edb86323022371c39aa48df6fd8b0a1647bc274577f72e90b/nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b17e2001cc0d751a5bc2c6ec6d26ad95913324a4adb86788c944f8ce9ba441f", size = 89954, upload-time = "2025-03-07T01:42:44.131Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f3/d86c845465a2723ad7e1e5c36dcd75ddb82898b3f53be47ebd429fb2fa5d/nvidia_nvtx-13.0.85-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4936d1d6780fbe68db454f5e72a42ff64d1fd6397df9f363ae786930fd5c1cd4", size = 148047, upload-time = "2025-09-04T08:29:01.761Z" }, + { url = "https://files.pythonhosted.org/packages/a8/64/3708a90d1ebe202ffdeb7185f878a3c84d15c2b2c31858da2ce0583e2def/nvidia_nvtx-13.0.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb7780edb6b14107373c835bf8b72e7a178bac7367e23da7acb108f973f157a6", size = 148878, upload-time = "2025-09-04T08:28:53.627Z" }, ] [[package]] name = "optuna" -version = "4.7.0" +version = "4.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alembic" }, @@ -2147,9 +2279,9 @@ dependencies = [ { name = "sqlalchemy" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/b2/b5e12de7b4486556fe2257611b55dbabf30d0300bdb031831aa943ad20e4/optuna-4.7.0.tar.gz", hash = "sha256:d91817e2079825557bd2e97de2e8c9ae260bfc99b32712502aef8a5095b2d2c0", size = 479740, upload-time = "2026-01-19T05:45:52.604Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/9b/62f120fb2ecbc4338bee70c5a3671c8e561714f3aa1a046b897ff142050e/optuna-4.8.0.tar.gz", hash = "sha256:6f7043e9f8ecb5e607af86a7eb00fb5ec2be26c3b08c201209a73d36aff37a38", size = 482603, upload-time = "2026-03-16T04:59:58.659Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/75/d1/6c8a4fbb38a9e3565f5c36b871262a85ecab3da48120af036b1e4937a15c/optuna-4.7.0-py3-none-any.whl", hash = "sha256:e41ec84018cecc10eabf28143573b1f0bde0ba56dba8151631a590ecbebc1186", size = 413894, upload-time = "2026-01-19T05:45:50.815Z" }, + { url = "https://files.pythonhosted.org/packages/ac/24/7c731839566d30dc70556d9824ef17692d896c15e3df627bce8c16f753e1/optuna-4.8.0-py3-none-any.whl", hash = "sha256:c57a7682679c36bfc9bca0da430698179e513874074b71bebedb0334964ab930", size = 419456, upload-time = "2026-03-16T04:59:56.977Z" }, ] [[package]] @@ -2302,7 +2434,7 @@ wheels = [ [[package]] name = "pint" -version = "0.25.2" +version = "0.25.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "flexcache" }, @@ -2310,18 +2442,18 @@ dependencies = [ { name = "platformdirs" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5f/74/bc3f671997158aef171194c3c4041e549946f4784b8690baa0626a0a164b/pint-0.25.2.tar.gz", hash = "sha256:85a45d1da8fe9c9f7477fed8aef59ad2b939af3d6611507e1a9cbdacdcd3450a", size = 254467, upload-time = "2025-11-06T22:08:09.184Z" } +sdist = { url = "https://files.pythonhosted.org/packages/52/9d/b1379cdbd33a49d17d627bc24e2b63cca06a1c5343b38072d2889499e82e/pint-0.25.3.tar.gz", hash = "sha256:f8f5df6cf65314d74da1ade1bf96f8e3e4d0c41b51577ac53c49e7d44ca5acee", size = 255106, upload-time = "2026-03-19T21:57:08.72Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/88/550d41e81e6d43335603a960cd9c75c1d88f9cf01bc9d4ee8e86290aba7d/pint-0.25.2-py3-none-any.whl", hash = "sha256:ca35ab1d8eeeb6f7d9942b3cb5f34ca42b61cdd5fb3eae79531553dcca04dda7", size = 306762, upload-time = "2025-11-06T22:08:07.745Z" }, + { url = "https://files.pythonhosted.org/packages/1b/dd/a9fe6a0a09512da23951c68bf36466aeecd89def3183dc095edbc807ddc5/pint-0.25.3-py3-none-any.whl", hash = "sha256:27eb25143bd5de9fcc4d5a4b484f16faf6b4615aa93ece6b3373a8c1a3c1b97d", size = 307488, upload-time = "2026-03-19T21:57:07.022Z" }, ] [[package]] name = "platformdirs" -version = "4.9.2" +version = "4.9.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394, upload-time = "2026-02-16T03:56:10.574Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/56/8d4c30c8a1d07013911a8fdbd8f89440ef9f08d07a1b50ab8ca8be5a20f9/platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934", size = 28737, upload-time = "2026-03-05T18:34:13.271Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/31/05e764397056194206169869b50cf2fee4dbbbc71b344705b9c0d878d4d8/platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd", size = 21168, upload-time = "2026-02-16T03:56:08.891Z" }, + { url = "https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868", size = 21216, upload-time = "2026-03-05T18:34:12.172Z" }, ] [[package]] @@ -2372,17 +2504,17 @@ wheels = [ [[package]] name = "protobuf" -version = "7.34.0" +version = "7.34.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/00/04a2ab36b70a52d0356852979e08b44edde0435f2115dc66e25f2100f3ab/protobuf-7.34.0.tar.gz", hash = "sha256:3871a3df67c710aaf7bb8d214cc997342e63ceebd940c8c7fc65c9b3d697591a", size = 454726, upload-time = "2026-02-27T00:30:25.421Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/6b/a0e95cad1ad7cc3f2c6821fcab91671bd5b78bd42afb357bb4765f29bc41/protobuf-7.34.1.tar.gz", hash = "sha256:9ce42245e704cc5027be797c1db1eb93184d44d1cdd71811fb2d9b25ad541280", size = 454708, upload-time = "2026-03-20T17:34:47.036Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/c4/6322ab5c8f279c4c358bc14eb8aefc0550b97222a39f04eb3c1af7a830fa/protobuf-7.34.0-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e329966799f2c271d5e05e236459fe1cbfdb8755aaa3b0914fa60947ddea408", size = 429248, upload-time = "2026-02-27T00:30:14.924Z" }, - { url = "https://files.pythonhosted.org/packages/45/99/b029bbbc61e8937545da5b79aa405ab2d9cf307a728f8c9459ad60d7a481/protobuf-7.34.0-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:9d7a5005fb96f3c1e64f397f91500b0eb371b28da81296ae73a6b08a5b76cdd6", size = 325753, upload-time = "2026-02-27T00:30:17.247Z" }, - { url = "https://files.pythonhosted.org/packages/cc/79/09f02671eb75b251c5550a1c48e7b3d4b0623efd7c95a15a50f6f9fc1e2e/protobuf-7.34.0-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:4a72a8ec94e7a9f7ef7fe818ed26d073305f347f8b3b5ba31e22f81fd85fca02", size = 340200, upload-time = "2026-02-27T00:30:18.672Z" }, - { url = "https://files.pythonhosted.org/packages/b5/57/89727baef7578897af5ed166735ceb315819f1c184da8c3441271dbcfde7/protobuf-7.34.0-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:964cf977e07f479c0697964e83deda72bcbc75c3badab506fb061b352d991b01", size = 324268, upload-time = "2026-02-27T00:30:20.088Z" }, - { url = "https://files.pythonhosted.org/packages/1f/3e/38ff2ddee5cc946f575c9d8cc822e34bde205cf61acf8099ad88ef19d7d2/protobuf-7.34.0-cp310-abi3-win32.whl", hash = "sha256:f791ec509707a1d91bd02e07df157e75e4fb9fbdad12a81b7396201ec244e2e3", size = 426628, upload-time = "2026-02-27T00:30:21.555Z" }, - { url = "https://files.pythonhosted.org/packages/cb/71/7c32eaf34a61a1bae1b62a2ac4ffe09b8d1bb0cf93ad505f42040023db89/protobuf-7.34.0-cp310-abi3-win_amd64.whl", hash = "sha256:9f9079f1dde4e32342ecbd1c118d76367090d4aaa19da78230c38101c5b3dd40", size = 437901, upload-time = "2026-02-27T00:30:22.836Z" }, - { url = "https://files.pythonhosted.org/packages/a4/e7/14dc9366696dcb53a413449881743426ed289d687bcf3d5aee4726c32ebb/protobuf-7.34.0-py3-none-any.whl", hash = "sha256:e3b914dd77fa33fa06ab2baa97937746ab25695f389869afdf03e81f34e45dc7", size = 170716, upload-time = "2026-02-27T00:30:23.994Z" }, + { url = "https://files.pythonhosted.org/packages/ec/11/3325d41e6ee15bf1125654301211247b042563bcc898784351252549a8ad/protobuf-7.34.1-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:d8b2cc79c4d8f62b293ad9b11ec3aebce9af481fa73e64556969f7345ebf9fc7", size = 429247, upload-time = "2026-03-20T17:34:37.024Z" }, + { url = "https://files.pythonhosted.org/packages/eb/9d/aa69df2724ff63efa6f72307b483ce0827f4347cc6d6df24b59e26659fef/protobuf-7.34.1-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:5185e0e948d07abe94bb76ec9b8416b604cfe5da6f871d67aad30cbf24c3110b", size = 325753, upload-time = "2026-03-20T17:34:38.751Z" }, + { url = "https://files.pythonhosted.org/packages/92/e8/d174c91fd48e50101943f042b09af9029064810b734e4160bbe282fa1caa/protobuf-7.34.1-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:403b093a6e28a960372b44e5eb081775c9b056e816a8029c61231743d63f881a", size = 340198, upload-time = "2026-03-20T17:34:39.871Z" }, + { url = "https://files.pythonhosted.org/packages/53/1b/3b431694a4dc6d37b9f653f0c64b0a0d9ec074ee810710c0c3da21d67ba7/protobuf-7.34.1-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:8ff40ce8cd688f7265326b38d5a1bed9bfdf5e6723d49961432f83e21d5713e4", size = 324267, upload-time = "2026-03-20T17:34:41.1Z" }, + { url = "https://files.pythonhosted.org/packages/85/29/64de04a0ac142fb685fd09999bc3d337943fb386f3a0ec57f92fd8203f97/protobuf-7.34.1-cp310-abi3-win32.whl", hash = "sha256:34b84ce27680df7cca9f231043ada0daa55d0c44a2ddfaa58ec1d0d89d8bf60a", size = 426628, upload-time = "2026-03-20T17:34:42.536Z" }, + { url = "https://files.pythonhosted.org/packages/4d/87/cb5e585192a22b8bd457df5a2c16a75ea0db9674c3a0a39fc9347d84e075/protobuf-7.34.1-cp310-abi3-win_amd64.whl", hash = "sha256:e97b55646e6ce5cbb0954a8c28cd39a5869b59090dfaa7df4598a7fba869468c", size = 437901, upload-time = "2026-03-20T17:34:44.112Z" }, + { url = "https://files.pythonhosted.org/packages/88/95/608f665226bca68b736b79e457fded9a2a38c4f4379a4a7614303d9db3bc/protobuf-7.34.1-py3-none-any.whl", hash = "sha256:bb3812cd53aefea2b028ef42bd780f5b96407247f20c6ef7c679807e9d188f11", size = 170715, upload-time = "2026-03-20T17:34:45.384Z" }, ] [[package]] @@ -2471,11 +2603,11 @@ wheels = [ [[package]] name = "pygments" -version = "2.19.2" +version = "2.20.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] [[package]] @@ -2505,16 +2637,16 @@ wheels = [ [[package]] name = "pytest-cov" -version = "7.0.0" +version = "7.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage", extra = ["toml"] }, { name = "pluggy" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, ] [[package]] @@ -2551,24 +2683,24 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.1.0" +version = "1.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/82/bb/93a3e83bdf9322c7e21cafd092e56a4a17c4d8ef4277b6eb01af1a540a6f/python_discovery-1.1.0.tar.gz", hash = "sha256:447941ba1aed8cc2ab7ee3cb91be5fc137c5bdbb05b7e6ea62fbdcb66e50b268", size = 55674, upload-time = "2026-02-26T09:42:49.668Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/88/815e53084c5079a59df912825a279f41dd2e0df82281770eadc732f5352c/python_discovery-1.2.1.tar.gz", hash = "sha256:180c4d114bff1c32462537eac5d6a332b768242b76b69c0259c7d14b1b680c9e", size = 58457, upload-time = "2026-03-26T22:30:44.496Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/54/82a6e2ef37f0f23dccac604b9585bdcbd0698604feb64807dcb72853693e/python_discovery-1.1.0-py3-none-any.whl", hash = "sha256:a162893b8809727f54594a99ad2179d2ede4bf953e12d4c7abc3cc9cdbd1437b", size = 30687, upload-time = "2026-02-26T09:42:48.548Z" }, + { url = "https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl", hash = "sha256:b6a957b24c1cd79252484d3566d1b49527581d46e789aaf43181005e56201502", size = 31674, upload-time = "2026-03-26T22:30:43.396Z" }, ] [[package]] name = "python-json-logger" -version = "4.0.0" +version = "4.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/29/bf/eca6a3d43db1dae7070f70e160ab20b807627ba953663ba07928cdd3dc58/python_json_logger-4.0.0.tar.gz", hash = "sha256:f58e68eb46e1faed27e0f574a55a0455eecd7b8a5b88b85a784519ba3cff047f", size = 17683, upload-time = "2025-10-06T04:15:18.984Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/ff/3cc9165fd44106973cd7ac9facb674a65ed853494592541d339bdc9a30eb/python_json_logger-4.1.0.tar.gz", hash = "sha256:b396b9e3ed782b09ff9d6e4f1683d46c83ad0d35d2e407c09a9ebbf038f88195", size = 17573, upload-time = "2026-03-29T04:39:56.805Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl", hash = "sha256:af09c9daf6a813aa4cc7180395f50f2a9e5fa056034c9953aec92e381c5ba1e2", size = 15548, upload-time = "2025-10-06T04:15:17.553Z" }, + { url = "https://files.pythonhosted.org/packages/27/be/0631a861af4d1c875f096c07d34e9a63639560a717130e7a87cbc82b7e3f/python_json_logger-4.1.0-py3-none-any.whl", hash = "sha256:132994765cf75bf44554be9aa49b06ef2345d23661a96720262716438141b6b2", size = 15021, upload-time = "2026-03-29T04:39:55.266Z" }, ] [[package]] @@ -2815,7 +2947,7 @@ wheels = [ [[package]] name = "requests" -version = "2.32.5" +version = "2.33.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -2823,9 +2955,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/64/8860370b167a9721e8956ae116825caff829224fbca0ca6e7bf8ddef8430/requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652", size = 134232, upload-time = "2026-03-25T15:10:41.586Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, + { url = "https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b", size = 65017, upload-time = "2026-03-25T15:10:40.382Z" }, ] [[package]] @@ -3024,27 +3156,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/da/31/d6e536cdebb6568ae75a7f00e4b4819ae0ad2640c3604c305a0428680b0c/ruff-0.15.4.tar.gz", hash = "sha256:3412195319e42d634470cc97aa9803d07e9d5c9223b99bcb1518f0c725f26ae1", size = 4569550, upload-time = "2026-02-26T20:04:14.959Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/82/c11a03cfec3a4d26a0ea1e571f0f44be5993b923f905eeddfc397c13d360/ruff-0.15.4-py3-none-linux_armv6l.whl", hash = "sha256:a1810931c41606c686bae8b5b9a8072adac2f611bb433c0ba476acba17a332e0", size = 10453333, upload-time = "2026-02-26T20:04:20.093Z" }, - { url = "https://files.pythonhosted.org/packages/ce/5d/6a1f271f6e31dffb31855996493641edc3eef8077b883eaf007a2f1c2976/ruff-0.15.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5a1632c66672b8b4d3e1d1782859e98d6e0b4e70829530666644286600a33992", size = 10853356, upload-time = "2026-02-26T20:04:05.808Z" }, - { url = "https://files.pythonhosted.org/packages/b1/d8/0fab9f8842b83b1a9c2bf81b85063f65e93fb512e60effa95b0be49bfc54/ruff-0.15.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a4386ba2cd6c0f4ff75252845906acc7c7c8e1ac567b7bc3d373686ac8c222ba", size = 10187434, upload-time = "2026-02-26T20:03:54.656Z" }, - { url = "https://files.pythonhosted.org/packages/85/cc/cc220fd9394eff5db8d94dec199eec56dd6c9f3651d8869d024867a91030/ruff-0.15.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2496488bdfd3732747558b6f95ae427ff066d1fcd054daf75f5a50674411e75", size = 10535456, upload-time = "2026-02-26T20:03:52.738Z" }, - { url = "https://files.pythonhosted.org/packages/fa/0f/bced38fa5cf24373ec767713c8e4cadc90247f3863605fb030e597878661/ruff-0.15.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3f1c4893841ff2d54cbda1b2860fa3260173df5ddd7b95d370186f8a5e66a4ac", size = 10287772, upload-time = "2026-02-26T20:04:08.138Z" }, - { url = "https://files.pythonhosted.org/packages/2b/90/58a1802d84fed15f8f281925b21ab3cecd813bde52a8ca033a4de8ab0e7a/ruff-0.15.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:820b8766bd65503b6c30aaa6331e8ef3a6e564f7999c844e9a547c40179e440a", size = 11049051, upload-time = "2026-02-26T20:04:03.53Z" }, - { url = "https://files.pythonhosted.org/packages/d2/ac/b7ad36703c35f3866584564dc15f12f91cb1a26a897dc2fd13d7cb3ae1af/ruff-0.15.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9fb74bab47139c1751f900f857fa503987253c3ef89129b24ed375e72873e85", size = 11890494, upload-time = "2026-02-26T20:04:10.497Z" }, - { url = "https://files.pythonhosted.org/packages/93/3d/3eb2f47a39a8b0da99faf9c54d3eb24720add1e886a5309d4d1be73a6380/ruff-0.15.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f80c98765949c518142b3a50a5db89343aa90f2c2bf7799de9986498ae6176db", size = 11326221, upload-time = "2026-02-26T20:04:12.84Z" }, - { url = "https://files.pythonhosted.org/packages/ff/90/bf134f4c1e5243e62690e09d63c55df948a74084c8ac3e48a88468314da6/ruff-0.15.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:451a2e224151729b3b6c9ffb36aed9091b2996fe4bdbd11f47e27d8f2e8888ec", size = 11168459, upload-time = "2026-02-26T20:04:00.969Z" }, - { url = "https://files.pythonhosted.org/packages/b5/e5/a64d27688789b06b5d55162aafc32059bb8c989c61a5139a36e1368285eb/ruff-0.15.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a8f157f2e583c513c4f5f896163a93198297371f34c04220daf40d133fdd4f7f", size = 11104366, upload-time = "2026-02-26T20:03:48.099Z" }, - { url = "https://files.pythonhosted.org/packages/f1/f6/32d1dcb66a2559763fc3027bdd65836cad9eb09d90f2ed6a63d8e9252b02/ruff-0.15.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:917cc68503357021f541e69b35361c99387cdbbf99bd0ea4aa6f28ca99ff5338", size = 10510887, upload-time = "2026-02-26T20:03:45.771Z" }, - { url = "https://files.pythonhosted.org/packages/ff/92/22d1ced50971c5b6433aed166fcef8c9343f567a94cf2b9d9089f6aa80fe/ruff-0.15.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e9737c8161da79fd7cfec19f1e35620375bd8b2a50c3e77fa3d2c16f574105cc", size = 10285939, upload-time = "2026-02-26T20:04:22.42Z" }, - { url = "https://files.pythonhosted.org/packages/e6/f4/7c20aec3143837641a02509a4668fb146a642fd1211846634edc17eb5563/ruff-0.15.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:291258c917539e18f6ba40482fe31d6f5ac023994ee11d7bdafd716f2aab8a68", size = 10765471, upload-time = "2026-02-26T20:03:58.924Z" }, - { url = "https://files.pythonhosted.org/packages/d0/09/6d2f7586f09a16120aebdff8f64d962d7c4348313c77ebb29c566cefc357/ruff-0.15.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3f83c45911da6f2cd5936c436cf86b9f09f09165f033a99dcf7477e34041cbc3", size = 11263382, upload-time = "2026-02-26T20:04:24.424Z" }, - { url = "https://files.pythonhosted.org/packages/1b/fa/2ef715a1cd329ef47c1a050e10dee91a9054b7ce2fcfdd6a06d139afb7ec/ruff-0.15.4-py3-none-win32.whl", hash = "sha256:65594a2d557d4ee9f02834fcdf0a28daa8b3b9f6cb2cb93846025a36db47ef22", size = 10506664, upload-time = "2026-02-26T20:03:50.56Z" }, - { url = "https://files.pythonhosted.org/packages/d0/a8/c688ef7e29983976820d18710f955751d9f4d4eb69df658af3d006e2ba3e/ruff-0.15.4-py3-none-win_amd64.whl", hash = "sha256:04196ad44f0df220c2ece5b0e959c2f37c777375ec744397d21d15b50a75264f", size = 11651048, upload-time = "2026-02-26T20:04:17.191Z" }, - { url = "https://files.pythonhosted.org/packages/3e/0a/9e1be9035b37448ce2e68c978f0591da94389ade5a5abafa4cf99985d1b2/ruff-0.15.4-py3-none-win_arm64.whl", hash = "sha256:60d5177e8cfc70e51b9c5fad936c634872a74209f934c1e79107d11787ad5453", size = 10966776, upload-time = "2026-02-26T20:03:56.908Z" }, +version = "0.15.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/14/b0/73cf7550861e2b4824950b8b52eebdcc5adc792a00c514406556c5b80817/ruff-0.15.8.tar.gz", hash = "sha256:995f11f63597ee362130d1d5a327a87cb6f3f5eae3094c620bcc632329a4d26e", size = 4610921, upload-time = "2026-03-26T18:39:38.675Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/92/c445b0cd6da6e7ae51e954939cb69f97e008dbe750cfca89b8cedc081be7/ruff-0.15.8-py3-none-linux_armv6l.whl", hash = "sha256:cbe05adeba76d58162762d6b239c9056f1a15a55bd4b346cfd21e26cd6ad7bc7", size = 10527394, upload-time = "2026-03-26T18:39:41.566Z" }, + { url = "https://files.pythonhosted.org/packages/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d3e3d0b6ba8dca1b7ef9ab80a28e840a20070c4b62e56d675c24f366ef330570", size = 10905693, upload-time = "2026-03-26T18:39:30.364Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ee3ae5c65a42f273f126686353f2e08ff29927b7b7e203b711514370d500de3", size = 10323044, upload-time = "2026-03-26T18:39:33.37Z" }, + { url = "https://files.pythonhosted.org/packages/67/18/1bf38e20914a05e72ef3b9569b1d5c70a7ef26cd188d69e9ca8ef588d5bf/ruff-0.15.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdce027ada77baa448077ccc6ebb2fa9c3c62fd110d8659d601cf2f475858d94", size = 10629135, upload-time = "2026-03-26T18:39:44.142Z" }, + { url = "https://files.pythonhosted.org/packages/d2/e9/138c150ff9af60556121623d41aba18b7b57d95ac032e177b6a53789d279/ruff-0.15.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12e617fc01a95e5821648a6df341d80456bd627bfab8a829f7cfc26a14a4b4a3", size = 10348041, upload-time = "2026-03-26T18:39:52.178Z" }, + { url = "https://files.pythonhosted.org/packages/02/f1/5bfb9298d9c323f842c5ddeb85f1f10ef51516ac7a34ba446c9347d898df/ruff-0.15.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:432701303b26416d22ba696c39f2c6f12499b89093b61360abc34bcc9bf07762", size = 11121987, upload-time = "2026-03-26T18:39:55.195Z" }, + { url = "https://files.pythonhosted.org/packages/10/11/6da2e538704e753c04e8d86b1fc55712fdbdcc266af1a1ece7a51fff0d10/ruff-0.15.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d910ae974b7a06a33a057cb87d2a10792a3b2b3b35e33d2699fdf63ec8f6b17a", size = 11951057, upload-time = "2026-03-26T18:39:19.18Z" }, + { url = "https://files.pythonhosted.org/packages/83/f0/c9208c5fd5101bf87002fed774ff25a96eea313d305f1e5d5744698dc314/ruff-0.15.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2033f963c43949d51e6fdccd3946633c6b37c484f5f98c3035f49c27395a8ab8", size = 11464613, upload-time = "2026-03-26T18:40:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f29b989a55572fb885b77464cf24af05500806ab4edf9a0fd8977f9759d85b1", size = 11257557, upload-time = "2026-03-26T18:39:57.972Z" }, + { url = "https://files.pythonhosted.org/packages/71/8c/382a9620038cf6906446b23ce8632ab8c0811b8f9d3e764f58bedd0c9a6f/ruff-0.15.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:ac51d486bf457cdc985a412fb1801b2dfd1bd8838372fc55de64b1510eff4bec", size = 11169440, upload-time = "2026-03-26T18:39:22.205Z" }, + { url = "https://files.pythonhosted.org/packages/4d/0d/0994c802a7eaaf99380085e4e40c845f8e32a562e20a38ec06174b52ef24/ruff-0.15.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c9861eb959edab053c10ad62c278835ee69ca527b6dcd72b47d5c1e5648964f6", size = 10605963, upload-time = "2026-03-26T18:39:46.682Z" }, + { url = "https://files.pythonhosted.org/packages/19/aa/d624b86f5b0aad7cef6bbf9cd47a6a02dfdc4f72c92a337d724e39c9d14b/ruff-0.15.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8d9a5b8ea13f26ae90838afc33f91b547e61b794865374f114f349e9036835fb", size = 10357484, upload-time = "2026-03-26T18:39:49.176Z" }, + { url = "https://files.pythonhosted.org/packages/35/c3/e0b7835d23001f7d999f3895c6b569927c4d39912286897f625736e1fd04/ruff-0.15.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c2a33a529fb3cbc23a7124b5c6ff121e4d6228029cba374777bd7649cc8598b8", size = 10830426, upload-time = "2026-03-26T18:40:03.702Z" }, + { url = "https://files.pythonhosted.org/packages/f0/51/ab20b322f637b369383adc341d761eaaa0f0203d6b9a7421cd6e783d81b9/ruff-0.15.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:75e5cd06b1cf3f47a3996cfc999226b19aa92e7cce682dcd62f80d7035f98f49", size = 11345125, upload-time = "2026-03-26T18:39:27.799Z" }, + { url = "https://files.pythonhosted.org/packages/37/e6/90b2b33419f59d0f2c4c8a48a4b74b460709a557e8e0064cf33ad894f983/ruff-0.15.8-py3-none-win32.whl", hash = "sha256:bc1f0a51254ba21767bfa9a8b5013ca8149dcf38092e6a9eb704d876de94dc34", size = 10571959, upload-time = "2026-03-26T18:39:36.117Z" }, + { url = "https://files.pythonhosted.org/packages/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl", hash = "sha256:04f79eff02a72db209d47d665ba7ebcad609d8918a134f86cb13dd132159fc89", size = 11743893, upload-time = "2026-03-26T18:39:25.01Z" }, + { url = "https://files.pythonhosted.org/packages/15/e2/77be4fff062fa78d9b2a4dea85d14785dac5f1d0c1fb58ed52331f0ebe28/ruff-0.15.8-py3-none-win_arm64.whl", hash = "sha256:cf891fa8e3bb430c0e7fac93851a5978fc99c8fa2c053b57b118972866f8e5f2", size = 11048175, upload-time = "2026-03-26T18:40:01.06Z" }, ] [[package]] @@ -3195,11 +3327,11 @@ wheels = [ [[package]] name = "setuptools" -version = "82.0.0" +version = "81.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/f3/748f4d6f65d1756b9ae577f329c951cda23fb900e4de9f70900ced962085/setuptools-82.0.0.tar.gz", hash = "sha256:22e0a2d69474c6ae4feb01951cb69d515ed23728cf96d05513d36e42b62b37cb", size = 1144893, upload-time = "2026-02-08T15:08:40.206Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/1c/73e719955c59b8e424d015ab450f51c0af856ae46ea2da83eba51cc88de1/setuptools-81.0.0.tar.gz", hash = "sha256:487b53915f52501f0a79ccfd0c02c165ffe06631443a886740b91af4b7a5845a", size = 1198299, upload-time = "2026-02-06T21:10:39.601Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl", hash = "sha256:70b18734b607bd1da571d097d236cfcfacaf01de45717d59e6e04b96877532e0", size = 1003468, upload-time = "2026-02-08T15:08:38.723Z" }, + { url = "https://files.pythonhosted.org/packages/e1/e3/c164c88b2e5ce7b24d667b9bd83589cf4f3520d97cad01534cd3c4f55fdb/setuptools-81.0.0-py3-none-any.whl", hash = "sha256:fdd925d5c5d9f62e4b74b30d6dd7828ce236fd6ed998a08d81de62ce5a6310d6", size = 1062021, upload-time = "2026-02-06T21:10:37.175Z" }, ] [[package]] @@ -3222,55 +3354,55 @@ wheels = [ [[package]] name = "sqlalchemy" -version = "2.0.47" +version = "2.0.48" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cd/4b/1e00561093fe2cd8eef09d406da003c8a118ff02d6548498c1ae677d68d9/sqlalchemy-2.0.47.tar.gz", hash = "sha256:e3e7feb57b267fe897e492b9721ae46d5c7de6f9e8dee58aacf105dc4e154f3d", size = 9886323, upload-time = "2026-02-24T16:34:27.947Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/13/886338d3e8ab5ddcfe84d54302c749b1793e16c4bba63d7004e3f7baa8ec/sqlalchemy-2.0.47-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a1dbf0913879c443617d6b64403cf2801c941651db8c60e96d204ed9388d6b0", size = 2157124, upload-time = "2026-02-24T16:43:54.706Z" }, - { url = "https://files.pythonhosted.org/packages/b6/bb/a897f6a66c9986aa9f27f5cf8550637d8a5ea368fd7fb42f6dac3105b4dc/sqlalchemy-2.0.47-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:775effbb97ea3b00c4dd3aeaf3ba8acba6e3e2b4b41d17d67a27e696843dbc95", size = 3313513, upload-time = "2026-02-24T17:29:00.527Z" }, - { url = "https://files.pythonhosted.org/packages/59/fb/69bfae022b681507565ab0d34f0c80aa1e9f954a5a7cbfb0ed054966ac8d/sqlalchemy-2.0.47-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56cc834a3ffac34270cc2a41875e0f40e97aa651f4f3ca1cfbbf421c044cb62b", size = 3313014, upload-time = "2026-02-24T17:27:11.679Z" }, - { url = "https://files.pythonhosted.org/packages/04/f3/0eba329f7c182d53205a228c4fd24651b95489b431ea2bd830887b4c13c4/sqlalchemy-2.0.47-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:49b5e0c7244262f39e767c018e4fdb5e5dbc23cd54c5ddac8eea8f0ba32ef890", size = 3265389, upload-time = "2026-02-24T17:29:02.497Z" }, - { url = "https://files.pythonhosted.org/packages/5c/06/654edc084b3b46ac79e04200d7c46467ae80c759c4ee41c897f9272b036f/sqlalchemy-2.0.47-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:15cd822a3f1f6f77b5b841a30c1a07a07f7dee3385f17e638e1722de9ab683be", size = 3287604, upload-time = "2026-02-24T17:27:13.295Z" }, - { url = "https://files.pythonhosted.org/packages/78/33/c18c8f63b61981219d3aa12321bb7ccee605034d195e868ed94f9727b27c/sqlalchemy-2.0.47-cp311-cp311-win32.whl", hash = "sha256:9847a19548cd283a65e1ce0afd54016598d55ff72682d6fd3e493af6fc044064", size = 2116916, upload-time = "2026-02-24T17:14:37.392Z" }, - { url = "https://files.pythonhosted.org/packages/f5/c6/a59e3f9796fff844e16afbd821db9abfd6e12698db9441a231a96193a100/sqlalchemy-2.0.47-cp311-cp311-win_amd64.whl", hash = "sha256:722abf1c82aeca46a1a0803711244a48a298279eeaec9e02f7bfee9e064182e5", size = 2141587, upload-time = "2026-02-24T17:14:39.746Z" }, - { url = "https://files.pythonhosted.org/packages/80/88/74eb470223ff88ea6572a132c0b8de8c1d8ed7b843d3b44a8a3c77f31d39/sqlalchemy-2.0.47-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4fa91b19d6b9821c04cc8f7aa2476429cc8887b9687c762815aa629f5c0edec1", size = 2155687, upload-time = "2026-02-24T17:05:46.451Z" }, - { url = "https://files.pythonhosted.org/packages/ef/ba/1447d3d558971b036cb93b557595cb5dcdfe728f1c7ac4dec16505ef5756/sqlalchemy-2.0.47-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c5bbbd14eff577c8c79cbfe39a0771eecd20f430f3678533476f0087138f356", size = 3336978, upload-time = "2026-02-24T17:18:04.597Z" }, - { url = "https://files.pythonhosted.org/packages/8a/07/b47472d2ffd0776826f17ccf0b4d01b224c99fbd1904aeb103dffbb4b1cc/sqlalchemy-2.0.47-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5a6c555da8d4280a3c4c78c5b7a3f990cee2b2884e5f934f87a226191682ff7", size = 3349939, upload-time = "2026-02-24T17:27:18.937Z" }, - { url = "https://files.pythonhosted.org/packages/bb/c6/95fa32b79b57769da3e16f054cf658d90940317b5ca0ec20eac84aa19c4f/sqlalchemy-2.0.47-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ed48a1701d24dff3bb49a5bce94d6bc84cbe33d98af2aa2d3cdcce3dea1709ec", size = 3279648, upload-time = "2026-02-24T17:18:07.038Z" }, - { url = "https://files.pythonhosted.org/packages/bb/c8/3d07e7c73928dc59a0bed40961ca4e313e797bce650b088e8d5fdd3ad939/sqlalchemy-2.0.47-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4f3178c920ad98158f0b6309382194df04b14808fa6052ae07099fdde29d5602", size = 3314695, upload-time = "2026-02-24T17:27:20.93Z" }, - { url = "https://files.pythonhosted.org/packages/6b/d2/ed32b1611c1e19fdb028eee1adc5a9aa138c2952d09ae11f1670170f80ae/sqlalchemy-2.0.47-cp312-cp312-win32.whl", hash = "sha256:b9c11ac9934dd59ece9619fe42780a08abe2faab7b0543bb00d5eabea4f421b9", size = 2115502, upload-time = "2026-02-24T17:22:52.546Z" }, - { url = "https://files.pythonhosted.org/packages/fd/52/9de590356a4dd8e9ef5a881dbba64b2bbc4cbc71bf02bc68e775fb9b1899/sqlalchemy-2.0.47-cp312-cp312-win_amd64.whl", hash = "sha256:db43b72cf8274a99e089755c9c1e0b947159b71adbc2c83c3de2e38d5d607acb", size = 2142435, upload-time = "2026-02-24T17:22:54.268Z" }, - { url = "https://files.pythonhosted.org/packages/4a/e5/0af64ce7d8f60ec5328c10084e2f449e7912a9b8bdbefdcfb44454a25f49/sqlalchemy-2.0.47-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:456a135b790da5d3c6b53d0ef71ac7b7d280b7f41eb0c438986352bf03ca7143", size = 2152551, upload-time = "2026-02-24T17:05:47.675Z" }, - { url = "https://files.pythonhosted.org/packages/63/79/746b8d15f6940e2ac469ce22d7aa5b1124b1ab820bad9b046eb3000c88a6/sqlalchemy-2.0.47-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09a2f7698e44b3135433387da5d8846cf7cc7c10e5425af7c05fee609df978b6", size = 3278782, upload-time = "2026-02-24T17:18:10.012Z" }, - { url = "https://files.pythonhosted.org/packages/91/b1/bd793ddb34345d1ed43b13ab2d88c95d7d4eb2e28f5b5a99128b9cc2bca2/sqlalchemy-2.0.47-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0bbc72e6a177c78d724f9106aaddc0d26a2ada89c6332b5935414eccf04cbd5", size = 3295155, upload-time = "2026-02-24T17:27:22.827Z" }, - { url = "https://files.pythonhosted.org/packages/97/84/7213def33f94e5ca6f5718d259bc9f29de0363134648425aa218d4356b23/sqlalchemy-2.0.47-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:75460456b043b78b6006e41bdf5b86747ee42eafaf7fffa3b24a6e9a456a2092", size = 3226834, upload-time = "2026-02-24T17:18:11.465Z" }, - { url = "https://files.pythonhosted.org/packages/ef/06/456810204f4dc29b5f025b1b0a03b4bd6b600ebf3c1040aebd90a257fa33/sqlalchemy-2.0.47-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5d9adaa616c3bc7d80f9ded57cd84b51d6617cad6a5456621d858c9f23aaee01", size = 3265001, upload-time = "2026-02-24T17:27:24.813Z" }, - { url = "https://files.pythonhosted.org/packages/fb/20/df3920a4b2217dbd7390a5bd277c1902e0393f42baaf49f49b3c935e7328/sqlalchemy-2.0.47-cp313-cp313-win32.whl", hash = "sha256:76e09f974382a496a5ed985db9343628b1cb1ac911f27342e4cc46a8bac10476", size = 2113647, upload-time = "2026-02-24T17:22:55.747Z" }, - { url = "https://files.pythonhosted.org/packages/46/06/7873ddf69918efbfabd7211829f4bd8019739d0a719253112d305d3ba51d/sqlalchemy-2.0.47-cp313-cp313-win_amd64.whl", hash = "sha256:0664089b0bf6724a0bfb49a0cf4d4da24868a0a5c8e937cd7db356d5dcdf2c66", size = 2139425, upload-time = "2026-02-24T17:22:57.033Z" }, - { url = "https://files.pythonhosted.org/packages/54/fa/61ad9731370c90ac7ea5bf8f5eaa12c48bb4beec41c0fa0360becf4ac10d/sqlalchemy-2.0.47-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed0c967c701ae13da98eb220f9ddab3044ab63504c1ba24ad6a59b26826ad003", size = 3558809, upload-time = "2026-02-24T17:12:15.232Z" }, - { url = "https://files.pythonhosted.org/packages/33/d5/221fac96f0529391fe374875633804c866f2b21a9c6d3a6ca57d9c12cfd7/sqlalchemy-2.0.47-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3537943a61fd25b241e976426a0c6814434b93cf9b09d39e8e78f3c9eb9a487", size = 3525480, upload-time = "2026-02-24T17:27:59.602Z" }, - { url = "https://files.pythonhosted.org/packages/ec/55/8247d53998c3673e4a8d1958eba75c6f5cc3b39082029d400bb1f2a911ae/sqlalchemy-2.0.47-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:57f7e336a64a0dba686c66392d46b9bc7af2c57d55ce6dc1697b4ef32b043ceb", size = 3466569, upload-time = "2026-02-24T17:12:16.94Z" }, - { url = "https://files.pythonhosted.org/packages/6b/b5/c1f0eea1bac6790845f71420a7fe2f2a0566203aa57543117d4af3b77d1c/sqlalchemy-2.0.47-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dff735a621858680217cb5142b779bad40ef7322ddbb7c12062190db6879772e", size = 3475770, upload-time = "2026-02-24T17:28:02.034Z" }, - { url = "https://files.pythonhosted.org/packages/c5/ed/2f43f92474ea0c43c204657dc47d9d002cd738b96ca2af8e6d29a9b5e42d/sqlalchemy-2.0.47-cp313-cp313t-win32.whl", hash = "sha256:3893dc096bb3cca9608ea3487372ffcea3ae9b162f40e4d3c51dd49db1d1b2dc", size = 2141300, upload-time = "2026-02-24T17:14:37.024Z" }, - { url = "https://files.pythonhosted.org/packages/cc/a9/8b73f9f1695b6e92f7aaf1711135a1e3bbeb78bca9eded35cb79180d3c6d/sqlalchemy-2.0.47-cp313-cp313t-win_amd64.whl", hash = "sha256:b5103427466f4b3e61f04833ae01f9a914b1280a2a8bcde3a9d7ab11f3755b42", size = 2173053, upload-time = "2026-02-24T17:14:38.688Z" }, - { url = "https://files.pythonhosted.org/packages/c1/30/98243209aae58ed80e090ea988d5182244ca7ab3ff59e6d850c3dfc7651e/sqlalchemy-2.0.47-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b03010a5a5dfe71676bc83f2473ebe082478e32d77e6f082c8fe15a31c3b42a6", size = 2154355, upload-time = "2026-02-24T17:05:48.959Z" }, - { url = "https://files.pythonhosted.org/packages/ab/62/12ca6ea92055fe486d6558a2a4efe93e194ff597463849c01f88e5adb99d/sqlalchemy-2.0.47-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8e3371aa9024520883a415a09cc20c33cfd3eeccf9e0f4f4c367f940b9cbd44", size = 3274486, upload-time = "2026-02-24T17:18:13.659Z" }, - { url = "https://files.pythonhosted.org/packages/97/88/7dfbdeaa8d42b1584e65d6cc713e9d33b6fa563e0d546d5cb87e545bb0e5/sqlalchemy-2.0.47-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9449f747e50d518c6e1b40cc379e48bfc796453c47b15e627ea901c201e48a6", size = 3279481, upload-time = "2026-02-24T17:27:26.491Z" }, - { url = "https://files.pythonhosted.org/packages/d0/b7/75e1c1970616a9dd64a8a6fd788248da2ddaf81c95f4875f2a1e8aee4128/sqlalchemy-2.0.47-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:21410f60d5cac1d6bfe360e05bd91b179be4fa0aa6eea6be46054971d277608f", size = 3224269, upload-time = "2026-02-24T17:18:15.078Z" }, - { url = "https://files.pythonhosted.org/packages/31/ac/eec1a13b891df9a8bc203334caf6e6aac60b02f61b018ef3b4124b8c4120/sqlalchemy-2.0.47-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:819841dd5bb4324c284c09e2874cf96fe6338bfb57a64548d9b81a4e39c9871f", size = 3246262, upload-time = "2026-02-24T17:27:27.986Z" }, - { url = "https://files.pythonhosted.org/packages/c9/b0/661b0245b06421058610da39f8ceb34abcc90b49f90f256380968d761dbe/sqlalchemy-2.0.47-cp314-cp314-win32.whl", hash = "sha256:e255ee44821a7ef45649c43064cf94e74f81f61b4df70547304b97a351e9b7db", size = 2116528, upload-time = "2026-02-24T17:22:59.363Z" }, - { url = "https://files.pythonhosted.org/packages/aa/ef/1035a90d899e61810791c052004958be622a2cf3eb3df71c3fe20778c5d0/sqlalchemy-2.0.47-cp314-cp314-win_amd64.whl", hash = "sha256:209467ff73ea1518fe1a5aaed9ba75bb9e33b2666e2553af9ccd13387bf192cb", size = 2142181, upload-time = "2026-02-24T17:23:01.001Z" }, - { url = "https://files.pythonhosted.org/packages/76/bb/17a1dd09cbba91258218ceb582225f14b5364d2683f9f5a274f72f2d764f/sqlalchemy-2.0.47-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e78fd9186946afaa287f8a1fe147ead06e5d566b08c0afcb601226e9c7322a64", size = 3563477, upload-time = "2026-02-24T17:12:18.46Z" }, - { url = "https://files.pythonhosted.org/packages/66/8f/1a03d24c40cc321ef2f2231f05420d140bb06a84f7047eaa7eaa21d230ba/sqlalchemy-2.0.47-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5740e2f31b5987ed9619d6912ae5b750c03637f2078850da3002934c9532f172", size = 3528568, upload-time = "2026-02-24T17:28:03.732Z" }, - { url = "https://files.pythonhosted.org/packages/fd/53/d56a213055d6b038a5384f0db5ece7343334aca230ff3f0fa1561106f22c/sqlalchemy-2.0.47-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb9ac00d03de93acb210e8ec7243fefe3e012515bf5fd2f0898c8dff38bc77a4", size = 3472284, upload-time = "2026-02-24T17:12:20.319Z" }, - { url = "https://files.pythonhosted.org/packages/ff/19/c235d81b9cfdd6130bf63143b7bade0dc4afa46c4b634d5d6b2a96bea233/sqlalchemy-2.0.47-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c72a0b9eb2672d70d112cb149fbaf172d466bc691014c496aaac594f1988e706", size = 3478410, upload-time = "2026-02-24T17:28:05.892Z" }, - { url = "https://files.pythonhosted.org/packages/0e/db/cafdeca5ecdaa3bb0811ba5449501da677ce0d83be8d05c5822da72d2e86/sqlalchemy-2.0.47-cp314-cp314t-win32.whl", hash = "sha256:c200db1128d72a71dc3c31c24b42eb9fd85b2b3e5a3c9ba1e751c11ac31250ff", size = 2147164, upload-time = "2026-02-24T17:14:40.783Z" }, - { url = "https://files.pythonhosted.org/packages/fc/5e/ff41a010e9e0f76418b02ad352060a4341bb15f0af66cedc924ab376c7c6/sqlalchemy-2.0.47-cp314-cp314t-win_amd64.whl", hash = "sha256:669837759b84e575407355dcff912835892058aea9b80bd1cb76d6a151cf37f7", size = 2182154, upload-time = "2026-02-24T17:14:43.205Z" }, - { url = "https://files.pythonhosted.org/packages/15/9f/7c378406b592fcf1fc157248607b495a40e3202ba4a6f1372a2ba6447717/sqlalchemy-2.0.47-py3-none-any.whl", hash = "sha256:e2647043599297a1ef10e720cf310846b7f31b6c841fee093d2b09d81215eb93", size = 1940159, upload-time = "2026-02-24T17:15:07.158Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/1f/73/b4a9737255583b5fa858e0bb8e116eb94b88c910164ed2ed719147bde3de/sqlalchemy-2.0.48.tar.gz", hash = "sha256:5ca74f37f3369b45e1f6b7b06afb182af1fd5dde009e4ffd831830d98cbe5fe7", size = 9886075, upload-time = "2026-03-02T15:28:51.474Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/6d/b8b78b5b80f3c3ab3f7fa90faa195ec3401f6d884b60221260fd4d51864c/sqlalchemy-2.0.48-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b4c575df7368b3b13e0cebf01d4679f9a28ed2ae6c1cd0b1d5beffb6b2007dc", size = 2157184, upload-time = "2026-03-02T15:38:28.161Z" }, + { url = "https://files.pythonhosted.org/packages/21/4b/4f3d4a43743ab58b95b9ddf5580a265b593d017693df9e08bd55780af5bb/sqlalchemy-2.0.48-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e83e3f959aaa1c9df95c22c528096d94848a1bc819f5d0ebf7ee3df0ca63db6c", size = 3313555, upload-time = "2026-03-02T15:58:57.21Z" }, + { url = "https://files.pythonhosted.org/packages/21/dd/3b7c53f1dbbf736fd27041aee68f8ac52226b610f914085b1652c2323442/sqlalchemy-2.0.48-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f7b7243850edd0b8b97043f04748f31de50cf426e939def5c16bedb540698f7", size = 3313057, upload-time = "2026-03-02T15:52:29.366Z" }, + { url = "https://files.pythonhosted.org/packages/d9/cc/3e600a90ae64047f33313d7d32e5ad025417f09d2ded487e8284b5e21a15/sqlalchemy-2.0.48-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:82745b03b4043e04600a6b665cb98697c4339b24e34d74b0a2ac0a2488b6f94d", size = 3265431, upload-time = "2026-03-02T15:58:59.096Z" }, + { url = "https://files.pythonhosted.org/packages/8b/19/780138dacfe3f5024f4cf96e4005e91edf6653d53d3673be4844578faf1d/sqlalchemy-2.0.48-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e5e088bf43f6ee6fec7dbf1ef7ff7774a616c236b5c0cb3e00662dd71a56b571", size = 3287646, upload-time = "2026-03-02T15:52:31.569Z" }, + { url = "https://files.pythonhosted.org/packages/40/fd/f32ced124f01a23151f4777e4c705f3a470adc7bd241d9f36a7c941a33bf/sqlalchemy-2.0.48-cp311-cp311-win32.whl", hash = "sha256:9c7d0a77e36b5f4b01ca398482230ab792061d243d715299b44a0b55c89fe617", size = 2116956, upload-time = "2026-03-02T15:46:54.535Z" }, + { url = "https://files.pythonhosted.org/packages/58/d5/dd767277f6feef12d05651538f280277e661698f617fa4d086cce6055416/sqlalchemy-2.0.48-cp311-cp311-win_amd64.whl", hash = "sha256:583849c743e0e3c9bb7446f5b5addeacedc168d657a69b418063dfdb2d90081c", size = 2141627, upload-time = "2026-03-02T15:46:55.849Z" }, + { url = "https://files.pythonhosted.org/packages/ef/91/a42ae716f8925e9659df2da21ba941f158686856107a61cc97a95e7647a3/sqlalchemy-2.0.48-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:348174f228b99f33ca1f773e85510e08927620caa59ffe7803b37170df30332b", size = 2155737, upload-time = "2026-03-02T15:49:13.207Z" }, + { url = "https://files.pythonhosted.org/packages/b9/52/f75f516a1f3888f027c1cfb5d22d4376f4b46236f2e8669dcb0cddc60275/sqlalchemy-2.0.48-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53667b5f668991e279d21f94ccfa6e45b4e3f4500e7591ae59a8012d0f010dcb", size = 3337020, upload-time = "2026-03-02T15:50:34.547Z" }, + { url = "https://files.pythonhosted.org/packages/37/9a/0c28b6371e0cdcb14f8f1930778cb3123acfcbd2c95bb9cf6b4a2ba0cce3/sqlalchemy-2.0.48-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34634e196f620c7a61d18d5cf7dc841ca6daa7961aed75d532b7e58b309ac894", size = 3349983, upload-time = "2026-03-02T15:53:25.542Z" }, + { url = "https://files.pythonhosted.org/packages/1c/46/0aee8f3ff20b1dcbceb46ca2d87fcc3d48b407925a383ff668218509d132/sqlalchemy-2.0.48-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:546572a1793cc35857a2ffa1fe0e58571af1779bcc1ffa7c9fb0839885ed69a9", size = 3279690, upload-time = "2026-03-02T15:50:36.277Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8c/a957bc91293b49181350bfd55e6dfc6e30b7f7d83dc6792d72043274a390/sqlalchemy-2.0.48-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:07edba08061bc277bfdc772dd2a1a43978f5a45994dd3ede26391b405c15221e", size = 3314738, upload-time = "2026-03-02T15:53:27.519Z" }, + { url = "https://files.pythonhosted.org/packages/4b/44/1d257d9f9556661e7bdc83667cc414ba210acfc110c82938cb3611eea58f/sqlalchemy-2.0.48-cp312-cp312-win32.whl", hash = "sha256:908a3fa6908716f803b86896a09a2c4dde5f5ce2bb07aacc71ffebb57986ce99", size = 2115546, upload-time = "2026-03-02T15:54:31.591Z" }, + { url = "https://files.pythonhosted.org/packages/f2/af/c3c7e1f3a2b383155a16454df62ae8c62a30dd238e42e68c24cebebbfae6/sqlalchemy-2.0.48-cp312-cp312-win_amd64.whl", hash = "sha256:68549c403f79a8e25984376480959975212a670405e3913830614432b5daa07a", size = 2142484, upload-time = "2026-03-02T15:54:34.072Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c6/569dc8bf3cd375abc5907e82235923e986799f301cd79a903f784b996fca/sqlalchemy-2.0.48-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e3070c03701037aa418b55d36532ecb8f8446ed0135acb71c678dbdf12f5b6e4", size = 2152599, upload-time = "2026-03-02T15:49:14.41Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ff/f4e04a4bd5a24304f38cb0d4aa2ad4c0fb34999f8b884c656535e1b2b74c/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2645b7d8a738763b664a12a1542c89c940daa55196e8d73e55b169cc5c99f65f", size = 3278825, upload-time = "2026-03-02T15:50:38.269Z" }, + { url = "https://files.pythonhosted.org/packages/fe/88/cb59509e4668d8001818d7355d9995be90c321313078c912420603a7cb95/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b19151e76620a412c2ac1c6f977ab1b9fa7ad43140178345136456d5265b32ed", size = 3295200, upload-time = "2026-03-02T15:53:29.366Z" }, + { url = "https://files.pythonhosted.org/packages/87/dc/1609a4442aefd750ea2f32629559394ec92e89ac1d621a7f462b70f736ff/sqlalchemy-2.0.48-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b193a7e29fd9fa56e502920dca47dffe60f97c863494946bd698c6058a55658", size = 3226876, upload-time = "2026-03-02T15:50:39.802Z" }, + { url = "https://files.pythonhosted.org/packages/37/c3/6ae2ab5ea2fa989fbac4e674de01224b7a9d744becaf59bb967d62e99bed/sqlalchemy-2.0.48-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:36ac4ddc3d33e852da9cb00ffb08cea62ca05c39711dc67062ca2bb1fae35fd8", size = 3265045, upload-time = "2026-03-02T15:53:31.421Z" }, + { url = "https://files.pythonhosted.org/packages/6f/82/ea4665d1bb98c50c19666e672f21b81356bd6077c4574e3d2bbb84541f53/sqlalchemy-2.0.48-cp313-cp313-win32.whl", hash = "sha256:389b984139278f97757ea9b08993e7b9d1142912e046ab7d82b3fbaeb0209131", size = 2113700, upload-time = "2026-03-02T15:54:35.825Z" }, + { url = "https://files.pythonhosted.org/packages/b7/2b/b9040bec58c58225f073f5b0c1870defe1940835549dafec680cbd58c3c3/sqlalchemy-2.0.48-cp313-cp313-win_amd64.whl", hash = "sha256:d612c976cbc2d17edfcc4c006874b764e85e990c29ce9bd411f926bbfb02b9a2", size = 2139487, upload-time = "2026-03-02T15:54:37.079Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/7b17bd50244b78a49d22cc63c969d71dc4de54567dc152a9b46f6fae40ce/sqlalchemy-2.0.48-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69f5bc24904d3bc3640961cddd2523e361257ef68585d6e364166dfbe8c78fae", size = 3558851, upload-time = "2026-03-02T15:57:48.607Z" }, + { url = "https://files.pythonhosted.org/packages/20/0d/213668e9aca61d370f7d2a6449ea4ec699747fac67d4bda1bb3d129025be/sqlalchemy-2.0.48-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd08b90d211c086181caed76931ecfa2bdfc83eea3cfccdb0f82abc6c4b876cb", size = 3525525, upload-time = "2026-03-02T16:04:38.058Z" }, + { url = "https://files.pythonhosted.org/packages/85/d7/a84edf412979e7d59c69b89a5871f90a49228360594680e667cb2c46a828/sqlalchemy-2.0.48-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1ccd42229aaac2df431562117ac7e667d702e8e44afdb6cf0e50fa3f18160f0b", size = 3466611, upload-time = "2026-03-02T15:57:50.759Z" }, + { url = "https://files.pythonhosted.org/packages/86/55/42404ce5770f6be26a2b0607e7866c31b9a4176c819e9a7a5e0a055770be/sqlalchemy-2.0.48-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0dcbc588cd5b725162c076eb9119342f6579c7f7f55057bb7e3c6ff27e13121", size = 3475812, upload-time = "2026-03-02T16:04:40.092Z" }, + { url = "https://files.pythonhosted.org/packages/ae/ae/29b87775fadc43e627cf582fe3bda4d02e300f6b8f2747c764950d13784c/sqlalchemy-2.0.48-cp313-cp313t-win32.whl", hash = "sha256:9764014ef5e58aab76220c5664abb5d47d5bc858d9debf821e55cfdd0f128485", size = 2141335, upload-time = "2026-03-02T15:52:51.518Z" }, + { url = "https://files.pythonhosted.org/packages/91/44/f39d063c90f2443e5b46ec4819abd3d8de653893aae92df42a5c4f5843de/sqlalchemy-2.0.48-cp313-cp313t-win_amd64.whl", hash = "sha256:e2f35b4cccd9ed286ad62e0a3c3ac21e06c02abc60e20aa51a3e305a30f5fa79", size = 2173095, upload-time = "2026-03-02T15:52:52.79Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b3/f437eaa1cf028bb3c927172c7272366393e73ccd104dcf5b6963f4ab5318/sqlalchemy-2.0.48-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e2d0d88686e3d35a76f3e15a34e8c12d73fc94c1dea1cd55782e695cc14086dd", size = 2154401, upload-time = "2026-03-02T15:49:17.24Z" }, + { url = "https://files.pythonhosted.org/packages/6c/1c/b3abdf0f402aa3f60f0df6ea53d92a162b458fca2321d8f1f00278506402/sqlalchemy-2.0.48-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49b7bddc1eebf011ea5ab722fdbe67a401caa34a350d278cc7733c0e88fecb1f", size = 3274528, upload-time = "2026-03-02T15:50:41.489Z" }, + { url = "https://files.pythonhosted.org/packages/f2/5e/327428a034407651a048f5e624361adf3f9fbac9d0fa98e981e9c6ff2f5e/sqlalchemy-2.0.48-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:426c5ca86415d9b8945c7073597e10de9644802e2ff502b8e1f11a7a2642856b", size = 3279523, upload-time = "2026-03-02T15:53:32.962Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ca/ece73c81a918add0965b76b868b7b5359e068380b90ef1656ee995940c02/sqlalchemy-2.0.48-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:288937433bd44e3990e7da2402fabc44a3c6c25d3704da066b85b89a85474ae0", size = 3224312, upload-time = "2026-03-02T15:50:42.996Z" }, + { url = "https://files.pythonhosted.org/packages/88/11/fbaf1ae91fa4ee43f4fe79661cead6358644824419c26adb004941bdce7c/sqlalchemy-2.0.48-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8183dc57ae7d9edc1346e007e840a9f3d6aa7b7f165203a99e16f447150140d2", size = 3246304, upload-time = "2026-03-02T15:53:34.937Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5fb0deb13930b4f2f698c5541ae076c18981173e27dd00376dbaea7a9c82/sqlalchemy-2.0.48-cp314-cp314-win32.whl", hash = "sha256:1182437cb2d97988cfea04cf6cdc0b0bb9c74f4d56ec3d08b81e23d621a28cc6", size = 2116565, upload-time = "2026-03-02T15:54:38.321Z" }, + { url = "https://files.pythonhosted.org/packages/95/7e/e83615cb63f80047f18e61e31e8e32257d39458426c23006deeaf48f463b/sqlalchemy-2.0.48-cp314-cp314-win_amd64.whl", hash = "sha256:144921da96c08feb9e2b052c5c5c1d0d151a292c6135623c6b2c041f2a45f9e0", size = 2142205, upload-time = "2026-03-02T15:54:39.831Z" }, + { url = "https://files.pythonhosted.org/packages/83/e3/69d8711b3f2c5135e9cde5f063bc1605860f0b2c53086d40c04017eb1f77/sqlalchemy-2.0.48-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5aee45fd2c6c0f2b9cdddf48c48535e7471e42d6fb81adfde801da0bd5b93241", size = 3563519, upload-time = "2026-03-02T15:57:52.387Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4f/a7cce98facca73c149ea4578981594aaa5fd841e956834931de503359336/sqlalchemy-2.0.48-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7cddca31edf8b0653090cbb54562ca027c421c58ddde2c0685f49ff56a1690e0", size = 3528611, upload-time = "2026-03-02T16:04:42.097Z" }, + { url = "https://files.pythonhosted.org/packages/cd/7d/5936c7a03a0b0cb0fa0cc425998821c6029756b0855a8f7ee70fba1de955/sqlalchemy-2.0.48-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7a936f1bb23d370b7c8cc079d5fce4c7d18da87a33c6744e51a93b0f9e97e9b3", size = 3472326, upload-time = "2026-03-02T15:57:54.423Z" }, + { url = "https://files.pythonhosted.org/packages/f4/33/cea7dfc31b52904efe3dcdc169eb4514078887dff1f5ae28a7f4c5d54b3c/sqlalchemy-2.0.48-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e004aa9248e8cb0a5f9b96d003ca7c1c0a5da8decd1066e7b53f59eb8ce7c62b", size = 3478453, upload-time = "2026-03-02T16:04:44.584Z" }, + { url = "https://files.pythonhosted.org/packages/c8/95/32107c4d13be077a9cae61e9ae49966a35dc4bf442a8852dd871db31f62e/sqlalchemy-2.0.48-cp314-cp314t-win32.whl", hash = "sha256:b8438ec5594980d405251451c5b7ea9aa58dda38eb7ac35fb7e4c696712ee24f", size = 2147209, upload-time = "2026-03-02T15:52:54.274Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d7/1e073da7a4bc645eb83c76067284a0374e643bc4be57f14cc6414656f92c/sqlalchemy-2.0.48-cp314-cp314t-win_amd64.whl", hash = "sha256:d854b3970067297f3a7fbd7a4683587134aa9b3877ee15aa29eea478dc68f933", size = 2182198, upload-time = "2026-03-02T15:52:55.606Z" }, + { url = "https://files.pythonhosted.org/packages/46/2c/9664130905f03db57961b8980b05cab624afd114bf2be2576628a9f22da4/sqlalchemy-2.0.48-py3-none-any.whl", hash = "sha256:a66fe406437dd65cacd96a72689a3aaaecaebbcd62d81c5ac1c0fdbeac835096", size = 1940202, upload-time = "2026-03-02T15:52:43.285Z" }, ] [[package]] @@ -3345,14 +3477,14 @@ wheels = [ [[package]] name = "tifffile" -version = "2026.2.24" +version = "2026.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6e/1c/19fc653e2b05ec0defae511b03b330ca60c95f2c47fcaaf21c52c6e84aa8/tifffile-2026.2.24.tar.gz", hash = "sha256:d73cfa6d7a8f5775a1e3c9f3bfca77c992946639fb41a5bbe888878cb6964dc6", size = 387373, upload-time = "2026-02-24T23:59:11.706Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/cb/2f6d79c7576e22c116352a801f4c3c8ace5957e9aced862012430b62e14f/tifffile-2026.3.3.tar.gz", hash = "sha256:d9a1266bed6f2ee1dd0abde2018a38b4f8b2935cb843df381d70ac4eac5458b7", size = 388745, upload-time = "2026-03-03T19:14:38.134Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/fe/80250dc06cd4a3a5afe7059875a8d53e97a78528c5dd9ea8c3f981fb897a/tifffile-2026.2.24-py3-none-any.whl", hash = "sha256:38ef6258c2bd8dd3551c7480c6d75a36c041616262e6cd55a50dd16046b71863", size = 243223, upload-time = "2026-02-24T23:59:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/1a/e4/e804505f87627cd8cdae9c010c47c4485fd8c1ce31a7dd0ab7fcc4707377/tifffile-2026.3.3-py3-none-any.whl", hash = "sha256:e8be15c94273113d31ecb7aa3a39822189dd11c4967e3cc88c178f1ad2fd1170", size = 243960, upload-time = "2026-03-03T19:14:35.808Z" }, ] [[package]] @@ -3369,56 +3501,56 @@ wheels = [ [[package]] name = "tomli" -version = "2.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" }, - { url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" }, - { url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" }, - { url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" }, - { url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" }, - { url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" }, - { url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" }, - { url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" }, - { url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" }, - { url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" }, - { url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" }, - { url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" }, - { url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" }, - { url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" }, - { url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" }, - { url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" }, - { url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" }, - { url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" }, - { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" }, - { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" }, - { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" }, - { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" }, - { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" }, - { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" }, - { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" }, - { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" }, - { url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" }, - { url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" }, - { url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" }, - { url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" }, - { url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" }, - { url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" }, - { url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" }, - { url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" }, - { url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" }, - { url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" }, - { url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" }, - { url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" }, - { url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" }, - { url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" }, - { url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" }, - { url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" }, - { url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" }, - { url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" }, - { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" }, +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, ] [[package]] @@ -3432,62 +3564,49 @@ wheels = [ [[package]] name = "torch" -version = "2.10.0" +version = "2.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cuda-bindings", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "cuda-bindings", marker = "sys_platform == 'linux'" }, + { name = "cuda-toolkit", extra = ["cublas", "cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"], marker = "sys_platform == 'linux'" }, { name = "filelock" }, { name = "fsspec" }, { name = "jinja2" }, { name = "networkx" }, - { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cufile-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cusparselt-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-nvshmem-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "setuptools", marker = "python_full_version >= '3.12'" }, + { name = "nvidia-cudnn-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-cusparselt-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nccl-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nvshmem-cu13", marker = "sys_platform == 'linux'" }, + { name = "setuptools" }, { name = "sympy" }, - { name = "triton", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "triton", marker = "sys_platform == 'linux'" }, { name = "typing-extensions" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/8b/4b61d6e13f7108f36910df9ab4b58fd389cc2520d54d81b88660804aad99/torch-2.10.0-2-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:418997cb02d0a0f1497cf6a09f63166f9f5df9f3e16c8a716ab76a72127c714f", size = 79423467, upload-time = "2026-02-10T21:44:48.711Z" }, - { url = "https://files.pythonhosted.org/packages/d3/54/a2ba279afcca44bbd320d4e73675b282fcee3d81400ea1b53934efca6462/torch-2.10.0-2-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:13ec4add8c3faaed8d13e0574f5cd4a323c11655546f91fbe6afa77b57423574", size = 79498202, upload-time = "2026-02-10T21:44:52.603Z" }, - { url = "https://files.pythonhosted.org/packages/ec/23/2c9fe0c9c27f7f6cb865abcea8a4568f29f00acaeadfc6a37f6801f84cb4/torch-2.10.0-2-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:e521c9f030a3774ed770a9c011751fb47c4d12029a3d6522116e48431f2ff89e", size = 79498254, upload-time = "2026-02-10T21:44:44.095Z" }, - { url = "https://files.pythonhosted.org/packages/78/89/f5554b13ebd71e05c0b002f95148033e730d3f7067f67423026cc9c69410/torch-2.10.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:3282d9febd1e4e476630a099692b44fdc214ee9bf8ee5377732d9d9dfe5712e4", size = 145992610, upload-time = "2026-01-21T16:25:26.327Z" }, - { url = "https://files.pythonhosted.org/packages/ae/30/a3a2120621bf9c17779b169fc17e3dc29b230c29d0f8222f499f5e159aa8/torch-2.10.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a2f9edd8dbc99f62bc4dfb78af7bf89499bca3d753423ac1b4e06592e467b763", size = 915607863, upload-time = "2026-01-21T16:25:06.696Z" }, - { url = "https://files.pythonhosted.org/packages/6f/3d/c87b33c5f260a2a8ad68da7147e105f05868c281c63d65ed85aa4da98c66/torch-2.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:29b7009dba4b7a1c960260fc8ac85022c784250af43af9fb0ebafc9883782ebd", size = 113723116, upload-time = "2026-01-21T16:25:21.916Z" }, - { url = "https://files.pythonhosted.org/packages/61/d8/15b9d9d3a6b0c01b883787bd056acbe5cc321090d4b216d3ea89a8fcfdf3/torch-2.10.0-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:b7bd80f3477b830dd166c707c5b0b82a898e7b16f59a7d9d42778dd058272e8b", size = 79423461, upload-time = "2026-01-21T16:24:50.266Z" }, - { url = "https://files.pythonhosted.org/packages/cc/af/758e242e9102e9988969b5e621d41f36b8f258bb4a099109b7a4b4b50ea4/torch-2.10.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:5fd4117d89ffd47e3dcc71e71a22efac24828ad781c7e46aaaf56bf7f2796acf", size = 145996088, upload-time = "2026-01-21T16:24:44.171Z" }, - { url = "https://files.pythonhosted.org/packages/23/8e/3c74db5e53bff7ed9e34c8123e6a8bfef718b2450c35eefab85bb4a7e270/torch-2.10.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:787124e7db3b379d4f1ed54dd12ae7c741c16a4d29b49c0226a89bea50923ffb", size = 915711952, upload-time = "2026-01-21T16:23:53.503Z" }, - { url = "https://files.pythonhosted.org/packages/6e/01/624c4324ca01f66ae4c7cd1b74eb16fb52596dce66dbe51eff95ef9e7a4c/torch-2.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:2c66c61f44c5f903046cc696d088e21062644cbe541c7f1c4eaae88b2ad23547", size = 113757972, upload-time = "2026-01-21T16:24:39.516Z" }, - { url = "https://files.pythonhosted.org/packages/c9/5c/dee910b87c4d5c0fcb41b50839ae04df87c1cfc663cf1b5fca7ea565eeaa/torch-2.10.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:6d3707a61863d1c4d6ebba7be4ca320f42b869ee657e9b2c21c736bf17000294", size = 79498198, upload-time = "2026-01-21T16:24:34.704Z" }, - { url = "https://files.pythonhosted.org/packages/c9/6f/f2e91e34e3fcba2e3fc8d8f74e7d6c22e74e480bbd1db7bc8900fdf3e95c/torch-2.10.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:5c4d217b14741e40776dd7074d9006fd28b8a97ef5654db959d8635b2fe5f29b", size = 146004247, upload-time = "2026-01-21T16:24:29.335Z" }, - { url = "https://files.pythonhosted.org/packages/98/fb/5160261aeb5e1ee12ee95fe599d0541f7c976c3701d607d8fc29e623229f/torch-2.10.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6b71486353fce0f9714ca0c9ef1c850a2ae766b409808acd58e9678a3edb7738", size = 915716445, upload-time = "2026-01-21T16:22:45.353Z" }, - { url = "https://files.pythonhosted.org/packages/6a/16/502fb1b41e6d868e8deb5b0e3ae926bbb36dab8ceb0d1b769b266ad7b0c3/torch-2.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:c2ee399c644dc92ef7bc0d4f7e74b5360c37cdbe7c5ba11318dda49ffac2bc57", size = 113757050, upload-time = "2026-01-21T16:24:19.204Z" }, - { url = "https://files.pythonhosted.org/packages/1a/0b/39929b148f4824bc3ad6f9f72a29d4ad865bcf7ebfc2fa67584773e083d2/torch-2.10.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:3202429f58309b9fa96a614885eace4b7995729f44beb54d3e4a47773649d382", size = 79851305, upload-time = "2026-01-21T16:24:09.209Z" }, - { url = "https://files.pythonhosted.org/packages/d8/14/21fbce63bc452381ba5f74a2c0a959fdf5ad5803ccc0c654e752e0dbe91a/torch-2.10.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:aae1b29cd68e50a9397f5ee897b9c24742e9e306f88a807a27d617f07adb3bd8", size = 146005472, upload-time = "2026-01-21T16:22:29.022Z" }, - { url = "https://files.pythonhosted.org/packages/54/fd/b207d1c525cb570ef47f3e9f836b154685011fce11a2f444ba8a4084d042/torch-2.10.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:6021db85958db2f07ec94e1bc77212721ba4920c12a18dc552d2ae36a3eb163f", size = 915612644, upload-time = "2026-01-21T16:21:47.019Z" }, - { url = "https://files.pythonhosted.org/packages/36/53/0197f868c75f1050b199fe58f9bf3bf3aecac9b4e85cc9c964383d745403/torch-2.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff43db38af76fda183156153983c9a096fc4c78d0cd1e07b14a2314c7f01c2c8", size = 113997015, upload-time = "2026-01-21T16:23:00.767Z" }, - { url = "https://files.pythonhosted.org/packages/0e/13/e76b4d9c160e89fff48bf16b449ea324bda84745d2ab30294c37c2434c0d/torch-2.10.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:cdf2a523d699b70d613243211ecaac14fe9c5df8a0b0a9c02add60fb2a413e0f", size = 79498248, upload-time = "2026-01-21T16:23:09.315Z" }, - { url = "https://files.pythonhosted.org/packages/4f/93/716b5ac0155f1be70ed81bacc21269c3ece8dba0c249b9994094110bfc51/torch-2.10.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:bf0d9ff448b0218e0433aeb198805192346c4fd659c852370d5cc245f602a06a", size = 79464992, upload-time = "2026-01-21T16:23:05.162Z" }, - { url = "https://files.pythonhosted.org/packages/69/2b/51e663ff190c9d16d4a8271203b71bc73a16aa7619b9f271a69b9d4a936b/torch-2.10.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:233aed0659a2503b831d8a67e9da66a62c996204c0bba4f4c442ccc0c68a3f60", size = 146018567, upload-time = "2026-01-21T16:22:23.393Z" }, - { url = "https://files.pythonhosted.org/packages/5e/cd/4b95ef7f293b927c283db0b136c42be91c8ec6845c44de0238c8c23bdc80/torch-2.10.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:682497e16bdfa6efeec8cde66531bc8d1fbbbb4d8788ec6173c089ed3cc2bfe5", size = 915721646, upload-time = "2026-01-21T16:21:16.983Z" }, - { url = "https://files.pythonhosted.org/packages/56/97/078a007208f8056d88ae43198833469e61a0a355abc0b070edd2c085eb9a/torch-2.10.0-cp314-cp314-win_amd64.whl", hash = "sha256:6528f13d2a8593a1a412ea07a99812495bec07e9224c28b2a25c0a30c7da025c", size = 113752373, upload-time = "2026-01-21T16:22:13.471Z" }, - { url = "https://files.pythonhosted.org/packages/d8/94/71994e7d0d5238393df9732fdab607e37e2b56d26a746cb59fdb415f8966/torch-2.10.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:f5ab4ba32383061be0fb74bda772d470140a12c1c3b58a0cfbf3dae94d164c28", size = 79850324, upload-time = "2026-01-21T16:22:09.494Z" }, - { url = "https://files.pythonhosted.org/packages/e2/65/1a05346b418ea8ccd10360eef4b3e0ce688fba544e76edec26913a8d0ee0/torch-2.10.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:716b01a176c2a5659c98f6b01bf868244abdd896526f1c692712ab36dbaf9b63", size = 146006482, upload-time = "2026-01-21T16:22:18.42Z" }, - { url = "https://files.pythonhosted.org/packages/1d/b9/5f6f9d9e859fc3235f60578fa64f52c9c6e9b4327f0fe0defb6de5c0de31/torch-2.10.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:d8f5912ba938233f86361e891789595ff35ca4b4e2ac8fe3670895e5976731d6", size = 915613050, upload-time = "2026-01-21T16:20:49.035Z" }, - { url = "https://files.pythonhosted.org/packages/66/4d/35352043ee0eaffdeff154fad67cd4a31dbed7ff8e3be1cc4549717d6d51/torch-2.10.0-cp314-cp314t-win_amd64.whl", hash = "sha256:71283a373f0ee2c89e0f0d5f446039bdabe8dbc3c9ccf35f0f784908b0acd185", size = 113995816, upload-time = "2026-01-21T16:22:05.312Z" }, + { url = "https://files.pythonhosted.org/packages/ae/0d/98b410492609e34a155fa8b121b55c7dca229f39636851c3a9ec20edea21/torch-2.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7b6a60d48062809f58595509c524b88e6ddec3ebe25833d6462eeab81e5f2ce4", size = 80529712, upload-time = "2026-03-23T18:12:02.608Z" }, + { url = "https://files.pythonhosted.org/packages/84/03/acea680005f098f79fd70c1d9d5ccc0cb4296ec2af539a0450108232fc0c/torch-2.11.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:d91aac77f24082809d2c5a93f52a5f085032740a1ebc9252a7b052ef5a4fddc6", size = 419718178, upload-time = "2026-03-23T18:10:46.675Z" }, + { url = "https://files.pythonhosted.org/packages/8c/8b/d7be22fbec9ffee6cff31a39f8750d4b3a65d349a286cf4aec74c2375662/torch-2.11.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:7aa2f9bbc6d4595ba72138026b2074be1233186150e9292865e04b7a63b8c67a", size = 530604548, upload-time = "2026-03-23T18:10:03.569Z" }, + { url = "https://files.pythonhosted.org/packages/d1/bd/9912d30b68845256aabbb4a40aeefeef3c3b20db5211ccda653544ada4b6/torch-2.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:73e24aaf8f36ab90d95cd1761208b2eb70841c2a9ca1a3f9061b39fc5331b708", size = 114519675, upload-time = "2026-03-23T18:11:52.995Z" }, + { url = "https://files.pythonhosted.org/packages/6f/8b/69e3008d78e5cee2b30183340cc425081b78afc5eff3d080daab0adda9aa/torch-2.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b5866312ee6e52ea625cd211dcb97d6a2cdc1131a5f15cc0d87eec948f6dd34", size = 80606338, upload-time = "2026-03-23T18:11:34.781Z" }, + { url = "https://files.pythonhosted.org/packages/13/16/42e5915ebe4868caa6bac83a8ed59db57f12e9a61b7d749d584776ed53d5/torch-2.11.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f99924682ef0aa6a4ab3b1b76f40dc6e273fca09f367d15a524266db100a723f", size = 419731115, upload-time = "2026-03-23T18:11:06.944Z" }, + { url = "https://files.pythonhosted.org/packages/1a/c9/82638ef24d7877510f83baf821f5619a61b45568ce21c0a87a91576510aa/torch-2.11.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:0f68f4ac6d95d12e896c3b7a912b5871619542ec54d3649cf48cc1edd4dd2756", size = 530712279, upload-time = "2026-03-23T18:10:31.481Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ff/6756f1c7ee302f6d202120e0f4f05b432b839908f9071157302cedfc5232/torch-2.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:fbf39280699d1b869f55eac536deceaa1b60bd6788ba74f399cc67e60a5fab10", size = 114556047, upload-time = "2026-03-23T18:10:55.931Z" }, + { url = "https://files.pythonhosted.org/packages/87/89/5ea6722763acee56b045435fb84258db7375c48165ec8be7880ab2b281c5/torch-2.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e6debd97ccd3205bbb37eb806a9d8219e1139d15419982c09e23ef7d4369d18", size = 80606801, upload-time = "2026-03-23T18:10:18.649Z" }, + { url = "https://files.pythonhosted.org/packages/32/d1/8ed2173589cbfe744ed54e5a73efc107c0085ba5777ee93a5f4c1ab90553/torch-2.11.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:63a68fa59de8f87acc7e85a5478bb2dddbb3392b7593ec3e78827c793c4b73fd", size = 419732382, upload-time = "2026-03-23T18:08:30.835Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e1/b73f7c575a4b8f87a5928f50a1e35416b5e27295d8be9397d5293e7e8d4c/torch-2.11.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:cc89b9b173d9adfab59fd227f0ab5e5516d9a52b658ae41d64e59d2e55a418db", size = 530711509, upload-time = "2026-03-23T18:08:47.213Z" }, + { url = "https://files.pythonhosted.org/packages/66/82/3e3fcdd388fbe54e29fd3f991f36846ff4ac90b0d0181e9c8f7236565f82/torch-2.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:4dda3b3f52d121063a731ddb835f010dc137b920d7fec2778e52f60d8e4bf0cd", size = 114555842, upload-time = "2026-03-23T18:09:52.111Z" }, + { url = "https://files.pythonhosted.org/packages/db/38/8ac78069621b8c2b4979c2f96dc8409ef5e9c4189f6aac629189a78677ca/torch-2.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8b394322f49af4362d4f80e424bcaca7efcd049619af03a4cf4501520bdf0fb4", size = 80959574, upload-time = "2026-03-23T18:10:14.214Z" }, + { url = "https://files.pythonhosted.org/packages/6d/6c/56bfb37073e7136e6dd86bfc6af7339946dd684e0ecf2155ac0eee687ae1/torch-2.11.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:2658f34ce7e2dabf4ec73b45e2ca68aedad7a5be87ea756ad656eaf32bf1e1ea", size = 419732324, upload-time = "2026-03-23T18:09:36.604Z" }, + { url = "https://files.pythonhosted.org/packages/07/f4/1b666b6d61d3394cca306ea543ed03a64aad0a201b6cd159f1d41010aeb1/torch-2.11.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:98bb213c3084cfe176302949bdc360074b18a9da7ab59ef2edc9d9f742504778", size = 530596026, upload-time = "2026-03-23T18:09:20.842Z" }, + { url = "https://files.pythonhosted.org/packages/48/6b/30d1459fa7e4b67e9e3fe1685ca1d8bb4ce7c62ef436c3a615963c6c866c/torch-2.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a97b94bbf62992949b4730c6cd2cc9aee7b335921ee8dc207d930f2ed09ae2db", size = 114793702, upload-time = "2026-03-23T18:09:47.304Z" }, + { url = "https://files.pythonhosted.org/packages/26/0d/8603382f61abd0db35841148ddc1ffd607bf3100b11c6e1dab6d2fc44e72/torch-2.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:01018087326984a33b64e04c8cb5c2795f9120e0d775ada1f6638840227b04d7", size = 80573442, upload-time = "2026-03-23T18:09:10.117Z" }, + { url = "https://files.pythonhosted.org/packages/c7/86/7cd7c66cb9cec6be330fff36db5bd0eef386d80c031b581ec81be1d4b26c/torch-2.11.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:2bb3cc54bd0dea126b0060bb1ec9de0f9c7f7342d93d436646516b0330cd5be7", size = 419749385, upload-time = "2026-03-23T18:07:33.77Z" }, + { url = "https://files.pythonhosted.org/packages/47/e8/b98ca2d39b2e0e4730c0ee52537e488e7008025bc77ca89552ff91021f7c/torch-2.11.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:4dc8b3809469b6c30b411bb8c4cad3828efd26236153d9beb6a3ec500f211a60", size = 530716756, upload-time = "2026-03-23T18:07:50.02Z" }, + { url = "https://files.pythonhosted.org/packages/78/88/d4a4cda8362f8a30d1ed428564878c3cafb0d87971fbd3947d4c84552095/torch-2.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:2b4e811728bd0cc58fb2b0948fe939a1ee2bf1422f6025be2fca4c7bd9d79718", size = 114552300, upload-time = "2026-03-23T18:09:05.617Z" }, + { url = "https://files.pythonhosted.org/packages/bf/46/4419098ed6d801750f26567b478fc185c3432e11e2cad712bc6b4c2ab0d0/torch-2.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8245477871c3700d4370352ffec94b103cfcb737229445cf9946cddb7b2ca7cd", size = 80959460, upload-time = "2026-03-23T18:09:00.818Z" }, + { url = "https://files.pythonhosted.org/packages/fd/66/54a56a4a6ceaffb567231994a9745821d3af922a854ed33b0b3a278e0a99/torch-2.11.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:ab9a8482f475f9ba20e12db84b0e55e2f58784bdca43a854a6ccd3fd4b9f75e6", size = 419735835, upload-time = "2026-03-23T18:07:18.974Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e7/0b6665f533aa9e337662dc190425abc0af1fe3234088f4454c52393ded61/torch-2.11.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:563ed3d25542d7e7bbc5b235ccfacfeb97fb470c7fee257eae599adb8005c8a2", size = 530613405, upload-time = "2026-03-23T18:08:07.014Z" }, + { url = "https://files.pythonhosted.org/packages/cf/bf/c8d12a2c86dbfd7f40fb2f56fbf5a505ccf2d9ce131eb559dfc7c51e1a04/torch-2.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b2a43985ff5ef6ddd923bbcf99943e5f58059805787c5c9a2622bf05ca2965b0", size = 114792991, upload-time = "2026-03-23T18:08:19.216Z" }, ] [[package]] @@ -3501,7 +3620,7 @@ wheels = [ [[package]] name = "torchmetrics" -version = "1.8.2" +version = "1.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "lightning-utilities" }, @@ -3509,14 +3628,14 @@ dependencies = [ { name = "packaging" }, { name = "torch" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/85/2e/48a887a59ecc4a10ce9e8b35b3e3c5cef29d902c4eac143378526e7485cb/torchmetrics-1.8.2.tar.gz", hash = "sha256:cf64a901036bf107f17a524009eea7781c9c5315d130713aeca5747a686fe7a5", size = 580679, upload-time = "2025-09-03T14:00:54.077Z" } +sdist = { url = "https://files.pythonhosted.org/packages/81/34/39b8b749333db56c0585d7a11fa62a283c087bb1dfc897d69fb8cedbefb1/torchmetrics-1.9.0.tar.gz", hash = "sha256:a488609948600df52d3db4fcdab02e62aab2a85ef34da67037dc3e65b8512faa", size = 581765, upload-time = "2026-03-09T17:41:22.443Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/21/aa0f434434c48490f91b65962b1ce863fdcce63febc166ca9fe9d706c2b6/torchmetrics-1.8.2-py3-none-any.whl", hash = "sha256:08382fd96b923e39e904c4d570f3d49e2cc71ccabd2a94e0f895d1f0dac86242", size = 983161, upload-time = "2025-09-03T14:00:51.921Z" }, + { url = "https://files.pythonhosted.org/packages/c3/a2/c7f6ebf546f8f644edf0f999aa98ece106986a77a7b922316bf6414ff825/torchmetrics-1.9.0-py3-none-any.whl", hash = "sha256:bfdcbff3dd1d96b3374bb2496eb39f23c4b28b8a845b6a18c313688e0d2d9ca1", size = 983384, upload-time = "2026-03-09T17:41:19.756Z" }, ] [[package]] name = "torchvision" -version = "0.25.0" +version = "0.26.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, @@ -3524,49 +3643,47 @@ dependencies = [ { name = "torch" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/be/c704bceaf11c4f6b19d64337a34a877fcdfe3bd68160a8c9ae9bea4a35a3/torchvision-0.25.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:db74a551946b75d19f9996c419a799ffdf6a223ecf17c656f90da011f1d75b20", size = 1874923, upload-time = "2026-01-21T16:27:46.574Z" }, - { url = "https://files.pythonhosted.org/packages/ae/e9/f143cd71232430de1f547ceab840f68c55e127d72558b1061a71d0b193cd/torchvision-0.25.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:f49964f96644dbac2506dffe1a0a7ec0f2bf8cf7a588c3319fed26e6329ffdf3", size = 2344808, upload-time = "2026-01-21T16:27:43.191Z" }, - { url = "https://files.pythonhosted.org/packages/43/ae/ad5d6165797de234c9658752acb4fce65b78a6a18d82efdf8367c940d8da/torchvision-0.25.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:153c0d2cbc34b7cf2da19d73450f24ba36d2b75ec9211b9962b5022fb9e4ecee", size = 8070752, upload-time = "2026-01-21T16:27:33.748Z" }, - { url = "https://files.pythonhosted.org/packages/23/19/55b28aecdc7f38df57b8eb55eb0b14a62b470ed8efeb22cdc74224df1d6a/torchvision-0.25.0-cp311-cp311-win_amd64.whl", hash = "sha256:ea580ffd6094cc01914ad32f8c8118174f18974629af905cea08cb6d5d48c7b7", size = 4038722, upload-time = "2026-01-21T16:27:41.355Z" }, - { url = "https://files.pythonhosted.org/packages/56/3a/6ea0d73f49a9bef38a1b3a92e8dd455cea58470985d25635beab93841748/torchvision-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c2abe430c90b1d5e552680037d68da4eb80a5852ebb1c811b2b89d299b10573b", size = 1874920, upload-time = "2026-01-21T16:27:45.348Z" }, - { url = "https://files.pythonhosted.org/packages/51/f8/c0e1ef27c66e15406fece94930e7d6feee4cb6374bbc02d945a630d6426e/torchvision-0.25.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:b75deafa2dfea3e2c2a525559b04783515e3463f6e830cb71de0fb7ea36fe233", size = 2344556, upload-time = "2026-01-21T16:27:40.125Z" }, - { url = "https://files.pythonhosted.org/packages/68/2f/f24b039169db474e8688f649377de082a965fbf85daf4e46c44412f1d15a/torchvision-0.25.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f25aa9e380865b11ea6e9d99d84df86b9cc959f1a007cd966fc6f1ab2ed0e248", size = 8072351, upload-time = "2026-01-21T16:27:21.074Z" }, - { url = "https://files.pythonhosted.org/packages/ad/16/8f650c2e288977cf0f8f85184b90ee56ed170a4919347fc74ee99286ed6f/torchvision-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:f9c55ae8d673ab493325d1267cbd285bb94d56f99626c00ac4644de32a59ede3", size = 4303059, upload-time = "2026-01-21T16:27:11.08Z" }, - { url = "https://files.pythonhosted.org/packages/f5/5b/1562a04a6a5a4cf8cf40016a0cdeda91ede75d6962cff7f809a85ae966a5/torchvision-0.25.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:24e11199e4d84ba9c5ee7825ebdf1cd37ce8deec225117f10243cae984ced3ec", size = 1874918, upload-time = "2026-01-21T16:27:39.02Z" }, - { url = "https://files.pythonhosted.org/packages/36/b1/3d6c42f62c272ce34fcce609bb8939bdf873dab5f1b798fd4e880255f129/torchvision-0.25.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:5f271136d2d2c0b7a24c5671795c6e4fd8da4e0ea98aeb1041f62bc04c4370ef", size = 2309106, upload-time = "2026-01-21T16:27:30.624Z" }, - { url = "https://files.pythonhosted.org/packages/c7/60/59bb9c8b67cce356daeed4cb96a717caa4f69c9822f72e223a0eae7a9bd9/torchvision-0.25.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:855c0dc6d37f462482da7531c6788518baedca1e0847f3df42a911713acdfe52", size = 8071522, upload-time = "2026-01-21T16:27:29.392Z" }, - { url = "https://files.pythonhosted.org/packages/32/a5/9a9b1de0720f884ea50dbf9acb22cbe5312e51d7b8c4ac6ba9b51efd9bba/torchvision-0.25.0-cp313-cp313-win_amd64.whl", hash = "sha256:cef0196be31be421f6f462d1e9da1101be7332d91984caa6f8022e6c78a5877f", size = 4321911, upload-time = "2026-01-21T16:27:35.195Z" }, - { url = "https://files.pythonhosted.org/packages/52/99/dca81ed21ebaeff2b67cc9f815a20fdaa418b69f5f9ea4c6ed71721470db/torchvision-0.25.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a8f8061284395ce31bcd460f2169013382ccf411148ceb2ee38e718e9860f5a7", size = 1896209, upload-time = "2026-01-21T16:27:32.159Z" }, - { url = "https://files.pythonhosted.org/packages/28/cc/2103149761fdb4eaed58a53e8437b2d716d48f05174fab1d9fcf1e2a2244/torchvision-0.25.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:146d02c9876858420adf41f3189fe90e3d6a409cbfa65454c09f25fb33bf7266", size = 2310735, upload-time = "2026-01-21T16:27:22.327Z" }, - { url = "https://files.pythonhosted.org/packages/76/ad/f4c985ad52ddd3b22711c588501be1b330adaeaf6850317f66751711b78c/torchvision-0.25.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:c4d395cb2c4a2712f6eb93a34476cdf7aae74bb6ea2ea1917f858e96344b00aa", size = 8089557, upload-time = "2026-01-21T16:27:27.666Z" }, - { url = "https://files.pythonhosted.org/packages/63/cc/0ea68b5802e5e3c31f44b307e74947bad5a38cc655231d845534ed50ddb8/torchvision-0.25.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5e6b449e9fa7d642142c0e27c41e5a43b508d57ed8e79b7c0a0c28652da8678c", size = 4344260, upload-time = "2026-01-21T16:27:17.018Z" }, - { url = "https://files.pythonhosted.org/packages/9e/1f/fa839532660e2602b7e704d65010787c5bb296258b44fa8b9c1cd6175e7d/torchvision-0.25.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:620a236288d594dcec7634c754484542dc0a5c1b0e0b83a34bda5e91e9b7c3a1", size = 1896193, upload-time = "2026-01-21T16:27:24.785Z" }, - { url = "https://files.pythonhosted.org/packages/80/ed/d51889da7ceaf5ff7a0574fb28f9b6b223df19667265395891f81b364ab3/torchvision-0.25.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:0b5e7f50002a8145a98c5694a018e738c50e2972608310c7e88e1bd4c058f6ce", size = 2309331, upload-time = "2026-01-21T16:27:19.97Z" }, - { url = "https://files.pythonhosted.org/packages/90/a5/f93fcffaddd8f12f9e812256830ec9c9ca65abbf1bc369379f9c364d1ff4/torchvision-0.25.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:632db02300e83793812eee4f61ae6a2686dab10b4cfd628b620dc47747aa9d03", size = 8088713, upload-time = "2026-01-21T16:27:15.281Z" }, - { url = "https://files.pythonhosted.org/packages/1f/eb/d0096eed5690d962853213f2ee00d91478dfcb586b62dbbb449fb8abc3a6/torchvision-0.25.0-cp314-cp314-win_amd64.whl", hash = "sha256:d1abd5ed030c708f5dbf4812ad5f6fbe9384b63c40d6bd79f8df41a4a759a917", size = 4325058, upload-time = "2026-01-21T16:27:26.165Z" }, - { url = "https://files.pythonhosted.org/packages/97/36/96374a4c7ab50dea9787ce987815614ccfe988a42e10ac1a2e3e5b60319a/torchvision-0.25.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ad9a8a5877782944d99186e4502a614770fe906626d76e9cd32446a0ac3075f2", size = 1896207, upload-time = "2026-01-21T16:27:23.383Z" }, - { url = "https://files.pythonhosted.org/packages/b5/e2/7abb10a867db79b226b41da419b63b69c0bd5b82438c4a4ed50e084c552f/torchvision-0.25.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:40a122c3cf4d14b651f095e0f672b688dde78632783fc5cd3d4d5e4f6a828563", size = 2310741, upload-time = "2026-01-21T16:27:18.712Z" }, - { url = "https://files.pythonhosted.org/packages/08/e6/0927784e6ffc340b6676befde1c60260bd51641c9c574b9298d791a9cda4/torchvision-0.25.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:846890161b825b38aa85fc37fb3ba5eea74e7091ff28bab378287111483b6443", size = 8089772, upload-time = "2026-01-21T16:27:14.048Z" }, - { url = "https://files.pythonhosted.org/packages/b6/37/e7ca4ec820d434c0f23f824eb29f0676a0c3e7a118f1514f5b949c3356da/torchvision-0.25.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f07f01d27375ad89d72aa2b3f2180f07da95dd9d2e4c758e015c0acb2da72977", size = 4425879, upload-time = "2026-01-21T16:27:12.579Z" }, + { url = "https://files.pythonhosted.org/packages/b4/bd/d552a2521bade3295b2c6e7a4a0d1022261cab7ca7011f4e2a330dbb3caa/torchvision-0.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55bd6ad4ae77be01ba67a410b05b51f53b0d0ee45f146eb6a0dfb9007e70ab3c", size = 1863499, upload-time = "2026-03-23T18:12:58.696Z" }, + { url = "https://files.pythonhosted.org/packages/33/bf/21b899792b08cae7a298551c68398a79e333697479ed311b3b067aab4bdc/torchvision-0.26.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:1c55dc8affbcc0eb2060fbabbe996ae9e5839b24bb6419777f17848945a411b1", size = 7767527, upload-time = "2026-03-23T18:12:44.348Z" }, + { url = "https://files.pythonhosted.org/packages/9a/45/57bbf9e216850d065e66dd31a50f57424b607f1d878ab8956e56a1f4e36b/torchvision-0.26.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:fd10b5f994c210f4f6d6761cf686f82d748554adf486cb0979770c3252868c8f", size = 7519925, upload-time = "2026-03-23T18:12:53.283Z" }, + { url = "https://files.pythonhosted.org/packages/10/58/ed8f7754299f3e91d6414b6dc09f62b3fa7c6e5d63dfe48d69ab81498a37/torchvision-0.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:de6424b12887ad884f39a0ee446994ae3cd3b6a00a9cafe1bead85a031132af0", size = 3983834, upload-time = "2026-03-23T18:13:00.224Z" }, + { url = "https://files.pythonhosted.org/packages/ae/e7/56b47cc3b132aea90ccce22bcb8975dec688b002150012acc842846039d0/torchvision-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c409e1c3fdebec7a3834465086dbda8bf7680eff79abf7fd2f10c6b59520a7a4", size = 1863502, upload-time = "2026-03-23T18:12:57.326Z" }, + { url = "https://files.pythonhosted.org/packages/f4/ec/5c31c92c08b65662fe9604a4067ae8232582805949f11ddc042cebe818ed/torchvision-0.26.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:406557718e62fdf10f5706e88d8a5ec000f872da913bf629aab9297622585547", size = 7767944, upload-time = "2026-03-23T18:12:42.805Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d8/cb6ccda1a1f35a6597645818641701207b3e8e13553e75fce5d86bac74b2/torchvision-0.26.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d61a5abb6b42a0c0c311996c2ac4b83a94418a97182c83b055a2a4ae985e05aa", size = 7522205, upload-time = "2026-03-23T18:12:54.654Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a9/c272623a0f735c35f0f6cd6dc74784d4f970e800cf063bb76687895a2ab9/torchvision-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:7993c01648e7c61d191b018e84d38fe0825c8fcb2720cd0f37caf7ba14404aa1", size = 4255155, upload-time = "2026-03-23T18:12:32.652Z" }, + { url = "https://files.pythonhosted.org/packages/da/80/0762f77f53605d10c9477be39bb47722cc8e383bbbc2531471ce0e396c07/torchvision-0.26.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5d63dd43162691258b1b3529b9041bac7d54caa37eae0925f997108268cbf7c4", size = 1860809, upload-time = "2026-03-23T18:12:47.629Z" }, + { url = "https://files.pythonhosted.org/packages/e6/81/0b3e58d1478c660a5af4268713486b2df7203f35abd9195fea87348a5178/torchvision-0.26.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a39c7a26538c41fda453f9a9692b5ff9b35a5437db1d94f3027f6f509c160eac", size = 7727494, upload-time = "2026-03-23T18:12:46.062Z" }, + { url = "https://files.pythonhosted.org/packages/b6/dc/d9ab5d29115aa05e12e30f1397a3eeae1d88a511241dc3bce48dc4342675/torchvision-0.26.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:b7e6213620bbf97742e5f79832f9e9d769e6cf0f744c5b53dad80b76db633691", size = 7521747, upload-time = "2026-03-23T18:12:36.815Z" }, + { url = "https://files.pythonhosted.org/packages/a9/1b/f1bc86a918c5f6feab1eeff11982e2060f4704332e96185463d27855bdf5/torchvision-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:4280c35ec8cba1fcc8294fb87e136924708726864c379e4c54494797d86bc474", size = 4319880, upload-time = "2026-03-23T18:12:38.168Z" }, + { url = "https://files.pythonhosted.org/packages/66/28/b4ad0a723ed95b003454caffcc41894b34bd8379df340848cae2c33871de/torchvision-0.26.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:358fc4726d0c08615b6d83b3149854f11efb2a564ed1acb6fce882e151412d23", size = 1951973, upload-time = "2026-03-23T18:12:48.781Z" }, + { url = "https://files.pythonhosted.org/packages/71/e2/7a89096e6cf2f3336353b5338ba925e0addf9d8601920340e6bdf47e8eb3/torchvision-0.26.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:3daf9cc149cf3cdcbd4df9c59dae69ffca86c6823250442c3bbfd63fc2e26c61", size = 7728679, upload-time = "2026-03-23T18:12:26.196Z" }, + { url = "https://files.pythonhosted.org/packages/69/1d/4e1eebc17d18ce080a11dcf3df3f8f717f0efdfa00983f06e8ba79259f61/torchvision-0.26.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:82c3965eca27e86a316e31e4c3e5a16d353e0bcbe0ef8efa2e66502c54493c4b", size = 7609138, upload-time = "2026-03-23T18:12:35.327Z" }, + { url = "https://files.pythonhosted.org/packages/f3/a4/f1155e943ae5b32400d7000adc81c79bb0392b16ceb33bcf13e02e48cced/torchvision-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ebc043cc5a4f0bf22e7680806dbba37ffb19e70f6953bbb44ed1a90aeb5c9bea", size = 4248202, upload-time = "2026-03-23T18:12:41.423Z" }, + { url = "https://files.pythonhosted.org/packages/7f/c8/9bffa9c7f7bdf95b2a0a2dc535c290b9f1cc580c3fb3033ab1246ffffdeb/torchvision-0.26.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:eb61804eb9dbe88c5a2a6c4da8dec1d80d2d0a6f18c999c524e32266cb1ebcd3", size = 1860813, upload-time = "2026-03-23T18:12:39.636Z" }, + { url = "https://files.pythonhosted.org/packages/7b/ac/48f28ffd227991f2e14f4392dde7e8dc14352bb9428c1ef4a4bbf5f7ed85/torchvision-0.26.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:9a904f2131cbfadab4df828088a9f66291ad33f49ff853872aed1f86848ef776", size = 7727777, upload-time = "2026-03-23T18:12:22.549Z" }, + { url = "https://files.pythonhosted.org/packages/a4/21/a2266f7f1b0e58e624ff15fd6f01041f59182c49551ece0db9a183071329/torchvision-0.26.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:0f3e572efe62ad645017ea847e0b5e4f2f638d4e39f05bc011d1eb9ac68d4806", size = 7522174, upload-time = "2026-03-23T18:12:29.565Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ba/1666f90bc0bdd77aaa11dcc42bb9f621a9c3668819c32430452e3d404730/torchvision-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:114bec0c0e98aa4ba446f63e2fe7a2cbca37b39ac933987ee4804f65de121800", size = 4348469, upload-time = "2026-03-23T18:12:24.44Z" }, + { url = "https://files.pythonhosted.org/packages/45/8f/1f0402ac55c2ae15651ff831957d083fe70b2d12282e72612a30ba601512/torchvision-0.26.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:b7d3e295624a28b3b1769228ce1345d94cf4d390dd31136766f76f2d20f718da", size = 1860826, upload-time = "2026-03-23T18:12:34.1Z" }, + { url = "https://files.pythonhosted.org/packages/d2/6a/18a582fe3c5ee26f49b5c9fb21ad8016b4d1c06d10178894a58653946fda/torchvision-0.26.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:7058c5878262937e876f20c25867b33724586aa4499e2853b2d52b99a5e51953", size = 7729089, upload-time = "2026-03-23T18:12:31.394Z" }, + { url = "https://files.pythonhosted.org/packages/c5/9b/f7e119b59499edc00c55c03adc9ec3bd96144d9b81c46852c431f9c64a9a/torchvision-0.26.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:8008474855623c6ba52876589dc52df0aa66e518c25eca841445348e5f79844c", size = 7522704, upload-time = "2026-03-23T18:12:20.301Z" }, + { url = "https://files.pythonhosted.org/packages/d0/6a/09f3844c10643f6c0de5d95abc863420cfaf194c88c7dffd0ac523e2015f/torchvision-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e9d0e022c19a78552fb055d0414d47fecb4a649309b9968573daea160ba6869c", size = 4454275, upload-time = "2026-03-23T18:12:27.487Z" }, ] [[package]] name = "tornado" -version = "6.5.4" +version = "6.5.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/37/1d/0a336abf618272d53f62ebe274f712e213f5a03c0b2339575430b8362ef2/tornado-6.5.4.tar.gz", hash = "sha256:a22fa9047405d03260b483980635f0b041989d8bcc9a313f8fe18b411d84b1d7", size = 513632, upload-time = "2025-12-15T19:21:03.836Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/f1/3173dfa4a18db4a9b03e5d55325559dab51ee653763bb8745a75af491286/tornado-6.5.5.tar.gz", hash = "sha256:192b8f3ea91bd7f1f50c06955416ed76c6b72f96779b962f07f911b91e8d30e9", size = 516006, upload-time = "2026-03-10T21:31:02.067Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d6241c1a16b1c9e4cc28148b1cda97dd1c6cb4fb7068ac1bedc610768dff0ba9", size = 443909, upload-time = "2025-12-15T19:20:48.382Z" }, - { url = "https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2d50f63dda1d2cac3ae1fa23d254e16b5e38153758470e9956cbc3d813d40843", size = 442163, upload-time = "2025-12-15T19:20:49.791Z" }, - { url = "https://files.pythonhosted.org/packages/ba/b5/206f82d51e1bfa940ba366a8d2f83904b15942c45a78dd978b599870ab44/tornado-6.5.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1cf66105dc6acb5af613c054955b8137e34a03698aa53272dbda4afe252be17", size = 445746, upload-time = "2025-12-15T19:20:51.491Z" }, - { url = "https://files.pythonhosted.org/packages/8e/9d/1a3338e0bd30ada6ad4356c13a0a6c35fbc859063fa7eddb309183364ac1/tornado-6.5.4-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50ff0a58b0dc97939d29da29cd624da010e7f804746621c78d14b80238669335", size = 445083, upload-time = "2025-12-15T19:20:52.778Z" }, - { url = "https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fb5e04efa54cf0baabdd10061eb4148e0be137166146fff835745f59ab9f7f", size = 445315, upload-time = "2025-12-15T19:20:53.996Z" }, - { url = "https://files.pythonhosted.org/packages/27/07/2273972f69ca63dbc139694a3fc4684edec3ea3f9efabf77ed32483b875c/tornado-6.5.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9c86b1643b33a4cd415f8d0fe53045f913bf07b4a3ef646b735a6a86047dda84", size = 446003, upload-time = "2025-12-15T19:20:56.101Z" }, - { url = "https://files.pythonhosted.org/packages/d1/83/41c52e47502bf7260044413b6770d1a48dda2f0246f95ee1384a3cd9c44a/tornado-6.5.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:6eb82872335a53dd063a4f10917b3efd28270b56a33db69009606a0312660a6f", size = 445412, upload-time = "2025-12-15T19:20:57.398Z" }, - { url = "https://files.pythonhosted.org/packages/10/c7/bc96917f06cbee182d44735d4ecde9c432e25b84f4c2086143013e7b9e52/tornado-6.5.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6076d5dda368c9328ff41ab5d9dd3608e695e8225d1cd0fd1e006f05da3635a8", size = 445392, upload-time = "2025-12-15T19:20:58.692Z" }, - { url = "https://files.pythonhosted.org/packages/0c/1a/d7592328d037d36f2d2462f4bc1fbb383eec9278bc786c1b111cbbd44cfa/tornado-6.5.4-cp39-abi3-win32.whl", hash = "sha256:1768110f2411d5cd281bac0a090f707223ce77fd110424361092859e089b38d1", size = 446481, upload-time = "2025-12-15T19:21:00.008Z" }, - { url = "https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl", hash = "sha256:fa07d31e0cd85c60713f2b995da613588aa03e1303d75705dca6af8babc18ddc", size = 446886, upload-time = "2025-12-15T19:21:01.287Z" }, - { url = "https://files.pythonhosted.org/packages/50/49/8dc3fd90902f70084bd2cd059d576ddb4f8bb44c2c7c0e33a11422acb17e/tornado-6.5.4-cp39-abi3-win_arm64.whl", hash = "sha256:053e6e16701eb6cbe641f308f4c1a9541f91b6261991160391bfc342e8a551a1", size = 445910, upload-time = "2025-12-15T19:21:02.571Z" }, + { url = "https://files.pythonhosted.org/packages/59/8c/77f5097695f4dd8255ecbd08b2a1ed8ba8b953d337804dd7080f199e12bf/tornado-6.5.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:487dc9cc380e29f58c7ab88f9e27cdeef04b2140862e5076a66fb6bb68bb1bfa", size = 445983, upload-time = "2026-03-10T21:30:44.28Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5e/7625b76cd10f98f1516c36ce0346de62061156352353ef2da44e5c21523c/tornado-6.5.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:65a7f1d46d4bb41df1ac99f5fcb685fb25c7e61613742d5108b010975a9a6521", size = 444246, upload-time = "2026-03-10T21:30:46.571Z" }, + { url = "https://files.pythonhosted.org/packages/b2/04/7b5705d5b3c0fab088f434f9c83edac1573830ca49ccf29fb83bf7178eec/tornado-6.5.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e74c92e8e65086b338fd56333fb9a68b9f6f2fe7ad532645a290a464bcf46be5", size = 447229, upload-time = "2026-03-10T21:30:48.273Z" }, + { url = "https://files.pythonhosted.org/packages/34/01/74e034a30ef59afb4097ef8659515e96a39d910b712a89af76f5e4e1f93c/tornado-6.5.5-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:435319e9e340276428bbdb4e7fa732c2d399386d1de5686cb331ec8eee754f07", size = 448192, upload-time = "2026-03-10T21:30:51.22Z" }, + { url = "https://files.pythonhosted.org/packages/be/00/fe9e02c5a96429fce1a1d15a517f5d8444f9c412e0bb9eadfbe3b0fc55bf/tornado-6.5.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3f54aa540bdbfee7b9eb268ead60e7d199de5021facd276819c193c0fb28ea4e", size = 448039, upload-time = "2026-03-10T21:30:53.52Z" }, + { url = "https://files.pythonhosted.org/packages/82/9e/656ee4cec0398b1d18d0f1eb6372c41c6b889722641d84948351ae19556d/tornado-6.5.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:36abed1754faeb80fbd6e64db2758091e1320f6bba74a4cf8c09cd18ccce8aca", size = 447445, upload-time = "2026-03-10T21:30:55.541Z" }, + { url = "https://files.pythonhosted.org/packages/5a/76/4921c00511f88af86a33de770d64141170f1cfd9c00311aea689949e274e/tornado-6.5.5-cp39-abi3-win32.whl", hash = "sha256:dd3eafaaeec1c7f2f8fdcd5f964e8907ad788fe8a5a32c4426fbbdda621223b7", size = 448582, upload-time = "2026-03-10T21:30:57.142Z" }, + { url = "https://files.pythonhosted.org/packages/2c/23/f6c6112a04d28eed765e374435fb1a9198f73e1ec4b4024184f21faeb1ad/tornado-6.5.5-cp39-abi3-win_amd64.whl", hash = "sha256:6443a794ba961a9f619b1ae926a2e900ac20c34483eea67be4ed8f1e58d3ef7b", size = 448990, upload-time = "2026-03-10T21:30:58.857Z" }, + { url = "https://files.pythonhosted.org/packages/b7/c8/876602cbc96469911f0939f703453c1157b0c826ecb05bdd32e023397d4e/tornado-6.5.5-cp39-abi3-win_arm64.whl", hash = "sha256:2c9a876e094109333f888539ddb2de4361743e5d21eece20688e3e351e4990a6", size = 448016, upload-time = "2026-03-10T21:31:00.43Z" }, ] [[package]] @@ -3595,11 +3712,17 @@ name = "triton" version = "3.6.0" source = { registry = "https://pypi.org/simple" } wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/2c/96f92f3c60387e14cc45aed49487f3486f89ea27106c1b1376913c62abe4/triton-3.6.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49df5ef37379c0c2b5c0012286f80174fcf0e073e5ade1ca9a86c36814553651", size = 176081190, upload-time = "2026-01-20T16:16:00.523Z" }, { url = "https://files.pythonhosted.org/packages/e0/12/b05ba554d2c623bffa59922b94b0775673de251f468a9609bc9e45de95e9/triton-3.6.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8e323d608e3a9bfcc2d9efcc90ceefb764a82b99dea12a86d643c72539ad5d3", size = 188214640, upload-time = "2026-01-20T16:00:35.869Z" }, + { url = "https://files.pythonhosted.org/packages/17/5d/08201db32823bdf77a0e2b9039540080b2e5c23a20706ddba942924ebcd6/triton-3.6.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:374f52c11a711fd062b4bfbb201fd9ac0a5febd28a96fb41b4a0f51dde3157f4", size = 176128243, upload-time = "2026-01-20T16:16:07.857Z" }, { url = "https://files.pythonhosted.org/packages/ab/a8/cdf8b3e4c98132f965f88c2313a4b493266832ad47fb52f23d14d4f86bb5/triton-3.6.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74caf5e34b66d9f3a429af689c1c7128daba1d8208df60e81106b115c00d6fca", size = 188266850, upload-time = "2026-01-20T16:00:43.041Z" }, + { url = "https://files.pythonhosted.org/packages/3c/12/34d71b350e89a204c2c7777a9bba0dcf2f19a5bfdd70b57c4dbc5ffd7154/triton-3.6.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448e02fe6dc898e9e5aa89cf0ee5c371e99df5aa5e8ad976a80b93334f3494fd", size = 176133521, upload-time = "2026-01-20T16:16:13.321Z" }, { url = "https://files.pythonhosted.org/packages/f9/0b/37d991d8c130ce81a8728ae3c25b6e60935838e9be1b58791f5997b24a54/triton-3.6.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10c7f76c6e72d2ef08df639e3d0d30729112f47a56b0c81672edc05ee5116ac9", size = 188289450, upload-time = "2026-01-20T16:00:49.136Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4e/41b0c8033b503fd3cfcd12392cdd256945026a91ff02452bef40ec34bee7/triton-3.6.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1722e172d34e32abc3eb7711d0025bb69d7959ebea84e3b7f7a341cd7ed694d6", size = 176276087, upload-time = "2026-01-20T16:16:18.989Z" }, { url = "https://files.pythonhosted.org/packages/35/f8/9c66bfc55361ec6d0e4040a0337fb5924ceb23de4648b8a81ae9d33b2b38/triton-3.6.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d002e07d7180fd65e622134fbd980c9a3d4211fb85224b56a0a0efbd422ab72f", size = 188400296, upload-time = "2026-01-20T16:00:56.042Z" }, + { url = "https://files.pythonhosted.org/packages/49/55/5ecf0dcaa0f2fbbd4420f7ef227ee3cb172e91e5fede9d0ecaddc43363b4/triton-3.6.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5523241e7d1abca00f1d240949eebdd7c673b005edbbce0aca95b8191f1d43", size = 176138577, upload-time = "2026-01-20T16:16:25.426Z" }, { url = "https://files.pythonhosted.org/packages/df/3d/9e7eee57b37c80cec63322c0231bb6da3cfe535a91d7a4d64896fcb89357/triton-3.6.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a17a5d5985f0ac494ed8a8e54568f092f7057ef60e1b0fa09d3fd1512064e803", size = 188273063, upload-time = "2026-01-20T16:01:07.278Z" }, + { url = "https://files.pythonhosted.org/packages/48/db/56ee649cab5eaff4757541325aca81f52d02d4a7cd3506776cad2451e060/triton-3.6.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b3a97e8ed304dfa9bd23bb41ca04cdf6b2e617d5e782a8653d616037a5d537d", size = 176274804, upload-time = "2026-01-20T16:16:31.528Z" }, { url = "https://files.pythonhosted.org/packages/f6/56/6113c23ff46c00aae423333eb58b3e60bdfe9179d542781955a5e1514cb3/triton-3.6.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46bd1c1af4b6704e554cad2eeb3b0a6513a980d470ccfa63189737340c7746a7", size = 188397994, upload-time = "2026-01-20T16:01:14.236Z" }, ] @@ -3641,7 +3764,7 @@ wheels = [ [[package]] name = "virtualenv" -version = "21.1.0" +version = "21.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -3649,9 +3772,9 @@ dependencies = [ { name = "platformdirs" }, { name = "python-discovery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2f/c9/18d4b36606d6091844daa3bd93cf7dc78e6f5da21d9f21d06c221104b684/virtualenv-21.1.0.tar.gz", hash = "sha256:1990a0188c8f16b6b9cf65c9183049007375b26aad415514d377ccacf1e4fb44", size = 5840471, upload-time = "2026-02-27T08:49:29.702Z" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/92/58199fe10049f9703c2666e809c4f686c54ef0a68b0f6afccf518c0b1eb9/virtualenv-21.2.0.tar.gz", hash = "sha256:1720dc3a62ef5b443092e3f499228599045d7fea4c79199770499df8becf9098", size = 5840618, upload-time = "2026-03-09T17:24:38.013Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/55/896b06bf93a49bec0f4ae2a6f1ed12bd05c8860744ac3a70eda041064e4d/virtualenv-21.1.0-py3-none-any.whl", hash = "sha256:164f5e14c5587d170cf98e60378eb91ea35bf037be313811905d3a24ea33cc07", size = 5825072, upload-time = "2026-02-27T08:49:27.516Z" }, + { url = "https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-py3-none-any.whl", hash = "sha256:1bd755b504931164a5a496d217c014d098426cddc79363ad66ac78125f9d908f", size = 5825084, upload-time = "2026-03-09T17:24:35.378Z" }, ] [[package]] @@ -3692,14 +3815,14 @@ wheels = [ [[package]] name = "werkzeug" -version = "3.1.6" +version = "3.1.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/61/f1/ee81806690a87dab5f5653c1f146c92bc066d7f4cebc603ef88eb9e13957/werkzeug-3.1.6.tar.gz", hash = "sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25", size = 864736, upload-time = "2026-02-19T15:17:18.884Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/43/76ded108b296a49f52de6bac5192ca1c4be84e886f9b5c9ba8427d9694fd/werkzeug-3.1.7.tar.gz", hash = "sha256:fb8c01fe6ab13b9b7cdb46892b99b1d66754e1d7ab8e542e865ec13f526b5351", size = 875700, upload-time = "2026-03-24T01:08:07.687Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/ec/d58832f89ede95652fd01f4f24236af7d32b70cab2196dfcc2d2fd13c5c2/werkzeug-3.1.6-py3-none-any.whl", hash = "sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131", size = 225166, upload-time = "2026-02-19T15:17:17.475Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b2/0bba9bbb4596d2d2f285a16c2ab04118f6b957d8441566e1abb892e6a6b2/werkzeug-3.1.7-py3-none-any.whl", hash = "sha256:4b314d81163a3e1a169b6a0be2a000a0e204e8873c5de6586f453c55688d422f", size = 226295, upload-time = "2026-03-24T01:08:06.133Z" }, ] [[package]] @@ -3713,7 +3836,7 @@ wheels = [ [[package]] name = "zarr" -version = "3.1.5" +version = "3.1.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "donfig" }, @@ -3723,9 +3846,9 @@ dependencies = [ { name = "packaging" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/76/7fa87f57c112c7b9c82f0a730f8b6f333e792574812872e2cd45ab604199/zarr-3.1.5.tar.gz", hash = "sha256:fbe0c79675a40c996de7ca08e80a1c0a20537bd4a9f43418b6d101395c0bba2b", size = 366825, upload-time = "2025-11-21T14:06:01.492Z" } +sdist = { url = "https://files.pythonhosted.org/packages/31/5a/b8a0cf39a14c770c30bd1f2d120c54000c8cd9e84e8e79f38d9a7ce58071/zarr-3.1.6.tar.gz", hash = "sha256:d95e72cbea4b90e9a70679468b8266400331756232576ae2b43400ac5108d0eb", size = 386531, upload-time = "2026-03-23T17:25:18.748Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/44/15/bb13b4913ef95ad5448490821eee4671d0e67673342e4d4070854e5fe081/zarr-3.1.5-py3-none-any.whl", hash = "sha256:29cd905afb6235b94c09decda4258c888fcb79bb6c862ef7c0b8fe009b5c8563", size = 284067, upload-time = "2025-11-21T14:05:59.235Z" }, + { url = "https://files.pythonhosted.org/packages/de/7c/ba8ca8cbe9dbef8e83a95fc208fed8e6686c98b4719aaa0aa7f3d31fe390/zarr-3.1.6-py3-none-any.whl", hash = "sha256:b5a82c5079d1c3d4ee8f06746fa3b9a98a7d804300fa3f4be154362a33e1207e", size = 295655, upload-time = "2026-03-23T17:25:17.189Z" }, ] [[package]] @@ -3735,4 +3858,4 @@ source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] \ No newline at end of file +] From 7d0ad3fb4e16f4a160540991fbebee0f632ac803 Mon Sep 17 00:00:00 2001 From: darshan-mali Date: Fri, 3 Apr 2026 17:43:35 -0700 Subject: [PATCH 228/335] Refactoring. Split all plotting functions from lattice.py into lattice_visualization.py --- src/quantem/imaging/__init__.py | 1 + src/quantem/imaging/lattice.py | 255 +++++++------------ src/quantem/imaging/lattice_visualization.py | 143 +++++++++++ tests/imaging/test_lattice.py | 24 +- 4 files changed, 247 insertions(+), 176 deletions(-) create mode 100644 src/quantem/imaging/lattice_visualization.py diff --git a/src/quantem/imaging/__init__.py b/src/quantem/imaging/__init__.py index e2183514..3637acdc 100644 --- a/src/quantem/imaging/__init__.py +++ b/src/quantem/imaging/__init__.py @@ -1,2 +1,3 @@ from quantem.imaging.drift import DriftCorrection as DriftCorrection from quantem.imaging.lattice import Lattice as Lattice +from quantem.imaging.lattice_visualization import PLOT_REGISTRY as PLOT_REGISTRY diff --git a/src/quantem/imaging/lattice.py b/src/quantem/imaging/lattice.py index 303ff26f..82e0d76f 100644 --- a/src/quantem/imaging/lattice.py +++ b/src/quantem/imaging/lattice.py @@ -1,9 +1,11 @@ +import inspect + import numpy as np from numpy.typing import NDArray from quantem.core.datastructures.dataset2d import Dataset2d from quantem.core.io.serialize import AutoSerialize -from quantem.core.visualization import show_2d +from quantem.imaging.lattice_visualization import PLOT_REGISTRY class Lattice(AutoSerialize): @@ -121,17 +123,14 @@ def image(self, value: Dataset2d | NDArray): self._image = Dataset2d(arr) # type: ignore[call-arg] ### --- Functions --- - def define_lattice( + def define_lattice_vectors( self, origin, u, v, refine_lattice: bool = True, block_size: int | None = None, - plot_lattice: bool = True, - bound_num_vectors: int | None = None, refine_maxiter: int = 200, - **kwargs, ) -> "Lattice": """ Define the lattice for the image using the origin and the u and v vectors starting from the origin. @@ -156,22 +155,19 @@ def define_lattice( For example, if block_size = 5, then the lattice points will be fit in steps of (-5, 5)u * (-5, 5)v -> (-10, 10)u * (-10, 10)v -> ... block_size = None means the entire image will be fit at once. - plot_lattice : bool, default=True - If True, the lattice vectors and lines will be plotted overlaid on the image. - bound_num_vectors : int | None, default=None - The maximum number of lattice vectors to plot in each direction. - For example, if bound_num_vectors = 5, lattice lines between (-5, 5)u * (-5, 5)v will be plotted. - If None, the plotting bounds are set to the image edges. refine_maxiter : int, default=200 Maximum number of iterations for the lattice refinement optimizer (Powell method). - **kwargs - Additional keyword arguments forwarded to the plotting function (show_2d), e.g., cmap, title, etc. Returns ------- self : Lattice Returns the same object, modified in-place. The final values of r0, u, v are stored in self._lat. + + Side Effects + ------------ + Creates self._lat with rows corresponding to r0, u and v + Sets self.default_plot to "lattice_vectors" """ # Lattice self._lat = np.vstack( @@ -342,162 +338,99 @@ def objective(theta: np.ndarray) -> float: lat_flat = res.x self._lat = res.x.reshape(3, 2) - # plotting - if plot_lattice: - fig, ax = show_2d( - self._image.array, - returnfig=True, - **kwargs, - ) + self.default_plot = "lattice_vectors" - # Put the image at lowest zorder so overlays sit on top - if ax.images: - ax.images[-1].set_zorder(0) + return self - H, W = self._image.shape - r0, u, v = (np.asarray(x, dtype=float) for x in self._lat) + ### --- Plot dispatcher --- + def plot(self, kind: str | None = None, show_docstring: bool = False, **kwargs): + """ + Dispatch to a registered visualization function. - # Origin marker (TOP of stack) - ax.scatter( - r0[1], - r0[0], # (y, x) - s=60, - edgecolor=(0, 0, 0), - facecolor=(0, 0.5, 0), - marker="s", - zorder=30, - ) + The function is selected by ``kind`` if given, otherwise by + ``self.default_plot``. Passing ``kind`` here does not mutate the instance. - # Lattice vectors as arrows - n_vec = int(bound_num_vectors) if bound_num_vectors is not None else 1 - - # draw n_vec arrows for u (red) - for k in range(1, n_vec + 1): - tip = r0 + k * u - ax.arrow( - r0[1], - r0[0], # base (y, x) - (tip - r0)[1], - (tip - r0)[0], # delta (y, x) - length_includes_head=True, - head_width=4.0, - head_length=6.0, - linewidth=2.0, - color="red", - zorder=20, - ) + Parameters + ---------- + kind : str | None + Name of the plot. Registered names: - # draw n_vec arrows for v (cyan) - for k in range(1, n_vec + 1): - tip = r0 + k * v - ax.arrow( - r0[1], - r0[0], - (tip - r0)[1], - (tip - r0)[0], - length_includes_head=True, - head_width=4.0, - head_length=6.0, - linewidth=2.0, - color=(0.0, 0.7, 1.0), - zorder=20, - ) + "image" | "dataset" + Shows the image using the default show_2d with no overlays. + Default if default_plot is not set. - # Solve for a,b at plot corners (bounds) - if bound_num_vectors is None: - corners = np.array( - [ - [0.0, 0.0], - [float(H), 0.0], - [0.0, float(W)], - [float(H), float(W)], - ] - ) - else: - n = float(bound_num_vectors) - corners = np.array( - [ - r0 - n * u, - r0 - n * v, - r0 + n * u, - r0 + n * v, - ], - dtype=float, - ) + "lattice_vectors" + Lattice vectors + grid lines overlaid on the image. + Call after define_lattice_vectors(). - # a,b from corners; A = [u v] in columns (2x2), rhs = (corner - r0) - A = np.column_stack((u, v)) - ab = np.linalg.lstsq(A, (corners - r0[None, :]).T, rcond=None)[0] + show_docstring : bool, default False + If True, return formatted signature and docstring of the plotting + function instead of calling it. If False, call the function and + return its result. + + **kwargs + Forwarded verbatim to the selected plotting function. + + Returns + ------- + str or function return + If ``show_docstring`` is True, returns a formatted string with the + function signature and docstring. Otherwise, returns whatever the + selected function returns (typically ``(fig, ax)``). + + Raises + ------ + ValueError + If ``kind`` or ``self.default_plot`` is not in PLOT_REGISTRY. + + Examples + -------- + :: + + lat.plot(kind="lattice_vectors") + lat.plot(kind="lattice_vectors", show_docstring=True) + """ + if not hasattr(self, "default_plot") or kind in ["image", "dataset"]: + from quantem.core.visualization import show_2d - a_min, a_max = int(np.floor(np.min(ab[0]))), int(np.ceil(np.max(ab[0]))) - b_min, b_max = int(np.floor(np.min(ab[1]))), int(np.ceil(np.max(ab[1]))) + return show_2d(self.image, **kwargs) # type:ignore - # Clipping rectangle (image or custom) - if bound_num_vectors is None: - x_lo, x_hi = 0.0, float(H) - y_lo, y_hi = 0.0, float(W) + plot_name = kind if kind is not None else self.default_plot + if plot_name not in PLOT_REGISTRY: + raise ValueError( + f"Unknown plot kind {plot_name!r}. Available: {sorted(PLOT_REGISTRY)}" + ) + + plot_func = PLOT_REGISTRY[plot_name] + if show_docstring: + plot_func = PLOT_REGISTRY[plot_name] + sig = inspect.signature(plot_func) + doc = inspect.getdoc(plot_func) + + if kind is None: + print(f"Current default plot: {self.default_plot}") + + print("\nSignature:") + # Format signature across multiple lines + params = [] + for param_name, param in sig.parameters.items(): + params.append(f" {param}") + + param_str = ",\n".join(params) + return_annotation = ( + f" -> {sig.return_annotation}" + if sig.return_annotation != inspect.Signature.empty + else "" + ) + + print(f"def {plot_func.__name__}(\n{param_str}\n){return_annotation}:") + + print("\nDocstring:") + if doc: + print(doc) else: - # Bounds are the min/max over the provided corners - x_lo, x_hi = float(np.min(corners[:, 0])), float(np.max(corners[:, 0])) - y_lo, y_hi = float(np.min(corners[:, 1])), float(np.max(corners[:, 1])) - - def clipped_segment(base: np.ndarray, direction: np.ndarray): - """Clip base + t*direction to rectangle [x_lo,x_hi] x [y_lo,y_hi].""" - x0, y0 = base - dx, dy = direction - t0, t1 = -np.inf, np.inf - eps = 1e-12 - - # x in [x_lo, x_hi] - if abs(dx) < eps: - if not (x_lo <= x0 <= x_hi): - return None - else: - tx0 = (x_lo - x0) / dx - tx1 = (x_hi - x0) / dx - t_enter, t_exit = (tx0, tx1) if tx0 <= tx1 else (tx1, tx0) - t0, t1 = max(t0, t_enter), min(t1, t_exit) - - # y in [y_lo, y_hi] - if abs(dy) < eps: - if not (y_lo <= y0 <= y_hi): - return None - else: - ty0 = (y_lo - y0) / dy - ty1 = (y_hi - y0) / dy - t_enter, t_exit = (ty0, ty1) if ty0 <= ty1 else (ty1, ty0) - t0, t1 = max(t0, t_enter), min(t1, t_exit) - - if t0 > t1: - return None - - p1 = base + t0 * direction # (x, y) - p2 = base + t1 * direction - return p1, p2 - - # Lattice lines (zorder above image) - # Using x=rows, y=cols: plot(y, x) - - # Lines parallel to v (vary a) - for a in range(a_min, a_max + 1): - base = r0 + a * u - seg = clipped_segment(base, v) - if seg is None: - continue - (x1, y1), (x2, y2) = seg - ax.plot([y1, y2], [x1, x2], color=(0.0, 0.7, 1.0), lw=1, clip_on=True, zorder=10) - - # Lines parallel to u (vary b) - for b in range(b_min, b_max + 1): - base = r0 + b * v - seg = clipped_segment(base, u) - if seg is None: - continue - (x1, y1), (x2, y2) = seg - ax.plot([y1, y2], [x1, x2], color="red", lw=1, clip_on=True, zorder=10) - - # Axes limits (x=rows vertical; y=cols horizontal) - ax.set_xlim(y_lo, y_hi) - ax.set_ylim(x_hi, x_lo) + print("[No docstring]") - return self + return None + + return plot_func(self, **kwargs) diff --git a/src/quantem/imaging/lattice_visualization.py b/src/quantem/imaging/lattice_visualization.py new file mode 100644 index 00000000..af2781c7 --- /dev/null +++ b/src/quantem/imaging/lattice_visualization.py @@ -0,0 +1,143 @@ +""" +lattice_visualization.py +-------------------------------- +All visualization helpers for the Lattice class. Nothing here modifies +Lattice state — functions only read from the instance they receive. + +Registered plot names (callable via ``lattice.plot(kind=...)``) +--------------------------------------------------------------- + "lattice_vectors" - image + lattice vectors/grid (after define_lattice_vectors) +""" + +import numpy as np + +from quantem.core.visualization import show_2d + +# --------------------------------------------------------------------------- +# Registry used by Lattice.plot() +# --------------------------------------------------------------------------- +PLOT_REGISTRY: dict[str, callable] = {} # type:ignore + + +def _register(name: str): + def decorator(fn): + PLOT_REGISTRY[name] = fn + return fn + + return decorator + + +### --- Plotting Functions --- +@_register("lattice_vectors") +def plot_lattice_vectors( + lattice, *, returnfig: bool = False, bound_num_vectors: int | None = None, **kwargs +) -> None | tuple: + """ + Overlay fitted lattice vectors and grid lines on the image. + Call after define_lattice_vectors(). + + Parameters + ---------- + bound_num_vectors : int | None + Number of lattice vectors to draw in each direction. + None clips lines to image edges. + **kwargs forwarded to show_2d (e.g. cmap, title). + """ + fig, ax = show_2d(lattice._image.array, returnfig=True, **kwargs) + if ax.images: + ax.images[-1].set_zorder(0) + H, W = lattice._image.shape + r0, u, v = (np.asarray(x, dtype=float) for x in lattice._lat) + + ax.scatter( + r0[1], r0[0], s=60, edgecolor=(0, 0, 0), facecolor=(0, 0.5, 0), marker="s", zorder=30 + ) + + n_vec = int(bound_num_vectors) if bound_num_vectors is not None else 1 + for k in range(1, n_vec + 1): + tip = r0 + k * u + ax.arrow( + r0[1], + r0[0], + (tip - r0)[1], + (tip - r0)[0], + length_includes_head=True, + head_width=4.0, + head_length=6.0, + linewidth=2.0, + color="red", + zorder=20, + ) + for k in range(1, n_vec + 1): + tip = r0 + k * v + ax.arrow( + r0[1], + r0[0], + (tip - r0)[1], + (tip - r0)[0], + length_includes_head=True, + head_width=4.0, + head_length=6.0, + linewidth=2.0, + color=(0.0, 0.7, 1.0), + zorder=20, + ) + + if bound_num_vectors is None: + corners = np.array([[0.0, 0.0], [float(H), 0.0], [0.0, float(W)], [float(H), float(W)]]) + x_lo, x_hi, y_lo, y_hi = 0.0, float(H), 0.0, float(W) + else: + n = float(bound_num_vectors) + corners = np.array([r0 - n * u, r0 - n * v, r0 + n * u, r0 + n * v], dtype=float) + x_lo = float(np.min(corners[:, 0])) + x_hi = float(np.max(corners[:, 0])) + y_lo = float(np.min(corners[:, 1])) + y_hi = float(np.max(corners[:, 1])) + + A = np.column_stack((u, v)) + ab = np.linalg.lstsq(A, (corners - r0[None, :]).T, rcond=None)[0] + a_min, a_max = int(np.floor(np.min(ab[0]))), int(np.ceil(np.max(ab[0]))) + b_min, b_max = int(np.floor(np.min(ab[1]))), int(np.ceil(np.max(ab[1]))) + + def clipped_segment(base, direction): + x0, y0 = base + dx, dy = direction + t0, t1 = -np.inf, np.inf + eps = 1e-12 + if abs(dx) < eps: + if not (x_lo <= x0 <= x_hi): + return None + else: + ts = sorted([(x_lo - x0) / dx, (x_hi - x0) / dx]) + t0, t1 = max(t0, ts[0]), min(t1, ts[1]) + if abs(dy) < eps: + if not (y_lo <= y0 <= y_hi): + return None + else: + ts = sorted([(y_lo - y0) / dy, (y_hi - y0) / dy]) + t0, t1 = max(t0, ts[0]), min(t1, ts[1]) + if t0 > t1: + return None + return base + t0 * direction, base + t1 * direction + + for a in range(a_min, a_max + 1): + seg = clipped_segment(r0 + a * u, v) + if seg: + ax.plot( + [seg[0][1], seg[1][1]], + [seg[0][0], seg[1][0]], + color=(0.0, 0.7, 1.0), + lw=1, + zorder=10, + ) + for b in range(b_min, b_max + 1): + seg = clipped_segment(r0 + b * v, u) + if seg: + ax.plot([seg[0][1], seg[1][1]], [seg[0][0], seg[1][0]], color="red", lw=1, zorder=10) + + ax.set_xlim(y_lo, y_hi) + ax.set_ylim(x_hi, x_lo) + if returnfig: + return fig, ax + else: + return None diff --git a/tests/imaging/test_lattice.py b/tests/imaging/test_lattice.py index 0d11e9c5..33a289ed 100644 --- a/tests/imaging/test_lattice.py +++ b/tests/imaging/test_lattice.py @@ -76,16 +76,16 @@ def test_image_property(self): lattice.image = np.array([1, 2, 3]) -class TestDefineLattice: - """Test define_lattice method.""" +class TestDefineLatticeVectors: + """Test define_lattice_vectors method.""" def test_basic_define(self): """Test basic lattice definition.""" image = np.random.randn(100, 100) lattice = Lattice.from_data(image) - result = lattice.define_lattice( - origin=[50, 50], u=[5, 0], v=[0, 5], refine_lattice=False, plot_lattice=False + result = lattice.define_lattice_vectors( + origin=[50, 50], u=[5, 0], v=[0, 5], refine_lattice=False ) assert result is lattice @@ -98,25 +98,23 @@ def test_refinement_options(self): lattice = Lattice.from_data(image) # With refinement - lattice.define_lattice( + lattice.define_lattice_vectors( origin=[50, 50], u=[5, 0], v=[0, 5], refine_lattice=True, refine_maxiter=5, - plot_lattice=False, ) assert lattice._lat.shape == (3, 2) # With block_size - lattice.define_lattice( + lattice.define_lattice_vectors( origin=[50, 50], u=[5, 0], v=[0, 5], refine_lattice=True, refine_maxiter=5, block_size=5, - plot_lattice=False, ) assert lattice._lat.shape == (3, 2) @@ -127,13 +125,11 @@ def test_invalid_lattice_params(self): # Wrong shape with pytest.raises(ValueError): - lattice.define_lattice(origin=[1, 2, 3], u=[5, 0], v=[0, 5], plot_lattice=False) + lattice.define_lattice_vectors(origin=[1, 2, 3], u=[5, 0], v=[0, 5]) # Negative block_size with pytest.raises(ValueError): - lattice.define_lattice( - origin=[50, 50], u=[5, 0], v=[0, 5], block_size=-1, plot_lattice=False - ) + lattice.define_lattice_vectors(origin=[50, 50], u=[5, 0], v=[0, 5], block_size=-1) class TestLatticeSerialize: @@ -145,9 +141,7 @@ def test_lattice_save_load(self, tmp_path, store): # Create lattice with image and defined lattice image = np.random.randn(100, 100) lattice = Lattice.from_data(image) - lattice.define_lattice( - origin=[50, 50], u=[5, 0], v=[0, 5], refine_lattice=False, plot_lattice=False - ) + lattice.define_lattice_vectors(origin=[50, 50], u=[5, 0], v=[0, 5], refine_lattice=False) # Save filepath = tmp_path / ("lattice.zip" if store == "zip" else "lattice_dir") From dd12d60afde6adae3ca78dcc16ba35ed2f307e96 Mon Sep 17 00:00:00 2001 From: smribet Date: Mon, 6 Apr 2026 15:38:43 -0700 Subject: [PATCH 229/335] fix --- src/quantem/core/datastructures/dataset.py | 10 ++++++++-- tests/datastructures/test_dataset.py | 21 ++++++++++++++++++++- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/quantem/core/datastructures/dataset.py b/src/quantem/core/datastructures/dataset.py index 3dc3b925..2cbdc29e 100644 --- a/src/quantem/core/datastructures/dataset.py +++ b/src/quantem/core/datastructures/dataset.py @@ -511,22 +511,28 @@ def crop( raise ValueError("Length of crop_widths must match length of axes.") full_slices = [] + new_origin = self.origin.astype(float).copy() crop_dict = dict(zip(axes, crop_widths)) - for axis, _ in enumerate(self.shape): + for axis, axis_size in enumerate(self.shape): if axis in crop_dict: before, after = crop_dict[axis] start = before stop = after if after != 0 else None - full_slices.append(slice(start, stop)) + axis_slice = slice(start, stop) + normalized_start, _, _ = axis_slice.indices(axis_size) + full_slices.append(axis_slice) + new_origin[axis] = new_origin[axis] + normalized_start * self.sampling[axis] else: full_slices.append(slice(None)) if modify_in_place is False: dataset = self.copy() dataset.array = dataset.array[tuple(full_slices)] + dataset.origin = new_origin return dataset self.array = self.array[tuple(full_slices)] + self.origin = new_origin return None @overload diff --git a/tests/datastructures/test_dataset.py b/tests/datastructures/test_dataset.py index fc5c108d..a005e420 100644 --- a/tests/datastructures/test_dataset.py +++ b/tests/datastructures/test_dataset.py @@ -243,11 +243,14 @@ def test_crop(self, sample_dataset_2d): cropped_dataset = sample_dataset_2d.crop(crop_widths=((1, 9), (1, 9))) # Check shape assert cropped_dataset.shape == (8, 8) # Original (10, 10) - 1 from each side + assert np.array_equal(cropped_dataset.origin, np.array([1, 1])) # Check that the original dataset is unchanged assert sample_dataset_2d.shape == (10, 10) + assert np.array_equal(sample_dataset_2d.origin, np.array([0, 0])) # Test modify_in_place sample_dataset_2d.crop(crop_widths=((1, 9), (1, 9)), modify_in_place=True) assert sample_dataset_2d.shape == (8, 8) + assert np.array_equal(sample_dataset_2d.origin, np.array([1, 1])) def test_crop_4dstem_kspace(self): """Test cropping k-space axes of a 4D-STEM dataset.""" @@ -258,9 +261,14 @@ def test_crop_4dstem_kspace(self): def test_crop_4dstem_realspace_in_place(self): """Test in-place real-space crop of a 4D-STEM dataset.""" - dset = Dataset.from_array(np.random.rand(16, 16, 32, 32)) + dset = Dataset.from_array( + np.random.rand(16, 16, 32, 32), + origin=(10, 20, 30, 40), + sampling=(0.5, 0.25, 2, 3), + ) dset.crop(crop_widths=((4, 12), (4, 12)), axes=(0, 1), modify_in_place=True) assert dset.shape == (8, 8, 32, 32) + assert np.array_equal(dset.origin, np.array([12, 21, 30, 40])) def test_crop_4dstem_stop_zero(self): """Test that stop=0 keeps all remaining elements.""" @@ -268,6 +276,17 @@ def test_crop_4dstem_stop_zero(self): cropped = dset.crop(crop_widths=((10, 0), (10, 0)), axes=(2, 3)) assert cropped.shape == (8, 8, 86, 86) + def test_crop_single_axis_updates_origin_in_place(self): + """Test in-place single-axis crop updates origin.""" + dset = Dataset.from_array( + np.random.rand(2048, 64), + origin=(5, 7), + sampling=(0.5, 2), + ) + dset.crop(crop_widths=((80, 2000),), axes=0, modify_in_place=True) + assert dset.shape == (1920, 64) + assert np.array_equal(dset.origin, np.array([45, 7])) + def test_bin(self, sample_dataset_2d): """Test bin method.""" # Bin by factor of 2 From 7dfb38be4e404e6b473ea4bd91f171ba6f585761 Mon Sep 17 00:00:00 2001 From: smribet Date: Tue, 7 Apr 2026 10:46:41 -0700 Subject: [PATCH 230/335] updating slicing too --- src/quantem/core/datastructures/dataset.py | 21 ++++++++------ tests/datastructures/test_dataset.py | 33 ++++++++++++++++++++++ 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/quantem/core/datastructures/dataset.py b/src/quantem/core/datastructures/dataset.py index 2cbdc29e..c7d2ce86 100644 --- a/src/quantem/core/datastructures/dataset.py +++ b/src/quantem/core/datastructures/dataset.py @@ -875,22 +875,25 @@ def __getitem__(self, index) -> Self: # Compute which dimensions are kept kept_axes = [i for i, idx in enumerate(index) if not isinstance(idx, (int, np.integer))] + kept_axis_to_index = {axis: j for j, axis in enumerate(kept_axes)} # Slice/reduce metadata accordingly - new_origin = ( - np.asarray(self.origin)[kept_axes] if np.ndim(self.origin) > 0 else self.origin - ) + origin_array = np.asarray(self.origin, dtype=float) + sampling_array = np.asarray(self.sampling, dtype=float) + new_origin = origin_array[kept_axes].copy() if np.ndim(self.origin) > 0 else self.origin new_sampling = ( - np.asarray(self.sampling)[kept_axes] if np.ndim(self.sampling) > 0 else self.sampling + sampling_array[kept_axes].copy() if np.ndim(self.sampling) > 0 else self.sampling ) new_units = [self.units[i] for i in kept_axes] if len(self.units) > 0 else self.units - # Adjust sampling for slice steps (e.g. [::2] doubles spacing) + # Adjust origin/sampling for sliced axes. for i, idx in enumerate(index): - if isinstance(idx, slice) and idx.step not in (None, 1): - if i in kept_axes: - j = kept_axes.index(i) - new_sampling[j] *= idx.step + if isinstance(idx, slice) and i in kept_axis_to_index: + j = kept_axis_to_index[i] + normalized_start, _, normalized_step = idx.indices(self.shape[i]) + new_origin[j] = new_origin[j] + normalized_start * sampling_array[i] + if normalized_step != 1: + new_sampling[j] *= normalized_step out_ndim = array_view.ndim diff --git a/tests/datastructures/test_dataset.py b/tests/datastructures/test_dataset.py index a005e420..9c83262c 100644 --- a/tests/datastructures/test_dataset.py +++ b/tests/datastructures/test_dataset.py @@ -287,6 +287,39 @@ def test_crop_single_axis_updates_origin_in_place(self): assert dset.shape == (1920, 64) assert np.array_equal(dset.origin, np.array([45, 7])) + def test_getitem_slice_updates_origin_like_crop(self): + """Test slicing keeps origin aligned with crop semantics.""" + dset = Dataset.from_array( + np.random.rand(16, 8), + origin=(10, 20), + sampling=(0.5, 2.0), + units=["nm", "nm"], + ) + + sliced = dset[4:12] + cropped = dset.crop(crop_widths=((4, 12),), axes=0) + + assert sliced.shape == (8, 8) + assert np.array_equal(sliced.origin, np.array([12, 20])) + assert np.array_equal(sliced.origin, cropped.origin) + assert np.array_equal(sliced.sampling, cropped.sampling) + + def test_getitem_slice_reduced_rank_updates_origin_and_sampling(self): + """Test slicing before integer indexing updates remaining metadata.""" + dset = Dataset.from_array( + np.random.rand(10, 6, 4), + origin=(1.5, 10, -2), + sampling=(0.25, 2.0, 5.0), + units=["nm", "nm", "1/nm"], + ) + + sliced = dset[2:8:2, 3] + + assert sliced.shape == (3, 4) + assert np.allclose(sliced.origin, np.array([2.0, -2.0])) + assert np.allclose(sliced.sampling, np.array([0.5, 5.0])) + assert sliced.units == ["nm", "1/nm"] + def test_bin(self, sample_dataset_2d): """Test bin method.""" # Bin by factor of 2 From d92e5c460402d6e17f7a9be9a9f26d5d9d289da0 Mon Sep 17 00:00:00 2001 From: smribet Date: Wed, 8 Apr 2026 06:17:43 -0700 Subject: [PATCH 231/335] adding doc strings --- src/quantem/core/datastructures/dataset.py | 4 ++-- src/quantem/core/datastructures/dataset2d.py | 6 +++--- src/quantem/core/datastructures/dataset3d.py | 9 ++++----- src/quantem/core/datastructures/dataset4d.py | 2 +- src/quantem/core/datastructures/dataset4dstem.py | 4 ++-- src/quantem/core/io/file_readers.py | 2 +- src/quantem/diffractive_imaging/dataset_models.py | 2 +- 7 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/quantem/core/datastructures/dataset.py b/src/quantem/core/datastructures/dataset.py index c7d2ce86..4d2ab9e1 100644 --- a/src/quantem/core/datastructures/dataset.py +++ b/src/quantem/core/datastructures/dataset.py @@ -23,7 +23,7 @@ class Dataset(AutoSerialize): Attributes (Properties): array (NDArray): The underlying n-dimensional NumPy array data. name (str): A descriptive name for the dataset. - origin (NDArray): The origin coordinates for each dimension (1D array). + origin (NDArray): The origin coordinates for each dimension (1D array) in calibrated units. sampling (NDArray): The sampling rate/spacing for each dimension (1D array). units (list[str]): Units for each dimension. signal_units (str): Units for the array values. @@ -84,7 +84,7 @@ def from_array( name: str | None The name of the Dataset. origin: NDArray | tuple | list | float | int | None - The origin of the Dataset. + The origin of the Dataset in calibrated units. sampling: NDArray | tuple | list | float | int | None The sampling of the Dataset. units: list[str] | tuple | list | None diff --git a/src/quantem/core/datastructures/dataset2d.py b/src/quantem/core/datastructures/dataset2d.py index a5a94eac..13dac401 100644 --- a/src/quantem/core/datastructures/dataset2d.py +++ b/src/quantem/core/datastructures/dataset2d.py @@ -40,7 +40,7 @@ def __init__( name : str A descriptive name for the dataset origin : NDArray | tuple | list | float | int - The origin coordinates for each dimension + The origin coordinates for each dimension in calibrated units sampling : NDArray | tuple | list | float | int The sampling rate/spacing for each dimension units : list[str] | tuple | list @@ -102,9 +102,9 @@ def from_array( name : str | None, optional A descriptive name for the dataset. If None, defaults to "2D dataset" origin : NDArray | tuple | list | float | int | None, optional - The origin coordinates for each dimension. If None, defaults to zeros + The origin coordinates for each dimension in calibrated units. If None, defaults to zeros sampling : NDArray | tuple | list | float | int | None, optional - The sampling rate/spacing for each dimension. If None, defaults to ones + The sampling rate/spacing for each dimension in calibrated units. If None, defaults to ones units : list[str] | tuple | list | None, optional Units for each dimension. If None, defaults to ["pixels"] * 4 signal_units : str, optional diff --git a/src/quantem/core/datastructures/dataset3d.py b/src/quantem/core/datastructures/dataset3d.py index 118ec66f..1af7b4e6 100644 --- a/src/quantem/core/datastructures/dataset3d.py +++ b/src/quantem/core/datastructures/dataset3d.py @@ -41,7 +41,7 @@ def __init__( name : str A descriptive name for the dataset origin : NDArray | tuple | list | float | int - The origin coordinates for each dimension + The origin coordinates for each dimension in calibrated units sampling : NDArray | tuple | list | float | int The sampling rate/spacing for each dimension units : list[str] | tuple | list @@ -80,7 +80,7 @@ def from_array( name : str | None Dataset name. Default: "3D dataset" origin : NDArray | tuple | list | float | int | None - Origin for each dimension. Default: [0, 0, 0] + Origin for each dimension in calibrated units. Default: [0, 0, 0] sampling : NDArray | tuple | list | float | int | None Sampling for each dimension. Default: [1, 1, 1] units : list[str] | tuple | list | None @@ -148,7 +148,7 @@ def from_shape( fill_value : float Value to fill array with. Default: 0.0 origin : NDArray | tuple | list | float | int | None - Origin for each dimension + Origin for each dimension in calibrated units. sampling : NDArray | tuple | list | float | int | None Sampling for each dimension units : list[str] | tuple | list | None @@ -318,8 +318,7 @@ def show( ncols = min(ncols, n_frames) # Don't create more columns than frames images = [self.array[i] for i in frame_idx] labels = [ - f"Frame {i}" if title_prefix is None else f"{title_prefix} {i}" - for i in frame_idx + f"Frame {i}" if title_prefix is None else f"{title_prefix} {i}" for i in frame_idx ] # Pad last row to complete the grid (show_2d requires rectangular input) remainder = n_frames % ncols diff --git a/src/quantem/core/datastructures/dataset4d.py b/src/quantem/core/datastructures/dataset4d.py index 981243df..8e5bdfa0 100644 --- a/src/quantem/core/datastructures/dataset4d.py +++ b/src/quantem/core/datastructures/dataset4d.py @@ -39,7 +39,7 @@ def __init__( name : str A descriptive name for the dataset origin : NDArray | tuple | list | float | int - The origin coordinates for each dimension + The origin coordinates for each dimension in calibrated units sampling : NDArray | tuple | list | float | int The sampling rate/spacing for each dimension units : list[str] | tuple | list diff --git a/src/quantem/core/datastructures/dataset4dstem.py b/src/quantem/core/datastructures/dataset4dstem.py index 28328636..79cbc479 100644 --- a/src/quantem/core/datastructures/dataset4dstem.py +++ b/src/quantem/core/datastructures/dataset4dstem.py @@ -59,7 +59,7 @@ def __init__( name : str A descriptive name for the dataset origin : NDArray | tuple | list | float | int - The origin coordinates for each dimension + The origin coordinates for each dimension in calibrated units sampling : NDArray | tuple | list | float | int The sampling rate/spacing for each dimension units : list[str] | tuple | list @@ -133,7 +133,7 @@ def from_array( name : str | None, optional A descriptive name for the dataset. If None, defaults to "4D-STEM dataset" origin : NDArray | tuple | list | float | int | None, optional - The origin coordinates for each dimension. If None, defaults to zeros + The origin coordinates for each dimension in calibrated units. If None, defaults to zeros sampling : NDArray | tuple | list | float | int | None, optional The sampling rate/spacing for each dimension. If None, defaults to ones units : list[str] | tuple | list | None, optional diff --git a/src/quantem/core/io/file_readers.py b/src/quantem/core/io/file_readers.py index 60716ac9..98e9e8fa 100644 --- a/src/quantem/core/io/file_readers.py +++ b/src/quantem/core/io/file_readers.py @@ -38,7 +38,7 @@ def read_4dstem( name : str | None, optional A descriptive name for the dataset. If None, defaults to "4D-STEM dataset" origin : NDArray | tuple | list | float | int | None, optional - The origin coordinates for each dimension. If None, defaults to zeros + The origin coordinates for each dimension in calibrated units. If None, defaults to zeros sampling : NDArray | tuple | list | float | int | None, optional The sampling rate/spacing for each dimension. If None, defaults to ones units : list[str] | tuple | list | None, optional diff --git a/src/quantem/diffractive_imaging/dataset_models.py b/src/quantem/diffractive_imaging/dataset_models.py index 2fbfa119..0f037a4a 100644 --- a/src/quantem/diffractive_imaging/dataset_models.py +++ b/src/quantem/diffractive_imaging/dataset_models.py @@ -754,7 +754,7 @@ def from_array( name : str | None, optional A descriptive name for the dataset. If None, defaults to "4D-STEM dataset" origin : np.ndarray | tuple | list | float | int | None, optional - The origin coordinates for each dimension. If None, defaults to zeros + The origin coordinates for each dimension in calibrated units. If None, defaults to zeros sampling : np.ndarray | tuple | list | float | int | None, optional The sampling rate/spacing for each dimension. If None, defaults to ones units : list[str] | tuple | list | None, optional From ea6cfa32be3b18c3244d0353c43cb750ed75814a Mon Sep 17 00:00:00 2001 From: quantem-bot Date: Mon, 13 Apr 2026 10:50:57 +0000 Subject: [PATCH 232/335] chore: update lock file --- uv.lock | 732 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 376 insertions(+), 356 deletions(-) diff --git a/uv.lock b/uv.lock index 5972dd69..9a59b1d3 100644 --- a/uv.lock +++ b/uv.lock @@ -51,16 +51,16 @@ wheels = [ [[package]] name = "anywidget" -version = "0.9.21" +version = "0.10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ipywidgets" }, { name = "psygnal" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/be/5e/cbea445bf062b81e4d366ca29dae4f0aedc7a64f384afc24670e07bec560/anywidget-0.9.21.tar.gz", hash = "sha256:b8d0172029ac426573053c416c6a587838661612208bb390fa0607862e594b27", size = 390517, upload-time = "2025-11-12T17:06:03.035Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/00/8b5d3cc6146dd091abf1495869ca447f8a801b9938dbd13b15477a51c658/anywidget-0.10.0.tar.gz", hash = "sha256:4ec7cba129613af8d210654d6297c5c3fb5d76fcd2efea829fd58050d8b28802", size = 391132, upload-time = "2026-04-07T04:27:18.795Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/03/c17464bbf682ea87e7e3de2ddc63395e359a78ae9c01f55fc78759ecbd79/anywidget-0.9.21-py3-none-any.whl", hash = "sha256:78c268e0fbdb1dfd15da37fb578f9cf0a0df58a430e68d9156942b7a9391a761", size = 231797, upload-time = "2025-11-12T17:06:01.564Z" }, + { url = "https://files.pythonhosted.org/packages/e6/f1/80a173ddbb7753d3a3bf9fb7fa698280a5f418dedce2a8662e960807441b/anywidget-0.10.0-py3-none-any.whl", hash = "sha256:a001543b55be9b4e9c9783b2f1aa0d5be733b321723d9bf30975fce840730a57", size = 254744, upload-time = "2026-04-07T04:27:17.252Z" }, ] [[package]] @@ -284,103 +284,103 @@ wheels = [ [[package]] name = "charset-normalizer" -version = "3.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/60/e3bec1881450851b087e301bedc3daa9377a4d45f1c26aa90b0b235e38aa/charset_normalizer-3.4.6.tar.gz", hash = "sha256:1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6", size = 143363, upload-time = "2026-03-15T18:53:25.478Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/28/ff6f234e628a2de61c458be2779cb182bc03f6eec12200d4a525bbfc9741/charset_normalizer-3.4.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:82060f995ab5003a2d6e0f4ad29065b7672b6593c8c63559beefe5b443242c3e", size = 293582, upload-time = "2026-03-15T18:50:25.454Z" }, - { url = "https://files.pythonhosted.org/packages/1c/b7/b1a117e5385cbdb3205f6055403c2a2a220c5ea80b8716c324eaf75c5c95/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60c74963d8350241a79cb8feea80e54d518f72c26db618862a8f53e5023deaf9", size = 197240, upload-time = "2026-03-15T18:50:27.196Z" }, - { url = "https://files.pythonhosted.org/packages/a1/5f/2574f0f09f3c3bc1b2f992e20bce6546cb1f17e111c5be07308dc5427956/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6e4333fb15c83f7d1482a76d45a0818897b3d33f00efd215528ff7c51b8e35d", size = 217363, upload-time = "2026-03-15T18:50:28.601Z" }, - { url = "https://files.pythonhosted.org/packages/4a/d1/0ae20ad77bc949ddd39b51bf383b6ca932f2916074c95cad34ae465ab71f/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bc72863f4d9aba2e8fd9085e63548a324ba706d2ea2c83b260da08a59b9482de", size = 212994, upload-time = "2026-03-15T18:50:30.102Z" }, - { url = "https://files.pythonhosted.org/packages/60/ac/3233d262a310c1b12633536a07cde5ddd16985e6e7e238e9f3f9423d8eb9/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9cc4fc6c196d6a8b76629a70ddfcd4635a6898756e2d9cac5565cf0654605d73", size = 204697, upload-time = "2026-03-15T18:50:31.654Z" }, - { url = "https://files.pythonhosted.org/packages/25/3c/8a18fc411f085b82303cfb7154eed5bd49c77035eb7608d049468b53f87c/charset_normalizer-3.4.6-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:0c173ce3a681f309f31b87125fecec7a5d1347261ea11ebbb856fa6006b23c8c", size = 191673, upload-time = "2026-03-15T18:50:33.433Z" }, - { url = "https://files.pythonhosted.org/packages/ff/a7/11cfe61d6c5c5c7438d6ba40919d0306ed83c9ab957f3d4da2277ff67836/charset_normalizer-3.4.6-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c907cdc8109f6c619e6254212e794d6548373cc40e1ec75e6e3823d9135d29cc", size = 201120, upload-time = "2026-03-15T18:50:35.105Z" }, - { url = "https://files.pythonhosted.org/packages/b5/10/cf491fa1abd47c02f69687046b896c950b92b6cd7337a27e6548adbec8e4/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:404a1e552cf5b675a87f0651f8b79f5f1e6fd100ee88dc612f89aa16abd4486f", size = 200911, upload-time = "2026-03-15T18:50:36.819Z" }, - { url = "https://files.pythonhosted.org/packages/28/70/039796160b48b18ed466fde0af84c1b090c4e288fae26cd674ad04a2d703/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e3c701e954abf6fc03a49f7c579cc80c2c6cc52525340ca3186c41d3f33482ef", size = 192516, upload-time = "2026-03-15T18:50:38.228Z" }, - { url = "https://files.pythonhosted.org/packages/ff/34/c56f3223393d6ff3124b9e78f7de738047c2d6bc40a4f16ac0c9d7a1cb3c/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7a6967aaf043bceabab5412ed6bd6bd26603dae84d5cb75bf8d9a74a4959d398", size = 218795, upload-time = "2026-03-15T18:50:39.664Z" }, - { url = "https://files.pythonhosted.org/packages/e8/3b/ce2d4f86c5282191a041fdc5a4ce18f1c6bd40a5bd1f74cf8625f08d51c1/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5feb91325bbceade6afab43eb3b508c63ee53579fe896c77137ded51c6b6958e", size = 201833, upload-time = "2026-03-15T18:50:41.552Z" }, - { url = "https://files.pythonhosted.org/packages/3b/9b/b6a9f76b0fd7c5b5ec58b228ff7e85095370282150f0bd50b3126f5506d6/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f820f24b09e3e779fe84c3c456cb4108a7aa639b0d1f02c28046e11bfcd088ed", size = 213920, upload-time = "2026-03-15T18:50:43.33Z" }, - { url = "https://files.pythonhosted.org/packages/ae/98/7bc23513a33d8172365ed30ee3a3b3fe1ece14a395e5fc94129541fc6003/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b35b200d6a71b9839a46b9b7fff66b6638bb52fc9658aa58796b0326595d3021", size = 206951, upload-time = "2026-03-15T18:50:44.789Z" }, - { url = "https://files.pythonhosted.org/packages/32/73/c0b86f3d1458468e11aec870e6b3feac931facbe105a894b552b0e518e79/charset_normalizer-3.4.6-cp311-cp311-win32.whl", hash = "sha256:9ca4c0b502ab399ef89248a2c84c54954f77a070f28e546a85e91da627d1301e", size = 143703, upload-time = "2026-03-15T18:50:46.103Z" }, - { url = "https://files.pythonhosted.org/packages/c6/e3/76f2facfe8eddee0bbd38d2594e709033338eae44ebf1738bcefe0a06185/charset_normalizer-3.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:a9e68c9d88823b274cf1e72f28cb5dc89c990edf430b0bfd3e2fb0785bfeabf4", size = 153857, upload-time = "2026-03-15T18:50:47.563Z" }, - { url = "https://files.pythonhosted.org/packages/e2/dc/9abe19c9b27e6cd3636036b9d1b387b78c40dedbf0b47f9366737684b4b0/charset_normalizer-3.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:97d0235baafca5f2b09cf332cc275f021e694e8362c6bb9c96fc9a0eb74fc316", size = 142751, upload-time = "2026-03-15T18:50:49.234Z" }, - { url = "https://files.pythonhosted.org/packages/e5/62/c0815c992c9545347aeea7859b50dc9044d147e2e7278329c6e02ac9a616/charset_normalizer-3.4.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ef7fedc7a6ecbe99969cd09632516738a97eeb8bd7258bf8a0f23114c057dab", size = 295154, upload-time = "2026-03-15T18:50:50.88Z" }, - { url = "https://files.pythonhosted.org/packages/a8/37/bdca6613c2e3c58c7421891d80cc3efa1d32e882f7c4a7ee6039c3fc951a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4ea868bc28109052790eb2b52a9ab33f3aa7adc02f96673526ff47419490e21", size = 199191, upload-time = "2026-03-15T18:50:52.658Z" }, - { url = "https://files.pythonhosted.org/packages/6c/92/9934d1bbd69f7f398b38c5dae1cbf9cc672e7c34a4adf7b17c0a9c17d15d/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:836ab36280f21fc1a03c99cd05c6b7af70d2697e374c7af0b61ed271401a72a2", size = 218674, upload-time = "2026-03-15T18:50:54.102Z" }, - { url = "https://files.pythonhosted.org/packages/af/90/25f6ab406659286be929fd89ab0e78e38aa183fc374e03aa3c12d730af8a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f1ce721c8a7dfec21fcbdfe04e8f68174183cf4e8188e0645e92aa23985c57ff", size = 215259, upload-time = "2026-03-15T18:50:55.616Z" }, - { url = "https://files.pythonhosted.org/packages/4e/ef/79a463eb0fff7f96afa04c1d4c51f8fc85426f918db467854bfb6a569ce3/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e28d62a8fc7a1fa411c43bd65e346f3bce9716dc51b897fbe930c5987b402d5", size = 207276, upload-time = "2026-03-15T18:50:57.054Z" }, - { url = "https://files.pythonhosted.org/packages/f7/72/d0426afec4b71dc159fa6b4e68f868cd5a3ecd918fec5813a15d292a7d10/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:530d548084c4a9f7a16ed4a294d459b4f229db50df689bfe92027452452943a0", size = 195161, upload-time = "2026-03-15T18:50:58.686Z" }, - { url = "https://files.pythonhosted.org/packages/bf/18/c82b06a68bfcb6ce55e508225d210c7e6a4ea122bfc0748892f3dc4e8e11/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30f445ae60aad5e1f8bdbb3108e39f6fbc09f4ea16c815c66578878325f8f15a", size = 203452, upload-time = "2026-03-15T18:51:00.196Z" }, - { url = "https://files.pythonhosted.org/packages/44/d6/0c25979b92f8adafdbb946160348d8d44aa60ce99afdc27df524379875cb/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ac2393c73378fea4e52aa56285a3d64be50f1a12395afef9cce47772f60334c2", size = 202272, upload-time = "2026-03-15T18:51:01.703Z" }, - { url = "https://files.pythonhosted.org/packages/2e/3d/7fea3e8fe84136bebbac715dd1221cc25c173c57a699c030ab9b8900cbb7/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:90ca27cd8da8118b18a52d5f547859cc1f8354a00cd1e8e5120df3e30d6279e5", size = 195622, upload-time = "2026-03-15T18:51:03.526Z" }, - { url = "https://files.pythonhosted.org/packages/57/8a/d6f7fd5cb96c58ef2f681424fbca01264461336d2a7fc875e4446b1f1346/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e5a94886bedca0f9b78fecd6afb6629142fd2605aa70a125d49f4edc6037ee6", size = 220056, upload-time = "2026-03-15T18:51:05.269Z" }, - { url = "https://files.pythonhosted.org/packages/16/50/478cdda782c8c9c3fb5da3cc72dd7f331f031e7f1363a893cdd6ca0f8de0/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:695f5c2823691a25f17bc5d5ffe79fa90972cc34b002ac6c843bb8a1720e950d", size = 203751, upload-time = "2026-03-15T18:51:06.858Z" }, - { url = "https://files.pythonhosted.org/packages/75/fc/cc2fcac943939c8e4d8791abfa139f685e5150cae9f94b60f12520feaa9b/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:231d4da14bcd9301310faf492051bee27df11f2bc7549bc0bb41fef11b82daa2", size = 216563, upload-time = "2026-03-15T18:51:08.564Z" }, - { url = "https://files.pythonhosted.org/packages/a8/b7/a4add1d9a5f68f3d037261aecca83abdb0ab15960a3591d340e829b37298/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a056d1ad2633548ca18ffa2f85c202cfb48b68615129143915b8dc72a806a923", size = 209265, upload-time = "2026-03-15T18:51:10.312Z" }, - { url = "https://files.pythonhosted.org/packages/6c/18/c094561b5d64a24277707698e54b7f67bd17a4f857bbfbb1072bba07c8bf/charset_normalizer-3.4.6-cp312-cp312-win32.whl", hash = "sha256:c2274ca724536f173122f36c98ce188fd24ce3dad886ec2b7af859518ce008a4", size = 144229, upload-time = "2026-03-15T18:51:11.694Z" }, - { url = "https://files.pythonhosted.org/packages/ab/20/0567efb3a8fd481b8f34f739ebddc098ed062a59fed41a8d193a61939e8f/charset_normalizer-3.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:c8ae56368f8cc97c7e40a7ee18e1cedaf8e780cd8bc5ed5ac8b81f238614facb", size = 154277, upload-time = "2026-03-15T18:51:13.004Z" }, - { url = "https://files.pythonhosted.org/packages/15/57/28d79b44b51933119e21f65479d0864a8d5893e494cf5daab15df0247c17/charset_normalizer-3.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:899d28f422116b08be5118ef350c292b36fc15ec2daeb9ea987c89281c7bb5c4", size = 142817, upload-time = "2026-03-15T18:51:14.408Z" }, - { url = "https://files.pythonhosted.org/packages/1e/1d/4fdabeef4e231153b6ed7567602f3b68265ec4e5b76d6024cf647d43d981/charset_normalizer-3.4.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:11afb56037cbc4b1555a34dd69151e8e069bee82e613a73bef6e714ce733585f", size = 294823, upload-time = "2026-03-15T18:51:15.755Z" }, - { url = "https://files.pythonhosted.org/packages/47/7b/20e809b89c69d37be748d98e84dce6820bf663cf19cf6b942c951a3e8f41/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:423fb7e748a08f854a08a222b983f4df1912b1daedce51a72bd24fe8f26a1843", size = 198527, upload-time = "2026-03-15T18:51:17.177Z" }, - { url = "https://files.pythonhosted.org/packages/37/a6/4f8d27527d59c039dce6f7622593cdcd3d70a8504d87d09eb11e9fdc6062/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d73beaac5e90173ac3deb9928a74763a6d230f494e4bfb422c217a0ad8e629bf", size = 218388, upload-time = "2026-03-15T18:51:18.934Z" }, - { url = "https://files.pythonhosted.org/packages/f6/9b/4770ccb3e491a9bacf1c46cc8b812214fe367c86a96353ccc6daf87b01ec/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d60377dce4511655582e300dc1e5a5f24ba0cb229005a1d5c8d0cb72bb758ab8", size = 214563, upload-time = "2026-03-15T18:51:20.374Z" }, - { url = "https://files.pythonhosted.org/packages/2b/58/a199d245894b12db0b957d627516c78e055adc3a0d978bc7f65ddaf7c399/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:530e8cebeea0d76bdcf93357aa5e41336f48c3dc709ac52da2bb167c5b8271d9", size = 206587, upload-time = "2026-03-15T18:51:21.807Z" }, - { url = "https://files.pythonhosted.org/packages/7e/70/3def227f1ec56f5c69dfc8392b8bd63b11a18ca8178d9211d7cc5e5e4f27/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:a26611d9987b230566f24a0a125f17fe0de6a6aff9f25c9f564aaa2721a5fb88", size = 194724, upload-time = "2026-03-15T18:51:23.508Z" }, - { url = "https://files.pythonhosted.org/packages/58/ab/9318352e220c05efd31c2779a23b50969dc94b985a2efa643ed9077bfca5/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:34315ff4fc374b285ad7f4a0bf7dcbfe769e1b104230d40f49f700d4ab6bbd84", size = 202956, upload-time = "2026-03-15T18:51:25.239Z" }, - { url = "https://files.pythonhosted.org/packages/75/13/f3550a3ac25b70f87ac98c40d3199a8503676c2f1620efbf8d42095cfc40/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ddd609f9e1af8c7bd6e2aca279c931aefecd148a14402d4e368f3171769fd", size = 201923, upload-time = "2026-03-15T18:51:26.682Z" }, - { url = "https://files.pythonhosted.org/packages/1b/db/c5c643b912740b45e8eec21de1bbab8e7fc085944d37e1e709d3dcd9d72f/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:80d0a5615143c0b3225e5e3ef22c8d5d51f3f72ce0ea6fb84c943546c7b25b6c", size = 195366, upload-time = "2026-03-15T18:51:28.129Z" }, - { url = "https://files.pythonhosted.org/packages/5a/67/3b1c62744f9b2448443e0eb160d8b001c849ec3fef591e012eda6484787c/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:92734d4d8d187a354a556626c221cd1a892a4e0802ccb2af432a1d85ec012194", size = 219752, upload-time = "2026-03-15T18:51:29.556Z" }, - { url = "https://files.pythonhosted.org/packages/f6/98/32ffbaf7f0366ffb0445930b87d103f6b406bc2c271563644bde8a2b1093/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:613f19aa6e082cf96e17e3ffd89383343d0d589abda756b7764cf78361fd41dc", size = 203296, upload-time = "2026-03-15T18:51:30.921Z" }, - { url = "https://files.pythonhosted.org/packages/41/12/5d308c1bbe60cabb0c5ef511574a647067e2a1f631bc8634fcafaccd8293/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2b1a63e8224e401cafe7739f77efd3f9e7f5f2026bda4aead8e59afab537784f", size = 215956, upload-time = "2026-03-15T18:51:32.399Z" }, - { url = "https://files.pythonhosted.org/packages/53/e9/5f85f6c5e20669dbe56b165c67b0260547dea97dba7e187938833d791687/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6cceb5473417d28edd20c6c984ab6fee6c6267d38d906823ebfe20b03d607dc2", size = 208652, upload-time = "2026-03-15T18:51:34.214Z" }, - { url = "https://files.pythonhosted.org/packages/f1/11/897052ea6af56df3eef3ca94edafee410ca699ca0c7b87960ad19932c55e/charset_normalizer-3.4.6-cp313-cp313-win32.whl", hash = "sha256:d7de2637729c67d67cf87614b566626057e95c303bc0a55ffe391f5205e7003d", size = 143940, upload-time = "2026-03-15T18:51:36.15Z" }, - { url = "https://files.pythonhosted.org/packages/a1/5c/724b6b363603e419829f561c854b87ed7c7e31231a7908708ac086cdf3e2/charset_normalizer-3.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:572d7c822caf521f0525ba1bce1a622a0b85cf47ffbdae6c9c19e3b5ac3c4389", size = 154101, upload-time = "2026-03-15T18:51:37.876Z" }, - { url = "https://files.pythonhosted.org/packages/01/a5/7abf15b4c0968e47020f9ca0935fb3274deb87cb288cd187cad92e8cdffd/charset_normalizer-3.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a4474d924a47185a06411e0064b803c68be044be2d60e50e8bddcc2649957c1f", size = 143109, upload-time = "2026-03-15T18:51:39.565Z" }, - { url = "https://files.pythonhosted.org/packages/25/6f/ffe1e1259f384594063ea1869bfb6be5cdb8bc81020fc36c3636bc8302a1/charset_normalizer-3.4.6-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9cc6e6d9e571d2f863fa77700701dae73ed5f78881efc8b3f9a4398772ff53e8", size = 294458, upload-time = "2026-03-15T18:51:41.134Z" }, - { url = "https://files.pythonhosted.org/packages/56/60/09bb6c13a8c1016c2ed5c6a6488e4ffef506461aa5161662bd7636936fb1/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5960d965e67165d75b7c7ffc60a83ec5abfc5c11b764ec13ea54fbef8b4421", size = 199277, upload-time = "2026-03-15T18:51:42.953Z" }, - { url = "https://files.pythonhosted.org/packages/00/50/dcfbb72a5138bbefdc3332e8d81a23494bf67998b4b100703fd15fa52d81/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b3694e3f87f8ac7ce279d4355645b3c878d24d1424581b46282f24b92f5a4ae2", size = 218758, upload-time = "2026-03-15T18:51:44.339Z" }, - { url = "https://files.pythonhosted.org/packages/03/b3/d79a9a191bb75f5aa81f3aaaa387ef29ce7cb7a9e5074ba8ea095cc073c2/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5d11595abf8dd942a77883a39d81433739b287b6aa71620f15164f8096221b30", size = 215299, upload-time = "2026-03-15T18:51:45.871Z" }, - { url = "https://files.pythonhosted.org/packages/76/7e/bc8911719f7084f72fd545f647601ea3532363927f807d296a8c88a62c0d/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7bda6eebafd42133efdca535b04ccb338ab29467b3f7bf79569883676fc628db", size = 206811, upload-time = "2026-03-15T18:51:47.308Z" }, - { url = "https://files.pythonhosted.org/packages/e2/40/c430b969d41dda0c465aa36cc7c2c068afb67177bef50905ac371b28ccc7/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:bbc8c8650c6e51041ad1be191742b8b421d05bbd3410f43fa2a00c8db87678e8", size = 193706, upload-time = "2026-03-15T18:51:48.849Z" }, - { url = "https://files.pythonhosted.org/packages/48/15/e35e0590af254f7df984de1323640ef375df5761f615b6225ba8deb9799a/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22c6f0c2fbc31e76c3b8a86fba1a56eda6166e238c29cdd3d14befdb4a4e4815", size = 202706, upload-time = "2026-03-15T18:51:50.257Z" }, - { url = "https://files.pythonhosted.org/packages/5e/bd/f736f7b9cc5e93a18b794a50346bb16fbfd6b37f99e8f306f7951d27c17c/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7edbed096e4a4798710ed6bc75dcaa2a21b68b6c356553ac4823c3658d53743a", size = 202497, upload-time = "2026-03-15T18:51:52.012Z" }, - { url = "https://files.pythonhosted.org/packages/9d/ba/2cc9e3e7dfdf7760a6ed8da7446d22536f3d0ce114ac63dee2a5a3599e62/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7f9019c9cb613f084481bd6a100b12e1547cf2efe362d873c2e31e4035a6fa43", size = 193511, upload-time = "2026-03-15T18:51:53.723Z" }, - { url = "https://files.pythonhosted.org/packages/9e/cb/5be49b5f776e5613be07298c80e1b02a2d900f7a7de807230595c85a8b2e/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:58c948d0d086229efc484fe2f30c2d382c86720f55cd9bc33591774348ad44e0", size = 220133, upload-time = "2026-03-15T18:51:55.333Z" }, - { url = "https://files.pythonhosted.org/packages/83/43/99f1b5dad345accb322c80c7821071554f791a95ee50c1c90041c157ae99/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:419a9d91bd238052642a51938af8ac05da5b3343becde08d5cdeab9046df9ee1", size = 203035, upload-time = "2026-03-15T18:51:56.736Z" }, - { url = "https://files.pythonhosted.org/packages/87/9a/62c2cb6a531483b55dddff1a68b3d891a8b498f3ca555fbcf2978e804d9d/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5273b9f0b5835ff0350c0828faea623c68bfa65b792720c453e22b25cc72930f", size = 216321, upload-time = "2026-03-15T18:51:58.17Z" }, - { url = "https://files.pythonhosted.org/packages/6e/79/94a010ff81e3aec7c293eb82c28f930918e517bc144c9906a060844462eb/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0e901eb1049fdb80f5bd11ed5ea1e498ec423102f7a9b9e4645d5b8204ff2815", size = 208973, upload-time = "2026-03-15T18:51:59.998Z" }, - { url = "https://files.pythonhosted.org/packages/2a/57/4ecff6d4ec8585342f0c71bc03efaa99cb7468f7c91a57b105bcd561cea8/charset_normalizer-3.4.6-cp314-cp314-win32.whl", hash = "sha256:b4ff1d35e8c5bd078be89349b6f3a845128e685e751b6ea1169cf2160b344c4d", size = 144610, upload-time = "2026-03-15T18:52:02.213Z" }, - { url = "https://files.pythonhosted.org/packages/80/94/8434a02d9d7f168c25767c64671fead8d599744a05d6a6c877144c754246/charset_normalizer-3.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:74119174722c4349af9708993118581686f343adc1c8c9c007d59be90d077f3f", size = 154962, upload-time = "2026-03-15T18:52:03.658Z" }, - { url = "https://files.pythonhosted.org/packages/46/4c/48f2cdbfd923026503dfd67ccea45c94fd8fe988d9056b468579c66ed62b/charset_normalizer-3.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:e5bcc1a1ae744e0bb59641171ae53743760130600da8db48cbb6e4918e186e4e", size = 143595, upload-time = "2026-03-15T18:52:05.123Z" }, - { url = "https://files.pythonhosted.org/packages/31/93/8878be7569f87b14f1d52032946131bcb6ebbd8af3e20446bc04053dc3f1/charset_normalizer-3.4.6-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ad8faf8df23f0378c6d527d8b0b15ea4a2e23c89376877c598c4870d1b2c7866", size = 314828, upload-time = "2026-03-15T18:52:06.831Z" }, - { url = "https://files.pythonhosted.org/packages/06/b6/fae511ca98aac69ecc35cde828b0a3d146325dd03d99655ad38fc2cc3293/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5ea69428fa1b49573eef0cc44a1d43bebd45ad0c611eb7d7eac760c7ae771bc", size = 208138, upload-time = "2026-03-15T18:52:08.239Z" }, - { url = "https://files.pythonhosted.org/packages/54/57/64caf6e1bf07274a1e0b7c160a55ee9e8c9ec32c46846ce59b9c333f7008/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:06a7e86163334edfc5d20fe104db92fcd666e5a5df0977cb5680a506fe26cc8e", size = 224679, upload-time = "2026-03-15T18:52:10.043Z" }, - { url = "https://files.pythonhosted.org/packages/aa/cb/9ff5a25b9273ef160861b41f6937f86fae18b0792fe0a8e75e06acb08f1d/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e1f6e2f00a6b8edb562826e4632e26d063ac10307e80f7461f7de3ad8ef3f077", size = 223475, upload-time = "2026-03-15T18:52:11.854Z" }, - { url = "https://files.pythonhosted.org/packages/fc/97/440635fc093b8d7347502a377031f9605a1039c958f3cd18dcacffb37743/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95b52c68d64c1878818687a473a10547b3292e82b6f6fe483808fb1468e2f52f", size = 215230, upload-time = "2026-03-15T18:52:13.325Z" }, - { url = "https://files.pythonhosted.org/packages/cd/24/afff630feb571a13f07c8539fbb502d2ab494019492aaffc78ef41f1d1d0/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:7504e9b7dc05f99a9bbb4525c67a2c155073b44d720470a148b34166a69c054e", size = 199045, upload-time = "2026-03-15T18:52:14.752Z" }, - { url = "https://files.pythonhosted.org/packages/e5/17/d1399ecdaf7e0498c327433e7eefdd862b41236a7e484355b8e0e5ebd64b/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:172985e4ff804a7ad08eebec0a1640ece87ba5041d565fff23c8f99c1f389484", size = 211658, upload-time = "2026-03-15T18:52:16.278Z" }, - { url = "https://files.pythonhosted.org/packages/b5/38/16baa0affb957b3d880e5ac2144caf3f9d7de7bc4a91842e447fbb5e8b67/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4be9f4830ba8741527693848403e2c457c16e499100963ec711b1c6f2049b7c7", size = 210769, upload-time = "2026-03-15T18:52:17.782Z" }, - { url = "https://files.pythonhosted.org/packages/05/34/c531bc6ac4c21da9ddfddb3107be2287188b3ea4b53b70fc58f2a77ac8d8/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:79090741d842f564b1b2827c0b82d846405b744d31e84f18d7a7b41c20e473ff", size = 201328, upload-time = "2026-03-15T18:52:19.553Z" }, - { url = "https://files.pythonhosted.org/packages/fa/73/a5a1e9ca5f234519c1953608a03fe109c306b97fdfb25f09182babad51a7/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:87725cfb1a4f1f8c2fc9890ae2f42094120f4b44db9360be5d99a4c6b0e03a9e", size = 225302, upload-time = "2026-03-15T18:52:21.043Z" }, - { url = "https://files.pythonhosted.org/packages/ba/f6/cd782923d112d296294dea4bcc7af5a7ae0f86ab79f8fefbda5526b6cfc0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:fcce033e4021347d80ed9c66dcf1e7b1546319834b74445f561d2e2221de5659", size = 211127, upload-time = "2026-03-15T18:52:22.491Z" }, - { url = "https://files.pythonhosted.org/packages/0e/c5/0b6898950627af7d6103a449b22320372c24c6feda91aa24e201a478d161/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:ca0276464d148c72defa8bb4390cce01b4a0e425f3b50d1435aa6d7a18107602", size = 222840, upload-time = "2026-03-15T18:52:24.113Z" }, - { url = "https://files.pythonhosted.org/packages/7d/25/c4bba773bef442cbdc06111d40daa3de5050a676fa26e85090fc54dd12f0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:197c1a244a274bb016dd8b79204850144ef77fe81c5b797dc389327adb552407", size = 216890, upload-time = "2026-03-15T18:52:25.541Z" }, - { url = "https://files.pythonhosted.org/packages/35/1a/05dacadb0978da72ee287b0143097db12f2e7e8d3ffc4647da07a383b0b7/charset_normalizer-3.4.6-cp314-cp314t-win32.whl", hash = "sha256:2a24157fa36980478dd1770b585c0f30d19e18f4fb0c47c13aa568f871718579", size = 155379, upload-time = "2026-03-15T18:52:27.05Z" }, - { url = "https://files.pythonhosted.org/packages/5d/7a/d269d834cb3a76291651256f3b9a5945e81d0a49ab9f4a498964e83c0416/charset_normalizer-3.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:cd5e2801c89992ed8c0a3f0293ae83c159a60d9a5d685005383ef4caca77f2c4", size = 169043, upload-time = "2026-03-15T18:52:28.502Z" }, - { url = "https://files.pythonhosted.org/packages/23/06/28b29fba521a37a8932c6a84192175c34d49f84a6d4773fa63d05f9aff22/charset_normalizer-3.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:47955475ac79cc504ef2704b192364e51d0d473ad452caedd0002605f780101c", size = 148523, upload-time = "2026-03-15T18:52:29.956Z" }, - { url = "https://files.pythonhosted.org/packages/2a/68/687187c7e26cb24ccbd88e5069f5ef00eba804d36dde11d99aad0838ab45/charset_normalizer-3.4.6-py3-none-any.whl", hash = "sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69", size = 61455, upload-time = "2026-03-15T18:53:23.833Z" }, +version = "3.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/d7/b5b7020a0565c2e9fa8c09f4b5fa6232feb326b8c20081ccded47ea368fd/charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7", size = 309705, upload-time = "2026-04-02T09:26:02.191Z" }, + { url = "https://files.pythonhosted.org/packages/5a/53/58c29116c340e5456724ecd2fff4196d236b98f3da97b404bc5e51ac3493/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7", size = 206419, upload-time = "2026-04-02T09:26:03.583Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/e8146dc6591a37a00e5144c63f29fb7c97a734ea8a111190783c0e60ab63/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e", size = 227901, upload-time = "2026-04-02T09:26:04.738Z" }, + { url = "https://files.pythonhosted.org/packages/fb/73/77486c4cd58f1267bf17db420e930c9afa1b3be3fe8c8b8ebbebc9624359/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c", size = 222742, upload-time = "2026-04-02T09:26:06.36Z" }, + { url = "https://files.pythonhosted.org/packages/a1/fa/f74eb381a7d94ded44739e9d94de18dc5edc9c17fb8c11f0a6890696c0a9/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df", size = 214061, upload-time = "2026-04-02T09:26:08.347Z" }, + { url = "https://files.pythonhosted.org/packages/dc/92/42bd3cefcf7687253fb86694b45f37b733c97f59af3724f356fa92b8c344/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265", size = 199239, upload-time = "2026-04-02T09:26:09.823Z" }, + { url = "https://files.pythonhosted.org/packages/4c/3d/069e7184e2aa3b3cddc700e3dd267413dc259854adc3380421c805c6a17d/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4", size = 210173, upload-time = "2026-04-02T09:26:10.953Z" }, + { url = "https://files.pythonhosted.org/packages/62/51/9d56feb5f2e7074c46f93e0ebdbe61f0848ee246e2f0d89f8e20b89ebb8f/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e", size = 209841, upload-time = "2026-04-02T09:26:12.142Z" }, + { url = "https://files.pythonhosted.org/packages/d2/59/893d8f99cc4c837dda1fe2f1139079703deb9f321aabcb032355de13b6c7/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38", size = 200304, upload-time = "2026-04-02T09:26:13.711Z" }, + { url = "https://files.pythonhosted.org/packages/7d/1d/ee6f3be3464247578d1ed5c46de545ccc3d3ff933695395c402c21fa6b77/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c", size = 229455, upload-time = "2026-04-02T09:26:14.941Z" }, + { url = "https://files.pythonhosted.org/packages/54/bb/8fb0a946296ea96a488928bdce8ef99023998c48e4713af533e9bb98ef07/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b", size = 210036, upload-time = "2026-04-02T09:26:16.478Z" }, + { url = "https://files.pythonhosted.org/packages/9a/bc/015b2387f913749f82afd4fcba07846d05b6d784dd16123cb66860e0237d/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c", size = 224739, upload-time = "2026-04-02T09:26:17.751Z" }, + { url = "https://files.pythonhosted.org/packages/17/ab/63133691f56baae417493cba6b7c641571a2130eb7bceba6773367ab9ec5/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d", size = 216277, upload-time = "2026-04-02T09:26:18.981Z" }, + { url = "https://files.pythonhosted.org/packages/06/6d/3be70e827977f20db77c12a97e6a9f973631a45b8d186c084527e53e77a4/charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad", size = 147819, upload-time = "2026-04-02T09:26:20.295Z" }, + { url = "https://files.pythonhosted.org/packages/20/d9/5f67790f06b735d7c7637171bbfd89882ad67201891b7275e51116ed8207/charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00", size = 159281, upload-time = "2026-04-02T09:26:21.74Z" }, + { url = "https://files.pythonhosted.org/packages/ca/83/6413f36c5a34afead88ce6f66684d943d91f233d76dd083798f9602b75ae/charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1", size = 147843, upload-time = "2026-04-02T09:26:22.901Z" }, + { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, + { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, + { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, + { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, + { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, + { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, + { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, + { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, + { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, + { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, + { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, + { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, + { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, + { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, + { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, + { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, + { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, + { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, + { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, + { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, + { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, + { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, ] [[package]] name = "click" -version = "8.3.1" +version = "8.3.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +sdist = { url = "https://files.pythonhosted.org/packages/57/75/31212c6bf2503fdf920d87fee5d7a86a2e3bcf444984126f13d8e4016804/click-8.3.2.tar.gz", hash = "sha256:14162b8b3b3550a7d479eafa77dfd3c38d9dc8951f6f69c78913a8f9a7540fd5", size = 302856, upload-time = "2026-04-03T19:14:45.118Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, + { url = "https://files.pythonhosted.org/packages/e4/20/71885d8b97d4f3dde17b1fdb92dbd4908b00541c5a3379787137285f602e/click-8.3.2-py3-none-any.whl", hash = "sha256:1924d2c27c5653561cd2cae4548d1406039cb79b858b747cfea24924bbc1616d", size = 108379, upload-time = "2026-04-03T19:14:43.505Z" }, ] [[package]] @@ -656,10 +656,10 @@ wheels = [ [[package]] name = "cuda-pathfinder" -version = "1.5.0" +version = "1.5.2" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/93/66/0c02bd330e7d976f83fa68583d6198d76f23581bcbb5c0e98a6148f326e5/cuda_pathfinder-1.5.0-py3-none-any.whl", hash = "sha256:498f90a9e9de36044a7924742aecce11c50c49f735f1bc53e05aa46de9ea4110", size = 49739, upload-time = "2026-03-24T21:14:30.869Z" }, + { url = "https://files.pythonhosted.org/packages/f2/f9/1b9b60a30fc463c14cdea7a77228131a0ccc89572e8df9cb86c9648271ab/cuda_pathfinder-1.5.2-py3-none-any.whl", hash = "sha256:0c5f160a7756c5b072723cbbd6d861e38917ef956c68150b02f0b6e9271c71fa", size = 49988, upload-time = "2026-04-06T23:01:05.17Z" }, ] [[package]] @@ -961,49 +961,49 @@ wheels = [ [[package]] name = "greenlet" -version = "3.3.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a3/51/1664f6b78fc6ebbd98019a1fd730e83fa78f2db7058f72b1463d3612b8db/greenlet-3.3.2.tar.gz", hash = "sha256:2eaf067fc6d886931c7962e8c6bede15d2f01965560f3359b27c80bde2d151f2", size = 188267, upload-time = "2026-02-20T20:54:15.531Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/47/16400cb42d18d7a6bb46f0626852c1718612e35dcb0dffa16bbaffdf5dd2/greenlet-3.3.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:c56692189a7d1c7606cb794be0a8381470d95c57ce5be03fb3d0ef57c7853b86", size = 278890, upload-time = "2026-02-20T20:19:39.263Z" }, - { url = "https://files.pythonhosted.org/packages/a3/90/42762b77a5b6aa96cd8c0e80612663d39211e8ae8a6cd47c7f1249a66262/greenlet-3.3.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ebd458fa8285960f382841da585e02201b53a5ec2bac6b156fc623b5ce4499f", size = 581120, upload-time = "2026-02-20T20:47:30.161Z" }, - { url = "https://files.pythonhosted.org/packages/bf/6f/f3d64f4fa0a9c7b5c5b3c810ff1df614540d5aa7d519261b53fba55d4df9/greenlet-3.3.2-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a443358b33c4ec7b05b79a7c8b466f5d275025e750298be7340f8fc63dff2a55", size = 594363, upload-time = "2026-02-20T20:55:56.965Z" }, - { url = "https://files.pythonhosted.org/packages/72/83/3e06a52aca8128bdd4dcd67e932b809e76a96ab8c232a8b025b2850264c5/greenlet-3.3.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e2cd90d413acbf5e77ae41e5d3c9b3ac1d011a756d7284d7f3f2b806bbd6358", size = 594156, upload-time = "2026-02-20T20:20:59.955Z" }, - { url = "https://files.pythonhosted.org/packages/70/79/0de5e62b873e08fe3cef7dbe84e5c4bc0e8ed0c7ff131bccb8405cd107c8/greenlet-3.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:442b6057453c8cb29b4fb36a2ac689382fc71112273726e2423f7f17dc73bf99", size = 1554649, upload-time = "2026-02-20T20:49:32.293Z" }, - { url = "https://files.pythonhosted.org/packages/5a/00/32d30dee8389dc36d42170a9c66217757289e2afb0de59a3565260f38373/greenlet-3.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:45abe8eb6339518180d5a7fa47fa01945414d7cca5ecb745346fc6a87d2750be", size = 1619472, upload-time = "2026-02-20T20:21:07.966Z" }, - { url = "https://files.pythonhosted.org/packages/f1/3a/efb2cf697fbccdf75b24e2c18025e7dfa54c4f31fab75c51d0fe79942cef/greenlet-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e692b2dae4cc7077cbb11b47d258533b48c8fde69a33d0d8a82e2fe8d8531d5", size = 230389, upload-time = "2026-02-20T20:17:18.772Z" }, - { url = "https://files.pythonhosted.org/packages/e1/a1/65bbc059a43a7e2143ec4fc1f9e3f673e04f9c7b371a494a101422ac4fd5/greenlet-3.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:02b0a8682aecd4d3c6c18edf52bc8e51eacdd75c8eac52a790a210b06aa295fd", size = 229645, upload-time = "2026-02-20T20:18:18.695Z" }, - { url = "https://files.pythonhosted.org/packages/ea/ab/1608e5a7578e62113506740b88066bf09888322a311cff602105e619bd87/greenlet-3.3.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ac8d61d4343b799d1e526db579833d72f23759c71e07181c2d2944e429eb09cd", size = 280358, upload-time = "2026-02-20T20:17:43.971Z" }, - { url = "https://files.pythonhosted.org/packages/a5/23/0eae412a4ade4e6623ff7626e38998cb9b11e9ff1ebacaa021e4e108ec15/greenlet-3.3.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ceec72030dae6ac0c8ed7591b96b70410a8be370b6a477b1dbc072856ad02bd", size = 601217, upload-time = "2026-02-20T20:47:31.462Z" }, - { url = "https://files.pythonhosted.org/packages/f8/16/5b1678a9c07098ecb9ab2dd159fafaf12e963293e61ee8d10ecb55273e5e/greenlet-3.3.2-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2a5be83a45ce6188c045bcc44b0ee037d6a518978de9a5d97438548b953a1ac", size = 611792, upload-time = "2026-02-20T20:55:58.423Z" }, - { url = "https://files.pythonhosted.org/packages/50/1f/5155f55bd71cabd03765a4aac9ac446be129895271f73872c36ebd4b04b6/greenlet-3.3.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e99d1749147ac21dde49b99c9abffcbc1e2d55c67501465ef0930d6e78e070", size = 613875, upload-time = "2026-02-20T20:21:01.102Z" }, - { url = "https://files.pythonhosted.org/packages/fc/dd/845f249c3fcd69e32df80cdab059b4be8b766ef5830a3d0aa9d6cad55beb/greenlet-3.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c956a19350e2c37f2c48b336a3afb4bff120b36076d9d7fb68cb44e05d95b79", size = 1571467, upload-time = "2026-02-20T20:49:33.495Z" }, - { url = "https://files.pythonhosted.org/packages/2a/50/2649fe21fcc2b56659a452868e695634722a6655ba245d9f77f5656010bf/greenlet-3.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c6f8ba97d17a1e7d664151284cb3315fc5f8353e75221ed4324f84eb162b395", size = 1640001, upload-time = "2026-02-20T20:21:09.154Z" }, - { url = "https://files.pythonhosted.org/packages/9b/40/cc802e067d02af8b60b6771cea7d57e21ef5e6659912814babb42b864713/greenlet-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:34308836d8370bddadb41f5a7ce96879b72e2fdfb4e87729330c6ab52376409f", size = 231081, upload-time = "2026-02-20T20:17:28.121Z" }, - { url = "https://files.pythonhosted.org/packages/58/2e/fe7f36ff1982d6b10a60d5e0740c759259a7d6d2e1dc41da6d96de32fff6/greenlet-3.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:d3a62fa76a32b462a97198e4c9e99afb9ab375115e74e9a83ce180e7a496f643", size = 230331, upload-time = "2026-02-20T20:17:23.34Z" }, - { url = "https://files.pythonhosted.org/packages/ac/48/f8b875fa7dea7dd9b33245e37f065af59df6a25af2f9561efa8d822fde51/greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:aa6ac98bdfd716a749b84d4034486863fd81c3abde9aa3cf8eff9127981a4ae4", size = 279120, upload-time = "2026-02-20T20:19:01.9Z" }, - { url = "https://files.pythonhosted.org/packages/49/8d/9771d03e7a8b1ee456511961e1b97a6d77ae1dea4a34a5b98eee706689d3/greenlet-3.3.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab0c7e7901a00bc0a7284907273dc165b32e0d109a6713babd04471327ff7986", size = 603238, upload-time = "2026-02-20T20:47:32.873Z" }, - { url = "https://files.pythonhosted.org/packages/59/0e/4223c2bbb63cd5c97f28ffb2a8aee71bdfb30b323c35d409450f51b91e3e/greenlet-3.3.2-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d248d8c23c67d2291ffd47af766e2a3aa9fa1c6703155c099feb11f526c63a92", size = 614219, upload-time = "2026-02-20T20:55:59.817Z" }, - { url = "https://files.pythonhosted.org/packages/7a/34/259b28ea7a2a0c904b11cd36c79b8cef8019b26ee5dbe24e73b469dea347/greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6997d360a4e6a4e936c0f9625b1c20416b8a0ea18a8e19cabbefc712e7397ab", size = 616774, upload-time = "2026-02-20T20:21:02.454Z" }, - { url = "https://files.pythonhosted.org/packages/0a/03/996c2d1689d486a6e199cb0f1cf9e4aa940c500e01bdf201299d7d61fa69/greenlet-3.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64970c33a50551c7c50491671265d8954046cb6e8e2999aacdd60e439b70418a", size = 1571277, upload-time = "2026-02-20T20:49:34.795Z" }, - { url = "https://files.pythonhosted.org/packages/d9/c4/2570fc07f34a39f2caf0bf9f24b0a1a0a47bc2e8e465b2c2424821389dfc/greenlet-3.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1a9172f5bf6bd88e6ba5a84e0a68afeac9dc7b6b412b245dd64f52d83c81e55b", size = 1640455, upload-time = "2026-02-20T20:21:10.261Z" }, - { url = "https://files.pythonhosted.org/packages/91/39/5ef5aa23bc545aa0d31e1b9b55822b32c8da93ba657295840b6b34124009/greenlet-3.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:a7945dd0eab63ded0a48e4dcade82939783c172290a7903ebde9e184333ca124", size = 230961, upload-time = "2026-02-20T20:16:58.461Z" }, - { url = "https://files.pythonhosted.org/packages/62/6b/a89f8456dcb06becff288f563618e9f20deed8dd29beea14f9a168aef64b/greenlet-3.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:394ead29063ee3515b4e775216cb756b2e3b4a7e55ae8fd884f17fa579e6b327", size = 230221, upload-time = "2026-02-20T20:17:37.152Z" }, - { url = "https://files.pythonhosted.org/packages/3f/ae/8bffcbd373b57a5992cd077cbe8858fff39110480a9d50697091faea6f39/greenlet-3.3.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8d1658d7291f9859beed69a776c10822a0a799bc4bfe1bd4272bb60e62507dab", size = 279650, upload-time = "2026-02-20T20:18:00.783Z" }, - { url = "https://files.pythonhosted.org/packages/d1/c0/45f93f348fa49abf32ac8439938726c480bd96b2a3c6f4d949ec0124b69f/greenlet-3.3.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18cb1b7337bca281915b3c5d5ae19f4e76d35e1df80f4ad3c1a7be91fadf1082", size = 650295, upload-time = "2026-02-20T20:47:34.036Z" }, - { url = "https://files.pythonhosted.org/packages/b3/de/dd7589b3f2b8372069ab3e4763ea5329940fc7ad9dcd3e272a37516d7c9b/greenlet-3.3.2-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2e47408e8ce1c6f1ceea0dffcdf6ebb85cc09e55c7af407c99f1112016e45e9", size = 662163, upload-time = "2026-02-20T20:56:01.295Z" }, - { url = "https://files.pythonhosted.org/packages/d2/d8/09bfa816572a4d83bccd6750df1926f79158b1c36c5f73786e26dbe4ee38/greenlet-3.3.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63d10328839d1973e5ba35e98cccbca71b232b14051fd957b6f8b6e8e80d0506", size = 664160, upload-time = "2026-02-20T20:21:04.015Z" }, - { url = "https://files.pythonhosted.org/packages/48/cf/56832f0c8255d27f6c35d41b5ec91168d74ec721d85f01a12131eec6b93c/greenlet-3.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e4ab3cfb02993c8cc248ea73d7dae6cec0253e9afa311c9b37e603ca9fad2ce", size = 1619181, upload-time = "2026-02-20T20:49:36.052Z" }, - { url = "https://files.pythonhosted.org/packages/0a/23/b90b60a4aabb4cec0796e55f25ffbfb579a907c3898cd2905c8918acaa16/greenlet-3.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94ad81f0fd3c0c0681a018a976e5c2bd2ca2d9d94895f23e7bb1af4e8af4e2d5", size = 1687713, upload-time = "2026-02-20T20:21:11.684Z" }, - { url = "https://files.pythonhosted.org/packages/f3/ca/2101ca3d9223a1dc125140dbc063644dca76df6ff356531eb27bc267b446/greenlet-3.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:8c4dd0f3997cf2512f7601563cc90dfb8957c0cff1e3a1b23991d4ea1776c492", size = 232034, upload-time = "2026-02-20T20:20:08.186Z" }, - { url = "https://files.pythonhosted.org/packages/f6/4a/ecf894e962a59dea60f04877eea0fd5724618da89f1867b28ee8b91e811f/greenlet-3.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:cd6f9e2bbd46321ba3bbb4c8a15794d32960e3b0ae2cc4d49a1a53d314805d71", size = 231437, upload-time = "2026-02-20T20:18:59.722Z" }, - { url = "https://files.pythonhosted.org/packages/98/6d/8f2ef704e614bcf58ed43cfb8d87afa1c285e98194ab2cfad351bf04f81e/greenlet-3.3.2-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:e26e72bec7ab387ac80caa7496e0f908ff954f31065b0ffc1f8ecb1338b11b54", size = 286617, upload-time = "2026-02-20T20:19:29.856Z" }, - { url = "https://files.pythonhosted.org/packages/5e/0d/93894161d307c6ea237a43988f27eba0947b360b99ac5239ad3fe09f0b47/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b466dff7a4ffda6ca975979bab80bdadde979e29fc947ac3be4451428d8b0e4", size = 655189, upload-time = "2026-02-20T20:47:35.742Z" }, - { url = "https://files.pythonhosted.org/packages/f5/2c/d2d506ebd8abcb57386ec4f7ba20f4030cbe56eae541bc6fd6ef399c0b41/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b8bddc5b73c9720bea487b3bffdb1840fe4e3656fba3bd40aa1489e9f37877ff", size = 658225, upload-time = "2026-02-20T20:56:02.527Z" }, - { url = "https://files.pythonhosted.org/packages/8e/30/3a09155fbf728673a1dea713572d2d31159f824a37c22da82127056c44e4/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b26b0f4428b871a751968285a1ac9648944cea09807177ac639b030bddebcea4", size = 657907, upload-time = "2026-02-20T20:21:05.259Z" }, - { url = "https://files.pythonhosted.org/packages/f3/fd/d05a4b7acd0154ed758797f0a43b4c0962a843bedfe980115e842c5b2d08/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1fb39a11ee2e4d94be9a76671482be9398560955c9e568550de0224e41104727", size = 1618857, upload-time = "2026-02-20T20:49:37.309Z" }, - { url = "https://files.pythonhosted.org/packages/6f/e1/50ee92a5db521de8f35075b5eff060dd43d39ebd46c2181a2042f7070385/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:20154044d9085151bc309e7689d6f7ba10027f8f5a8c0676ad398b951913d89e", size = 1680010, upload-time = "2026-02-20T20:21:13.427Z" }, - { url = "https://files.pythonhosted.org/packages/29/4b/45d90626aef8e65336bed690106d1382f7a43665e2249017e9527df8823b/greenlet-3.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c04c5e06ec3e022cbfe2cd4a846e1d4e50087444f875ff6d2c2ad8445495cf1a", size = 237086, upload-time = "2026-02-20T20:20:45.786Z" }, +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/86/94/a5935717b307d7c71fe877b52b884c6af707d2d2090db118a03fbd799369/greenlet-3.4.0.tar.gz", hash = "sha256:f50a96b64dafd6169e595a5c56c9146ef80333e67d4476a65a9c55f400fc22ff", size = 195913, upload-time = "2026-04-08T17:08:00.863Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/c6/dba32cab7e3a625b011aa5647486e2d28423a48845a2998c126dd69c85e1/greenlet-3.4.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:805bebb4945094acbab757d34d6e1098be6de8966009ab9ca54f06ff492def58", size = 285504, upload-time = "2026-04-08T15:52:14.071Z" }, + { url = "https://files.pythonhosted.org/packages/54/f4/7cb5c2b1feb9a1f50e038be79980dfa969aa91979e5e3a18fdbcfad2c517/greenlet-3.4.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:439fc2f12b9b512d9dfa681c5afe5f6b3232c708d13e6f02c845e0d9f4c2d8c6", size = 605476, upload-time = "2026-04-08T16:24:37.064Z" }, + { url = "https://files.pythonhosted.org/packages/d6/af/b66ab0b2f9a4c5a867c136bf66d9599f34f21a1bcca26a2884a29c450bd9/greenlet-3.4.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a70ed1cb0295bee1df57b63bf7f46b4e56a5c93709eea769c1fec1bb23a95875", size = 618336, upload-time = "2026-04-08T16:30:56.59Z" }, + { url = "https://files.pythonhosted.org/packages/e5/5c/8c5633ece6ba611d64bf2770219a98dd439921d6424e4e8cf16b0ac74ea5/greenlet-3.4.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c660bce1940a1acae5f51f0a064f1bc785d07ea16efcb4bc708090afc4d69e83", size = 613515, upload-time = "2026-04-08T15:56:32.478Z" }, + { url = "https://files.pythonhosted.org/packages/a9/df/950d15bca0d90a0e7395eb777903060504cdb509b7b705631e8fb69ff415/greenlet-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ee407d4d1ca9dc632265aee1c8732c4a2d60adff848057cdebfe5fe94eb2c8a2", size = 1574623, upload-time = "2026-04-08T16:26:18.596Z" }, + { url = "https://files.pythonhosted.org/packages/1a/e7/0839afab829fcb7333c9ff6d80c040949510055d2d4d63251f0d1c7c804e/greenlet-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:956215d5e355fffa7c021d168728321fd4d31fd730ac609b1653b450f6a4bc71", size = 1639579, upload-time = "2026-04-08T15:57:29.231Z" }, + { url = "https://files.pythonhosted.org/packages/d9/2b/b4482401e9bcaf9f5c97f67ead38db89c19520ff6d0d6699979c6efcc200/greenlet-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:5cb614ace7c27571270354e9c9f696554d073f8aa9319079dcba466bbdead711", size = 238233, upload-time = "2026-04-08T17:02:54.286Z" }, + { url = "https://files.pythonhosted.org/packages/0c/4d/d8123a4e0bcd583d5cfc8ddae0bbe29c67aab96711be331a7cc935a35966/greenlet-3.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:04403ac74fe295a361f650818de93be11b5038a78f49ccfb64d3b1be8fbf1267", size = 235045, upload-time = "2026-04-08T17:04:05.072Z" }, + { url = "https://files.pythonhosted.org/packages/65/8b/3669ad3b3f247a791b2b4aceb3aa5a31f5f6817bf547e4e1ff712338145a/greenlet-3.4.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:1a54a921561dd9518d31d2d3db4d7f80e589083063ab4d3e2e950756ef809e1a", size = 286902, upload-time = "2026-04-08T15:52:12.138Z" }, + { url = "https://files.pythonhosted.org/packages/38/3e/3c0e19b82900873e2d8469b590a6c4b3dfd2b316d0591f1c26b38a4879a5/greenlet-3.4.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16dec271460a9a2b154e3b1c2fa1050ce6280878430320e85e08c166772e3f97", size = 606099, upload-time = "2026-04-08T16:24:38.408Z" }, + { url = "https://files.pythonhosted.org/packages/b5/33/99fef65e7754fc76a4ed14794074c38c9ed3394a5bd129d7f61b705f3168/greenlet-3.4.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:90036ce224ed6fe75508c1907a77e4540176dcf0744473627785dd519c6f9996", size = 618837, upload-time = "2026-04-08T16:30:58.298Z" }, + { url = "https://files.pythonhosted.org/packages/36/f7/229f3aed6948faa20e0616a0b8568da22e365ede6a54d7d369058b128afd/greenlet-3.4.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a1c4f6b453006efb8310affb2d132832e9bbb4fc01ce6df6b70d810d38f1f6dc", size = 615062, upload-time = "2026-04-08T15:56:33.766Z" }, + { url = "https://files.pythonhosted.org/packages/08/97/d988180011aa40135c46cd0d0cf01dd97f7162bae14139b4a3ef54889ba5/greenlet-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9b2d9a138ffa0e306d0e2b72976d2fb10b97e690d40ab36a472acaab0838e2de", size = 1573511, upload-time = "2026-04-08T16:26:20.058Z" }, + { url = "https://files.pythonhosted.org/packages/d4/0f/a5a26fe152fb3d12e6a474181f6e9848283504d0afd095f353d85726374b/greenlet-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8424683caf46eb0eb6f626cb95e008e8cc30d0cb675bdfa48200925c79b38a08", size = 1640396, upload-time = "2026-04-08T15:57:30.88Z" }, + { url = "https://files.pythonhosted.org/packages/42/cf/bb2c32d9a100e36ee9f6e38fad6b1e082b8184010cb06259b49e1266ca01/greenlet-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0a53fb071531d003b075c444014ff8f8b1a9898d36bb88abd9ac7b3524648a2", size = 238892, upload-time = "2026-04-08T17:03:10.094Z" }, + { url = "https://files.pythonhosted.org/packages/b7/47/6c41314bac56e71436ce551c7fbe3cc830ed857e6aa9708dbb9c65142eb6/greenlet-3.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:f38b81880ba28f232f1f675893a39cf7b6db25b31cc0a09bb50787ecf957e85e", size = 235599, upload-time = "2026-04-08T15:52:54.3Z" }, + { url = "https://files.pythonhosted.org/packages/7a/75/7e9cd1126a1e1f0cd67b0eda02e5221b28488d352684704a78ed505bd719/greenlet-3.4.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:43748988b097f9c6f09364f260741aa73c80747f63389824435c7a50bfdfd5c1", size = 285856, upload-time = "2026-04-08T15:52:45.82Z" }, + { url = "https://files.pythonhosted.org/packages/9d/c4/3e2df392e5cb199527c4d9dbcaa75c14edcc394b45040f0189f649631e3c/greenlet-3.4.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5566e4e2cd7a880e8c27618e3eab20f3494452d12fd5129edef7b2f7aa9a36d1", size = 610208, upload-time = "2026-04-08T16:24:39.674Z" }, + { url = "https://files.pythonhosted.org/packages/da/af/750cdfda1d1bd30a6c28080245be8d0346e669a98fdbae7f4102aa95fff3/greenlet-3.4.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1054c5a3c78e2ab599d452f23f7adafef55062a783a8e241d24f3b633ba6ff82", size = 621269, upload-time = "2026-04-08T16:30:59.767Z" }, + { url = "https://files.pythonhosted.org/packages/54/78/0cbc693622cd54ebe25207efbb3a0eb07c2639cb8594f6e3aaaa0bb077a8/greenlet-3.4.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f82cb6cddc27dd81c96b1506f4aa7def15070c3b2a67d4e46fd19016aacce6cf", size = 617549, upload-time = "2026-04-08T15:56:34.893Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c0/8966767de01343c1ff47e8b855dc78e7d1a8ed2b7b9c83576a57e289f81d/greenlet-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:227a46251ecba4ff46ae742bc5ce95c91d5aceb4b02f885487aff269c127a729", size = 1575310, upload-time = "2026-04-08T16:26:21.671Z" }, + { url = "https://files.pythonhosted.org/packages/b8/38/bcdc71ba05e9a5fda87f63ffc2abcd1f15693b659346df994a48c968003d/greenlet-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5b99e87be7eba788dd5b75ba1cde5639edffdec5f91fe0d734a249535ec3408c", size = 1640435, upload-time = "2026-04-08T15:57:32.572Z" }, + { url = "https://files.pythonhosted.org/packages/a1/c2/19b664b7173b9e4ef5f77e8cef9f14c20ec7fce7920dc1ccd7afd955d093/greenlet-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:849f8bc17acd6295fcb5de8e46d55cc0e52381c56eaf50a2afd258e97bc65940", size = 238760, upload-time = "2026-04-08T17:04:03.878Z" }, + { url = "https://files.pythonhosted.org/packages/9b/96/795619651d39c7fbd809a522f881aa6f0ead504cc8201c3a5b789dfaef99/greenlet-3.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:9390ad88b652b1903814eaabd629ca184db15e0eeb6fe8a390bbf8b9106ae15a", size = 235498, upload-time = "2026-04-08T17:05:00.584Z" }, + { url = "https://files.pythonhosted.org/packages/78/02/bde66806e8f169cf90b14d02c500c44cdbe02c8e224c9c67bafd1b8cadd1/greenlet-3.4.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:10a07aca6babdd18c16a3f4f8880acfffc2b88dfe431ad6aa5f5740759d7d75e", size = 286291, upload-time = "2026-04-08T17:09:34.307Z" }, + { url = "https://files.pythonhosted.org/packages/05/1f/39da1c336a87d47c58352fb8a78541ce63d63ae57c5b9dae1fe02801bbc2/greenlet-3.4.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:076e21040b3a917d3ce4ad68fb5c3c6b32f1405616c4a57aa83120979649bd3d", size = 656749, upload-time = "2026-04-08T16:24:41.721Z" }, + { url = "https://files.pythonhosted.org/packages/d3/6c/90ee29a4ee27af7aa2e2ec408799eeb69ee3fcc5abcecac6ddd07a5cd0f2/greenlet-3.4.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e82689eea4a237e530bb5cb41b180ef81fa2160e1f89422a67be7d90da67f615", size = 669084, upload-time = "2026-04-08T16:31:01.372Z" }, + { url = "https://files.pythonhosted.org/packages/07/49/d4cad6e5381a50947bb973d2f6cf6592621451b09368b8c20d9b8af49c5b/greenlet-3.4.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4df3b0b2289ec686d3c821a5fee44259c05cfe824dd5e6e12c8e5f5df23085cf", size = 665621, upload-time = "2026-04-08T15:56:35.995Z" }, + { url = "https://files.pythonhosted.org/packages/37/31/d1edd54f424761b5d47718822f506b435b6aab2f3f93b465441143ea5119/greenlet-3.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8bff29d586ea415688f4cec96a591fcc3bf762d046a796cdadc1fdb6e7f2d5bf", size = 1622259, upload-time = "2026-04-08T16:26:23.201Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c6/6d3f9cdcb21c4e12a79cb332579f1c6aa1af78eb68059c5a957c7812d95e/greenlet-3.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8a569c2fb840c53c13a2b8967c63621fafbd1a0e015b9c82f408c33d626a2fda", size = 1686916, upload-time = "2026-04-08T15:57:34.282Z" }, + { url = "https://files.pythonhosted.org/packages/63/45/c1ca4a1ad975de4727e52d3ffe641ae23e1d7a8ffaa8ff7a0477e1827b92/greenlet-3.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:207ba5b97ea8b0b60eb43ffcacf26969dd83726095161d676aac03ff913ee50d", size = 239821, upload-time = "2026-04-08T17:03:48.423Z" }, + { url = "https://files.pythonhosted.org/packages/71/c4/6f621023364d7e85a4769c014c8982f98053246d142420e0328980933ceb/greenlet-3.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:f8296d4e2b92af34ebde81085a01690f26a51eb9ac09a0fcadb331eb36dbc802", size = 236932, upload-time = "2026-04-08T17:04:33.551Z" }, + { url = "https://files.pythonhosted.org/packages/d4/8f/18d72b629783f5e8d045a76f5325c1e938e659a9e4da79c7dcd10169a48d/greenlet-3.4.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:d70012e51df2dbbccfaf63a40aaf9b40c8bed37c3e3a38751c926301ce538ece", size = 294681, upload-time = "2026-04-08T15:52:35.778Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ad/5fa86ec46769c4153820d58a04062285b3b9e10ba3d461ee257b68dcbf53/greenlet-3.4.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a58bec0751f43068cd40cff31bb3ca02ad6000b3a51ca81367af4eb5abc480c8", size = 658899, upload-time = "2026-04-08T16:24:43.32Z" }, + { url = "https://files.pythonhosted.org/packages/43/f0/4e8174ca0e87ae748c409f055a1ba161038c43cc0a5a6f1433a26ac2e5bf/greenlet-3.4.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05fa0803561028f4b2e3b490ee41216a842eaee11aed004cc343a996d9523aa2", size = 665284, upload-time = "2026-04-08T16:31:02.833Z" }, + { url = "https://files.pythonhosted.org/packages/19/da/991cf7cd33662e2df92a1274b7eb4d61769294d38a1bba8a45f31364845e/greenlet-3.4.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e60d38719cb80b3ab5e85f9f1aed4960acfde09868af6762ccb27b260d68f4ed", size = 661861, upload-time = "2026-04-08T15:56:37.269Z" }, + { url = "https://files.pythonhosted.org/packages/36/c5/6c2c708e14db3d9caea4b459d8464f58c32047451142fe2cfd90e7458f41/greenlet-3.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7f50c804733b43eded05ae694691c9aa68bca7d0a867d67d4a3f514742a2d53f", size = 1622182, upload-time = "2026-04-08T16:26:24.777Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4c/50c5fed19378e11a29fabab1f6be39ea95358f4a0a07e115a51ca93385d8/greenlet-3.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2d4f0635dc4aa638cda4b2f5a07ae9a2cff9280327b581a3fcb6f317b4fbc38a", size = 1685050, upload-time = "2026-04-08T15:57:36.453Z" }, + { url = "https://files.pythonhosted.org/packages/db/72/85ae954d734703ab48e622c59d4ce35d77ce840c265814af9c078cacc7aa/greenlet-3.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1a4a48f24681300c640f143ba7c404270e1ebbbcf34331d7104a4ff40f8ea705", size = 245554, upload-time = "2026-04-08T17:03:50.044Z" }, ] [[package]] @@ -1461,14 +1461,14 @@ wheels = [ [[package]] name = "jupyter-lsp" -version = "2.3.0" +version = "2.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jupyter-server" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/eb/5a/9066c9f8e94ee517133cd98dba393459a16cd48bba71a82f16a65415206c/jupyter_lsp-2.3.0.tar.gz", hash = "sha256:458aa59339dc868fb784d73364f17dbce8836e906cd75fd471a325cba02e0245", size = 54823, upload-time = "2025-08-27T17:47:34.671Z" } +sdist = { url = "https://files.pythonhosted.org/packages/36/ff/1e4a61f5170a9a1d978f3ac3872449de6c01fc71eaf89657824c878b1549/jupyter_lsp-2.3.1.tar.gz", hash = "sha256:fdf8a4aa7d85813976d6e29e95e6a2c8f752701f926f2715305249a3829805a6", size = 55677, upload-time = "2026-04-02T08:10:06.749Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl", hash = "sha256:e914a3cb2addf48b1c7710914771aaf1819d46b2e5a79b0f917b5478ec93f34f", size = 76687, upload-time = "2025-08-27T17:47:33.15Z" }, + { url = "https://files.pythonhosted.org/packages/23/e8/9d61dcbd1dce8ef418f06befd4ac084b4720429c26b0b1222bc218685eff/jupyter_lsp-2.3.1-py3-none-any.whl", hash = "sha256:71b954d834e85ff3096400554f2eefaf7fe37053036f9a782b0f7c5e42dadb81", size = 77513, upload-time = "2026-04-02T08:10:01.753Z" }, ] [[package]] @@ -1929,7 +1929,7 @@ wheels = [ [[package]] name = "nbconvert" -version = "7.17.0" +version = "7.17.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "beautifulsoup4" }, @@ -1947,9 +1947,9 @@ dependencies = [ { name = "pygments" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/38/47/81f886b699450d0569f7bc551df2b1673d18df7ff25cc0c21ca36ed8a5ff/nbconvert-7.17.0.tar.gz", hash = "sha256:1b2696f1b5be12309f6c7d707c24af604b87dfaf6d950794c7b07acab96dda78", size = 862855, upload-time = "2026-01-29T16:37:48.478Z" } +sdist = { url = "https://files.pythonhosted.org/packages/01/b1/708e53fe2e429c103c6e6e159106bcf0357ac41aa4c28772bd8402339051/nbconvert-7.17.1.tar.gz", hash = "sha256:34d0d0a7e73ce3cbab6c5aae8f4f468797280b01fd8bd2ca746da8569eddd7d2", size = 865311, upload-time = "2026-04-08T00:44:14.914Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl", hash = "sha256:4f99a63b337b9a23504347afdab24a11faa7d86b405e5c8f9881cd313336d518", size = 261510, upload-time = "2026-01-29T16:37:46.322Z" }, + { url = "https://files.pythonhosted.org/packages/67/f8/bb0a9d5f46819c821dc1f004aa2cc29b1d91453297dbf5ff20470f00f193/nbconvert-7.17.1-py3-none-any.whl", hash = "sha256:aa85c087b435e7bf1ffd03319f658e285f2b89eccab33bc1ba7025495ab3e7c8", size = 261927, upload-time = "2026-04-08T00:44:12.845Z" }, ] [[package]] @@ -2347,89 +2347,89 @@ wheels = [ [[package]] name = "pillow" -version = "12.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/46/5da1ec4a5171ee7bf1a0efa064aba70ba3d6e0788ce3f5acd1375d23c8c0/pillow-12.1.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e879bb6cd5c73848ef3b2b48b8af9ff08c5b71ecda8048b7dd22d8a33f60be32", size = 5304084, upload-time = "2026-02-11T04:20:27.501Z" }, - { url = "https://files.pythonhosted.org/packages/78/93/a29e9bc02d1cf557a834da780ceccd54e02421627200696fcf805ebdc3fb/pillow-12.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:365b10bb9417dd4498c0e3b128018c4a624dc11c7b97d8cc54effe3b096f4c38", size = 4657866, upload-time = "2026-02-11T04:20:29.827Z" }, - { url = "https://files.pythonhosted.org/packages/13/84/583a4558d492a179d31e4aae32eadce94b9acf49c0337c4ce0b70e0a01f2/pillow-12.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d4ce8e329c93845720cd2014659ca67eac35f6433fd3050393d85f3ecef0dad5", size = 6232148, upload-time = "2026-02-11T04:20:31.329Z" }, - { url = "https://files.pythonhosted.org/packages/d5/e2/53c43334bbbb2d3b938978532fbda8e62bb6e0b23a26ce8592f36bcc4987/pillow-12.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc354a04072b765eccf2204f588a7a532c9511e8b9c7f900e1b64e3e33487090", size = 8038007, upload-time = "2026-02-11T04:20:34.225Z" }, - { url = "https://files.pythonhosted.org/packages/b8/a6/3d0e79c8a9d58150dd98e199d7c1c56861027f3829a3a60b3c2784190180/pillow-12.1.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e7976bf1910a8116b523b9f9f58bf410f3e8aa330cd9a2bb2953f9266ab49af", size = 6345418, upload-time = "2026-02-11T04:20:35.858Z" }, - { url = "https://files.pythonhosted.org/packages/a2/c8/46dfeac5825e600579157eea177be43e2f7ff4a99da9d0d0a49533509ac5/pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:597bd9c8419bc7c6af5604e55847789b69123bbe25d65cc6ad3012b4f3c98d8b", size = 7034590, upload-time = "2026-02-11T04:20:37.91Z" }, - { url = "https://files.pythonhosted.org/packages/af/bf/e6f65d3db8a8bbfeaf9e13cc0417813f6319863a73de934f14b2229ada18/pillow-12.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2c1fc0f2ca5f96a3c8407e41cca26a16e46b21060fe6d5b099d2cb01412222f5", size = 6458655, upload-time = "2026-02-11T04:20:39.496Z" }, - { url = "https://files.pythonhosted.org/packages/f9/c2/66091f3f34a25894ca129362e510b956ef26f8fb67a0e6417bc5744e56f1/pillow-12.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:578510d88c6229d735855e1f278aa305270438d36a05031dfaae5067cc8eb04d", size = 7159286, upload-time = "2026-02-11T04:20:41.139Z" }, - { url = "https://files.pythonhosted.org/packages/7b/5a/24bc8eb526a22f957d0cec6243146744966d40857e3d8deb68f7902ca6c1/pillow-12.1.1-cp311-cp311-win32.whl", hash = "sha256:7311c0a0dcadb89b36b7025dfd8326ecfa36964e29913074d47382706e516a7c", size = 6328663, upload-time = "2026-02-11T04:20:43.184Z" }, - { url = "https://files.pythonhosted.org/packages/31/03/bef822e4f2d8f9d7448c133d0a18185d3cce3e70472774fffefe8b0ed562/pillow-12.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:fbfa2a7c10cc2623f412753cddf391c7f971c52ca40a3f65dc5039b2939e8563", size = 7031448, upload-time = "2026-02-11T04:20:44.696Z" }, - { url = "https://files.pythonhosted.org/packages/49/70/f76296f53610bd17b2e7d31728b8b7825e3ac3b5b3688b51f52eab7c0818/pillow-12.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:b81b5e3511211631b3f672a595e3221252c90af017e399056d0faabb9538aa80", size = 2453651, upload-time = "2026-02-11T04:20:46.243Z" }, - { url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803, upload-time = "2026-02-11T04:20:47.653Z" }, - { url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601, upload-time = "2026-02-11T04:20:49.328Z" }, - { url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995, upload-time = "2026-02-11T04:20:51.032Z" }, - { url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012, upload-time = "2026-02-11T04:20:52.882Z" }, - { url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638, upload-time = "2026-02-11T04:20:54.444Z" }, - { url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540, upload-time = "2026-02-11T04:20:55.97Z" }, - { url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613, upload-time = "2026-02-11T04:20:57.542Z" }, - { url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745, upload-time = "2026-02-11T04:20:59.196Z" }, - { url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823, upload-time = "2026-02-11T04:21:01.385Z" }, - { url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367, upload-time = "2026-02-11T04:21:03.536Z" }, - { url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811, upload-time = "2026-02-11T04:21:05.116Z" }, - { url = "https://files.pythonhosted.org/packages/d5/11/6db24d4bd7685583caeae54b7009584e38da3c3d4488ed4cd25b439de486/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e", size = 4062689, upload-time = "2026-02-11T04:21:06.804Z" }, - { url = "https://files.pythonhosted.org/packages/33/c0/ce6d3b1fe190f0021203e0d9b5b99e57843e345f15f9ef22fcd43842fd21/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", size = 4138535, upload-time = "2026-02-11T04:21:08.452Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c6/d5eb6a4fb32a3f9c21a8c7613ec706534ea1cf9f4b3663e99f0d83f6fca8/pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6", size = 3601364, upload-time = "2026-02-11T04:21:10.194Z" }, - { url = "https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60", size = 5262561, upload-time = "2026-02-11T04:21:11.742Z" }, - { url = "https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2", size = 4657460, upload-time = "2026-02-11T04:21:13.786Z" }, - { url = "https://files.pythonhosted.org/packages/9e/1b/f1a4ea9a895b5732152789326202a82464d5254759fbacae4deea3069334/pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850", size = 6232698, upload-time = "2026-02-11T04:21:15.949Z" }, - { url = "https://files.pythonhosted.org/packages/95/f4/86f51b8745070daf21fd2e5b1fe0eb35d4db9ca26e6d58366562fb56a743/pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289", size = 8041706, upload-time = "2026-02-11T04:21:17.723Z" }, - { url = "https://files.pythonhosted.org/packages/29/9b/d6ecd956bb1266dd1045e995cce9b8d77759e740953a1c9aad9502a0461e/pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e", size = 6346621, upload-time = "2026-02-11T04:21:19.547Z" }, - { url = "https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717", size = 7038069, upload-time = "2026-02-11T04:21:21.378Z" }, - { url = "https://files.pythonhosted.org/packages/94/0e/58cb1a6bc48f746bc4cb3adb8cabff73e2742c92b3bf7a220b7cf69b9177/pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a", size = 6460040, upload-time = "2026-02-11T04:21:23.148Z" }, - { url = "https://files.pythonhosted.org/packages/6c/57/9045cb3ff11eeb6c1adce3b2d60d7d299d7b273a2e6c8381a524abfdc474/pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029", size = 7164523, upload-time = "2026-02-11T04:21:25.01Z" }, - { url = "https://files.pythonhosted.org/packages/73/f2/9be9cb99f2175f0d4dbadd6616ce1bf068ee54a28277ea1bf1fbf729c250/pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b", size = 6332552, upload-time = "2026-02-11T04:21:27.238Z" }, - { url = "https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1", size = 7040108, upload-time = "2026-02-11T04:21:29.462Z" }, - { url = "https://files.pythonhosted.org/packages/d5/7d/fc09634e2aabdd0feabaff4a32f4a7d97789223e7c2042fd805ea4b4d2c2/pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a", size = 2453712, upload-time = "2026-02-11T04:21:31.072Z" }, - { url = "https://files.pythonhosted.org/packages/19/2a/b9d62794fc8a0dd14c1943df68347badbd5511103e0d04c035ffe5cf2255/pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da", size = 5264880, upload-time = "2026-02-11T04:21:32.865Z" }, - { url = "https://files.pythonhosted.org/packages/26/9d/e03d857d1347fa5ed9247e123fcd2a97b6220e15e9cb73ca0a8d91702c6e/pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc", size = 4660616, upload-time = "2026-02-11T04:21:34.97Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ec/8a6d22afd02570d30954e043f09c32772bfe143ba9285e2fdb11284952cd/pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c", size = 6269008, upload-time = "2026-02-11T04:21:36.623Z" }, - { url = "https://files.pythonhosted.org/packages/3d/1d/6d875422c9f28a4a361f495a5f68d9de4a66941dc2c619103ca335fa6446/pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8", size = 8073226, upload-time = "2026-02-11T04:21:38.585Z" }, - { url = "https://files.pythonhosted.org/packages/a1/cd/134b0b6ee5eda6dc09e25e24b40fdafe11a520bc725c1d0bbaa5e00bf95b/pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20", size = 6380136, upload-time = "2026-02-11T04:21:40.562Z" }, - { url = "https://files.pythonhosted.org/packages/7a/a9/7628f013f18f001c1b98d8fffe3452f306a70dc6aba7d931019e0492f45e/pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13", size = 7067129, upload-time = "2026-02-11T04:21:42.521Z" }, - { url = "https://files.pythonhosted.org/packages/1e/f8/66ab30a2193b277785601e82ee2d49f68ea575d9637e5e234faaa98efa4c/pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf", size = 6491807, upload-time = "2026-02-11T04:21:44.22Z" }, - { url = "https://files.pythonhosted.org/packages/da/0b/a877a6627dc8318fdb84e357c5e1a758c0941ab1ddffdafd231983788579/pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524", size = 7190954, upload-time = "2026-02-11T04:21:46.114Z" }, - { url = "https://files.pythonhosted.org/packages/83/43/6f732ff85743cf746b1361b91665d9f5155e1483817f693f8d57ea93147f/pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986", size = 6336441, upload-time = "2026-02-11T04:21:48.22Z" }, - { url = "https://files.pythonhosted.org/packages/3b/44/e865ef3986611bb75bfabdf94a590016ea327833f434558801122979cd0e/pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c", size = 7045383, upload-time = "2026-02-11T04:21:50.015Z" }, - { url = "https://files.pythonhosted.org/packages/a8/c6/f4fb24268d0c6908b9f04143697ea18b0379490cb74ba9e8d41b898bd005/pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3", size = 2456104, upload-time = "2026-02-11T04:21:51.633Z" }, - { url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652, upload-time = "2026-02-11T04:21:53.19Z" }, - { url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823, upload-time = "2026-02-11T04:22:03.088Z" }, - { url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143, upload-time = "2026-02-11T04:22:04.909Z" }, - { url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254, upload-time = "2026-02-11T04:22:07.656Z" }, - { url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499, upload-time = "2026-02-11T04:22:09.613Z" }, - { url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137, upload-time = "2026-02-11T04:22:11.434Z" }, - { url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721, upload-time = "2026-02-11T04:22:13.321Z" }, - { url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798, upload-time = "2026-02-11T04:22:15.449Z" }, - { url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315, upload-time = "2026-02-11T04:22:17.24Z" }, - { url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360, upload-time = "2026-02-11T04:22:19.111Z" }, - { url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438, upload-time = "2026-02-11T04:22:21.041Z" }, - { url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503, upload-time = "2026-02-11T04:22:22.833Z" }, - { url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748, upload-time = "2026-02-11T04:22:24.64Z" }, - { url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314, upload-time = "2026-02-11T04:22:26.685Z" }, - { url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612, upload-time = "2026-02-11T04:22:29.884Z" }, - { url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567, upload-time = "2026-02-11T04:22:31.799Z" }, - { url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951, upload-time = "2026-02-11T04:22:33.921Z" }, - { url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769, upload-time = "2026-02-11T04:22:35.877Z" }, - { url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358, upload-time = "2026-02-11T04:22:37.698Z" }, - { url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558, upload-time = "2026-02-11T04:22:39.597Z" }, - { url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028, upload-time = "2026-02-11T04:22:42.73Z" }, - { url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940, upload-time = "2026-02-11T04:22:44.543Z" }, - { url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736, upload-time = "2026-02-11T04:22:46.347Z" }, - { url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894, upload-time = "2026-02-11T04:22:48.114Z" }, - { url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446, upload-time = "2026-02-11T04:22:50.342Z" }, - { url = "https://files.pythonhosted.org/packages/56/11/5d43209aa4cb58e0cc80127956ff1796a68b928e6324bbf06ef4db34367b/pillow-12.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:600fd103672b925fe62ed08e0d874ea34d692474df6f4bf7ebe148b30f89f39f", size = 5228606, upload-time = "2026-02-11T04:22:52.106Z" }, - { url = "https://files.pythonhosted.org/packages/5f/d5/3b005b4e4fda6698b371fa6c21b097d4707585d7db99e98d9b0b87ac612a/pillow-12.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:665e1b916b043cef294bc54d47bf02d87e13f769bc4bc5fa225a24b3a6c5aca9", size = 4622321, upload-time = "2026-02-11T04:22:53.827Z" }, - { url = "https://files.pythonhosted.org/packages/df/36/ed3ea2d594356fd8037e5a01f6156c74bc8d92dbb0fa60746cc96cabb6e8/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:495c302af3aad1ca67420ddd5c7bd480c8867ad173528767d906428057a11f0e", size = 5247579, upload-time = "2026-02-11T04:22:56.094Z" }, - { url = "https://files.pythonhosted.org/packages/54/9a/9cc3e029683cf6d20ae5085da0dafc63148e3252c2f13328e553aaa13cfb/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8fd420ef0c52c88b5a035a0886f367748c72147b2b8f384c9d12656678dfdfa9", size = 6989094, upload-time = "2026-02-11T04:22:58.288Z" }, - { url = "https://files.pythonhosted.org/packages/00/98/fc53ab36da80b88df0967896b6c4b4cd948a0dc5aa40a754266aa3ae48b3/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f975aa7ef9684ce7e2c18a3aa8f8e2106ce1e46b94ab713d156b2898811651d3", size = 5313850, upload-time = "2026-02-11T04:23:00.554Z" }, - { url = "https://files.pythonhosted.org/packages/30/02/00fa585abfd9fe9d73e5f6e554dc36cc2b842898cbfc46d70353dae227f8/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8089c852a56c2966cf18835db62d9b34fef7ba74c726ad943928d494fa7f4735", size = 5963343, upload-time = "2026-02-11T04:23:02.934Z" }, - { url = "https://files.pythonhosted.org/packages/f2/26/c56ce33ca856e358d27fda9676c055395abddb82c35ac0f593877ed4562e/pillow-12.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:cb9bb857b2d057c6dfc72ac5f3b44836924ba15721882ef103cecb40d002d80e", size = 7029880, upload-time = "2026-02-11T04:23:04.783Z" }, +version = "12.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/e1/748f5663efe6edcfc4e74b2b93edfb9b8b99b67f21a854c3ae416500a2d9/pillow-12.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:8be29e59487a79f173507c30ddf57e733a357f67881430449bb32614075a40ab", size = 5354347, upload-time = "2026-04-01T14:42:44.255Z" }, + { url = "https://files.pythonhosted.org/packages/47/a1/d5ff69e747374c33a3b53b9f98cca7889fce1fd03d79cdc4e1bccc6c5a87/pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65", size = 4695873, upload-time = "2026-04-01T14:42:46.452Z" }, + { url = "https://files.pythonhosted.org/packages/df/21/e3fbdf54408a973c7f7f89a23b2cb97a7ef30c61ab4142af31eee6aebc88/pillow-12.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f490f9368b6fc026f021db16d7ec2fbf7d89e2edb42e8ec09d2c60505f5729c7", size = 6280168, upload-time = "2026-04-01T14:42:49.228Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f1/00b7278c7dd52b17ad4329153748f87b6756ec195ff786c2bdf12518337d/pillow-12.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8bd7903a5f2a4545f6fd5935c90058b89d30045568985a71c79f5fd6edf9b91e", size = 8088188, upload-time = "2026-04-01T14:42:51.735Z" }, + { url = "https://files.pythonhosted.org/packages/ad/cf/220a5994ef1b10e70e85748b75649d77d506499352be135a4989c957b701/pillow-12.2.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3997232e10d2920a68d25191392e3a4487d8183039e1c74c2297f00ed1c50705", size = 6394401, upload-time = "2026-04-01T14:42:54.343Z" }, + { url = "https://files.pythonhosted.org/packages/e9/bd/e51a61b1054f09437acfbc2ff9106c30d1eb76bc1453d428399946781253/pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e74473c875d78b8e9d5da2a70f7099549f9eb37ded4e2f6a463e60125bccd176", size = 7079655, upload-time = "2026-04-01T14:42:56.954Z" }, + { url = "https://files.pythonhosted.org/packages/6b/3d/45132c57d5fb4b5744567c3817026480ac7fc3ce5d4c47902bc0e7f6f853/pillow-12.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:56a3f9c60a13133a98ecff6197af34d7824de9b7b38c3654861a725c970c197b", size = 6503105, upload-time = "2026-04-01T14:42:59.847Z" }, + { url = "https://files.pythonhosted.org/packages/7d/2e/9df2fc1e82097b1df3dce58dc43286aa01068e918c07574711fcc53e6fb4/pillow-12.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90e6f81de50ad6b534cab6e5aef77ff6e37722b2f5d908686f4a5c9eba17a909", size = 7203402, upload-time = "2026-04-01T14:43:02.664Z" }, + { url = "https://files.pythonhosted.org/packages/bd/2e/2941e42858ebb67e50ae741473de81c2984e6eff7b397017623c676e2e8d/pillow-12.2.0-cp311-cp311-win32.whl", hash = "sha256:8c984051042858021a54926eb597d6ee3012393ce9c181814115df4c60b9a808", size = 6378149, upload-time = "2026-04-01T14:43:05.274Z" }, + { url = "https://files.pythonhosted.org/packages/69/42/836b6f3cd7f3e5fa10a1f1a5420447c17966044c8fbf589cc0452d5502db/pillow-12.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60", size = 7082626, upload-time = "2026-04-01T14:43:08.557Z" }, + { url = "https://files.pythonhosted.org/packages/c2/88/549194b5d6f1f494b485e493edc6693c0a16f4ada488e5bd974ed1f42fad/pillow-12.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:9a8a34cc89c67a65ea7437ce257cea81a9dad65b29805f3ecee8c8fe8ff25ffe", size = 2463531, upload-time = "2026-04-01T14:43:10.743Z" }, + { url = "https://files.pythonhosted.org/packages/58/be/7482c8a5ebebbc6470b3eb791812fff7d5e0216c2be3827b30b8bb6603ed/pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5", size = 5308279, upload-time = "2026-04-01T14:43:13.246Z" }, + { url = "https://files.pythonhosted.org/packages/d8/95/0a351b9289c2b5cbde0bacd4a83ebc44023e835490a727b2a3bd60ddc0f4/pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421", size = 4695490, upload-time = "2026-04-01T14:43:15.584Z" }, + { url = "https://files.pythonhosted.org/packages/de/af/4e8e6869cbed569d43c416fad3dc4ecb944cb5d9492defaed89ddd6fe871/pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987", size = 6284462, upload-time = "2026-04-01T14:43:18.268Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9e/c05e19657fd57841e476be1ab46c4d501bffbadbafdc31a6d665f8b737b6/pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76", size = 8094744, upload-time = "2026-04-01T14:43:20.716Z" }, + { url = "https://files.pythonhosted.org/packages/2b/54/1789c455ed10176066b6e7e6da1b01e50e36f94ba584dc68d9eebfe9156d/pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005", size = 6398371, upload-time = "2026-04-01T14:43:23.443Z" }, + { url = "https://files.pythonhosted.org/packages/43/e3/fdc657359e919462369869f1c9f0e973f353f9a9ee295a39b1fea8ee1a77/pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780", size = 7087215, upload-time = "2026-04-01T14:43:26.758Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f8/2f6825e441d5b1959d2ca5adec984210f1ec086435b0ed5f52c19b3b8a6e/pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5", size = 6509783, upload-time = "2026-04-01T14:43:29.56Z" }, + { url = "https://files.pythonhosted.org/packages/67/f9/029a27095ad20f854f9dba026b3ea6428548316e057e6fc3545409e86651/pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5", size = 7212112, upload-time = "2026-04-01T14:43:32.091Z" }, + { url = "https://files.pythonhosted.org/packages/be/42/025cfe05d1be22dbfdb4f264fe9de1ccda83f66e4fc3aac94748e784af04/pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940", size = 6378489, upload-time = "2026-04-01T14:43:34.601Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7b/25a221d2c761c6a8ae21bfa3874988ff2583e19cf8a27bf2fee358df7942/pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5", size = 7084129, upload-time = "2026-04-01T14:43:37.213Z" }, + { url = "https://files.pythonhosted.org/packages/10/e1/542a474affab20fd4a0f1836cb234e8493519da6b76899e30bcc5d990b8b/pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414", size = 2463612, upload-time = "2026-04-01T14:43:39.421Z" }, + { url = "https://files.pythonhosted.org/packages/4a/01/53d10cf0dbad820a8db274d259a37ba50b88b24768ddccec07355382d5ad/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c", size = 4100837, upload-time = "2026-04-01T14:43:41.506Z" }, + { url = "https://files.pythonhosted.org/packages/0f/98/f3a6657ecb698c937f6c76ee564882945f29b79bad496abcba0e84659ec5/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2", size = 4176528, upload-time = "2026-04-01T14:43:43.773Z" }, + { url = "https://files.pythonhosted.org/packages/69/bc/8986948f05e3ea490b8442ea1c1d4d990b24a7e43d8a51b2c7d8b1dced36/pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c", size = 3640401, upload-time = "2026-04-01T14:43:45.87Z" }, + { url = "https://files.pythonhosted.org/packages/34/46/6c717baadcd62bc8ed51d238d521ab651eaa74838291bda1f86fe1f864c9/pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795", size = 5308094, upload-time = "2026-04-01T14:43:48.438Z" }, + { url = "https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f", size = 4695402, upload-time = "2026-04-01T14:43:51.292Z" }, + { url = "https://files.pythonhosted.org/packages/73/dd/42107efcb777b16fa0393317eac58f5b5cf30e8392e266e76e51cff28c3d/pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed", size = 6280005, upload-time = "2026-04-01T14:43:54.242Z" }, + { url = "https://files.pythonhosted.org/packages/a8/68/b93e09e5e8549019e61acf49f65b1a8530765a7f812c77a7461bca7e4494/pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9", size = 8090669, upload-time = "2026-04-01T14:43:57.335Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6e/3ccb54ce8ec4ddd1accd2d89004308b7b0b21c4ac3d20fa70af4760a4330/pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed", size = 6395194, upload-time = "2026-04-01T14:43:59.864Z" }, + { url = "https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3", size = 7082423, upload-time = "2026-04-01T14:44:02.74Z" }, + { url = "https://files.pythonhosted.org/packages/78/5f/e9f86ab0146464e8c133fe85df987ed9e77e08b29d8d35f9f9f4d6f917ba/pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9", size = 6505667, upload-time = "2026-04-01T14:44:05.381Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1e/409007f56a2fdce61584fd3acbc2bbc259857d555196cedcadc68c015c82/pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795", size = 7208580, upload-time = "2026-04-01T14:44:08.39Z" }, + { url = "https://files.pythonhosted.org/packages/23/c4/7349421080b12fb35414607b8871e9534546c128a11965fd4a7002ccfbee/pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e", size = 6375896, upload-time = "2026-04-01T14:44:11.197Z" }, + { url = "https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b", size = 7081266, upload-time = "2026-04-01T14:44:13.947Z" }, + { url = "https://files.pythonhosted.org/packages/c3/25/f968f618a062574294592f668218f8af564830ccebdd1fa6200f598e65c5/pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06", size = 2463508, upload-time = "2026-04-01T14:44:16.312Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a4/b342930964e3cb4dce5038ae34b0eab4653334995336cd486c5a8c25a00c/pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b", size = 5309927, upload-time = "2026-04-01T14:44:18.89Z" }, + { url = "https://files.pythonhosted.org/packages/9f/de/23198e0a65a9cf06123f5435a5d95cea62a635697f8f03d134d3f3a96151/pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f", size = 4698624, upload-time = "2026-04-01T14:44:21.115Z" }, + { url = "https://files.pythonhosted.org/packages/01/a6/1265e977f17d93ea37aa28aa81bad4fa597933879fac2520d24e021c8da3/pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612", size = 6321252, upload-time = "2026-04-01T14:44:23.663Z" }, + { url = "https://files.pythonhosted.org/packages/3c/83/5982eb4a285967baa70340320be9f88e57665a387e3a53a7f0db8231a0cd/pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c", size = 8126550, upload-time = "2026-04-01T14:44:26.772Z" }, + { url = "https://files.pythonhosted.org/packages/4e/48/6ffc514adce69f6050d0753b1a18fd920fce8cac87620d5a31231b04bfc5/pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea", size = 6433114, upload-time = "2026-04-01T14:44:29.615Z" }, + { url = "https://files.pythonhosted.org/packages/36/a3/f9a77144231fb8d40ee27107b4463e205fa4677e2ca2548e14da5cf18dce/pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4", size = 7115667, upload-time = "2026-04-01T14:44:32.773Z" }, + { url = "https://files.pythonhosted.org/packages/c1/fc/ac4ee3041e7d5a565e1c4fd72a113f03b6394cc72ab7089d27608f8aaccb/pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4", size = 6538966, upload-time = "2026-04-01T14:44:35.252Z" }, + { url = "https://files.pythonhosted.org/packages/c0/a8/27fb307055087f3668f6d0a8ccb636e7431d56ed0750e07a60547b1e083e/pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea", size = 7238241, upload-time = "2026-04-01T14:44:37.875Z" }, + { url = "https://files.pythonhosted.org/packages/ad/4b/926ab182c07fccae9fcb120043464e1ff1564775ec8864f21a0ebce6ac25/pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24", size = 6379592, upload-time = "2026-04-01T14:44:40.336Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c4/f9e476451a098181b30050cc4c9a3556b64c02cf6497ea421ac047e89e4b/pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98", size = 7085542, upload-time = "2026-04-01T14:44:43.251Z" }, + { url = "https://files.pythonhosted.org/packages/00/a4/285f12aeacbe2d6dc36c407dfbbe9e96d4a80b0fb710a337f6d2ad978c75/pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453", size = 2465765, upload-time = "2026-04-01T14:44:45.996Z" }, + { url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" }, + { url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" }, + { url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" }, + { url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" }, + { url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" }, + { url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" }, + { url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" }, + { url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" }, + { url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" }, + { url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" }, + { url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" }, + { url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" }, + { url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" }, + { url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" }, + { url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" }, + { url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" }, + { url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" }, + { url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" }, + { url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" }, + { url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b7/2437044fb910f499610356d1352e3423753c98e34f915252aafecc64889f/pillow-12.2.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538bd5e05efec03ae613fd89c4ce0368ecd2ba239cc25b9f9be7ed426b0af1f", size = 5273969, upload-time = "2026-04-01T14:45:55.538Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f4/8316e31de11b780f4ac08ef3654a75555e624a98db1056ecb2122d008d5a/pillow-12.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:394167b21da716608eac917c60aa9b969421b5dcbbe02ae7f013e7b85811c69d", size = 4659674, upload-time = "2026-04-01T14:45:58.093Z" }, + { url = "https://files.pythonhosted.org/packages/d4/37/664fca7201f8bb2aa1d20e2c3d5564a62e6ae5111741966c8319ca802361/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5d04bfa02cc2d23b497d1e90a0f927070043f6cbf303e738300532379a4b4e0f", size = 5288479, upload-time = "2026-04-01T14:46:01.141Z" }, + { url = "https://files.pythonhosted.org/packages/49/62/5b0ed78fce87346be7a5cfcfaaad91f6a1f98c26f86bdbafa2066c647ef6/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0c838a5125cee37e68edec915651521191cef1e6aa336b855f495766e77a366e", size = 7032230, upload-time = "2026-04-01T14:46:03.874Z" }, + { url = "https://files.pythonhosted.org/packages/c3/28/ec0fc38107fc32536908034e990c47914c57cd7c5a3ece4d8d8f7ffd7e27/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a6c9fa44005fa37a91ebfc95d081e8079757d2e904b27103f4f5fa6f0bf78c0", size = 5355404, upload-time = "2026-04-01T14:46:06.33Z" }, + { url = "https://files.pythonhosted.org/packages/5e/8b/51b0eddcfa2180d60e41f06bd6d0a62202b20b59c68f5a132e615b75aecf/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25373b66e0dd5905ed63fa3cae13c82fbddf3079f2c8bf15c6fb6a35586324c1", size = 6002215, upload-time = "2026-04-01T14:46:08.83Z" }, + { url = "https://files.pythonhosted.org/packages/bc/60/5382c03e1970de634027cee8e1b7d39776b778b81812aaf45b694dfe9e28/pillow-12.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e", size = 7080946, upload-time = "2026-04-01T14:46:11.734Z" }, ] [[package]] @@ -2449,11 +2449,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.9.4" +version = "4.9.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/56/8d4c30c8a1d07013911a8fdbd8f89440ef9f08d07a1b50ab8ca8be5a20f9/platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934", size = 28737, upload-time = "2026-03-05T18:34:13.271Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400, upload-time = "2026-04-09T00:04:10.812Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868", size = 21216, upload-time = "2026-03-05T18:34:12.172Z" }, + { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348, upload-time = "2026-04-09T00:04:09.463Z" }, ] [[package]] @@ -2483,11 +2483,11 @@ wheels = [ [[package]] name = "prometheus-client" -version = "0.24.1" +version = "0.25.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f0/58/a794d23feb6b00fc0c72787d7e87d872a6730dd9ed7c7b3e954637d8f280/prometheus_client-0.24.1.tar.gz", hash = "sha256:7e0ced7fbbd40f7b84962d5d2ab6f17ef88a72504dcf7c0b40737b43b2a461f9", size = 85616, upload-time = "2026-01-14T15:26:26.965Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/fb/d9aa83ffe43ce1f19e557c0971d04b90561b0cfd50762aafb01968285553/prometheus_client-0.25.0.tar.gz", hash = "sha256:5e373b75c31afb3c86f1a52fa1ad470c9aace18082d39ec0d2f918d11cc9ba28", size = 86035, upload-time = "2026-04-09T19:53:42.359Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.1-py3-none-any.whl", hash = "sha256:150db128af71a5c2482b36e588fc8a6b95e498750da4b17065947c16070f4055", size = 64057, upload-time = "2026-01-14T15:26:24.42Z" }, + { url = "https://files.pythonhosted.org/packages/8d/9b/d4b1e644385499c8346fa9b622a3f030dce14cd6ef8a1871c221a17a67e7/prometheus_client-0.25.0-py3-none-any.whl", hash = "sha256:d5aec89e349a6ec230805d0df882f3807f74fd6c1a2fa86864e3c2279059fed1", size = 64154, upload-time = "2026-04-09T19:53:41.324Z" }, ] [[package]] @@ -2621,7 +2621,7 @@ wheels = [ [[package]] name = "pytest" -version = "9.0.2" +version = "9.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -2630,9 +2630,9 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, ] [[package]] @@ -2683,15 +2683,15 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.2.1" +version = "1.2.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/88/815e53084c5079a59df912825a279f41dd2e0df82281770eadc732f5352c/python_discovery-1.2.1.tar.gz", hash = "sha256:180c4d114bff1c32462537eac5d6a332b768242b76b69c0259c7d14b1b680c9e", size = 58457, upload-time = "2026-03-26T22:30:44.496Z" } +sdist = { url = "https://files.pythonhosted.org/packages/de/ef/3bae0e537cfe91e8431efcba4434463d2c5a65f5a89edd47c6cf2f03c55f/python_discovery-1.2.2.tar.gz", hash = "sha256:876e9c57139eb757cb5878cbdd9ae5379e5d96266c99ef731119e04fffe533bb", size = 58872, upload-time = "2026-04-07T17:28:49.249Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl", hash = "sha256:b6a957b24c1cd79252484d3566d1b49527581d46e789aaf43181005e56201502", size = 31674, upload-time = "2026-03-26T22:30:43.396Z" }, + { url = "https://files.pythonhosted.org/packages/d8/db/795879cc3ddfe338599bddea6388cc5100b088db0a4caf6e6c1af1c27e04/python_discovery-1.2.2-py3-none-any.whl", hash = "sha256:e1ae95d9af875e78f15e19aed0c6137ab1bb49c200f21f5061786490c9585c7a", size = 31894, upload-time = "2026-04-07T17:28:48.09Z" }, ] [[package]] @@ -2947,7 +2947,7 @@ wheels = [ [[package]] name = "requests" -version = "2.33.0" +version = "2.33.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -2955,9 +2955,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/64/8860370b167a9721e8956ae116825caff829224fbca0ca6e7bf8ddef8430/requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652", size = 134232, upload-time = "2026-03-25T15:10:41.586Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b", size = 65017, upload-time = "2026-03-25T15:10:40.382Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" }, ] [[package]] @@ -2995,7 +2995,7 @@ wheels = [ [[package]] name = "rosettasciio" -version = "0.12.0" +version = "0.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "dask", extra = ["array"] }, @@ -3005,45 +3005,45 @@ dependencies = [ { name = "python-dateutil" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/2f/cbcd0a75fce3bf955fb3317dae037a60bcdbe848c98105afaa25c31d1475/rosettasciio-0.12.0.tar.gz", hash = "sha256:e02575d451e9c3d301fcb68c319ce0728175df9940feff5b09b855e18945a093", size = 1180824, upload-time = "2025-12-29T17:33:06.459Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/00/34/0b9aa833f2dfdb8f22ad6fb337b71b638f9a937abb2796f8112bc507195f/rosettasciio-0.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c529232347e251abd07dd8b9f92f618994cdc289aca687a1df21e2846936a3d3", size = 817077, upload-time = "2025-12-29T17:32:03.968Z" }, - { url = "https://files.pythonhosted.org/packages/15/2b/caac69f14e9ee9486901bca609fe66fb2d21ad144ccaa6e562f696115bc3/rosettasciio-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:33bc1382770b74a1b4c5c9605656cf2f3a63e0409903bce3620a1d5d8401d270", size = 816215, upload-time = "2025-12-29T17:32:05.776Z" }, - { url = "https://files.pythonhosted.org/packages/b0/9b/345d28ce5a802d82048de418a427b3239e4f9ff44553709eef7a3a2b5134/rosettasciio-0.12.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:931d5251c25f4db537573671723fddcb1938684ed3047425f2f35cc9c89bab2b", size = 1271765, upload-time = "2025-12-29T17:32:07.928Z" }, - { url = "https://files.pythonhosted.org/packages/c9/62/de7bd49e68760d4deda46d58f089e936e1a6bcd14dbd4e7acb4d9c7c1769/rosettasciio-0.12.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:754345908d083083d54aa24572290eb44632fa9ef0c6fca7d547a9e6754a2e88", size = 1276286, upload-time = "2025-12-29T17:32:09.831Z" }, - { url = "https://files.pythonhosted.org/packages/9b/5c/d1f62362790d601b0749089ab30f6193e0a328af523b0569f36361deeda2/rosettasciio-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a57699c95226d6f71714629d23e58045c0ee3378e0aa00e8fe5fb0da88aff2ba", size = 805836, upload-time = "2025-12-29T17:32:12.107Z" }, - { url = "https://files.pythonhosted.org/packages/3c/75/f6f81c945da3c3b8d67c14d4ca26dda0ac3421ad9074bc54196019cfe119/rosettasciio-0.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:e1203f9960a2026122d2900062db1ea48700aaec4fa427ed731cb7f8b0433740", size = 794372, upload-time = "2025-12-29T17:32:13.931Z" }, - { url = "https://files.pythonhosted.org/packages/8b/42/48eebee13ace6e0c714433111ef7854b023f63ced48ccb38c26c4a325e8b/rosettasciio-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:18608de5b37a197d15cf4236d045adfb398dbcd2bc0917a8a193e9a5c87dc871", size = 818054, upload-time = "2025-12-29T17:32:15.366Z" }, - { url = "https://files.pythonhosted.org/packages/90/d6/0971527544fc27f59856700c6220aee2199bd631c51c4eb05bf3edd1e58d/rosettasciio-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2567a490f4be1ce7402551f21ed32f5f34dc5956286fa819594bdc899999d32f", size = 816403, upload-time = "2025-12-29T17:32:16.85Z" }, - { url = "https://files.pythonhosted.org/packages/87/c9/81e6fceaeba01f6d3bf36de9e43145590acba43063f2566d45f11d9a0999/rosettasciio-0.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0bf7ce36dbed455554892c8b84732ec25f3026da8374c56e1d491aac6efddf35", size = 1274114, upload-time = "2025-12-29T17:32:18.488Z" }, - { url = "https://files.pythonhosted.org/packages/88/cb/325acfd3255132574c6fa975362b03bf286db9b8dce0f57eaef9d4d2bf9c/rosettasciio-0.12.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:45f74c2414704cba896c13c09102a960b1624b610f7a2e7a177d70ebea6bb2a6", size = 1278900, upload-time = "2025-12-29T17:32:19.97Z" }, - { url = "https://files.pythonhosted.org/packages/24/4e/b0bcce83693873e26fc7d104708b8b14f710562e6b414c28a8b35c9030d7/rosettasciio-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:7e7bfc6f23604ae5133db33241053c265d35b0649d85029e85b2c261796bd865", size = 806447, upload-time = "2025-12-29T17:32:21.349Z" }, - { url = "https://files.pythonhosted.org/packages/c1/ac/aa09ee2a13608bdbed8049183a9f6983146de38f67d20fc91819f90fd7fc/rosettasciio-0.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:f15b9ab80c2ebd468c554b026bddd62cee02b9caa3d5ef20e354214c321e3ac0", size = 794087, upload-time = "2025-12-29T17:32:22.783Z" }, - { url = "https://files.pythonhosted.org/packages/72/ee/99d31a8514f82b47cebf3a4adad44c04e96c7d05d6821aaba5b737d6f819/rosettasciio-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0289c5f7bbb065880041f1fa1e777ddaf0dea37cc667c0da5d05cfbec6885a1c", size = 817296, upload-time = "2025-12-29T17:32:24.206Z" }, - { url = "https://files.pythonhosted.org/packages/61/26/83a280dfcd703cd2300e7ae4586dc0df2d24d2ed7a42693a355c9f3f875e/rosettasciio-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:35d6d8e8b947277fa6e5a829e0a367cdc1c8fbf29dccdfc667cec12dd1da8d3d", size = 815608, upload-time = "2025-12-29T17:32:26.191Z" }, - { url = "https://files.pythonhosted.org/packages/0b/8e/87e07335406125cff5095d72cffd0360a9fe4c76067659c5ccac5ee712e7/rosettasciio-0.12.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f88523c38eba7c991d711668667aac5c146737c613bbf4e1325f3b0d174eedf", size = 1267905, upload-time = "2025-12-29T17:32:27.612Z" }, - { url = "https://files.pythonhosted.org/packages/cb/7b/f10c0d7d8110cf14a51c47ddeba0fcd32633f87eefdd77d667da1e44c16d/rosettasciio-0.12.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:97727411c3c7560d4349d39582e1888b7e5bda1dcda21d48e30e2047e2f6e45c", size = 1277855, upload-time = "2025-12-29T17:32:29.694Z" }, - { url = "https://files.pythonhosted.org/packages/30/0f/daaa1b14c9fb2b295ff52be0b13604d59d7ae4968cb5779abfd414d61ddb/rosettasciio-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:7769f2caa80c761d7f30adaf5aa11da2dadf4149a0df8f0c939400e2ad924a1c", size = 806235, upload-time = "2025-12-29T17:32:31.571Z" }, - { url = "https://files.pythonhosted.org/packages/ef/ce/89729079e3527cf7cd2bb5e90c0d466d0852ee5cc7267a7f45cc8cdc5482/rosettasciio-0.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:5f307f3abfc061499535313d55b6d630cead7e450040d72276923a98342df930", size = 793943, upload-time = "2025-12-29T17:32:32.989Z" }, - { url = "https://files.pythonhosted.org/packages/33/5e/049b670a32e09cc788e085b503d083ab81f341837c2f1e46bd7ab4714d98/rosettasciio-0.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7ae3e37730b81cfb9b894a0e562693befa2ea44471c8c469ef82caf15c8ca42d", size = 821718, upload-time = "2025-12-29T17:32:35.3Z" }, - { url = "https://files.pythonhosted.org/packages/cf/a1/4884bcb0e187246ec24aee3175d1b2294cfd97b42afee8ccd7ec33a4c8b2/rosettasciio-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:07288b70421dc7ca91ccf59c91f7d9675d425582eae499662f6c3a27f1562e7f", size = 821588, upload-time = "2025-12-29T17:32:37.094Z" }, - { url = "https://files.pythonhosted.org/packages/9b/e8/b223f9b63614982b09f1c29b118466102580b95c518705db792866edb645/rosettasciio-0.12.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c810177218abfc74729f175856bf082bc435ad964ca5ceb1158a87fd23dbe992", size = 1281610, upload-time = "2025-12-29T17:32:38.621Z" }, - { url = "https://files.pythonhosted.org/packages/a4/53/f779609a7f0a887ea97233a00482b3034b262c982549e81337fdcc26bd8c/rosettasciio-0.12.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f9ab696534dc4cc5fd3b3eeac3758ab9c5260b09b4aea0cd94ea52decbede97", size = 1268743, upload-time = "2025-12-29T17:32:40.257Z" }, - { url = "https://files.pythonhosted.org/packages/34/b4/4ec99972f93b605a662ce09e56b2cc562051900492c8516dd935c0db3087/rosettasciio-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c6aecd74a7a0248843efc392a689a8dc68044f7183e2b8b9252c91335ee2f16f", size = 816540, upload-time = "2025-12-29T17:32:41.644Z" }, - { url = "https://files.pythonhosted.org/packages/73/0d/d1d99c3b9614a7525c082737f11c140671d690bdd9fc6a6085f1282bc713/rosettasciio-0.12.0-cp313-cp313t-win_arm64.whl", hash = "sha256:30fe143cd4aede2260cdff388a5686ad4725a27207d095f0c35b6e1fa09e0663", size = 798650, upload-time = "2025-12-29T17:32:43.09Z" }, - { url = "https://files.pythonhosted.org/packages/36/9f/98eaa0d086d6f569b40b8ec13b62d99c2ab6c60bf7a8a5d6999c45a18e6b/rosettasciio-0.12.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:268697caf043823c69e7c6c397db364a798abc1e3c31e56d48c4e1142cb85af7", size = 817569, upload-time = "2025-12-29T17:32:44.6Z" }, - { url = "https://files.pythonhosted.org/packages/18/60/09cb9b438107032251f49c07fad46539814adddfc58eece4790d3ab65a9f/rosettasciio-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fc66c00a4e3c4a564741e77296c120135c0a3fbc4b56640bc812298ca83b5a3c", size = 816443, upload-time = "2025-12-29T17:32:46.279Z" }, - { url = "https://files.pythonhosted.org/packages/61/90/be7c89b63f1cd5c702f23f0247b1a93040c867e37b3817004b89ec2b89a6/rosettasciio-0.12.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fa6023d4f206c37d0e2a4ef12070a0ef03a44ca5a6fbc25ae5ee58be72f236da", size = 1265935, upload-time = "2025-12-29T17:32:47.788Z" }, - { url = "https://files.pythonhosted.org/packages/c5/08/6b9a0a11686d8e10f43da14262129b4b4eb02a7f0a6e8d681b8f6e308fa8/rosettasciio-0.12.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84adcd658b7be1b03b1f8f36b3f5690c43d87b20b8cc8723db561fbfe05510d1", size = 1273714, upload-time = "2025-12-29T17:32:49.394Z" }, - { url = "https://files.pythonhosted.org/packages/f0/a5/50517ea5c856c64e6405ea9c550acd4cdea4b4f595144d73ad344bdca6cf/rosettasciio-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:59b51d45a888ee9361b5b5fb459adf7f5c0ac0e62f20610331e8af94ae5b695b", size = 805318, upload-time = "2025-12-29T17:32:50.861Z" }, - { url = "https://files.pythonhosted.org/packages/48/23/90534c33567d1473dbf59efcb4ba27699d5471824e0036937cb699bbb0cf/rosettasciio-0.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:4623e8263af07174c3fbe02981d15920910ce2d7eaf989ddc100a74bc162b2f6", size = 793074, upload-time = "2025-12-29T17:32:52.227Z" }, - { url = "https://files.pythonhosted.org/packages/62/ca/d6e04ac4b41e8f92e7754bc02f115a4bfa70a782815aacc2d5cad1a21b25/rosettasciio-0.12.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8e445003a48000f6dd462c180b8cea7a54a0e5ce17fe0706228fac619b80cce8", size = 822221, upload-time = "2025-12-29T17:32:54.074Z" }, - { url = "https://files.pythonhosted.org/packages/c4/a5/078548b2760ba2c529de3b4e677d48030d41092d011b4034cb0e2c23310b/rosettasciio-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:608aa659d42654dcd28eeb7076761a736fbbcb59876f8907d5160f9f2d7c5f5d", size = 821917, upload-time = "2025-12-29T17:32:57.044Z" }, - { url = "https://files.pythonhosted.org/packages/bd/ee/88dcea135add00ef59d03138b0b8378028153b00f4c0374ebd86cd19fe58/rosettasciio-0.12.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d5bccd360790e1c17bca31223f4e946eb4b19b55b9c1aae937365ed45839df0", size = 1282089, upload-time = "2025-12-29T17:32:58.535Z" }, - { url = "https://files.pythonhosted.org/packages/05/83/b4e55db04a54cd50ce85ad44604cef619e5a0e72b1eedf1f9d71a77fc0aa/rosettasciio-0.12.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04fa825a951b6fe11c4fca422e30e2d03156d04b197d0a75649db8562f4c7dcd", size = 1269179, upload-time = "2025-12-29T17:33:00.169Z" }, - { url = "https://files.pythonhosted.org/packages/5b/c1/d3ce8ada006d8e0e608859c335c83069cbc1dbce548a07ab870d3d336ee9/rosettasciio-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:3f89eeea6a624179fc11924a904ad7f4699a332a1d7b13176e3e9c2ca116e00a", size = 817913, upload-time = "2025-12-29T17:33:01.779Z" }, - { url = "https://files.pythonhosted.org/packages/4c/08/d41bf9dbbfe60463ab68268c5458d644fcb2d7cab9453c958a53b577b9ce/rosettasciio-0.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:38aec6a42a0810bee065502467f941d563a51d236af762432c65e212bac27ccf", size = 797652, upload-time = "2025-12-29T17:33:03.331Z" }, - { url = "https://files.pythonhosted.org/packages/56/00/772baf64631f9ec440b3291b708a04a00442aad796625cc0f0c6f8a1c0c4/rosettasciio-0.12.0-py3-none-any.whl", hash = "sha256:3974e75b1502b4e974c227f0b80d35240011c445b6d63394482fd9e6afc5c9fb", size = 567619, upload-time = "2025-12-29T17:33:04.939Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/f8/08/435d64b1605fb871b445259f6f275b946960e1a918afc854c847f452147c/rosettasciio-0.13.0.tar.gz", hash = "sha256:59bf2834c4ecb84132b1f89380bc95375d170b48cf59a5608e3bed684caf52c9", size = 1189632, upload-time = "2026-04-09T09:39:02.511Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/6d/fafb202c842ff399f445ec15af58befbc04d8d18aa6698d83f2d810ee5f9/rosettasciio-0.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bdbdea62745fb3d6854d8995bd156987d1e2b48a5e2296c6b1c7edd913ceb096", size = 822742, upload-time = "2026-04-09T09:37:56.153Z" }, + { url = "https://files.pythonhosted.org/packages/50/60/5ad9b2576427833f8f37d31583d49b49109a4ac5b84697de72dfc42d64f0/rosettasciio-0.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4cc73fce7e09a75dca154197e2694a2685bfffa82862b99ac7ea2de6454303f1", size = 821901, upload-time = "2026-04-09T09:37:57.83Z" }, + { url = "https://files.pythonhosted.org/packages/00/63/ecd34cb552824c4cd38805be8e6dab8a689833dea865a467aeeb4f281601/rosettasciio-0.13.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0bdc2c22ed1f422851b3b84b27beb93e0fe610642f254486c3b9c8a3f721981", size = 1277605, upload-time = "2026-04-09T09:37:59.365Z" }, + { url = "https://files.pythonhosted.org/packages/62/a9/6dbe0468db382c39f9293a69268df9d554e2f3fd5fdf9cddab8c89cbb028/rosettasciio-0.13.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5140c6ff68443c97c487454da23c5f1ac9f753d534aaf119f9cdadc03899ee6b", size = 1281953, upload-time = "2026-04-09T09:38:01.862Z" }, + { url = "https://files.pythonhosted.org/packages/c6/dc/8a1024dca145dc8ed2891ae98232b536d1abebd959e5f7f334d7355c28e1/rosettasciio-0.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:56b9ddaaa9d570c9d4fe43bd5a353cc4b4d609fc9970550e1f186465b35d8cd7", size = 811825, upload-time = "2026-04-09T09:38:03.907Z" }, + { url = "https://files.pythonhosted.org/packages/e0/6f/73d874f47b40cf1fe3b97d01f321f9dca4e1546551ff6fa22e8bfb707a97/rosettasciio-0.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:61878596a661e0b3004537be40779c2a9298678469c355dfe121f2e3688d8949", size = 800557, upload-time = "2026-04-09T09:38:05.404Z" }, + { url = "https://files.pythonhosted.org/packages/d0/43/8c3b5ffc09d54d94177aedea457fe571feb97c1631dbcc643a31ef4cc5b1/rosettasciio-0.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aaa1dbeeaa6798fd3b29fe0727160a37c3cc257234126db0d3c8016f8991f255", size = 823809, upload-time = "2026-04-09T09:38:07.017Z" }, + { url = "https://files.pythonhosted.org/packages/11/2f/c90520fe1a70585330950ab2a1cd4251a2ad76901e7c0d5ec74227fea9bf/rosettasciio-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27ad8e41a3c68df9404e2bd0d87deada52068a50d60649bd2d57b852b9fefb99", size = 822143, upload-time = "2026-04-09T09:38:08.642Z" }, + { url = "https://files.pythonhosted.org/packages/ff/05/7d5e18d60d5e50733ece82461d45a0f74517e1d75dfc8bc7e11140903d33/rosettasciio-0.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf6618fcdbccf61a773e1f86509e82f6ec5476bd38343856dcff707312ce4073", size = 1279557, upload-time = "2026-04-09T09:38:10.533Z" }, + { url = "https://files.pythonhosted.org/packages/1a/e2/7051df866c689552770b46fc35b0757199d16e8a3108927f5510c4db5224/rosettasciio-0.13.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:addc3f82fb4ea03c208c151d06eb65dba7e5fb0bea0244c5c054dbc5510ad962", size = 1284323, upload-time = "2026-04-09T09:38:12.365Z" }, + { url = "https://files.pythonhosted.org/packages/97/8f/450fb8bed3cbf05f875852ca243489e43fa0ed844438e700d9d5cb26e74b/rosettasciio-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:5157fd2468f9b77fd36948e7a54013d8608df30c243d6833ea48fa42f8f8a044", size = 812447, upload-time = "2026-04-09T09:38:14.357Z" }, + { url = "https://files.pythonhosted.org/packages/c0/cf/2d5686cf88a7d65760ecfa4583d4e7e26154932c7f60f405ac350ef7a898/rosettasciio-0.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:b0cfe472b561b74e678d71e05982979dc705dfda76e812278de283be6b295b8d", size = 800290, upload-time = "2026-04-09T09:38:16.311Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b0/446ecd78b50d268df112c393b47f8e764415f84097a778faa0e4730a6569/rosettasciio-0.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:337e63409040541196a530ed9a4a7cfb80ffdd5333183097c47a6774597e5e29", size = 823025, upload-time = "2026-04-09T09:38:17.798Z" }, + { url = "https://files.pythonhosted.org/packages/60/df/e7e7f9a6410543403a2675c13c68f8d4efa825dfce9308484fdb44333941/rosettasciio-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3d94b6a3cac5f99a055a71dca7a21fd16447376a990621788c8c87370a35fa4f", size = 821326, upload-time = "2026-04-09T09:38:20.027Z" }, + { url = "https://files.pythonhosted.org/packages/34/1a/db727b64bd30669951cd779b8ac98cc7ec0590c2d433bba451280b8c9ff5/rosettasciio-0.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d817ca99b43df9d478b1da6c007b345fc4f65ab80bb502486190e88df74d6123", size = 1272603, upload-time = "2026-04-09T09:38:21.879Z" }, + { url = "https://files.pythonhosted.org/packages/bf/f6/16f10a129e2675ba680daa88dd5f38a512ada1351b64488d49f03544cfb3/rosettasciio-0.13.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:252d19fd5eff9cf9524fe1c2df4cf618f5e742c26ecd376adacb567df49527b9", size = 1283074, upload-time = "2026-04-09T09:38:23.819Z" }, + { url = "https://files.pythonhosted.org/packages/58/06/59425c136ff864bb4cff7d888f185c42579e2b76cd778bf0733629111fc8/rosettasciio-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:6e61bbc1babab958234c455e2ae2aad3155ef1d897cebcc76c55ceb2d89f8fe2", size = 812215, upload-time = "2026-04-09T09:38:26.04Z" }, + { url = "https://files.pythonhosted.org/packages/f4/8a/c1d39f91cdea09984ce37c546b1f28ad3a077426766d7c85d49ac3eb2b7d/rosettasciio-0.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:5f3557ea5d116e87ad6d0b09b192c7ebe4d2d87e1fedd2f56eab701d8f8846d0", size = 800093, upload-time = "2026-04-09T09:38:27.696Z" }, + { url = "https://files.pythonhosted.org/packages/8b/2a/2d01c6ab98dc522eb41bbfcd04afaef6a7793f5a59d6160acba8ebd0f033/rosettasciio-0.13.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1fa9bcc7112b9b28f4b95e9e92f887cf0e499db927577273b3487bd5765e5bd8", size = 827252, upload-time = "2026-04-09T09:38:29.369Z" }, + { url = "https://files.pythonhosted.org/packages/30/2c/42d58c33f69237787db336eaaa6cb067acef63665dde8d13df8fe082fdb2/rosettasciio-0.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0176857cf0e38b0fb6d71b143e207581447995fb41933b3c80eec1315b65549e", size = 827226, upload-time = "2026-04-09T09:38:31.536Z" }, + { url = "https://files.pythonhosted.org/packages/5d/29/a91d1055ff02c806df84c4f95f4f712a739369bada65eda849d9cc8cbae4/rosettasciio-0.13.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c2c36529cf8707aaa68876ce96110707a9341f2e3284fde6273c6cf5a88bfe7", size = 1287317, upload-time = "2026-04-09T09:38:33.001Z" }, + { url = "https://files.pythonhosted.org/packages/58/d3/dea667d2742e4ba9c78ae50c08cdd839550697b9c7676660d53d633cac31/rosettasciio-0.13.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ee2e1d51bfd010340f0bc55781d833f556ffd2bfd7d8890a33c1bd4173c5de3", size = 1274142, upload-time = "2026-04-09T09:38:34.643Z" }, + { url = "https://files.pythonhosted.org/packages/77/37/b4e71488292bb8eac88e22e4c59378181e511b26e26bb3dc4a53e3eee51e/rosettasciio-0.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:74cb1ce27667011611bf5f1b96c460985cd28b0dac9dbf7723259f5a0205f63f", size = 822578, upload-time = "2026-04-09T09:38:36.241Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6a/3e074d0c9b75f6971bcd6266b08e739c1fe3709e9ce08fb9e960ea64cb8d/rosettasciio-0.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:727cc1f1bd37a139098184a426517dd60664b4a6e5b836e4e254de91a5d6e357", size = 804707, upload-time = "2026-04-09T09:38:37.98Z" }, + { url = "https://files.pythonhosted.org/packages/3c/72/00d7aa20493963e3337276fe97927b38f58117b4e488236c6b76332df199/rosettasciio-0.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:79332f1165b4f2029a7c03a339f467302e4d71f3aee2c699a49ab6236dd2aeec", size = 823255, upload-time = "2026-04-09T09:38:39.778Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ea/6230f0f1afd3f73af32a229b2907a86d184ad584e1f75c16b234d7e48ec5/rosettasciio-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0be2e98e862cd1d725b105d2b20849e4608497ef77152ff5994cdbb28f27e0df", size = 822154, upload-time = "2026-04-09T09:38:41.409Z" }, + { url = "https://files.pythonhosted.org/packages/01/1a/5af45536c4e527cef10085135efd553e79d822d4bad5304109b31a00583e/rosettasciio-0.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:059ee09c3d335e590470820ce16c4f779822b91908f3506456cb825849714ddd", size = 1271542, upload-time = "2026-04-09T09:38:43.243Z" }, + { url = "https://files.pythonhosted.org/packages/cb/55/95bc7d475a861fe4ed17c10b1cbf62ccf00073e149983091cdc046d94ed2/rosettasciio-0.13.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8fd84038d0744065d9dc0be37050679f7f2ee59286c05e6841d9983b54544a6", size = 1278938, upload-time = "2026-04-09T09:38:45.044Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ce/59addee6caea52623690f1e2d56b52c60b12c9eb4d993cb86da88b187398/rosettasciio-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:9567d347f368109b43301a3a31c2f243d9cb081315e361b72ec0d90065e37d04", size = 811208, upload-time = "2026-04-09T09:38:46.954Z" }, + { url = "https://files.pythonhosted.org/packages/b0/87/faf3209b600c579b3e38187b7a9d9263462a97e0858e99258f20ea6c40e3/rosettasciio-0.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:b6bbec93b2947e2035aca559284441be3f3dde5aa755b318a6413eeb21ab7c69", size = 799008, upload-time = "2026-04-09T09:38:48.629Z" }, + { url = "https://files.pythonhosted.org/packages/9b/9b/007063117e177147705b3cefb618689d7d4b5e243acd8e39e7e48a35fb69/rosettasciio-0.13.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:34dd225f0e11635ca09ccebb0ac3d8d38b71483491247d1f7789a9501288d659", size = 827711, upload-time = "2026-04-09T09:38:50.625Z" }, + { url = "https://files.pythonhosted.org/packages/97/64/42082e253b0ae2acf7aa7a67bd4547c6d918c248d24e715cbd63c4cadd4d/rosettasciio-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:dbaed91a90c118e74b2729a0738d2d5170d4d2f5763d84b06e8993f82fa5644d", size = 827561, upload-time = "2026-04-09T09:38:52.143Z" }, + { url = "https://files.pythonhosted.org/packages/91/ac/95837b692eafdb4ec67464cc13780da01b358ab8b293f327e6d59bc59f27/rosettasciio-0.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6d249fbc568dd4077df94fc98d512ea8bdabb7d87f73724bf6bf75c9a5649dc", size = 1287593, upload-time = "2026-04-09T09:38:53.66Z" }, + { url = "https://files.pythonhosted.org/packages/2c/cc/69c859bcedad6564a46456058ba952c54b76040240e556c9930adf292534/rosettasciio-0.13.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3fb7ff9b9c55aefb2596b900e7b009a49b36c506e473130dd7304834463e0f73", size = 1274514, upload-time = "2026-04-09T09:38:55.152Z" }, + { url = "https://files.pythonhosted.org/packages/54/2b/5888169b6dc708f68af424120f27f8e28b0d628d6d1526b3e9232b599785/rosettasciio-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:ad1111fa3a582b88cd7401dda5f66bf6f7c6f3f0a7ab3a881338062ff0d8fb80", size = 823903, upload-time = "2026-04-09T09:38:57.121Z" }, + { url = "https://files.pythonhosted.org/packages/9e/eb/df4ed7ef0a525611b3b42947879a59cb26f2dac1f5359bd448c545913207/rosettasciio-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f15d405a134a8e6e50eaf4b69ac7b69d227d014b839c15832b068914fe8a6ddf", size = 803657, upload-time = "2026-04-09T09:38:58.93Z" }, + { url = "https://files.pythonhosted.org/packages/23/94/83d57a093891773b2c28d16098bda58e10c43e1425583e40873baa0c09c2/rosettasciio-0.13.0-py3-none-any.whl", hash = "sha256:bc3e8b0dab257e5ff6f9f1f4b4207ee67f6d2c55adf5f7112f92dec2ab50ef59", size = 573680, upload-time = "2026-04-09T09:39:00.338Z" }, ] [[package]] @@ -3156,27 +3156,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/14/b0/73cf7550861e2b4824950b8b52eebdcc5adc792a00c514406556c5b80817/ruff-0.15.8.tar.gz", hash = "sha256:995f11f63597ee362130d1d5a327a87cb6f3f5eae3094c620bcc632329a4d26e", size = 4610921, upload-time = "2026-03-26T18:39:38.675Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/92/c445b0cd6da6e7ae51e954939cb69f97e008dbe750cfca89b8cedc081be7/ruff-0.15.8-py3-none-linux_armv6l.whl", hash = "sha256:cbe05adeba76d58162762d6b239c9056f1a15a55bd4b346cfd21e26cd6ad7bc7", size = 10527394, upload-time = "2026-03-26T18:39:41.566Z" }, - { url = "https://files.pythonhosted.org/packages/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d3e3d0b6ba8dca1b7ef9ab80a28e840a20070c4b62e56d675c24f366ef330570", size = 10905693, upload-time = "2026-03-26T18:39:30.364Z" }, - { url = "https://files.pythonhosted.org/packages/ca/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ee3ae5c65a42f273f126686353f2e08ff29927b7b7e203b711514370d500de3", size = 10323044, upload-time = "2026-03-26T18:39:33.37Z" }, - { url = "https://files.pythonhosted.org/packages/67/18/1bf38e20914a05e72ef3b9569b1d5c70a7ef26cd188d69e9ca8ef588d5bf/ruff-0.15.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdce027ada77baa448077ccc6ebb2fa9c3c62fd110d8659d601cf2f475858d94", size = 10629135, upload-time = "2026-03-26T18:39:44.142Z" }, - { url = "https://files.pythonhosted.org/packages/d2/e9/138c150ff9af60556121623d41aba18b7b57d95ac032e177b6a53789d279/ruff-0.15.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12e617fc01a95e5821648a6df341d80456bd627bfab8a829f7cfc26a14a4b4a3", size = 10348041, upload-time = "2026-03-26T18:39:52.178Z" }, - { url = "https://files.pythonhosted.org/packages/02/f1/5bfb9298d9c323f842c5ddeb85f1f10ef51516ac7a34ba446c9347d898df/ruff-0.15.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:432701303b26416d22ba696c39f2c6f12499b89093b61360abc34bcc9bf07762", size = 11121987, upload-time = "2026-03-26T18:39:55.195Z" }, - { url = "https://files.pythonhosted.org/packages/10/11/6da2e538704e753c04e8d86b1fc55712fdbdcc266af1a1ece7a51fff0d10/ruff-0.15.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d910ae974b7a06a33a057cb87d2a10792a3b2b3b35e33d2699fdf63ec8f6b17a", size = 11951057, upload-time = "2026-03-26T18:39:19.18Z" }, - { url = "https://files.pythonhosted.org/packages/83/f0/c9208c5fd5101bf87002fed774ff25a96eea313d305f1e5d5744698dc314/ruff-0.15.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2033f963c43949d51e6fdccd3946633c6b37c484f5f98c3035f49c27395a8ab8", size = 11464613, upload-time = "2026-03-26T18:40:06.301Z" }, - { url = "https://files.pythonhosted.org/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f29b989a55572fb885b77464cf24af05500806ab4edf9a0fd8977f9759d85b1", size = 11257557, upload-time = "2026-03-26T18:39:57.972Z" }, - { url = "https://files.pythonhosted.org/packages/71/8c/382a9620038cf6906446b23ce8632ab8c0811b8f9d3e764f58bedd0c9a6f/ruff-0.15.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:ac51d486bf457cdc985a412fb1801b2dfd1bd8838372fc55de64b1510eff4bec", size = 11169440, upload-time = "2026-03-26T18:39:22.205Z" }, - { url = "https://files.pythonhosted.org/packages/4d/0d/0994c802a7eaaf99380085e4e40c845f8e32a562e20a38ec06174b52ef24/ruff-0.15.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c9861eb959edab053c10ad62c278835ee69ca527b6dcd72b47d5c1e5648964f6", size = 10605963, upload-time = "2026-03-26T18:39:46.682Z" }, - { url = "https://files.pythonhosted.org/packages/19/aa/d624b86f5b0aad7cef6bbf9cd47a6a02dfdc4f72c92a337d724e39c9d14b/ruff-0.15.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8d9a5b8ea13f26ae90838afc33f91b547e61b794865374f114f349e9036835fb", size = 10357484, upload-time = "2026-03-26T18:39:49.176Z" }, - { url = "https://files.pythonhosted.org/packages/35/c3/e0b7835d23001f7d999f3895c6b569927c4d39912286897f625736e1fd04/ruff-0.15.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c2a33a529fb3cbc23a7124b5c6ff121e4d6228029cba374777bd7649cc8598b8", size = 10830426, upload-time = "2026-03-26T18:40:03.702Z" }, - { url = "https://files.pythonhosted.org/packages/f0/51/ab20b322f637b369383adc341d761eaaa0f0203d6b9a7421cd6e783d81b9/ruff-0.15.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:75e5cd06b1cf3f47a3996cfc999226b19aa92e7cce682dcd62f80d7035f98f49", size = 11345125, upload-time = "2026-03-26T18:39:27.799Z" }, - { url = "https://files.pythonhosted.org/packages/37/e6/90b2b33419f59d0f2c4c8a48a4b74b460709a557e8e0064cf33ad894f983/ruff-0.15.8-py3-none-win32.whl", hash = "sha256:bc1f0a51254ba21767bfa9a8b5013ca8149dcf38092e6a9eb704d876de94dc34", size = 10571959, upload-time = "2026-03-26T18:39:36.117Z" }, - { url = "https://files.pythonhosted.org/packages/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl", hash = "sha256:04f79eff02a72db209d47d665ba7ebcad609d8918a134f86cb13dd132159fc89", size = 11743893, upload-time = "2026-03-26T18:39:25.01Z" }, - { url = "https://files.pythonhosted.org/packages/15/e2/77be4fff062fa78d9b2a4dea85d14785dac5f1d0c1fb58ed52331f0ebe28/ruff-0.15.8-py3-none-win_arm64.whl", hash = "sha256:cf891fa8e3bb430c0e7fac93851a5978fc99c8fa2c053b57b118972866f8e5f2", size = 11048175, upload-time = "2026-03-26T18:40:01.06Z" }, +version = "0.15.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/d9/aa3f7d59a10ef6b14fe3431706f854dbf03c5976be614a9796d36326810c/ruff-0.15.10.tar.gz", hash = "sha256:d1f86e67ebfdef88e00faefa1552b5e510e1d35f3be7d423dc7e84e63788c94e", size = 4631728, upload-time = "2026-04-09T14:06:09.884Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/00/a1c2fdc9939b2c03691edbda290afcd297f1f389196172826b03d6b6a595/ruff-0.15.10-py3-none-linux_armv6l.whl", hash = "sha256:0744e31482f8f7d0d10a11fcbf897af272fefdfcb10f5af907b18c2813ff4d5f", size = 10563362, upload-time = "2026-04-09T14:06:21.189Z" }, + { url = "https://files.pythonhosted.org/packages/5c/15/006990029aea0bebe9d33c73c3e28c80c391ebdba408d1b08496f00d422d/ruff-0.15.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b1e7c16ea0ff5a53b7c2df52d947e685973049be1cdfe2b59a9c43601897b22e", size = 10951122, upload-time = "2026-04-09T14:06:02.236Z" }, + { url = "https://files.pythonhosted.org/packages/f2/c0/4ac978fe874d0618c7da647862afe697b281c2806f13ce904ad652fa87e4/ruff-0.15.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:93cc06a19e5155b4441dd72808fdf84290d84ad8a39ca3b0f994363ade4cebb1", size = 10314005, upload-time = "2026-04-09T14:06:00.026Z" }, + { url = "https://files.pythonhosted.org/packages/da/73/c209138a5c98c0d321266372fc4e33ad43d506d7e5dd817dd89b60a8548f/ruff-0.15.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83e1dd04312997c99ea6965df66a14fb4f03ba978564574ffc68b0d61fd3989e", size = 10643450, upload-time = "2026-04-09T14:05:42.137Z" }, + { url = "https://files.pythonhosted.org/packages/ec/76/0deec355d8ec10709653635b1f90856735302cb8e149acfdf6f82a5feb70/ruff-0.15.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8154d43684e4333360fedd11aaa40b1b08a4e37d8ffa9d95fee6fa5b37b6fab1", size = 10379597, upload-time = "2026-04-09T14:05:49.984Z" }, + { url = "https://files.pythonhosted.org/packages/dc/be/86bba8fc8798c081e28a4b3bb6d143ccad3fd5f6f024f02002b8f08a9fa3/ruff-0.15.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ab88715f3a6deb6bde6c227f3a123410bec7b855c3ae331b4c006189e895cef", size = 11146645, upload-time = "2026-04-09T14:06:12.246Z" }, + { url = "https://files.pythonhosted.org/packages/a8/89/140025e65911b281c57be1d385ba1d932c2366ca88ae6663685aed8d4881/ruff-0.15.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a768ff5969b4f44c349d48edf4ab4f91eddb27fd9d77799598e130fb628aa158", size = 12030289, upload-time = "2026-04-09T14:06:04.776Z" }, + { url = "https://files.pythonhosted.org/packages/88/de/ddacca9545a5e01332567db01d44bd8cf725f2db3b3d61a80550b48308ea/ruff-0.15.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ee3ef42dab7078bda5ff6a1bcba8539e9857deb447132ad5566a038674540d0", size = 11496266, upload-time = "2026-04-09T14:05:55.485Z" }, + { url = "https://files.pythonhosted.org/packages/bc/bb/7ddb00a83760ff4a83c4e2fc231fd63937cc7317c10c82f583302e0f6586/ruff-0.15.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51cb8cc943e891ba99989dd92d61e29b1d231e14811db9be6440ecf25d5c1609", size = 11256418, upload-time = "2026-04-09T14:05:57.69Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8d/55de0d35aacf6cd50b6ee91ee0f291672080021896543776f4170fc5c454/ruff-0.15.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e59c9bdc056a320fb9ea1700a8d591718b8faf78af065484e801258d3a76bc3f", size = 11288416, upload-time = "2026-04-09T14:05:44.695Z" }, + { url = "https://files.pythonhosted.org/packages/68/cf/9438b1a27426ec46a80e0a718093c7f958ef72f43eb3111862949ead3cc1/ruff-0.15.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:136c00ca2f47b0018b073f28cb5c1506642a830ea941a60354b0e8bc8076b151", size = 10621053, upload-time = "2026-04-09T14:05:52.782Z" }, + { url = "https://files.pythonhosted.org/packages/4c/50/e29be6e2c135e9cd4cb15fbade49d6a2717e009dff3766dd080fcb82e251/ruff-0.15.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8b80a2f3c9c8a950d6237f2ca12b206bccff626139be9fa005f14feb881a1ae8", size = 10378302, upload-time = "2026-04-09T14:06:14.361Z" }, + { url = "https://files.pythonhosted.org/packages/18/2f/e0b36a6f99c51bb89f3a30239bc7bf97e87a37ae80aa2d6542d6e5150364/ruff-0.15.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e3e53c588164dc025b671c9df2462429d60357ea91af7e92e9d56c565a9f1b07", size = 10850074, upload-time = "2026-04-09T14:06:16.581Z" }, + { url = "https://files.pythonhosted.org/packages/11/08/874da392558ce087a0f9b709dc6ec0d60cbc694c1c772dab8d5f31efe8cb/ruff-0.15.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b0c52744cf9f143a393e284125d2576140b68264a93c6716464e129a3e9adb48", size = 11358051, upload-time = "2026-04-09T14:06:18.948Z" }, + { url = "https://files.pythonhosted.org/packages/e4/46/602938f030adfa043e67112b73821024dc79f3ab4df5474c25fa4c1d2d14/ruff-0.15.10-py3-none-win32.whl", hash = "sha256:d4272e87e801e9a27a2e8df7b21011c909d9ddd82f4f3281d269b6ba19789ca5", size = 10588964, upload-time = "2026-04-09T14:06:07.14Z" }, + { url = "https://files.pythonhosted.org/packages/25/b6/261225b875d7a13b33a6d02508c39c28450b2041bb01d0f7f1a83d569512/ruff-0.15.10-py3-none-win_amd64.whl", hash = "sha256:28cb32d53203242d403d819fd6983152489b12e4a3ae44993543d6fe62ab42ed", size = 11745044, upload-time = "2026-04-09T14:05:39.473Z" }, + { url = "https://files.pythonhosted.org/packages/58/ed/dea90a65b7d9e69888890fb14c90d7f51bf0c1e82ad800aeb0160e4bacfd/ruff-0.15.10-py3-none-win_arm64.whl", hash = "sha256:601d1610a9e1f1c2165a4f561eeaa2e2ea1e97f3287c5aa258d3dab8b57c6188", size = 11035607, upload-time = "2026-04-09T14:05:47.593Z" }, ] [[package]] @@ -3191,7 +3191,8 @@ dependencies = [ { name = "packaging" }, { name = "pillow" }, { name = "scipy" }, - { name = "tifffile" }, + { name = "tifffile", version = "2026.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "tifffile", version = "2026.4.11", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a1/b4/2528bb43c67d48053a7a649a9666432dc307d66ba02e3a6d5c40f46655df/scikit_image-0.26.0.tar.gz", hash = "sha256:f5f970ab04efad85c24714321fcc91613fcb64ef2a892a13167df2f3e59199fa", size = 22729739, upload-time = "2025-12-20T17:12:21.824Z" } wheels = [ @@ -3354,55 +3355,55 @@ wheels = [ [[package]] name = "sqlalchemy" -version = "2.0.48" +version = "2.0.49" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1f/73/b4a9737255583b5fa858e0bb8e116eb94b88c910164ed2ed719147bde3de/sqlalchemy-2.0.48.tar.gz", hash = "sha256:5ca74f37f3369b45e1f6b7b06afb182af1fd5dde009e4ffd831830d98cbe5fe7", size = 9886075, upload-time = "2026-03-02T15:28:51.474Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/6d/b8b78b5b80f3c3ab3f7fa90faa195ec3401f6d884b60221260fd4d51864c/sqlalchemy-2.0.48-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b4c575df7368b3b13e0cebf01d4679f9a28ed2ae6c1cd0b1d5beffb6b2007dc", size = 2157184, upload-time = "2026-03-02T15:38:28.161Z" }, - { url = "https://files.pythonhosted.org/packages/21/4b/4f3d4a43743ab58b95b9ddf5580a265b593d017693df9e08bd55780af5bb/sqlalchemy-2.0.48-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e83e3f959aaa1c9df95c22c528096d94848a1bc819f5d0ebf7ee3df0ca63db6c", size = 3313555, upload-time = "2026-03-02T15:58:57.21Z" }, - { url = "https://files.pythonhosted.org/packages/21/dd/3b7c53f1dbbf736fd27041aee68f8ac52226b610f914085b1652c2323442/sqlalchemy-2.0.48-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f7b7243850edd0b8b97043f04748f31de50cf426e939def5c16bedb540698f7", size = 3313057, upload-time = "2026-03-02T15:52:29.366Z" }, - { url = "https://files.pythonhosted.org/packages/d9/cc/3e600a90ae64047f33313d7d32e5ad025417f09d2ded487e8284b5e21a15/sqlalchemy-2.0.48-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:82745b03b4043e04600a6b665cb98697c4339b24e34d74b0a2ac0a2488b6f94d", size = 3265431, upload-time = "2026-03-02T15:58:59.096Z" }, - { url = "https://files.pythonhosted.org/packages/8b/19/780138dacfe3f5024f4cf96e4005e91edf6653d53d3673be4844578faf1d/sqlalchemy-2.0.48-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e5e088bf43f6ee6fec7dbf1ef7ff7774a616c236b5c0cb3e00662dd71a56b571", size = 3287646, upload-time = "2026-03-02T15:52:31.569Z" }, - { url = "https://files.pythonhosted.org/packages/40/fd/f32ced124f01a23151f4777e4c705f3a470adc7bd241d9f36a7c941a33bf/sqlalchemy-2.0.48-cp311-cp311-win32.whl", hash = "sha256:9c7d0a77e36b5f4b01ca398482230ab792061d243d715299b44a0b55c89fe617", size = 2116956, upload-time = "2026-03-02T15:46:54.535Z" }, - { url = "https://files.pythonhosted.org/packages/58/d5/dd767277f6feef12d05651538f280277e661698f617fa4d086cce6055416/sqlalchemy-2.0.48-cp311-cp311-win_amd64.whl", hash = "sha256:583849c743e0e3c9bb7446f5b5addeacedc168d657a69b418063dfdb2d90081c", size = 2141627, upload-time = "2026-03-02T15:46:55.849Z" }, - { url = "https://files.pythonhosted.org/packages/ef/91/a42ae716f8925e9659df2da21ba941f158686856107a61cc97a95e7647a3/sqlalchemy-2.0.48-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:348174f228b99f33ca1f773e85510e08927620caa59ffe7803b37170df30332b", size = 2155737, upload-time = "2026-03-02T15:49:13.207Z" }, - { url = "https://files.pythonhosted.org/packages/b9/52/f75f516a1f3888f027c1cfb5d22d4376f4b46236f2e8669dcb0cddc60275/sqlalchemy-2.0.48-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53667b5f668991e279d21f94ccfa6e45b4e3f4500e7591ae59a8012d0f010dcb", size = 3337020, upload-time = "2026-03-02T15:50:34.547Z" }, - { url = "https://files.pythonhosted.org/packages/37/9a/0c28b6371e0cdcb14f8f1930778cb3123acfcbd2c95bb9cf6b4a2ba0cce3/sqlalchemy-2.0.48-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34634e196f620c7a61d18d5cf7dc841ca6daa7961aed75d532b7e58b309ac894", size = 3349983, upload-time = "2026-03-02T15:53:25.542Z" }, - { url = "https://files.pythonhosted.org/packages/1c/46/0aee8f3ff20b1dcbceb46ca2d87fcc3d48b407925a383ff668218509d132/sqlalchemy-2.0.48-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:546572a1793cc35857a2ffa1fe0e58571af1779bcc1ffa7c9fb0839885ed69a9", size = 3279690, upload-time = "2026-03-02T15:50:36.277Z" }, - { url = "https://files.pythonhosted.org/packages/ce/8c/a957bc91293b49181350bfd55e6dfc6e30b7f7d83dc6792d72043274a390/sqlalchemy-2.0.48-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:07edba08061bc277bfdc772dd2a1a43978f5a45994dd3ede26391b405c15221e", size = 3314738, upload-time = "2026-03-02T15:53:27.519Z" }, - { url = "https://files.pythonhosted.org/packages/4b/44/1d257d9f9556661e7bdc83667cc414ba210acfc110c82938cb3611eea58f/sqlalchemy-2.0.48-cp312-cp312-win32.whl", hash = "sha256:908a3fa6908716f803b86896a09a2c4dde5f5ce2bb07aacc71ffebb57986ce99", size = 2115546, upload-time = "2026-03-02T15:54:31.591Z" }, - { url = "https://files.pythonhosted.org/packages/f2/af/c3c7e1f3a2b383155a16454df62ae8c62a30dd238e42e68c24cebebbfae6/sqlalchemy-2.0.48-cp312-cp312-win_amd64.whl", hash = "sha256:68549c403f79a8e25984376480959975212a670405e3913830614432b5daa07a", size = 2142484, upload-time = "2026-03-02T15:54:34.072Z" }, - { url = "https://files.pythonhosted.org/packages/d1/c6/569dc8bf3cd375abc5907e82235923e986799f301cd79a903f784b996fca/sqlalchemy-2.0.48-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e3070c03701037aa418b55d36532ecb8f8446ed0135acb71c678dbdf12f5b6e4", size = 2152599, upload-time = "2026-03-02T15:49:14.41Z" }, - { url = "https://files.pythonhosted.org/packages/6d/ff/f4e04a4bd5a24304f38cb0d4aa2ad4c0fb34999f8b884c656535e1b2b74c/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2645b7d8a738763b664a12a1542c89c940daa55196e8d73e55b169cc5c99f65f", size = 3278825, upload-time = "2026-03-02T15:50:38.269Z" }, - { url = "https://files.pythonhosted.org/packages/fe/88/cb59509e4668d8001818d7355d9995be90c321313078c912420603a7cb95/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b19151e76620a412c2ac1c6f977ab1b9fa7ad43140178345136456d5265b32ed", size = 3295200, upload-time = "2026-03-02T15:53:29.366Z" }, - { url = "https://files.pythonhosted.org/packages/87/dc/1609a4442aefd750ea2f32629559394ec92e89ac1d621a7f462b70f736ff/sqlalchemy-2.0.48-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b193a7e29fd9fa56e502920dca47dffe60f97c863494946bd698c6058a55658", size = 3226876, upload-time = "2026-03-02T15:50:39.802Z" }, - { url = "https://files.pythonhosted.org/packages/37/c3/6ae2ab5ea2fa989fbac4e674de01224b7a9d744becaf59bb967d62e99bed/sqlalchemy-2.0.48-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:36ac4ddc3d33e852da9cb00ffb08cea62ca05c39711dc67062ca2bb1fae35fd8", size = 3265045, upload-time = "2026-03-02T15:53:31.421Z" }, - { url = "https://files.pythonhosted.org/packages/6f/82/ea4665d1bb98c50c19666e672f21b81356bd6077c4574e3d2bbb84541f53/sqlalchemy-2.0.48-cp313-cp313-win32.whl", hash = "sha256:389b984139278f97757ea9b08993e7b9d1142912e046ab7d82b3fbaeb0209131", size = 2113700, upload-time = "2026-03-02T15:54:35.825Z" }, - { url = "https://files.pythonhosted.org/packages/b7/2b/b9040bec58c58225f073f5b0c1870defe1940835549dafec680cbd58c3c3/sqlalchemy-2.0.48-cp313-cp313-win_amd64.whl", hash = "sha256:d612c976cbc2d17edfcc4c006874b764e85e990c29ce9bd411f926bbfb02b9a2", size = 2139487, upload-time = "2026-03-02T15:54:37.079Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f4/7b17bd50244b78a49d22cc63c969d71dc4de54567dc152a9b46f6fae40ce/sqlalchemy-2.0.48-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69f5bc24904d3bc3640961cddd2523e361257ef68585d6e364166dfbe8c78fae", size = 3558851, upload-time = "2026-03-02T15:57:48.607Z" }, - { url = "https://files.pythonhosted.org/packages/20/0d/213668e9aca61d370f7d2a6449ea4ec699747fac67d4bda1bb3d129025be/sqlalchemy-2.0.48-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd08b90d211c086181caed76931ecfa2bdfc83eea3cfccdb0f82abc6c4b876cb", size = 3525525, upload-time = "2026-03-02T16:04:38.058Z" }, - { url = "https://files.pythonhosted.org/packages/85/d7/a84edf412979e7d59c69b89a5871f90a49228360594680e667cb2c46a828/sqlalchemy-2.0.48-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1ccd42229aaac2df431562117ac7e667d702e8e44afdb6cf0e50fa3f18160f0b", size = 3466611, upload-time = "2026-03-02T15:57:50.759Z" }, - { url = "https://files.pythonhosted.org/packages/86/55/42404ce5770f6be26a2b0607e7866c31b9a4176c819e9a7a5e0a055770be/sqlalchemy-2.0.48-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0dcbc588cd5b725162c076eb9119342f6579c7f7f55057bb7e3c6ff27e13121", size = 3475812, upload-time = "2026-03-02T16:04:40.092Z" }, - { url = "https://files.pythonhosted.org/packages/ae/ae/29b87775fadc43e627cf582fe3bda4d02e300f6b8f2747c764950d13784c/sqlalchemy-2.0.48-cp313-cp313t-win32.whl", hash = "sha256:9764014ef5e58aab76220c5664abb5d47d5bc858d9debf821e55cfdd0f128485", size = 2141335, upload-time = "2026-03-02T15:52:51.518Z" }, - { url = "https://files.pythonhosted.org/packages/91/44/f39d063c90f2443e5b46ec4819abd3d8de653893aae92df42a5c4f5843de/sqlalchemy-2.0.48-cp313-cp313t-win_amd64.whl", hash = "sha256:e2f35b4cccd9ed286ad62e0a3c3ac21e06c02abc60e20aa51a3e305a30f5fa79", size = 2173095, upload-time = "2026-03-02T15:52:52.79Z" }, - { url = "https://files.pythonhosted.org/packages/f7/b3/f437eaa1cf028bb3c927172c7272366393e73ccd104dcf5b6963f4ab5318/sqlalchemy-2.0.48-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e2d0d88686e3d35a76f3e15a34e8c12d73fc94c1dea1cd55782e695cc14086dd", size = 2154401, upload-time = "2026-03-02T15:49:17.24Z" }, - { url = "https://files.pythonhosted.org/packages/6c/1c/b3abdf0f402aa3f60f0df6ea53d92a162b458fca2321d8f1f00278506402/sqlalchemy-2.0.48-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49b7bddc1eebf011ea5ab722fdbe67a401caa34a350d278cc7733c0e88fecb1f", size = 3274528, upload-time = "2026-03-02T15:50:41.489Z" }, - { url = "https://files.pythonhosted.org/packages/f2/5e/327428a034407651a048f5e624361adf3f9fbac9d0fa98e981e9c6ff2f5e/sqlalchemy-2.0.48-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:426c5ca86415d9b8945c7073597e10de9644802e2ff502b8e1f11a7a2642856b", size = 3279523, upload-time = "2026-03-02T15:53:32.962Z" }, - { url = "https://files.pythonhosted.org/packages/2a/ca/ece73c81a918add0965b76b868b7b5359e068380b90ef1656ee995940c02/sqlalchemy-2.0.48-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:288937433bd44e3990e7da2402fabc44a3c6c25d3704da066b85b89a85474ae0", size = 3224312, upload-time = "2026-03-02T15:50:42.996Z" }, - { url = "https://files.pythonhosted.org/packages/88/11/fbaf1ae91fa4ee43f4fe79661cead6358644824419c26adb004941bdce7c/sqlalchemy-2.0.48-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8183dc57ae7d9edc1346e007e840a9f3d6aa7b7f165203a99e16f447150140d2", size = 3246304, upload-time = "2026-03-02T15:53:34.937Z" }, - { url = "https://files.pythonhosted.org/packages/fa/a8/5fb0deb13930b4f2f698c5541ae076c18981173e27dd00376dbaea7a9c82/sqlalchemy-2.0.48-cp314-cp314-win32.whl", hash = "sha256:1182437cb2d97988cfea04cf6cdc0b0bb9c74f4d56ec3d08b81e23d621a28cc6", size = 2116565, upload-time = "2026-03-02T15:54:38.321Z" }, - { url = "https://files.pythonhosted.org/packages/95/7e/e83615cb63f80047f18e61e31e8e32257d39458426c23006deeaf48f463b/sqlalchemy-2.0.48-cp314-cp314-win_amd64.whl", hash = "sha256:144921da96c08feb9e2b052c5c5c1d0d151a292c6135623c6b2c041f2a45f9e0", size = 2142205, upload-time = "2026-03-02T15:54:39.831Z" }, - { url = "https://files.pythonhosted.org/packages/83/e3/69d8711b3f2c5135e9cde5f063bc1605860f0b2c53086d40c04017eb1f77/sqlalchemy-2.0.48-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5aee45fd2c6c0f2b9cdddf48c48535e7471e42d6fb81adfde801da0bd5b93241", size = 3563519, upload-time = "2026-03-02T15:57:52.387Z" }, - { url = "https://files.pythonhosted.org/packages/f8/4f/a7cce98facca73c149ea4578981594aaa5fd841e956834931de503359336/sqlalchemy-2.0.48-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7cddca31edf8b0653090cbb54562ca027c421c58ddde2c0685f49ff56a1690e0", size = 3528611, upload-time = "2026-03-02T16:04:42.097Z" }, - { url = "https://files.pythonhosted.org/packages/cd/7d/5936c7a03a0b0cb0fa0cc425998821c6029756b0855a8f7ee70fba1de955/sqlalchemy-2.0.48-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7a936f1bb23d370b7c8cc079d5fce4c7d18da87a33c6744e51a93b0f9e97e9b3", size = 3472326, upload-time = "2026-03-02T15:57:54.423Z" }, - { url = "https://files.pythonhosted.org/packages/f4/33/cea7dfc31b52904efe3dcdc169eb4514078887dff1f5ae28a7f4c5d54b3c/sqlalchemy-2.0.48-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e004aa9248e8cb0a5f9b96d003ca7c1c0a5da8decd1066e7b53f59eb8ce7c62b", size = 3478453, upload-time = "2026-03-02T16:04:44.584Z" }, - { url = "https://files.pythonhosted.org/packages/c8/95/32107c4d13be077a9cae61e9ae49966a35dc4bf442a8852dd871db31f62e/sqlalchemy-2.0.48-cp314-cp314t-win32.whl", hash = "sha256:b8438ec5594980d405251451c5b7ea9aa58dda38eb7ac35fb7e4c696712ee24f", size = 2147209, upload-time = "2026-03-02T15:52:54.274Z" }, - { url = "https://files.pythonhosted.org/packages/d2/d7/1e073da7a4bc645eb83c76067284a0374e643bc4be57f14cc6414656f92c/sqlalchemy-2.0.48-cp314-cp314t-win_amd64.whl", hash = "sha256:d854b3970067297f3a7fbd7a4683587134aa9b3877ee15aa29eea478dc68f933", size = 2182198, upload-time = "2026-03-02T15:52:55.606Z" }, - { url = "https://files.pythonhosted.org/packages/46/2c/9664130905f03db57961b8980b05cab624afd114bf2be2576628a9f22da4/sqlalchemy-2.0.48-py3-none-any.whl", hash = "sha256:a66fe406437dd65cacd96a72689a3aaaecaebbcd62d81c5ac1c0fdbeac835096", size = 1940202, upload-time = "2026-03-02T15:52:43.285Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/09/45/461788f35e0364a8da7bda51a1fe1b09762d0c32f12f63727998d85a873b/sqlalchemy-2.0.49.tar.gz", hash = "sha256:d15950a57a210e36dd4cec1aac22787e2a4d57ba9318233e2ef8b2daf9ff2d5f", size = 9898221, upload-time = "2026-04-03T16:38:11.704Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/b5/e3617cc67420f8f403efebd7b043128f94775e57e5b84e7255203390ceae/sqlalchemy-2.0.49-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5070135e1b7409c4161133aa525419b0062088ed77c92b1da95366ec5cbebbe", size = 2159126, upload-time = "2026-04-03T16:50:13.242Z" }, + { url = "https://files.pythonhosted.org/packages/20/9b/91ca80403b17cd389622a642699e5f6564096b698e7cdcbcbb6409898bc4/sqlalchemy-2.0.49-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ac7a3e245fd0310fd31495eb61af772e637bdf7d88ee81e7f10a3f271bff014", size = 3315509, upload-time = "2026-04-03T16:54:49.332Z" }, + { url = "https://files.pythonhosted.org/packages/b1/61/0722511d98c54de95acb327824cb759e8653789af2b1944ab1cc69d32565/sqlalchemy-2.0.49-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d4e5a0ceba319942fa6b585cf82539288a61e314ef006c1209f734551ab9536", size = 3315014, upload-time = "2026-04-03T16:56:56.376Z" }, + { url = "https://files.pythonhosted.org/packages/46/55/d514a653ffeb4cebf4b54c47bec32ee28ad89d39fafba16eeed1d81dccd5/sqlalchemy-2.0.49-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3ddcb27fb39171de36e207600116ac9dfd4ae46f86c82a9bf3934043e80ebb88", size = 3267388, upload-time = "2026-04-03T16:54:51.272Z" }, + { url = "https://files.pythonhosted.org/packages/2f/16/0dcc56cb6d3335c1671a2258f5d2cb8267c9a2260e27fde53cbfb1b3540a/sqlalchemy-2.0.49-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:32fe6a41ad97302db2931f05bb91abbcc65b5ce4c675cd44b972428dd2947700", size = 3289602, upload-time = "2026-04-03T16:56:57.63Z" }, + { url = "https://files.pythonhosted.org/packages/51/6c/f8ab6fb04470a133cd80608db40aa292e6bae5f162c3a3d4ab19544a67af/sqlalchemy-2.0.49-cp311-cp311-win32.whl", hash = "sha256:46d51518d53edfbe0563662c96954dc8fcace9832332b914375f45a99b77cc9a", size = 2119044, upload-time = "2026-04-03T17:00:53.455Z" }, + { url = "https://files.pythonhosted.org/packages/c4/59/55a6d627d04b6ebb290693681d7683c7da001eddf90b60cfcc41ee907978/sqlalchemy-2.0.49-cp311-cp311-win_amd64.whl", hash = "sha256:951d4a210744813be63019f3df343bf233b7432aadf0db54c75802247330d3af", size = 2143642, upload-time = "2026-04-03T17:00:54.769Z" }, + { url = "https://files.pythonhosted.org/packages/49/b3/2de412451330756aaaa72d27131db6dde23995efe62c941184e15242a5fa/sqlalchemy-2.0.49-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4bbccb45260e4ff1b7db0be80a9025bb1e6698bdb808b83fff0000f7a90b2c0b", size = 2157681, upload-time = "2026-04-03T16:53:07.132Z" }, + { url = "https://files.pythonhosted.org/packages/50/84/b2a56e2105bd11ebf9f0b93abddd748e1a78d592819099359aa98134a8bf/sqlalchemy-2.0.49-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb37f15714ec2652d574f021d479e78cd4eb9d04396dca36568fdfffb3487982", size = 3338976, upload-time = "2026-04-03T17:07:40Z" }, + { url = "https://files.pythonhosted.org/packages/2c/fa/65fcae2ed62f84ab72cf89536c7c3217a156e71a2c111b1305ab6f0690e2/sqlalchemy-2.0.49-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb9ec6436a820a4c006aad1ac351f12de2f2dbdaad171692ee457a02429b672", size = 3351937, upload-time = "2026-04-03T17:12:23.374Z" }, + { url = "https://files.pythonhosted.org/packages/f8/2f/6fd118563572a7fe475925742eb6b3443b2250e346a0cc27d8d408e73773/sqlalchemy-2.0.49-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8d6efc136f44a7e8bc8088507eaabbb8c2b55b3dbb63fe102c690da0ddebe55e", size = 3281646, upload-time = "2026-04-03T17:07:41.949Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d7/410f4a007c65275b9cf82354adb4bb8ba587b176d0a6ee99caa16fe638f8/sqlalchemy-2.0.49-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e06e617e3d4fd9e51d385dfe45b077a41e9d1b033a7702551e3278ac597dc750", size = 3316695, upload-time = "2026-04-03T17:12:25.642Z" }, + { url = "https://files.pythonhosted.org/packages/d9/95/81f594aa60ded13273a844539041ccf1e66c5a7bed0a8e27810a3b52d522/sqlalchemy-2.0.49-cp312-cp312-win32.whl", hash = "sha256:83101a6930332b87653886c01d1ee7e294b1fe46a07dd9a2d2b4f91bcc88eec0", size = 2117483, upload-time = "2026-04-03T17:05:40.896Z" }, + { url = "https://files.pythonhosted.org/packages/47/9e/fd90114059175cac64e4fafa9bf3ac20584384d66de40793ae2e2f26f3bb/sqlalchemy-2.0.49-cp312-cp312-win_amd64.whl", hash = "sha256:618a308215b6cececb6240b9abde545e3acdabac7ae3e1d4e666896bf5ba44b4", size = 2144494, upload-time = "2026-04-03T17:05:42.282Z" }, + { url = "https://files.pythonhosted.org/packages/ae/81/81755f50eb2478eaf2049728491d4ea4f416c1eb013338682173259efa09/sqlalchemy-2.0.49-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df2d441bacf97022e81ad047e1597552eb3f83ca8a8f1a1fdd43cd7fe3898120", size = 2154547, upload-time = "2026-04-03T16:53:08.64Z" }, + { url = "https://files.pythonhosted.org/packages/a2/bc/3494270da80811d08bcfa247404292428c4fe16294932bce5593f215cad9/sqlalchemy-2.0.49-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8e20e511dc15265fb433571391ba313e10dd8ea7e509d51686a51313b4ac01a2", size = 3280782, upload-time = "2026-04-03T17:07:43.508Z" }, + { url = "https://files.pythonhosted.org/packages/cd/f5/038741f5e747a5f6ea3e72487211579d8cbea5eb9827a9cbd61d0108c4bd/sqlalchemy-2.0.49-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47604cb2159f8bbd5a1ab48a714557156320f20871ee64d550d8bf2683d980d3", size = 3297156, upload-time = "2026-04-03T17:12:27.697Z" }, + { url = "https://files.pythonhosted.org/packages/88/50/a6af0ff9dc954b43a65ca9b5367334e45d99684c90a3d3413fc19a02d43c/sqlalchemy-2.0.49-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:22d8798819f86720bc646ab015baff5ea4c971d68121cb36e2ebc2ee43ead2b7", size = 3228832, upload-time = "2026-04-03T17:07:45.38Z" }, + { url = "https://files.pythonhosted.org/packages/bc/d1/5f6bdad8de0bf546fc74370939621396515e0cdb9067402d6ba1b8afbe9a/sqlalchemy-2.0.49-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9b1c058c171b739e7c330760044803099c7fff11511e3ab3573e5327116a9c33", size = 3267000, upload-time = "2026-04-03T17:12:29.657Z" }, + { url = "https://files.pythonhosted.org/packages/f7/30/ad62227b4a9819a5e1c6abff77c0f614fa7c9326e5a3bdbee90f7139382b/sqlalchemy-2.0.49-cp313-cp313-win32.whl", hash = "sha256:a143af2ea6672f2af3f44ed8f9cd020e9cc34c56f0e8db12019d5d9ecf41cb3b", size = 2115641, upload-time = "2026-04-03T17:05:43.989Z" }, + { url = "https://files.pythonhosted.org/packages/17/3a/7215b1b7d6d49dc9a87211be44562077f5f04f9bb5a59552c1c8e2d98173/sqlalchemy-2.0.49-cp313-cp313-win_amd64.whl", hash = "sha256:12b04d1db2663b421fe072d638a138460a51d5a862403295671c4f3987fb9148", size = 2141498, upload-time = "2026-04-03T17:05:45.7Z" }, + { url = "https://files.pythonhosted.org/packages/28/4b/52a0cb2687a9cd1648252bb257be5a1ba2c2ded20ba695c65756a55a15a4/sqlalchemy-2.0.49-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24bd94bb301ec672d8f0623eba9226cc90d775d25a0c92b5f8e4965d7f3a1518", size = 3560807, upload-time = "2026-04-03T16:58:31.666Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d8/fda95459204877eed0458550d6c7c64c98cc50c2d8d618026737de9ed41a/sqlalchemy-2.0.49-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a51d3db74ba489266ef55c7a4534eb0b8db9a326553df481c11e5d7660c8364d", size = 3527481, upload-time = "2026-04-03T17:06:00.155Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0a/2aac8b78ac6487240cf7afef8f203ca783e8796002dc0cf65c4ee99ff8bb/sqlalchemy-2.0.49-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:55250fe61d6ebfd6934a272ee16ef1244e0f16b7af6cd18ab5b1fc9f08631db0", size = 3468565, upload-time = "2026-04-03T16:58:33.414Z" }, + { url = "https://files.pythonhosted.org/packages/a5/3d/ce71cfa82c50a373fd2148b3c870be05027155ce791dc9a5dcf439790b8b/sqlalchemy-2.0.49-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:46796877b47034b559a593d7e4b549aba151dae73f9e78212a3478161c12ab08", size = 3477769, upload-time = "2026-04-03T17:06:02.787Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e8/0a9f5c1f7c6f9ca480319bf57c2d7423f08d31445974167a27d14483c948/sqlalchemy-2.0.49-cp313-cp313t-win32.whl", hash = "sha256:9c4969a86e41454f2858256c39bdfb966a20961e9b58bf8749b65abf447e9a8d", size = 2143319, upload-time = "2026-04-03T17:02:04.328Z" }, + { url = "https://files.pythonhosted.org/packages/0e/51/fb5240729fbec73006e137c4f7a7918ffd583ab08921e6ff81a999d6517a/sqlalchemy-2.0.49-cp313-cp313t-win_amd64.whl", hash = "sha256:b9870d15ef00e4d0559ae10ee5bc71b654d1f20076dbe8bc7ed19b4c0625ceba", size = 2175104, upload-time = "2026-04-03T17:02:05.989Z" }, + { url = "https://files.pythonhosted.org/packages/55/33/bf28f618c0a9597d14e0b9ee7d1e0622faff738d44fe986ee287cdf1b8d0/sqlalchemy-2.0.49-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:233088b4b99ebcbc5258c755a097aa52fbf90727a03a5a80781c4b9c54347a2e", size = 2156356, upload-time = "2026-04-03T16:53:09.914Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a7/5f476227576cb8644650eff68cc35fa837d3802b997465c96b8340ced1e2/sqlalchemy-2.0.49-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57ca426a48eb2c682dae8204cd89ea8ab7031e2675120a47924fabc7caacbc2a", size = 3276486, upload-time = "2026-04-03T17:07:46.9Z" }, + { url = "https://files.pythonhosted.org/packages/2e/84/efc7c0bf3a1c5eef81d397f6fddac855becdbb11cb38ff957888603014a7/sqlalchemy-2.0.49-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:685e93e9c8f399b0c96a624799820176312f5ceef958c0f88215af4013d29066", size = 3281479, upload-time = "2026-04-03T17:12:32.226Z" }, + { url = "https://files.pythonhosted.org/packages/91/68/bb406fa4257099c67bd75f3f2261b129c63204b9155de0d450b37f004698/sqlalchemy-2.0.49-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e0400fa22f79acc334d9a6b185dc00a44a8e6578aa7e12d0ddcd8434152b187", size = 3226269, upload-time = "2026-04-03T17:07:48.678Z" }, + { url = "https://files.pythonhosted.org/packages/67/84/acb56c00cca9f251f437cb49e718e14f7687505749ea9255d7bd8158a6df/sqlalchemy-2.0.49-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a05977bffe9bffd2229f477fa75eabe3192b1b05f408961d1bebff8d1cd4d401", size = 3248260, upload-time = "2026-04-03T17:12:34.381Z" }, + { url = "https://files.pythonhosted.org/packages/56/19/6a20ea25606d1efd7bd1862149bb2a22d1451c3f851d23d887969201633f/sqlalchemy-2.0.49-cp314-cp314-win32.whl", hash = "sha256:0f2fa354ba106eafff2c14b0cc51f22801d1e8b2e4149342023bd6f0955de5f5", size = 2118463, upload-time = "2026-04-03T17:05:47.093Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4f/8297e4ed88e80baa1f5aa3c484a0ee29ef3c69c7582f206c916973b75057/sqlalchemy-2.0.49-cp314-cp314-win_amd64.whl", hash = "sha256:77641d299179c37b89cf2343ca9972c88bb6eef0d5fc504a2f86afd15cd5adf5", size = 2144204, upload-time = "2026-04-03T17:05:48.694Z" }, + { url = "https://files.pythonhosted.org/packages/1f/33/95e7216df810c706e0cd3655a778604bbd319ed4f43333127d465a46862d/sqlalchemy-2.0.49-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c1dc3368794d522f43914e03312202523cc89692f5389c32bea0233924f8d977", size = 3565474, upload-time = "2026-04-03T16:58:35.128Z" }, + { url = "https://files.pythonhosted.org/packages/0c/a4/ed7b18d8ccf7f954a83af6bb73866f5bc6f5636f44c7731fbb741f72cc4f/sqlalchemy-2.0.49-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c821c47ecfe05cc32140dcf8dc6fd5d21971c86dbd56eabfe5ba07a64910c01", size = 3530567, upload-time = "2026-04-03T17:06:04.587Z" }, + { url = "https://files.pythonhosted.org/packages/73/a3/20faa869c7e21a827c4a2a42b41353a54b0f9f5e96df5087629c306df71e/sqlalchemy-2.0.49-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9c04bff9a5335eb95c6ecf1c117576a0aa560def274876fd156cfe5510fccc61", size = 3474282, upload-time = "2026-04-03T16:58:37.131Z" }, + { url = "https://files.pythonhosted.org/packages/b7/50/276b9a007aa0764304ad467eceb70b04822dc32092492ee5f322d559a4dc/sqlalchemy-2.0.49-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7f605a456948c35260e7b2a39f8952a26f077fd25653c37740ed186b90aaa68a", size = 3480406, upload-time = "2026-04-03T17:06:07.176Z" }, + { url = "https://files.pythonhosted.org/packages/e5/c3/c80fcdb41905a2df650c2a3e0337198b6848876e63d66fe9188ef9003d24/sqlalchemy-2.0.49-cp314-cp314t-win32.whl", hash = "sha256:6270d717b11c5476b0cbb21eedc8d4dbb7d1a956fd6c15a23e96f197a6193158", size = 2149151, upload-time = "2026-04-03T17:02:07.281Z" }, + { url = "https://files.pythonhosted.org/packages/05/52/9f1a62feab6ed368aff068524ff414f26a6daebc7361861035ae00b05530/sqlalchemy-2.0.49-cp314-cp314t-win_amd64.whl", hash = "sha256:275424295f4256fd301744b8f335cff367825d270f155d522b30c7bf49903ee7", size = 2184178, upload-time = "2026-04-03T17:02:08.623Z" }, + { url = "https://files.pythonhosted.org/packages/e5/30/8519fdde58a7bdf155b714359791ad1dc018b47d60269d5d160d311fdc36/sqlalchemy-2.0.49-py3-none-any.whl", hash = "sha256:ec44cfa7ef1a728e88ad41674de50f6db8cfdb3e2af84af86e0041aaf02d43d0", size = 1942158, upload-time = "2026-04-03T16:53:44.135Z" }, ] [[package]] @@ -3479,14 +3480,33 @@ wheels = [ name = "tifffile" version = "2026.3.3" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.12'", +] dependencies = [ - { name = "numpy" }, + { name = "numpy", marker = "python_full_version < '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c5/cb/2f6d79c7576e22c116352a801f4c3c8ace5957e9aced862012430b62e14f/tifffile-2026.3.3.tar.gz", hash = "sha256:d9a1266bed6f2ee1dd0abde2018a38b4f8b2935cb843df381d70ac4eac5458b7", size = 388745, upload-time = "2026-03-03T19:14:38.134Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/1a/e4/e804505f87627cd8cdae9c010c47c4485fd8c1ce31a7dd0ab7fcc4707377/tifffile-2026.3.3-py3-none-any.whl", hash = "sha256:e8be15c94273113d31ecb7aa3a39822189dd11c4967e3cc88c178f1ad2fd1170", size = 243960, upload-time = "2026-03-03T19:14:35.808Z" }, ] +[[package]] +name = "tifffile" +version = "2026.4.11" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version >= '3.12' and python_full_version < '3.14'", +] +dependencies = [ + { name = "numpy", marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d7/4a/e687f5957fead200faad58dbf9c9431a2bbb118040e96f5fb8a55f7ebc50/tifffile-2026.4.11.tar.gz", hash = "sha256:17758ff0c0d4db385792a083ad3ca51fcb0f4d942642f4d8f8bc1287fdcf17bc", size = 394956, upload-time = "2026-04-12T01:57:28.793Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/9f/74f110b4271ded519c7add4341cbabc824de26817ff1c345b3109df9e99c/tifffile-2026.4.11-py3-none-any.whl", hash = "sha256:9b94ffeddb39e97601af646345e8808f885773de01b299e480ed6d3a41509ec9", size = 248227, upload-time = "2026-04-12T01:57:26.969Z" }, +] + [[package]] name = "tinycss2" version = "1.4.0" @@ -3737,11 +3757,11 @@ wheels = [ [[package]] name = "tzdata" -version = "2025.3" +version = "2026.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/f5/cd531b2d15a671a40c0f66cf06bc3570a12cd56eef98960068ebbad1bf5a/tzdata-2026.1.tar.gz", hash = "sha256:67658a1903c75917309e753fdc349ac0efd8c27db7a0cb406a25be4840f87f98", size = 197639, upload-time = "2026-04-03T11:25:22.002Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, + { url = "https://files.pythonhosted.org/packages/b0/70/d460bd685a170790ec89317e9bd33047988e4bce507b831f5db771e142de/tzdata-2026.1-py2.py3-none-any.whl", hash = "sha256:4b1d2be7ac37ceafd7327b961aa3a54e467efbdb563a23655fbfe0d39cfc42a9", size = 348952, upload-time = "2026-04-03T11:25:20.313Z" }, ] [[package]] @@ -3764,7 +3784,7 @@ wheels = [ [[package]] name = "virtualenv" -version = "21.2.0" +version = "21.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -3772,9 +3792,9 @@ dependencies = [ { name = "platformdirs" }, { name = "python-discovery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/aa/92/58199fe10049f9703c2666e809c4f686c54ef0a68b0f6afccf518c0b1eb9/virtualenv-21.2.0.tar.gz", hash = "sha256:1720dc3a62ef5b443092e3f499228599045d7fea4c79199770499df8becf9098", size = 5840618, upload-time = "2026-03-09T17:24:38.013Z" } +sdist = { url = "https://files.pythonhosted.org/packages/97/c5/aff062c66b42e2183201a7ace10c6b2e959a9a16525c8e8ca8e59410d27a/virtualenv-21.2.1.tar.gz", hash = "sha256:b66ffe81301766c0d5e2208fc3576652c59d44e7b731fc5f5ed701c9b537fa78", size = 5844770, upload-time = "2026-04-09T18:47:11.482Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-py3-none-any.whl", hash = "sha256:1bd755b504931164a5a496d217c014d098426cddc79363ad66ac78125f9d908f", size = 5825084, upload-time = "2026-03-09T17:24:35.378Z" }, + { url = "https://files.pythonhosted.org/packages/20/0e/f083a76cb590e60dff3868779558eefefb8dfb7c9ed020babc7aa014ccbf/virtualenv-21.2.1-py3-none-any.whl", hash = "sha256:bd16b49c53562b28cf1a3ad2f36edb805ad71301dee70ddc449e5c88a9f919a2", size = 5828326, upload-time = "2026-04-09T18:47:09.331Z" }, ] [[package]] @@ -3815,14 +3835,14 @@ wheels = [ [[package]] name = "werkzeug" -version = "3.1.7" +version = "3.1.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b5/43/76ded108b296a49f52de6bac5192ca1c4be84e886f9b5c9ba8427d9694fd/werkzeug-3.1.7.tar.gz", hash = "sha256:fb8c01fe6ab13b9b7cdb46892b99b1d66754e1d7ab8e542e865ec13f526b5351", size = 875700, upload-time = "2026-03-24T01:08:07.687Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dd/b2/381be8cfdee792dd117872481b6e378f85c957dd7c5bca38897b08f765fd/werkzeug-3.1.8.tar.gz", hash = "sha256:9bad61a4268dac112f1c5cd4630a56ede601b6ed420300677a869083d70a4c44", size = 875852, upload-time = "2026-04-02T18:49:14.268Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/b2/0bba9bbb4596d2d2f285a16c2ab04118f6b957d8441566e1abb892e6a6b2/werkzeug-3.1.7-py3-none-any.whl", hash = "sha256:4b314d81163a3e1a169b6a0be2a000a0e204e8873c5de6586f453c55688d422f", size = 226295, upload-time = "2026-03-24T01:08:06.133Z" }, + { url = "https://files.pythonhosted.org/packages/93/8c/2e650f2afeb7ee576912636c23ddb621c91ac6a98e66dc8d29c3c69446e1/werkzeug-3.1.8-py3-none-any.whl", hash = "sha256:63a77fb8892bf28ebc3178683445222aa500e48ebad5ec77b0ad80f8726b1f50", size = 226459, upload-time = "2026-04-02T18:49:12.72Z" }, ] [[package]] From cbb59d34d6cf7f471370634b129007b35dedc09b Mon Sep 17 00:00:00 2001 From: smribet Date: Wed, 15 Apr 2026 10:00:17 -0700 Subject: [PATCH 233/335] fix for fft for non square scans --- .../ptychography_visualizations.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/quantem/diffractive_imaging/ptychography_visualizations.py b/src/quantem/diffractive_imaging/ptychography_visualizations.py index fe8f84bf..8cf2fa49 100644 --- a/src/quantem/diffractive_imaging/ptychography_visualizations.py +++ b/src/quantem/diffractive_imaging/ptychography_visualizations.py @@ -1,4 +1,3 @@ - import warnings from typing import Any, Literal @@ -171,19 +170,26 @@ def show_obj_fft( obj_show = obj_pad else: # complex or pure phase just show the phase obj_show = np.angle(obj_pad) - show_2d( + fig, ax = show_2d( [ obj_show, np.abs(obj_fft), ], title=[t + "Object", t + "Fourier Transform"], scalebar=[obj_scalebar, fft_scalebar], + return_fig=True, **kwargs, ) + ax[1].set_aspect(obj_np.shape[-1] / obj_np.shape[-2]) else: - show_2d( - np.abs(obj_fft), scalebar=fft_scalebar, title=t + "Fourier Transform", **kwargs + fig, ax = show_2d( + np.abs(obj_fft), + scalebar=fft_scalebar, + title=t + "Fourier Transform", + return_fig=True, + **kwargs, ) + ax.set_aspect(obj_np.shape[-1] / obj_np.shape[-2]) if return_fft: return obj_fft else: @@ -891,6 +897,7 @@ def show_scan_positions( if conv_angle is not None and energy is not None: from quantem.core.utils.utils import electron_wavelength_angstrom + wavelength = electron_wavelength_angstrom(energy) conv_angle_rad = conv_angle * 1e-3 # For defocused probe: radius ≈ |defocus| * convergence_angle + diffraction_limit From 21486792a6786c77694177ab1682418240f44370 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Wed, 15 Apr 2026 17:06:44 +0000 Subject: [PATCH 234/335] Added k-planes model --- src/quantem/core/ml/kplanes.py | 248 +++++++++++++++++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 src/quantem/core/ml/kplanes.py diff --git a/src/quantem/core/ml/kplanes.py b/src/quantem/core/ml/kplanes.py new file mode 100644 index 00000000..52a4adfc --- /dev/null +++ b/src/quantem/core/ml/kplanes.py @@ -0,0 +1,248 @@ +""" +Tensor Decomposition Methods for INR-based reconstructions +""" + +from typing import Any, Callable, Optional, Sequence + +import tinycudann as tcnn +import torch +import torch.nn.functional as F +from torch import nn + +""" +K-planes utility functions +""" +def grid_sample_wrapper(grid: torch.Tensor, coords: torch.Tensor, align_corners: bool = True) -> torch.Tensor: + """ + Performs bilinear interpolation on a grid at given coordinates. + + Args: + grid: Grid tensor of shape [B, C, H, W] or [C, H, W] + coords: Coordinate tensor of shape [B, N, 2] or [N, 2] + align_corners: Whether to align corners + + Returns: + Interpolated values of shape [B, N, C] or [N, C] + """ + grid_dim = coords.shape[-1] + + if grid.dim() == grid_dim + 1: + # no batch dimension present, need to add it + grid = grid.unsqueeze(0) + if coords.dim() == 2: + coords = coords.unsqueeze(0) + + if grid_dim == 2 or grid_dim == 3: + grid_sampler = F.grid_sample + else: + raise NotImplementedError(f"Grid-sample was called with {grid_dim}D data but is only " + f"implemented for 2 and 3D data.") + + coords = coords.view([coords.shape[0]] + [1] * (grid_dim - 1) + list(coords.shape[1:])) + B, feature_dim = grid.shape[:2] + n = coords.shape[-2] + interp = grid_sampler( + grid, # [B, feature_dim, reso, ...] + coords, # [B, 1, ..., n, grid_dim] + align_corners=align_corners, + mode='bilinear', padding_mode='border') + interp = interp.view(B, feature_dim, n).transpose(-1, -2) # [B, n, feature_dim] + interp = interp.squeeze() # [B?, n, feature_dim?] + return interp + +def init_planes( + in_dim: int, + out_dim: int, + resolution: Sequence[int], + init_range: tuple = (0.1, 0.5), +) -> nn.ParameterList: + """Create the set of 2D planes for a k-plane decomposition. + + For in_dim=3 (spatial), this creates 3 planes: XY, XZ, YZ. + For in_dim=4 (spatial + time), this creates 6 planes: XY, XZ, XT, YZ, YT, ZT. + Time planes (those involving axis 3) are initialized to 1 so they start + as identity multipliers. + + Args: + in_dim: Dimensionality of the input coordinates (3 or 4). + out_dim: Number of feature channels per plane. + resolution: Resolution along each axis, e.g. [128, 128, 128]. + init_range: (a, b) for uniform initialization of spatial planes. + + Returns: + nn.ParameterList of plane parameters, each of shape [1, out_dim, res_j, res_i]. + """ + assert len(resolution) == in_dim + # All pairs of axes + axis_pairs = list(itertools.combinations(range(in_dim), 2)) + planes = nn.ParameterList() + a, b = init_range + for pair in axis_pairs: + # grid_sample expects (N, C, H, W) — so resolution is reversed + shape = [1, out_dim] + [resolution[ax] for ax in reversed(pair)] + param = nn.Parameter(torch.empty(*shape)) + # Time planes init to 1; spatial planes init uniform + if in_dim == 4 and 3 in pair: + nn.init.ones_(param) + else: + nn.init.uniform_(param, a=a, b=b) + planes.append(param) + return planes + +def query_planes( + pts: torch.Tensor, + planes: nn.ParameterList, + in_dim: int, +) -> float: + """Query the k-plane representation at a batch of points. + + Projects each point onto every axis-pair plane, bilinearly interpolates, + and returns the element-wise product across all planes. + + Args: + pts: (B, in_dim) coordinates in [-1, 1]. + planes: The ParameterList from init_planes. + in_dim: 3 or 4. + + Returns: + (B, out_dim) features. + """ + axis_pairs = list(itertools.combinations(range(in_dim), 2)) + result = 1.0 + for plane_param, pair in zip(planes, axis_pairs): + # Extract the 2D coords for this plane + coords_2d = pts[..., list(pair)] # (B, 2) + coords_2d = coords_2d.view(1, -1, 1, 2) # (1, B, 1, 2) for grid_sample + # grid_sample: input (N,C,H,W), grid (N, H_out, W_out, 2) + sampled = F.grid_sample( + plane_param, # (1, C, H, W) + coords_2d, # (1, B, 1, 2) + align_corners=True, + mode="bilinear", + padding_mode="border", + ) # -> (1, C, B, 1) + sampled = sampled.squeeze(0).squeeze(-1).T # (B, C) + result = result * sampled + return result # pyright: ignore[reportReturnType] + + +def interpolate_ms_features( + pts: torch.Tensor, + ms_grids: nn.ModuleList, +) -> torch.Tensor: + coo_combs = list(itertools.combinations(range(3), 2)) # [(0,1), (0,2), (1,2)] + multi_scale_interp = [] + + for grid in ms_grids: + interp_space = 1. + for ci, coo_comb in enumerate(coo_combs): + feature_dim = grid[ci].shape[1] + interp_out_plane = ( + grid_sample_wrapper(grid[ci], pts[..., coo_comb]) + .view(-1, feature_dim) + ) + interp_space = interp_space * interp_out_plane + multi_scale_interp.append(interp_space) + + return torch.cat(multi_scale_interp, dim=-1) + + +""" +K-planes Model +""" +class KPlanes(nn.Module): + + def __init__( + self, + # Grid parameters + grid_dimensions: int = 2, + input_coords_dims: int = 3, + M_features: int = 32, + resolution: Sequence[int] = (200, 200, 200), + multiscale_res_multipliers: Optional[Sequence[int]] = None, + concat_features: bool = True, + density_activation: Callable = lambda x: F.softplus(x - 1), + ): + """ + Assume coords are [-1, 1] in each dimension. + """ + super().__init__() + + self.grid_dimensions = grid_dimensions + self.input_coords_dims = input_coords_dims + self.M_features = M_features + self.resolution = resolution + self.multiscale_res_multipliers = multiscale_res_multipliers or [1] + self.concat_features = concat_features + self.density_activation = density_activation + + + # Initialize planes + self.grids = nn.ParameterList() + self.feature_dim = 0 + + # Resolution pyramid + for res_mult in self.multiscale_res_multipliers: + scaled_res = [r * res_mult for r in self.resolution] + gp = init_planes( + in_dim=self.input_coords_dims, + out_dim=self.M_features, + resolution=scaled_res, + ) + + self.feature_dim += gp[-1].shape[1] + self.grids.append(gp) + + + # Linear net + self.sigma_net = tcnn.Network( + n_input_dims=self.feature_dim, + n_output_dims=1, + network_config={ + "otype": "CutlassMLP", + "activation": "None", + "output_activation": "None", + "n_neurons": 128, + "n_hidden_layers": 0, + }, + ) + + + + def get_densities(self, coords: torch.Tensor): + """Computes and returns densities""" + + pts = coords.reshape(-1, 3) + features = interpolate_ms_features( + pts=pts, + ms_grids=self.grids, + ) + density_before_activation = self.sigma_net(features) + density = self.density_activation(density_before_activation) + return density + + def forward( + self, + pts: torch.Tensor, + ): + return self.get_densities(pts) + + def get_params(self) -> dict[str, list[torch.nn.Parameter]]: + return { + "grids": [p for grid in self.grids for p in grid], # flatten ParameterLists + "sigma_net": list(self.sigma_net.parameters()), + } + + + def set_optimizer(self, optimizer_params: dict[str, Any]): + + self._grids.set_optimizer(optimizer_params["grids"]) + self._sigmanet.set_optimizer(optimizer_params["sigmanet"]) + + + + def get_params(self) -> dict[str, list[torch.nn.Parameter]]: + return { + "grids": self._grids.params # flatten ParameterLists + "sigma_net": self._sigma_net.params + } \ No newline at end of file From c299ca8bd03d18ffaa7ac56b4c1ac998c6130bab Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Wed, 15 Apr 2026 18:20:42 +0000 Subject: [PATCH 235/335] Added PPLR stuff --- src/quantem/core/ml/{ => models}/kplanes.py | 11 ++++++++++- src/quantem/core/ml/models/model_base.py | 13 +++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) rename src/quantem/core/ml/{ => models}/kplanes.py (97%) create mode 100644 src/quantem/core/ml/models/model_base.py diff --git a/src/quantem/core/ml/kplanes.py b/src/quantem/core/ml/models/kplanes.py similarity index 97% rename from src/quantem/core/ml/kplanes.py rename to src/quantem/core/ml/models/kplanes.py index 52a4adfc..6964415c 100644 --- a/src/quantem/core/ml/kplanes.py +++ b/src/quantem/core/ml/models/kplanes.py @@ -9,6 +9,8 @@ import torch.nn.functional as F from torch import nn +from .model_base import PPLR + """ K-planes utility functions """ @@ -150,7 +152,7 @@ def interpolate_ms_features( """ K-planes Model """ -class KPlanes(nn.Module): +class KPlanes(nn.Module, PPLR): def __init__( self, @@ -226,7 +228,14 @@ def forward( pts: torch.Tensor, ): return self.get_densities(pts) + + def get_optimization_parameters(self) -> Dict[str, list[torch.nn.Parameter]]: + return [ + {"params": } + ] + + def get_params(self) -> dict[str, list[torch.nn.Parameter]]: return { "grids": [p for grid in self.grids for p in grid], # flatten ParameterLists diff --git a/src/quantem/core/ml/models/model_base.py b/src/quantem/core/ml/models/model_base.py new file mode 100644 index 00000000..b40a13f8 --- /dev/null +++ b/src/quantem/core/ml/models/model_base.py @@ -0,0 +1,13 @@ +from abc import ABC, abstractmethod +from typing import Dict + +import torch + + +class PPLR(ABC): + """ + Abstract base class for models that require multi-scale parameter optimization. + """ + @abstractmethod + def get_optimization_parameters(self) -> Dict[str, list[torch.nn.Parameter]]: + pass \ No newline at end of file From 5cf3d876f535a6ff9635444669409d486209e091 Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Wed, 15 Apr 2026 22:32:38 +0000 Subject: [PATCH 236/335] updating docstrings and examples in show2d, linescan for 3d --- src/quantem/core/visualization/line_scan.py | 7 +- .../core/visualization/visualization.py | 128 ++++++++---------- 2 files changed, 60 insertions(+), 75 deletions(-) diff --git a/src/quantem/core/visualization/line_scan.py b/src/quantem/core/visualization/line_scan.py index 6d22beb2..b154558b 100644 --- a/src/quantem/core/visualization/line_scan.py +++ b/src/quantem/core/visualization/line_scan.py @@ -9,6 +9,7 @@ # TODO update sampling to allow for 3D and to plot with appropriate units along xy/z +# TODO proper normalization for line going through edges/corner with finite linewidth def linescan( image: np.ndarray, center: tuple[int, int] | None = None, @@ -234,11 +235,13 @@ def _show_linescan( if profile.ndim == 1: ax0.plot(positions, profile, linewidth=2) + title = "Image" else: # For 3D input, show as image # im = ax0.imshow(profile, aspect="equal", origin="upper") - im = ax0.imshow(profile, aspect="auto", origin="upper") + im = ax0.matshow(profile, aspect="auto", origin="upper") plt.colorbar(im, ax=ax0) + title = "Mean of depth slices" ax0.set_xlabel(f"Position ({sampling_units})") ax0.set_ylabel("Intensity" if profile.ndim == 1 else "Depth (pixels)") @@ -259,7 +262,7 @@ def _show_linescan( ax1.set_xlim(0, image.shape[1] - 1) ax1.set_ylim(image.shape[0] - 1, 0) - ax1.set_title("Image with Line Scan") + ax1.set_title(title) plt.tight_layout() return fig, (ax0, ax1) diff --git a/src/quantem/core/visualization/visualization.py b/src/quantem/core/visualization/visualization.py index 9bccbf86..77f647b4 100644 --- a/src/quantem/core/visualization/visualization.py +++ b/src/quantem/core/visualization/visualization.py @@ -36,12 +36,35 @@ ArrayLike: TypeAlias = Union[NDArray, torch.Tensor, "Dataset2d"] # union required here +# --- show_2d / grid broadcast inputs --- +# There might be a cleaner way to do this, but better to have it here than in the functions +NormInputCell: TypeAlias = NormalizationConfig | ShowParams.Norm | dict | str +Show2dNormInput: TypeAlias = ( + NormInputCell + | None + | Sequence[NormInputCell] + | Sequence[Sequence[NormInputCell]] +) + +ScalebarInputCell: TypeAlias = ScalebarConfig | ShowParams.Scalebar | dict | bool | None +Show2dScalebarInput: TypeAlias = ( + ScalebarConfig + | ShowParams.Scalebar + | dict + | bool + | None + | Sequence[ScalebarInputCell] + | Sequence[Sequence[ScalebarInputCell]] +) + +CmapType: TypeAlias = str | colors.Colormap + def _show_2d_array( array: NDArray, *, - norm: NormalizationConfig | ShowParams.Norm | dict | str | None = None, - scalebar: ScalebarConfig | ShowParams.Scalebar | dict | bool | None = None, + norm: NormInputCell | None = None, + scalebar: ScalebarInputCell = None, cmap: str | colors.Colormap = "gray", chroma_boost: float = 1.0, cbar: bool = False, @@ -51,11 +74,9 @@ def _show_2d_array( show_ticks: bool = False, **kwargs: Any, ) -> tuple[Any, Any]: - """Display a 2D array as an image with optional colorbar and scalebar. + """Render a single 2D array (real or complex) with optional colorbar/scalebar. - This function visualizes a 2D array, handling both real and complex data. - For complex data, it displays amplitude and phase information using a - perceptually-uniform color representation. + Complex data uses amplitude + phase in a perceptually uniform RGB encoding. Parameters ---------- @@ -174,8 +195,8 @@ def _show_2d_array( def _show_2d_combined( list_of_arrays: Sequence[NDArray], *, - norm: NormalizationConfig | ShowParams.Norm | dict | str | None = None, - scalebar: ScalebarConfig | ShowParams.Scalebar | dict | bool | None = None, + norm: NormInputCell | None = None, + scalebar: ScalebarInputCell = None, chroma_boost: float = 1.0, cbar: bool = False, figax: tuple[Any, Any] | None = None, @@ -184,7 +205,7 @@ def _show_2d_combined( show_ticks: bool = False, **kwargs: Any, ) -> tuple[Any, Any]: - """Display multiple 2D arrays as a single combined image. + """Fuse multiple 2D arrays into one RGB panel (``show_2d(..., combine_images=True)``). This function takes a list of 2D arrays and creates a single visualization where each array is assigned a unique color, and their amplitudes determine @@ -401,24 +422,9 @@ def _norm_show_args( def _normalize_show_args_to_grid( shape: tuple[int, int], - norm: NormalizationConfig - | ShowParams.Norm - | dict - | str - | Sequence[NormalizationConfig | ShowParams.Norm | dict | str] - | Sequence[Sequence[NormalizationConfig | ShowParams.Norm | dict | str]] - | None = None, - scalebar: ScalebarConfig - | ShowParams.Scalebar - | dict - | bool - | Sequence[ScalebarConfig | ShowParams.Scalebar | dict | bool | None] - | Sequence[Sequence[ScalebarConfig | ShowParams.Scalebar | dict | bool | None]] - | None = None, - cmap: str - | colors.Colormap - | Sequence[str | colors.Colormap] - | Sequence[Sequence[str | colors.Colormap]] = "gray", + norm: Show2dNormInput = None, + scalebar: Show2dScalebarInput = None, + cmap: CmapType | Sequence[CmapType] | Sequence[Sequence[CmapType]] = "gray", cbar: bool | Sequence[bool] | Sequence[Sequence[bool]] = False, title: str | Sequence[str] | Sequence[Sequence[str]] | None = None, chroma_boost: float | Sequence[float] = 1.0, @@ -451,30 +457,12 @@ def _normalize_show_args_to_grid( return args -# the type hinting is a bit of a mess, but not sure how to improve it def show_2d( arrays: ArrayLike | Sequence[ArrayLike] | Sequence[Sequence[ArrayLike]], *, - norm: ( - NormalizationConfig - | ShowParams.Norm - | dict - | str - | Sequence[NormalizationConfig | ShowParams.Norm | dict | str] - | Sequence[Sequence[NormalizationConfig | ShowParams.Norm | dict | str]] - | None - ) = None, - scalebar: ScalebarConfig - | ShowParams.Scalebar - | dict - | bool - | Sequence[ScalebarConfig | ShowParams.Scalebar | dict | bool | None] - | Sequence[Sequence[ScalebarConfig | ShowParams.Scalebar | dict | bool | None]] - | None = None, - cmap: str - | colors.Colormap - | Sequence[str | colors.Colormap] - | Sequence[Sequence[str | colors.Colormap]] = "gray", + norm: Show2dNormInput = None, + scalebar: Show2dScalebarInput = None, + cmap: CmapType | Sequence[CmapType] | Sequence[Sequence[CmapType]] = "gray", cbar: bool | Sequence[bool] | Sequence[Sequence[bool]] = False, title: str | Sequence[str] | Sequence[Sequence[str]] | None = None, figax: tuple[Any, Any] | None = None, @@ -484,12 +472,8 @@ def show_2d( ) -> tuple[Any, Any]: """Display one or more 2D arrays in a grid layout. - This is the main visualization function that can display a single array, - a list of arrays, or a grid of arrays. It supports both individual and - combined visualization modes. - - The display arguments, i.e. everything except figax and axsize, can be given as single values - or as sequences that will be broadcasted to the grid shape defined by the input arrays. + ``norm``, ``scalebar``, ``cmap``, ``cbar``, and ``title`` may be scalars or nested + sequences broadcast to the panel grid. Parameters ---------- @@ -558,35 +542,33 @@ def show_2d( Examples -------- - Display a single image: + Typed normalization and scale bar (same information as str/dict forms): >>> import numpy as np - >>> from quantem.core.visualization import show_2d - >>> image = np.random.rand(256, 256) - >>> fig, ax = show_2d(image, axsize=(3, 3)) - - Display a 2-row grid with titles and a scale bar: - - >>> image_rows = [[np.random.rand(128, 128) for _ in range(3)] for _ in range(2)] - >>> label_rows = [["BF", "ADF", "ABF"], ["HAADF", "DPC", "SSB"]] - >>> fig, axs = show_2d( - ... image_rows, - ... title=label_rows, - ... cmap="gray", - ... scalebar={"sampling": 0.5, "units": "Å"}, + >>> from quantem.core.visualization import show_2d, ShowParams + >>> img = np.random.rand(128, 128) + >>> fig, ax = show_2d( + ... img, + ... norm=ShowParams.Norm.log_auto(), + ... scalebar=ShowParams.Scalebar(sampling=0.5, units="Å"), + ... cbar=True, ... ) - Display diffraction patterns with log normalization, colorbar, and scale bar: + Preset strings and dicts: - >>> dps = [np.random.rand(256, 256) ** 3 for _ in range(3)] >>> fig, axs = show_2d( - ... dps, - ... title=["Zone axis", "Off-axis", "CBED"], + ... [np.random.rand(64, 64) ** 3 for _ in range(3)], ... norm="log_auto", - ... cbar=True, ... cmap="turbo", + ... title=["a", "b", "c"], ... scalebar={"sampling": 0.02, "units": "1/Å"}, ... ) + + Multi-row grid with titles: + + >>> rows = [[np.random.rand(48, 48) for _ in range(3)] for _ in range(2)] + >>> labels = [["BF", "ADF", "ABF"], ["HAADF", "DPC", "SSB"]] + >>> fig, axs = show_2d(rows, title=labels, cmap="gray", scalebar={"sampling": 0.5, "units": "Å"}) """ arrays = to_cpu(arrays) grid = _normalize_show_input_to_grid(arrays) From 9ac27a994c8b0d401cb5a2842bbaab6effe2c085 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Wed, 15 Apr 2026 23:54:57 +0000 Subject: [PATCH 237/335] object_models optimization setting is working well. Only thing that needs to be overloaded is set_optimizer for PPLR cases --- src/quantem/core/ml/models/kplanes.py | 26 ++--------- src/quantem/core/ml/models/model_base.py | 16 ++++++- src/quantem/tomography/object_models.py | 58 +++++++++++++++++++++++- src/quantem/tomography/tomography_opt.py | 4 +- 4 files changed, 79 insertions(+), 25 deletions(-) diff --git a/src/quantem/core/ml/models/kplanes.py b/src/quantem/core/ml/models/kplanes.py index 6964415c..954e383d 100644 --- a/src/quantem/core/ml/models/kplanes.py +++ b/src/quantem/core/ml/models/kplanes.py @@ -2,6 +2,7 @@ Tensor Decomposition Methods for INR-based reconstructions """ +import itertools from typing import Any, Callable, Optional, Sequence import tinycudann as tcnn @@ -209,8 +210,6 @@ def __init__( }, ) - - def get_densities(self, coords: torch.Tensor): """Computes and returns densities""" @@ -229,29 +228,14 @@ def forward( ): return self.get_densities(pts) - - def get_optimization_parameters(self) -> Dict[str, list[torch.nn.Parameter]]: - return [ - {"params": } - ] - - def get_params(self) -> dict[str, list[torch.nn.Parameter]]: return { "grids": [p for grid in self.grids for p in grid], # flatten ParameterLists "sigma_net": list(self.sigma_net.parameters()), } - - def set_optimizer(self, optimizer_params: dict[str, Any]): - - self._grids.set_optimizer(optimizer_params["grids"]) - self._sigmanet.set_optimizer(optimizer_params["sigmanet"]) - + @property + def param_keys(self) -> list[str]: + return ["grids", "sigma_net"] - - def get_params(self) -> dict[str, list[torch.nn.Parameter]]: - return { - "grids": self._grids.params # flatten ParameterLists - "sigma_net": self._sigma_net.params - } \ No newline at end of file + \ No newline at end of file diff --git a/src/quantem/core/ml/models/model_base.py b/src/quantem/core/ml/models/model_base.py index b40a13f8..42bee71c 100644 --- a/src/quantem/core/ml/models/model_base.py +++ b/src/quantem/core/ml/models/model_base.py @@ -9,5 +9,19 @@ class PPLR(ABC): Abstract base class for models that require multi-scale parameter optimization. """ @abstractmethod - def get_optimization_parameters(self) -> Dict[str, list[torch.nn.Parameter]]: + def get_params(self) -> Dict[str, list[torch.nn.Parameter]]: + """ + This abstract method should return a dictionary of parameters based on a key. + + For example if your nn.Module has multiple optimizable parameter groups, + you can return a dictionary with the keys "grids" and "sigma_net" (KPlanes example). + """ + pass + + @property + @abstractmethod + def param_keys(self) -> list[str]: + """ + This abstract property should return a list of available parameter keys. + """ pass \ No newline at end of file diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index c5eb2406..6c21c410 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -10,10 +10,12 @@ from tqdm.auto import tqdm from quantem.core.io.serialize import AutoSerialize +from quantem.core.ml import OptimizerParams from quantem.core.ml.constraints import BaseConstraints, Constraints from quantem.core.ml.ddp import DDPMixin from quantem.core.ml.loss_functions import get_loss_module -from quantem.core.ml.optimizer_mixin import OptimizerMixin +from quantem.core.ml.models.model_base import PPLR +from quantem.core.ml.optimizer_mixin import OptimizerMixin, OptimizerType from quantem.core.utils.rng import RNGMixin from quantem.tomography.dataset_models import TomographyINRPretrainDataset @@ -552,11 +554,65 @@ def apply_hard_constraints(self, pred: torch.Tensor) -> torch.Tensor: # --- Optimization Parameters --- @property def params(self) -> Generator[torch.nn.Parameter, None, None]: + """ + Returns the optimization parameters, here we also check if PPLR is used and return the appropriate parameters. + """ + return self.model.parameters() # type: ignore[attr-defined] def get_optimization_parameters(self) -> list[nn.Parameter]: + + if isinstance(self.model, PPLR): + + # DEBUG + for key, value in self.optimizer_params.items(): + print(key, value) + return [ + { + "params": self.model.get_params()[key], + **self.optimizer_params[key].params(), + } + for key in self.model.param_keys + ] return list(self.params) + + # --- DDP Mixin Overloads in the case of PPLR --- + + @property + def optimizer_params(self) -> OptimizerType | dict[str, OptimizerType]: + """Get the optimizer parameters.""" + return self._optimizer_params + + @optimizer_params.setter + def optimizer_params(self, params: OptimizerType | dict[str, OptimizerType] | dict[str, Any]): + """Set the optimizer parameters.""" + if isinstance(params, OptimizerType): + self._optimizer_params = params + return + if isinstance(self.model, PPLR): + if not isinstance(params, dict): + raise TypeError(f"optimizer parameters must be a dict for PPLR, got {type(params)}") + + object_params = params + + if set(object_params.keys()) != set(self.model.param_keys): + raise ValueError(f"optimizer parameters keys must match PPLR param_keys, got {object_params.keys()} != {self.model.param_keys}") + + params = {} + for key, value in object_params.items(): + if isinstance(value, dict): + params[key] = OptimizerParams.parse_dict(d=value) + elif isinstance(value, OptimizerType): + params[key] = value + else: + raise TypeError(f"optimizer parameters must be a dict or OptimizerType, got {type(value)}") + + self._optimizer_params = params + else: + raise TypeError(f"optimizer parameters must be a dict for non-PPLR, got {type(params)}") + + # Pretraining @property def pretrained_weights(self) -> dict[str, torch.Tensor]: diff --git a/src/quantem/tomography/tomography_opt.py b/src/quantem/tomography/tomography_opt.py index 16c846ab..0f8c44be 100644 --- a/src/quantem/tomography/tomography_opt.py +++ b/src/quantem/tomography/tomography_opt.py @@ -52,8 +52,8 @@ def optimizer_params(self, d: dict[str, OptimizerType] | dict[str, dict]): if k not in targets: raise ValueError(f"Unknown optimization key: {k}") - if not isinstance(v, OptimizerType): - v = OptimizerParams.parse_dict(v) + # if not isinstance(v, OptimizerType): + # v = OptimizerParams.parse_dict(v) targets[k].optimizer_params = v From 5f40d5aaef38484e7b6769ea2de4c0d25c26cf91 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Thu, 16 Apr 2026 00:13:03 +0000 Subject: [PATCH 238/335] Optimizing, set_optimizer is just default to Adam now, probably need to do the matching in set_optimizer instead of parsing in optimizer_params maybe? --- src/quantem/tomography/object_models.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 6c21c410..01698769 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -564,9 +564,6 @@ def get_optimization_parameters(self) -> list[nn.Parameter]: if isinstance(self.model, PPLR): - # DEBUG - for key, value in self.optimizer_params.items(): - print(key, value) return [ { "params": self.model.get_params()[key], @@ -612,6 +609,26 @@ def optimizer_params(self, params: OptimizerType | dict[str, OptimizerType] | di else: raise TypeError(f"optimizer parameters must be a dict for non-PPLR, got {type(params)}") + def set_optimizer(self, opt_params: OptimizerType | dict | None = None) -> None: + """ + Set the optimizer for this model. + Currently supports single LR for all parameters, TODO allow for per parameter LRs by + updating get_optimization_parameters to return a list of parameters and their LRs. + """ + if not isinstance(self.model, PPLR): + super().set_optimizer(opt_params) + return + + if opt_params is not None: + self.optimizer_params = opt_params + + if not self._optimizer_params: + self._optimizer = None + return + + params = self.get_optimization_parameters() + + self._optimizer = torch.optim.Adam(params) # Pretraining @property From 7f51dc50e5233ce5437da4d2b1de25f8505a29ef Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Thu, 16 Apr 2026 01:36:54 +0000 Subject: [PATCH 239/335] KPlanes Tilted claude implementation, need to talk to Corneel. Things to check: Look at object_models.py and see how the optimizer matching should be handled. It seems like set_optimizers doesn't really do what it's supposed to do. --- src/quantem/core/ml/models/kplanes.py | 535 +++++++++++++++++++++++--- 1 file changed, 482 insertions(+), 53 deletions(-) diff --git a/src/quantem/core/ml/models/kplanes.py b/src/quantem/core/ml/models/kplanes.py index 954e383d..7e206983 100644 --- a/src/quantem/core/ml/models/kplanes.py +++ b/src/quantem/core/ml/models/kplanes.py @@ -3,7 +3,8 @@ """ import itertools -from typing import Any, Callable, Optional, Sequence +import math +from typing import Callable, Optional, Sequence import tinycudann as tcnn import torch @@ -131,28 +132,27 @@ def query_planes( def interpolate_ms_features( pts: torch.Tensor, - ms_grids: nn.ModuleList, + ms_grids: nn.ParameterList, ) -> torch.Tensor: - coo_combs = list(itertools.combinations(range(3), 2)) # [(0,1), (0,2), (1,2)] - multi_scale_interp = [] - - for grid in ms_grids: - interp_space = 1. - for ci, coo_comb in enumerate(coo_combs): - feature_dim = grid[ci].shape[1] - interp_out_plane = ( - grid_sample_wrapper(grid[ci], pts[..., coo_comb]) - .view(-1, feature_dim) - ) - interp_space = interp_space * interp_out_plane - multi_scale_interp.append(interp_space) + mat_mode = [[0, 1], [0, 2], [1, 2]] + coord_plane = torch.stack([ + pts[:, mat_mode[0]], + pts[:, mat_mode[1]], + pts[:, mat_mode[2]], + ]).view(3, -1, 1, 2) - return torch.cat(multi_scale_interp, dim=-1) + per_scale = [] + for plane_coef in ms_grids: + C = plane_coef.shape[1] + feats = F.grid_sample( + plane_coef, coord_plane, align_corners=True, mode="bilinear", padding_mode="border" + ).reshape(3, C, -1) + fused = feats[0] * feats[1] * feats[2] + per_scale.append(fused.T) + + return torch.cat(per_scale, dim=-1) -""" -K-planes Model -""" class KPlanes(nn.Module, PPLR): def __init__( @@ -165,6 +165,10 @@ def __init__( multiscale_res_multipliers: Optional[Sequence[int]] = None, concat_features: bool = True, density_activation: Callable = lambda x: F.softplus(x - 1), + # Hybrid MLP parameters + use_hybrid_mlp: bool = False, + hybrid_hidden_dim: int = 64, + hybrid_num_layers: int = 2, ): """ Assume coords are [-1, 1] in each dimension. @@ -179,40 +183,55 @@ def __init__( self.concat_features = concat_features self.density_activation = density_activation - - # Initialize planes self.grids = nn.ParameterList() self.feature_dim = 0 - - # Resolution pyramid for res_mult in self.multiscale_res_multipliers: - scaled_res = [r * res_mult for r in self.resolution] - gp = init_planes( - in_dim=self.input_coords_dims, - out_dim=self.M_features, - resolution=scaled_res, + scaled_res = [int(r * res_mult) for r in self.resolution] + plane = nn.Parameter(torch.empty(3, self.M_features, scaled_res[1], scaled_res[0])) + nn.init.uniform_(plane, 0.1, 0.5) + self.grids.append(plane) + self.feature_dim += self.M_features + + # Network head + if use_hybrid_mlp: + hybrid_hidden_dim = int(hybrid_hidden_dim) + hybrid_num_layers = int(hybrid_num_layers) + if hybrid_hidden_dim <= 0: + raise ValueError(f"hybrid_hidden_dim must be >= 1, got {hybrid_hidden_dim}") + if hybrid_num_layers <= 0: + raise ValueError(f"hybrid_num_layers must be >= 1, got {hybrid_num_layers}") + + factory = {} # add dtype/device kwargs here if needed + layers = [] + in_dim = self.feature_dim + for _ in range(hybrid_num_layers): + lin = nn.Linear(in_dim, hybrid_hidden_dim, **factory) + nn.init.kaiming_uniform_(lin.weight, a=0.0, nonlinearity="relu") + nn.init.zeros_(lin.bias) + layers.append(lin) + layers.append(nn.ReLU(inplace=True)) + in_dim = hybrid_hidden_dim + + out = nn.Linear(in_dim, 1, bias=True, **factory) + nn.init.normal_(out.weight, std=0.01) + nn.init.zeros_(out.bias) + layers.append(out) + self.sigma_net = nn.Sequential(*layers) + else: + self.sigma_net = tcnn.Network( + n_input_dims=self.feature_dim, + n_output_dims=1, + network_config={ + "otype": "CutlassMLP", + "activation": "None", + "output_activation": "None", + "n_neurons": 128, + "n_hidden_layers": 0, + }, ) - - self.feature_dim += gp[-1].shape[1] - self.grids.append(gp) - - - # Linear net - self.sigma_net = tcnn.Network( - n_input_dims=self.feature_dim, - n_output_dims=1, - network_config={ - "otype": "CutlassMLP", - "activation": "None", - "output_activation": "None", - "n_neurons": 128, - "n_hidden_layers": 0, - }, - ) def get_densities(self, coords: torch.Tensor): """Computes and returns densities""" - pts = coords.reshape(-1, 3) features = interpolate_ms_features( pts=pts, @@ -222,15 +241,12 @@ def get_densities(self, coords: torch.Tensor): density = self.density_activation(density_before_activation) return density - def forward( - self, - pts: torch.Tensor, - ): + def forward(self, pts: torch.Tensor): return self.get_densities(pts) - + def get_params(self) -> dict[str, list[torch.nn.Parameter]]: return { - "grids": [p for grid in self.grids for p in grid], # flatten ParameterLists + "grids": list(self.grids.parameters()), "sigma_net": list(self.sigma_net.parameters()), } @@ -238,4 +254,417 @@ def get_params(self) -> dict[str, list[torch.nn.Parameter]]: def param_keys(self) -> list[str]: return ["grids", "sigma_net"] - \ No newline at end of file + +# --- Tilted KPlanes --- + +# --------------------------------------------------------------------------- +# SO(3) quaternion parameter module +# --------------------------------------------------------------------------- + +class SO3Param(nn.Module): + """ + Stores T unit quaternions as learnable parameters in R^4 and normalises + them on every call to `as_matrix()`. + + Quaternion convention: [x, y, z, w] (scalar-last, same as scipy). + + Initialisation + -------------- + "random" – uniform sampling over SO(3) via Shoemake's method. + "identity" – all rotations start as the identity (good for fine-tuning). + """ + + def __init__(self, T: int, init: str = "random"): + super().__init__() + if T < 1: + raise ValueError(f"T must be >= 1, got {T}") + quats = self._init_quaternions(T, init) # (T, 4) + self.quats = nn.Parameter(quats) + + # ------------------------------------------------------------------ + # Initialisers + # ------------------------------------------------------------------ + + @staticmethod + def _shoemake_sample(T: int) -> torch.Tensor: + """Uniform SO(3) sampling via Shoemake (1992). Returns (T, 4) [x,y,z,w].""" + u = torch.rand(T, 3) + sqrt1_u0 = torch.sqrt(1.0 - u[:, 0]) + sqrt_u0 = torch.sqrt(u[:, 0]) + two_pi = 2.0 * math.pi + x = sqrt1_u0 * torch.sin(two_pi * u[:, 1]) + y = sqrt1_u0 * torch.cos(two_pi * u[:, 1]) + z = sqrt_u0 * torch.sin(two_pi * u[:, 2]) + w = sqrt_u0 * torch.cos(two_pi * u[:, 2]) + return torch.stack([x, y, z, w], dim=-1) # (T, 4) + + @staticmethod + def _identity(T: int) -> torch.Tensor: + """All-identity rotations: [0,0,0,1] * T.""" + q = torch.zeros(T, 4) + q[:, 3] = 1.0 + return q + + @classmethod + def _init_quaternions(cls, T: int, init: str) -> torch.Tensor: + if init == "random": + return cls._shoemake_sample(T) + elif init == "identity": + return cls._identity(T) + else: + raise ValueError(f"Unknown init '{init}'; choose 'random' or 'identity'.") + + # ------------------------------------------------------------------ + # Forward helpers + # ------------------------------------------------------------------ + + def normalized(self) -> torch.Tensor: + """Returns (T, 4) unit quaternions.""" + return F.normalize(self.quats, p=2, dim=-1) + + def as_matrix(self) -> torch.Tensor: + """ + Converts the T stored quaternions to (T, 3, 3) rotation matrices. + + Uses the standard formula; no trig, just multiplications. + """ + q = self.normalized() # (T, 4) [x, y, z, w] + x, y, z, w = q.unbind(dim=-1) # each (T,) + + # Precompute products + xx, yy, zz = x*x, y*y, z*z + xy, xz, yz = x*y, x*z, y*z + wx, wy, wz = w*x, w*y, w*z + + # Row-major: R[i,j] + R = torch.stack([ + 1 - 2*(yy + zz), 2*(xy - wz), 2*(xz + wy), + 2*(xy + wz), 1 - 2*(xx + zz), 2*(yz - wx), + 2*(xz - wy), 2*(yz + wx), 1 - 2*(xx + yy), + ], dim=-1).reshape(-1, 3, 3) # (T, 3, 3) + + return R + + def extra_repr(self) -> str: + return f"T={self.quats.shape[0]}" +def interpolate_ms_features_tilted( + pts: torch.Tensor, # (B, 3) + ms_grids: nn.ParameterList, # each grid: (3*T, C, H, W) + rotation_matrices: torch.Tensor, # (T, 3, 3) +) -> torch.Tensor: + """ + Multi-scale, multi-rotation K-Planes feature interpolation. + + For each of the T rotations: + 1. Rotate pts -> (B, 3) + 2. Project to XY / XZ / YZ planes -> 3 × (B, 2) grids of coords + 3. Bilinear interpolation on the corresponding planes + 4. Hadamard product across the 3 planes -> (B, C) + + Across T transforms, outputs are *concatenated* (not summed), so each + rotation owns a disjoint slice of the feature dimension. Across scales, + outputs are also concatenated, matching the base KPlanes behaviour. + + Returns + ------- + features : (B, C * T * num_scales) + """ + T = rotation_matrices.shape[0] + B = pts.shape[0] + + # Rotate all points by all T rotation matrices at once. + # pts: (B, 3), R: (T, 3, 3) -> rotated: (T, B, 3) + rotated = torch.einsum("tij,bj->tbi", rotation_matrices, pts) + + per_scale_features = [] + + for plane_coef in ms_grids: + # plane_coef shape: (3*T, C, H, W) + C = plane_coef.shape[1] + + # Build the (3*T, B, 1, 2) coordinate tensor for grid_sample. + # For transform t, we need coords for planes XY, XZ, YZ. + # grid_sample expects coords in [-1, 1] with shape (N, Hout, Wout, 2). + all_plane_coords = [] + for t in range(T): + rp = rotated[t] # (B, 3) + all_plane_coords.append(rp[:, [0, 1]]) # XY (B, 2) + all_plane_coords.append(rp[:, [2, 0]]) # ZX (B, 2) matches ki + all_plane_coords.append(rp[:, [1, 2]]) # YZ (B, 2) matches jk + + # Stack -> (3*T, B, 2) -> (3*T, B, 1, 2) for grid_sample + coord_tensor = torch.stack(all_plane_coords, dim=0).unsqueeze(2) + # coord_tensor: (3*T, B, 1, 2) + + # grid_sample: input (N, C, H, W), grid (N, Hout, Wout, 2) + sampled = F.grid_sample( + plane_coef, # (3*T, C, H, W) + coord_tensor, # (3*T, B, 1, 2) + align_corners=True, + mode="bilinear", + padding_mode="border", + ) # -> (3*T, C, B, 1) + sampled = sampled.squeeze(-1) # (3*T, C, B) + + # Hadamard product within each transform group (3 planes per transform). + transform_features = [] + for t in range(T): + p_xy = sampled[3*t + 0] # (C, B) + p_zx = sampled[3*t + 1] + p_yz = sampled[3*t + 2] + fused = (p_xy * p_zx * p_yz).T # (B, C) + transform_features.append(fused) + + # Concatenate across transforms -> (B, C*T) + per_scale_features.append(torch.cat(transform_features, dim=-1)) + + # Concatenate across scales -> (B, C*T*num_scales) + return torch.cat(per_scale_features, dim=-1) + +# --------------------------------------------------------------------------- +# KPlanesTILTED +# --------------------------------------------------------------------------- + +class KPlanesTILTED(KPlanes): + """ + K-Planes with T learned SO(3) rotations (TILTED). + + Inherits KPlanes for the sigma_net, density_activation, and get_params + interface. Overrides: + * __init__ – replaces the axis-aligned grids with (3*T)-plane grids + and adds SO3Param. + * get_densities – calls the TILTED interpolation instead. + * get_params – adds "so3" key so callers can set a separate lr. + * param_keys – updated list. + + Parameters + ---------- + M_features : int + Feature channels *per transform per scale*. Total feature_dim will + be M_features * T * len(multiscale_res_multipliers). + T : int + Number of learned rotations (TILTED-T in the paper; 4 or 8 recommended). + tau_init : str + "random" (paper default) or "identity". + tau_warmup_steps : int + If > 0, grids and sigma_net are frozen for this many steps so the + rotations can find good basins first (two-phase warm-up). + Call model.training_step() once per optimiser step. + All other args are forwarded to KPlanes. + """ + + def __init__( + self, + # Grid parameters + input_coords_dims: int = 3, + M_features: int = 32, + resolution: Sequence[int] = (200, 200, 200), + multiscale_res_multipliers: Optional[Sequence[int]] = None, + density_activation: Callable = lambda x: F.softplus(x - 1), + # TILTED parameters + T: int = 4, + tau_init: str = "random", + tau_warmup_steps: int = 0, + # Hybrid MLP parameters + use_hybrid_mlp: bool = False, + hybrid_hidden_dim: int = 64, + hybrid_num_layers: int = 2, + ): + if input_coords_dims != 3: + raise NotImplementedError("KPlanesTILTED is implemented for 3D only.") + if T < 1: + raise ValueError(f"T must be >= 1, got {T}") + + multiscale_res_multipliers = list(multiscale_res_multipliers or [1]) + num_scales = len(multiscale_res_multipliers) + + # Total feature dim seen by the MLP head. + # Each scale contributes M_features * T channels. + feature_dim = M_features * T * num_scales + + # Call KPlanes.__init__ with grid_dimensions=2 so it builds sigma_net + # correctly; we immediately replace self.grids below. + super().__init__( + grid_dimensions=2, + input_coords_dims=3, + M_features=M_features, # base class stores this + resolution=resolution, + multiscale_res_multipliers=multiscale_res_multipliers, + concat_features=True, + density_activation=density_activation, + use_hybrid_mlp=use_hybrid_mlp, + hybrid_hidden_dim=hybrid_hidden_dim, + hybrid_num_layers=hybrid_num_layers, + ) + # KPlanes.__init__ built grids with shape (3, M, H, W) and feature_dim + # = M * num_scales. We rebuild them for the TILTED shape. + + self.T = T + self.tau_warmup_steps = tau_warmup_steps + self._global_step: int = 0 + + # ---- Rebuild grids: (3*T, M_features, H, W) per scale ---- + self.grids = nn.ParameterList() + for res_mult in multiscale_res_multipliers: + scaled_res = [int(r * res_mult) for r in resolution] + plane = nn.Parameter( + torch.empty(3 * T, M_features, scaled_res[1], scaled_res[0]) + ) + nn.init.uniform_(plane, 0.1, 0.5) + self.grids.append(plane) + + # ---- Rebuild sigma_net with the correct feature_dim ---- + # KPlanes built sigma_net with self.feature_dim (= M * num_scales), + # which is wrong for T > 1. Rebuild here. + self.feature_dim = feature_dim + self._build_sigma_net(use_hybrid_mlp, hybrid_hidden_dim, hybrid_num_layers) + + # ---- Learnable rotations ---- + self.so3 = SO3Param(T, init=tau_init) + + # ------------------------------------------------------------------ + # Internal helpers + # ------------------------------------------------------------------ + + def _build_sigma_net( + self, + use_hybrid_mlp: bool, + hybrid_hidden_dim: int, + hybrid_num_layers: int, + ) -> None: + """Rebuild sigma_net for self.feature_dim (called after grids are set).""" + if use_hybrid_mlp: + layers = [] + in_dim = self.feature_dim + for _ in range(hybrid_num_layers): + lin = nn.Linear(in_dim, hybrid_hidden_dim) + nn.init.kaiming_uniform_(lin.weight, a=0.0, nonlinearity="relu") + nn.init.zeros_(lin.bias) + layers.append(lin) + layers.append(nn.ReLU(inplace=True)) + in_dim = hybrid_hidden_dim + out = nn.Linear(in_dim, 1, bias=True) + nn.init.normal_(out.weight, std=0.01) + nn.init.zeros_(out.bias) + layers.append(out) + self.sigma_net = nn.Sequential(*layers) + else: + self.sigma_net = tcnn.Network( + n_input_dims=self.feature_dim, + n_output_dims=1, + network_config={ + "otype": "CutlassMLP", + "activation": "None", + "output_activation": "None", + "n_neurons": 128, + "n_hidden_layers": 0, + }, + ) + + # ------------------------------------------------------------------ + # Warm-up bookkeeping + # ------------------------------------------------------------------ + + def training_step(self) -> None: + """ + Call once per optimiser step to advance the internal counter. + + During the first `tau_warmup_steps` iterations, grids and sigma_net + have their gradients zeroed after the backward pass so only the SO(3) + parameters update. This is the lightweight version of two-phase + optimisation from the paper. + """ + self._global_step += 1 + + def _in_warmup(self) -> bool: + return self.tau_warmup_steps > 0 and self._global_step < self.tau_warmup_steps + + def zero_non_tau_grads(self) -> None: + """ + Call after loss.backward() and before optimizer.step() when you want + to implement the rotation warm-up manually. Alternatively just check + model.in_warmup and configure your optimizer accordingly. + """ + if self._in_warmup(): + for p in self.grids.parameters(): + if p.grad is not None: + p.grad.zero_() + for p in self.sigma_net.parameters(): + if p.grad is not None: + p.grad.zero_() + + @property + def in_warmup(self) -> bool: + return self._in_warmup() + + # ------------------------------------------------------------------ + # Core forward + # ------------------------------------------------------------------ + + def get_densities(self, coords: torch.Tensor) -> torch.Tensor: + pts = coords.reshape(-1, 3) + R = self.so3.as_matrix() # (T, 3, 3) + features = interpolate_ms_features_tilted( + pts=pts, + ms_grids=self.grids, + rotation_matrices=R, + ) + density_before_activation = self.sigma_net(features) + return self.density_activation(density_before_activation) + + def forward(self, pts: torch.Tensor) -> torch.Tensor: + return self.get_densities(pts) + + # ------------------------------------------------------------------ + # Parameter groups + # ------------------------------------------------------------------ + + def get_params(self) -> dict[str, list[nn.Parameter]]: + return { + "grids": list(self.grids.parameters()), + "sigma_net": list(self.sigma_net.parameters()), + "so3": list(self.so3.parameters()), + } + + @property + def param_keys(self) -> list[str]: + return ["grids", "sigma_net", "so3"] + + + # ------------------------------------------------------------------ + # Two-phase helper: extract tau for phase-2 initialisation + # ------------------------------------------------------------------ + + def extract_tau_state(self) -> torch.Tensor: + """ + Returns the current quaternion tensor (detached copy) so it can be + used to initialise a larger phase-2 model via `load_tau_state`. + """ + return self.so3.quats.detach().clone() + + def load_tau_state(self, quats: torch.Tensor) -> None: + """ + Load pre-trained quaternions (e.g. from a bottleneck phase-1 model). + + quats : (T, 4) tensor, will be normalised internally. + """ + if quats.shape != self.so3.quats.shape: + raise ValueError( + f"Shape mismatch: got {quats.shape}, " + f"expected {self.so3.quats.shape}" + ) + with torch.no_grad(): + self.so3.quats.copy_(F.normalize(quats, p=2, dim=-1)) + + # ------------------------------------------------------------------ + # Pretty print + # ------------------------------------------------------------------ + + def extra_repr(self) -> str: + return ( + f"T={self.T}, " + f"M_features={self.M_features}, " + f"feature_dim={self.feature_dim}, " + f"num_scales={len(self.multiscale_res_multipliers)}, " + f"tau_warmup_steps={self.tau_warmup_steps}" + ) \ No newline at end of file From 9752ad00cca6d38120552b2691ce7f4cc2888db4 Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Thu, 16 Apr 2026 06:51:43 +0000 Subject: [PATCH 240/335] type fixes --- src/quantem/diffractive_imaging/object_models.py | 6 +++--- src/quantem/diffractive_imaging/probe_models.py | 6 +++--- src/quantem/diffractive_imaging/ptychography_lite.py | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/quantem/diffractive_imaging/object_models.py b/src/quantem/diffractive_imaging/object_models.py index 9623e7b3..1c6fdab9 100644 --- a/src/quantem/diffractive_imaging/object_models.py +++ b/src/quantem/diffractive_imaging/object_models.py @@ -14,7 +14,7 @@ from quantem.core.io.serialize import AutoSerialize from quantem.core.ml.blocks import reset_weights from quantem.core.ml.loss_functions import get_loss_module -from quantem.core.ml.optimizer_mixin import OptimizerMixin +from quantem.core.ml.optimizer_mixin import OptimizerMixin, OptimizerType, SchedulerType from quantem.core.utils.rng import RNGMixin from quantem.core.utils.validators import ( validate_arr_gt, @@ -1050,8 +1050,8 @@ def pretrain( pretrain_target: torch.Tensor | None = None, reset: bool = False, num_iters: int = 100, - optimizer_params: dict | None = None, - scheduler_params: dict | None = None, + optimizer_params: dict | OptimizerType | None = None, + scheduler_params: dict | SchedulerType | None = None, loss_fn: Callable | str = "l2", apply_constraints: bool = False, show: bool = True, diff --git a/src/quantem/diffractive_imaging/probe_models.py b/src/quantem/diffractive_imaging/probe_models.py index e9027620..f47ea1f7 100644 --- a/src/quantem/diffractive_imaging/probe_models.py +++ b/src/quantem/diffractive_imaging/probe_models.py @@ -15,7 +15,7 @@ from quantem.core.io.serialize import AutoSerialize from quantem.core.ml.blocks import reset_weights from quantem.core.ml.loss_functions import get_loss_module -from quantem.core.ml.optimizer_mixin import OptimizerMixin +from quantem.core.ml.optimizer_mixin import OptimizerMixin, OptimizerType, SchedulerType from quantem.core.utils.rng import RNGMixin from quantem.core.utils.utils import electron_wavelength_angstrom, to_numpy from quantem.core.utils.validators import ( @@ -1359,8 +1359,8 @@ def pretrain( pretrain_target: torch.Tensor | None = None, reset: bool = False, num_iters: int = 100, - optimizer_params: dict | None = None, - scheduler_params: dict | None = None, + optimizer_params: dict | OptimizerType | None = None, + scheduler_params: dict | SchedulerType | None = None, loss_fn: Callable | str = "l2", apply_constraints: bool = False, show: bool = True, diff --git a/src/quantem/diffractive_imaging/ptychography_lite.py b/src/quantem/diffractive_imaging/ptychography_lite.py index 9fc7d039..e3837b83 100644 --- a/src/quantem/diffractive_imaging/ptychography_lite.py +++ b/src/quantem/diffractive_imaging/ptychography_lite.py @@ -373,9 +373,9 @@ def reconstruct( # type:ignore could do overloads but this is simpler... self, num_iters: int = 0, reset: bool = False, - lr_obj: float = 5e-4, + lr_obj: float = 1e-3, learn_probe: bool = True, - lr_probe: float = 5e-4, + lr_probe: float = 1e-3, batch_size: int | None = None, scheduler_type: Literal["exp", "cyclic", "plateau", "none"] = "none", scheduler_factor: float = 0.5, From af140be45ee83abd52b6f6e40a2e531406d261ef Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Thu, 16 Apr 2026 07:19:22 +0000 Subject: [PATCH 241/335] Added TV loss for PPLR models, I don't like this solution though will probably have to do TV loss computation within the model? --- src/quantem/core/ml/models/kplanes.py | 1 + src/quantem/tomography/object_models.py | 59 +++++++++++++++---------- 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/src/quantem/core/ml/models/kplanes.py b/src/quantem/core/ml/models/kplanes.py index 7e206983..dbc8f982 100644 --- a/src/quantem/core/ml/models/kplanes.py +++ b/src/quantem/core/ml/models/kplanes.py @@ -574,6 +574,7 @@ def training_step(self) -> None: parameters update. This is the lightweight version of two-phase optimisation from the paper. """ + print("Global Stepped") self._global_step += 1 def _in_warmup(self) -> bool: diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 01698769..9f3ce764 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -506,29 +506,42 @@ def apply_soft_constraints( ) -> torch.Tensor: soft_loss = torch.tensor(0.0, device=pred.device) if self.constraints.tv_vol > 0: - num_tv_samples = min(10_000, coords.shape[0]) - tv_indices = torch.randperm(coords.shape[0], device=coords.device)[:num_tv_samples] - - tv_coords = coords[tv_indices].detach().requires_grad_(True) - tv_densities_recomputed = self.model(tv_coords) - if isinstance(tv_densities_recomputed, tuple): - tv_densities_recomputed = tv_densities_recomputed[0] - - # Ensure shape is [num_samples, num_channels] - if tv_densities_recomputed.dim() == 1: - tv_densities_recomputed = tv_densities_recomputed.unsqueeze(-1) - - # Compute gradients for each channel - grad_outputs = torch.autograd.grad( - outputs=tv_densities_recomputed, - inputs=tv_coords, - grad_outputs=torch.ones_like(tv_densities_recomputed), - create_graph=True, - )[0] # Shape: [num_samples, coord_dim] - - # Compute TV loss - gradient magnitude per sample - grad_norm = torch.norm(grad_outputs, dim=1) # Shape: [num_samples] - soft_loss += self.constraints.tv_vol * grad_norm.mean() + + if isinstance(self.model, PPLR): # TODO: Temporary + for plane in self.model.grids: + # plane: (3*T, C, H, W) + # Differences along H (axis -2) and W (axis -1) + diff_h = plane[..., 1:, :] - plane[..., :-1, :] # (3*T, C, H-1, W) + diff_w = plane[..., :, 1:] - plane[..., :, :-1] # (3*T, C, H, W-1) + + + soft_loss += diff_h.pow(2).mean() + diff_w.pow(2).mean() + + soft_loss /= len(self.model.grids) + else: + num_tv_samples = min(10_000, coords.shape[0]) + tv_indices = torch.randperm(coords.shape[0], device=coords.device)[:num_tv_samples] + + tv_coords = coords[tv_indices].detach().requires_grad_(True) + tv_densities_recomputed = self.model(tv_coords) + if isinstance(tv_densities_recomputed, tuple): + tv_densities_recomputed = tv_densities_recomputed[0] + + # Ensure shape is [num_samples, num_channels] + if tv_densities_recomputed.dim() == 1: + tv_densities_recomputed = tv_densities_recomputed.unsqueeze(-1) + + # Compute gradients for each channel + grad_outputs = torch.autograd.grad( + outputs=tv_densities_recomputed, + inputs=tv_coords, + grad_outputs=torch.ones_like(tv_densities_recomputed), + create_graph=True, + )[0] # Shape: [num_samples, coord_dim] + + # Compute TV loss - gradient magnitude per sample + grad_norm = torch.norm(grad_outputs, dim=1) # Shape: [num_samples] + soft_loss += self.constraints.tv_vol * grad_norm.mean() if ( isinstance(self.constraints, ObjConstraintParams.ObjINRConstraints) From 73c0d30a387627e54797318ec97da2042cb4e169 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Thu, 16 Apr 2026 16:54:37 +0000 Subject: [PATCH 242/335] object_models.py now has tv_loss for both KPlanes and INR architectures. Also overloaded reconnecting optimizers --- src/quantem/core/ml/optimizer_mixin.py | 2 +- src/quantem/tomography/object_models.py | 178 +++++++++++++++--------- 2 files changed, 114 insertions(+), 66 deletions(-) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index 4ea263c0..9fe8a32a 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -733,7 +733,7 @@ def reconnect_optimizer_to_parameters(self) -> None: if not optimizable_params: print( - f"souldn't be getting here! No optimizable parameters found for {self.__class__.__name__}, removing optimizer" + f"shouldn't be getting here! No optimizable parameters found for {self.__class__.__name__}, removing optimizer" ) self.remove_optimizer() return diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 9f3ce764..0c09a372 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -14,6 +14,7 @@ from quantem.core.ml.constraints import BaseConstraints, Constraints from quantem.core.ml.ddp import DDPMixin from quantem.core.ml.loss_functions import get_loss_module +from quantem.core.ml.models.kplanes import KPlanes, KPlanesTILTED from quantem.core.ml.models.model_base import PPLR from quantem.core.ml.optimizer_mixin import OptimizerMixin, OptimizerType from quantem.core.utils.rng import RNGMixin @@ -499,6 +500,8 @@ def obj_view(self) -> np.ndarray: self.create_volume() return self._obj.cpu().numpy().transpose(0, 1, 3, 2) + # --- Constraints --- + def apply_soft_constraints( self, coords: torch.Tensor, @@ -506,42 +509,7 @@ def apply_soft_constraints( ) -> torch.Tensor: soft_loss = torch.tensor(0.0, device=pred.device) if self.constraints.tv_vol > 0: - - if isinstance(self.model, PPLR): # TODO: Temporary - for plane in self.model.grids: - # plane: (3*T, C, H, W) - # Differences along H (axis -2) and W (axis -1) - diff_h = plane[..., 1:, :] - plane[..., :-1, :] # (3*T, C, H-1, W) - diff_w = plane[..., :, 1:] - plane[..., :, :-1] # (3*T, C, H, W-1) - - - soft_loss += diff_h.pow(2).mean() + diff_w.pow(2).mean() - - soft_loss /= len(self.model.grids) - else: - num_tv_samples = min(10_000, coords.shape[0]) - tv_indices = torch.randperm(coords.shape[0], device=coords.device)[:num_tv_samples] - - tv_coords = coords[tv_indices].detach().requires_grad_(True) - tv_densities_recomputed = self.model(tv_coords) - if isinstance(tv_densities_recomputed, tuple): - tv_densities_recomputed = tv_densities_recomputed[0] - - # Ensure shape is [num_samples, num_channels] - if tv_densities_recomputed.dim() == 1: - tv_densities_recomputed = tv_densities_recomputed.unsqueeze(-1) - - # Compute gradients for each channel - grad_outputs = torch.autograd.grad( - outputs=tv_densities_recomputed, - inputs=tv_coords, - grad_outputs=torch.ones_like(tv_densities_recomputed), - create_graph=True, - )[0] # Shape: [num_samples, coord_dim] - - # Compute TV loss - gradient magnitude per sample - grad_norm = torch.norm(grad_outputs, dim=1) # Shape: [num_samples] - soft_loss += self.constraints.tv_vol * grad_norm.mean() + soft_loss += self.get_tv_loss(coords, pred) if ( isinstance(self.constraints, ObjConstraintParams.ObjINRConstraints) @@ -552,6 +520,53 @@ def apply_soft_constraints( return soft_loss + def get_tv_loss(self, coords: torch.Tensor, pred: torch.Tensor) -> torch.Tensor: + """ + Calculate the TV loss for the given coordinates and predictions. + + Current supported architectures are KPlanes and INRs + """ + + if isinstance(self.model, (KPlanes, KPlanesTILTED)): + + per_level = [] + + for p in self.model.grids: + dh = p[:, :, 1:, :] - p[:, :, :-1, :] + dw = p[:, :, :, 1:] - p[:, :, :, :-1] + tv = 0.0 + if self.constraints.tv_vol > 0: + tv = tv + self.constraints.tv_vol * (dh.pow(2).mean() + dw.pow(2).mean()) + per_level.append(tv) + + return torch.stack(per_level).sum() + else: + num_tv_samples = min(10_000, coords.shape[0]) + tv_indices = torch.randperm(coords.shape[0], device=coords.device)[:num_tv_samples] + + tv_coords = coords[tv_indices].detach().requires_grad_(True) + tv_densities_recomputed = self.model(tv_coords) + if isinstance(tv_densities_recomputed, tuple): + tv_densities_recomputed = tv_densities_recomputed[0] + + # Ensure shape is [num_samples, num_channels] + if tv_densities_recomputed.dim() == 1: + tv_densities_recomputed = tv_densities_recomputed.unsqueeze(-1) + + # Compute gradients for each channel + grad_outputs = torch.autograd.grad( + outputs=tv_densities_recomputed, + inputs=tv_coords, + grad_outputs=torch.ones_like(tv_densities_recomputed), + create_graph=True, + )[0] # Shape: [num_samples, coord_dim] + + # Compute TV loss - gradient magnitude per sample + grad_norm = torch.norm(grad_outputs, dim=1) # Shape: [num_samples] + return self.constraints.tv_vol * grad_norm.mean() + + + def apply_hard_constraints(self, pred: torch.Tensor) -> torch.Tensor: """ Apply hard constraints to the predicted values of the INR model. @@ -573,7 +588,7 @@ def params(self) -> Generator[torch.nn.Parameter, None, None]: return self.model.parameters() # type: ignore[attr-defined] - def get_optimization_parameters(self) -> list[nn.Parameter]: + def get_optimization_parameters(self) -> list[nn.Parameter] | list[dict[str, Any]]: if isinstance(self.model, PPLR): @@ -622,6 +637,7 @@ def optimizer_params(self, params: OptimizerType | dict[str, OptimizerType] | di else: raise TypeError(f"optimizer parameters must be a dict for non-PPLR, got {type(params)}") + def set_optimizer(self, opt_params: OptimizerType | dict | None = None) -> None: """ Set the optimizer for this model. @@ -643,6 +659,66 @@ def set_optimizer(self, opt_params: OptimizerType | dict | None = None) -> None: self._optimizer = torch.optim.Adam(params) + def reconnect_optimizer_to_parameters(self) -> None: + """ + Reconnect optimizer overload, defaults back to the standard implementation if no `PPLR` is detected. + """ + + if self.optimizer is None: + return + + if isinstance(self.model, PPLR): + current_params = self.get_optimization_parameters() + + + optimizable_params = [ + p for p in current_params + if isinstance(p['params'][0], torch.Tensor) and p['params'][0].is_leaf + ] + + + if not optimizable_params: + raise ValueError(f"Shouldn't be getting here! No optimizable parameters found for {self.__class__.__name__}.") + + for p in optimizable_params: + print(f"Setting requires_grad for parameter: {p}") + p['params'][0].requires_grad_(True) + + assert self._optimizer is not None + # Preserve optimizer states and param_group settings + old_state = self._optimizer.state.copy() + old_param_groups = self._optimizer.param_groups.copy() + + # Reconnect to new parameters + self._optimizer.param_groups.clear() + for param_group in optimizable_params: + self._optimizer.add_param_group(param_group) + + # Restore per-group hyperparameters (lr, betas, weight_decay, etc.) by index, + # excluding 'params' which comes from the new groups + for new_pg, old_pg in zip(self._optimizer.param_groups, old_param_groups): + new_pg.update({k: v for k, v in old_pg.items() if k != "params"}) + + # Remap optimizer state: for any new param that IS the same tensor as an old param, + # carry its state over (moved to the right device just in case). + new_state = {} + for new_pg in self._optimizer.param_groups: + for new_param in new_pg["params"]: + if new_param in old_state: + device = new_param.device + new_state[new_param] = { + k: (v.to(device) if isinstance(v, torch.Tensor) else v) + for k, v in old_state[new_param].items() + } + + self._optimizer.state.clear() + self._optimizer.state.update(new_state) + + if self._scheduler is not None and self._optimizer is not None: + self._scheduler.optimizer = self._optimizer + else: + super().reconnect_optimizer_to_parameters() + # Pretraining @property def pretrained_weights(self) -> dict[str, torch.Tensor]: @@ -894,34 +970,6 @@ def create_volume(self, return_vol: bool = False): self._obj = pred_full.detach().cpu() - def get_tv_loss( # pyright: ignore[reportIncompatibleMethodOverride] - self, - coords: torch.Tensor, - ) -> torch.Tensor: - tv_loss = torch.tensor(0.0, device=coords.device) - - num_tv_samples = min(10000, coords.shape[0]) - tv_indices = torch.randperm(coords.shape[0], device=coords.device)[:num_tv_samples] - - tv_coords = coords[tv_indices].detach().requires_grad_(True) - - tv_densities_recomputed = self.forward(tv_coords) - - if tv_densities_recomputed.dim() > 1: - tv_densities_recomputed = tv_densities_recomputed.squeeze(-1) - - grad_outputs = torch.autograd.grad( - outputs=tv_densities_recomputed, - inputs=tv_coords, - grad_outputs=torch.ones_like(tv_densities_recomputed), - create_graph=True, - )[0] - - grad_norm = torch.norm(grad_outputs, dim=1) - - tv_loss += self.constraints.tv_vol * grad_norm.mean() - return tv_loss - def to(self, device: str | torch.device): # pyright: ignore[reportIncompatibleMethodOverride] -> better to do this device change if isinstance(device, str): device = torch.device(device) From 35157fbabf702f83821d84b2133666fbb1ff6704 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Fri, 17 Apr 2026 00:30:12 +0000 Subject: [PATCH 243/335] KPlanes with R9+SVD parameterization, everything seems to be working well. Only things to ask Corneel about is multiscale res since this adds a significant amount of compute. Should I be doing variable num_samples_per_ray? --- src/quantem/core/ml/models/kplanes.py | 291 ++++++++++++------------ src/quantem/tomography/object_models.py | 31 +-- src/quantem/tomography/tomography.py | 6 +- 3 files changed, 167 insertions(+), 161 deletions(-) diff --git a/src/quantem/core/ml/models/kplanes.py b/src/quantem/core/ml/models/kplanes.py index dbc8f982..7839f20d 100644 --- a/src/quantem/core/ml/models/kplanes.py +++ b/src/quantem/core/ml/models/kplanes.py @@ -261,164 +261,175 @@ def param_keys(self) -> list[str]: # SO(3) quaternion parameter module # --------------------------------------------------------------------------- +# class SO3Param(nn.Module): +# """ +# Stores T unit quaternions as learnable parameters in R^4 and normalises +# them on every call to `as_matrix()`. + +# Quaternion convention: [x, y, z, w] (scalar-last, same as scipy). + +# Initialisation +# -------------- +# "random" – uniform sampling over SO(3) via Shoemake's method. +# "identity" – all rotations start as the identity (good for fine-tuning). +# """ + +# def __init__(self, T: int, init: str = "random"): +# super().__init__() +# if T < 1: +# raise ValueError(f"T must be >= 1, got {T}") +# quats = self._init_quaternions(T, init) # (T, 4) +# self.quats = nn.Parameter(quats) + +# # ------------------------------------------------------------------ +# # Initialisers +# # ------------------------------------------------------------------ + +# @staticmethod +# def _shoemake_sample(T: int) -> torch.Tensor: +# """Uniform SO(3) sampling via Shoemake (1992). Returns (T, 4) [x,y,z,w].""" +# u = torch.rand(T, 3) +# sqrt1_u0 = torch.sqrt(1.0 - u[:, 0]) +# sqrt_u0 = torch.sqrt(u[:, 0]) +# two_pi = 2.0 * math.pi +# x = sqrt1_u0 * torch.sin(two_pi * u[:, 1]) +# y = sqrt1_u0 * torch.cos(two_pi * u[:, 1]) +# z = sqrt_u0 * torch.sin(two_pi * u[:, 2]) +# w = sqrt_u0 * torch.cos(two_pi * u[:, 2]) +# return torch.stack([x, y, z, w], dim=-1) # (T, 4) + +# @staticmethod +# def _identity(T: int) -> torch.Tensor: +# """All-identity rotations: [0,0,0,1] * T.""" +# q = torch.zeros(T, 4) +# q[:, 3] = 1.0 +# return q + +# @classmethod +# def _init_quaternions(cls, T: int, init: str) -> torch.Tensor: +# if init == "random": +# return cls._shoemake_sample(T) +# elif init == "identity": +# return cls._identity(T) +# else: +# raise ValueError(f"Unknown init '{init}'; choose 'random' or 'identity'.") + +# # ------------------------------------------------------------------ +# # Forward helpers +# # ------------------------------------------------------------------ + +# def normalized(self) -> torch.Tensor: +# """Returns (T, 4) unit quaternions.""" +# return F.normalize(self.quats, p=2, dim=-1) + +# def as_matrix(self) -> torch.Tensor: +# """ +# Converts the T stored quaternions to (T, 3, 3) rotation matrices. + +# Uses the standard formula; no trig, just multiplications. +# """ +# q = self.normalized() # (T, 4) [x, y, z, w] +# x, y, z, w = q.unbind(dim=-1) # each (T,) + +# # Precompute products +# xx, yy, zz = x*x, y*y, z*z +# xy, xz, yz = x*y, x*z, y*z +# wx, wy, wz = w*x, w*y, w*z + +# # Row-major: R[i,j] +# R = torch.stack([ +# 1 - 2*(yy + zz), 2*(xy - wz), 2*(xz + wy), +# 2*(xy + wz), 1 - 2*(xx + zz), 2*(yz - wx), +# 2*(xz - wy), 2*(yz + wx), 1 - 2*(xx + yy), +# ], dim=-1).reshape(-1, 3, 3) # (T, 3, 3) + +# return R + +# def extra_repr(self) -> str: +# return f"T={self.quats.shape[0]}" + + class SO3Param(nn.Module): """ - Stores T unit quaternions as learnable parameters in R^4 and normalises - them on every call to `as_matrix()`. - - Quaternion convention: [x, y, z, w] (scalar-last, same as scipy). - - Initialisation - -------------- - "random" – uniform sampling over SO(3) via Shoemake's method. - "identity" – all rotations start as the identity (good for fine-tuning). + SO(3) rotation bank using R9+SVD parameterization. + Each rotation is stored as an unconstrained 3x3 matrix M, + projected to SO(3) via SVD+(M) = U diag(1,1,det(UVt)) Vt. """ - + def __init__(self, T: int, init: str = "random"): super().__init__() - if T < 1: - raise ValueError(f"T must be >= 1, got {T}") - quats = self._init_quaternions(T, init) # (T, 4) - self.quats = nn.Parameter(quats) - - # ------------------------------------------------------------------ - # Initialisers - # ------------------------------------------------------------------ - - @staticmethod - def _shoemake_sample(T: int) -> torch.Tensor: - """Uniform SO(3) sampling via Shoemake (1992). Returns (T, 4) [x,y,z,w].""" - u = torch.rand(T, 3) - sqrt1_u0 = torch.sqrt(1.0 - u[:, 0]) - sqrt_u0 = torch.sqrt(u[:, 0]) - two_pi = 2.0 * math.pi - x = sqrt1_u0 * torch.sin(two_pi * u[:, 1]) - y = sqrt1_u0 * torch.cos(two_pi * u[:, 1]) - z = sqrt_u0 * torch.sin(two_pi * u[:, 2]) - w = sqrt_u0 * torch.cos(two_pi * u[:, 2]) - return torch.stack([x, y, z, w], dim=-1) # (T, 4) - - @staticmethod - def _identity(T: int) -> torch.Tensor: - """All-identity rotations: [0,0,0,1] * T.""" - q = torch.zeros(T, 4) - q[:, 3] = 1.0 - return q - - @classmethod - def _init_quaternions(cls, T: int, init: str) -> torch.Tensor: + print("SVD Module") if init == "random": - return cls._shoemake_sample(T) + # Initialize near identity with small noise + M = torch.eye(3).unsqueeze(0).repeat(T, 1, 1) + M = M + 0.1 * torch.randn(T, 3, 3) elif init == "identity": - return cls._identity(T) + M = torch.eye(3).unsqueeze(0).repeat(T, 1, 1) else: - raise ValueError(f"Unknown init '{init}'; choose 'random' or 'identity'.") - - # ------------------------------------------------------------------ - # Forward helpers - # ------------------------------------------------------------------ - - def normalized(self) -> torch.Tensor: - """Returns (T, 4) unit quaternions.""" - return F.normalize(self.quats, p=2, dim=-1) - + raise ValueError(f"Unknown init '{init}'") + self.M = nn.Parameter(M) # (T, 3, 3) + def as_matrix(self) -> torch.Tensor: - """ - Converts the T stored quaternions to (T, 3, 3) rotation matrices. - - Uses the standard formula; no trig, just multiplications. - """ - q = self.normalized() # (T, 4) [x, y, z, w] - x, y, z, w = q.unbind(dim=-1) # each (T,) - - # Precompute products - xx, yy, zz = x*x, y*y, z*z - xy, xz, yz = x*y, x*z, y*z - wx, wy, wz = w*x, w*y, w*z - - # Row-major: R[i,j] - R = torch.stack([ - 1 - 2*(yy + zz), 2*(xy - wz), 2*(xz + wy), - 2*(xy + wz), 1 - 2*(xx + zz), 2*(yz - wx), - 2*(xz - wy), 2*(yz + wx), 1 - 2*(xx + yy), - ], dim=-1).reshape(-1, 3, 3) # (T, 3, 3) - - return R - - def extra_repr(self) -> str: - return f"T={self.quats.shape[0]}" + """Projects each M to SO(3) via SVD. Returns (T, 3, 3).""" + U, _, Vh = torch.linalg.svd(self.M) # U: (T,3,3), Vh: (T,3,3) + # Fix reflections: det(U Vh) must be +1 + d = torch.det(U @ Vh) # (T,) + diag = torch.ones(self.M.shape[0], 3, device=self.M.device) + diag[:, 2] = d # multiply last singular vector by sign + return U @ (diag.unsqueeze(-1) * Vh) # (T, 3, 3) + def interpolate_ms_features_tilted( pts: torch.Tensor, # (B, 3) ms_grids: nn.ParameterList, # each grid: (3*T, C, H, W) rotation_matrices: torch.Tensor, # (T, 3, 3) ) -> torch.Tensor: """ - Multi-scale, multi-rotation K-Planes feature interpolation. - - For each of the T rotations: - 1. Rotate pts -> (B, 3) - 2. Project to XY / XZ / YZ planes -> 3 × (B, 2) grids of coords - 3. Bilinear interpolation on the corresponding planes - 4. Hadamard product across the 3 planes -> (B, C) - - Across T transforms, outputs are *concatenated* (not summed), so each - rotation owns a disjoint slice of the feature dimension. Across scales, - outputs are also concatenated, matching the base KPlanes behaviour. - - Returns - ------- - features : (B, C * T * num_scales) + Fully-vectorized multi-scale, multi-rotation K-Planes feature interpolation. + Returns features of shape (B, C * T * num_scales). """ T = rotation_matrices.shape[0] B = pts.shape[0] - - # Rotate all points by all T rotation matrices at once. - # pts: (B, 3), R: (T, 3, 3) -> rotated: (T, B, 3) + + # (T, B, 3) — rotate all points by all rotations at once rotated = torch.einsum("tij,bj->tbi", rotation_matrices, pts) - + + # Build (T, 3, B, 2) coords for planes XY, ZX, YZ in one shot. + # index_select is faster and cleaner than advanced indexing with python lists. + # Plane axis layout: XY=(0,1), ZX=(2,0), YZ=(1,2) + idx = torch.tensor([[0, 1], + [2, 0], + [1, 2]], device=pts.device) # (3, 2) + # rotated: (T, B, 3) -> gather along last dim with idx (3, 2) + # Result: (T, 3, B, 2) + coords = rotated.unsqueeze(1).expand(T, 3, B, 3).gather( + -1, idx.view(1, 3, 1, 2).expand(T, 3, B, 2) + ) + + # Flatten (T, 3) -> 3*T so it matches grid's first dim, and add the H_out=1 axis + coord_tensor = coords.reshape(3 * T, B, 1, 2) # (3T, B, 1, 2) + per_scale_features = [] - for plane_coef in ms_grids: - # plane_coef shape: (3*T, C, H, W) + # plane_coef: (3T, C, H, W) C = plane_coef.shape[1] - - # Build the (3*T, B, 1, 2) coordinate tensor for grid_sample. - # For transform t, we need coords for planes XY, XZ, YZ. - # grid_sample expects coords in [-1, 1] with shape (N, Hout, Wout, 2). - all_plane_coords = [] - for t in range(T): - rp = rotated[t] # (B, 3) - all_plane_coords.append(rp[:, [0, 1]]) # XY (B, 2) - all_plane_coords.append(rp[:, [2, 0]]) # ZX (B, 2) matches ki - all_plane_coords.append(rp[:, [1, 2]]) # YZ (B, 2) matches jk - - # Stack -> (3*T, B, 2) -> (3*T, B, 1, 2) for grid_sample - coord_tensor = torch.stack(all_plane_coords, dim=0).unsqueeze(2) - # coord_tensor: (3*T, B, 1, 2) - - # grid_sample: input (N, C, H, W), grid (N, Hout, Wout, 2) + sampled = F.grid_sample( - plane_coef, # (3*T, C, H, W) - coord_tensor, # (3*T, B, 1, 2) + plane_coef, + coord_tensor, align_corners=True, mode="bilinear", padding_mode="border", - ) # -> (3*T, C, B, 1) - sampled = sampled.squeeze(-1) # (3*T, C, B) - - # Hadamard product within each transform group (3 planes per transform). - transform_features = [] - for t in range(T): - p_xy = sampled[3*t + 0] # (C, B) - p_zx = sampled[3*t + 1] - p_yz = sampled[3*t + 2] - fused = (p_xy * p_zx * p_yz).T # (B, C) - transform_features.append(fused) - - # Concatenate across transforms -> (B, C*T) - per_scale_features.append(torch.cat(transform_features, dim=-1)) - - # Concatenate across scales -> (B, C*T*num_scales) + ) # (3T, C, B, 1) + + # (3T, C, B) -> (T, 3, C, B) -> Hadamard across the "3" dim -> (T, C, B) + sampled = sampled.squeeze(-1).view(T, 3, C, B).prod(dim=1) + + # (T, C, B) -> (B, T, C) -> (B, T*C) to concatenate rotations along feature dim + per_scale_features.append( + sampled.permute(2, 0, 1).reshape(B, T * C) + ) + + # Concatenate across scales -> (B, T * C * num_scales) return torch.cat(per_scale_features, dim=-1) # --------------------------------------------------------------------------- @@ -549,17 +560,11 @@ def _build_sigma_net( layers.append(out) self.sigma_net = nn.Sequential(*layers) else: - self.sigma_net = tcnn.Network( - n_input_dims=self.feature_dim, - n_output_dims=1, - network_config={ - "otype": "CutlassMLP", - "activation": "None", - "output_activation": "None", - "n_neurons": 128, - "n_hidden_layers": 0, - }, - ) + # Match mentor's "explicit" decoder: a single linear layer. + # Small init so density stays near 0 initially. + self.sigma_net = nn.Linear(self.feature_dim, 1, bias=True) + nn.init.normal_(self.sigma_net.weight, std=0.01) + nn.init.zeros_(self.sigma_net.bias) # ------------------------------------------------------------------ # Warm-up bookkeeping diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 0c09a372..1d077c2e 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -506,10 +506,12 @@ def apply_soft_constraints( self, coords: torch.Tensor, pred: torch.Tensor, + curr_batch_idx: int, + max_batch_size: int ) -> torch.Tensor: soft_loss = torch.tensor(0.0, device=pred.device) if self.constraints.tv_vol > 0: - soft_loss += self.get_tv_loss(coords, pred) + soft_loss += self.get_tv_loss(coords, pred, curr_batch_idx, max_batch_size) if ( isinstance(self.constraints, ObjConstraintParams.ObjINRConstraints) @@ -520,7 +522,7 @@ def apply_soft_constraints( return soft_loss - def get_tv_loss(self, coords: torch.Tensor, pred: torch.Tensor) -> torch.Tensor: + def get_tv_loss(self, coords: torch.Tensor, pred: torch.Tensor, curr_batch_idx: int, max_batch_size: int) -> torch.Tensor: """ Calculate the TV loss for the given coordinates and predictions. @@ -528,18 +530,19 @@ def get_tv_loss(self, coords: torch.Tensor, pred: torch.Tensor) -> torch.Tensor: """ if isinstance(self.model, (KPlanes, KPlanesTILTED)): - - per_level = [] - - for p in self.model.grids: - dh = p[:, :, 1:, :] - p[:, :, :-1, :] - dw = p[:, :, :, 1:] - p[:, :, :, :-1] - tv = 0.0 - if self.constraints.tv_vol > 0: - tv = tv + self.constraints.tv_vol * (dh.pow(2).mean() + dw.pow(2).mean()) - per_level.append(tv) - - return torch.stack(per_level).sum() + if curr_batch_idx == max_batch_size - 1: + per_level = [] + for p in self.model.grids: + dh = p[:, :, 1:, :] - p[:, :, :-1, :] + dw = p[:, :, :, 1:] - p[:, :, :, :-1] + tv = 0.0 + if self.constraints.tv_vol > 0: + tv = tv + self.constraints.tv_vol * (dh.pow(2).mean() + dw.pow(2).mean()) + per_level.append(tv) + + return torch.stack(per_level).sum() * max_batch_size + else: + return 0.0 else: num_tv_samples = min(10_000, coords.shape[0]) tv_indices = torch.randperm(coords.shape[0], device=coords.device)[:num_tv_samples] diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 907ad994..c37af039 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -201,7 +201,7 @@ def reconstruct( with torch.autocast( device_type=self.device.type, dtype=torch.bfloat16, - enabled=True, + enabled=False, ): all_coords = self.dset.get_coords(batch, N, curr_num_samples_per_ray) @@ -214,9 +214,7 @@ def reconstruct( ) pred = integrated_densities.float() - soft_constraints_loss = 0.0 - if self.num_epochs > 0: - soft_constraints_loss = self.obj_model.apply_soft_constraints(all_coords, pred) + soft_constraints_loss = self.obj_model.apply_soft_constraints(all_coords, pred, batch_idx, len(self.dataloader)) target = batch["target_value"].to(self.device, non_blocking=True).float() From 1c89ad5fda5a148ad6fc4fcf6c45d85b9294dd4c Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Fri, 17 Apr 2026 01:30:55 +0000 Subject: [PATCH 244/335] Everything seems to be working; only things to do is to take a look at DDP, clean-up KPlanes, fix up object_models.py since it's insanely cluttered now --- src/quantem/core/ml/models/kplanes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/quantem/core/ml/models/kplanes.py b/src/quantem/core/ml/models/kplanes.py index 7839f20d..0f44bf7e 100644 --- a/src/quantem/core/ml/models/kplanes.py +++ b/src/quantem/core/ml/models/kplanes.py @@ -358,7 +358,6 @@ class SO3Param(nn.Module): def __init__(self, T: int, init: str = "random"): super().__init__() - print("SVD Module") if init == "random": # Initialize near identity with small noise M = torch.eye(3).unsqueeze(0).repeat(T, 1, 1) From 851cff8967b583b82c46a3de12cb0225e72bcb2e Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 17 Apr 2026 10:02:21 -0700 Subject: [PATCH 245/335] update PR into a statement --- .github/PULL_REQUEST_TEMPLATE/pull_request_template.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md index e3c0d0d0..cf5e0174 100644 --- a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -1,8 +1,8 @@ -### What problem does this PR address? +### What problem this PR addreseses -### What should the reviewer(s) do? +### What should the reviewer(s) do From 4215d6e2ed0b2f114e7746b17e09863d5be4d021 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Fri, 17 Apr 2026 17:45:20 +0000 Subject: [PATCH 246/335] New TV loss function --- src/quantem/tomography/object_models.py | 41 ++++++++++++++----------- src/quantem/tomography/tomography.py | 2 +- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 1d077c2e..dd8b62ff 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -505,24 +505,24 @@ def obj_view(self) -> np.ndarray: def apply_soft_constraints( self, coords: torch.Tensor, + all_densities: torch.Tensor, pred: torch.Tensor, - curr_batch_idx: int, - max_batch_size: int ) -> torch.Tensor: + soft_loss = torch.tensor(0.0, device=pred.device) if self.constraints.tv_vol > 0: - soft_loss += self.get_tv_loss(coords, pred, curr_batch_idx, max_batch_size) + soft_loss += self.get_tv_loss(coords, pred) if ( isinstance(self.constraints, ObjConstraintParams.ObjINRConstraints) and self.constraints.sparsity > 0 ): # NOTE: For the linter, I must make this :) - sparsity_loss = self.constraints.sparsity * torch.norm(pred, p=1) + sparsity_loss = self.constraints.sparsity * torch.norm(all_densities, p=1) soft_loss += sparsity_loss return soft_loss - def get_tv_loss(self, coords: torch.Tensor, pred: torch.Tensor, curr_batch_idx: int, max_batch_size: int) -> torch.Tensor: + def get_tv_loss(self, coords: torch.Tensor, pred: torch.Tensor) -> torch.Tensor: """ Calculate the TV loss for the given coordinates and predictions. @@ -530,19 +530,24 @@ def get_tv_loss(self, coords: torch.Tensor, pred: torch.Tensor, curr_batch_idx: """ if isinstance(self.model, (KPlanes, KPlanesTILTED)): - if curr_batch_idx == max_batch_size - 1: - per_level = [] - for p in self.model.grids: - dh = p[:, :, 1:, :] - p[:, :, :-1, :] - dw = p[:, :, :, 1:] - p[:, :, :, :-1] - tv = 0.0 - if self.constraints.tv_vol > 0: - tv = tv + self.constraints.tv_vol * (dh.pow(2).mean() + dw.pow(2).mean()) - per_level.append(tv) - - return torch.stack(per_level).sum() * max_batch_size - else: - return 0.0 + is_tilted = isinstance(self.model, KPlanesTILTED) + per_level = [] + for p in self.model.grids: + # p: (3*T, C, H, W) for TILTED, (3, C, H, W) for KPlanes + dh = (p[:, :, 1:, :] - p[:, :, :-1, :]).pow(2).mean(dim=(1, 2, 3)) + dw = (p[:, :, :, 1:] - p[:, :, :, :-1]).pow(2).mean(dim=(1, 2, 3)) + per_plane = dh + dw # (3*T,) or (3,) + + if is_tilted: + T = self.model.T + per_rotation = per_plane.view(T, 3).sum(dim=1) # sum 3 planes per rotation + level_tv = per_rotation.mean() # average across rotations + else: + level_tv = per_plane.sum() # standard K-planes behavior + + per_level.append(level_tv) + + return self.constraints.tv_vol * torch.stack(per_level).sum() else: num_tv_samples = min(10_000, coords.shape[0]) tv_indices = torch.randperm(coords.shape[0], device=coords.device)[:num_tv_samples] diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index c37af039..fc715362 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -214,7 +214,7 @@ def reconstruct( ) pred = integrated_densities.float() - soft_constraints_loss = self.obj_model.apply_soft_constraints(all_coords, pred, batch_idx, len(self.dataloader)) + soft_constraints_loss = self.obj_model.apply_soft_constraints(all_coords, all_densities, pred) target = batch["target_value"].to(self.device, non_blocking=True).float() From aebf8011d77e0229edfd0009c6a923409191dd2a Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Sat, 18 Apr 2026 08:45:32 +0000 Subject: [PATCH 247/335] TV volume -- needs significant refactoring everywhere --- src/quantem/core/ml/models/kplanes.py | 273 +++++++++++++++-------- src/quantem/tomography/dataset_models.py | 23 ++ src/quantem/tomography/object_models.py | 153 +++++++++---- src/quantem/tomography/tomography.py | 20 ++ 4 files changed, 340 insertions(+), 129 deletions(-) diff --git a/src/quantem/core/ml/models/kplanes.py b/src/quantem/core/ml/models/kplanes.py index 0f44bf7e..b54fe1ee 100644 --- a/src/quantem/core/ml/models/kplanes.py +++ b/src/quantem/core/ml/models/kplanes.py @@ -438,15 +438,23 @@ def interpolate_ms_features_tilted( class KPlanesTILTED(KPlanes): """ K-Planes with T learned SO(3) rotations (TILTED). - + Inherits KPlanes for the sigma_net, density_activation, and get_params interface. Overrides: - * __init__ – replaces the axis-aligned grids with (3*T)-plane grids - and adds SO3Param. + * __init__ – replaces the axis-aligned grids with (3*T)-plane + grids and adds SO3Param. * get_densities – calls the TILTED interpolation instead. * get_params – adds "so3" key so callers can set a separate lr. * param_keys – updated list. - + + Two-phase optimization + ---------------------- + Phase 1: instantiate with small `M_features` (and optionally smaller + `resolution` / fewer scales) — the bottleneck model. Train it + until τ converges, then call `extract_tau_state()`. + Phase 2: instantiate at full capacity, call `load_tau_state(M_bneck)` + to seed the rotations, then train normally. + Parameters ---------- M_features : int @@ -454,15 +462,13 @@ class KPlanesTILTED(KPlanes): be M_features * T * len(multiscale_res_multipliers). T : int Number of learned rotations (TILTED-T in the paper; 4 or 8 recommended). + Must match between phase 1 and phase 2 when doing two-phase transfer. tau_init : str "random" (paper default) or "identity". - tau_warmup_steps : int - If > 0, grids and sigma_net are frozen for this many steps so the - rotations can find good basins first (two-phase warm-up). - Call model.training_step() once per optimiser step. + Irrelevant if you're calling load_tau_state() right after __init__. All other args are forwarded to KPlanes. """ - + def __init__( self, # Grid parameters @@ -474,7 +480,6 @@ def __init__( # TILTED parameters T: int = 4, tau_init: str = "random", - tau_warmup_steps: int = 0, # Hybrid MLP parameters use_hybrid_mlp: bool = False, hybrid_hidden_dim: int = 64, @@ -484,20 +489,20 @@ def __init__( raise NotImplementedError("KPlanesTILTED is implemented for 3D only.") if T < 1: raise ValueError(f"T must be >= 1, got {T}") - + multiscale_res_multipliers = list(multiscale_res_multipliers or [1]) num_scales = len(multiscale_res_multipliers) - + # Total feature dim seen by the MLP head. # Each scale contributes M_features * T channels. feature_dim = M_features * T * num_scales - + # Call KPlanes.__init__ with grid_dimensions=2 so it builds sigma_net # correctly; we immediately replace self.grids below. super().__init__( grid_dimensions=2, input_coords_dims=3, - M_features=M_features, # base class stores this + M_features=M_features, resolution=resolution, multiscale_res_multipliers=multiscale_res_multipliers, concat_features=True, @@ -506,13 +511,9 @@ def __init__( hybrid_hidden_dim=hybrid_hidden_dim, hybrid_num_layers=hybrid_num_layers, ) - # KPlanes.__init__ built grids with shape (3, M, H, W) and feature_dim - # = M * num_scales. We rebuild them for the TILTED shape. - + self.T = T - self.tau_warmup_steps = tau_warmup_steps - self._global_step: int = 0 - + # ---- Rebuild grids: (3*T, M_features, H, W) per scale ---- self.grids = nn.ParameterList() for res_mult in multiscale_res_multipliers: @@ -522,20 +523,20 @@ def __init__( ) nn.init.uniform_(plane, 0.1, 0.5) self.grids.append(plane) - + # ---- Rebuild sigma_net with the correct feature_dim ---- # KPlanes built sigma_net with self.feature_dim (= M * num_scales), # which is wrong for T > 1. Rebuild here. self.feature_dim = feature_dim self._build_sigma_net(use_hybrid_mlp, hybrid_hidden_dim, hybrid_num_layers) - + # ---- Learnable rotations ---- self.so3 = SO3Param(T, init=tau_init) - + # ------------------------------------------------------------------ # Internal helpers # ------------------------------------------------------------------ - + def _build_sigma_net( self, use_hybrid_mlp: bool, @@ -559,53 +560,15 @@ def _build_sigma_net( layers.append(out) self.sigma_net = nn.Sequential(*layers) else: - # Match mentor's "explicit" decoder: a single linear layer. - # Small init so density stays near 0 initially. + # Single-linear "explicit" decoder. Small init -> density ~ 0 initially. self.sigma_net = nn.Linear(self.feature_dim, 1, bias=True) nn.init.normal_(self.sigma_net.weight, std=0.01) nn.init.zeros_(self.sigma_net.bias) - - # ------------------------------------------------------------------ - # Warm-up bookkeeping - # ------------------------------------------------------------------ - - def training_step(self) -> None: - """ - Call once per optimiser step to advance the internal counter. - - During the first `tau_warmup_steps` iterations, grids and sigma_net - have their gradients zeroed after the backward pass so only the SO(3) - parameters update. This is the lightweight version of two-phase - optimisation from the paper. - """ - print("Global Stepped") - self._global_step += 1 - - def _in_warmup(self) -> bool: - return self.tau_warmup_steps > 0 and self._global_step < self.tau_warmup_steps - - def zero_non_tau_grads(self) -> None: - """ - Call after loss.backward() and before optimizer.step() when you want - to implement the rotation warm-up manually. Alternatively just check - model.in_warmup and configure your optimizer accordingly. - """ - if self._in_warmup(): - for p in self.grids.parameters(): - if p.grad is not None: - p.grad.zero_() - for p in self.sigma_net.parameters(): - if p.grad is not None: - p.grad.zero_() - - @property - def in_warmup(self) -> bool: - return self._in_warmup() - + # ------------------------------------------------------------------ # Core forward # ------------------------------------------------------------------ - + def get_densities(self, coords: torch.Tensor) -> torch.Tensor: pts = coords.reshape(-1, 3) R = self.so3.as_matrix() # (T, 3, 3) @@ -616,60 +579,192 @@ def get_densities(self, coords: torch.Tensor) -> torch.Tensor: ) density_before_activation = self.sigma_net(features) return self.density_activation(density_before_activation) - + def forward(self, pts: torch.Tensor) -> torch.Tensor: return self.get_densities(pts) - + # ------------------------------------------------------------------ # Parameter groups # ------------------------------------------------------------------ - + def get_params(self) -> dict[str, list[nn.Parameter]]: return { "grids": list(self.grids.parameters()), "sigma_net": list(self.sigma_net.parameters()), "so3": list(self.so3.parameters()), } - + @property def param_keys(self) -> list[str]: return ["grids", "sigma_net", "so3"] - # ------------------------------------------------------------------ - # Two-phase helper: extract tau for phase-2 initialisation + # Two-phase transfer: extract / load learned rotations # ------------------------------------------------------------------ - + def extract_tau_state(self) -> torch.Tensor: """ - Returns the current quaternion tensor (detached copy) so it can be - used to initialise a larger phase-2 model via `load_tau_state`. + Returns the current raw R^9 matrices (detached copy) so they can be + used to initialise a phase-2 model via `load_tau_state`. + + Returns + ------- + torch.Tensor of shape (T, 3, 3) """ - return self.so3.quats.detach().clone() - - def load_tau_state(self, quats: torch.Tensor) -> None: + return self.so3.M.detach().cpu().clone() + + def load_tau_state(self, M: torch.Tensor) -> None: """ - Load pre-trained quaternions (e.g. from a bottleneck phase-1 model). - - quats : (T, 4) tensor, will be normalised internally. + Load pre-trained rotation matrices (e.g. from a bottleneck phase-1 model). + + No orthogonalization is needed — SO3Param.as_matrix() projects to SO(3) + via SVD on every forward pass. + + Parameters + ---------- + M : torch.Tensor of shape (T, 3, 3) + Raw unconstrained matrices from `extract_tau_state()`. """ - if quats.shape != self.so3.quats.shape: + if M.shape != self.so3.M.shape: raise ValueError( - f"Shape mismatch: got {quats.shape}, " - f"expected {self.so3.quats.shape}" + f"Shape mismatch: got {M.shape}, expected {self.so3.M.shape}. " + f"Make sure T matches between phase 1 and phase 2." ) with torch.no_grad(): - self.so3.quats.copy_(F.normalize(quats, p=2, dim=-1)) - + self.so3.M.copy_(M.to(self.so3.M.device)) + # ------------------------------------------------------------------ # Pretty print # ------------------------------------------------------------------ - + def extra_repr(self) -> str: return ( f"T={self.T}, " f"M_features={self.M_features}, " f"feature_dim={self.feature_dim}, " - f"num_scales={len(self.multiscale_res_multipliers)}, " - f"tau_warmup_steps={self.tau_warmup_steps}" - ) \ No newline at end of file + f"num_scales={len(self.multiscale_res_multipliers)}" + ) + + + + + +# CP Decomp for Warmup SO3 rotations + +def interpolate_ms_features_cp_tilted( + pts: torch.Tensor, # (B, 3) + ms_grids: nn.ParameterList, # each grid: (3*T, C, L) — 1D lines + rotation_matrices: torch.Tensor, # (T, 3, 3) +) -> torch.Tensor: + """ + CP (vector outer product) version of TILTED interpolation. + Returns features of shape (B, C * T * num_scales). + """ + T = rotation_matrices.shape[0] + B = pts.shape[0] + + # Rotate all points by all rotations: (T, B, 3) + rotated = torch.einsum("tij,bj->tbi", rotation_matrices, pts) + + per_scale_features = [] + for line_coef in ms_grids: + # line_coef: (3T, C, L) — three 1D feature lines per transform (x, y, z) + C, L = line_coef.shape[1], line_coef.shape[2] + + # For each transform t, we need three 1D samples: at x_t, y_t, z_t. + # Lay them out as (3T, B) coords, matching line_coef's first dim. + # Axis order per transform: x, y, z. + coords_1d = rotated.reshape(T, B, 3).permute(0, 2, 1).reshape(3 * T, B) + # coords_1d: (3T, B), each row is samples along one axis for one transform + + # grid_sample wants 4D input for 2D sampling, or we can use 1D via a + # (3T, C, 1, L) reshape and pass 2D coords with y fixed at 0. + # Simpler: use F.grid_sample with a 4D trick, or just do manual linear interp. + # Here's the grid_sample way: + line_coef_4d = line_coef.unsqueeze(2) # (3T, C, 1, L) + # grid: need (3T, Hout=1, Wout=B, 2), with x = coord, y = 0 + grid = torch.stack([ + coords_1d, # x + torch.zeros_like(coords_1d), # y + ], dim=-1).unsqueeze(1) # (3T, 1, B, 2) + + sampled = F.grid_sample( + line_coef_4d, grid, + align_corners=True, mode="bilinear", padding_mode="border", + ).squeeze(2) # (3T, C, B) + + # Hadamard across the 3 axes per transform: (T, 3, C, B) -> (T, C, B) + sampled = sampled.view(T, 3, C, B).prod(dim=1) + + # (T, C, B) -> (B, T*C) + per_scale_features.append(sampled.permute(2, 0, 1).reshape(B, T * C)) + + return torch.cat(per_scale_features, dim=-1) + + +class CPTilted(nn.Module, PPLR): + """ + CP decomposition with TILTED rotations — the true bottleneck model for + phase 1. Rank-1-per-channel feature representation. + + Shares the SO3Param and sigma_net design with KPlanesTILTED so you can + lift τ directly across: cp_model.extract_tau_state() -> + kplanes_model.load_tau_state(). + """ + + def __init__( + self, + C: int = 4, # channels per transform per scale + resolution: Sequence[int] = (128, 128, 128), + multiscale_res_multipliers: Optional[Sequence[int]] = None, + T: int = 4, + tau_init: str = "random", + density_activation: Callable = lambda x: F.softplus(x - 1), + ): + super().__init__() + self.T = T + self.C = C + self.multiscale_res_multipliers = list(multiscale_res_multipliers or [1]) + self.density_activation = density_activation + + # 1D feature lines, one per axis per transform per scale. + # Shape per scale: (3*T, C, L). We use max(resolution) for L; if your + # scene is strongly anisotropic use 3 separate lines per axis. + self.grids = nn.ParameterList() + for mult in self.multiscale_res_multipliers: + L = int(max(resolution) * mult) + line = nn.Parameter(torch.empty(3 * T, C, L)) + nn.init.uniform_(line, 0.1, 0.5) + self.grids.append(line) + + self.feature_dim = C * T * len(self.multiscale_res_multipliers) + + # Same minimal single-linear decoder as your KPlanesTILTED default. + self.sigma_net = nn.Linear(self.feature_dim, 1, bias=True) + nn.init.normal_(self.sigma_net.weight, std=0.01) + nn.init.zeros_(self.sigma_net.bias) + + self.so3 = SO3Param(T, init=tau_init) + + def get_densities(self, coords: torch.Tensor) -> torch.Tensor: + pts = coords.reshape(-1, 3) + R = self.so3.as_matrix() + features = interpolate_ms_features_cp_tilted(pts, self.grids, R) + return self.density_activation(self.sigma_net(features)) + + def forward(self, pts): + return self.get_densities(pts) + + def get_params(self): + return { + "grids": list(self.grids.parameters()), + "sigma_net": list(self.sigma_net.parameters()), + "so3": list(self.so3.parameters()), + } + + @property + def param_keys(self): + return ["grids", "sigma_net", "so3"] + + def extract_tau_state(self) -> torch.Tensor: + return self.so3.M.detach().clone() \ No newline at end of file diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index f6343e4e..e3b61377 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -643,6 +643,29 @@ def to(self, device: torch.device | str): self.device = device self.reconnect_optimizer_to_parameters() + # --- Save learned parameters --- + + def save_parameters(self, path: str): + """ + Saves the learned parameters to a file. + """ + torch.save({ + "z1": self._z1_params.detach().cpu(), + "z3": self._z3_params.detach().cpu(), + "shifts": self._shifts_params.detach().cpu(), + }, path) + + def load_parameters(self, path: str): + """ + Loads the learned parameters from a file. + """ + data = torch.load(path) + self._z1_params = nn.Parameter(data["z1"]) + self._z3_params = nn.Parameter(data["z3"]) + self._shifts_params = nn.Parameter(data["shifts"]) + if self.optimizer is not None: + self.reconnect_optimizer_to_parameters() + class TomographyINRPretrainDataset(Dataset): """ diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index dd8b62ff..964e0c53 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -99,10 +99,11 @@ class ObjINRConstraints(Constraints): positivity: bool = True shrinkage: float = 0.0 tv_vol: float = 0.0 + tv_plane: float = 0.0 sparsity: float = 0.0 _name: str = "obj_inr" - soft_constraint_keys = ["tv_vol", "sparsity"] + soft_constraint_keys = ["tv_vol", "tv_plane", "sparsity"] hard_constraint_keys = ["positivity", "shrinkage"] @classmethod @@ -521,59 +522,131 @@ def apply_soft_constraints( soft_loss += sparsity_loss return soft_loss + # TV Losses def get_tv_loss(self, coords: torch.Tensor, pred: torch.Tensor) -> torch.Tensor: """ - Calculate the TV loss for the given coordinates and predictions. + Dispatch to the appropriate TV loss based on model architecture. - Current supported architectures are KPlanes and INRs + - KPlanes / KPlanesTILTED: per-plane TV (regularizes the stored feature + planes directly). Cheap; regularizes the representation. + - CPTilted: per-line TV (same idea, applied to 1D feature lines). + - SIREN / other INRs: volume TV via autograd (exact gradient). + - Fallback: volume TV via finite differences (works anywhere). + + If you want *volume* smoothness regardless of architecture, call + `get_volume_tv_loss(coords)` directly. """ - + tv_loss = torch.tensor(0.0, device=pred.device) if isinstance(self.model, (KPlanes, KPlanesTILTED)): - is_tilted = isinstance(self.model, KPlanesTILTED) - per_level = [] - for p in self.model.grids: - # p: (3*T, C, H, W) for TILTED, (3, C, H, W) for KPlanes - dh = (p[:, :, 1:, :] - p[:, :, :-1, :]).pow(2).mean(dim=(1, 2, 3)) - dw = (p[:, :, :, 1:] - p[:, :, :, :-1]).pow(2).mean(dim=(1, 2, 3)) - per_plane = dh + dw # (3*T,) or (3,) - - if is_tilted: - T = self.model.T - per_rotation = per_plane.view(T, 3).sum(dim=1) # sum 3 planes per rotation - level_tv = per_rotation.mean() # average across rotations - else: - level_tv = per_plane.sum() # standard K-planes behavior + tv_loss += self._get_plane_tv_loss() - per_level.append(level_tv) + # SIREN and other INRs support double-backward → use exact autograd. + # Everything else falls through to finite-difference volume TV. + if not isinstance(self.model, PPLR): # adjust to your actual INR class + tv_loss += self._get_volume_tv_loss_autograd(coords) - return self.constraints.tv_vol * torch.stack(per_level).sum() - else: - num_tv_samples = min(10_000, coords.shape[0]) - tv_indices = torch.randperm(coords.shape[0], device=coords.device)[:num_tv_samples] + if isinstance(self.model, (KPlanes, KPlanesTILTED)): + tv_loss += self.get_volume_tv_loss(coords) + return tv_loss + + + # ---------------------------------------------------------------------- + # Plane TV (K-Planes family) — regularizes the stored feature planes. + # ---------------------------------------------------------------------- + + def _get_plane_tv_loss(self) -> torch.Tensor: + is_tilted = isinstance(self.model, KPlanesTILTED) + per_level = [] + + for p in self.model.grids: + # p: (3*T, C, H, W) for TILTED, (3, C, H, W) for KPlanes + dh = (p[:, :, 1:, :] - p[:, :, :-1, :]).pow(2).mean(dim=(1, 2, 3)) + dw = (p[:, :, :, 1:] - p[:, :, :, :-1]).pow(2).mean(dim=(1, 2, 3)) + per_plane = dh + dw # (3*T,) or (3,) + + if is_tilted: + T = self.model.T + per_rotation = per_plane.view(T, 3).sum(dim=1) # sum 3 planes per rotation + level_tv = per_rotation.mean() # avg across rotations + else: + level_tv = per_plane.sum() - tv_coords = coords[tv_indices].detach().requires_grad_(True) - tv_densities_recomputed = self.model(tv_coords) - if isinstance(tv_densities_recomputed, tuple): - tv_densities_recomputed = tv_densities_recomputed[0] + per_level.append(level_tv) - # Ensure shape is [num_samples, num_channels] - if tv_densities_recomputed.dim() == 1: - tv_densities_recomputed = tv_densities_recomputed.unsqueeze(-1) + return self.constraints.tv_plane * torch.stack(per_level).sum() - # Compute gradients for each channel - grad_outputs = torch.autograd.grad( - outputs=tv_densities_recomputed, - inputs=tv_coords, - grad_outputs=torch.ones_like(tv_densities_recomputed), - create_graph=True, - )[0] # Shape: [num_samples, coord_dim] - # Compute TV loss - gradient magnitude per sample - grad_norm = torch.norm(grad_outputs, dim=1) # Shape: [num_samples] - return self.constraints.tv_vol * grad_norm.mean() + # ---------------------------------------------------------------------- + # Volume TV (autograd) — exact gradient, needs double-backward support. + # ---------------------------------------------------------------------- + + def _get_volume_tv_loss_autograd(self, coords: torch.Tensor) -> torch.Tensor: + """ + Isotropic volume TV using autograd. Exact gradient, but requires the + model to support double-backward (SIREN yes, grid_sample-based models no). + """ + num_tv_samples = min(10_000, coords.shape[0]) + tv_indices = torch.randperm(coords.shape[0], device=coords.device)[:num_tv_samples] + + tv_coords = coords[tv_indices].detach().requires_grad_(True) + pred = self.model(tv_coords) + if isinstance(pred, tuple): + pred = pred[0] + if pred.dim() == 1: + pred = pred.unsqueeze(-1) + + grad_outputs = torch.autograd.grad( + outputs=pred, + inputs=tv_coords, + grad_outputs=torch.ones_like(pred), + create_graph=True, + )[0] # (N, 3) + + grad_norm = torch.norm(grad_outputs, dim=1) # (N,) + return self.constraints.tv_vol * grad_norm.mean() + # ---------------------------------------------------------------------- + # Volume TV (finite differences) — works for any model. + # ---------------------------------------------------------------------- + + def get_volume_tv_loss(self, coords: torch.Tensor) -> torch.Tensor: + """ + Isotropic volume TV via finite differences. Same form as the autograd + version (L1 of gradient L2-norm) but avoids double-backward, so it + works for KPlanesTILTED, CPTilted, and anything else. + """ + num_tv_samples = min(10_000, coords.shape[0]) + tv_indices = torch.randperm(coords.shape[0], device=coords.device)[:num_tv_samples] + tv_coords = coords[tv_indices] # (N, 3) + + if hasattr(self.model, "resolution"): + h = 2.0 / min(self.model.resolution) + else: + h = 1e-2 + + pred = self.model(tv_coords) + if isinstance(pred, tuple): + pred = pred[0] + if pred.dim() == 1: + pred = pred.unsqueeze(-1) # (N, 1) + + grads = [] + for axis in range(3): + offset = torch.zeros(3, device=tv_coords.device) + offset[axis] = h + shifted_pred = self.model(tv_coords + offset) + if isinstance(shifted_pred, tuple): + shifted_pred = shifted_pred[0] + if shifted_pred.dim() == 1: + shifted_pred = shifted_pred.unsqueeze(-1) + grads.append((shifted_pred - pred) / h) # (N, 1) + + grad_stack = torch.stack(grads, dim=-1) # (N, C, 3) + grad_norm = torch.norm(grad_stack, dim=-1) # (N, C) + + return self.constraints.tv_vol * grad_norm.mean() def apply_hard_constraints(self, pred: torch.Tensor) -> torch.Tensor: """ diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index fc715362..a96136df 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -10,6 +10,7 @@ from quantem.core.io.serialize import load as autoserialize_load from quantem.core.ml.loss_functions import get_loss_module +from quantem.core.ml.models.kplanes import CPTilted from quantem.core.utils.filter import gaussian_filter_2d_stack, gaussian_kernel_1d from quantem.core.utils.tomography_utils import torch_phase_cross_correlation from quantem.tomography.dataset_models import ( @@ -233,6 +234,25 @@ def reconstruct( total_loss += batch_loss.detach() consistency_loss += batch_consistency_loss.detach() + if isinstance(self.obj_model.model, CPTilted): + if a0 == 0: + prev_R = self.obj_model.model.so3.as_matrix().detach().clone() + elif (a0 + 1) % 20 == 0: + R_now = self.obj_model.model.so3.as_matrix().detach() + # Cumulative angular change per rotation over the last 20 iters. + # trace(R_prev^T R_now) = 1 + 2*cos(theta), so theta = acos((trace - 1) / 2). + rel_trace = torch.einsum('tij,tij->t', prev_R, R_now) + angle = torch.acos(((rel_trace - 1) / 2).clamp(-1, 1)) # (T,) radians + angle_deg = torch.rad2deg(angle) + per_tau_str = ", ".join(f"{a:.2f}°" for a in angle_deg.tolist()) + print( + f"iter {a0}: 20-iter τ change " + f"max={angle_deg.max().item():.2f}°, " + f"mean={angle_deg.mean().item():.2f}°, " + f"per-τ=[{per_tau_str}]" + ) + prev_R = R_now.clone() + if self.world_size > 1: dist.all_reduce(total_loss, dist.ReduceOp.AVG) dist.all_reduce(consistency_loss, dist.ReduceOp.AVG) From 25798477718ea888b2117410cab069c518fbd663 Mon Sep 17 00:00:00 2001 From: Cedric Lim Date: Sat, 18 Apr 2026 22:09:07 -0700 Subject: [PATCH 248/335] DDP Fixes for PPLR stuff --- src/quantem/core/ml/models/kplanes.py | 19 +++-------- src/quantem/tomography/object_models.py | 42 ++++++++++++++++--------- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/src/quantem/core/ml/models/kplanes.py b/src/quantem/core/ml/models/kplanes.py index b54fe1ee..d6cc9540 100644 --- a/src/quantem/core/ml/models/kplanes.py +++ b/src/quantem/core/ml/models/kplanes.py @@ -6,7 +6,7 @@ import math from typing import Callable, Optional, Sequence -import tinycudann as tcnn +# import tinycudann as tcnn import torch import torch.nn.functional as F from torch import nn @@ -217,18 +217,7 @@ def __init__( nn.init.zeros_(out.bias) layers.append(out) self.sigma_net = nn.Sequential(*layers) - else: - self.sigma_net = tcnn.Network( - n_input_dims=self.feature_dim, - n_output_dims=1, - network_config={ - "otype": "CutlassMLP", - "activation": "None", - "output_activation": "None", - "n_neurons": 128, - "n_hidden_layers": 0, - }, - ) + def get_densities(self, coords: torch.Tensor): """Computes and returns densities""" @@ -370,10 +359,12 @@ def __init__(self, T: int, init: str = "random"): def as_matrix(self) -> torch.Tensor: """Projects each M to SO(3) via SVD. Returns (T, 3, 3).""" + + U, _, Vh = torch.linalg.svd(self.M) # U: (T,3,3), Vh: (T,3,3) # Fix reflections: det(U Vh) must be +1 d = torch.det(U @ Vh) # (T,) - diag = torch.ones(self.M.shape[0], 3, device=self.M.device) + diag = torch.ones(self.M.shape[0], 3, device=self.M.device, dtype=self.M.dtype) diag[:, 2] = d # multiply last singular vector by sign return U @ (diag.unsqueeze(-1) * Vh) # (T, 3, 3) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 964e0c53..19f26c05 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -20,6 +20,11 @@ from quantem.core.utils.rng import RNGMixin from quantem.tomography.dataset_models import TomographyINRPretrainDataset +def _unwrap(model): + """Unwrap DDP/FSDP/any wrapper that exposes `.module`.""" + while hasattr(model, "module") and isinstance(model.module, torch.nn.Module): + model = model.module + return model class ObjConstraintParams: """ @@ -538,15 +543,16 @@ def get_tv_loss(self, coords: torch.Tensor, pred: torch.Tensor) -> torch.Tensor: `get_volume_tv_loss(coords)` directly. """ tv_loss = torch.tensor(0.0, device=pred.device) - if isinstance(self.model, (KPlanes, KPlanesTILTED)): + inner = _unwrap(self.model) + if isinstance(inner, (KPlanes, KPlanesTILTED)): tv_loss += self._get_plane_tv_loss() # SIREN and other INRs support double-backward → use exact autograd. # Everything else falls through to finite-difference volume TV. - if not isinstance(self.model, PPLR): # adjust to your actual INR class + if not isinstance(inner, PPLR): # adjust to your actual INR class tv_loss += self._get_volume_tv_loss_autograd(coords) - if isinstance(self.model, (KPlanes, KPlanesTILTED)): + if isinstance(inner, (KPlanes, KPlanesTILTED)): tv_loss += self.get_volume_tv_loss(coords) return tv_loss @@ -556,17 +562,18 @@ def get_tv_loss(self, coords: torch.Tensor, pred: torch.Tensor) -> torch.Tensor: # ---------------------------------------------------------------------- def _get_plane_tv_loss(self) -> torch.Tensor: - is_tilted = isinstance(self.model, KPlanesTILTED) + inner = _unwrap(self.model) + is_tilted = isinstance(inner, KPlanesTILTED) per_level = [] - - for p in self.model.grids: + + for p in inner.grids: # p: (3*T, C, H, W) for TILTED, (3, C, H, W) for KPlanes dh = (p[:, :, 1:, :] - p[:, :, :-1, :]).pow(2).mean(dim=(1, 2, 3)) dw = (p[:, :, :, 1:] - p[:, :, :, :-1]).pow(2).mean(dim=(1, 2, 3)) per_plane = dh + dw # (3*T,) or (3,) if is_tilted: - T = self.model.T + T = inner.T per_rotation = per_plane.view(T, 3).sum(dim=1) # sum 3 planes per rotation level_tv = per_rotation.mean() # avg across rotations else: @@ -621,12 +628,13 @@ def get_volume_tv_loss(self, coords: torch.Tensor) -> torch.Tensor: tv_indices = torch.randperm(coords.shape[0], device=coords.device)[:num_tv_samples] tv_coords = coords[tv_indices] # (N, 3) - if hasattr(self.model, "resolution"): - h = 2.0 / min(self.model.resolution) + inner = _unwrap(self.model) + if hasattr(inner, "resolution"): + h = 2.0 / min(inner.resolution) else: h = 1e-2 - pred = self.model(tv_coords) + pred = inner(tv_coords) if isinstance(pred, tuple): pred = pred[0] if pred.dim() == 1: @@ -636,7 +644,7 @@ def get_volume_tv_loss(self, coords: torch.Tensor) -> torch.Tensor: for axis in range(3): offset = torch.zeros(3, device=tv_coords.device) offset[axis] = h - shifted_pred = self.model(tv_coords + offset) + shifted_pred = inner(tv_coords + offset) if isinstance(shifted_pred, tuple): shifted_pred = shifted_pred[0] if shifted_pred.dim() == 1: @@ -696,14 +704,16 @@ def optimizer_params(self, params: OptimizerType | dict[str, OptimizerType] | di if isinstance(params, OptimizerType): self._optimizer_params = params return - if isinstance(self.model, PPLR): + + inner = _unwrap(self.model) + if isinstance(inner, PPLR): if not isinstance(params, dict): raise TypeError(f"optimizer parameters must be a dict for PPLR, got {type(params)}") object_params = params - if set(object_params.keys()) != set(self.model.param_keys): - raise ValueError(f"optimizer parameters keys must match PPLR param_keys, got {object_params.keys()} != {self.model.param_keys}") + if set(object_params.keys()) != set(inner.param_keys): + raise ValueError(f"optimizer parameters keys must match PPLR param_keys, got {object_params.keys()} != {inner.param_keys}") params = {} for key, value in object_params.items(): @@ -725,7 +735,9 @@ def set_optimizer(self, opt_params: OptimizerType | dict | None = None) -> None: Currently supports single LR for all parameters, TODO allow for per parameter LRs by updating get_optimization_parameters to return a list of parameters and their LRs. """ - if not isinstance(self.model, PPLR): + + inner = _unwrap(self.model) + if not isinstance(inner, PPLR): super().set_optimizer(opt_params) return From 7ef264f449d69b6caf773dceccb2becfd3695a09 Mon Sep 17 00:00:00 2001 From: darshan-mali Date: Tue, 21 Apr 2026 10:37:38 -0700 Subject: [PATCH 249/335] Added additional test cases as requested in PR Review --- src/quantem/imaging/lattice.py | 10 ++++++++++ tests/imaging/test_lattice.py | 27 +++++++++++++++++++++++---- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/quantem/imaging/lattice.py b/src/quantem/imaging/lattice.py index 82e0d76f..7253cdcd 100644 --- a/src/quantem/imaging/lattice.py +++ b/src/quantem/imaging/lattice.py @@ -179,6 +179,16 @@ def define_lattice_vectors( ) if not self._lat.shape == (3, 2): raise ValueError("origin, u, v must be in (row, col) format only.") + if not ( + 0 <= origin[0] < self.image.array.shape[0] + and 0 <= origin[1] < self.image.array.shape[1] + ): + raise ValueError("origin must be within the image bounds.") + try: + L = self._lat[1:] + _ = np.linalg.inv(L) + except np.linalg.LinAlgError: + raise ValueError("u, v must be invertible.") # Refine lattice coordinates # Note that we currently assume corners are local maxima diff --git a/tests/imaging/test_lattice.py b/tests/imaging/test_lattice.py index 33a289ed..1b9a7d44 100644 --- a/tests/imaging/test_lattice.py +++ b/tests/imaging/test_lattice.py @@ -18,14 +18,17 @@ def test_init_and_constructor(self): with pytest.raises(RuntimeError, match="Use Lattice.from_data"): Lattice(ds2d) - lattice = Lattice.from_data(image) - assert isinstance(lattice, Lattice) - assert lattice.image is not None + lattice_img = Lattice.from_data(image) + lattice_dset = Lattice.from_data(ds2d) + assert isinstance(lattice_img, Lattice) + assert lattice_img.image is not None + assert isinstance(lattice_dset, Lattice) + assert lattice_dset.image is not None def test_normalization(self): """Test min/max normalization.""" image = np.random.randn(100, 100) * 1000.0 - image[0, 0] = 0.0 + image[0, 0] = -10.0 image[99, 99] = 1000.0 # Both normalizations @@ -37,6 +40,14 @@ def test_normalization(self): lattice = Lattice.from_data(image, normalize_min=False, normalize_max=False) assert_array_almost_equal(lattice.image.array, image) + # Min normalization + lattice = Lattice.from_data(image, normalize_min=True, normalize_max=False) + assert lattice.image.array.min() == 0 + + # Max normalization + lattice = Lattice.from_data(image, normalize_min=False, normalize_max=True) + assert lattice.image.array.max() == 1 + def test_edge_cases(self): """Test NaN handling.""" nan_arr = np.array([[1, np.nan], [3, 4]], dtype=float) @@ -131,6 +142,14 @@ def test_invalid_lattice_params(self): with pytest.raises(ValueError): lattice.define_lattice_vectors(origin=[50, 50], u=[5, 0], v=[0, 5], block_size=-1) + # Origin out of bounds + with pytest.raises(ValueError): + lattice.define_lattice_vectors(origin=[10, 105], u=[5, 0], v=[0, 5]) + + # Non-ivertible lattice vectors + with pytest.raises(ValueError): + lattice.define_lattice_vectors(origin=[50, 50], u=[5, 0], v=[10, 0]) + class TestLatticeSerialize: """Test Lattice Autoserialize implementation.""" From 40bb9ec838e4268f511a6babcf1334134be4978d Mon Sep 17 00:00:00 2001 From: darshan-mali Date: Tue, 21 Apr 2026 11:11:09 -0700 Subject: [PATCH 250/335] Added plotting fixes as requested in PR review --- src/quantem/imaging/lattice_visualization.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/quantem/imaging/lattice_visualization.py b/src/quantem/imaging/lattice_visualization.py index af2781c7..a7d31363 100644 --- a/src/quantem/imaging/lattice_visualization.py +++ b/src/quantem/imaging/lattice_visualization.py @@ -43,6 +43,14 @@ def plot_lattice_vectors( None clips lines to image edges. **kwargs forwarded to show_2d (e.g. cmap, title). """ + # Check if lattice vectors have been defined + if not hasattr(lattice, "_lat"): + raise ValueError( + "Must define lattice vectors first. Call `Lattice.define_lattice_vectors()`" + ) + # Adding a defualt figsize + if "figsize" not in kwargs: + kwargs["figsize"] = (10, 10) fig, ax = show_2d(lattice._image.array, returnfig=True, **kwargs) if ax.images: ax.images[-1].set_zorder(0) @@ -50,10 +58,10 @@ def plot_lattice_vectors( r0, u, v = (np.asarray(x, dtype=float) for x in lattice._lat) ax.scatter( - r0[1], r0[0], s=60, edgecolor=(0, 0, 0), facecolor=(0, 0.5, 0), marker="s", zorder=30 + r0[1], r0[0], s=60, edgecolor=(0, 0, 0), facecolor=(0, 0.5, 0), marker="s", zorder=5 ) - n_vec = int(bound_num_vectors) if bound_num_vectors is not None else 1 + n_vec = int(np.ceil(bound_num_vectors)) if bound_num_vectors is not None else 1 for k in range(1, n_vec + 1): tip = r0 + k * u ax.arrow( @@ -66,7 +74,7 @@ def plot_lattice_vectors( head_length=6.0, linewidth=2.0, color="red", - zorder=20, + zorder=4, ) for k in range(1, n_vec + 1): tip = r0 + k * v @@ -80,7 +88,7 @@ def plot_lattice_vectors( head_length=6.0, linewidth=2.0, color=(0.0, 0.7, 1.0), - zorder=20, + zorder=4, ) if bound_num_vectors is None: From a59baac0208d48f219e555caaa27264b251fe44f Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Wed, 22 Apr 2026 11:11:04 -0700 Subject: [PATCH 251/335] Changes --- src/quantem/core/ml/models/kplanes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quantem/core/ml/models/kplanes.py b/src/quantem/core/ml/models/kplanes.py index d6cc9540..117ea280 100644 --- a/src/quantem/core/ml/models/kplanes.py +++ b/src/quantem/core/ml/models/kplanes.py @@ -164,7 +164,7 @@ def __init__( resolution: Sequence[int] = (200, 200, 200), multiscale_res_multipliers: Optional[Sequence[int]] = None, concat_features: bool = True, - density_activation: Callable = lambda x: F.softplus(x - 1), + density_activation: Callable = lambda x: F.softplus(x - 1), # Keep playing around with this and trunc_exp # Hybrid MLP parameters use_hybrid_mlp: bool = False, hybrid_hidden_dim: int = 64, From 251f3fbfd5b287615fc39dcd5a6d9aa254419489 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Thu, 23 Apr 2026 11:31:51 -0700 Subject: [PATCH 252/335] Reorganizing tensor decomposition methods. Defined a new TensorDecompositionModel ABC, make sure to have a property for which kind of tensor decomposition method is being used. SO3Params are moved to a different file, thinking of making a kplanes_utils.py. Starting reorganization of object_models.py to have ObjectINR and ObjectTensorDecomp --- src/quantem/core/ml/models/kplanes.py | 214 ++------- src/quantem/core/ml/models/model_base.py | 10 + src/quantem/core/ml/models/so3params.py | 183 ++++++++ src/quantem/tomography/object_models.py | 538 +++++++++++++++++++---- 4 files changed, 689 insertions(+), 256 deletions(-) create mode 100644 src/quantem/core/ml/models/so3params.py diff --git a/src/quantem/core/ml/models/kplanes.py b/src/quantem/core/ml/models/kplanes.py index 117ea280..1b0a40ab 100644 --- a/src/quantem/core/ml/models/kplanes.py +++ b/src/quantem/core/ml/models/kplanes.py @@ -11,7 +11,8 @@ import torch.nn.functional as F from torch import nn -from .model_base import PPLR +from .model_base import PPLR, TensorDecompositionModel +from .so3params import SO3ParamQuat, SO3ParamR9SVD """ K-planes utility functions @@ -153,7 +154,7 @@ def interpolate_ms_features( return torch.cat(per_scale, dim=-1) -class KPlanes(nn.Module, PPLR): +class KPlanes(nn.Module, PPLR, TensorDecompositionModel): def __init__( self, @@ -174,7 +175,7 @@ def __init__( Assume coords are [-1, 1] in each dimension. """ super().__init__() - + self._td_type = "kplanes" self.grid_dimensions = grid_dimensions self.input_coords_dims = input_coords_dims self.M_features = M_features @@ -243,184 +244,10 @@ def get_params(self) -> dict[str, list[torch.nn.Parameter]]: def param_keys(self) -> list[str]: return ["grids", "sigma_net"] + @property + def td_type(self) -> str: + return self._td_type -# --- Tilted KPlanes --- - -# --------------------------------------------------------------------------- -# SO(3) quaternion parameter module -# --------------------------------------------------------------------------- - -# class SO3Param(nn.Module): -# """ -# Stores T unit quaternions as learnable parameters in R^4 and normalises -# them on every call to `as_matrix()`. - -# Quaternion convention: [x, y, z, w] (scalar-last, same as scipy). - -# Initialisation -# -------------- -# "random" – uniform sampling over SO(3) via Shoemake's method. -# "identity" – all rotations start as the identity (good for fine-tuning). -# """ - -# def __init__(self, T: int, init: str = "random"): -# super().__init__() -# if T < 1: -# raise ValueError(f"T must be >= 1, got {T}") -# quats = self._init_quaternions(T, init) # (T, 4) -# self.quats = nn.Parameter(quats) - -# # ------------------------------------------------------------------ -# # Initialisers -# # ------------------------------------------------------------------ - -# @staticmethod -# def _shoemake_sample(T: int) -> torch.Tensor: -# """Uniform SO(3) sampling via Shoemake (1992). Returns (T, 4) [x,y,z,w].""" -# u = torch.rand(T, 3) -# sqrt1_u0 = torch.sqrt(1.0 - u[:, 0]) -# sqrt_u0 = torch.sqrt(u[:, 0]) -# two_pi = 2.0 * math.pi -# x = sqrt1_u0 * torch.sin(two_pi * u[:, 1]) -# y = sqrt1_u0 * torch.cos(two_pi * u[:, 1]) -# z = sqrt_u0 * torch.sin(two_pi * u[:, 2]) -# w = sqrt_u0 * torch.cos(two_pi * u[:, 2]) -# return torch.stack([x, y, z, w], dim=-1) # (T, 4) - -# @staticmethod -# def _identity(T: int) -> torch.Tensor: -# """All-identity rotations: [0,0,0,1] * T.""" -# q = torch.zeros(T, 4) -# q[:, 3] = 1.0 -# return q - -# @classmethod -# def _init_quaternions(cls, T: int, init: str) -> torch.Tensor: -# if init == "random": -# return cls._shoemake_sample(T) -# elif init == "identity": -# return cls._identity(T) -# else: -# raise ValueError(f"Unknown init '{init}'; choose 'random' or 'identity'.") - -# # ------------------------------------------------------------------ -# # Forward helpers -# # ------------------------------------------------------------------ - -# def normalized(self) -> torch.Tensor: -# """Returns (T, 4) unit quaternions.""" -# return F.normalize(self.quats, p=2, dim=-1) - -# def as_matrix(self) -> torch.Tensor: -# """ -# Converts the T stored quaternions to (T, 3, 3) rotation matrices. - -# Uses the standard formula; no trig, just multiplications. -# """ -# q = self.normalized() # (T, 4) [x, y, z, w] -# x, y, z, w = q.unbind(dim=-1) # each (T,) - -# # Precompute products -# xx, yy, zz = x*x, y*y, z*z -# xy, xz, yz = x*y, x*z, y*z -# wx, wy, wz = w*x, w*y, w*z - -# # Row-major: R[i,j] -# R = torch.stack([ -# 1 - 2*(yy + zz), 2*(xy - wz), 2*(xz + wy), -# 2*(xy + wz), 1 - 2*(xx + zz), 2*(yz - wx), -# 2*(xz - wy), 2*(yz + wx), 1 - 2*(xx + yy), -# ], dim=-1).reshape(-1, 3, 3) # (T, 3, 3) - -# return R - -# def extra_repr(self) -> str: -# return f"T={self.quats.shape[0]}" - - -class SO3Param(nn.Module): - """ - SO(3) rotation bank using R9+SVD parameterization. - Each rotation is stored as an unconstrained 3x3 matrix M, - projected to SO(3) via SVD+(M) = U diag(1,1,det(UVt)) Vt. - """ - - def __init__(self, T: int, init: str = "random"): - super().__init__() - if init == "random": - # Initialize near identity with small noise - M = torch.eye(3).unsqueeze(0).repeat(T, 1, 1) - M = M + 0.1 * torch.randn(T, 3, 3) - elif init == "identity": - M = torch.eye(3).unsqueeze(0).repeat(T, 1, 1) - else: - raise ValueError(f"Unknown init '{init}'") - self.M = nn.Parameter(M) # (T, 3, 3) - - def as_matrix(self) -> torch.Tensor: - """Projects each M to SO(3) via SVD. Returns (T, 3, 3).""" - - - U, _, Vh = torch.linalg.svd(self.M) # U: (T,3,3), Vh: (T,3,3) - # Fix reflections: det(U Vh) must be +1 - d = torch.det(U @ Vh) # (T,) - diag = torch.ones(self.M.shape[0], 3, device=self.M.device, dtype=self.M.dtype) - diag[:, 2] = d # multiply last singular vector by sign - return U @ (diag.unsqueeze(-1) * Vh) # (T, 3, 3) - -def interpolate_ms_features_tilted( - pts: torch.Tensor, # (B, 3) - ms_grids: nn.ParameterList, # each grid: (3*T, C, H, W) - rotation_matrices: torch.Tensor, # (T, 3, 3) -) -> torch.Tensor: - """ - Fully-vectorized multi-scale, multi-rotation K-Planes feature interpolation. - Returns features of shape (B, C * T * num_scales). - """ - T = rotation_matrices.shape[0] - B = pts.shape[0] - - # (T, B, 3) — rotate all points by all rotations at once - rotated = torch.einsum("tij,bj->tbi", rotation_matrices, pts) - - # Build (T, 3, B, 2) coords for planes XY, ZX, YZ in one shot. - # index_select is faster and cleaner than advanced indexing with python lists. - # Plane axis layout: XY=(0,1), ZX=(2,0), YZ=(1,2) - idx = torch.tensor([[0, 1], - [2, 0], - [1, 2]], device=pts.device) # (3, 2) - # rotated: (T, B, 3) -> gather along last dim with idx (3, 2) - # Result: (T, 3, B, 2) - coords = rotated.unsqueeze(1).expand(T, 3, B, 3).gather( - -1, idx.view(1, 3, 1, 2).expand(T, 3, B, 2) - ) - - # Flatten (T, 3) -> 3*T so it matches grid's first dim, and add the H_out=1 axis - coord_tensor = coords.reshape(3 * T, B, 1, 2) # (3T, B, 1, 2) - - per_scale_features = [] - for plane_coef in ms_grids: - # plane_coef: (3T, C, H, W) - C = plane_coef.shape[1] - - sampled = F.grid_sample( - plane_coef, - coord_tensor, - align_corners=True, - mode="bilinear", - padding_mode="border", - ) # (3T, C, B, 1) - - # (3T, C, B) -> (T, 3, C, B) -> Hadamard across the "3" dim -> (T, C, B) - sampled = sampled.squeeze(-1).view(T, 3, C, B).prod(dim=1) - - # (T, C, B) -> (B, T, C) -> (B, T*C) to concatenate rotations along feature dim - per_scale_features.append( - sampled.permute(2, 0, 1).reshape(B, T * C) - ) - - # Concatenate across scales -> (B, T * C * num_scales) - return torch.cat(per_scale_features, dim=-1) # --------------------------------------------------------------------------- # KPlanesTILTED @@ -475,7 +302,10 @@ def __init__( use_hybrid_mlp: bool = False, hybrid_hidden_dim: int = 64, hybrid_num_layers: int = 2, + so3_param_type: str = "r9svd", ): + + self._td_type = "tilted" if input_coords_dims != 3: raise NotImplementedError("KPlanesTILTED is implemented for 3D only.") if T < 1: @@ -636,6 +466,23 @@ def extra_repr(self) -> str: f"num_scales={len(self.multiscale_res_multipliers)}" ) + def set_so3_param_type(self, so3_param_type: str) -> None: + """ + Set the SO3 parameterization type. + + Parameters + ---------- + so3_param_type : str + SO3 parameterization type ("quat" or "r9svd"). + """ + if so3_param_type == "r9svd": + self.so3 = SO3ParamR9SVD(self.T) + elif so3_param_type == "quat": + self.so3 = SO3ParamQuat(self.T) + else: + raise ValueError(f"Invalid SO3 parameterization type: {so3_param_type}") + + @@ -693,7 +540,7 @@ def interpolate_ms_features_cp_tilted( return torch.cat(per_scale_features, dim=-1) -class CPTilted(nn.Module, PPLR): +class CPTilted(nn.Module, PPLR, TensorDecompositionModel): """ CP decomposition with TILTED rotations — the true bottleneck model for phase 1. Rank-1-per-channel feature representation. @@ -713,6 +560,7 @@ def __init__( density_activation: Callable = lambda x: F.softplus(x - 1), ): super().__init__() + self._td_type = "cp_tilted" self.T = T self.C = C self.multiscale_res_multipliers = list(multiscale_res_multipliers or [1]) @@ -757,5 +605,9 @@ def get_params(self): def param_keys(self): return ["grids", "sigma_net", "so3"] + @property + def td_type(self) -> str: + return self._td_type + def extract_tau_state(self) -> torch.Tensor: return self.so3.M.detach().clone() \ No newline at end of file diff --git a/src/quantem/core/ml/models/model_base.py b/src/quantem/core/ml/models/model_base.py index 42bee71c..44bbdef5 100644 --- a/src/quantem/core/ml/models/model_base.py +++ b/src/quantem/core/ml/models/model_base.py @@ -24,4 +24,14 @@ def param_keys(self) -> list[str]: """ This abstract property should return a list of available parameter keys. """ + pass + +class TensorDecompositionModel(ABC): + + @property + @abstractmethod + def td_type(self) -> str: + """ + This abstract property should return the type of tensor decomposition used. + """ pass \ No newline at end of file diff --git a/src/quantem/core/ml/models/so3params.py b/src/quantem/core/ml/models/so3params.py new file mode 100644 index 00000000..29314fb6 --- /dev/null +++ b/src/quantem/core/ml/models/so3params.py @@ -0,0 +1,183 @@ +import math + +import torch +import torch.nn as nn +import torch.nn.functional as F + +# --- Tilted KPlanes --- + +# --------------------------------------------------------------------------- +# SO(3) quaternion parameter module +# --------------------------------------------------------------------------- + +class SO3ParamQuat(nn.Module): + """ + Stores T unit quaternions as learnable parameters in R^4 and normalises + them on every call to `as_matrix()`. + + Quaternion convention: [x, y, z, w] (scalar-last, same as scipy). + + Initialisation + -------------- + "random" – uniform sampling over SO(3) via Shoemake's method. + "identity" – all rotations start as the identity (good for fine-tuning). + """ + + def __init__(self, T: int, init: str = "random"): + super().__init__() + if T < 1: + raise ValueError(f"T must be >= 1, got {T}") + quats = self._init_quaternions(T, init) # (T, 4) + self.quats = nn.Parameter(quats) + + # ------------------------------------------------------------------ + # Initialisers + # ------------------------------------------------------------------ + + @staticmethod + def _shoemake_sample(T: int) -> torch.Tensor: + """Uniform SO(3) sampling via Shoemake (1992). Returns (T, 4) [x,y,z,w].""" + u = torch.rand(T, 3) + sqrt1_u0 = torch.sqrt(1.0 - u[:, 0]) + sqrt_u0 = torch.sqrt(u[:, 0]) + two_pi = 2.0 * math.pi + x = sqrt1_u0 * torch.sin(two_pi * u[:, 1]) + y = sqrt1_u0 * torch.cos(two_pi * u[:, 1]) + z = sqrt_u0 * torch.sin(two_pi * u[:, 2]) + w = sqrt_u0 * torch.cos(two_pi * u[:, 2]) + return torch.stack([x, y, z, w], dim=-1) # (T, 4) + + @staticmethod + def _identity(T: int) -> torch.Tensor: + """All-identity rotations: [0,0,0,1] * T.""" + q = torch.zeros(T, 4) + q[:, 3] = 1.0 + return q + + @classmethod + def _init_quaternions(cls, T: int, init: str) -> torch.Tensor: + if init == "random": + return cls._shoemake_sample(T) + elif init == "identity": + return cls._identity(T) + else: + raise ValueError(f"Unknown init '{init}'; choose 'random' or 'identity'.") + + # ------------------------------------------------------------------ + # Forward helpers + # ------------------------------------------------------------------ + + def normalized(self) -> torch.Tensor: + """Returns (T, 4) unit quaternions.""" + return F.normalize(self.quats, p=2, dim=-1) + + def as_matrix(self) -> torch.Tensor: + """ + Converts the T stored quaternions to (T, 3, 3) rotation matrices. + + Uses the standard formula; no trig, just multiplications. + """ + q = self.normalized() # (T, 4) [x, y, z, w] + x, y, z, w = q.unbind(dim=-1) # each (T,) + + # Precompute products + xx, yy, zz = x*x, y*y, z*z + xy, xz, yz = x*y, x*z, y*z + wx, wy, wz = w*x, w*y, w*z + + # Row-major: R[i,j] + R = torch.stack([ + 1 - 2*(yy + zz), 2*(xy - wz), 2*(xz + wy), + 2*(xy + wz), 1 - 2*(xx + zz), 2*(yz - wx), + 2*(xz - wy), 2*(yz + wx), 1 - 2*(xx + yy), + ], dim=-1).reshape(-1, 3, 3) # (T, 3, 3) + + return R + + def extra_repr(self) -> str: + return f"T={self.quats.shape[0]}" + + +class SO3ParamR9SVD(nn.Module): + """ + SO(3) rotation bank using R9+SVD parameterization. + Each rotation is stored as an unconstrained 3x3 matrix M, + projected to SO(3) via SVD+(M) = U diag(1,1,det(UVt)) Vt. + """ + + def __init__(self, T: int, init: str = "random"): + super().__init__() + if init == "random": + # Initialize near identity with small noise + M = torch.eye(3).unsqueeze(0).repeat(T, 1, 1) + M = M + 0.1 * torch.randn(T, 3, 3) + elif init == "identity": + M = torch.eye(3).unsqueeze(0).repeat(T, 1, 1) + else: + raise ValueError(f"Unknown init '{init}'") + self.M = nn.Parameter(M) # (T, 3, 3) + + def as_matrix(self) -> torch.Tensor: + """Projects each M to SO(3) via SVD. Returns (T, 3, 3).""" + + + U, _, Vh = torch.linalg.svd(self.M) # U: (T,3,3), Vh: (T,3,3) + # Fix reflections: det(U Vh) must be +1 + d = torch.det(U @ Vh) # (T,) + diag = torch.ones(self.M.shape[0], 3, device=self.M.device, dtype=self.M.dtype) + diag[:, 2] = d # multiply last singular vector by sign + return U @ (diag.unsqueeze(-1) * Vh) # (T, 3, 3) + +def interpolate_ms_features_tilted( + pts: torch.Tensor, # (B, 3) + ms_grids: nn.ParameterList, # each grid: (3*T, C, H, W) + rotation_matrices: torch.Tensor, # (T, 3, 3) +) -> torch.Tensor: + """ + Fully-vectorized multi-scale, multi-rotation K-Planes feature interpolation. + Returns features of shape (B, C * T * num_scales). + """ + T = rotation_matrices.shape[0] + B = pts.shape[0] + + # (T, B, 3) — rotate all points by all rotations at once + rotated = torch.einsum("tij,bj->tbi", rotation_matrices, pts) + + # Build (T, 3, B, 2) coords for planes XY, ZX, YZ in one shot. + # index_select is faster and cleaner than advanced indexing with python lists. + # Plane axis layout: XY=(0,1), ZX=(2,0), YZ=(1,2) + idx = torch.tensor([[0, 1], + [2, 0], + [1, 2]], device=pts.device) # (3, 2) + # rotated: (T, B, 3) -> gather along last dim with idx (3, 2) + # Result: (T, 3, B, 2) + coords = rotated.unsqueeze(1).expand(T, 3, B, 3).gather( + -1, idx.view(1, 3, 1, 2).expand(T, 3, B, 2) + ) + + # Flatten (T, 3) -> 3*T so it matches grid's first dim, and add the H_out=1 axis + coord_tensor = coords.reshape(3 * T, B, 1, 2) # (3T, B, 1, 2) + + per_scale_features = [] + for plane_coef in ms_grids: + # plane_coef: (3T, C, H, W) + C = plane_coef.shape[1] + + sampled = F.grid_sample( + plane_coef, + coord_tensor, + align_corners=True, + mode="bilinear", + padding_mode="border", + ) # (3T, C, B, 1) + + # (3T, C, B) -> (T, 3, C, B) -> Hadamard across the "3" dim -> (T, C, B) + sampled = sampled.squeeze(-1).view(T, 3, C, B).prod(dim=1) + + # (T, C, B) -> (B, T, C) -> (B, T*C) to concatenate rotations along feature dim + per_scale_features.append( + sampled.permute(2, 0, 1).reshape(B, T * C) + ) + + # Concatenate across scales -> (B, T * C * num_scales) + return torch.cat(per_scale_features, dim=-1) \ No newline at end of file diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 19f26c05..8ef2830d 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -20,12 +20,6 @@ from quantem.core.utils.rng import RNGMixin from quantem.tomography.dataset_models import TomographyINRPretrainDataset -def _unwrap(model): - """Unwrap DDP/FSDP/any wrapper that exposes `.module`.""" - while hasattr(model, "module") and isinstance(model.module, torch.nn.Module): - model = model.module - return model - class ObjConstraintParams: """ Namespace class for object reconstruction constraint dataclasses and parsing utilities. @@ -104,17 +98,45 @@ class ObjINRConstraints(Constraints): positivity: bool = True shrinkage: float = 0.0 tv_vol: float = 0.0 - tv_plane: float = 0.0 sparsity: float = 0.0 _name: str = "obj_inr" soft_constraint_keys = ["tv_vol", "tv_plane", "sparsity"] hard_constraint_keys = ["positivity", "shrinkage"] + @dataclass + class ObjTensorDecompConstraints(Constraints): + """ + Constraints for a tensor decomposition object representation. + + Attributes + ---------- + positivity : bool + If ``True``, enforces non-negative values in the reconstruction. + shrinkage : float + Shrinkage regularization strength; pushes values toward zero. + tv_vol : float + Total variation regularization weight for the 3-D volume. + soft_constraint_keys : list[str] + Constraint fields penalized softly during optimization. + hard_constraint_keys : list[str] + Constraint fields enforced strictly during optimization. + """ + + positivity: bool = True + shrinkage: float = 0.0 + tv_vol: float = 0.0 + tv_plane: float = 0.0 + sparsity:float = 0.0 + _name: str = "obj_tensor_decomp" + + soft_constraint_keys = ["tv_vol", "tv_plane", "sparsity"] + hard_constraint_keys = ["positivity", "shrinkage"] + @classmethod def parse_dict( cls, d: dict - ) -> "ObjConstraintParams.ObjPixelatedConstraints | ObjConstraintParams.ObjINRConstraints": + ) -> "ObjConstraintParams.ObjPixelatedConstraints | ObjConstraintParams.ObjINRConstraints | ObjConstraintParams.ObjTensorDecompConstraints": """ Instantiate an object constraint dataclass from a configuration dictionary. @@ -162,12 +184,16 @@ def parse_dict( return ObjConstraintParams.ObjPixelatedConstraints(**d) elif name == "obj_inr": return ObjConstraintParams.ObjINRConstraints(**d) + elif name == "obj_tensor_decomp": + return ObjConstraintParams.ObjTensorDecompConstraints(**d) else: raise ValueError(f"Unknown object constraint type: {name.lower()}") ObjConstraintsType = ( - ObjConstraintParams.ObjPixelatedConstraints | ObjConstraintParams.ObjINRConstraints + ObjConstraintParams.ObjPixelatedConstraints + | ObjConstraintParams.ObjINRConstraints + | ObjConstraintParams.ObjTensorDecompConstraints ) @@ -285,6 +311,7 @@ def get_tv_loss(self, **kwargs) -> torch.Tensor: class ObjectPixelated(ObjectConstraints): + """ Object model for pixelated objects. @@ -424,7 +451,6 @@ def to(self, device: str | torch.device): # pyright: ignore[reportIncompatibleM self.reconnect_optimizer_to_parameters() return self - class ObjectINR(ObjectConstraints, DDPMixin): DEFAULT_CONSTRAINTS = ObjConstraintParams.ObjINRConstraints() @@ -488,6 +514,427 @@ def model(self) -> nn.Module | nn.parallel.DistributedDataParallel: # """ # raise RuntimeError("\n\n\nsetting model, this shouldn't be reachable???\n\n\n") + @property + def obj(self) -> torch.Tensor: + return self._obj + + @obj.setter + def obj(self, obj: torch.Tensor): + self._obj = obj + + @property + def obj_view(self) -> np.ndarray: + """ + Returns the object as a view of the x, y, z axes. + + Matches the axes of conventionally reconstructed objects, this is the object that will be saved. + """ + self.create_volume() + return self._obj.cpu().numpy().transpose(0, 1, 3, 2) + + def apply_soft_constraints( + self, + coords: torch.Tensor, + pred: torch.Tensor, + ) -> torch.Tensor: + soft_loss = torch.tensor(0.0, device=pred.device) + if self.constraints.tv_vol > 0: + num_tv_samples = min(10_000, coords.shape[0]) + tv_indices = torch.randperm(coords.shape[0], device=coords.device)[:num_tv_samples] + + tv_coords = coords[tv_indices].detach().requires_grad_(True) + tv_densities_recomputed = self.model(tv_coords) + if isinstance(tv_densities_recomputed, tuple): + tv_densities_recomputed = tv_densities_recomputed[0] + + # Ensure shape is [num_samples, num_channels] + if tv_densities_recomputed.dim() == 1: + tv_densities_recomputed = tv_densities_recomputed.unsqueeze(-1) + + # Compute gradients for each channel + grad_outputs = torch.autograd.grad( + outputs=tv_densities_recomputed, + inputs=tv_coords, + grad_outputs=torch.ones_like(tv_densities_recomputed), + create_graph=True, + )[0] # Shape: [num_samples, coord_dim] + + # Compute TV loss - gradient magnitude per sample + grad_norm = torch.norm(grad_outputs, dim=1) # Shape: [num_samples] + soft_loss += self.constraints.tv_vol * grad_norm.mean() + + if ( + isinstance(self.constraints, ObjConstraintParams.ObjINRConstraints) + and self.constraints.sparsity > 0 + ): # NOTE: For the linter, I must make this :) + sparsity_loss = self.constraints.sparsity * torch.norm(pred, p=1) + soft_loss += sparsity_loss + + return soft_loss + + def apply_hard_constraints(self, pred: torch.Tensor) -> torch.Tensor: + """ + Apply hard constraints to the predicted values of the INR model. + """ + + if self.constraints.positivity: + pred = torch.clamp(pred, min=0.0, max=None) + if self.constraints.shrinkage: + pred = torch.max(pred - self.constraints.shrinkage, torch.zeros_like(pred)) + + return pred + + # --- Optimization Parameters --- + @property + def params(self) -> Generator[torch.nn.Parameter, None, None]: + return self.model.parameters() # type: ignore[attr-defined] + + def get_optimization_parameters(self) -> list[nn.Parameter]: + return list(self.params) + + # Pretraining + @property + def pretrained_weights(self) -> dict[str, torch.Tensor]: + """get the pretrained weights of the INR model""" + return self._pretrained_weights + + def _set_pretrained_weights(self, model: "torch.nn.Module"): + """set the pretrained weights of the INR model""" + if not isinstance(model, torch.nn.Module): + raise TypeError(f"Pretrained model must be a torch.nn.Module, got {type(model)}") + self._pretrained_weights = deepcopy(model.state_dict()) + + @property + def pretrain_target(self) -> TomographyINRPretrainDataset: + """get the pretrain target""" + return self._pretrain_target + + @pretrain_target.setter + def pretrain_target(self, target: TomographyINRPretrainDataset): + """set the pretrain target""" + self._pretrain_target = target + + @property + def dtype(self) -> torch.dtype: + """ + Returns the dtype of the object. + """ + # TODO: This is a temporary solution to get the dtype of the object. + return torch.float32 + + @property + def shape(self) -> tuple[int, int, int]: + return self._shape + + @shape.setter + def shape(self, shape: tuple[int, int, int]): + self._shape = shape + + # --- Helper Functions --- + def rebuild_model(self): + self._model = self.distribute_model(self._model) + + # Reset method that goes back to the pretrained weights. + def reset(self): + """reset the model to the pretrained weights""" + self.model.load_state_dict(self._pretrained_weights.copy()) + self._model = self.distribute_model( + self.model + ) # Maybe add a check to see if distributed or not, but not very computationally expensive to do this. + + # --- Forward Method --- + + def forward(self, coords: torch.Tensor) -> torch.Tensor: + """forward pass for the INR model""" + all_densities = self.model(coords) + + if all_densities.dim() > 1: + all_densities = all_densities.squeeze(-1) + valid_mask = ( + (coords[:, 0] >= -1) & (coords[:, 0] <= 1) & (coords[:, 1] >= -1) & (coords[:, 1] <= 1) + ).float() + + if all_densities.dim() > 1: + valid_mask = valid_mask.unsqueeze(-1) + # Multi-dimensional mask + all_densities = all_densities * valid_mask + + all_densities = self.apply_hard_constraints(all_densities) + + return all_densities + + # Pretrain Loop + + def pretrain( + self, + pretrain_dataset: TomographyINRPretrainDataset, + batch_size: int, + reset: bool = False, + num_iters: int = 10, + num_workers: int = 0, + optimizer_params: dict | None = None, + scheduler_params: dict | None = None, + loss_fn: Callable | str = "l1", + verbose: bool = True, + ): + """ + Pretrain the INR model to fit target volume. + """ + + if ( + pretrain_dataset is not None + ): # Need to make a check if there's already a pretrain dataset to not go through with the setup again. + self.pretrain_dataset = pretrain_dataset + ( + self.pretraining_dataloader, + self.pretraining_sampler, + self.pretraining_val_dataloader, + self.pretraining_val_sampler, + ) = self.setup_dataloader(pretrain_dataset, batch_size, num_workers=num_workers) + + if optimizer_params is not None: + self.set_optimizer(optimizer_params) + if scheduler_params is not None: + self.set_scheduler(scheduler_params, num_iters) + + if reset: + self.reset() + + loss_fn = get_loss_module(loss_fn, self.dtype) + + self._pretrain( + num_iters=num_iters, + loss_fn=loss_fn, + verbose=verbose, + ) + + def _pretrain( + self, + num_iters: int, + loss_fn: Callable, + verbose: bool, + ): + if self.optimizer is None: + raise RuntimeError("Optimizer not set. Call set_optimizer() first.") + if self.scheduler is None: + raise RuntimeError("Scheduler not set. Call set_scheduler() first.") + + self.model.train() + optimizer = self.optimizer + scheduler = self.scheduler + + pbar = tqdm(range(num_iters), desc="Pretraining", disable=not verbose) + for a0 in pbar: + epoch_loss = 0 + for batch_idx, batch in enumerate[Any](self.pretraining_dataloader): + coords = batch["coords"].to(self.device, non_blocking=True) + target = batch["target"].to(self.device, non_blocking=True) + + with torch.autocast( + device_type=self.device.type, dtype=torch.bfloat16, enabled=True + ): + outputs = self.forward(coords) + loss = loss_fn(outputs, target) + + loss.backward() + epoch_loss += loss.item() + + # Clip gradients + torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm=1.0) + + optimizer.step() + optimizer.zero_grad() + + if scheduler is not None: + if isinstance(scheduler, torch.optim.lr_scheduler.ReduceLROnPlateau): + scheduler.step(epoch_loss) + else: + scheduler.step() + + self._pretrain_losses.append(epoch_loss / len(self.pretraining_dataloader)) + print( + f"Epoch {a0 + 1}/{num_iters}, Pretrain Loss: {epoch_loss / len(self.pretraining_dataloader):.4f}" + ) + self._pretrain_lrs.append(optimizer.param_groups[0]["lr"]) + + def create_volume(self, return_vol: bool = False): + N = max(self._shape) + with torch.no_grad(): + coords_1d = torch.linspace(-1, 1, N) + x, y, z = torch.meshgrid(coords_1d, coords_1d, coords_1d, indexing="ij") + inputs = torch.stack([x, y, z], dim=-1).reshape(-1, 3) + model = self.model.module if isinstance(self.model, nn.DataParallel) else self.model + + inference_batch_size = 5 * N * N + total_samples = N**3 + samples_per_gpu = total_samples // self.world_size + remainder = total_samples % self.world_size + + if self.global_rank < remainder: + start_idx = self.global_rank * (samples_per_gpu + 1) + end_idx = start_idx + samples_per_gpu + 1 + else: + start_idx = self.global_rank * samples_per_gpu + remainder + end_idx = start_idx + samples_per_gpu + + inputs_subset = inputs[start_idx:end_idx] + num_samples = inputs_subset.shape[0] + + outputs_list = [] + for batch_start in range(0, num_samples, inference_batch_size): + batch_end = min(batch_start + inference_batch_size, num_samples) + batch_coords = inputs_subset[batch_start:batch_end].to( + self.device, non_blocking=True + ) + + batch_outputs = model(batch_coords) # (B, C) or (B,) etc. + + if isinstance(batch_outputs, tuple): + batch_outputs = batch_outputs[0] + batch_outputs = self.apply_hard_constraints(batch_outputs) + + # Ensure shape is (B, C) + if batch_outputs.dim() == 1: + batch_outputs = batch_outputs.unsqueeze(-1) # (B, 1) + + outputs_list.append(batch_outputs.cpu()) + + outputs = torch.cat(outputs_list, dim=0) # (local_B, C) + C = outputs.shape[-1] # e.g. 5 + + if self.world_size > 1: + # gather variable-sized first dimension (local_B) while keeping channels + local_B = outputs.shape[0] + output_size = torch.tensor(local_B, device=self.device, dtype=torch.long) + all_sizes = [ + torch.zeros(1, device=self.device, dtype=torch.long) + for _ in range(self.world_size) + ] + dist.all_gather(all_sizes, output_size) + max_size = max(size.item() for size in all_sizes) + + outputs_dev = outputs.to(self.device) # (local_B, C) + if local_B < max_size: + pad = torch.zeros( + (max_size - local_B, C), # type: ignore + device=self.device, + dtype=outputs_dev.dtype, + ) + outputs_padded = torch.cat([outputs_dev, pad], dim=0) # (max_size, C) + else: + outputs_padded = outputs_dev + + gathered_outputs = [ + torch.empty((max_size, C), device=self.device, dtype=outputs_dev.dtype) # type: ignore + for _ in range(self.world_size) + ] + dist.all_gather(gathered_outputs, outputs_padded.contiguous()) + + trimmed_outputs = [] + for rank, size in enumerate(all_sizes): + trimmed_outputs.append(gathered_outputs[rank][: size.item(), :]) + + pred_full = torch.cat(trimmed_outputs, dim=0).reshape(C, N, N, N).float() + else: + pred_full = outputs.reshape(C, N, N, N).float() + + if return_vol: + return pred_full.detach().cpu() + + self._obj = pred_full.detach().cpu() + + def get_tv_loss( # pyright: ignore[reportIncompatibleMethodOverride] + self, + coords: torch.Tensor, + ) -> torch.Tensor: + tv_loss = torch.tensor(0.0, device=coords.device) + + num_tv_samples = min(10000, coords.shape[0]) + tv_indices = torch.randperm(coords.shape[0], device=coords.device)[:num_tv_samples] + + tv_coords = coords[tv_indices].detach().requires_grad_(True) + + tv_densities_recomputed = self.forward(tv_coords) + + if tv_densities_recomputed.dim() > 1: + tv_densities_recomputed = tv_densities_recomputed.squeeze(-1) + + grad_outputs = torch.autograd.grad( + outputs=tv_densities_recomputed, + inputs=tv_coords, + grad_outputs=torch.ones_like(tv_densities_recomputed), + create_graph=True, + )[0] + + grad_norm = torch.norm(grad_outputs, dim=1) + + tv_loss += self.constraints.tv_vol * grad_norm.mean() + return tv_loss + + def to(self, device: str | torch.device): # pyright: ignore[reportIncompatibleMethodOverride] -> better to do this device change + if isinstance(device, str): + device = torch.device(device) + self._device = device + if self.world_size == 1: + self._model = self._model.to(device) + elif not isinstance(self._model, torch.nn.parallel.DistributedDataParallel): + self.distribute_model(self._model) + self.reconnect_optimizer_to_parameters() + + +class ObjectTensorDecomp(ObjectConstraints, DDPMixin): + DEFAULT_CONSTRAINTS = ObjConstraintParams.ObjTensorDecompConstraints() + + def __init__( + self, + shape: tuple[int, int, int], + device: str = "cpu", + rng: np.random.Generator | int | None = None, + model: nn.Module | None = None, + _token: object | None = None, + ): + super().__init__( + shape=shape, + device=device, + rng=rng, + _token=self._token, + ) + self._pretrain_losses = [] + self._pretrain_lrs = [] + self.constraints: ObjConstraintsType = self.DEFAULT_CONSTRAINTS.copy() + # Register the network submodule (important: real nn.Module attribute) + if model is not None: + self.setup_distributed(device=device) + self._model = self.distribute_model(model) + + @classmethod + def from_model( + cls, + model: nn.Module, + shape: tuple[int, int, int], + device: str = "cpu", + rng: np.random.Generator | int | None = None, + ): + obj_model = cls( + shape=shape, + device=device, + rng=rng, + model=model, # ✅ build/register in __init__ + ) + + obj_model.setup_distributed(device=device) + obj_model.to(device) + return obj_model + + # --- Properties --- + + @property + def model(self) -> nn.Module | nn.parallel.DistributedDataParallel: + """ + Returns the INR model. + """ + return self._model + @property def obj(self) -> torch.Tensor: return self._obj @@ -527,53 +974,29 @@ def apply_soft_constraints( soft_loss += sparsity_loss return soft_loss + # TV Losses def get_tv_loss(self, coords: torch.Tensor, pred: torch.Tensor) -> torch.Tensor: - """ - Dispatch to the appropriate TV loss based on model architecture. - - KPlanes / KPlanesTILTED: per-plane TV (regularizes the stored feature - planes directly). Cheap; regularizes the representation. - - CPTilted: per-line TV (same idea, applied to 1D feature lines). - - SIREN / other INRs: volume TV via autograd (exact gradient). - - Fallback: volume TV via finite differences (works anywhere). - - If you want *volume* smoothness regardless of architecture, call - `get_volume_tv_loss(coords)` directly. - """ tv_loss = torch.tensor(0.0, device=pred.device) - inner = _unwrap(self.model) - if isinstance(inner, (KPlanes, KPlanesTILTED)): - tv_loss += self._get_plane_tv_loss() - - # SIREN and other INRs support double-backward → use exact autograd. - # Everything else falls through to finite-difference volume TV. - if not isinstance(inner, PPLR): # adjust to your actual INR class - tv_loss += self._get_volume_tv_loss_autograd(coords) - - if isinstance(inner, (KPlanes, KPlanesTILTED)): - tv_loss += self.get_volume_tv_loss(coords) + tv_loss += self._get_plane_tv_loss() + tv_loss += self.get_volume_tv_loss(coords) return tv_loss - # ---------------------------------------------------------------------- - # Plane TV (K-Planes family) — regularizes the stored feature planes. - # ---------------------------------------------------------------------- - def _get_plane_tv_loss(self) -> torch.Tensor: - inner = _unwrap(self.model) - is_tilted = isinstance(inner, KPlanesTILTED) + is_tilted = isinstance(self.model, KPlanesTILTED) per_level = [] - for p in inner.grids: + for p in self.model.grids: # p: (3*T, C, H, W) for TILTED, (3, C, H, W) for KPlanes dh = (p[:, :, 1:, :] - p[:, :, :-1, :]).pow(2).mean(dim=(1, 2, 3)) dw = (p[:, :, :, 1:] - p[:, :, :, :-1]).pow(2).mean(dim=(1, 2, 3)) per_plane = dh + dw # (3*T,) or (3,) if is_tilted: - T = inner.T + T = self.model.T per_rotation = per_plane.view(T, 3).sum(dim=1) # sum 3 planes per rotation level_tv = per_rotation.mean() # avg across rotations else: @@ -584,40 +1007,6 @@ def _get_plane_tv_loss(self) -> torch.Tensor: return self.constraints.tv_plane * torch.stack(per_level).sum() - # ---------------------------------------------------------------------- - # Volume TV (autograd) — exact gradient, needs double-backward support. - # ---------------------------------------------------------------------- - - def _get_volume_tv_loss_autograd(self, coords: torch.Tensor) -> torch.Tensor: - """ - Isotropic volume TV using autograd. Exact gradient, but requires the - model to support double-backward (SIREN yes, grid_sample-based models no). - """ - num_tv_samples = min(10_000, coords.shape[0]) - tv_indices = torch.randperm(coords.shape[0], device=coords.device)[:num_tv_samples] - - tv_coords = coords[tv_indices].detach().requires_grad_(True) - pred = self.model(tv_coords) - if isinstance(pred, tuple): - pred = pred[0] - if pred.dim() == 1: - pred = pred.unsqueeze(-1) - - grad_outputs = torch.autograd.grad( - outputs=pred, - inputs=tv_coords, - grad_outputs=torch.ones_like(pred), - create_graph=True, - )[0] # (N, 3) - - grad_norm = torch.norm(grad_outputs, dim=1) # (N,) - return self.constraints.tv_vol * grad_norm.mean() - - - # ---------------------------------------------------------------------- - # Volume TV (finite differences) — works for any model. - # ---------------------------------------------------------------------- - def get_volume_tv_loss(self, coords: torch.Tensor) -> torch.Tensor: """ Isotropic volume TV via finite differences. Same form as the autograd @@ -1073,5 +1462,4 @@ def to(self, device: str | torch.device): # pyright: ignore[reportIncompatibleM self.distribute_model(self._model) self.reconnect_optimizer_to_parameters() - ObjectModelType = ObjectPixelated | ObjectINR From e33fec1ebe0119565979ba9198f566dbab1a3887 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Thu, 23 Apr 2026 11:54:52 -0700 Subject: [PATCH 253/335] Removed some the _unwrap dependencies. Added ObjectTensorDecomp on top-level Tomography --- src/quantem/core/ml/models/kplanes.py | 66 ++++++++++++++++++++--- src/quantem/core/ml/models/so3params.py | 53 ------------------ src/quantem/tomography/object_models.py | 19 +++---- src/quantem/tomography/tomography.py | 5 +- src/quantem/tomography/tomography_base.py | 3 +- 5 files changed, 71 insertions(+), 75 deletions(-) diff --git a/src/quantem/core/ml/models/kplanes.py b/src/quantem/core/ml/models/kplanes.py index 1b0a40ab..58d6fc81 100644 --- a/src/quantem/core/ml/models/kplanes.py +++ b/src/quantem/core/ml/models/kplanes.py @@ -253,6 +253,60 @@ def td_type(self) -> str: # KPlanesTILTED # --------------------------------------------------------------------------- +def interpolate_ms_features_tilted( + pts: torch.Tensor, # (B, 3) + ms_grids: nn.ParameterList, # each grid: (3*T, C, H, W) + rotation_matrices: torch.Tensor, # (T, 3, 3) +) -> torch.Tensor: + """ + Fully-vectorized multi-scale, multi-rotation K-Planes feature interpolation. + Returns features of shape (B, C * T * num_scales). + """ + T = rotation_matrices.shape[0] + B = pts.shape[0] + + # (T, B, 3) — rotate all points by all rotations at once + rotated = torch.einsum("tij,bj->tbi", rotation_matrices, pts) + + # Build (T, 3, B, 2) coords for planes XY, ZX, YZ in one shot. + # index_select is faster and cleaner than advanced indexing with python lists. + # Plane axis layout: XY=(0,1), ZX=(2,0), YZ=(1,2) + idx = torch.tensor([[0, 1], + [2, 0], + [1, 2]], device=pts.device) # (3, 2) + # rotated: (T, B, 3) -> gather along last dim with idx (3, 2) + # Result: (T, 3, B, 2) + coords = rotated.unsqueeze(1).expand(T, 3, B, 3).gather( + -1, idx.view(1, 3, 1, 2).expand(T, 3, B, 2) + ) + + # Flatten (T, 3) -> 3*T so it matches grid's first dim, and add the H_out=1 axis + coord_tensor = coords.reshape(3 * T, B, 1, 2) # (3T, B, 1, 2) + + per_scale_features = [] + for plane_coef in ms_grids: + # plane_coef: (3T, C, H, W) + C = plane_coef.shape[1] + + sampled = F.grid_sample( + plane_coef, + coord_tensor, + align_corners=True, + mode="bilinear", + padding_mode="border", + ) # (3T, C, B, 1) + + # (3T, C, B) -> (T, 3, C, B) -> Hadamard across the "3" dim -> (T, C, B) + sampled = sampled.squeeze(-1).view(T, 3, C, B).prod(dim=1) + + # (T, C, B) -> (B, T, C) -> (B, T*C) to concatenate rotations along feature dim + per_scale_features.append( + sampled.permute(2, 0, 1).reshape(B, T * C) + ) + + # Concatenate across scales -> (B, T * C * num_scales) + return torch.cat(per_scale_features, dim=-1) + class KPlanesTILTED(KPlanes): """ K-Planes with T learned SO(3) rotations (TILTED). @@ -352,7 +406,7 @@ def __init__( self._build_sigma_net(use_hybrid_mlp, hybrid_hidden_dim, hybrid_num_layers) # ---- Learnable rotations ---- - self.so3 = SO3Param(T, init=tau_init) + self.set_so3_param_type(so3_param_type, init=tau_init) # ------------------------------------------------------------------ # Internal helpers @@ -466,7 +520,7 @@ def extra_repr(self) -> str: f"num_scales={len(self.multiscale_res_multipliers)}" ) - def set_so3_param_type(self, so3_param_type: str) -> None: + def set_so3_param_type(self, so3_param_type: str, init: str = "rand") -> None: """ Set the SO3 parameterization type. @@ -476,17 +530,13 @@ def set_so3_param_type(self, so3_param_type: str) -> None: SO3 parameterization type ("quat" or "r9svd"). """ if so3_param_type == "r9svd": - self.so3 = SO3ParamR9SVD(self.T) + self.so3 = SO3ParamR9SVD(self.T, init=init) elif so3_param_type == "quat": - self.so3 = SO3ParamQuat(self.T) + self.so3 = SO3ParamQuat(self.T, init=init) else: raise ValueError(f"Invalid SO3 parameterization type: {so3_param_type}") - - - - # CP Decomp for Warmup SO3 rotations def interpolate_ms_features_cp_tilted( diff --git a/src/quantem/core/ml/models/so3params.py b/src/quantem/core/ml/models/so3params.py index 29314fb6..14f60867 100644 --- a/src/quantem/core/ml/models/so3params.py +++ b/src/quantem/core/ml/models/so3params.py @@ -128,56 +128,3 @@ def as_matrix(self) -> torch.Tensor: diag[:, 2] = d # multiply last singular vector by sign return U @ (diag.unsqueeze(-1) * Vh) # (T, 3, 3) -def interpolate_ms_features_tilted( - pts: torch.Tensor, # (B, 3) - ms_grids: nn.ParameterList, # each grid: (3*T, C, H, W) - rotation_matrices: torch.Tensor, # (T, 3, 3) -) -> torch.Tensor: - """ - Fully-vectorized multi-scale, multi-rotation K-Planes feature interpolation. - Returns features of shape (B, C * T * num_scales). - """ - T = rotation_matrices.shape[0] - B = pts.shape[0] - - # (T, B, 3) — rotate all points by all rotations at once - rotated = torch.einsum("tij,bj->tbi", rotation_matrices, pts) - - # Build (T, 3, B, 2) coords for planes XY, ZX, YZ in one shot. - # index_select is faster and cleaner than advanced indexing with python lists. - # Plane axis layout: XY=(0,1), ZX=(2,0), YZ=(1,2) - idx = torch.tensor([[0, 1], - [2, 0], - [1, 2]], device=pts.device) # (3, 2) - # rotated: (T, B, 3) -> gather along last dim with idx (3, 2) - # Result: (T, 3, B, 2) - coords = rotated.unsqueeze(1).expand(T, 3, B, 3).gather( - -1, idx.view(1, 3, 1, 2).expand(T, 3, B, 2) - ) - - # Flatten (T, 3) -> 3*T so it matches grid's first dim, and add the H_out=1 axis - coord_tensor = coords.reshape(3 * T, B, 1, 2) # (3T, B, 1, 2) - - per_scale_features = [] - for plane_coef in ms_grids: - # plane_coef: (3T, C, H, W) - C = plane_coef.shape[1] - - sampled = F.grid_sample( - plane_coef, - coord_tensor, - align_corners=True, - mode="bilinear", - padding_mode="border", - ) # (3T, C, B, 1) - - # (3T, C, B) -> (T, 3, C, B) -> Hadamard across the "3" dim -> (T, C, B) - sampled = sampled.squeeze(-1).view(T, 3, C, B).prod(dim=1) - - # (T, C, B) -> (B, T, C) -> (B, T*C) to concatenate rotations along feature dim - per_scale_features.append( - sampled.permute(2, 0, 1).reshape(B, T * C) - ) - - # Concatenate across scales -> (B, T * C * num_scales) - return torch.cat(per_scale_features, dim=-1) \ No newline at end of file diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 8ef2830d..0eda0bb9 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -1017,13 +1017,12 @@ def get_volume_tv_loss(self, coords: torch.Tensor) -> torch.Tensor: tv_indices = torch.randperm(coords.shape[0], device=coords.device)[:num_tv_samples] tv_coords = coords[tv_indices] # (N, 3) - inner = _unwrap(self.model) - if hasattr(inner, "resolution"): - h = 2.0 / min(inner.resolution) + if hasattr(self.model, "resolution"): + h = 2.0 / min(self.model.resolution) else: h = 1e-2 - pred = inner(tv_coords) + pred = self.model(tv_coords) if isinstance(pred, tuple): pred = pred[0] if pred.dim() == 1: @@ -1033,7 +1032,7 @@ def get_volume_tv_loss(self, coords: torch.Tensor) -> torch.Tensor: for axis in range(3): offset = torch.zeros(3, device=tv_coords.device) offset[axis] = h - shifted_pred = inner(tv_coords + offset) + shifted_pred = self.model(tv_coords + offset) if isinstance(shifted_pred, tuple): shifted_pred = shifted_pred[0] if shifted_pred.dim() == 1: @@ -1094,15 +1093,14 @@ def optimizer_params(self, params: OptimizerType | dict[str, OptimizerType] | di self._optimizer_params = params return - inner = _unwrap(self.model) - if isinstance(inner, PPLR): + if isinstance(self.model, PPLR): if not isinstance(params, dict): raise TypeError(f"optimizer parameters must be a dict for PPLR, got {type(params)}") object_params = params - if set(object_params.keys()) != set(inner.param_keys): - raise ValueError(f"optimizer parameters keys must match PPLR param_keys, got {object_params.keys()} != {inner.param_keys}") + if set(object_params.keys()) != set(self.model.param_keys): + raise ValueError(f"optimizer parameters keys must match PPLR param_keys, got {object_params.keys()} != {self.model.param_keys}") params = {} for key, value in object_params.items(): @@ -1125,8 +1123,7 @@ def set_optimizer(self, opt_params: OptimizerType | dict | None = None) -> None: updating get_optimization_parameters to return a list of parameters and their LRs. """ - inner = _unwrap(self.model) - if not isinstance(inner, PPLR): + if not isinstance(self.model, PPLR): super().set_optimizer(opt_params) return diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index a96136df..826ad360 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -26,6 +26,7 @@ ObjConstraintsType, ObjectINR, ObjectPixelated, + ObjectTensorDecomp, ) from quantem.tomography.radon.radon import iradon_torch, radon_torch from quantem.tomography.tomography_base import TomographyBase @@ -42,7 +43,7 @@ class Tomography(TomographyOpt, TomographyBase): def from_models( cls, dset: DatasetModelType, - obj_model: ObjectINR, + obj_model: ObjectINR | ObjectTensorDecomp, logger: LoggerTomography | None = None, device: str = "cuda", verbose: int | bool = True, @@ -180,7 +181,7 @@ def reconstruct( consistency_loss = torch.tensor(0.0, device=self.device) total_loss = torch.tensor(0.0, device=self.device) epoch_soft_constraint_loss = torch.tensor(0.0, device=self.device) - if isinstance(self.obj_model, ObjectINR): + if isinstance(self.obj_model, ObjectINR) or isinstance(self.obj_model, ObjectTensorDecomp): self.obj_model.model.train() else: raise NotImplementedError( diff --git a/src/quantem/tomography/tomography_base.py b/src/quantem/tomography/tomography_base.py index 58909bdf..419b5ede 100644 --- a/src/quantem/tomography/tomography_base.py +++ b/src/quantem/tomography/tomography_base.py @@ -11,6 +11,7 @@ ObjConstraintsType, ObjectINR, ObjectModelType, + ObjectTensorDecomp, ) @@ -51,7 +52,7 @@ def __init__( self._val_losses: list[float] = [] self._lrs: dict[str, list] = {} # DDP Initialization - if isinstance(obj_model, ObjectINR): + if isinstance(obj_model, ObjectINR) or isinstance(obj_model, ObjectTensorDecomp): self.setup_distributed(device=device) if self.global_rank == 0: print("Setting up DDP for obj_model") From 15bdcd4bc99f8e62999d033bfce46d8e9c45c3e2 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Thu, 23 Apr 2026 16:48:45 -0700 Subject: [PATCH 254/335] Revamped model_base.py to cover type-hinting stuff. KPlanes has a set of parameters now that helps with type-setting. The main reason for having model_base.py as is it is right now is if we ever wanted to go do TensoRF or something just to validate --- src/quantem/core/ml/models/__init__.py | 0 src/quantem/core/ml/models/kplanes.py | 55 +++++++++- src/quantem/core/ml/models/model_base.py | 44 +++++--- src/quantem/tomography/object_models.py | 131 ++++------------------- 4 files changed, 100 insertions(+), 130 deletions(-) create mode 100644 src/quantem/core/ml/models/__init__.py diff --git a/src/quantem/core/ml/models/__init__.py b/src/quantem/core/ml/models/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/quantem/core/ml/models/kplanes.py b/src/quantem/core/ml/models/kplanes.py index 58d6fc81..16c56575 100644 --- a/src/quantem/core/ml/models/kplanes.py +++ b/src/quantem/core/ml/models/kplanes.py @@ -154,7 +154,7 @@ def interpolate_ms_features( return torch.cat(per_scale, dim=-1) -class KPlanes(nn.Module, PPLR, TensorDecompositionModel): +class KPlanes(PPLR, TensorDecompositionModel): def __init__( self, @@ -248,6 +248,42 @@ def param_keys(self) -> list[str]: def td_type(self) -> str: return self._td_type + @td_type.setter + def td_type(self, td_type: str): + if not isinstance(td_type, str): + raise TypeError("td_type must be a string") + self._td_type = td_type + + @property + def tilted(self) -> bool: + return False + + @tilted.setter + def tilted(self, tilted: bool): + if not isinstance(tilted, bool): + raise TypeError("tilted must be a boolean") + self._tilted = tilted + + @property + def grids(self) -> torch.nn.ParameterList: + return self._grids + + @grids.setter + def grids(self, grids: torch.nn.ParameterList): + if not isinstance(grids, torch.nn.ParameterList): + raise TypeError("Grids must be a ParameterList") + self._grids = grids + + @property + def resolution(self) -> list[int]: + return self._resolution + + @resolution.setter + def resolution(self, resolution: Sequence[int]): + if not isinstance(resolution, Sequence): + raise TypeError("Resolution must be a sequence") + self._resolution = list(resolution) + # --------------------------------------------------------------------------- # KPlanesTILTED @@ -535,6 +571,10 @@ def set_so3_param_type(self, so3_param_type: str, init: str = "rand") -> None: self.so3 = SO3ParamQuat(self.T, init=init) else: raise ValueError(f"Invalid SO3 parameterization type: {so3_param_type}") + + @property + def tilted(self) -> bool: + return True # CP Decomp for Warmup SO3 rotations @@ -590,7 +630,7 @@ def interpolate_ms_features_cp_tilted( return torch.cat(per_scale_features, dim=-1) -class CPTilted(nn.Module, PPLR, TensorDecompositionModel): +class CPTilted(PPLR, TensorDecompositionModel): """ CP decomposition with TILTED rotations — the true bottleneck model for phase 1. Rank-1-per-channel feature representation. @@ -660,4 +700,13 @@ def td_type(self) -> str: return self._td_type def extract_tau_state(self) -> torch.Tensor: - return self.so3.M.detach().clone() \ No newline at end of file + return self.so3.M.detach().clone() + + @property + def tilted(self) -> bool: + return True + + + + +KPlanesType = KPlanes | KPlanesTILTED | CPTilted \ No newline at end of file diff --git a/src/quantem/core/ml/models/model_base.py b/src/quantem/core/ml/models/model_base.py index 44bbdef5..e1a486d7 100644 --- a/src/quantem/core/ml/models/model_base.py +++ b/src/quantem/core/ml/models/model_base.py @@ -2,36 +2,50 @@ from typing import Dict import torch +import torch.nn as nn class PPLR(ABC): """ Abstract base class for models that require multi-scale parameter optimization. """ + @abstractmethod - def get_params(self) -> Dict[str, list[torch.nn.Parameter]]: + def get_params(self) -> Dict[str, list[nn.Parameter]]: """ - This abstract method should return a dictionary of parameters based on a key. + Return a dictionary of parameters grouped by key. - For example if your nn.Module has multiple optimizable parameter groups, - you can return a dictionary with the keys "grids" and "sigma_net" (KPlanes example). + For example if your nn.Module has multiple optimizable parameter groups, + you can return a dictionary with the keys "grids" and "sigma_net" + (KPlanes example). """ pass @property @abstractmethod def param_keys(self) -> list[str]: - """ - This abstract property should return a list of available parameter keys. - """ + """List of available parameter-group keys.""" pass -class TensorDecompositionModel(ABC): - @property - @abstractmethod - def td_type(self) -> str: - """ - This abstract property should return the type of tensor decomposition used. - """ - pass \ No newline at end of file +class TensorDecompositionModel(nn.Module, ABC): + """ + Base class for factored tensor-decomposition models. + + Subclasses must set ``td_type`` as a normal attribute in ``__init__``. + """ + + td_type: str + + +class PlanarDecompositionModel(TensorDecompositionModel): + """ + Planar factored-grid models: K-Planes, K-Planes-TILTED, HexPlane, tri-planes. + + Subclasses must set ``grids``, ``tilted``, and ``resolution`` as normal + attributes in ``__init__``. + """ + + grids: nn.ParameterList + tilted: bool + resolution: list[int] \ No newline at end of file diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 0eda0bb9..346e01da 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -14,12 +14,12 @@ from quantem.core.ml.constraints import BaseConstraints, Constraints from quantem.core.ml.ddp import DDPMixin from quantem.core.ml.loss_functions import get_loss_module -from quantem.core.ml.models.kplanes import KPlanes, KPlanesTILTED -from quantem.core.ml.models.model_base import PPLR +from quantem.core.ml.models.model_base import PPLR, PlanarDecompositionModel from quantem.core.ml.optimizer_mixin import OptimizerMixin, OptimizerType from quantem.core.utils.rng import RNGMixin from quantem.tomography.dataset_models import TomographyINRPretrainDataset + class ObjConstraintParams: """ Namespace class for object reconstruction constraint dataclasses and parsing utilities. @@ -197,6 +197,12 @@ def parse_dict( ) +def _unwrap(model: nn.Module | nn.parallel.DistributedDataParallel) -> PlanarDecompositionModel: + """Unwrap a DistributedDataParallel model to get the underlying module ONLY for tensor decomposition models.""" + if isinstance(model, nn.parallel.DistributedDataParallel): + return model.module + return model + class ObjectBase(AutoSerialize, nn.Module, RNGMixin, OptimizerMixin): DEFAULT_LRS = { "object": 8e-6, @@ -205,7 +211,6 @@ class ObjectBase(AutoSerialize, nn.Module, RNGMixin, OptimizerMixin): """ Base class for all ObjectModels to inherit from. """ - def __init__( self, shape: tuple[int, int, int], # pyright: ignore[reportRedeclaration] @@ -297,7 +302,6 @@ def to(self, *args, **kwargs): raise NotImplementedError - class ObjectConstraints(BaseConstraints, ObjectBase): # TODO: Ask Arthur why we still need this def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -309,7 +313,6 @@ def get_tv_loss(self, **kwargs) -> torch.Tensor: """ raise NotImplementedError - class ObjectPixelated(ObjectConstraints): """ @@ -453,7 +456,6 @@ def to(self, device: str | torch.device): # pyright: ignore[reportIncompatibleM class ObjectINR(ObjectConstraints, DDPMixin): DEFAULT_CONSTRAINTS = ObjConstraintParams.ObjINRConstraints() - def __init__( self, shape: tuple[int, int, int], @@ -470,7 +472,7 @@ def __init__( ) self._pretrain_losses = [] self._pretrain_lrs = [] - self.constraints: ObjConstraintsType = self.DEFAULT_CONSTRAINTS.copy() + self.constraints: ObjConstraintParams.ObjINRConstraints = self.DEFAULT_CONSTRAINTS.copy() # Register the network submodule (important: real nn.Module attribute) if model is not None: self.setup_distributed(device=device) @@ -881,10 +883,8 @@ def to(self, device: str | torch.device): # pyright: ignore[reportIncompatibleM self.distribute_model(self._model) self.reconnect_optimizer_to_parameters() - class ObjectTensorDecomp(ObjectConstraints, DDPMixin): DEFAULT_CONSTRAINTS = ObjConstraintParams.ObjTensorDecompConstraints() - def __init__( self, shape: tuple[int, int, int], @@ -901,7 +901,7 @@ def __init__( ) self._pretrain_losses = [] self._pretrain_lrs = [] - self.constraints: ObjConstraintsType = self.DEFAULT_CONSTRAINTS.copy() + self.constraints: ObjConstraintParams.ObjTensorDecompConstraints = self.DEFAULT_CONSTRAINTS.copy() # Register the network submodule (important: real nn.Module attribute) if model is not None: self.setup_distributed(device=device) @@ -929,7 +929,7 @@ def from_model( # --- Properties --- @property - def model(self) -> nn.Module | nn.parallel.DistributedDataParallel: + def model(self) -> nn.Module | nn.parallel.DistributedDataParallel | PlanarDecompositionModel: """ Returns the INR model. """ @@ -970,7 +970,7 @@ def apply_soft_constraints( isinstance(self.constraints, ObjConstraintParams.ObjINRConstraints) and self.constraints.sparsity > 0 ): # NOTE: For the linter, I must make this :) - sparsity_loss = self.constraints.sparsity * torch.norm(all_densities, p=1) + sparsity_loss = self.constraints.sparsity * all_densities.abs().mean() soft_loss += sparsity_loss return soft_loss @@ -986,10 +986,11 @@ def get_tv_loss(self, coords: torch.Tensor, pred: torch.Tensor) -> torch.Tensor: def _get_plane_tv_loss(self) -> torch.Tensor: - is_tilted = isinstance(self.model, KPlanesTILTED) + is_tilted = self.model.tilted per_level = [] - for p in self.model.grids: + model = _unwrap(self.model) + for p in model.grids: # p: (3*T, C, H, W) for TILTED, (3, C, H, W) for KPlanes dh = (p[:, :, 1:, :] - p[:, :, :-1, :]).pow(2).mean(dim=(1, 2, 3)) dw = (p[:, :, :, 1:] - p[:, :, :, :-1]).pow(2).mean(dim=(1, 2, 3)) @@ -1017,12 +1018,10 @@ def get_volume_tv_loss(self, coords: torch.Tensor) -> torch.Tensor: tv_indices = torch.randperm(coords.shape[0], device=coords.device)[:num_tv_samples] tv_coords = coords[tv_indices] # (N, 3) - if hasattr(self.model, "resolution"): - h = 2.0 / min(self.model.resolution) - else: - h = 1e-2 + model = _unwrap(self.model) + h = 2.0 / min(model.resolution) - pred = self.model(tv_coords) + pred = model(tv_coords) if isinstance(pred, tuple): pred = pred[0] if pred.dim() == 1: @@ -1269,99 +1268,7 @@ def forward(self, coords: torch.Tensor) -> torch.Tensor: return all_densities - # Pretrain Loop - - def pretrain( - self, - pretrain_dataset: TomographyINRPretrainDataset, - batch_size: int, - reset: bool = False, - num_iters: int = 10, - num_workers: int = 0, - optimizer_params: dict | None = None, - scheduler_params: dict | None = None, - loss_fn: Callable | str = "l1", - verbose: bool = True, - ): - """ - Pretrain the INR model to fit target volume. - """ - - if ( - pretrain_dataset is not None - ): # Need to make a check if there's already a pretrain dataset to not go through with the setup again. - self.pretrain_dataset = pretrain_dataset - ( - self.pretraining_dataloader, - self.pretraining_sampler, - self.pretraining_val_dataloader, - self.pretraining_val_sampler, - ) = self.setup_dataloader(pretrain_dataset, batch_size, num_workers=num_workers) - - if optimizer_params is not None: - self.set_optimizer(optimizer_params) - if scheduler_params is not None: - self.set_scheduler(scheduler_params, num_iters) - - if reset: - self.reset() - - loss_fn = get_loss_module(loss_fn, self.dtype) - - self._pretrain( - num_iters=num_iters, - loss_fn=loss_fn, - verbose=verbose, - ) - - def _pretrain( - self, - num_iters: int, - loss_fn: Callable, - verbose: bool, - ): - if self.optimizer is None: - raise RuntimeError("Optimizer not set. Call set_optimizer() first.") - if self.scheduler is None: - raise RuntimeError("Scheduler not set. Call set_scheduler() first.") - - self.model.train() - optimizer = self.optimizer - scheduler = self.scheduler - - pbar = tqdm(range(num_iters), desc="Pretraining", disable=not verbose) - for a0 in pbar: - epoch_loss = 0 - for batch_idx, batch in enumerate[Any](self.pretraining_dataloader): - coords = batch["coords"].to(self.device, non_blocking=True) - target = batch["target"].to(self.device, non_blocking=True) - - with torch.autocast( - device_type=self.device.type, dtype=torch.bfloat16, enabled=True - ): - outputs = self.forward(coords) - loss = loss_fn(outputs, target) - - loss.backward() - epoch_loss += loss.item() - - # Clip gradients - torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm=1.0) - - optimizer.step() - optimizer.zero_grad() - - if scheduler is not None: - if isinstance(scheduler, torch.optim.lr_scheduler.ReduceLROnPlateau): - scheduler.step(epoch_loss) - else: - scheduler.step() - - self._pretrain_losses.append(epoch_loss / len(self.pretraining_dataloader)) - print( - f"Epoch {a0 + 1}/{num_iters}, Pretrain Loss: {epoch_loss / len(self.pretraining_dataloader):.4f}" - ) - self._pretrain_lrs.append(optimizer.param_groups[0]["lr"]) + # NOTE: Pretraining is done in a two-phase fashion as shown in TILTED paper. def create_volume(self, return_vol: bool = False): N = max(self._shape) From 0c71a5dc10d287ac5005390466a2bc7111c3bccf Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Thu, 23 Apr 2026 16:53:20 -0700 Subject: [PATCH 255/335] Final changes prior to draft PR --- src/quantem/core/ml/models/kplanes.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/quantem/core/ml/models/kplanes.py b/src/quantem/core/ml/models/kplanes.py index 16c56575..b1be28ee 100644 --- a/src/quantem/core/ml/models/kplanes.py +++ b/src/quantem/core/ml/models/kplanes.py @@ -130,7 +130,6 @@ def query_planes( result = result * sampled return result # pyright: ignore[reportReturnType] - def interpolate_ms_features( pts: torch.Tensor, ms_grids: nn.ParameterList, @@ -284,7 +283,6 @@ def resolution(self, resolution: Sequence[int]): raise TypeError("Resolution must be a sequence") self._resolution = list(resolution) - # --------------------------------------------------------------------------- # KPlanesTILTED # --------------------------------------------------------------------------- @@ -576,7 +574,6 @@ def set_so3_param_type(self, so3_param_type: str, init: str = "rand") -> None: def tilted(self) -> bool: return True - # CP Decomp for Warmup SO3 rotations def interpolate_ms_features_cp_tilted( From 99fdd885c75afa867628000c40e41b38859a5741 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Thu, 23 Apr 2026 17:09:47 -0700 Subject: [PATCH 256/335] Fixed typo --- src/quantem/tomography/object_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 346e01da..10509655 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -1078,7 +1078,7 @@ def get_optimization_parameters(self) -> list[nn.Parameter] | list[dict[str, Any return list(self.params) - # --- DDP Mixin Overloads in the case of PPLR --- + # --- Optimizer Mixin Overloads in the case of PPLR --- @property def optimizer_params(self) -> OptimizerType | dict[str, OptimizerType]: From 4a44826943324dac0f6aa527bdf6bba0f79c3f1f Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Thu, 23 Apr 2026 17:28:17 -0700 Subject: [PATCH 257/335] Small change to set_optimizer in OptimizerMixin to allow for dictionary parsing --- src/quantem/core/ml/models/kplanes.py | 2 +- src/quantem/core/ml/optimizer_mixin.py | 43 ++++-- src/quantem/tomography/object_models.py | 177 ++++++++++-------------- 3 files changed, 106 insertions(+), 116 deletions(-) diff --git a/src/quantem/core/ml/models/kplanes.py b/src/quantem/core/ml/models/kplanes.py index b1be28ee..c8b49e70 100644 --- a/src/quantem/core/ml/models/kplanes.py +++ b/src/quantem/core/ml/models/kplanes.py @@ -381,7 +381,7 @@ def __init__( input_coords_dims: int = 3, M_features: int = 32, resolution: Sequence[int] = (200, 200, 200), - multiscale_res_multipliers: Optional[Sequence[int]] = None, + multiscale_res_multipliers: Optional[Sequence[float]] = None, density_activation: Callable = lambda x: F.softplus(x - 1), # TILTED parameters T: int = 4, diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index 9fe8a32a..4c97181a 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -613,18 +613,41 @@ def set_optimizer(self, opt_params: OptimizerType | dict | None = None) -> None: params = list(params) # Ensure parameters require gradients - for p in params: - p.requires_grad_(True) + for group in params: + tensors = group["params"] if isinstance(group, dict) else [group] + for p in tensors: + p.requires_grad_(True) + # Figure out which optimizer class to use + if isinstance(self._optimizer_params, dict): + # Per-group case: all groups must agree on the optimizer class, + # and per-group hyperparameters are already baked into each dict + # by get_optimization_parameters(). + opt_specs = list(self._optimizer_params.values()) + if not opt_specs: + self._optimizer = None + return + optimizer_cls = self._optimizer_class_for(opt_specs[0]) + for spec in opt_specs[1:]: + if type(spec) is not type(opt_specs[0]): + raise ValueError( + f"All parameter groups must use the same optimizer type, " + f"got {type(opt_specs[0]).__name__} and {type(spec).__name__}" + ) + self._optimizer = optimizer_cls(params) + else: + # Single-optimizer case: splat global hyperparameters + optimizer_cls = self._optimizer_class_for(self._optimizer_params) + self._optimizer = optimizer_cls(params, **self._optimizer_params.params()) - match self._optimizer_params: - case OptimizerParams.Adam(): - self._optimizer = torch.optim.Adam(params, **self._optimizer_params.params()) - case OptimizerParams.AdamW(): - self._optimizer = torch.optim.AdamW(params, **self._optimizer_params.params()) - case OptimizerParams.SGD(): - self._optimizer = torch.optim.SGD(params, **self._optimizer_params.params()) + + def _optimizer_class_for(self, opt_params) -> type[torch.optim.Optimizer]: + match opt_params: + case OptimizerParams.Adam(): return torch.optim.Adam + case OptimizerParams.AdamW(): return torch.optim.AdamW + case OptimizerParams.SGD(): return torch.optim.SGD case _: - raise NotImplementedError(f"Unknown optimizer type: {self._optimizer_params}") + raise NotImplementedError(f"Unknown optimizer type: {opt_params}") + def set_scheduler( self, scheduler_params: SchedulerType | dict | None = None, num_iter: int | None = None diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 10509655..281e07bf 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -1066,16 +1066,14 @@ def params(self) -> Generator[torch.nn.Parameter, None, None]: def get_optimization_parameters(self) -> list[nn.Parameter] | list[dict[str, Any]]: - if isinstance(self.model, PPLR): - - return [ - { - "params": self.model.get_params()[key], - **self.optimizer_params[key].params(), - } - for key in self.model.param_keys - ] - return list(self.params) + model = _unwrap (self.model) + return [ + { + "params": model.get_params()[key], + **self.optimizer_params[key].params(), + } + for key in model.param_keys + ] # --- Optimizer Mixin Overloads in the case of PPLR --- @@ -1088,54 +1086,25 @@ def optimizer_params(self) -> OptimizerType | dict[str, OptimizerType]: @optimizer_params.setter def optimizer_params(self, params: OptimizerType | dict[str, OptimizerType] | dict[str, Any]): """Set the optimizer parameters.""" - if isinstance(params, OptimizerType): - self._optimizer_params = params - return - if isinstance(self.model, PPLR): - if not isinstance(params, dict): - raise TypeError(f"optimizer parameters must be a dict for PPLR, got {type(params)}") - - object_params = params + if not isinstance(params, dict): + raise TypeError(f"optimizer parameters must be a dict for PPLR, got {type(params)}") - if set(object_params.keys()) != set(self.model.param_keys): - raise ValueError(f"optimizer parameters keys must match PPLR param_keys, got {object_params.keys()} != {self.model.param_keys}") - - params = {} - for key, value in object_params.items(): - if isinstance(value, dict): - params[key] = OptimizerParams.parse_dict(d=value) - elif isinstance(value, OptimizerType): - params[key] = value - else: - raise TypeError(f"optimizer parameters must be a dict or OptimizerType, got {type(value)}") - - self._optimizer_params = params - else: - raise TypeError(f"optimizer parameters must be a dict for non-PPLR, got {type(params)}") - - - def set_optimizer(self, opt_params: OptimizerType | dict | None = None) -> None: - """ - Set the optimizer for this model. - Currently supports single LR for all parameters, TODO allow for per parameter LRs by - updating get_optimization_parameters to return a list of parameters and their LRs. - """ - - if not isinstance(self.model, PPLR): - super().set_optimizer(opt_params) - return - - if opt_params is not None: - self.optimizer_params = opt_params - - if not self._optimizer_params: - self._optimizer = None - return - - params = self.get_optimization_parameters() + object_params = params - self._optimizer = torch.optim.Adam(params) + if set(object_params.keys()) != set(self.model.param_keys): + raise ValueError(f"optimizer parameters keys must match PPLR param_keys, got {object_params.keys()} != {self.model.param_keys}") + + params = {} + for key, value in object_params.items(): + if isinstance(value, dict): + params[key] = OptimizerParams.parse_dict(d=value) + elif isinstance(value, OptimizerType): + params[key] = value + else: + raise TypeError(f"optimizer parameters must be a dict or OptimizerType, got {type(value)}") + + self._optimizer_params = params def reconnect_optimizer_to_parameters(self) -> None: """ @@ -1144,58 +1113,56 @@ def reconnect_optimizer_to_parameters(self) -> None: if self.optimizer is None: return - - if isinstance(self.model, PPLR): - current_params = self.get_optimization_parameters() - + + current_params = self.get_optimization_parameters() + - optimizable_params = [ - p for p in current_params - if isinstance(p['params'][0], torch.Tensor) and p['params'][0].is_leaf - ] - + optimizable_params = [ + p for p in current_params + if isinstance(p['params'][0], torch.Tensor) and p['params'][0].is_leaf + ] + + + if not optimizable_params: + raise ValueError(f"Shouldn't be getting here! No optimizable parameters found for {self.__class__.__name__}.") + + for p in optimizable_params: + print(f"Setting requires_grad for parameter: {p}") + p['params'][0].requires_grad_(True) + + assert self._optimizer is not None + # Preserve optimizer states and param_group settings + old_state = self._optimizer.state.copy() + old_param_groups = self._optimizer.param_groups.copy() + + # Reconnect to new parameters + self._optimizer.param_groups.clear() + for param_group in optimizable_params: + self._optimizer.add_param_group(param_group) + + # Restore per-group hyperparameters (lr, betas, weight_decay, etc.) by index, + # excluding 'params' which comes from the new groups + for new_pg, old_pg in zip(self._optimizer.param_groups, old_param_groups): + new_pg.update({k: v for k, v in old_pg.items() if k != "params"}) + + # Remap optimizer state: for any new param that IS the same tensor as an old param, + # carry its state over (moved to the right device just in case). + new_state = {} + for new_pg in self._optimizer.param_groups: + for new_param in new_pg["params"]: + if new_param in old_state: + device = new_param.device + new_state[new_param] = { + k: (v.to(device) if isinstance(v, torch.Tensor) else v) + for k, v in old_state[new_param].items() + } + + self._optimizer.state.clear() + self._optimizer.state.update(new_state) + + if self._scheduler is not None and self._optimizer is not None: + self._scheduler.optimizer = self._optimizer - if not optimizable_params: - raise ValueError(f"Shouldn't be getting here! No optimizable parameters found for {self.__class__.__name__}.") - - for p in optimizable_params: - print(f"Setting requires_grad for parameter: {p}") - p['params'][0].requires_grad_(True) - - assert self._optimizer is not None - # Preserve optimizer states and param_group settings - old_state = self._optimizer.state.copy() - old_param_groups = self._optimizer.param_groups.copy() - - # Reconnect to new parameters - self._optimizer.param_groups.clear() - for param_group in optimizable_params: - self._optimizer.add_param_group(param_group) - - # Restore per-group hyperparameters (lr, betas, weight_decay, etc.) by index, - # excluding 'params' which comes from the new groups - for new_pg, old_pg in zip(self._optimizer.param_groups, old_param_groups): - new_pg.update({k: v for k, v in old_pg.items() if k != "params"}) - - # Remap optimizer state: for any new param that IS the same tensor as an old param, - # carry its state over (moved to the right device just in case). - new_state = {} - for new_pg in self._optimizer.param_groups: - for new_param in new_pg["params"]: - if new_param in old_state: - device = new_param.device - new_state[new_param] = { - k: (v.to(device) if isinstance(v, torch.Tensor) else v) - for k, v in old_state[new_param].items() - } - - self._optimizer.state.clear() - self._optimizer.state.update(new_state) - - if self._scheduler is not None and self._optimizer is not None: - self._scheduler.optimizer = self._optimizer - else: - super().reconnect_optimizer_to_parameters() # Pretraining @property From ede9d566a777f68b225f8f6e6cc2ce2e9660d70a Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Thu, 23 Apr 2026 17:36:39 -0700 Subject: [PATCH 258/335] Pretraining warning on ObjectTensorDecomp --- src/quantem/tomography/object_models.py | 209 +----------------------- 1 file changed, 5 insertions(+), 204 deletions(-) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 281e07bf..0ae1e74a 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -883,7 +883,7 @@ def to(self, device: str | torch.device): # pyright: ignore[reportIncompatibleM self.distribute_model(self._model) self.reconnect_optimizer_to_parameters() -class ObjectTensorDecomp(ObjectConstraints, DDPMixin): +class ObjectTensorDecomp(ObjectINR): DEFAULT_CONSTRAINTS = ObjConstraintParams.ObjTensorDecompConstraints() def __init__( self, @@ -926,33 +926,6 @@ def from_model( obj_model.to(device) return obj_model - # --- Properties --- - - @property - def model(self) -> nn.Module | nn.parallel.DistributedDataParallel | PlanarDecompositionModel: - """ - Returns the INR model. - """ - return self._model - - @property - def obj(self) -> torch.Tensor: - return self._obj - - @obj.setter - def obj(self, obj: torch.Tensor): - self._obj = obj - - @property - def obj_view(self) -> np.ndarray: - """ - Returns the object as a view of the x, y, z axes. - - Matches the axes of conventionally reconstructed objects, this is the object that will be saved. - """ - self.create_volume() - return self._obj.cpu().numpy().transpose(0, 1, 3, 2) - # --- Constraints --- def apply_soft_constraints( @@ -966,10 +939,7 @@ def apply_soft_constraints( if self.constraints.tv_vol > 0: soft_loss += self.get_tv_loss(coords, pred) - if ( - isinstance(self.constraints, ObjConstraintParams.ObjINRConstraints) - and self.constraints.sparsity > 0 - ): # NOTE: For the linter, I must make this :) + if self.constraints.sparsity > 0: # NOTE: For the linter, I must make this :) sparsity_loss = self.constraints.sparsity * all_densities.abs().mean() soft_loss += sparsity_loss @@ -1107,9 +1077,6 @@ def optimizer_params(self, params: OptimizerType | dict[str, OptimizerType] | di self._optimizer_params = params def reconnect_optimizer_to_parameters(self) -> None: - """ - Reconnect optimizer overload, defaults back to the standard implementation if no `PPLR` is detected. - """ if self.optimizer is None: return @@ -1163,174 +1130,8 @@ def reconnect_optimizer_to_parameters(self) -> None: if self._scheduler is not None and self._optimizer is not None: self._scheduler.optimizer = self._optimizer - - # Pretraining - @property - def pretrained_weights(self) -> dict[str, torch.Tensor]: - """get the pretrained weights of the INR model""" - return self._pretrained_weights - - def _set_pretrained_weights(self, model: "torch.nn.Module"): - """set the pretrained weights of the INR model""" - if not isinstance(model, torch.nn.Module): - raise TypeError(f"Pretrained model must be a torch.nn.Module, got {type(model)}") - self._pretrained_weights = deepcopy(model.state_dict()) - - @property - def pretrain_target(self) -> TomographyINRPretrainDataset: - """get the pretrain target""" - return self._pretrain_target - - @pretrain_target.setter - def pretrain_target(self, target: TomographyINRPretrainDataset): - """set the pretrain target""" - self._pretrain_target = target - - @property - def dtype(self) -> torch.dtype: - """ - Returns the dtype of the object. - """ - # TODO: This is a temporary solution to get the dtype of the object. - return torch.float32 - - @property - def shape(self) -> tuple[int, int, int]: - return self._shape - - @shape.setter - def shape(self, shape: tuple[int, int, int]): - self._shape = shape - - # --- Helper Functions --- - def rebuild_model(self): - self._model = self.distribute_model(self._model) - - # Reset method that goes back to the pretrained weights. - def reset(self): - """reset the model to the pretrained weights""" - self.model.load_state_dict(self._pretrained_weights.copy()) - self._model = self.distribute_model( - self.model - ) # Maybe add a check to see if distributed or not, but not very computationally expensive to do this. - - # --- Forward Method --- - - def forward(self, coords: torch.Tensor) -> torch.Tensor: - """forward pass for the INR model""" - all_densities = self.model(coords) - - if all_densities.dim() > 1: - all_densities = all_densities.squeeze(-1) - valid_mask = ( - (coords[:, 0] >= -1) & (coords[:, 0] <= 1) & (coords[:, 1] >= -1) & (coords[:, 1] <= 1) - ).float() - - if all_densities.dim() > 1: - valid_mask = valid_mask.unsqueeze(-1) - # Multi-dimensional mask - all_densities = all_densities * valid_mask - - all_densities = self.apply_hard_constraints(all_densities) - - return all_densities - - # NOTE: Pretraining is done in a two-phase fashion as shown in TILTED paper. - - def create_volume(self, return_vol: bool = False): - N = max(self._shape) - with torch.no_grad(): - coords_1d = torch.linspace(-1, 1, N) - x, y, z = torch.meshgrid(coords_1d, coords_1d, coords_1d, indexing="ij") - inputs = torch.stack([x, y, z], dim=-1).reshape(-1, 3) - model = self.model.module if isinstance(self.model, nn.DataParallel) else self.model - - inference_batch_size = 5 * N * N - total_samples = N**3 - samples_per_gpu = total_samples // self.world_size - remainder = total_samples % self.world_size - - if self.global_rank < remainder: - start_idx = self.global_rank * (samples_per_gpu + 1) - end_idx = start_idx + samples_per_gpu + 1 - else: - start_idx = self.global_rank * samples_per_gpu + remainder - end_idx = start_idx + samples_per_gpu - - inputs_subset = inputs[start_idx:end_idx] - num_samples = inputs_subset.shape[0] - - outputs_list = [] - for batch_start in range(0, num_samples, inference_batch_size): - batch_end = min(batch_start + inference_batch_size, num_samples) - batch_coords = inputs_subset[batch_start:batch_end].to( - self.device, non_blocking=True - ) - - batch_outputs = model(batch_coords) # (B, C) or (B,) etc. - - if isinstance(batch_outputs, tuple): - batch_outputs = batch_outputs[0] - batch_outputs = self.apply_hard_constraints(batch_outputs) - - # Ensure shape is (B, C) - if batch_outputs.dim() == 1: - batch_outputs = batch_outputs.unsqueeze(-1) # (B, 1) - - outputs_list.append(batch_outputs.cpu()) - - outputs = torch.cat(outputs_list, dim=0) # (local_B, C) - C = outputs.shape[-1] # e.g. 5 - - if self.world_size > 1: - # gather variable-sized first dimension (local_B) while keeping channels - local_B = outputs.shape[0] - output_size = torch.tensor(local_B, device=self.device, dtype=torch.long) - all_sizes = [ - torch.zeros(1, device=self.device, dtype=torch.long) - for _ in range(self.world_size) - ] - dist.all_gather(all_sizes, output_size) - max_size = max(size.item() for size in all_sizes) - - outputs_dev = outputs.to(self.device) # (local_B, C) - if local_B < max_size: - pad = torch.zeros( - (max_size - local_B, C), # type: ignore - device=self.device, - dtype=outputs_dev.dtype, - ) - outputs_padded = torch.cat([outputs_dev, pad], dim=0) # (max_size, C) - else: - outputs_padded = outputs_dev - - gathered_outputs = [ - torch.empty((max_size, C), device=self.device, dtype=outputs_dev.dtype) # type: ignore - for _ in range(self.world_size) - ] - dist.all_gather(gathered_outputs, outputs_padded.contiguous()) - - trimmed_outputs = [] - for rank, size in enumerate(all_sizes): - trimmed_outputs.append(gathered_outputs[rank][: size.item(), :]) - - pred_full = torch.cat(trimmed_outputs, dim=0).reshape(C, N, N, N).float() - else: - pred_full = outputs.reshape(C, N, N, N).float() - - if return_vol: - return pred_full.detach().cpu() - - self._obj = pred_full.detach().cpu() - - def to(self, device: str | torch.device): # pyright: ignore[reportIncompatibleMethodOverride] -> better to do this device change - if isinstance(device, str): - device = torch.device(device) - self._device = device - if self.world_size == 1: - self._model = self._model.to(device) - elif not isinstance(self._model, torch.nn.parallel.DistributedDataParallel): - self.distribute_model(self._model) - self.reconnect_optimizer_to_parameters() + def pretrain(self) -> None: + raise NotImplementedError("Tensor decomposition pretraining is not usually required, and for TILTED there is a two-phase warmup approach.") + ObjectModelType = ObjectPixelated | ObjectINR From d0b6e84c15af848574bbeb7e3cf792daf004de45 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 23 Apr 2026 20:03:43 -0700 Subject: [PATCH 259/335] Delete .vscode directory --- .vscode/settings.json | 81 ------------------------------------------- 1 file changed, 81 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 1659b33b..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "python.languageServer": "None", - "python.terminal.activateEnvInCurrentTerminal": true, - "python.defaultInterpreterPath": ".venv/bin/python", - "cSpell.words": [ - "abtem", - "altk", - "arcsinh", - "argmin", - "asnumpy", - "astropy's", - "axsize", - "BFGS", - "cbar", - "cmap", - "coefs", - "colorbar", - "colorbars", - "colormaps", - "colorspacious", - "cspace", - "cupy", - "datacube", - "deconvolve", - "dstack", - "dstem", - "emdfile", - "errstate", - "fftfreq", - "fftshifted", - "Fienup", - "figax", - "figsize", - "frameon", - "Guizar", - "halfrange", - "hstack", - "ifft", - "ifftshift", - "imshow", - "inds", - "interp", - "iscomplexobj", - "isscalar", - "issubdtype", - "lowpass", - "maxiter", - "meshgrid", - "metadatabundle", - "minmax", - "ncols", - "ndarray", - "ndimage", - "ndindex", - "ndinfo", - "nrows", - "nval", - "polyfit", - "polyval", - "powerlimits", - "quantem", - "rosettasciio", - "rsciio", - "Scalebar", - "scanline", - "scanlines", - "Sicairos", - "sinc", - "Subarray", - "ticklabels", - "toolkits", - "tukey", - "upsampling", - "vmax", - "vmin", - "xticks", - "yaxis", - "yticks" - ], - "basedpyright.analysis.typeCheckingMode": "standard", -} From 24468cc593e7102d0156543d2daad29ee4182e92 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 24 Apr 2026 16:54:34 -0700 Subject: [PATCH 260/335] enable branch PR CI pytest and move pull request template --- .github/{PULL_REQUEST_TEMPLATE => }/pull_request_template.md | 0 .github/workflows/uv-pytests.yml | 4 +--- 2 files changed, 1 insertion(+), 3 deletions(-) rename .github/{PULL_REQUEST_TEMPLATE => }/pull_request_template.md (100%) diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/pull_request_template.md similarity index 100% rename from .github/PULL_REQUEST_TEMPLATE/pull_request_template.md rename to .github/pull_request_template.md diff --git a/.github/workflows/uv-pytests.yml b/.github/workflows/uv-pytests.yml index 474ad347..fe14f19c 100644 --- a/.github/workflows/uv-pytests.yml +++ b/.github/workflows/uv-pytests.yml @@ -2,9 +2,7 @@ name: PyTests on: pull_request: - branches: - - dev - - main + workflow_dispatch: jobs: uv-pytests: From a6563952faec8c9b982f222f66ad90708eb75d68 Mon Sep 17 00:00:00 2001 From: quantem-bot Date: Mon, 27 Apr 2026 11:09:22 +0000 Subject: [PATCH 261/335] chore: update lock file --- uv.lock | 275 +++++++++++++++++++++++++------------------------------- 1 file changed, 123 insertions(+), 152 deletions(-) diff --git a/uv.lock b/uv.lock index 9a59b1d3..43a03fd0 100644 --- a/uv.lock +++ b/uv.lock @@ -196,11 +196,11 @@ css = [ [[package]] name = "certifi" -version = "2026.2.25" +version = "2026.4.22" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } +sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, + { url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" }, ] [[package]] @@ -373,14 +373,14 @@ wheels = [ [[package]] name = "click" -version = "8.3.2" +version = "8.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/57/75/31212c6bf2503fdf920d87fee5d7a86a2e3bcf444984126f13d8e4016804/click-8.3.2.tar.gz", hash = "sha256:14162b8b3b3550a7d479eafa77dfd3c38d9dc8951f6f69c78913a8f9a7540fd5", size = 302856, upload-time = "2026-04-03T19:14:45.118Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/63/f9e1ea081ce35720d8b92acde70daaedace594dc93b693c869e0d5910718/click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2", size = 328061, upload-time = "2026-04-22T15:11:27.506Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/20/71885d8b97d4f3dde17b1fdb92dbd4908b00541c5a3379787137285f602e/click-8.3.2-py3-none-any.whl", hash = "sha256:1924d2c27c5653561cd2cae4548d1406039cb79b858b747cfea24924bbc1616d", size = 108379, upload-time = "2026-04-03T19:14:43.505Z" }, + { url = "https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613", size = 110502, upload-time = "2026-04-22T15:11:25.044Z" }, ] [[package]] @@ -656,10 +656,10 @@ wheels = [ [[package]] name = "cuda-pathfinder" -version = "1.5.2" +version = "1.5.3" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/f9/1b9b60a30fc463c14cdea7a77228131a0ccc89572e8df9cb86c9648271ab/cuda_pathfinder-1.5.2-py3-none-any.whl", hash = "sha256:0c5f160a7756c5b072723cbbd6d861e38917ef956c68150b02f0b6e9271c71fa", size = 49988, upload-time = "2026-04-06T23:01:05.17Z" }, + { url = "https://files.pythonhosted.org/packages/d3/d6/ac63065d33dd700fee7ebd7d287332401b54e31b9346e142f871e1f0b116/cuda_pathfinder-1.5.3-py3-none-any.whl", hash = "sha256:dff021123aedbb4117cc7ec81717bbfe198fb4e8b5f1ee57e0e084fec5c8577d", size = 49991, upload-time = "2026-04-14T20:09:27.037Z" }, ] [[package]] @@ -831,11 +831,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.25.2" +version = "3.29.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/b8/00651a0f559862f3bb7d6f7477b192afe3f583cc5e26403b44e59a55ab34/filelock-3.25.2.tar.gz", hash = "sha256:b64ece2b38f4ca29dd3e810287aa8c48182bbecd1ae6e9ae126c9b35f1382694", size = 40480, upload-time = "2026-03-11T20:45:38.487Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/fe/997687a931ab51049acce6fa1f23e8f01216374ea81374ddee763c493db5/filelock-3.29.0.tar.gz", hash = "sha256:69974355e960702e789734cb4871f884ea6fe50bd8404051a3530bc07809cf90", size = 57571, upload-time = "2026-04-19T15:39:10.068Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl", hash = "sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70", size = 26759, upload-time = "2026-03-11T20:45:37.437Z" }, + { url = "https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258", size = 39812, upload-time = "2026-04-19T15:39:08.752Z" }, ] [[package]] @@ -1163,20 +1163,20 @@ wheels = [ [[package]] name = "identify" -version = "2.6.18" +version = "2.6.19" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/46/c4/7fb4db12296cdb11893d61c92048fe617ee853f8523b9b296ac03b43757e/identify-2.6.18.tar.gz", hash = "sha256:873ac56a5e3fd63e7438a7ecbc4d91aca692eb3fefa4534db2b7913f3fc352fd", size = 99580, upload-time = "2026-03-15T18:39:50.319Z" } +sdist = { url = "https://files.pythonhosted.org/packages/52/63/51723b5f116cc04b061cb6f5a561790abf249d25931d515cd375e063e0f4/identify-2.6.19.tar.gz", hash = "sha256:6be5020c38fcb07da56c53733538a3081ea5aa70d36a156f83044bfbf9173842", size = 99567, upload-time = "2026-04-17T18:39:50.265Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl", hash = "sha256:8db9d3c8ea9079db92cafb0ebf97abdc09d52e97f4dcf773a2e694048b7cd737", size = 99394, upload-time = "2026-03-15T18:39:48.915Z" }, + { url = "https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl", hash = "sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a", size = 99397, upload-time = "2026-04-17T18:39:49.221Z" }, ] [[package]] name = "idna" -version = "3.11" +version = "3.13" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/cc/762dfb036166873f0059f3b7de4565e1b5bc3d6f28a414c13da27e442f99/idna-3.13.tar.gz", hash = "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242", size = 194210, upload-time = "2026-04-22T16:42:42.314Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, + { url = "https://files.pythonhosted.org/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3", size = 68629, upload-time = "2026-04-22T16:42:40.909Z" }, ] [[package]] @@ -1221,8 +1221,7 @@ dependencies = [ { name = "appnope", marker = "sys_platform == 'darwin'" }, { name = "comm" }, { name = "debugpy" }, - { name = "ipython", version = "9.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "ipython", version = "9.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "ipython" }, { name = "jupyter-client" }, { name = "jupyter-core" }, { name = "matplotlib-inline" }, @@ -1240,52 +1239,25 @@ wheels = [ [[package]] name = "ipython" -version = "9.10.1" +version = "9.13.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.12'", -] dependencies = [ - { name = "colorama", marker = "python_full_version < '3.12' and sys_platform == 'win32'" }, - { name = "decorator", marker = "python_full_version < '3.12'" }, - { name = "ipython-pygments-lexers", marker = "python_full_version < '3.12'" }, - { name = "jedi", marker = "python_full_version < '3.12'" }, - { name = "matplotlib-inline", marker = "python_full_version < '3.12'" }, - { name = "pexpect", marker = "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit", marker = "python_full_version < '3.12'" }, - { name = "pygments", marker = "python_full_version < '3.12'" }, - { name = "stack-data", marker = "python_full_version < '3.12'" }, - { name = "traitlets", marker = "python_full_version < '3.12'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "ipython-pygments-lexers" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "psutil" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, { name = "typing-extensions", marker = "python_full_version < '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c5/25/daae0e764047b0a2480c7bbb25d48f4f509b5818636562eeac145d06dfee/ipython-9.10.1.tar.gz", hash = "sha256:e170e9b2a44312484415bdb750492699bf329233b03f2557a9692cce6466ada4", size = 4426663, upload-time = "2026-03-27T09:53:26.244Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/c4/87cda5842cf5c31837c06ddb588e11c3c35d8ece89b7a0108c06b8c9b00a/ipython-9.13.0.tar.gz", hash = "sha256:7e834b6afc99f020e3f05966ced34792f40267d64cb1ea9043886dab0dde5967", size = 4430549, upload-time = "2026-04-24T12:24:55.221Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/09/ba70f8d662d5671687da55ad2cc0064cf795b15e1eea70907532202e7c97/ipython-9.10.1-py3-none-any.whl", hash = "sha256:82d18ae9fb9164ded080c71ef92a182ee35ee7db2395f67616034bebb020a232", size = 622827, upload-time = "2026-03-27T09:53:24.566Z" }, -] - -[[package]] -name = "ipython" -version = "9.12.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.12' and python_full_version < '3.14'", -] -dependencies = [ - { name = "colorama", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" }, - { name = "decorator", marker = "python_full_version >= '3.12'" }, - { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.12'" }, - { name = "jedi", marker = "python_full_version >= '3.12'" }, - { name = "matplotlib-inline", marker = "python_full_version >= '3.12'" }, - { name = "pexpect", marker = "python_full_version >= '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit", marker = "python_full_version >= '3.12'" }, - { name = "pygments", marker = "python_full_version >= '3.12'" }, - { name = "stack-data", marker = "python_full_version >= '3.12'" }, - { name = "traitlets", marker = "python_full_version >= '3.12'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3a/73/7114f80a8f9cabdb13c27732dce24af945b2923dcab80723602f7c8bc2d8/ipython-9.12.0.tar.gz", hash = "sha256:01daa83f504b693ba523b5a407246cabde4eb4513285a3c6acaff11a66735ee4", size = 4428879, upload-time = "2026-03-27T09:42:45.312Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/59/22/906c8108974c673ebef6356c506cebb6870d48cedea3c41e949e2dd556bb/ipython-9.12.0-py3-none-any.whl", hash = "sha256:0f2701e8ee86e117e37f50563205d36feaa259d2e08d4a6bc6b6d74b18ce128d", size = 625661, upload-time = "2026-03-27T09:42:42.831Z" }, + { url = "https://files.pythonhosted.org/packages/b9/86/3060e8029b7cc505cce9a0137431dda81d0a3fde93a8f0f50ee0bf37a795/ipython-9.13.0-py3-none-any.whl", hash = "sha256:57f9d4639e20818d328d287c7b549af3d05f12486ea8f2e7f73e52a36ec4d201", size = 627274, upload-time = "2026-04-24T12:24:53.038Z" }, ] [[package]] @@ -1306,8 +1278,7 @@ version = "8.1.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "comm" }, - { name = "ipython", version = "9.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "ipython", version = "9.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "ipython" }, { name = "jupyterlab-widgets" }, { name = "traitlets" }, { name = "widgetsnbextension" }, @@ -1442,7 +1413,7 @@ wheels = [ [[package]] name = "jupyter-events" -version = "0.12.0" +version = "0.12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonschema", extra = ["format-nongpl"] }, @@ -1454,9 +1425,9 @@ dependencies = [ { name = "rfc3986-validator" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9d/c3/306d090461e4cf3cd91eceaff84bede12a8e52cd821c2d20c9a4fd728385/jupyter_events-0.12.0.tar.gz", hash = "sha256:fc3fce98865f6784c9cd0a56a20644fc6098f21c8c33834a8d9fe383c17e554b", size = 62196, upload-time = "2025-02-03T17:23:41.485Z" } +sdist = { url = "https://files.pythonhosted.org/packages/18/f8/475c4241b2b75af0deaae453ed003c6c851766dbc44d332d8baf245dc931/jupyter_events-0.12.1.tar.gz", hash = "sha256:faff25f77218335752f35f23c5fe6e4a392a7bd99a5939ccb9b8fbf594636cf3", size = 62854, upload-time = "2026-04-20T23:17:50.66Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl", hash = "sha256:6464b2fa5ad10451c3d35fabc75eab39556ae1e2853ad0c0cc31b656731a97fb", size = 19430, upload-time = "2025-02-03T17:23:38.643Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6c/6fcde0c8f616ed360ffd3587f7db9e225a7e62b583a04494d2f069cf64ea/jupyter_events-0.12.1-py3-none-any.whl", hash = "sha256:c366585253f537a627da52fa7ca7410c5b5301fe893f511e7b077c2d93ec8bcf", size = 19512, upload-time = "2026-04-20T23:17:48.927Z" }, ] [[package]] @@ -1725,14 +1696,14 @@ wheels = [ [[package]] name = "mako" -version = "1.3.10" +version = "1.3.11" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474, upload-time = "2025-04-10T12:44:31.16Z" } +sdist = { url = "https://files.pythonhosted.org/packages/59/8a/805404d0c0b9f3d7a326475ca008db57aea9c5c9f2e1e39ed0faa335571c/mako-1.3.11.tar.gz", hash = "sha256:071eb4ab4c5010443152255d77db7faa6ce5916f35226eb02dc34479b6858069", size = 399811, upload-time = "2026-04-14T20:19:51.493Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" }, + { url = "https://files.pythonhosted.org/packages/68/a5/19d7aaa7e433713ffe881df33705925a196afb9532efc8475d26593921a6/mako-1.3.11-py3-none-any.whl", hash = "sha256:e372c6e333cf004aa736a15f425087ec977e1fcbd2966aae7f17c8dc1da27a77", size = 78503, upload-time = "2026-04-14T20:19:53.233Z" }, ] [[package]] @@ -1820,7 +1791,7 @@ wheels = [ [[package]] name = "matplotlib" -version = "3.10.8" +version = "3.10.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "contourpy" }, @@ -1833,53 +1804,53 @@ dependencies = [ { name = "pyparsing" }, { name = "python-dateutil" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8a/76/d3c6e3a13fe484ebe7718d14e269c9569c4eb0020a968a327acb3b9a8fe6/matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3", size = 34806269, upload-time = "2025-12-10T22:56:51.155Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/86/de7e3a1cdcfc941483af70609edc06b83e7c8a0e0dc9ac325200a3f4d220/matplotlib-3.10.8-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6be43b667360fef5c754dda5d25a32e6307a03c204f3c0fc5468b78fa87b4160", size = 8251215, upload-time = "2025-12-10T22:55:16.175Z" }, - { url = "https://files.pythonhosted.org/packages/fd/14/baad3222f424b19ce6ad243c71de1ad9ec6b2e4eb1e458a48fdc6d120401/matplotlib-3.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2b336e2d91a3d7006864e0990c83b216fcdca64b5a6484912902cef87313d78", size = 8139625, upload-time = "2025-12-10T22:55:17.712Z" }, - { url = "https://files.pythonhosted.org/packages/8f/a0/7024215e95d456de5883e6732e708d8187d9753a21d32f8ddb3befc0c445/matplotlib-3.10.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efb30e3baaea72ce5928e32bab719ab4770099079d66726a62b11b1ef7273be4", size = 8712614, upload-time = "2025-12-10T22:55:20.8Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f4/b8347351da9a5b3f41e26cf547252d861f685c6867d179a7c9d60ad50189/matplotlib-3.10.8-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d56a1efd5bfd61486c8bc968fa18734464556f0fb8e51690f4ac25d85cbbbbc2", size = 9540997, upload-time = "2025-12-10T22:55:23.258Z" }, - { url = "https://files.pythonhosted.org/packages/9e/c0/c7b914e297efe0bc36917bf216b2acb91044b91e930e878ae12981e461e5/matplotlib-3.10.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238b7ce5717600615c895050239ec955d91f321c209dd110db988500558e70d6", size = 9596825, upload-time = "2025-12-10T22:55:25.217Z" }, - { url = "https://files.pythonhosted.org/packages/6f/d3/a4bbc01c237ab710a1f22b4da72f4ff6d77eb4c7735ea9811a94ae239067/matplotlib-3.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:18821ace09c763ec93aef5eeff087ee493a24051936d7b9ebcad9662f66501f9", size = 8135090, upload-time = "2025-12-10T22:55:27.162Z" }, - { url = "https://files.pythonhosted.org/packages/89/dd/a0b6588f102beab33ca6f5218b31725216577b2a24172f327eaf6417d5c9/matplotlib-3.10.8-cp311-cp311-win_arm64.whl", hash = "sha256:bab485bcf8b1c7d2060b4fcb6fc368a9e6f4cd754c9c2fea281f4be21df394a2", size = 8012377, upload-time = "2025-12-10T22:55:29.185Z" }, - { url = "https://files.pythonhosted.org/packages/9e/67/f997cdcbb514012eb0d10cd2b4b332667997fb5ebe26b8d41d04962fa0e6/matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a", size = 8260453, upload-time = "2025-12-10T22:55:30.709Z" }, - { url = "https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58", size = 8148321, upload-time = "2025-12-10T22:55:33.265Z" }, - { url = "https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04", size = 8716944, upload-time = "2025-12-10T22:55:34.922Z" }, - { url = "https://files.pythonhosted.org/packages/00/f9/7638f5cc82ec8a7aa005de48622eecc3ed7c9854b96ba15bd76b7fd27574/matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f", size = 9550099, upload-time = "2025-12-10T22:55:36.789Z" }, - { url = "https://files.pythonhosted.org/packages/57/61/78cd5920d35b29fd2a0fe894de8adf672ff52939d2e9b43cb83cd5ce1bc7/matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466", size = 9613040, upload-time = "2025-12-10T22:55:38.715Z" }, - { url = "https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf", size = 8142717, upload-time = "2025-12-10T22:55:41.103Z" }, - { url = "https://files.pythonhosted.org/packages/f1/76/934db220026b5fef85f45d51a738b91dea7d70207581063cd9bd8fafcf74/matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b", size = 8012751, upload-time = "2025-12-10T22:55:42.684Z" }, - { url = "https://files.pythonhosted.org/packages/3d/b9/15fd5541ef4f5b9a17eefd379356cf12175fe577424e7b1d80676516031a/matplotlib-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3f2e409836d7f5ac2f1c013110a4d50b9f7edc26328c108915f9075d7d7a91b6", size = 8261076, upload-time = "2025-12-10T22:55:44.648Z" }, - { url = "https://files.pythonhosted.org/packages/8d/a0/2ba3473c1b66b9c74dc7107c67e9008cb1782edbe896d4c899d39ae9cf78/matplotlib-3.10.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56271f3dac49a88d7fca5060f004d9d22b865f743a12a23b1e937a0be4818ee1", size = 8148794, upload-time = "2025-12-10T22:55:46.252Z" }, - { url = "https://files.pythonhosted.org/packages/75/97/a471f1c3eb1fd6f6c24a31a5858f443891d5127e63a7788678d14e249aea/matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0a7f52498f72f13d4a25ea70f35f4cb60642b466cbb0a9be951b5bc3f45a486", size = 8718474, upload-time = "2025-12-10T22:55:47.864Z" }, - { url = "https://files.pythonhosted.org/packages/01/be/cd478f4b66f48256f42927d0acbcd63a26a893136456cd079c0cc24fbabf/matplotlib-3.10.8-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:646d95230efb9ca614a7a594d4fcacde0ac61d25e37dd51710b36477594963ce", size = 9549637, upload-time = "2025-12-10T22:55:50.048Z" }, - { url = "https://files.pythonhosted.org/packages/5d/7c/8dc289776eae5109e268c4fb92baf870678dc048a25d4ac903683b86d5bf/matplotlib-3.10.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f89c151aab2e2e23cb3fe0acad1e8b82841fd265379c4cecd0f3fcb34c15e0f6", size = 9613678, upload-time = "2025-12-10T22:55:52.21Z" }, - { url = "https://files.pythonhosted.org/packages/64/40/37612487cc8a437d4dd261b32ca21fe2d79510fe74af74e1f42becb1bdb8/matplotlib-3.10.8-cp313-cp313-win_amd64.whl", hash = "sha256:e8ea3e2d4066083e264e75c829078f9e149fa119d27e19acd503de65e0b13149", size = 8142686, upload-time = "2025-12-10T22:55:54.253Z" }, - { url = "https://files.pythonhosted.org/packages/66/52/8d8a8730e968185514680c2a6625943f70269509c3dcfc0dcf7d75928cb8/matplotlib-3.10.8-cp313-cp313-win_arm64.whl", hash = "sha256:c108a1d6fa78a50646029cb6d49808ff0fc1330fda87fa6f6250c6b5369b6645", size = 8012917, upload-time = "2025-12-10T22:55:56.268Z" }, - { url = "https://files.pythonhosted.org/packages/b5/27/51fe26e1062f298af5ef66343d8ef460e090a27fea73036c76c35821df04/matplotlib-3.10.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ad3d9833a64cf48cc4300f2b406c3d0f4f4724a91c0bd5640678a6ba7c102077", size = 8305679, upload-time = "2025-12-10T22:55:57.856Z" }, - { url = "https://files.pythonhosted.org/packages/2c/1e/4de865bc591ac8e3062e835f42dd7fe7a93168d519557837f0e37513f629/matplotlib-3.10.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:eb3823f11823deade26ce3b9f40dcb4a213da7a670013929f31d5f5ed1055b22", size = 8198336, upload-time = "2025-12-10T22:55:59.371Z" }, - { url = "https://files.pythonhosted.org/packages/c6/cb/2f7b6e75fb4dce87ef91f60cac4f6e34f4c145ab036a22318ec837971300/matplotlib-3.10.8-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d9050fee89a89ed57b4fb2c1bfac9a3d0c57a0d55aed95949eedbc42070fea39", size = 8731653, upload-time = "2025-12-10T22:56:01.032Z" }, - { url = "https://files.pythonhosted.org/packages/46/b3/bd9c57d6ba670a37ab31fb87ec3e8691b947134b201f881665b28cc039ff/matplotlib-3.10.8-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b44d07310e404ba95f8c25aa5536f154c0a8ec473303535949e52eb71d0a1565", size = 9561356, upload-time = "2025-12-10T22:56:02.95Z" }, - { url = "https://files.pythonhosted.org/packages/c0/3d/8b94a481456dfc9dfe6e39e93b5ab376e50998cddfd23f4ae3b431708f16/matplotlib-3.10.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0a33deb84c15ede243aead39f77e990469fff93ad1521163305095b77b72ce4a", size = 9614000, upload-time = "2025-12-10T22:56:05.411Z" }, - { url = "https://files.pythonhosted.org/packages/bd/cd/bc06149fe5585ba800b189a6a654a75f1f127e8aab02fd2be10df7fa500c/matplotlib-3.10.8-cp313-cp313t-win_amd64.whl", hash = "sha256:3a48a78d2786784cc2413e57397981fb45c79e968d99656706018d6e62e57958", size = 8220043, upload-time = "2025-12-10T22:56:07.551Z" }, - { url = "https://files.pythonhosted.org/packages/e3/de/b22cf255abec916562cc04eef457c13e58a1990048de0c0c3604d082355e/matplotlib-3.10.8-cp313-cp313t-win_arm64.whl", hash = "sha256:15d30132718972c2c074cd14638c7f4592bd98719e2308bccea40e0538bc0cb5", size = 8062075, upload-time = "2025-12-10T22:56:09.178Z" }, - { url = "https://files.pythonhosted.org/packages/3c/43/9c0ff7a2f11615e516c3b058e1e6e8f9614ddeca53faca06da267c48345d/matplotlib-3.10.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b53285e65d4fa4c86399979e956235deb900be5baa7fc1218ea67fbfaeaadd6f", size = 8262481, upload-time = "2025-12-10T22:56:10.885Z" }, - { url = "https://files.pythonhosted.org/packages/6f/ca/e8ae28649fcdf039fda5ef554b40a95f50592a3c47e6f7270c9561c12b07/matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b", size = 8151473, upload-time = "2025-12-10T22:56:12.377Z" }, - { url = "https://files.pythonhosted.org/packages/f1/6f/009d129ae70b75e88cbe7e503a12a4c0670e08ed748a902c2568909e9eb5/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cf267add95b1c88300d96ca837833d4112756045364f5c734a2276038dae27d", size = 9553896, upload-time = "2025-12-10T22:56:14.432Z" }, - { url = "https://files.pythonhosted.org/packages/f5/26/4221a741eb97967bc1fd5e4c52b9aa5a91b2f4ec05b59f6def4d820f9df9/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008", size = 9824193, upload-time = "2025-12-10T22:56:16.29Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f3/3abf75f38605772cf48a9daf5821cd4f563472f38b4b828c6fba6fa6d06e/matplotlib-3.10.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:41703cc95688f2516b480f7f339d8851a6035f18e100ee6a32bc0b8536a12a9c", size = 9615444, upload-time = "2025-12-10T22:56:18.155Z" }, - { url = "https://files.pythonhosted.org/packages/93/a5/de89ac80f10b8dc615807ee1133cd99ac74082581196d4d9590bea10690d/matplotlib-3.10.8-cp314-cp314-win_amd64.whl", hash = "sha256:83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11", size = 8272719, upload-time = "2025-12-10T22:56:20.366Z" }, - { url = "https://files.pythonhosted.org/packages/69/ce/b006495c19ccc0a137b48083168a37bd056392dee02f87dba0472f2797fe/matplotlib-3.10.8-cp314-cp314-win_arm64.whl", hash = "sha256:2c1998e92cd5999e295a731bcb2911c75f597d937341f3030cc24ef2733d78a8", size = 8144205, upload-time = "2025-12-10T22:56:22.239Z" }, - { url = "https://files.pythonhosted.org/packages/68/d9/b31116a3a855bd313c6fcdb7226926d59b041f26061c6c5b1be66a08c826/matplotlib-3.10.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b5a2b97dbdc7d4f353ebf343744f1d1f1cca8aa8bfddb4262fcf4306c3761d50", size = 8305785, upload-time = "2025-12-10T22:56:24.218Z" }, - { url = "https://files.pythonhosted.org/packages/1e/90/6effe8103f0272685767ba5f094f453784057072f49b393e3ea178fe70a5/matplotlib-3.10.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3f5c3e4da343bba819f0234186b9004faba952cc420fbc522dc4e103c1985908", size = 8198361, upload-time = "2025-12-10T22:56:26.787Z" }, - { url = "https://files.pythonhosted.org/packages/d7/65/a73188711bea603615fc0baecca1061429ac16940e2385433cc778a9d8e7/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f62550b9a30afde8c1c3ae450e5eb547d579dd69b25c2fc7a1c67f934c1717a", size = 9561357, upload-time = "2025-12-10T22:56:28.953Z" }, - { url = "https://files.pythonhosted.org/packages/f4/3d/b5c5d5d5be8ce63292567f0e2c43dde9953d3ed86ac2de0a72e93c8f07a1/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:495672de149445ec1b772ff2c9ede9b769e3cb4f0d0aa7fa730d7f59e2d4e1c1", size = 9823610, upload-time = "2025-12-10T22:56:31.455Z" }, - { url = "https://files.pythonhosted.org/packages/4d/4b/e7beb6bbd49f6bae727a12b270a2654d13c397576d25bd6786e47033300f/matplotlib-3.10.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:595ba4d8fe983b88f0eec8c26a241e16d6376fe1979086232f481f8f3f67494c", size = 9614011, upload-time = "2025-12-10T22:56:33.85Z" }, - { url = "https://files.pythonhosted.org/packages/7c/e6/76f2813d31f032e65f6f797e3f2f6e4aab95b65015924b1c51370395c28a/matplotlib-3.10.8-cp314-cp314t-win_amd64.whl", hash = "sha256:25d380fe8b1dc32cf8f0b1b448470a77afb195438bafdf1d858bfb876f3edf7b", size = 8362801, upload-time = "2025-12-10T22:56:36.107Z" }, - { url = "https://files.pythonhosted.org/packages/5d/49/d651878698a0b67f23aa28e17f45a6d6dd3d3f933fa29087fa4ce5947b5a/matplotlib-3.10.8-cp314-cp314t-win_arm64.whl", hash = "sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f", size = 8192560, upload-time = "2025-12-10T22:56:38.008Z" }, - { url = "https://files.pythonhosted.org/packages/04/30/3afaa31c757f34b7725ab9d2ba8b48b5e89c2019c003e7d0ead143aabc5a/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6da7c2ce169267d0d066adcf63758f0604aa6c3eebf67458930f9d9b79ad1db1", size = 8249198, upload-time = "2025-12-10T22:56:45.584Z" }, - { url = "https://files.pythonhosted.org/packages/48/2f/6334aec331f57485a642a7c8be03cb286f29111ae71c46c38b363230063c/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9153c3292705be9f9c64498a8872118540c3f4123d1a1c840172edf262c8be4a", size = 8136817, upload-time = "2025-12-10T22:56:47.339Z" }, - { url = "https://files.pythonhosted.org/packages/73/e4/6d6f14b2a759c622f191b2d67e9075a3f56aaccb3be4bb9bb6890030d0a0/matplotlib-3.10.8-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2", size = 8713867, upload-time = "2025-12-10T22:56:48.954Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/63/1b/4be5be87d43d327a0cf4de1a56e86f7f84c89312452406cf122efe2839e6/matplotlib-3.10.9.tar.gz", hash = "sha256:fd66508e8c6877d98e586654b608a0456db8d7e8a546eb1e2600efd957302358", size = 34811233, upload-time = "2026-04-24T00:14:13.539Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/8c/290f021104741fea63769c31494f5324c0cd249bf536a65a4350767b1f22/matplotlib-3.10.9-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:68cfdcede415f7c8f5577b03303dd94526cdb6d11036cecdc205e08733b2d2bb", size = 8306860, upload-time = "2026-04-24T00:12:01.207Z" }, + { url = "https://files.pythonhosted.org/packages/51/18/325cd32ece1120d1da51cc4e4294c6580190699490183fc2fe8cb6d61ec5/matplotlib-3.10.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfca0129678bd56379db26c52b5d77ed7de314c047492fbdc763aa7501710cfb", size = 8199254, upload-time = "2026-04-24T00:12:04.239Z" }, + { url = "https://files.pythonhosted.org/packages/79/db/e28c1b83e3680740aa78925f5fb2ae4d16207207419ad75ea9fe604f8676/matplotlib-3.10.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e436d155fa8a3399dc62683f8f5d0e2e50d25d0144a73edd73f82eec8f4abfb", size = 8777092, upload-time = "2026-04-24T00:12:06.793Z" }, + { url = "https://files.pythonhosted.org/packages/55/fa/3ce7adfe9ba101748f465211660d9c6374c876b671bdb8c2bb6d347e8b94/matplotlib-3.10.9-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56fc0bd271b00025c6edfdc7c2dcd247372c8e1544971d62e1dc7c17367e8bf9", size = 9595691, upload-time = "2026-04-24T00:12:09.706Z" }, + { url = "https://files.pythonhosted.org/packages/36/c4/6960a76686ed668f2c60f84e9799ba4c0d56abdb36b1577b60c1d061d1ec/matplotlib-3.10.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5a6104ed666402ba5106d7f36e0e0cdca4e8d7fa4d39708ca88019e2835a2eb", size = 9659771, upload-time = "2026-04-24T00:12:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/7e/0d/271aace3342157c64700c9ff4c59c7b392f3dbab393692e8db6fbe7ab96c/matplotlib-3.10.9-cp311-cp311-win_amd64.whl", hash = "sha256:d730e984eddf56974c3e72b6129c7ca462ac38dc624338f4b0b23eb23ecba00f", size = 8205112, upload-time = "2026-04-24T00:12:15.773Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ee/cb57ad4754f3e7b9174ce6ce66d9205fb827067e48a9f58ac09d7e7d6b77/matplotlib-3.10.9-cp311-cp311-win_arm64.whl", hash = "sha256:51bf0ddbdc598e060d46c16b5590708f81a1624cefbaaf62f6a81bf9285b8c80", size = 8132310, upload-time = "2026-04-24T00:12:18.645Z" }, + { url = "https://files.pythonhosted.org/packages/35/c6/5581e26c72233ebb2a2a6fed2d24fb7c66b4700120b813f51b0555acf0b6/matplotlib-3.10.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f0c3c28d9fbcc1fe7a03be236d73430cf6409c41fb2383a7ac52fe932b072cb1", size = 8319908, upload-time = "2026-04-24T00:12:21.323Z" }, + { url = "https://files.pythonhosted.org/packages/b7/18/4880dd762e40cd360c1bf06e890c5a97b997e91cb324602b1a19950ad5ce/matplotlib-3.10.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cb28c2bd769aa3e98322c6ab09854cbcc52ab69d2759d681bba3e327b2b320", size = 8216016, upload-time = "2026-04-24T00:12:23.4Z" }, + { url = "https://files.pythonhosted.org/packages/32/91/d024616abdba99e83120e07a20658976f6a343646710760c4a51df126029/matplotlib-3.10.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ae20801130378b82d647ff5047c07316295b68dc054ca6b3c13519d0ea624285", size = 8789336, upload-time = "2026-04-24T00:12:26.096Z" }, + { url = "https://files.pythonhosted.org/packages/5c/04/030a2f61ef2158f5e4c259487a92ac877732499fb33d871585d89e03c42d/matplotlib-3.10.9-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c63ebcd8b4b169eb2f5c200552ae6b8be8999a005b6b507ed76fb8d7d674fe2", size = 9604602, upload-time = "2026-04-24T00:12:29.052Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c2/541e4d09d87bb6b5830fc28b4c887a9a8cf4e1c6cee698a8c05552ae2003/matplotlib-3.10.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d75d11c949914165976c621b2324f9ef162af7ebf4b057ddf95dd1dba7e5edcf", size = 9670966, upload-time = "2026-04-24T00:12:32.131Z" }, + { url = "https://files.pythonhosted.org/packages/04/a1/4571fc46e7702de8d0c2dc54ad1b2f8e29328dea3ee90831181f7353d93c/matplotlib-3.10.9-cp312-cp312-win_amd64.whl", hash = "sha256:d091f9d758b34aaaaa6331d13574bf01891d903b3dec59bfff458ef7551de5d6", size = 8217462, upload-time = "2026-04-24T00:12:35.226Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d0/2269edb12aa30c13c8bcc9382892e39943ce1d28aab4ec296e0381798e81/matplotlib-3.10.9-cp312-cp312-win_arm64.whl", hash = "sha256:10cc5ce06d10231c36f40e875f3c7e8050362a4ee8f0ee5d29a6b3277d57bb42", size = 8136688, upload-time = "2026-04-24T00:12:37.442Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d3/8d4f6afbecb49fc04e060a57c0fce39ea51cc163a6bd87303ccd698e4fa6/matplotlib-3.10.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b580440f1ff81a0e34122051a3dfabb7e4b7f9e380629929bde0eff9af72165f", size = 8320331, upload-time = "2026-04-24T00:12:39.688Z" }, + { url = "https://files.pythonhosted.org/packages/63/d9/9e14bc7564bf92d5ffa801ae5fac819ce74b925dfb55e3ebde61a3bbad3e/matplotlib-3.10.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b1b745c489cd1a77a0dc1120a05dc87af9798faebc913601feb8c73d89bf2d1e", size = 8216461, upload-time = "2026-04-24T00:12:42.494Z" }, + { url = "https://files.pythonhosted.org/packages/8a/17/4402d0d14ccf1dfc70932600b68097fbbf9c898a4871d2cbbe79c7801a32/matplotlib-3.10.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8f3bcac1ca5ed000a6f4337d47ba67dfddf37ed6a46c15fd7f014997f7bf865f", size = 8790091, upload-time = "2026-04-24T00:12:44.789Z" }, + { url = "https://files.pythonhosted.org/packages/3e/0b/322aeec06dd9b91411f92028b37d447342770a24392aa4813e317064dad5/matplotlib-3.10.9-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a8d66a55def891c33147ba3ba9bfcabf0b526a43764c818acbb4525e5ed0838", size = 9605027, upload-time = "2026-04-24T00:12:47.583Z" }, + { url = "https://files.pythonhosted.org/packages/74/88/5f13482f55e7b00bcfc09838b093c2456e1379978d2a146844aae05350ad/matplotlib-3.10.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d843374407c4017a6403b59c6c81606773d136f3259d5b6da3131bc814542cc2", size = 9671269, upload-time = "2026-04-24T00:12:50.878Z" }, + { url = "https://files.pythonhosted.org/packages/c5/e0/0840fd2f93da988ec660b8ad1984abe9f25d2aed22a5e394ff1c68c88307/matplotlib-3.10.9-cp313-cp313-win_amd64.whl", hash = "sha256:f4399f64b3e94cd500195490972ae1ee81170df1636fa15364d157d5bdd7b921", size = 8217588, upload-time = "2026-04-24T00:12:53.784Z" }, + { url = "https://files.pythonhosted.org/packages/47/b9/d706d06dd605c49b9f83a2aed8c13e3e5db70697d7a80b7e3d7915de6b17/matplotlib-3.10.9-cp313-cp313-win_arm64.whl", hash = "sha256:ba7b3b8ef09eab7df0e86e9ae086faa433efbfbdb46afcb3aa16aabf779469a8", size = 8136913, upload-time = "2026-04-24T00:12:56.501Z" }, + { url = "https://files.pythonhosted.org/packages/9b/45/6e32d96978264c8ca8c4b1010adb955a1a49cfaf314e212bbc8908f04a61/matplotlib-3.10.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:09218df8a93712bd6ea133e83a153c755448cf7868316c531cffcc43f69d1cc9", size = 8368019, upload-time = "2026-04-24T00:12:58.896Z" }, + { url = "https://files.pythonhosted.org/packages/86/0a/c8e3d3bba245f0f7fc424937f8ff7ef77291a36af3edb97ccd78aa93d84f/matplotlib-3.10.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:82368699727bfb7b0182e1aa13082e3c08e092fa1a25d3e1fd92405bff96f6d4", size = 8264645, upload-time = "2026-04-24T00:13:01.406Z" }, + { url = "https://files.pythonhosted.org/packages/3d/aa/5bf5a14fe4fed73a4209a155606f8096ff797aad89c6c35179026571133e/matplotlib-3.10.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3225f4e1edcb8c86c884ddf79ebe20ecd0a67d30188f279897554ccd8fded4dc", size = 8802194, upload-time = "2026-04-24T00:13:03.702Z" }, + { url = "https://files.pythonhosted.org/packages/dd/5e/b4be852d6bba6fd15893fadf91ff26ae49cb91aac789e95dde9d342e664f/matplotlib-3.10.9-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de2445a0c6690d21b7eb6ce071cebad6d40a2e9bdf10d039074a96ba19797b99", size = 9622684, upload-time = "2026-04-24T00:13:06.647Z" }, + { url = "https://files.pythonhosted.org/packages/4c/3d/ed428c971139112ef730f62770654d609467346d09d4b62617e1afd68a5a/matplotlib-3.10.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b2b9516251cb89ff618d757daec0e2ed1bf21248013844a853d87ef85ab3081d", size = 9680790, upload-time = "2026-04-24T00:13:10.009Z" }, + { url = "https://files.pythonhosted.org/packages/e7/09/052e884aaf2b985c63cb79f715f1d5b6a3eaa7de78f6a52b9dbc077d5b53/matplotlib-3.10.9-cp313-cp313t-win_amd64.whl", hash = "sha256:e9fae004b941b23ff2edcf1567a857ed77bafc8086ffa258190462328434faf8", size = 8287571, upload-time = "2026-04-24T00:13:13.087Z" }, + { url = "https://files.pythonhosted.org/packages/f4/38/ae27288e788c35a4250491422f3db7750366fc8c97d6f36fbdecfc1f5518/matplotlib-3.10.9-cp313-cp313t-win_arm64.whl", hash = "sha256:6b63d9c7c769b88ab81e10dc86e4e0607cf56817b9f9e6cf24b2a5f1693b8e38", size = 8188292, upload-time = "2026-04-24T00:13:15.546Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e6/3bd8afd04949f02eabc1c17115ea5255e19cacd4d06fc5abdde4eeb0052c/matplotlib-3.10.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:172db52c9e683f5d12eaf57f0f54834190e12581fe1cc2a19595a8f5acb4e77d", size = 8321276, upload-time = "2026-04-24T00:13:18.318Z" }, + { url = "https://files.pythonhosted.org/packages/41/86/86231232fff41c9f8e4a1a7d7a597d349a02527109c3af7d618366122139/matplotlib-3.10.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97e35e8d39ccc85859095e01a53847432ba9a53ddf7986f7a54a11b73d0e143f", size = 8218218, upload-time = "2026-04-24T00:13:20.974Z" }, + { url = "https://files.pythonhosted.org/packages/85/8f/becc9722cafc64f5d2eb0b7c1bf5f585271c618a45dbd8fabeb021f898b6/matplotlib-3.10.9-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aba1615dabe83188e19d4f75a253c6a08423e04c1425e64039f800050a69de6b", size = 9608145, upload-time = "2026-04-24T00:13:23.228Z" }, + { url = "https://files.pythonhosted.org/packages/32/5d/f7e914f7d9325abff4057cee62c0fa70263683189f774473cbfb534cd13b/matplotlib-3.10.9-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34cf8167e023ad956c15f36302911d5406bd99a9862c1a8499ea6f7c0e015dc2", size = 9885085, upload-time = "2026-04-24T00:13:25.849Z" }, + { url = "https://files.pythonhosted.org/packages/a5/fd/fa69f2221534e80cc5772ac2b7d222011a2acafc2ec7216d5dd174c864ae/matplotlib-3.10.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:59476c6d29d612b8e9bb6ce8c5b631be6ba8f9e3a2421f22a02b192c7dd28716", size = 9672358, upload-time = "2026-04-24T00:13:28.906Z" }, + { url = "https://files.pythonhosted.org/packages/ab/1a/5a4f747a8b271cbb024946d2dd3c913ab5032ba430626f8c3528ada96b4b/matplotlib-3.10.9-cp314-cp314-win_amd64.whl", hash = "sha256:336b9acc64d309063126edcdaca00db9373af3c476bb94388fe9c5a53ad13e6f", size = 8349970, upload-time = "2026-04-24T00:13:31.904Z" }, + { url = "https://files.pythonhosted.org/packages/64/dc/95d60ecaefe30680a154b52ea96ab4b0dab547f1fd6aa12f5fb655e89cae/matplotlib-3.10.9-cp314-cp314-win_arm64.whl", hash = "sha256:2dc9477819ffd78ad12a20df1d9d6a6bd4fec6aaa9072681465fddca052f1456", size = 8272785, upload-time = "2026-04-24T00:13:34.511Z" }, + { url = "https://files.pythonhosted.org/packages/70/a0/005d68bc8b8418300ce6591f18586910a8526806e2ab663933d9f20a41e9/matplotlib-3.10.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:da4e09638420548f31c354032a6250e473c68e5a4e96899b4844cf39ddea23fe", size = 8367999, upload-time = "2026-04-24T00:13:36.962Z" }, + { url = "https://files.pythonhosted.org/packages/22/05/1236cc9290be70b2498af20ca348add76e3fffe7f67b477db5133a84f3ea/matplotlib-3.10.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:345f6f68ecc8da0ca56fad2ea08fde1a115eda530079eca185d50a7bc3e146c6", size = 8264543, upload-time = "2026-04-24T00:13:39.851Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c2/071f5a5ff6c5bd63aaaf2f45c811d9bf2ced94bde188d9e1a519e21d0cba/matplotlib-3.10.9-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4edcfbd8565339aa62f1cd4012f7180926fdbe71850f7b0d3c379c175cd6b66c", size = 9622800, upload-time = "2026-04-24T00:13:42.296Z" }, + { url = "https://files.pythonhosted.org/packages/95/57/da7d1f10a85624b9e7db68e069dd94e58dc41dbf9463c5921632ecbe3661/matplotlib-3.10.9-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6be157fe17fc37cb95ac1d7374cf717ce9259616edec911a78d9d26dae8522d4", size = 9888561, upload-time = "2026-04-24T00:13:45.026Z" }, + { url = "https://files.pythonhosted.org/packages/67/b2/ef8d6bb59b0edb6c16c968b70f548aa13b54348972def5aa6ac85df67145/matplotlib-3.10.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4e42042d54db34fda4e95a7bd3e5789c2a995d2dad3eb8850232ee534092fbbf", size = 9680884, upload-time = "2026-04-24T00:13:48.066Z" }, + { url = "https://files.pythonhosted.org/packages/61/1c/d21bfeb9931881ebe96bcfcff27c7ae4b160ae0ec291a714c42641a56d75/matplotlib-3.10.9-cp314-cp314t-win_amd64.whl", hash = "sha256:c27df8b3848f32a83d1767566595e43cfaa4460380974da06f4279a7ec143c39", size = 8432333, upload-time = "2026-04-24T00:13:51.008Z" }, + { url = "https://files.pythonhosted.org/packages/78/23/92493c3e6e1b635ccfff146f7b99e674808787915420373ac399283764c2/matplotlib-3.10.9-cp314-cp314t-win_arm64.whl", hash = "sha256:a49f1eadc84ca85fd72fa4e89e70e61bf86452df6f971af04b12c60761a0772c", size = 8324785, upload-time = "2026-04-24T00:13:53.633Z" }, + { url = "https://files.pythonhosted.org/packages/63/e2/9f66ca6a651a52abfe0d4964ce01439ed34f3f1e119de10ff3a07f403043/matplotlib-3.10.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:42fb814efabe95c06c1994d8ab5a8385f43a249e23badd3ba931d4308e5bca20", size = 8304420, upload-time = "2026-04-24T00:14:04.57Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e8/467c03568218792906aa87b5e7bb379b605e056ed0c74fe00c051786d925/matplotlib-3.10.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f76e640a5268850bfda54b5131b1b1941cc685e42c5fa98ed9f2d64038308cba", size = 8197981, upload-time = "2026-04-24T00:14:07.233Z" }, + { url = "https://files.pythonhosted.org/packages/6f/87/afead29192170917537934c6aff4b008c805fff7b1ccea0c79120d96beda/matplotlib-3.10.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3fc0364dfbe1d07f6d15c5ebd0c5bf89e126916e5a8667dd4a7a6e84c36653d4", size = 8774002, upload-time = "2026-04-24T00:14:09.816Z" }, ] [[package]] @@ -2295,11 +2266,11 @@ wheels = [ [[package]] name = "packaging" -version = "26.0" +version = "26.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, ] [[package]] @@ -2467,7 +2438,7 @@ wheels = [ [[package]] name = "pre-commit" -version = "4.5.1" +version = "4.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cfgv" }, @@ -2476,9 +2447,9 @@ dependencies = [ { name = "pyyaml" }, { name = "virtualenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/22/2de9408ac81acbb8a7d05d4cc064a152ccf33b3d480ebe0cd292153db239/pre_commit-4.6.0.tar.gz", hash = "sha256:718d2208cef53fdc38206e40524a6d4d9576d103eb16f0fec11c875e7716e9d9", size = 198525, upload-time = "2026-04-21T20:31:41.613Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" }, + { url = "https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl", hash = "sha256:e2cf246f7299edcabcf15f9b0571fdce06058527f0a06535068a86d38089f29b", size = 226472, upload-time = "2026-04-21T20:31:40.092Z" }, ] [[package]] @@ -3156,27 +3127,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/d9/aa3f7d59a10ef6b14fe3431706f854dbf03c5976be614a9796d36326810c/ruff-0.15.10.tar.gz", hash = "sha256:d1f86e67ebfdef88e00faefa1552b5e510e1d35f3be7d423dc7e84e63788c94e", size = 4631728, upload-time = "2026-04-09T14:06:09.884Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/00/a1c2fdc9939b2c03691edbda290afcd297f1f389196172826b03d6b6a595/ruff-0.15.10-py3-none-linux_armv6l.whl", hash = "sha256:0744e31482f8f7d0d10a11fcbf897af272fefdfcb10f5af907b18c2813ff4d5f", size = 10563362, upload-time = "2026-04-09T14:06:21.189Z" }, - { url = "https://files.pythonhosted.org/packages/5c/15/006990029aea0bebe9d33c73c3e28c80c391ebdba408d1b08496f00d422d/ruff-0.15.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b1e7c16ea0ff5a53b7c2df52d947e685973049be1cdfe2b59a9c43601897b22e", size = 10951122, upload-time = "2026-04-09T14:06:02.236Z" }, - { url = "https://files.pythonhosted.org/packages/f2/c0/4ac978fe874d0618c7da647862afe697b281c2806f13ce904ad652fa87e4/ruff-0.15.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:93cc06a19e5155b4441dd72808fdf84290d84ad8a39ca3b0f994363ade4cebb1", size = 10314005, upload-time = "2026-04-09T14:06:00.026Z" }, - { url = "https://files.pythonhosted.org/packages/da/73/c209138a5c98c0d321266372fc4e33ad43d506d7e5dd817dd89b60a8548f/ruff-0.15.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83e1dd04312997c99ea6965df66a14fb4f03ba978564574ffc68b0d61fd3989e", size = 10643450, upload-time = "2026-04-09T14:05:42.137Z" }, - { url = "https://files.pythonhosted.org/packages/ec/76/0deec355d8ec10709653635b1f90856735302cb8e149acfdf6f82a5feb70/ruff-0.15.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8154d43684e4333360fedd11aaa40b1b08a4e37d8ffa9d95fee6fa5b37b6fab1", size = 10379597, upload-time = "2026-04-09T14:05:49.984Z" }, - { url = "https://files.pythonhosted.org/packages/dc/be/86bba8fc8798c081e28a4b3bb6d143ccad3fd5f6f024f02002b8f08a9fa3/ruff-0.15.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ab88715f3a6deb6bde6c227f3a123410bec7b855c3ae331b4c006189e895cef", size = 11146645, upload-time = "2026-04-09T14:06:12.246Z" }, - { url = "https://files.pythonhosted.org/packages/a8/89/140025e65911b281c57be1d385ba1d932c2366ca88ae6663685aed8d4881/ruff-0.15.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a768ff5969b4f44c349d48edf4ab4f91eddb27fd9d77799598e130fb628aa158", size = 12030289, upload-time = "2026-04-09T14:06:04.776Z" }, - { url = "https://files.pythonhosted.org/packages/88/de/ddacca9545a5e01332567db01d44bd8cf725f2db3b3d61a80550b48308ea/ruff-0.15.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ee3ef42dab7078bda5ff6a1bcba8539e9857deb447132ad5566a038674540d0", size = 11496266, upload-time = "2026-04-09T14:05:55.485Z" }, - { url = "https://files.pythonhosted.org/packages/bc/bb/7ddb00a83760ff4a83c4e2fc231fd63937cc7317c10c82f583302e0f6586/ruff-0.15.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51cb8cc943e891ba99989dd92d61e29b1d231e14811db9be6440ecf25d5c1609", size = 11256418, upload-time = "2026-04-09T14:05:57.69Z" }, - { url = "https://files.pythonhosted.org/packages/dc/8d/55de0d35aacf6cd50b6ee91ee0f291672080021896543776f4170fc5c454/ruff-0.15.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e59c9bdc056a320fb9ea1700a8d591718b8faf78af065484e801258d3a76bc3f", size = 11288416, upload-time = "2026-04-09T14:05:44.695Z" }, - { url = "https://files.pythonhosted.org/packages/68/cf/9438b1a27426ec46a80e0a718093c7f958ef72f43eb3111862949ead3cc1/ruff-0.15.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:136c00ca2f47b0018b073f28cb5c1506642a830ea941a60354b0e8bc8076b151", size = 10621053, upload-time = "2026-04-09T14:05:52.782Z" }, - { url = "https://files.pythonhosted.org/packages/4c/50/e29be6e2c135e9cd4cb15fbade49d6a2717e009dff3766dd080fcb82e251/ruff-0.15.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8b80a2f3c9c8a950d6237f2ca12b206bccff626139be9fa005f14feb881a1ae8", size = 10378302, upload-time = "2026-04-09T14:06:14.361Z" }, - { url = "https://files.pythonhosted.org/packages/18/2f/e0b36a6f99c51bb89f3a30239bc7bf97e87a37ae80aa2d6542d6e5150364/ruff-0.15.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e3e53c588164dc025b671c9df2462429d60357ea91af7e92e9d56c565a9f1b07", size = 10850074, upload-time = "2026-04-09T14:06:16.581Z" }, - { url = "https://files.pythonhosted.org/packages/11/08/874da392558ce087a0f9b709dc6ec0d60cbc694c1c772dab8d5f31efe8cb/ruff-0.15.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b0c52744cf9f143a393e284125d2576140b68264a93c6716464e129a3e9adb48", size = 11358051, upload-time = "2026-04-09T14:06:18.948Z" }, - { url = "https://files.pythonhosted.org/packages/e4/46/602938f030adfa043e67112b73821024dc79f3ab4df5474c25fa4c1d2d14/ruff-0.15.10-py3-none-win32.whl", hash = "sha256:d4272e87e801e9a27a2e8df7b21011c909d9ddd82f4f3281d269b6ba19789ca5", size = 10588964, upload-time = "2026-04-09T14:06:07.14Z" }, - { url = "https://files.pythonhosted.org/packages/25/b6/261225b875d7a13b33a6d02508c39c28450b2041bb01d0f7f1a83d569512/ruff-0.15.10-py3-none-win_amd64.whl", hash = "sha256:28cb32d53203242d403d819fd6983152489b12e4a3ae44993543d6fe62ab42ed", size = 11745044, upload-time = "2026-04-09T14:05:39.473Z" }, - { url = "https://files.pythonhosted.org/packages/58/ed/dea90a65b7d9e69888890fb14c90d7f51bf0c1e82ad800aeb0160e4bacfd/ruff-0.15.10-py3-none-win_arm64.whl", hash = "sha256:601d1610a9e1f1c2165a4f561eeaa2e2ea1e97f3287c5aa258d3dab8b57c6188", size = 11035607, upload-time = "2026-04-09T14:05:47.593Z" }, +version = "0.15.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/99/43/3291f1cc9106f4c63bdce7a8d0df5047fe8422a75b091c16b5e9355e0b11/ruff-0.15.12.tar.gz", hash = "sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6", size = 4643852, upload-time = "2026-04-24T18:17:14.305Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/6e/e78ffb61d4686f3d96ba3df2c801161843746dcbcbb17a1e927d4829312b/ruff-0.15.12-py3-none-linux_armv6l.whl", hash = "sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c", size = 10640713, upload-time = "2026-04-24T18:17:22.841Z" }, + { url = "https://files.pythonhosted.org/packages/ae/08/a317bc231fb9e7b93e4ef3089501e51922ff88d6936ce5cf870c4fe55419/ruff-0.15.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3bcd123364c3770b8e1b7baaf343cc99a35f197c5c6e8af79015c666c423a6c", size = 11069267, upload-time = "2026-04-24T18:17:30.105Z" }, + { url = "https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5", size = 10397182, upload-time = "2026-04-24T18:17:07.177Z" }, + { url = "https://files.pythonhosted.org/packages/71/e0/3310fc6d1b5e1fdea22bf3b1b807c7e187b581021b0d7d4514cccdb5fb71/ruff-0.15.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84a1630093121375a3e2a95b4a6dc7b59e2b4ee76216e32d81aae550a832d002", size = 10758012, upload-time = "2026-04-24T18:16:55.759Z" }, + { url = "https://files.pythonhosted.org/packages/11/c1/a606911aee04c324ddaa883ae418f3569792fd3c4a10c50e0dd0a2311e1e/ruff-0.15.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb129f40f114f089ebe0ca56c0d251cf2061b17651d464bb6478dc01e69f11f5", size = 10447479, upload-time = "2026-04-24T18:16:51.677Z" }, + { url = "https://files.pythonhosted.org/packages/9d/68/4201e8444f0894f21ab4aeeaee68aa4f10b51613514a20d80bd628d57e88/ruff-0.15.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0c862b172d695db7598426b8af465e7e9ac00a3ea2a3630ee67eb82e366aaa6", size = 11234040, upload-time = "2026-04-24T18:17:16.529Z" }, + { url = "https://files.pythonhosted.org/packages/34/ff/8a6d6cf4ccc23fd67060874e832c18919d1557a0611ebef03fdb01fff11e/ruff-0.15.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2849ea9f3484c3aca43a82f484210370319e7170df4dfe4843395ddf6c57bc33", size = 12087377, upload-time = "2026-04-24T18:17:04.944Z" }, + { url = "https://files.pythonhosted.org/packages/85/f6/c669cf73f5152f623d34e69866a46d5e6185816b19fcd5b6dd8a2d299922/ruff-0.15.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e77c7e51c07fe396826d5969a5b846d9cd4c402535835fb6e21ce8b28fef847", size = 11367784, upload-time = "2026-04-24T18:17:25.409Z" }, + { url = "https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0", size = 11344088, upload-time = "2026-04-24T18:17:12.258Z" }, + { url = "https://files.pythonhosted.org/packages/c2/8d/49afab3645e31e12c590acb6d3b5b69d7aab5b81926dbaf7461f9441f37a/ruff-0.15.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9ba3b8f1afd7e2e43d8943e55f249e13f9682fde09711644a6e7290eb4f3e339", size = 11271770, upload-time = "2026-04-24T18:17:02.457Z" }, + { url = "https://files.pythonhosted.org/packages/46/06/33f41fe94403e2b755481cdfb9b7ef3e4e0ed031c4581124658d935d52b4/ruff-0.15.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e852ba9fdc890655e1d78f2df1499efbe0e54126bd405362154a75e2bde159c5", size = 10719355, upload-time = "2026-04-24T18:17:27.648Z" }, + { url = "https://files.pythonhosted.org/packages/0d/59/18aa4e014debbf559670e4048e39260a85c7fcee84acfd761ac01e7b8d35/ruff-0.15.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dd8aed930da53780d22fc70bdf84452c843cf64f8cb4eb38984319c24c5cd5fd", size = 10462758, upload-time = "2026-04-24T18:17:32.347Z" }, + { url = "https://files.pythonhosted.org/packages/25/e7/cc9f16fd0f3b5fddcbd7ec3d6ae30c8f3fde1047f32a4093a98d633c6570/ruff-0.15.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01da3988d225628b709493d7dc67c3b9b12c0210016b08690ef9bd27970b262b", size = 10953498, upload-time = "2026-04-24T18:17:20.674Z" }, + { url = "https://files.pythonhosted.org/packages/72/7a/a9ba7f98c7a575978698f4230c5e8cc54bbc761af34f560818f933dafa0c/ruff-0.15.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9cae0f92bd5700d1213188b31cd3bdd2b315361296d10b96b8e2337d3d11f53e", size = 11447765, upload-time = "2026-04-24T18:17:09.755Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f9/0ae446942c846b8266059ad8a30702a35afae55f5cdc54c5adf8d7afdc27/ruff-0.15.12-py3-none-win32.whl", hash = "sha256:d0185894e038d7043ba8fd6aee7499ece6462dc0ea9f1e260c7451807c714c20", size = 10657277, upload-time = "2026-04-24T18:17:18.591Z" }, + { url = "https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl", hash = "sha256:c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d", size = 11837758, upload-time = "2026-04-24T18:17:00.113Z" }, + { url = "https://files.pythonhosted.org/packages/c0/98/6beb4b351e472e5f4c4613f7c35a5290b8be2497e183825310c4c3a3984b/ruff-0.15.12-py3-none-win_arm64.whl", hash = "sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f", size = 11120821, upload-time = "2026-04-24T18:16:57.979Z" }, ] [[package]] @@ -3757,11 +3728,11 @@ wheels = [ [[package]] name = "tzdata" -version = "2026.1" +version = "2026.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/f5/cd531b2d15a671a40c0f66cf06bc3570a12cd56eef98960068ebbad1bf5a/tzdata-2026.1.tar.gz", hash = "sha256:67658a1903c75917309e753fdc349ac0efd8c27db7a0cb406a25be4840f87f98", size = 197639, upload-time = "2026-04-03T11:25:22.002Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/19/1b9b0e29f30c6d35cb345486df41110984ea67ae69dddbc0e8a100999493/tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", size = 198254, upload-time = "2026-04-24T15:22:08.651Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/70/d460bd685a170790ec89317e9bd33047988e4bce507b831f5db771e142de/tzdata-2026.1-py2.py3-none-any.whl", hash = "sha256:4b1d2be7ac37ceafd7327b961aa3a54e467efbdb563a23655fbfe0d39cfc42a9", size = 348952, upload-time = "2026-04-03T11:25:20.313Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321, upload-time = "2026-04-24T15:22:05.876Z" }, ] [[package]] @@ -3784,7 +3755,7 @@ wheels = [ [[package]] name = "virtualenv" -version = "21.2.1" +version = "21.2.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -3792,9 +3763,9 @@ dependencies = [ { name = "platformdirs" }, { name = "python-discovery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/97/c5/aff062c66b42e2183201a7ace10c6b2e959a9a16525c8e8ca8e59410d27a/virtualenv-21.2.1.tar.gz", hash = "sha256:b66ffe81301766c0d5e2208fc3576652c59d44e7b731fc5f5ed701c9b537fa78", size = 5844770, upload-time = "2026-04-09T18:47:11.482Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/98/3a7e644e19cb26133488caff231be390579860bbbb3da35913c49a1d0a46/virtualenv-21.2.4.tar.gz", hash = "sha256:b294ef68192638004d72524ce7ef303e9d0cf5a44c95ce2e54a7500a6381cada", size = 5850742, upload-time = "2026-04-14T22:15:31.438Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/0e/f083a76cb590e60dff3868779558eefefb8dfb7c9ed020babc7aa014ccbf/virtualenv-21.2.1-py3-none-any.whl", hash = "sha256:bd16b49c53562b28cf1a3ad2f36edb805ad71301dee70ddc449e5c88a9f919a2", size = 5828326, upload-time = "2026-04-09T18:47:09.331Z" }, + { url = "https://files.pythonhosted.org/packages/27/8d/edd0bd910ff803c308ee9a6b7778621af0d10252219ad9f19ef4d4982a61/virtualenv-21.2.4-py3-none-any.whl", hash = "sha256:29d21e941795206138d0f22f4e45ff7050e5da6c6472299fb7103318763861ac", size = 5831232, upload-time = "2026-04-14T22:15:29.342Z" }, ] [[package]] @@ -3873,9 +3844,9 @@ wheels = [ [[package]] name = "zipp" -version = "3.23.0" +version = "3.23.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } +sdist = { url = "https://files.pythonhosted.org/packages/30/21/093488dfc7cc8964ded15ab726fad40f25fd3d788fd741cc1c5a17d78ee8/zipp-3.23.1.tar.gz", hash = "sha256:32120e378d32cd9714ad503c1d024619063ec28aad2248dc6672ad13edfa5110", size = 25965, upload-time = "2026-04-13T23:21:46.6Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, + { url = "https://files.pythonhosted.org/packages/08/8a/0861bec20485572fbddf3dfba2910e38fe249796cb73ecdeb74e07eeb8d3/zipp-3.23.1-py3-none-any.whl", hash = "sha256:0b3596c50a5c700c9cb40ba8d86d9f2cc4807e9bedb06bcdf7fac85633e444dc", size = 10378, upload-time = "2026-04-13T23:21:45.386Z" }, ] From dc0b5f0c845d4ce7788896a0c4be11b58b9eb19a Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Mon, 27 Apr 2026 10:31:29 -0700 Subject: [PATCH 262/335] Doing some refactoring, adding reconstruction context to help with type hinting and make PPLR more extensible --- src/quantem/tomography/object_models.py | 1 + src/quantem/tomography/tomography.py | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 0ae1e74a..2bc8fa4a 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -18,6 +18,7 @@ from quantem.core.ml.optimizer_mixin import OptimizerMixin, OptimizerType from quantem.core.utils.rng import RNGMixin from quantem.tomography.dataset_models import TomographyINRPretrainDataset +from quantem.tomography.tomography import ReconstructionContext as ctx class ObjConstraintParams: diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 826ad360..f21a30d1 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -1,6 +1,7 @@ import os +from dataclasses import dataclass from pathlib import Path -from typing import Literal, Self, Sequence +from typing import Literal, Optional, Self, Sequence import matplotlib.pyplot as plt import numpy as np @@ -625,3 +626,20 @@ def plot_losses(self): ax.set_title("Reconstruction Loss") ax.set_yscale("log") plt.show() + + +@dataclass +class ReconstructionContext: + """ + Handles all reconstruction parameters to be passed into object models. + + Subclasses will pick whatever parameter they need + - Pixelated reads ".volume" + - INR reads ".coords" and recomputes via the model. + - TEnsorDEcomp reads ".coords" and ".pred" (and ".all densities") + """ + + coords: Optional[torch.Tensor] = None + pred: Optional[torch.Tensor] = None + all_densities: Optional[torch.Tensor] = None + volume: Optional[torch.Tensor] = None \ No newline at end of file From 01456ed62cb12e9857acc6be9a3774d3566e9135 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Mon, 27 Apr 2026 11:12:18 -0700 Subject: [PATCH 263/335] Working on ObjectPixelated implementing ctx --- .vscode/settings.json | 1 + src/quantem/__init__.py | 1 + src/quantem/core/datastructures/dataset3d.py | 3 +- src/quantem/core/ml/constraints.py | 6 +- src/quantem/core/ml/models/kplanes.py | 156 ++++++----- src/quantem/core/ml/models/model_base.py | 3 +- src/quantem/core/ml/models/so3params.py | 84 +++--- src/quantem/core/ml/optimizer_mixin.py | 17 +- .../core/visualization/visualization.py | 7 +- .../ptychography_visualizations.py | 2 +- src/quantem/imaging/drift.py | 2 +- src/quantem/tomography/dataset_models.py | 13 +- src/quantem/tomography/object_models.py | 265 +++++++++--------- src/quantem/tomography/tomography.py | 14 +- src/quantem/tomography/tomography_opt.py | 2 +- tests/datastructures/test_dataset3d_show.py | 82 +++--- 16 files changed, 352 insertions(+), 306 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 1659b33b..80434c6b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -78,4 +78,5 @@ "yticks" ], "basedpyright.analysis.typeCheckingMode": "standard", + "python.REPL.enableREPLSmartSend": false, } diff --git a/src/quantem/__init__.py b/src/quantem/__init__.py index b9aa54b4..ba70f629 100644 --- a/src/quantem/__init__.py +++ b/src/quantem/__init__.py @@ -1,4 +1,5 @@ from pkgutil import extend_path + __path__ = extend_path(__path__, __name__) from importlib.metadata import version diff --git a/src/quantem/core/datastructures/dataset3d.py b/src/quantem/core/datastructures/dataset3d.py index 118ec66f..154d53ee 100644 --- a/src/quantem/core/datastructures/dataset3d.py +++ b/src/quantem/core/datastructures/dataset3d.py @@ -318,8 +318,7 @@ def show( ncols = min(ncols, n_frames) # Don't create more columns than frames images = [self.array[i] for i in frame_idx] labels = [ - f"Frame {i}" if title_prefix is None else f"{title_prefix} {i}" - for i in frame_idx + f"Frame {i}" if title_prefix is None else f"{title_prefix} {i}" for i in frame_idx ] # Pad last row to complete the grid (show_2d requires rectangular input) remainder = n_frames % ncols diff --git a/src/quantem/core/ml/constraints.py b/src/quantem/core/ml/constraints.py index 553b0611..137ed4df 100644 --- a/src/quantem/core/ml/constraints.py +++ b/src/quantem/core/ml/constraints.py @@ -7,6 +7,8 @@ import torch from numpy.typing import NDArray +from quantem.tomography.tomography import ReconstructionContext + @dataclass(slots=False) class Constraints(ABC): @@ -86,14 +88,14 @@ def constraints(self, constraints: Constraints | dict[str, Any]): # --- Required methods tha tneeds to implemented in subclasses --- @abstractmethod - def apply_hard_constraints(self, *args, **kwargs) -> torch.Tensor: + def apply_hard_constraints(self, pred: torch.Tensor) -> torch.Tensor: """ Apply hard constraints to the model. """ raise NotImplementedError @abstractmethod - def apply_soft_constraints(self, *args, **kwargs) -> torch.Tensor: + def apply_soft_constraints(self, ctx: ReconstructionContext) -> torch.Tensor: """ Apply soft constraints to the model. """ diff --git a/src/quantem/core/ml/models/kplanes.py b/src/quantem/core/ml/models/kplanes.py index c8b49e70..fe36dfcc 100644 --- a/src/quantem/core/ml/models/kplanes.py +++ b/src/quantem/core/ml/models/kplanes.py @@ -3,7 +3,6 @@ """ import itertools -import math from typing import Callable, Optional, Sequence # import tinycudann as tcnn @@ -17,15 +16,19 @@ """ K-planes utility functions """ -def grid_sample_wrapper(grid: torch.Tensor, coords: torch.Tensor, align_corners: bool = True) -> torch.Tensor: + + +def grid_sample_wrapper( + grid: torch.Tensor, coords: torch.Tensor, align_corners: bool = True +) -> torch.Tensor: """ Performs bilinear interpolation on a grid at given coordinates. - + Args: grid: Grid tensor of shape [B, C, H, W] or [C, H, W] coords: Coordinate tensor of shape [B, N, 2] or [N, 2] align_corners: Whether to align corners - + Returns: Interpolated values of shape [B, N, C] or [N, C] """ @@ -40,8 +43,10 @@ def grid_sample_wrapper(grid: torch.Tensor, coords: torch.Tensor, align_corners: if grid_dim == 2 or grid_dim == 3: grid_sampler = F.grid_sample else: - raise NotImplementedError(f"Grid-sample was called with {grid_dim}D data but is only " - f"implemented for 2 and 3D data.") + raise NotImplementedError( + f"Grid-sample was called with {grid_dim}D data but is only " + f"implemented for 2 and 3D data." + ) coords = coords.view([coords.shape[0]] + [1] * (grid_dim - 1) + list(coords.shape[1:])) B, feature_dim = grid.shape[:2] @@ -50,11 +55,14 @@ def grid_sample_wrapper(grid: torch.Tensor, coords: torch.Tensor, align_corners: grid, # [B, feature_dim, reso, ...] coords, # [B, 1, ..., n, grid_dim] align_corners=align_corners, - mode='bilinear', padding_mode='border') + mode="bilinear", + padding_mode="border", + ) interp = interp.view(B, feature_dim, n).transpose(-1, -2) # [B, n, feature_dim] interp = interp.squeeze() # [B?, n, feature_dim?] return interp + def init_planes( in_dim: int, out_dim: int, @@ -62,18 +70,18 @@ def init_planes( init_range: tuple = (0.1, 0.5), ) -> nn.ParameterList: """Create the set of 2D planes for a k-plane decomposition. - + For in_dim=3 (spatial), this creates 3 planes: XY, XZ, YZ. For in_dim=4 (spatial + time), this creates 6 planes: XY, XZ, XT, YZ, YT, ZT. Time planes (those involving axis 3) are initialized to 1 so they start as identity multipliers. - + Args: in_dim: Dimensionality of the input coordinates (3 or 4). out_dim: Number of feature channels per plane. resolution: Resolution along each axis, e.g. [128, 128, 128]. init_range: (a, b) for uniform initialization of spatial planes. - + Returns: nn.ParameterList of plane parameters, each of shape [1, out_dim, res_j, res_i]. """ @@ -94,21 +102,22 @@ def init_planes( planes.append(param) return planes + def query_planes( pts: torch.Tensor, planes: nn.ParameterList, in_dim: int, ) -> float: """Query the k-plane representation at a batch of points. - + Projects each point onto every axis-pair plane, bilinearly interpolates, and returns the element-wise product across all planes. - + Args: pts: (B, in_dim) coordinates in [-1, 1]. planes: The ParameterList from init_planes. in_dim: 3 or 4. - + Returns: (B, out_dim) features. """ @@ -116,30 +125,33 @@ def query_planes( result = 1.0 for plane_param, pair in zip(planes, axis_pairs): # Extract the 2D coords for this plane - coords_2d = pts[..., list(pair)] # (B, 2) - coords_2d = coords_2d.view(1, -1, 1, 2) # (1, B, 1, 2) for grid_sample + coords_2d = pts[..., list(pair)] # (B, 2) + coords_2d = coords_2d.view(1, -1, 1, 2) # (1, B, 1, 2) for grid_sample # grid_sample: input (N,C,H,W), grid (N, H_out, W_out, 2) sampled = F.grid_sample( - plane_param, # (1, C, H, W) - coords_2d, # (1, B, 1, 2) + plane_param, # (1, C, H, W) + coords_2d, # (1, B, 1, 2) align_corners=True, mode="bilinear", padding_mode="border", ) # -> (1, C, B, 1) - sampled = sampled.squeeze(0).squeeze(-1).T # (B, C) + sampled = sampled.squeeze(0).squeeze(-1).T # (B, C) result = result * sampled return result # pyright: ignore[reportReturnType] - + + def interpolate_ms_features( pts: torch.Tensor, ms_grids: nn.ParameterList, ) -> torch.Tensor: mat_mode = [[0, 1], [0, 2], [1, 2]] - coord_plane = torch.stack([ - pts[:, mat_mode[0]], - pts[:, mat_mode[1]], - pts[:, mat_mode[2]], - ]).view(3, -1, 1, 2) + coord_plane = torch.stack( + [ + pts[:, mat_mode[0]], + pts[:, mat_mode[1]], + pts[:, mat_mode[2]], + ] + ).view(3, -1, 1, 2) per_scale = [] for plane_coef in ms_grids: @@ -154,7 +166,6 @@ def interpolate_ms_features( class KPlanes(PPLR, TensorDecompositionModel): - def __init__( self, # Grid parameters @@ -164,7 +175,9 @@ def __init__( resolution: Sequence[int] = (200, 200, 200), multiscale_res_multipliers: Optional[Sequence[int]] = None, concat_features: bool = True, - density_activation: Callable = lambda x: F.softplus(x - 1), # Keep playing around with this and trunc_exp + density_activation: Callable = lambda x: F.softplus( + x - 1 + ), # Keep playing around with this and trunc_exp # Hybrid MLP parameters use_hybrid_mlp: bool = False, hybrid_hidden_dim: int = 64, @@ -218,7 +231,6 @@ def __init__( layers.append(out) self.sigma_net = nn.Sequential(*layers) - def get_densities(self, coords: torch.Tensor): """Computes and returns densities""" pts = coords.reshape(-1, 3) @@ -252,7 +264,7 @@ def td_type(self, td_type: str): if not isinstance(td_type, str): raise TypeError("td_type must be a string") self._td_type = td_type - + @property def tilted(self) -> bool: return False @@ -283,13 +295,15 @@ def resolution(self, resolution: Sequence[int]): raise TypeError("Resolution must be a sequence") self._resolution = list(resolution) + # --------------------------------------------------------------------------- # KPlanesTILTED # --------------------------------------------------------------------------- - + + def interpolate_ms_features_tilted( - pts: torch.Tensor, # (B, 3) - ms_grids: nn.ParameterList, # each grid: (3*T, C, H, W) + pts: torch.Tensor, # (B, 3) + ms_grids: nn.ParameterList, # each grid: (3*T, C, H, W) rotation_matrices: torch.Tensor, # (T, 3, 3) ) -> torch.Tensor: """ @@ -305,17 +319,15 @@ def interpolate_ms_features_tilted( # Build (T, 3, B, 2) coords for planes XY, ZX, YZ in one shot. # index_select is faster and cleaner than advanced indexing with python lists. # Plane axis layout: XY=(0,1), ZX=(2,0), YZ=(1,2) - idx = torch.tensor([[0, 1], - [2, 0], - [1, 2]], device=pts.device) # (3, 2) + idx = torch.tensor([[0, 1], [2, 0], [1, 2]], device=pts.device) # (3, 2) # rotated: (T, B, 3) -> gather along last dim with idx (3, 2) # Result: (T, 3, B, 2) - coords = rotated.unsqueeze(1).expand(T, 3, B, 3).gather( - -1, idx.view(1, 3, 1, 2).expand(T, 3, B, 2) + coords = ( + rotated.unsqueeze(1).expand(T, 3, B, 3).gather(-1, idx.view(1, 3, 1, 2).expand(T, 3, B, 2)) ) # Flatten (T, 3) -> 3*T so it matches grid's first dim, and add the H_out=1 axis - coord_tensor = coords.reshape(3 * T, B, 1, 2) # (3T, B, 1, 2) + coord_tensor = coords.reshape(3 * T, B, 1, 2) # (3T, B, 1, 2) per_scale_features = [] for plane_coef in ms_grids: @@ -334,13 +346,12 @@ def interpolate_ms_features_tilted( sampled = sampled.squeeze(-1).view(T, 3, C, B).prod(dim=1) # (T, C, B) -> (B, T, C) -> (B, T*C) to concatenate rotations along feature dim - per_scale_features.append( - sampled.permute(2, 0, 1).reshape(B, T * C) - ) + per_scale_features.append(sampled.permute(2, 0, 1).reshape(B, T * C)) # Concatenate across scales -> (B, T * C * num_scales) return torch.cat(per_scale_features, dim=-1) + class KPlanesTILTED(KPlanes): """ K-Planes with T learned SO(3) rotations (TILTED). @@ -392,7 +403,6 @@ def __init__( hybrid_num_layers: int = 2, so3_param_type: str = "r9svd", ): - self._td_type = "tilted" if input_coords_dims != 3: raise NotImplementedError("KPlanesTILTED is implemented for 3D only.") @@ -427,9 +437,7 @@ def __init__( self.grids = nn.ParameterList() for res_mult in multiscale_res_multipliers: scaled_res = [int(r * res_mult) for r in resolution] - plane = nn.Parameter( - torch.empty(3 * T, M_features, scaled_res[1], scaled_res[0]) - ) + plane = nn.Parameter(torch.empty(3 * T, M_features, scaled_res[1], scaled_res[0])) nn.init.uniform_(plane, 0.1, 0.5) self.grids.append(plane) @@ -480,7 +488,7 @@ def _build_sigma_net( def get_densities(self, coords: torch.Tensor) -> torch.Tensor: pts = coords.reshape(-1, 3) - R = self.so3.as_matrix() # (T, 3, 3) + R = self.so3.as_matrix() # (T, 3, 3) features = interpolate_ms_features_tilted( pts=pts, ms_grids=self.grids, @@ -498,9 +506,9 @@ def forward(self, pts: torch.Tensor) -> torch.Tensor: def get_params(self) -> dict[str, list[nn.Parameter]]: return { - "grids": list(self.grids.parameters()), + "grids": list(self.grids.parameters()), "sigma_net": list(self.sigma_net.parameters()), - "so3": list(self.so3.parameters()), + "so3": list(self.so3.parameters()), } @property @@ -557,7 +565,7 @@ def extra_repr(self) -> str: def set_so3_param_type(self, so3_param_type: str, init: str = "rand") -> None: """ Set the SO3 parameterization type. - + Parameters ---------- so3_param_type : str @@ -573,12 +581,14 @@ def set_so3_param_type(self, so3_param_type: str, init: str = "rand") -> None: @property def tilted(self) -> bool: return True - + + # CP Decomp for Warmup SO3 rotations + def interpolate_ms_features_cp_tilted( - pts: torch.Tensor, # (B, 3) - ms_grids: nn.ParameterList, # each grid: (3*T, C, L) — 1D lines + pts: torch.Tensor, # (B, 3) + ms_grids: nn.ParameterList, # each grid: (3*T, C, L) — 1D lines rotation_matrices: torch.Tensor, # (T, 3, 3) ) -> torch.Tensor: """ @@ -594,7 +604,7 @@ def interpolate_ms_features_cp_tilted( per_scale_features = [] for line_coef in ms_grids: # line_coef: (3T, C, L) — three 1D feature lines per transform (x, y, z) - C, L = line_coef.shape[1], line_coef.shape[2] + C, _ = line_coef.shape[1], line_coef.shape[2] # For each transform t, we need three 1D samples: at x_t, y_t, z_t. # Lay them out as (3T, B) coords, matching line_coef's first dim. @@ -606,17 +616,23 @@ def interpolate_ms_features_cp_tilted( # (3T, C, 1, L) reshape and pass 2D coords with y fixed at 0. # Simpler: use F.grid_sample with a 4D trick, or just do manual linear interp. # Here's the grid_sample way: - line_coef_4d = line_coef.unsqueeze(2) # (3T, C, 1, L) + line_coef_4d = line_coef.unsqueeze(2) # (3T, C, 1, L) # grid: need (3T, Hout=1, Wout=B, 2), with x = coord, y = 0 - grid = torch.stack([ - coords_1d, # x - torch.zeros_like(coords_1d), # y - ], dim=-1).unsqueeze(1) # (3T, 1, B, 2) + grid = torch.stack( + [ + coords_1d, # x + torch.zeros_like(coords_1d), # y + ], + dim=-1, + ).unsqueeze(1) # (3T, 1, B, 2) sampled = F.grid_sample( - line_coef_4d, grid, - align_corners=True, mode="bilinear", padding_mode="border", - ).squeeze(2) # (3T, C, B) + line_coef_4d, + grid, + align_corners=True, + mode="bilinear", + padding_mode="border", + ).squeeze(2) # (3T, C, B) # Hadamard across the 3 axes per transform: (T, 3, C, B) -> (T, C, B) sampled = sampled.view(T, 3, C, B).prod(dim=1) @@ -639,12 +655,13 @@ class CPTilted(PPLR, TensorDecompositionModel): def __init__( self, - C: int = 4, # channels per transform per scale + C: int = 4, # channels per transform per scale resolution: Sequence[int] = (128, 128, 128), multiscale_res_multipliers: Optional[Sequence[int]] = None, T: int = 4, tau_init: str = "random", density_activation: Callable = lambda x: F.softplus(x - 1), + so3_param_type: str = "r9svd", ): super().__init__() self._td_type = "cp_tilted" @@ -670,7 +687,12 @@ def __init__( nn.init.normal_(self.sigma_net.weight, std=0.01) nn.init.zeros_(self.sigma_net.bias) - self.so3 = SO3Param(T, init=tau_init) + if so3_param_type == "r9svd": + self.so3 = SO3ParamR9SVD(T, init=tau_init) + elif so3_param_type == "quat": + self.so3 = SO3ParamQuat(T, init=tau_init) + else: + raise ValueError(f"Unknown SO3 param type: {so3_param_type}") def get_densities(self, coords: torch.Tensor) -> torch.Tensor: pts = coords.reshape(-1, 3) @@ -683,9 +705,9 @@ def forward(self, pts): def get_params(self): return { - "grids": list(self.grids.parameters()), + "grids": list(self.grids.parameters()), "sigma_net": list(self.sigma_net.parameters()), - "so3": list(self.so3.parameters()), + "so3": list(self.so3.parameters()), } @property @@ -698,12 +720,10 @@ def td_type(self) -> str: def extract_tau_state(self) -> torch.Tensor: return self.so3.M.detach().clone() - + @property def tilted(self) -> bool: return True - - -KPlanesType = KPlanes | KPlanesTILTED | CPTilted \ No newline at end of file +KPlanesType = KPlanes | KPlanesTILTED | CPTilted diff --git a/src/quantem/core/ml/models/model_base.py b/src/quantem/core/ml/models/model_base.py index e1a486d7..c7ccf7aa 100644 --- a/src/quantem/core/ml/models/model_base.py +++ b/src/quantem/core/ml/models/model_base.py @@ -1,7 +1,6 @@ from abc import ABC, abstractmethod from typing import Dict -import torch import torch.nn as nn @@ -48,4 +47,4 @@ class PlanarDecompositionModel(TensorDecompositionModel): grids: nn.ParameterList tilted: bool - resolution: list[int] \ No newline at end of file + resolution: list[int] diff --git a/src/quantem/core/ml/models/so3params.py b/src/quantem/core/ml/models/so3params.py index 14f60867..6569ec93 100644 --- a/src/quantem/core/ml/models/so3params.py +++ b/src/quantem/core/ml/models/so3params.py @@ -9,51 +9,52 @@ # --------------------------------------------------------------------------- # SO(3) quaternion parameter module # --------------------------------------------------------------------------- - + + class SO3ParamQuat(nn.Module): """ Stores T unit quaternions as learnable parameters in R^4 and normalises them on every call to `as_matrix()`. - + Quaternion convention: [x, y, z, w] (scalar-last, same as scipy). - + Initialisation -------------- "random" – uniform sampling over SO(3) via Shoemake's method. "identity" – all rotations start as the identity (good for fine-tuning). """ - + def __init__(self, T: int, init: str = "random"): super().__init__() if T < 1: raise ValueError(f"T must be >= 1, got {T}") - quats = self._init_quaternions(T, init) # (T, 4) + quats = self._init_quaternions(T, init) # (T, 4) self.quats = nn.Parameter(quats) - + # ------------------------------------------------------------------ # Initialisers # ------------------------------------------------------------------ - + @staticmethod def _shoemake_sample(T: int) -> torch.Tensor: """Uniform SO(3) sampling via Shoemake (1992). Returns (T, 4) [x,y,z,w].""" u = torch.rand(T, 3) sqrt1_u0 = torch.sqrt(1.0 - u[:, 0]) - sqrt_u0 = torch.sqrt(u[:, 0]) - two_pi = 2.0 * math.pi + sqrt_u0 = torch.sqrt(u[:, 0]) + two_pi = 2.0 * math.pi x = sqrt1_u0 * torch.sin(two_pi * u[:, 1]) y = sqrt1_u0 * torch.cos(two_pi * u[:, 1]) - z = sqrt_u0 * torch.sin(two_pi * u[:, 2]) - w = sqrt_u0 * torch.cos(two_pi * u[:, 2]) - return torch.stack([x, y, z, w], dim=-1) # (T, 4) - + z = sqrt_u0 * torch.sin(two_pi * u[:, 2]) + w = sqrt_u0 * torch.cos(two_pi * u[:, 2]) + return torch.stack([x, y, z, w], dim=-1) # (T, 4) + @staticmethod def _identity(T: int) -> torch.Tensor: """All-identity rotations: [0,0,0,1] * T.""" q = torch.zeros(T, 4) q[:, 3] = 1.0 return q - + @classmethod def _init_quaternions(cls, T: int, init: str) -> torch.Tensor: if init == "random": @@ -62,38 +63,47 @@ def _init_quaternions(cls, T: int, init: str) -> torch.Tensor: return cls._identity(T) else: raise ValueError(f"Unknown init '{init}'; choose 'random' or 'identity'.") - + # ------------------------------------------------------------------ # Forward helpers # ------------------------------------------------------------------ - + def normalized(self) -> torch.Tensor: """Returns (T, 4) unit quaternions.""" return F.normalize(self.quats, p=2, dim=-1) - + def as_matrix(self) -> torch.Tensor: """ Converts the T stored quaternions to (T, 3, 3) rotation matrices. - + Uses the standard formula; no trig, just multiplications. """ - q = self.normalized() # (T, 4) [x, y, z, w] + q = self.normalized() # (T, 4) [x, y, z, w] x, y, z, w = q.unbind(dim=-1) # each (T,) - + # Precompute products - xx, yy, zz = x*x, y*y, z*z - xy, xz, yz = x*y, x*z, y*z - wx, wy, wz = w*x, w*y, w*z - + xx, yy, zz = x * x, y * y, z * z + xy, xz, yz = x * y, x * z, y * z + wx, wy, wz = w * x, w * y, w * z + # Row-major: R[i,j] - R = torch.stack([ - 1 - 2*(yy + zz), 2*(xy - wz), 2*(xz + wy), - 2*(xy + wz), 1 - 2*(xx + zz), 2*(yz - wx), - 2*(xz - wy), 2*(yz + wx), 1 - 2*(xx + yy), - ], dim=-1).reshape(-1, 3, 3) # (T, 3, 3) - + R = torch.stack( + [ + 1 - 2 * (yy + zz), + 2 * (xy - wz), + 2 * (xz + wy), + 2 * (xy + wz), + 1 - 2 * (xx + zz), + 2 * (yz - wx), + 2 * (xz - wy), + 2 * (yz + wx), + 1 - 2 * (xx + yy), + ], + dim=-1, + ).reshape(-1, 3, 3) # (T, 3, 3) + return R - + def extra_repr(self) -> str: return f"T={self.quats.shape[0]}" @@ -115,16 +125,14 @@ def __init__(self, T: int, init: str = "random"): M = torch.eye(3).unsqueeze(0).repeat(T, 1, 1) else: raise ValueError(f"Unknown init '{init}'") - self.M = nn.Parameter(M) # (T, 3, 3) + self.M = nn.Parameter(M) # (T, 3, 3) def as_matrix(self) -> torch.Tensor: """Projects each M to SO(3) via SVD. Returns (T, 3, 3).""" - - U, _, Vh = torch.linalg.svd(self.M) # U: (T,3,3), Vh: (T,3,3) + U, _, Vh = torch.linalg.svd(self.M) # U: (T,3,3), Vh: (T,3,3) # Fix reflections: det(U Vh) must be +1 - d = torch.det(U @ Vh) # (T,) + d = torch.det(U @ Vh) # (T,) diag = torch.ones(self.M.shape[0], 3, device=self.M.device, dtype=self.M.dtype) - diag[:, 2] = d # multiply last singular vector by sign - return U @ (diag.unsqueeze(-1) * Vh) # (T, 3, 3) - + diag[:, 2] = d # multiply last singular vector by sign + return U @ (diag.unsqueeze(-1) * Vh) # (T, 3, 3) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index 4c97181a..b5053a4c 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -1,7 +1,7 @@ import textwrap from abc import abstractmethod from dataclasses import dataclass -from typing import TYPE_CHECKING, Generator, Iterator, Literal, Sequence +from typing import TYPE_CHECKING, Any, Generator, Iterable, Literal from quantem.core import config @@ -581,11 +581,11 @@ def scheduler_params(self, params: SchedulerType | dict): @abstractmethod def get_optimization_parameters( self, - ) -> "torch.Tensor | Sequence[torch.Tensor] | Iterator[torch.Tensor]": + ) -> "Iterable[torch.Tensor] | Iterable[dict[str, Any]]": """ Get the parameters that should be optimized for this model. This could be replaced with just module.parameters(), but this allows for flexibility - in the future to allow for per parameter LRs. + in the future to allow for per parameter LRs. # NOTE: Cl 4/27/26 updated to iterable type-hint. """ raise NotImplementedError("Subclasses must implement get_optimization_parameters") @@ -639,16 +639,17 @@ def set_optimizer(self, opt_params: OptimizerType | dict | None = None) -> None: optimizer_cls = self._optimizer_class_for(self._optimizer_params) self._optimizer = optimizer_cls(params, **self._optimizer_params.params()) - def _optimizer_class_for(self, opt_params) -> type[torch.optim.Optimizer]: match opt_params: - case OptimizerParams.Adam(): return torch.optim.Adam - case OptimizerParams.AdamW(): return torch.optim.AdamW - case OptimizerParams.SGD(): return torch.optim.SGD + case OptimizerParams.Adam(): + return torch.optim.Adam + case OptimizerParams.AdamW(): + return torch.optim.AdamW + case OptimizerParams.SGD(): + return torch.optim.SGD case _: raise NotImplementedError(f"Unknown optimizer type: {opt_params}") - def set_scheduler( self, scheduler_params: SchedulerType | dict | None = None, num_iter: int | None = None ) -> None: diff --git a/src/quantem/core/visualization/visualization.py b/src/quantem/core/visualization/visualization.py index 77f647b4..e252caa6 100644 --- a/src/quantem/core/visualization/visualization.py +++ b/src/quantem/core/visualization/visualization.py @@ -40,10 +40,7 @@ # There might be a cleaner way to do this, but better to have it here than in the functions NormInputCell: TypeAlias = NormalizationConfig | ShowParams.Norm | dict | str Show2dNormInput: TypeAlias = ( - NormInputCell - | None - | Sequence[NormInputCell] - | Sequence[Sequence[NormInputCell]] + NormInputCell | None | Sequence[NormInputCell] | Sequence[Sequence[NormInputCell]] ) ScalebarInputCell: TypeAlias = ScalebarConfig | ShowParams.Scalebar | dict | bool | None @@ -57,7 +54,7 @@ | Sequence[Sequence[ScalebarInputCell]] ) -CmapType: TypeAlias = str | colors.Colormap +CmapType: TypeAlias = str | colors.Colormap def _show_2d_array( diff --git a/src/quantem/diffractive_imaging/ptychography_visualizations.py b/src/quantem/diffractive_imaging/ptychography_visualizations.py index fe8f84bf..8975df02 100644 --- a/src/quantem/diffractive_imaging/ptychography_visualizations.py +++ b/src/quantem/diffractive_imaging/ptychography_visualizations.py @@ -1,4 +1,3 @@ - import warnings from typing import Any, Literal @@ -891,6 +890,7 @@ def show_scan_positions( if conv_angle is not None and energy is not None: from quantem.core.utils.utils import electron_wavelength_angstrom + wavelength = electron_wavelength_angstrom(energy) conv_angle_rad = conv_angle * 1e-3 # For defocused probe: radius ≈ |defocus| * convergence_angle + diffraction_limit diff --git a/src/quantem/imaging/drift.py b/src/quantem/imaging/drift.py index 424e18e6..ba29e417 100644 --- a/src/quantem/imaging/drift.py +++ b/src/quantem/imaging/drift.py @@ -1,9 +1,9 @@ +import warnings from collections.abc import Sequence from typing import List, Optional, Union import matplotlib.pyplot as plt import numpy as np -import warnings from numpy.typing import NDArray from scipy.interpolate import interp1d from scipy.ndimage import distance_transform_edt, gaussian_filter diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index e3b61377..24f0f775 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -649,11 +649,14 @@ def save_parameters(self, path: str): """ Saves the learned parameters to a file. """ - torch.save({ - "z1": self._z1_params.detach().cpu(), - "z3": self._z3_params.detach().cpu(), - "shifts": self._shifts_params.detach().cpu(), - }, path) + torch.save( + { + "z1": self._z1_params.detach().cpu(), + "z3": self._z3_params.detach().cpu(), + "shifts": self._shifts_params.detach().cpu(), + }, + path, + ) def load_parameters(self, path: str): """ diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 2bc8fa4a..8f254533 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -1,7 +1,7 @@ from abc import abstractmethod from copy import deepcopy from dataclasses import dataclass -from typing import Any, Callable, Generator +from typing import Any, Callable, Generator, Optional import numpy as np import torch @@ -14,11 +14,11 @@ from quantem.core.ml.constraints import BaseConstraints, Constraints from quantem.core.ml.ddp import DDPMixin from quantem.core.ml.loss_functions import get_loss_module -from quantem.core.ml.models.model_base import PPLR, PlanarDecompositionModel +from quantem.core.ml.models.model_base import PlanarDecompositionModel from quantem.core.ml.optimizer_mixin import OptimizerMixin, OptimizerType from quantem.core.utils.rng import RNGMixin from quantem.tomography.dataset_models import TomographyINRPretrainDataset -from quantem.tomography.tomography import ReconstructionContext as ctx +from quantem.tomography.tomography import ReconstructionContext class ObjConstraintParams: @@ -128,7 +128,7 @@ class ObjTensorDecompConstraints(Constraints): shrinkage: float = 0.0 tv_vol: float = 0.0 tv_plane: float = 0.0 - sparsity:float = 0.0 + sparsity: float = 0.0 _name: str = "obj_tensor_decomp" soft_constraint_keys = ["tv_vol", "tv_plane", "sparsity"] @@ -192,7 +192,7 @@ def parse_dict( ObjConstraintsType = ( - ObjConstraintParams.ObjPixelatedConstraints + ObjConstraintParams.ObjPixelatedConstraints | ObjConstraintParams.ObjINRConstraints | ObjConstraintParams.ObjTensorDecompConstraints ) @@ -204,6 +204,7 @@ def _unwrap(model: nn.Module | nn.parallel.DistributedDataParallel) -> PlanarDec return model.module return model + class ObjectBase(AutoSerialize, nn.Module, RNGMixin, OptimizerMixin): DEFAULT_LRS = { "object": 8e-6, @@ -212,6 +213,7 @@ class ObjectBase(AutoSerialize, nn.Module, RNGMixin, OptimizerMixin): """ Base class for all ObjectModels to inherit from. """ + def __init__( self, shape: tuple[int, int, int], # pyright: ignore[reportRedeclaration] @@ -231,91 +233,92 @@ def __init__( # --- Instantiation ---- - # --- Properties --- - @property - def shape(self) -> tuple[int, int, int]: - """ - Shape of the object (x, y, z). - """ - return self._shape - - @shape.setter - def shape(self, new_shape: tuple[int, int, int]): - self._shape = new_shape - - @property - def obj(self) -> torch.Tensor: - """ - Returns the object, should be implemented in subclasses. - """ - raise NotImplementedError - - @property - def model(self) -> nn.Module: - """ - Returns the model, should be implemented in subclasses. - """ - raise NotImplementedError - - @abstractmethod - def dtype(self) -> torch.dtype: - """ - Returns the dtype of the object. - """ - raise NotImplementedError - - @abstractmethod - def forward(self, *args, **kwargs) -> torch.Tensor: - """ - Forward pass, should be implemented in subclasses. Note for any nn.Module this is - a required method. - """ - raise NotImplementedError - - @abstractmethod - def reset(self) -> None: - """ - Reset the object, should be implemented in subclasses. - """ - raise NotImplementedError - - @property - def params(self) -> Generator[torch.nn.Parameter, None, None]: - """ - Get the parameters that should be optimized for this model. - - Should be implemented in subclasses. - """ - raise NotImplementedError - - # --- Helper Functions --- - def get_optimization_parameters(self) -> list[nn.Parameter]: - """ - Get the parameters that should be optimized for this model. - """ - return list(self.params) - - @abstractmethod # Each subclass should implement this. - def to(self, *args, **kwargs): - """ - Move the object to a device - """ - - raise NotImplementedError + # --- Properties --- + @property + def shape(self) -> tuple[int, int, int]: + """ + Shape of the object (x, y, z). + """ + return self._shape + + @shape.setter + def shape(self, new_shape: tuple[int, int, int]): + self._shape = new_shape + + @property + def obj(self) -> torch.Tensor: + """ + Returns the object, should be implemented in subclasses. + """ + raise NotImplementedError + + @property + def model(self) -> nn.Module: + """ + Returns the model, should be implemented in subclasses. + """ + raise NotImplementedError + + @abstractmethod + def dtype(self) -> torch.dtype: + """ + Returns the dtype of the object. + """ + raise NotImplementedError + + @abstractmethod + def forward(self, coords: Optional[torch.Tensor] = None) -> torch.Tensor: + """ + Forward pass, should be implemented in subclasses. Note for any nn.Module this is + a required method. + """ + raise NotImplementedError + + @abstractmethod + def reset(self) -> None: + """ + Reset the object, should be implemented in subclasses. + """ + raise NotImplementedError + + @property + def params(self) -> Generator[torch.nn.Parameter, None, None]: + """ + Get the parameters that should be optimized for this model. + + Should be implemented in subclasses. + """ + raise NotImplementedError + + # --- Helper Functions --- + def get_optimization_parameters(self) -> list[nn.Parameter] | list[dict[str, Any]]: + """ + Get the parameters that should be optimized for this model. + """ + return list(self.params) + + @abstractmethod # Each subclass should implement this. + def to(self, device: str | torch.device): + """ + Move the object to a device + """ + + raise NotImplementedError + class ObjectConstraints(BaseConstraints, ObjectBase): # TODO: Ask Arthur why we still need this def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @abstractmethod - def get_tv_loss(self, **kwargs) -> torch.Tensor: + def get_tv_loss(self, ctx: ReconstructionContext) -> torch.Tensor: """ Get the TV loss for the object model. Must be implemented in each subclass. """ raise NotImplementedError -class ObjectPixelated(ObjectConstraints): +class ObjectPixelated(ObjectConstraints): """ Object model for pixelated objects. @@ -382,14 +385,6 @@ def obj(self, obj: torch.Tensor): def obj_view(self) -> np.ndarray: return self.obj.cpu().unsqueeze(0).numpy() - @property - def shape(self) -> tuple[int, int, int]: - return self._shape - - @shape.setter - def shape(self, shape: tuple[int, int, int]): - self._shape = shape - @property def soft_loss(self) -> torch.Tensor: return self.apply_soft_constraints(self._obj) @@ -424,30 +419,32 @@ def apply_hard_constraints( # TODO: Need to implement the other hard constraints: Fourier Filter and Circular Mask. return obj2 - def apply_soft_constraints(self, obj: torch.Tensor) -> torch.Tensor: - soft_loss = torch.tensor(0.0, device=obj.device, dtype=obj.dtype, requires_grad=True) + def apply_soft_constraints(self, ctx: ReconstructionContext) -> torch.Tensor: + assert ctx.obj is not None, "ObjectPixelated requires ctx.obj to be set" + soft_loss = torch.tensor(0.0, device=ctx.obj.device, dtype=ctx.obj.dtype, requires_grad=True) if self.constraints.tv_vol > 0: tv_loss = self.get_tv_loss( - obj.unsqueeze(0).unsqueeze(0), tv_weight=self.constraints.tv_vol + ctx ) soft_loss += tv_loss return soft_loss # --- Forward method --- - def forward(self, dummy_input=None) -> torch.Tensor: + def forward(self, coords=None) -> torch.Tensor: return self.obj # --- Defining the TV loss --- - def get_tv_loss(self, obj: torch.Tensor, tv_weight: float = 1e-3) -> torch.Tensor: # pyright: ignore[reportIncompatibleMethodOverride] -> get_tv_loss has different arguments depending on the object. - tv_d = torch.pow(obj[:, :, 1:, :, :] - obj[:, :, :-1, :, :], 2).sum() - tv_h = torch.pow(obj[:, :, :, 1:, :] - obj[:, :, :, :-1, :], 2).sum() - tv_w = torch.pow(obj[:, :, :, :, 1:] - obj[:, :, :, :, :-1], 2).sum() + def get_tv_loss(self, ctx: ReconstructionContext) -> torch.Tensor: + assert ctx.obj is not None, "ObjectPixelated requires ctx.obj to be set" + tv_d = torch.pow(ctx.obj[:, :, 1:, :, :] - ctx.obj[:, :, :-1, :, :], 2).sum() + tv_h = torch.pow(ctx.obj[:, :, :, 1:, :] - ctx.obj[:, :, :, :-1, :], 2).sum() + tv_w = torch.pow(ctx.obj[:, :, :, :, 1:] - ctx.obj[:, :, :, :, :-1], 2).sum() tv_loss = tv_d + tv_h + tv_w - return tv_loss * tv_weight / (torch.prod(torch.tensor(obj.shape))) + return tv_loss * self.constraints.tv_vol / (torch.prod(torch.tensor(obj.shape))) # --- Helper Functions --- - def to(self, device: str | torch.device): # pyright: ignore[reportIncompatibleMethodOverride] -> better to do this device change + def to(self, device: str | torch.device): if isinstance(device, str): device = torch.device(device) self._device = device @@ -455,8 +452,10 @@ def to(self, device: str | torch.device): # pyright: ignore[reportIncompatibleM self.reconnect_optimizer_to_parameters() return self + class ObjectINR(ObjectConstraints, DDPMixin): DEFAULT_CONSTRAINTS = ObjConstraintParams.ObjINRConstraints() + def __init__( self, shape: tuple[int, int, int], @@ -884,8 +883,10 @@ def to(self, device: str | torch.device): # pyright: ignore[reportIncompatibleM self.distribute_model(self._model) self.reconnect_optimizer_to_parameters() + class ObjectTensorDecomp(ObjectINR): DEFAULT_CONSTRAINTS = ObjConstraintParams.ObjTensorDecompConstraints() + def __init__( self, shape: tuple[int, int, int], @@ -902,7 +903,9 @@ def __init__( ) self._pretrain_losses = [] self._pretrain_lrs = [] - self.constraints: ObjConstraintParams.ObjTensorDecompConstraints = self.DEFAULT_CONSTRAINTS.copy() + self.constraints: ObjConstraintParams.ObjTensorDecompConstraints = ( + self.DEFAULT_CONSTRAINTS.copy() + ) # Register the network submodule (important: real nn.Module attribute) if model is not None: self.setup_distributed(device=device) @@ -935,7 +938,6 @@ def apply_soft_constraints( all_densities: torch.Tensor, pred: torch.Tensor, ) -> torch.Tensor: - soft_loss = torch.tensor(0.0, device=pred.device) if self.constraints.tv_vol > 0: soft_loss += self.get_tv_loss(coords, pred) @@ -949,28 +951,26 @@ def apply_soft_constraints( # TV Losses def get_tv_loss(self, coords: torch.Tensor, pred: torch.Tensor) -> torch.Tensor: - tv_loss = torch.tensor(0.0, device=pred.device) tv_loss += self._get_plane_tv_loss() tv_loss += self.get_volume_tv_loss(coords) return tv_loss - def _get_plane_tv_loss(self) -> torch.Tensor: is_tilted = self.model.tilted per_level = [] - + model = _unwrap(self.model) for p in model.grids: # p: (3*T, C, H, W) for TILTED, (3, C, H, W) for KPlanes dh = (p[:, :, 1:, :] - p[:, :, :-1, :]).pow(2).mean(dim=(1, 2, 3)) dw = (p[:, :, :, 1:] - p[:, :, :, :-1]).pow(2).mean(dim=(1, 2, 3)) - per_plane = dh + dw # (3*T,) or (3,) + per_plane = dh + dw # (3*T,) or (3,) if is_tilted: T = self.model.T - per_rotation = per_plane.view(T, 3).sum(dim=1) # sum 3 planes per rotation - level_tv = per_rotation.mean() # avg across rotations + per_rotation = per_plane.view(T, 3).sum(dim=1) # sum 3 planes per rotation + level_tv = per_rotation.mean() # avg across rotations else: level_tv = per_plane.sum() @@ -978,7 +978,6 @@ def _get_plane_tv_loss(self) -> torch.Tensor: return self.constraints.tv_plane * torch.stack(per_level).sum() - def get_volume_tv_loss(self, coords: torch.Tensor) -> torch.Tensor: """ Isotropic volume TV via finite differences. Same form as the autograd @@ -987,7 +986,7 @@ def get_volume_tv_loss(self, coords: torch.Tensor) -> torch.Tensor: """ num_tv_samples = min(10_000, coords.shape[0]) tv_indices = torch.randperm(coords.shape[0], device=coords.device)[:num_tv_samples] - tv_coords = coords[tv_indices] # (N, 3) + tv_coords = coords[tv_indices] # (N, 3) model = _unwrap(self.model) h = 2.0 / min(model.resolution) @@ -996,7 +995,7 @@ def get_volume_tv_loss(self, coords: torch.Tensor) -> torch.Tensor: if isinstance(pred, tuple): pred = pred[0] if pred.dim() == 1: - pred = pred.unsqueeze(-1) # (N, 1) + pred = pred.unsqueeze(-1) # (N, 1) grads = [] for axis in range(3): @@ -1007,10 +1006,10 @@ def get_volume_tv_loss(self, coords: torch.Tensor) -> torch.Tensor: shifted_pred = shifted_pred[0] if shifted_pred.dim() == 1: shifted_pred = shifted_pred.unsqueeze(-1) - grads.append((shifted_pred - pred) / h) # (N, 1) + grads.append((shifted_pred - pred) / h) # (N, 1) - grad_stack = torch.stack(grads, dim=-1) # (N, C, 3) - grad_norm = torch.norm(grad_stack, dim=-1) # (N, C) + grad_stack = torch.stack(grads, dim=-1) # (N, C, 3) + grad_norm = torch.norm(grad_stack, dim=-1) # (N, C) return self.constraints.tv_vol * grad_norm.mean() @@ -1032,21 +1031,19 @@ def params(self) -> Generator[torch.nn.Parameter, None, None]: """ Returns the optimization parameters, here we also check if PPLR is used and return the appropriate parameters. """ - + return self.model.parameters() # type: ignore[attr-defined] def get_optimization_parameters(self) -> list[nn.Parameter] | list[dict[str, Any]]: - - model = _unwrap (self.model) + model = _unwrap(self.model) return [ { - "params": model.get_params()[key], + "params": model.get_params()[key], **self.optimizer_params[key].params(), } for key in model.param_keys ] - # --- Optimizer Mixin Overloads in the case of PPLR --- @property @@ -1060,11 +1057,13 @@ def optimizer_params(self, params: OptimizerType | dict[str, OptimizerType] | di if not isinstance(params, dict): raise TypeError(f"optimizer parameters must be a dict for PPLR, got {type(params)}") - + object_params = params - + if set(object_params.keys()) != set(self.model.param_keys): - raise ValueError(f"optimizer parameters keys must match PPLR param_keys, got {object_params.keys()} != {self.model.param_keys}") + raise ValueError( + f"optimizer parameters keys must match PPLR param_keys, got {object_params.keys()} != {self.model.param_keys}" + ) params = {} for key, value in object_params.items(): @@ -1073,31 +1072,33 @@ def optimizer_params(self, params: OptimizerType | dict[str, OptimizerType] | di elif isinstance(value, OptimizerType): params[key] = value else: - raise TypeError(f"optimizer parameters must be a dict or OptimizerType, got {type(value)}") + raise TypeError( + f"optimizer parameters must be a dict or OptimizerType, got {type(value)}" + ) self._optimizer_params = params def reconnect_optimizer_to_parameters(self) -> None: - if self.optimizer is None: return - + current_params = self.get_optimization_parameters() - optimizable_params = [ - p for p in current_params - if isinstance(p['params'][0], torch.Tensor) and p['params'][0].is_leaf + p + for p in current_params + if isinstance(p["params"][0], torch.Tensor) and p["params"][0].is_leaf ] - if not optimizable_params: - raise ValueError(f"Shouldn't be getting here! No optimizable parameters found for {self.__class__.__name__}.") - + raise ValueError( + f"Shouldn't be getting here! No optimizable parameters found for {self.__class__.__name__}." + ) + for p in optimizable_params: print(f"Setting requires_grad for parameter: {p}") - p['params'][0].requires_grad_(True) - + p["params"][0].requires_grad_(True) + assert self._optimizer is not None # Preserve optimizer states and param_group settings old_state = self._optimizer.state.copy() @@ -1132,7 +1133,9 @@ def reconnect_optimizer_to_parameters(self) -> None: self._scheduler.optimizer = self._optimizer def pretrain(self) -> None: - raise NotImplementedError("Tensor decomposition pretraining is not usually required, and for TILTED there is a two-phase warmup approach.") - + raise NotImplementedError( + "Tensor decomposition pretraining is not usually required, and for TILTED there is a two-phase warmup approach." + ) + ObjectModelType = ObjectPixelated | ObjectINR diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index f21a30d1..899f1a1d 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -182,7 +182,9 @@ def reconstruct( consistency_loss = torch.tensor(0.0, device=self.device) total_loss = torch.tensor(0.0, device=self.device) epoch_soft_constraint_loss = torch.tensor(0.0, device=self.device) - if isinstance(self.obj_model, ObjectINR) or isinstance(self.obj_model, ObjectTensorDecomp): + if isinstance(self.obj_model, ObjectINR) or isinstance( + self.obj_model, ObjectTensorDecomp + ): self.obj_model.model.train() else: raise NotImplementedError( @@ -217,7 +219,9 @@ def reconstruct( ) pred = integrated_densities.float() - soft_constraints_loss = self.obj_model.apply_soft_constraints(all_coords, all_densities, pred) + soft_constraints_loss = self.obj_model.apply_soft_constraints( + all_coords, all_densities, pred + ) target = batch["target_value"].to(self.device, non_blocking=True).float() @@ -243,7 +247,7 @@ def reconstruct( R_now = self.obj_model.model.so3.as_matrix().detach() # Cumulative angular change per rotation over the last 20 iters. # trace(R_prev^T R_now) = 1 + 2*cos(theta), so theta = acos((trace - 1) / 2). - rel_trace = torch.einsum('tij,tij->t', prev_R, R_now) + rel_trace = torch.einsum("tij,tij->t", prev_R, R_now) angle = torch.acos(((rel_trace - 1) / 2).clamp(-1, 1)) # (T,) radians angle_deg = torch.rad2deg(angle) per_tau_str = ", ".join(f"{a:.2f}°" for a in angle_deg.tolist()) @@ -636,10 +640,10 @@ class ReconstructionContext: Subclasses will pick whatever parameter they need - Pixelated reads ".volume" - INR reads ".coords" and recomputes via the model. - - TEnsorDEcomp reads ".coords" and ".pred" (and ".all densities") + - TensorDecomp reads ".coords" and ".pred" (and ".all densities") """ coords: Optional[torch.Tensor] = None pred: Optional[torch.Tensor] = None all_densities: Optional[torch.Tensor] = None - volume: Optional[torch.Tensor] = None \ No newline at end of file + obj: Optional[torch.Tensor] = None diff --git a/src/quantem/tomography/tomography_opt.py b/src/quantem/tomography/tomography_opt.py index 0f8c44be..c75de1e3 100644 --- a/src/quantem/tomography/tomography_opt.py +++ b/src/quantem/tomography/tomography_opt.py @@ -2,7 +2,7 @@ import torch -from quantem.core.ml.optimizer_mixin import OptimizerParams, OptimizerType, SchedulerType +from quantem.core.ml.optimizer_mixin import OptimizerType, SchedulerType from quantem.tomography.tomography_base import TomographyBase diff --git a/tests/datastructures/test_dataset3d_show.py b/tests/datastructures/test_dataset3d_show.py index 27c18f41..7938c66f 100644 --- a/tests/datastructures/test_dataset3d_show.py +++ b/tests/datastructures/test_dataset3d_show.py @@ -31,16 +31,19 @@ def extract_frame_indices_from_figure(fig): class TestShowInputValidation: """Test that invalid inputs raise clear errors.""" - @pytest.mark.parametrize("kwargs,match", [ - ({"step": 0}, "cannot be zero"), - ({"start": 100}, "out of bounds"), - ({"start": -100}, "out of bounds"), - ({"start": 5, "end": 5}, "No frames to display"), - ({"ncols": 0}, "ncols must be >= 1"), - ({"ncols": -1}, "ncols must be >= 1"), - ({"max": 0}, "max must be >= 1"), - ({"max": -1}, "max must be >= 1"), - ]) + @pytest.mark.parametrize( + "kwargs,match", + [ + ({"step": 0}, "cannot be zero"), + ({"start": 100}, "out of bounds"), + ({"start": -100}, "out of bounds"), + ({"start": 5, "end": 5}, "No frames to display"), + ({"ncols": 0}, "ncols must be >= 1"), + ({"ncols": -1}, "ncols must be >= 1"), + ({"max": 0}, "max must be >= 1"), + ({"max": -1}, "max must be >= 1"), + ], + ) def test_raises_value_error(self, dataset_with_10_frames, kwargs, match): with pytest.raises(ValueError, match=match): dataset_with_10_frames.show(**kwargs) @@ -49,39 +52,44 @@ def test_raises_value_error(self, dataset_with_10_frames, kwargs, match): class TestShowFrameSelection: """Test frame selection with start, end, step, max combinations.""" - @pytest.mark.parametrize("kwargs,expected_indices", [ - ({}, list(range(20))), - ({"max": 5}, [0, 1, 2, 3, 4]), - ({"max": None}, list(range(100))), - ({"start": 90}, list(range(90, 100))), - ({"start": 95, "max": 3}, [95, 96, 97]), - ]) + @pytest.mark.parametrize( + "kwargs,expected_indices", + [ + ({}, list(range(20))), + ({"max": 5}, [0, 1, 2, 3, 4]), + ({"max": None}, list(range(100))), + ({"start": 90}, list(range(90, 100))), + ({"start": 95, "max": 3}, [95, 96, 97]), + ], + ) def test_large_dataset(self, dataset_with_100_frames, kwargs, expected_indices): fig, _ = dataset_with_100_frames.show(returnfig=True, **kwargs) assert extract_frame_indices_from_figure(fig) == expected_indices plt.close(fig) - @pytest.mark.parametrize("kwargs,expected_indices", [ - # Default shows all frames (< max) - ({}, list(range(10))), - # Start and end - ({"start": 5}, [5, 6, 7, 8, 9]), - ({"end": 5}, [0, 1, 2, 3, 4]), - # Step - ({"step": 2}, [0, 2, 4, 6, 8]), - ({"step": 3}, [0, 3, 6, 9]), - ({"start": 2, "end": 8, "step": 2}, [2, 4, 6]), - # Negative start index - ({"start": -1, "max": 1}, [9]), - ({"start": -3, "max": 2}, [7, 8]), - # Negative step (reverse order) - ({"start": 9, "step": -1}, [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]), - ({"start": 9, "end": 4, "step": -1}, [9, 8, 7, 6, 5]), - ({"start": 9, "step": -2}, [9, 7, 5, 3, 1]), - ({"start": 9, "step": -1, "max": 3}, [9, 8, 7]), - ]) + @pytest.mark.parametrize( + "kwargs,expected_indices", + [ + # Default shows all frames (< max) + ({}, list(range(10))), + # Start and end + ({"start": 5}, [5, 6, 7, 8, 9]), + ({"end": 5}, [0, 1, 2, 3, 4]), + # Step + ({"step": 2}, [0, 2, 4, 6, 8]), + ({"step": 3}, [0, 3, 6, 9]), + ({"start": 2, "end": 8, "step": 2}, [2, 4, 6]), + # Negative start index + ({"start": -1, "max": 1}, [9]), + ({"start": -3, "max": 2}, [7, 8]), + # Negative step (reverse order) + ({"start": 9, "step": -1}, [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]), + ({"start": 9, "end": 4, "step": -1}, [9, 8, 7, 6, 5]), + ({"start": 9, "step": -2}, [9, 7, 5, 3, 1]), + ({"start": 9, "step": -1, "max": 3}, [9, 8, 7]), + ], + ) def test_small_dataset(self, dataset_with_10_frames, kwargs, expected_indices): fig, _ = dataset_with_10_frames.show(returnfig=True, **kwargs) assert extract_frame_indices_from_figure(fig) == expected_indices plt.close(fig) - From a061845add3789341bf635cc66cf0867398b05d9 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Mon, 27 Apr 2026 11:24:01 -0700 Subject: [PATCH 264/335] Added pyrightconfig.json to gitignore, PrivateImportUsage error annoying --- .gitignore | 3 +++ src/quantem/tomography/object_models.py | 17 +++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index d87d85c8..a66c803d 100644 --- a/.gitignore +++ b/.gitignore @@ -167,6 +167,9 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ +# BasedPyRight Config +pyrightconfig.json + # Ruff stuff: .ruff_cache/ diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 8f254533..5d5d8b1e 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -260,6 +260,7 @@ def model(self) -> nn.Module: raise NotImplementedError @abstractmethod + @property def dtype(self) -> torch.dtype: """ Returns the dtype of the object. @@ -385,9 +386,9 @@ def obj(self, obj: torch.Tensor): def obj_view(self) -> np.ndarray: return self.obj.cpu().unsqueeze(0).numpy() - @property - def soft_loss(self) -> torch.Tensor: - return self.apply_soft_constraints(self._obj) + # @property + # def soft_loss(self) -> torch.Tensor: + # return self.apply_soft_constraints(self._obj) @property def name(self) -> str: @@ -421,11 +422,11 @@ def apply_hard_constraints( def apply_soft_constraints(self, ctx: ReconstructionContext) -> torch.Tensor: assert ctx.obj is not None, "ObjectPixelated requires ctx.obj to be set" - soft_loss = torch.tensor(0.0, device=ctx.obj.device, dtype=ctx.obj.dtype, requires_grad=True) + soft_loss = torch.tensor( + 0.0, device=ctx.obj.device, dtype=ctx.obj.dtype, requires_grad=True + ) if self.constraints.tv_vol > 0: - tv_loss = self.get_tv_loss( - ctx - ) + tv_loss = self.get_tv_loss(ctx) soft_loss += tv_loss return soft_loss @@ -441,7 +442,7 @@ def get_tv_loss(self, ctx: ReconstructionContext) -> torch.Tensor: tv_w = torch.pow(ctx.obj[:, :, :, :, 1:] - ctx.obj[:, :, :, :, :-1], 2).sum() tv_loss = tv_d + tv_h + tv_w - return tv_loss * self.constraints.tv_vol / (torch.prod(torch.tensor(obj.shape))) + return tv_loss * self.constraints.tv_vol / (torch.prod(torch.tensor(ctx.obj.shape))) # --- Helper Functions --- def to(self, device: str | torch.device): From 23cfd72de885c7e86c5b992f190d9fb49518ac70 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Mon, 27 Apr 2026 11:35:35 -0700 Subject: [PATCH 265/335] ObjectINR implemented --- src/quantem/tomography/object_models.py | 103 +++++++++--------------- 1 file changed, 40 insertions(+), 63 deletions(-) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 5d5d8b1e..19c69e3e 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -259,7 +259,6 @@ def model(self) -> nn.Module: """ raise NotImplementedError - @abstractmethod @property def dtype(self) -> torch.dtype: """ @@ -537,40 +536,19 @@ def obj_view(self) -> np.ndarray: def apply_soft_constraints( self, - coords: torch.Tensor, - pred: torch.Tensor, + ctx: ReconstructionContext, ) -> torch.Tensor: - soft_loss = torch.tensor(0.0, device=pred.device) + soft_loss = torch.tensor(0.0, device=ctx.coords.device) if self.constraints.tv_vol > 0: - num_tv_samples = min(10_000, coords.shape[0]) - tv_indices = torch.randperm(coords.shape[0], device=coords.device)[:num_tv_samples] - - tv_coords = coords[tv_indices].detach().requires_grad_(True) - tv_densities_recomputed = self.model(tv_coords) - if isinstance(tv_densities_recomputed, tuple): - tv_densities_recomputed = tv_densities_recomputed[0] - - # Ensure shape is [num_samples, num_channels] - if tv_densities_recomputed.dim() == 1: - tv_densities_recomputed = tv_densities_recomputed.unsqueeze(-1) - - # Compute gradients for each channel - grad_outputs = torch.autograd.grad( - outputs=tv_densities_recomputed, - inputs=tv_coords, - grad_outputs=torch.ones_like(tv_densities_recomputed), - create_graph=True, - )[0] # Shape: [num_samples, coord_dim] - - # Compute TV loss - gradient magnitude per sample - grad_norm = torch.norm(grad_outputs, dim=1) # Shape: [num_samples] - soft_loss += self.constraints.tv_vol * grad_norm.mean() + assert ctx.coords is not None, "coords must be provided for INR object model to compute the TV loss" + soft_loss += self.get_tv_loss(ctx) if ( isinstance(self.constraints, ObjConstraintParams.ObjINRConstraints) and self.constraints.sparsity > 0 ): # NOTE: For the linter, I must make this :) - sparsity_loss = self.constraints.sparsity * torch.norm(pred, p=1) + assert ctx.pred is not None, "pred must be provided for INR object model to compute the sparsity loss" + sparsity_loss = self.constraints.sparsity * torch.norm(ctx.pred, p=1) soft_loss += sparsity_loss return soft_loss @@ -587,6 +565,37 @@ def apply_hard_constraints(self, pred: torch.Tensor) -> torch.Tensor: return pred + # --- Define get_tv_loss --- + + def get_tv_loss(self, ctx: ReconstructionContext) -> torch.Tensor: + """ + Compute the total variation loss for the INR model. + """ + assert ctx.coords is not None, "coords must be provided for INR object model" + num_tv_samples = min(10_000, ctx.coords.shape[0]) + tv_indices = torch.randperm(ctx.coords.shape[0], device=ctx.coords.device)[:num_tv_samples] + + tv_coords = ctx.coords[tv_indices].detach().requires_grad_(True) + tv_densities_recomputed = self.model(tv_coords) + if isinstance(tv_densities_recomputed, tuple): + tv_densities_recomputed = tv_densities_recomputed[0] + + # Ensure shape is [num_samples, num_channels] + if tv_densities_recomputed.dim() == 1: + tv_densities_recomputed = tv_densities_recomputed.unsqueeze(-1) + + # Compute gradients for each channel + grad_outputs = torch.autograd.grad( + outputs=tv_densities_recomputed, + inputs=tv_coords, + grad_outputs=torch.ones_like(tv_densities_recomputed), + create_graph=True, + )[0] # Shape: [num_samples, coord_dim] + + # Compute TV loss - gradient magnitude per sample + grad_norm = torch.norm(grad_outputs, dim=1) # Shape: [num_samples] + return self.constraints.tv_vol * grad_norm.mean() + # --- Optimization Parameters --- @property def params(self) -> Generator[torch.nn.Parameter, None, None]: @@ -625,13 +634,6 @@ def dtype(self) -> torch.dtype: # TODO: This is a temporary solution to get the dtype of the object. return torch.float32 - @property - def shape(self) -> tuple[int, int, int]: - return self._shape - - @shape.setter - def shape(self, shape: tuple[int, int, int]): - self._shape = shape # --- Helper Functions --- def rebuild_model(self): @@ -647,8 +649,10 @@ def reset(self): # --- Forward Method --- - def forward(self, coords: torch.Tensor) -> torch.Tensor: + def forward(self, coords: Optional[torch.Tensor] = None) -> torch.Tensor: """forward pass for the INR model""" + assert coords is not None, "ObjectINR.forward requires coords" + all_densities = self.model(coords) if all_densities.dim() > 1: @@ -846,33 +850,6 @@ def create_volume(self, return_vol: bool = False): self._obj = pred_full.detach().cpu() - def get_tv_loss( # pyright: ignore[reportIncompatibleMethodOverride] - self, - coords: torch.Tensor, - ) -> torch.Tensor: - tv_loss = torch.tensor(0.0, device=coords.device) - - num_tv_samples = min(10000, coords.shape[0]) - tv_indices = torch.randperm(coords.shape[0], device=coords.device)[:num_tv_samples] - - tv_coords = coords[tv_indices].detach().requires_grad_(True) - - tv_densities_recomputed = self.forward(tv_coords) - - if tv_densities_recomputed.dim() > 1: - tv_densities_recomputed = tv_densities_recomputed.squeeze(-1) - - grad_outputs = torch.autograd.grad( - outputs=tv_densities_recomputed, - inputs=tv_coords, - grad_outputs=torch.ones_like(tv_densities_recomputed), - create_graph=True, - )[0] - - grad_norm = torch.norm(grad_outputs, dim=1) - - tv_loss += self.constraints.tv_vol * grad_norm.mean() - return tv_loss def to(self, device: str | torch.device): # pyright: ignore[reportIncompatibleMethodOverride] -> better to do this device change if isinstance(device, str): From fb07d14f919d1a7f0133d51432f85d52ab089342 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Mon, 27 Apr 2026 13:34:16 -0700 Subject: [PATCH 266/335] Claude OptimizerMixin changes to account for different optimizable params. reconnect_optimizer_to_parameters, and optimizer_params changes --- src/quantem/core/ml/models/model_base.py | 2 +- src/quantem/core/ml/optimizer_mixin.py | 113 +++++++-------- src/quantem/tomography/object_models.py | 166 +++++++---------------- 3 files changed, 110 insertions(+), 171 deletions(-) diff --git a/src/quantem/core/ml/models/model_base.py b/src/quantem/core/ml/models/model_base.py index c7ccf7aa..503f61bb 100644 --- a/src/quantem/core/ml/models/model_base.py +++ b/src/quantem/core/ml/models/model_base.py @@ -37,7 +37,7 @@ class TensorDecompositionModel(nn.Module, ABC): td_type: str -class PlanarDecompositionModel(TensorDecompositionModel): +class PlanarDecompositionModel(TensorDecompositionModel, PPLR): """ Planar factored-grid models: K-Planes, K-Planes-TILTED, HexPlane, tri-planes. diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index b5053a4c..9655eb76 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -1,7 +1,7 @@ import textwrap from abc import abstractmethod from dataclasses import dataclass -from typing import TYPE_CHECKING, Any, Generator, Iterable, Literal +from typing import TYPE_CHECKING, Any, Literal from quantem.core import config @@ -536,7 +536,9 @@ def __init__(self): """Initialize the optimizer mixin.""" self._optimizer = None self._scheduler = None - self._optimizer_params: OptimizerType = OptimizerParams.NoneOptimizer() + self._optimizer_params: OptimizerType | dict[str, OptimizerType] = ( + OptimizerParams.NoneOptimizer() + ) self._scheduler_params: SchedulerType = SchedulerParams.NoneScheduler() # Don't call super().__init__() in mixin classes to avoid MRO issues @@ -551,18 +553,36 @@ def scheduler(self) -> "torch.optim.lr_scheduler.LRScheduler | None": return self._scheduler @property - def optimizer_params(self) -> OptimizerType: + def optimizer_params(self) -> OptimizerType | dict[str, OptimizerType]: """Get the optimizer parameters.""" return self._optimizer_params @optimizer_params.setter - def optimizer_params(self, params: OptimizerType | dict): - """Set the optimizer parameters.""" + def optimizer_params( + self, params: OptimizerType | dict[str, OptimizerType] | dict[str, Any] + ) -> None: + self._optimizer_params = self._normalize_optimizer_params(params) + + def _normalize_optimizer_params( + self, params: OptimizerType | dict[str, Any] + ) -> OptimizerType | dict[str, OptimizerType]: + """Normalize input. Subclasses can override to validate keys.""" + # dict-of-OptimizerType form (PPLR) + if isinstance(params, dict) and not self._is_single_optimizer_dict(params): + return { + k: v if isinstance(v, OptimizerType) else OptimizerParams.parse_dict(d=v) + for k, v in params.items() + } + # Single optimizer form (with dict shorthand like {"name": "adam", "lr": 1e-3}) if isinstance(params, dict): params = OptimizerParams.parse_dict(d=params) if not isinstance(params, OptimizerType): - raise TypeError(f"optimizer parameters must be a OptimizerType, got {type(params)}") - self._optimizer_params = params + raise TypeError(f"optimizer_params must be OptimizerType or dict, got {type(params)}") + return params + + @staticmethod + def _is_single_optimizer_dict(d: dict) -> bool: + return "type" in d or "name" in d @property def scheduler_params(self) -> SchedulerType: @@ -581,7 +601,7 @@ def scheduler_params(self, params: SchedulerType | dict): @abstractmethod def get_optimization_parameters( self, - ) -> "Iterable[torch.Tensor] | Iterable[dict[str, Any]]": + ) -> "list[dict[str, Any]]": """ Get the parameters that should be optimized for this model. This could be replaced with just module.parameters(), but this allows for flexibility @@ -606,16 +626,11 @@ def set_optimizer(self, opt_params: OptimizerType | dict | None = None) -> None: self.remove_optimizer() return - params = self.get_optimization_parameters() - if isinstance(params, torch.Tensor): - params = [params] - elif isinstance(params, Generator): - params = list(params) + params = self.get_optimization_parameters() # always list[dict] # Ensure parameters require gradients for group in params: - tensors = group["params"] if isinstance(group, dict) else [group] - for p in tensors: + for p in group["params"]: p.requires_grad_(True) # Figure out which optimizer class to use if isinstance(self._optimizer_params, dict): @@ -745,56 +760,44 @@ def reconnect_optimizer_to_parameters(self) -> None: if self._optimizer is None: return - current_params = self.get_optimization_parameters() - if isinstance(current_params, torch.Tensor): - current_params = [current_params] - elif isinstance(current_params, Generator): - current_params = list(current_params) - - optimizable_params = [ - p for p in current_params if isinstance(p, torch.Tensor) and p.is_leaf - ] - - if not optimizable_params: - print( - f"shouldn't be getting here! No optimizable parameters found for {self.__class__.__name__}, removing optimizer" - ) + new_groups = self.get_optimization_parameters() + if not new_groups: + print(f"No optimizable parameters for {type(self).__name__}, removing optimizer") self.remove_optimizer() return - for p in optimizable_params: - p.requires_grad_(True) + # Ensure leaf params with grad + for group in new_groups: + for p in group["params"]: + if not p.is_leaf: + raise ValueError("Non-leaf tensor in param group; build groups from leaves") + p.requires_grad_(True) - # Preserve optimizer state and param_group settings - old_state = self._optimizer.state.copy() - current_param_group = self._optimizer.param_groups[0].copy() + old_state = dict(self._optimizer.state) + old_hyperparams = [ + {k: v for k, v in pg.items() if k != "params"} for pg in self._optimizer.param_groups + ] - # Reconnect to new parameters self._optimizer.param_groups.clear() - self._optimizer.add_param_group({"params": optimizable_params}) + for group in new_groups: + self._optimizer.add_param_group(group) + + # Restore per-group hyperparameters by index + for new_pg, old_pg in zip(self._optimizer.param_groups, old_hyperparams): + new_pg.update(old_pg) - # Update state mapping and move tensors to correct device + # Remap state for tensors that survived new_state = {} - device = optimizable_params[0].device - for i, old_param in enumerate(old_state.keys()): - if i < len(optimizable_params): - new_param = optimizable_params[i] - new_state[new_param] = {} - for key, value in old_state[old_param].items(): - if isinstance(value, torch.Tensor): - new_state[new_param][key] = value.to(device) - else: - new_state[new_param][key] = value + for new_pg in self._optimizer.param_groups: + for new_param in new_pg["params"]: + if new_param in old_state: + new_state[new_param] = { + k: (v.to(new_param.device) if isinstance(v, torch.Tensor) else v) + for k, v in old_state[new_param].items() + } self._optimizer.state.clear() self._optimizer.state.update(new_state) - # Restore param_group settings (LR, betas, etc.) but keep new parameters - self._optimizer.param_groups[0].update( - {k: v for k, v in current_param_group.items() if k != "params"} - ) - - # Reconnect scheduler - if self._scheduler is not None and self._optimizer is not None: + if self._scheduler is not None: self._scheduler.optimizer = self._optimizer - return diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 19c69e3e..d7f93268 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -10,12 +10,11 @@ from tqdm.auto import tqdm from quantem.core.io.serialize import AutoSerialize -from quantem.core.ml import OptimizerParams from quantem.core.ml.constraints import BaseConstraints, Constraints from quantem.core.ml.ddp import DDPMixin from quantem.core.ml.loss_functions import get_loss_module from quantem.core.ml.models.model_base import PlanarDecompositionModel -from quantem.core.ml.optimizer_mixin import OptimizerMixin, OptimizerType +from quantem.core.ml.optimizer_mixin import OptimizerMixin from quantem.core.utils.rng import RNGMixin from quantem.tomography.dataset_models import TomographyINRPretrainDataset from quantem.tomography.tomography import ReconstructionContext @@ -291,11 +290,14 @@ def params(self) -> Generator[torch.nn.Parameter, None, None]: raise NotImplementedError # --- Helper Functions --- - def get_optimization_parameters(self) -> list[nn.Parameter] | list[dict[str, Any]]: - """ - Get the parameters that should be optimized for this model. - """ - return list(self.params) + def get_optimization_parameters(self) -> list[dict[str, Any]]: + """Default: wrap self.params in a single param group.""" + if isinstance(self._optimizer_params, dict): + # Shouldn't happen for single-group models, but be defensive + opt = next(iter(self._optimizer_params.values())) + else: + opt = self._optimizer_params + return [{"params": list(self.params), **opt.params()}] @abstractmethod # Each subclass should implement this. def to(self, device: str | torch.device): @@ -540,14 +542,18 @@ def apply_soft_constraints( ) -> torch.Tensor: soft_loss = torch.tensor(0.0, device=ctx.coords.device) if self.constraints.tv_vol > 0: - assert ctx.coords is not None, "coords must be provided for INR object model to compute the TV loss" + assert ctx.coords is not None, ( + "coords must be provided for INR object model to compute the TV loss" + ) soft_loss += self.get_tv_loss(ctx) if ( isinstance(self.constraints, ObjConstraintParams.ObjINRConstraints) and self.constraints.sparsity > 0 ): # NOTE: For the linter, I must make this :) - assert ctx.pred is not None, "pred must be provided for INR object model to compute the sparsity loss" + assert ctx.pred is not None, ( + "pred must be provided for INR object model to compute the sparsity loss" + ) sparsity_loss = self.constraints.sparsity * torch.norm(ctx.pred, p=1) soft_loss += sparsity_loss @@ -601,7 +607,7 @@ def get_tv_loss(self, ctx: ReconstructionContext) -> torch.Tensor: def params(self) -> Generator[torch.nn.Parameter, None, None]: return self.model.parameters() # type: ignore[attr-defined] - def get_optimization_parameters(self) -> list[nn.Parameter]: + def get_optimization_parameters(self) -> list[nn.Parameter] | list[dict[str, Any]]: return list(self.params) # Pretraining @@ -634,7 +640,6 @@ def dtype(self) -> torch.dtype: # TODO: This is a temporary solution to get the dtype of the object. return torch.float32 - # --- Helper Functions --- def rebuild_model(self): self._model = self.distribute_model(self._model) @@ -652,7 +657,7 @@ def reset(self): def forward(self, coords: Optional[torch.Tensor] = None) -> torch.Tensor: """forward pass for the INR model""" assert coords is not None, "ObjectINR.forward requires coords" - + all_densities = self.model(coords) if all_densities.dim() > 1: @@ -850,7 +855,6 @@ def create_volume(self, return_vol: bool = False): self._obj = pred_full.detach().cpu() - def to(self, device: str | torch.device): # pyright: ignore[reportIncompatibleMethodOverride] -> better to do this device change if isinstance(device, str): device = torch.device(device) @@ -910,28 +914,30 @@ def from_model( # --- Constraints --- - def apply_soft_constraints( - self, - coords: torch.Tensor, - all_densities: torch.Tensor, - pred: torch.Tensor, - ) -> torch.Tensor: - soft_loss = torch.tensor(0.0, device=pred.device) + def apply_soft_constraints(self, ctx: ReconstructionContext) -> torch.Tensor: + soft_loss = torch.tensor(0.0, device=ctx.pred.device) if self.constraints.tv_vol > 0: - soft_loss += self.get_tv_loss(coords, pred) + assert ctx.coords is not None, "Coordinates must be provided for TV loss" + assert ctx.pred is not None, "Prediction must be provided for TV loss" + soft_loss += self.get_tv_loss(ctx) if self.constraints.sparsity > 0: # NOTE: For the linter, I must make this :) - sparsity_loss = self.constraints.sparsity * all_densities.abs().mean() + assert ctx.all_densities is not None, ( + "All densities must be provided for sparsity loss" + ) + sparsity_loss = self.constraints.sparsity * ctx.all_densities.abs().mean() soft_loss += sparsity_loss return soft_loss # TV Losses - def get_tv_loss(self, coords: torch.Tensor, pred: torch.Tensor) -> torch.Tensor: - tv_loss = torch.tensor(0.0, device=pred.device) + def get_tv_loss(self, ctx: ReconstructionContext) -> torch.Tensor: + assert ctx.coords is not None, "Coordinates must be provided for TV loss" + assert ctx.pred is not None, "Prediction must be provided for TV loss" + tv_loss = torch.tensor(0.0, device=ctx.pred.device) tv_loss += self._get_plane_tv_loss() - tv_loss += self.get_volume_tv_loss(coords) + tv_loss += self.get_volume_tv_loss(ctx.coords) return tv_loss def _get_plane_tv_loss(self) -> torch.Tensor: @@ -1012,103 +1018,33 @@ def params(self) -> Generator[torch.nn.Parameter, None, None]: return self.model.parameters() # type: ignore[attr-defined] - def get_optimization_parameters(self) -> list[nn.Parameter] | list[dict[str, Any]]: + def get_optimization_parameters(self) -> list[dict[str, Any]]: + """PPLR: per-key param groups.""" model = _unwrap(self.model) + assert isinstance(self._optimizer_params, dict), ( + "ObjectTensorDecomp requires dict-form optimizer_params" + ) return [ - { - "params": model.get_params()[key], - **self.optimizer_params[key].params(), - } + {"params": model.get_params()[key], **self._optimizer_params[key].params()} for key in model.param_keys ] - # --- Optimizer Mixin Overloads in the case of PPLR --- - - @property - def optimizer_params(self) -> OptimizerType | dict[str, OptimizerType]: - """Get the optimizer parameters.""" - return self._optimizer_params - - @optimizer_params.setter - def optimizer_params(self, params: OptimizerType | dict[str, OptimizerType] | dict[str, Any]): - """Set the optimizer parameters.""" - - if not isinstance(params, dict): - raise TypeError(f"optimizer parameters must be a dict for PPLR, got {type(params)}") - - object_params = params - - if set(object_params.keys()) != set(self.model.param_keys): - raise ValueError( - f"optimizer parameters keys must match PPLR param_keys, got {object_params.keys()} != {self.model.param_keys}" + def _normalize_optimizer_params(self, params): + """ObjectTensorDecomp requires a dict matching model.param_keys.""" + if not isinstance(params, dict) or self._is_single_optimizer_dict(params): + raise TypeError( + f"ObjectTensorDecomp requires dict[str, OptimizerType] keyed by " + f"param_keys; got {type(params)}" ) - - params = {} - for key, value in object_params.items(): - if isinstance(value, dict): - params[key] = OptimizerParams.parse_dict(d=value) - elif isinstance(value, OptimizerType): - params[key] = value - else: - raise TypeError( - f"optimizer parameters must be a dict or OptimizerType, got {type(value)}" - ) - - self._optimizer_params = params - - def reconnect_optimizer_to_parameters(self) -> None: - if self.optimizer is None: - return - - current_params = self.get_optimization_parameters() - - optimizable_params = [ - p - for p in current_params - if isinstance(p["params"][0], torch.Tensor) and p["params"][0].is_leaf - ] - - if not optimizable_params: + model = _unwrap(self.model) + expected = set(model.param_keys) + got = set(params.keys()) + if got != expected: raise ValueError( - f"Shouldn't be getting here! No optimizable parameters found for {self.__class__.__name__}." + f"optimizer_params keys must match model.param_keys: " + f"got {got}, expected {expected}" ) - - for p in optimizable_params: - print(f"Setting requires_grad for parameter: {p}") - p["params"][0].requires_grad_(True) - - assert self._optimizer is not None - # Preserve optimizer states and param_group settings - old_state = self._optimizer.state.copy() - old_param_groups = self._optimizer.param_groups.copy() - - # Reconnect to new parameters - self._optimizer.param_groups.clear() - for param_group in optimizable_params: - self._optimizer.add_param_group(param_group) - - # Restore per-group hyperparameters (lr, betas, weight_decay, etc.) by index, - # excluding 'params' which comes from the new groups - for new_pg, old_pg in zip(self._optimizer.param_groups, old_param_groups): - new_pg.update({k: v for k, v in old_pg.items() if k != "params"}) - - # Remap optimizer state: for any new param that IS the same tensor as an old param, - # carry its state over (moved to the right device just in case). - new_state = {} - for new_pg in self._optimizer.param_groups: - for new_param in new_pg["params"]: - if new_param in old_state: - device = new_param.device - new_state[new_param] = { - k: (v.to(device) if isinstance(v, torch.Tensor) else v) - for k, v in old_state[new_param].items() - } - - self._optimizer.state.clear() - self._optimizer.state.update(new_state) - - if self._scheduler is not None and self._optimizer is not None: - self._scheduler.optimizer = self._optimizer + return super()._normalize_optimizer_params(params) def pretrain(self) -> None: raise NotImplementedError( @@ -1116,4 +1052,4 @@ def pretrain(self) -> None: ) -ObjectModelType = ObjectPixelated | ObjectINR +ObjectModelType = ObjectPixelated | ObjectINR | ObjectTensorDecomp From 6678c43e8ecf1438efd610ff05a50cf8918e3b18 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Mon, 27 Apr 2026 13:46:13 -0700 Subject: [PATCH 267/335] Moved ReconContext, changed get_optimization_parameters in dataset_models.py --- src/quantem/core/ml/constraints.py | 2 +- src/quantem/tomography/dataset_models.py | 11 +++++--- src/quantem/tomography/object_models.py | 6 +++-- src/quantem/tomography/tomography.py | 28 ++++++-------------- src/quantem/tomography/tomography_context.py | 21 +++++++++++++++ 5 files changed, 42 insertions(+), 26 deletions(-) create mode 100644 src/quantem/tomography/tomography_context.py diff --git a/src/quantem/core/ml/constraints.py b/src/quantem/core/ml/constraints.py index 137ed4df..f437924f 100644 --- a/src/quantem/core/ml/constraints.py +++ b/src/quantem/core/ml/constraints.py @@ -7,7 +7,7 @@ import torch from numpy.typing import NDArray -from quantem.tomography.tomography import ReconstructionContext +from quantem.tomography.tomography_context import ReconstructionContext @dataclass(slots=False) diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index 24f0f775..b50c5976 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -232,11 +232,16 @@ def from_data( # --- Optimization Parameters --- - def get_optimization_parameters(self) -> list[nn.Parameter]: + def get_optimization_parameters(self) -> list[dict[str, Any]]: """ - Get the parameters that should be optimized for this model. + Get the parameters that should be optimized for this model, + wrapped in a single param group. """ - return list(self.parameters()) + if isinstance(self._optimizer_params, dict): + opt = next(iter(self._optimizer_params.values())) + else: + opt = self._optimizer_params + return [{"params": list(self.parameters()), **opt.params()}] # --- Forward pass --- @abstractmethod diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index d7f93268..10bae9e4 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -17,7 +17,7 @@ from quantem.core.ml.optimizer_mixin import OptimizerMixin from quantem.core.utils.rng import RNGMixin from quantem.tomography.dataset_models import TomographyINRPretrainDataset -from quantem.tomography.tomography import ReconstructionContext +from quantem.tomography.tomography_context import ReconstructionContext class ObjConstraintParams: @@ -915,7 +915,9 @@ def from_model( # --- Constraints --- def apply_soft_constraints(self, ctx: ReconstructionContext) -> torch.Tensor: - soft_loss = torch.tensor(0.0, device=ctx.pred.device) + soft_loss = torch.tensor( + 0.0, device=ctx.pred.device if ctx.pred is not None else self.device + ) if self.constraints.tv_vol > 0: assert ctx.coords is not None, "Coordinates must be provided for TV loss" assert ctx.pred is not None, "Prediction must be provided for TV loss" diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 899f1a1d..8976272e 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -1,7 +1,6 @@ import os -from dataclasses import dataclass from pathlib import Path -from typing import Literal, Optional, Self, Sequence +from typing import Literal, Self, Sequence import matplotlib.pyplot as plt import numpy as np @@ -31,6 +30,7 @@ ) from quantem.tomography.radon.radon import iradon_torch, radon_torch from quantem.tomography.tomography_base import TomographyBase +from quantem.tomography.tomography_context import ReconstructionContext from quantem.tomography.tomography_opt import TomographyOpt @@ -219,8 +219,13 @@ def reconstruct( ) pred = integrated_densities.float() + soft_constraints_loss = self.obj_model.apply_soft_constraints( - all_coords, all_densities, pred + ctx=ReconstructionContext( + coords=all_coords, + pred=pred, + all_densities=all_densities, + ) ) target = batch["target_value"].to(self.device, non_blocking=True).float() @@ -630,20 +635,3 @@ def plot_losses(self): ax.set_title("Reconstruction Loss") ax.set_yscale("log") plt.show() - - -@dataclass -class ReconstructionContext: - """ - Handles all reconstruction parameters to be passed into object models. - - Subclasses will pick whatever parameter they need - - Pixelated reads ".volume" - - INR reads ".coords" and recomputes via the model. - - TensorDecomp reads ".coords" and ".pred" (and ".all densities") - """ - - coords: Optional[torch.Tensor] = None - pred: Optional[torch.Tensor] = None - all_densities: Optional[torch.Tensor] = None - obj: Optional[torch.Tensor] = None diff --git a/src/quantem/tomography/tomography_context.py b/src/quantem/tomography/tomography_context.py new file mode 100644 index 00000000..4b67f118 --- /dev/null +++ b/src/quantem/tomography/tomography_context.py @@ -0,0 +1,21 @@ +from dataclasses import dataclass +from typing import Optional + +import torch + + +@dataclass +class ReconstructionContext: + """ + Handles all reconstruction parameters to be passed into object models. + + Subclasses will pick whatever parameter they need + - Pixelated reads ".volume" + - INR reads ".coords" and recomputes via the model. + - TensorDecomp reads ".coords" and ".pred" (and ".all densities") + """ + + coords: Optional[torch.Tensor] = None + pred: Optional[torch.Tensor] = None + all_densities: Optional[torch.Tensor] = None + obj: Optional[torch.Tensor] = None From 9a9641545450b48ca3894d38730e070b9636be52 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Mon, 27 Apr 2026 21:29:03 -0700 Subject: [PATCH 268/335] Small changes --- src/quantem/tomography/object_models.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 10bae9e4..a094fae8 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -607,9 +607,6 @@ def get_tv_loss(self, ctx: ReconstructionContext) -> torch.Tensor: def params(self) -> Generator[torch.nn.Parameter, None, None]: return self.model.parameters() # type: ignore[attr-defined] - def get_optimization_parameters(self) -> list[nn.Parameter] | list[dict[str, Any]]: - return list(self.params) - # Pretraining @property def pretrained_weights(self) -> dict[str, torch.Tensor]: From 1033cd668bf502e0ff11cc9d813da78e8fa33c7e Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 2 May 2026 14:24:04 -0700 Subject: [PATCH 269/335] feat: add Show2D and Show4DSTEM flagship widgets --- widget/js/colormaps.ts | 1086 +++++ widget/js/control-customizer.tsx | 174 + widget/js/format.ts | 40 + widget/js/histogram.ts | 19 + widget/js/index.jsx | 33 - widget/js/scalebar.ts | 444 ++ widget/js/show2d/index.tsx | 4185 +++++++++++++++++++ widget/js/show2d/show2d.css | 9 + widget/js/show4dstem/index.tsx | 4259 +++++++++++++++++++ widget/js/show4dstem/styles.css | 5 + widget/js/stats.ts | 101 + widget/js/theme.ts | 149 + widget/js/tool-parity.ts | 156 + widget/js/webgpu-fft.ts | 509 +++ widget/package-lock.json | 971 ++++- widget/package.json | 22 +- widget/pyproject.toml | 5 + widget/src/quantem/widget/__init__.py | 25 +- widget/src/quantem/widget/array_utils.py | 282 ++ widget/src/quantem/widget/json_state.py | 47 + widget/src/quantem/widget/show2d.py | 1309 ++++++ widget/src/quantem/widget/show4dstem.py | 4337 ++++++++++++++++++++ widget/src/quantem/widget/tool_parity.json | 93 + widget/src/quantem/widget/tool_parity.py | 184 + widget/tsconfig.json | 25 + widget/vite.config.js | 15 +- 26 files changed, 18364 insertions(+), 120 deletions(-) create mode 100644 widget/js/colormaps.ts create mode 100644 widget/js/control-customizer.tsx create mode 100644 widget/js/format.ts create mode 100644 widget/js/histogram.ts delete mode 100644 widget/js/index.jsx create mode 100644 widget/js/scalebar.ts create mode 100644 widget/js/show2d/index.tsx create mode 100644 widget/js/show2d/show2d.css create mode 100644 widget/js/show4dstem/index.tsx create mode 100644 widget/js/show4dstem/styles.css create mode 100644 widget/js/stats.ts create mode 100644 widget/js/theme.ts create mode 100644 widget/js/tool-parity.ts create mode 100644 widget/js/webgpu-fft.ts create mode 100644 widget/src/quantem/widget/array_utils.py create mode 100644 widget/src/quantem/widget/json_state.py create mode 100644 widget/src/quantem/widget/show2d.py create mode 100644 widget/src/quantem/widget/show4dstem.py create mode 100644 widget/src/quantem/widget/tool_parity.json create mode 100644 widget/src/quantem/widget/tool_parity.py create mode 100644 widget/tsconfig.json diff --git a/widget/js/colormaps.ts b/widget/js/colormaps.ts new file mode 100644 index 00000000..ba160698 --- /dev/null +++ b/widget/js/colormaps.ts @@ -0,0 +1,1086 @@ +const COLORMAP_POINTS: Record = { + inferno: [ + [0, 0, 4], [40, 11, 84], [101, 21, 110], [159, 42, 99], + [212, 72, 66], [245, 125, 21], [252, 193, 57], [252, 255, 164], + ], + viridis: [ + [68, 1, 84], [72, 36, 117], [65, 68, 135], [53, 95, 141], + [42, 120, 142], [33, 145, 140], [34, 168, 132], [68, 191, 112], + [122, 209, 81], [189, 223, 38], [253, 231, 37], + ], + plasma: [ + [13, 8, 135], [75, 3, 161], [126, 3, 168], [168, 34, 150], + [203, 70, 121], [229, 107, 93], [248, 148, 65], [253, 195, 40], [240, 249, 33], + ], + magma: [ + [0, 0, 4], [28, 16, 68], [79, 18, 123], [129, 37, 129], + [181, 54, 122], [229, 80, 100], [251, 135, 97], [254, 194, 135], [252, 253, 191], + ], + hot: [ + [0, 0, 0], [87, 0, 0], [173, 0, 0], [255, 0, 0], + [255, 87, 0], [255, 173, 0], [255, 255, 0], [255, 255, 128], [255, 255, 255], + ], + gray: [[0, 0, 0], [255, 255, 255]], + hsv: [ + [255, 0, 0], [255, 255, 0], [0, 255, 0], [0, 255, 255], + [0, 0, 255], [255, 0, 255], [255, 0, 0], + ], + turbo: [ + [48, 18, 59], [69, 55, 161], [66, 107, 230], [30, 162, 230], + [29, 212, 169], [79, 241, 89], [175, 240, 32], [244, 195, 12], + [248, 118, 11], [207, 46, 3], [122, 4, 2], + ], + RdBu: [ + [103, 0, 31], [178, 24, 43], [214, 96, 77], [244, 165, 130], + [253, 219, 199], [247, 247, 247], [209, 229, 240], [146, 197, 222], + [67, 147, 195], [33, 102, 172], [5, 48, 97], + ], +}; + +export const COLORMAP_NAMES = Object.keys(COLORMAP_POINTS); + +function createColormapLUT(points: number[][]): Uint8Array { + const lut = new Uint8Array(256 * 3); + for (let i = 0; i < 256; i++) { + const t = (i / 255) * (points.length - 1); + const idx = Math.floor(t); + const frac = t - idx; + const p0 = points[Math.min(idx, points.length - 1)]; + const p1 = points[Math.min(idx + 1, points.length - 1)]; + lut[i * 3] = Math.round(p0[0] + frac * (p1[0] - p0[0])); + lut[i * 3 + 1] = Math.round(p0[1] + frac * (p1[1] - p0[1])); + lut[i * 3 + 2] = Math.round(p0[2] + frac * (p1[2] - p0[2])); + } + return lut; +} + +export const COLORMAPS: Record = Object.fromEntries( + Object.entries(COLORMAP_POINTS).map(([name, points]) => [name, createColormapLUT(points)]) +); + +/** Apply colormap LUT to float data, writing into an RGBA Uint8ClampedArray. */ +export function applyColormap( + data: Float32Array, + rgba: Uint8ClampedArray, + lut: Uint8Array, + vmin: number, + vmax: number, +): void { + const range = vmax > vmin ? vmax - vmin : 1; + const uniformData = !(vmax > vmin); + for (let i = 0; i < data.length; i++) { + const clipped = Math.max(vmin, Math.min(vmax, data[i])); + const v = uniformData ? 128 : Math.min(255, Math.floor(((clipped - vmin) / range) * 255)); + const j = i * 4; + const lutIdx = v * 3; + rgba[j] = lut[lutIdx]; + rgba[j + 1] = lut[lutIdx + 1]; + rgba[j + 2] = lut[lutIdx + 2]; + rgba[j + 3] = 255; + } +} + +/** Create an offscreen canvas with colormapped data. Returns null if context unavailable. */ +export function renderToOffscreen( + data: Float32Array, + width: number, + height: number, + lut: Uint8Array, + vmin: number, + vmax: number, +): HTMLCanvasElement | null { + const offscreen = document.createElement("canvas"); + offscreen.width = width; + offscreen.height = height; + const ctx = offscreen.getContext("2d"); + if (!ctx) return null; + const imgData = ctx.createImageData(width, height); + applyColormap(data, imgData.data, lut, vmin, vmax); + ctx.putImageData(imgData, 0, 0); + return offscreen; +} + +/** Render colormapped data to a reusable offscreen canvas + ImageData (avoids per-frame allocation). */ +export function renderToOffscreenReuse( + data: Float32Array, + lut: Uint8Array, + vmin: number, + vmax: number, + offscreen: HTMLCanvasElement, + imgData: ImageData, +): void { + applyColormap(data, imgData.data, lut, vmin, vmax); + offscreen.getContext("2d")!.putImageData(imgData, 0, 0); +} + +// ============================================================================ +// WebGPU-accelerated colormap engine +// ============================================================================ + +// 2D dispatch (16×16 workgroups) to stay within WebGPU's 65535 workgroup limit. +// 1D dispatch with wg=256 needs ceil(4096*4096/256)=65536 — exceeds the limit by 1. +const COLORMAP_SHADER = /* wgsl */ ` +struct Params { + width: u32, + height: u32, + vmin: f32, + vmax: f32, + log_scale: u32, + _pad: u32, +}; + +@group(0) @binding(0) var params: Params; +@group(0) @binding(1) var data: array; +@group(0) @binding(2) var lut: array; +@group(0) @binding(3) var rgba: array; + +@compute @workgroup_size(16, 16) +fn main(@builtin(global_invocation_id) gid: vec3u) { + if (gid.x >= params.width || gid.y >= params.height) { return; } + let idx = gid.y * params.width + gid.x; + var val = data[idx]; + if (params.log_scale == 1u) { + val = log(1.0 + max(val, 0.0)); + } + let range = max(params.vmax - params.vmin, 1e-30); + let clipped = clamp(val, params.vmin, params.vmax); + let t = (clipped - params.vmin) / range; + let lutIdx = min(u32(t * 255.0), 255u); + let rgb = lut[lutIdx]; + // Simplified: LUT is already packed as R|(G<<8)|(B<<16), just add alpha + rgba[idx] = rgb | 0xFF000000u; +} +`; + +// Fullscreen-quad blit shader: reads RGBA u32 buffer, renders to canvas texture +const BLIT_SHADER = /* wgsl */ ` +struct BlitParams { width: u32, height: u32 }; +@group(0) @binding(0) var params: BlitParams; +@group(0) @binding(1) var rgba: array; + +struct VSOut { @builtin(position) pos: vec4f, @location(0) uv: vec2f }; + +@vertex fn vs(@builtin(vertex_index) vi: u32) -> VSOut { + // Fullscreen triangle (3 vertices, covers entire clip space) + var out: VSOut; + let x = f32(i32(vi & 1u)) * 4.0 - 1.0; + let y = f32(i32(vi >> 1u)) * 4.0 - 1.0; + out.pos = vec4f(x, y, 0.0, 1.0); + out.uv = vec2f((x + 1.0) * 0.5, (1.0 - y) * 0.5); + return out; +} + +@fragment fn fs(in: VSOut) -> @location(0) vec4f { + let px = u32(in.uv.x * f32(params.width)); + let py = u32(in.uv.y * f32(params.height)); + let idx = py * params.width + px; + let packed = rgba[idx]; + let r = f32(packed & 0xFFu) / 255.0; + let g = f32((packed >> 8u) & 0xFFu) / 255.0; + let b = f32((packed >> 16u) & 0xFFu) / 255.0; + return vec4f(r, g, b, 1.0); +} +`; + +/** + * GPU-accelerated colormap engine. Holds persistent data buffers on GPU; + * histogram slider changes only update a small uniform — no data re-upload. + */ +type GPUSlot = { + dataBuffer: GPUBuffer; + rgbaBuffer: GPUBuffer; + readBuffer: GPUBuffer; + paramsBuffer: GPUBuffer; + histBinsBuffer: GPUBuffer; + histReadBuffer: GPUBuffer; + count: number; + width: number; + height: number; +}; + +export class GPUColormapEngine { + private device: GPUDevice; + private pipeline: GPUComputePipeline | null = null; + private blitPipeline: GPURenderPipeline | null = null; + // Per-image GPU state: persistent buffers (data, rgba, read, params, histogram) + private slots: GPUSlot[] = []; + private lutBuffer: GPUBuffer | null = null; + private currentLutName: string = ""; + + constructor(device: GPUDevice) { this.device = device; } + + private ensurePipeline(): void { + if (this.pipeline) return; + const module = this.device.createShaderModule({ code: COLORMAP_SHADER }); + this.pipeline = this.device.createComputePipeline({ + layout: "auto", + compute: { module, entryPoint: "main" }, + }); + } + + /** Upload LUT to GPU (only when colormap name changes). */ + uploadLUT(lutName: string, lut: Uint8Array): void { + if (this.currentLutName === lutName && this.lutBuffer) return; + this.ensurePipeline(); + if (this.lutBuffer) this.lutBuffer.destroy(); + // Pack RGB triplets into u32 for GPU (R in low bits) + const packed = new Uint32Array(256); + for (let i = 0; i < 256; i++) { + packed[i] = lut[i * 3] | (lut[i * 3 + 1] << 8) | (lut[i * 3 + 2] << 16); + } + this.lutBuffer = this.device.createBuffer({ + size: packed.byteLength, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, + }); + this.device.queue.writeBuffer(this.lutBuffer, 0, packed); + this.currentLutName = lutName; + } + + + /** Upload float32 image data for slot `idx`. Only call when data changes. */ + uploadData(idx: number, data: Float32Array, width?: number, height?: number): void { + this.ensurePipeline(); + while (this.slots.length <= idx) this.slots.push(null as never); + if (this.slots[idx]) { + this.slots[idx].dataBuffer.destroy(); + this.slots[idx].rgbaBuffer.destroy(); + this.slots[idx].readBuffer.destroy(); + this.slots[idx].paramsBuffer.destroy(); + this.slots[idx].histBinsBuffer.destroy(); + this.slots[idx].histReadBuffer.destroy(); + } + // Validate dimensions — if width*height doesn't match data length, derive from sqrt + // (catches stale closure values like width=1 from mount effects) + const validDims = width && height && width > 1 && height > 1 && width * height === data.length; + const w = validDims ? width : Math.round(Math.sqrt(data.length)); + const h = validDims ? height : Math.round(data.length / w); + const byteSize = data.byteLength; + const rgbaSize = data.length * 4; + const dataBuffer = this.device.createBuffer({ + size: byteSize, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, + }); + this.device.queue.writeBuffer(dataBuffer, 0, data.buffer as ArrayBuffer, data.byteOffset, data.byteLength); + const rgbaBuffer = this.device.createBuffer({ + size: rgbaSize, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + }); + // Persistent read buffer — reused on every applySlots call (no create/destroy overhead) + const readBuffer = this.device.createBuffer({ + size: rgbaSize, + usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST, + }); + // Persistent params buffer — reused (just writeBuffer on each call) + const paramsBuffer = this.device.createBuffer({ + size: 24, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, + }); + // Persistent histogram buffers (256 bins × 4 bytes = 1KB each) + const histBinsBuffer = this.device.createBuffer({ + size: 256 * 4, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + }); + const histReadBuffer = this.device.createBuffer({ + size: 256 * 4, + usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST, + }); + this.slots[idx] = { dataBuffer, rgbaBuffer, readBuffer, paramsBuffer, histBinsBuffer, histReadBuffer, count: data.length, width: w, height: h }; + } + + // Params buffer: 24 bytes = { width: u32, height: u32, vmin: f32, vmax: f32, log_scale: u32, _pad: u32 } + private _writeParams(buf: ArrayBuffer, width: number, height: number, vmin: number, vmax: number, logScale: boolean): void { + const u = new Uint32Array(buf); + const f = new Float32Array(buf); + u[0] = width; + u[1] = height; + f[2] = vmin; + f[3] = vmax; + u[4] = logScale ? 1 : 0; + u[5] = 0; // pad + } + + /** + * Apply colormap to specific slot indices with per-image vmin/vmax. + * Uses persistent per-slot read buffers (no create/destroy overhead). + * Log scale is applied on GPU per pixel. + */ + async applySlots( + indices: number[], + ranges: { vmin: number; vmax: number }[], + logScale: boolean = false, + ): Promise<{ idx: number; rgba: Uint8ClampedArray }[]> { + if (!this.pipeline || !this.lutBuffer || indices.length === 0) return []; + + const activeSlots: { idx: number; slot: GPUSlot; count: number }[] = []; + const encoder = this.device.createCommandEncoder(); + const params = new ArrayBuffer(24); + + for (let k = 0; k < indices.length; k++) { + const i = indices[k]; + const slot = this.slots[i]; + if (!slot) continue; + const range = ranges[k] || { vmin: 0, vmax: 1 }; + + // Reuse persistent paramsBuffer — just write new values + this._writeParams(params, slot.width, slot.height, range.vmin, range.vmax, logScale); + this.device.queue.writeBuffer(slot.paramsBuffer, 0, params); + + const bindGroup = this.device.createBindGroup({ + layout: this.pipeline.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: { buffer: slot.paramsBuffer } }, + { binding: 1, resource: { buffer: slot.dataBuffer } }, + { binding: 2, resource: { buffer: this.lutBuffer } }, + { binding: 3, resource: { buffer: slot.rgbaBuffer } }, + ], + }); + + const pass = encoder.beginComputePass(); + pass.setPipeline(this.pipeline); + pass.setBindGroup(0, bindGroup); + pass.dispatchWorkgroups(Math.ceil(slot.width / 16), Math.ceil(slot.height / 16)); + pass.end(); + + // Copy to persistent read buffer + encoder.copyBufferToBuffer(slot.rgbaBuffer, 0, slot.readBuffer, 0, slot.count * 4); + activeSlots.push({ idx: i, slot, count: slot.count }); + } + this.device.queue.submit([encoder.finish()]); + await Promise.all(activeSlots.map(s => s.slot.readBuffer.mapAsync(GPUMapMode.READ))); + + const results: { idx: number; rgba: Uint8ClampedArray }[] = []; + for (const s of activeSlots) { + const mapped = s.slot.readBuffer.getMappedRange(); + const rgba = new Uint8ClampedArray(s.count * 4); + rgba.set(new Uint8ClampedArray(mapped)); + s.slot.readBuffer.unmap(); + results.push({ idx: s.idx, rgba }); + } + + // applySlots is for callers that need raw RGBA arrays (not rendering to canvas) + // For rendering, use renderSlots which avoids the intermediate copy + return results; + } + + /** Apply colormap to ALL slots with shared vmin/vmax. */ + async apply(vmin: number, vmax: number, logScale: boolean = false): Promise { + const indices = this.slots.map((_, i) => i).filter(i => this.slots[i]); + const ranges = indices.map(() => ({ vmin, vmax })); + const results = await this.applySlots(indices, ranges, logScale); + // Return in slot order + const out: Uint8ClampedArray[] = []; + for (const r of results) out[r.idx] = r.rgba; + return out.filter(x => x); + } + + /** Apply colormap with per-image vmin/vmax. */ + async applyPerImage(ranges: { vmin: number; vmax: number }[], logScale: boolean = false): Promise { + const indices = this.slots.map((_, i) => i).filter(i => this.slots[i]); + const perSlotRanges = indices.map(i => ranges[i] || { vmin: 0, vmax: 1 }); + const results = await this.applySlots(indices, perSlotRanges, logScale); + const out: Uint8ClampedArray[] = []; + for (const r of results) out[r.idx] = r.rgba; + return out.filter(x => x); + } + + /** Apply colormap to a SINGLE slot (fast path for slider drag). */ + async applySingle(idx: number, vmin: number, vmax: number, logScale: boolean = false): Promise { + const results = await this.applySlots([idx], [{ vmin, vmax }], logScale); + return results.length > 0 ? results[0].rgba : null; + } + + /** + * GPU colormap → offscreen canvas in one pass (zero intermediate allocation). + * Writes from GPU mapped memory directly into ImageData, then putImageData. + * Eliminates the 768MB temp Uint8ClampedArray that applySlots allocates. + */ + async renderSlots( + indices: number[], + ranges: { vmin: number; vmax: number }[], + offscreens: (HTMLCanvasElement | null)[], + imgDatas: (ImageData | null)[], + logScale: boolean = false, + ): Promise { + if (!this.pipeline || !this.lutBuffer || indices.length === 0) return 0; + + const activeSlots: { k: number; idx: number; slot: GPUSlot }[] = []; + const encoder = this.device.createCommandEncoder(); + const params = new ArrayBuffer(24); + + for (let k = 0; k < indices.length; k++) { + const i = indices[k]; + const slot = this.slots[i]; + if (!slot || !offscreens[k] || !imgDatas[k]) continue; + const range = ranges[k] || { vmin: 0, vmax: 1 }; + + this._writeParams(params, slot.width, slot.height, range.vmin, range.vmax, logScale); + this.device.queue.writeBuffer(slot.paramsBuffer, 0, params); + + const bindGroup = this.device.createBindGroup({ + layout: this.pipeline.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: { buffer: slot.paramsBuffer } }, + { binding: 1, resource: { buffer: slot.dataBuffer } }, + { binding: 2, resource: { buffer: this.lutBuffer } }, + { binding: 3, resource: { buffer: slot.rgbaBuffer } }, + ], + }); + + const pass = encoder.beginComputePass(); + pass.setPipeline(this.pipeline); + pass.setBindGroup(0, bindGroup); + pass.dispatchWorkgroups(Math.ceil(slot.width / 16), Math.ceil(slot.height / 16)); + pass.end(); + encoder.copyBufferToBuffer(slot.rgbaBuffer, 0, slot.readBuffer, 0, slot.count * 4); + activeSlots.push({ k, idx: i, slot }); + } + this.device.queue.submit([encoder.finish()]); + await Promise.all(activeSlots.map(s => s.slot.readBuffer.mapAsync(GPUMapMode.READ))); + + // Write directly from GPU mapped memory → ImageData → offscreen canvas + let rendered = 0; + for (const s of activeSlots) { + const mapped = s.slot.readBuffer.getMappedRange(); + const imgData = imgDatas[s.k]!; + imgData.data.set(new Uint8ClampedArray(mapped)); + s.slot.readBuffer.unmap(); + offscreens[s.k]!.getContext("2d")!.putImageData(imgData, 0, 0); + rendered++; + } + return rendered; + } + + private ensureBlitPipeline(format: GPUTextureFormat): void { + if (this.blitPipeline) return; + const module = this.device.createShaderModule({ code: BLIT_SHADER }); + this.blitPipeline = this.device.createRenderPipeline({ + layout: "auto", + vertex: { module, entryPoint: "vs" }, + fragment: { + module, entryPoint: "fs", + targets: [{ format }], + }, + primitive: { topology: "triangle-list" }, + }); + } + + /** + * Zero-copy GPU render: compute colormap + blit directly to WebGPU canvas textures. + * No mapAsync, no CPU copy, no putImageData. Target: <16ms for 60fps. + * + * Each canvas must have a 'webgpu' context (not '2d'). Call configureCanvas() first. + * Returns the number of images rendered. + */ + renderSlotsZeroCopy( + indices: number[], + ranges: { vmin: number; vmax: number }[], + contexts: (GPUCanvasContext | null)[], + logScale: boolean = false, + ): number { + if (!this.pipeline || !this.lutBuffer || indices.length === 0) return 0; + + // Get texture format from first valid context + const fmt = navigator.gpu.getPreferredCanvasFormat(); + this.ensureBlitPipeline(fmt); + if (!this.blitPipeline) return 0; + + const encoder = this.device.createCommandEncoder(); + const params = new ArrayBuffer(24); + let rendered = 0; + + for (let k = 0; k < indices.length; k++) { + const i = indices[k]; + const slot = this.slots[i]; + const ctx = contexts[k]; + if (!slot || !ctx) continue; + const range = ranges[k] || { vmin: 0, vmax: 1 }; + + // 1. Compute colormap (same as renderSlots) + this._writeParams(params, slot.width, slot.height, range.vmin, range.vmax, logScale); + this.device.queue.writeBuffer(slot.paramsBuffer, 0, params); + + const computeGroup = this.device.createBindGroup({ + layout: this.pipeline.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: { buffer: slot.paramsBuffer } }, + { binding: 1, resource: { buffer: slot.dataBuffer } }, + { binding: 2, resource: { buffer: this.lutBuffer } }, + { binding: 3, resource: { buffer: slot.rgbaBuffer } }, + ], + }); + const computePass = encoder.beginComputePass(); + computePass.setPipeline(this.pipeline); + computePass.setBindGroup(0, computeGroup); + computePass.dispatchWorkgroups(Math.ceil(slot.width / 16), Math.ceil(slot.height / 16)); + computePass.end(); + + // 2. Blit RGBA buffer → canvas texture (zero-copy render pass) + const blitParamsBuffer = this.device.createBuffer({ + size: 8, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, + }); + this.device.queue.writeBuffer(blitParamsBuffer, 0, new Uint32Array([slot.width, slot.height])); + + const blitGroup = this.device.createBindGroup({ + layout: this.blitPipeline.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: { buffer: blitParamsBuffer } }, + { binding: 1, resource: { buffer: slot.rgbaBuffer } }, + ], + }); + + const texture = ctx.getCurrentTexture(); + const renderPass = encoder.beginRenderPass({ + colorAttachments: [{ + view: texture.createView(), + loadOp: "clear" as GPULoadOp, + storeOp: "store" as GPUStoreOp, + clearValue: { r: 0, g: 0, b: 0, a: 1 }, + }], + }); + renderPass.setPipeline(this.blitPipeline); + renderPass.setBindGroup(0, blitGroup); + renderPass.draw(3); // fullscreen triangle + renderPass.end(); + rendered++; + + // Note: blitParamsBuffer is a temporary — ideally per-slot persistent + // For now, acceptable overhead (8 bytes per image) + } + + this.device.queue.submit([encoder.finish()]); + if (rendered > 0) { + } + return rendered; + } + + /** + * GPU colormap → OffscreenCanvas → ImageBitmap (zero mapAsync). + * Compute shader writes RGBA, render pass blits to OffscreenCanvas texture, + * transferToImageBitmap() returns ImageBitmap for drawImage on 2D canvas. + * Eliminates the 35ms JS memcpy for 12×4K images. + */ + renderSlotsToImageBitmap( + indices: number[], + ranges: { vmin: number; vmax: number }[], + logScale: boolean = false, + ): ImageBitmap[] | null { + if (!this.pipeline || !this.lutBuffer || indices.length === 0) return null; + const fmt = navigator.gpu.getPreferredCanvasFormat(); + this.ensureBlitPipeline(fmt); + if (!this.blitPipeline) return null; + + const encoder = this.device.createCommandEncoder(); + const params = new ArrayBuffer(24); + const canvases: OffscreenCanvas[] = []; + + for (let k = 0; k < indices.length; k++) { + const i = indices[k]; + const slot = this.slots[i]; + if (!slot) { canvases.push(null as never); continue; } + const range = ranges[k] || { vmin: 0, vmax: 1 }; + + // Compute colormap + this._writeParams(params, slot.width, slot.height, range.vmin, range.vmax, logScale); + this.device.queue.writeBuffer(slot.paramsBuffer, 0, params); + + const computeGroup = this.device.createBindGroup({ + layout: this.pipeline.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: { buffer: slot.paramsBuffer } }, + { binding: 1, resource: { buffer: slot.dataBuffer } }, + { binding: 2, resource: { buffer: this.lutBuffer } }, + { binding: 3, resource: { buffer: slot.rgbaBuffer } }, + ], + }); + const computePass = encoder.beginComputePass(); + computePass.setPipeline(this.pipeline); + computePass.setBindGroup(0, computeGroup); + computePass.dispatchWorkgroups(Math.ceil(slot.width / 16), Math.ceil(slot.height / 16)); + computePass.end(); + + // Blit to OffscreenCanvas + const oc = new OffscreenCanvas(slot.width, slot.height); + const ctx = oc.getContext("webgpu") as GPUCanvasContext; + ctx.configure({ device: this.device, format: fmt, alphaMode: "opaque" }); + + const blitParamsBuffer = this.device.createBuffer({ + size: 8, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, + }); + this.device.queue.writeBuffer(blitParamsBuffer, 0, new Uint32Array([slot.width, slot.height])); + + const blitGroup = this.device.createBindGroup({ + layout: this.blitPipeline!.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: { buffer: blitParamsBuffer } }, + { binding: 1, resource: { buffer: slot.rgbaBuffer } }, + ], + }); + + const texture = ctx.getCurrentTexture(); + const renderPass = encoder.beginRenderPass({ + colorAttachments: [{ + view: texture.createView(), + loadOp: "clear" as GPULoadOp, + storeOp: "store" as GPUStoreOp, + clearValue: { r: 0, g: 0, b: 0, a: 1 }, + }], + }); + renderPass.setPipeline(this.blitPipeline!); + renderPass.setBindGroup(0, blitGroup); + renderPass.draw(3); + renderPass.end(); + canvases.push(oc); + } + + this.device.queue.submit([encoder.finish()]); + + // transferToImageBitmap after GPU finishes (synchronous, no mapAsync) + const bitmaps: ImageBitmap[] = []; + for (const oc of canvases) { + if (oc) bitmaps.push(oc.transferToImageBitmap()); + else bitmaps.push(null as never); + } + return bitmaps; + } + + /** + * Configure a canvas for WebGPU zero-copy rendering. + * Returns the GPUCanvasContext, or null if WebGPU canvas is not supported. + */ + configureCanvas(canvas: HTMLCanvasElement, width: number, height: number): GPUCanvasContext | null { + try { + const ctx = canvas.getContext("webgpu") as GPUCanvasContext | null; + if (!ctx) return null; + ctx.configure({ + device: this.device, + format: navigator.gpu.getPreferredCanvasFormat(), + alphaMode: "opaque", + }); + canvas.width = width; + canvas.height = height; + return ctx; + } catch { + return null; + } + } + + /** Release all GPU resources. */ + destroy(): void { + for (const slot of this.slots) { + if (slot) { + slot.dataBuffer.destroy(); + slot.rgbaBuffer.destroy(); + slot.readBuffer.destroy(); + slot.paramsBuffer.destroy(); + slot.histBinsBuffer.destroy(); + slot.histReadBuffer.destroy(); + } + } + this.slots = []; + this.lutBuffer?.destroy(); + this.lutBuffer = null; + this.currentLutName = ""; + } + + /** Number of uploaded image slots. */ + get slotCount(): number { return this.slots.filter(s => s).length; } + + // ── GPU min/max reduction ── + + private rangePipeline: GPUComputePipeline | null = null; + private RANGE_WG_SIZE = 256; + + private ensureRangePipeline(): void { + if (this.rangePipeline) return; + // Two-pass parallel reduction: each workgroup reduces a chunk to one min/max pair. + // Output: array of [min, max] pairs (one per workgroup). JS reduces the partials. + const code = /* wgsl */ ` +@group(0) @binding(0) var data: array; +@group(0) @binding(1) var out: array; +@group(0) @binding(2) var count: u32; + +var sMin: array; +var sMax: array; + +@compute @workgroup_size(256) +fn reduce(@builtin(global_invocation_id) gid: vec3u, @builtin(local_invocation_id) lid: vec3u, @builtin(workgroup_id) wid: vec3u) { + let i = gid.x; + if (i < count) { + sMin[lid.x] = data[i]; + sMax[lid.x] = data[i]; + } else { + sMin[lid.x] = 3.4028235e+38; + sMax[lid.x] = -3.4028235e+38; + } + workgroupBarrier(); + + // Tree reduction in shared memory + for (var s = 128u; s > 0u; s >>= 1u) { + if (lid.x < s) { + sMin[lid.x] = min(sMin[lid.x], sMin[lid.x + s]); + sMax[lid.x] = max(sMax[lid.x], sMax[lid.x + s]); + } + workgroupBarrier(); + } + + if (lid.x == 0u) { + out[wid.x * 2u] = sMin[0]; + out[wid.x * 2u + 1u] = sMax[0]; + } +} +`; + const module = this.device.createShaderModule({ code }); + this.rangePipeline = this.device.createComputePipeline({ + layout: "auto", + compute: { module, entryPoint: "reduce" }, + }); + } + + /** + * Batch-compute min/max for multiple slots on GPU. + * Returns { min, max } per slot. One GPU submission for all slots. + */ + async computeRangeBatch(indices: number[]): Promise<{ min: number; max: number }[]> { + this.ensureRangePipeline(); + if (!this.rangePipeline || indices.length === 0) return []; + const WG = this.RANGE_WG_SIZE; + + const encoder = this.device.createCommandEncoder(); + const jobs: { idx: number; nGroups: number; outBuf: GPUBuffer; readBuf: GPUBuffer; countBuf: GPUBuffer }[] = []; + + for (const i of indices) { + const slot = this.slots[i]; + if (!slot) continue; + const N = slot.count; + const nGroups = Math.ceil(N / WG); + const outSize = nGroups * 2 * 4; // 2 floats (min, max) per workgroup + const outBuf = this.device.createBuffer({ size: outSize, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC }); + const readBuf = this.device.createBuffer({ size: outSize, usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST }); + const countBuf = this.device.createBuffer({ size: 4, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST }); + this.device.queue.writeBuffer(countBuf, 0, new Uint32Array([N])); + + const bg = this.device.createBindGroup({ + layout: this.rangePipeline.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: { buffer: slot.dataBuffer } }, + { binding: 1, resource: { buffer: outBuf } }, + { binding: 2, resource: { buffer: countBuf } }, + ], + }); + const pass = encoder.beginComputePass(); + pass.setPipeline(this.rangePipeline); + pass.setBindGroup(0, bg); + pass.dispatchWorkgroups(nGroups); + pass.end(); + encoder.copyBufferToBuffer(outBuf, 0, readBuf, 0, outSize); + jobs.push({ idx: i, nGroups, outBuf, readBuf, countBuf }); + } + + this.device.queue.submit([encoder.finish()]); + await Promise.all(jobs.map(j => j.readBuf.mapAsync(GPUMapMode.READ))); + + const results: { min: number; max: number }[] = []; + for (const j of jobs) { + const partials = new Float32Array(j.readBuf.getMappedRange().slice(0)); + j.readBuf.unmap(); + j.outBuf.destroy(); j.readBuf.destroy(); j.countBuf.destroy(); + // JS reduces partials: ~65K elements for 16M data = trivial + let dmin = Infinity, dmax = -Infinity; + for (let k = 0; k < j.nGroups; k++) { + if (partials[k * 2] < dmin) dmin = partials[k * 2]; + if (partials[k * 2 + 1] > dmax) dmax = partials[k * 2 + 1]; + } + results.push({ min: dmin, max: dmax }); + } + return results; + } + + // ── GPU histogram ── + + private histPipeline: GPUComputePipeline | null = null; + private histClearPipeline: GPUComputePipeline | null = null; + + private ensureHistPipeline(): void { + if (this.histPipeline) return; + const code = /* wgsl */ ` +struct HistParams { + width: u32, + height: u32, + dmin: f32, + dmax: f32, + log_scale: u32, + _pad: u32, +}; +@group(0) @binding(0) var params: HistParams; +@group(0) @binding(1) var data: array; +@group(0) @binding(2) var bins: array>; + +@compute @workgroup_size(16, 16) +fn histogram(@builtin(global_invocation_id) gid: vec3u) { + if (gid.x >= params.width || gid.y >= params.height) { return; } + let idx = gid.y * params.width + gid.x; + var val = data[idx]; + if (params.log_scale == 1u) { val = log(1.0 + max(val, 0.0)); } + let range = max(params.dmax - params.dmin, 1e-30); + let t = clamp((val - params.dmin) / range, 0.0, 1.0); + let bin = min(u32(t * 256.0), 255u); + atomicAdd(&bins[bin], 1u); +} + +@compute @workgroup_size(256) +fn clear_bins(@builtin(global_invocation_id) gid: vec3u) { + if (gid.x < 256u) { atomicStore(&bins[gid.x], 0u); } +} +`; + const module = this.device.createShaderModule({ code }); + this.histPipeline = this.device.createComputePipeline({ + layout: "auto", + compute: { module, entryPoint: "histogram" }, + }); + this.histClearPipeline = this.device.createComputePipeline({ + layout: "auto", + compute: { module, entryPoint: "clear_bins" }, + }); + } + + /** + * Compute a 256-bin histogram for slot `idx` on GPU. + * Returns normalized bins (0–1) matching `computeHistogramFromBytes`. + */ + async computeHistogram(idx: number, _logScale: boolean = false): Promise { + this.ensureHistPipeline(); + const slot = this.slots[idx]; + if (!slot || !this.histPipeline || !this.histClearPipeline) return new Array(256).fill(0); + + // Find data range (we need min/max for binning) + // For GPU efficiency, do a quick CPU scan — findDataRange is fast (<5ms for 16M) + // A full GPU min/max reduction would add complexity for minimal gain here. + // Note: when logScale is true, we need the log-transformed range. + + const binsBuffer = this.device.createBuffer({ + size: 256 * 4, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + }); + const readBuffer = this.device.createBuffer({ + size: 256 * 4, + usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST, + }); + const paramsBuf = this.device.createBuffer({ + size: 16, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, + }); + + // We need min/max from the (possibly log-transformed) data for proper binning. + // Pass raw min/max = 0; the shader will use the actual data range. + // Actually, we need to know the range to bin correctly. Read it back from + // the data we already uploaded. For now, accept min/max as parameters. + // The caller (Show2D data effect) already computes findDataRange. + // So let's accept dmin/dmax as params. + + // This method needs dmin/dmax — return a version that takes them: + binsBuffer.destroy(); + readBuffer.destroy(); + paramsBuf.destroy(); + return new Array(256).fill(0); + } + + /** + * Batch-compute 256-bin histograms for multiple slots in ONE GPU submission. + * Uses persistent per-slot histogram buffers (zero create/destroy overhead). + * Returns normalized bins per image. + */ + async computeHistogramBatch( + indices: number[], + ranges: { min: number; max: number }[], + logScale: boolean = false, + ): Promise { + this.ensureHistPipeline(); + if (!this.histPipeline || !this.histClearPipeline || indices.length === 0) return []; + + const encoder = this.device.createCommandEncoder(); + const activeSlots: { k: number; slot: GPUSlot }[] = []; + const params = new ArrayBuffer(24); + + for (let k = 0; k < indices.length; k++) { + const i = indices[k]; + const slot = this.slots[i]; + if (!slot) continue; + const r = ranges[k] || { min: 0, max: 1 }; + if (r.min === r.max) continue; + + // Reuse persistent paramsBuffer for histogram (same layout as colormap params) + const pu = new Uint32Array(params); + const pf = new Float32Array(params); + pu[0] = slot.width; pu[1] = slot.height; + pf[2] = r.min; pf[3] = r.max; + pu[4] = logScale ? 1 : 0; pu[5] = 0; + this.device.queue.writeBuffer(slot.paramsBuffer, 0, params); + + // Clear bins (persistent buffer) + const clearGroup = this.device.createBindGroup({ + layout: this.histClearPipeline!.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: { buffer: slot.paramsBuffer } }, + { binding: 1, resource: { buffer: slot.dataBuffer } }, + { binding: 2, resource: { buffer: slot.histBinsBuffer } }, + ], + }); + const clearPass = encoder.beginComputePass(); + clearPass.setPipeline(this.histClearPipeline!); + clearPass.setBindGroup(0, clearGroup); + clearPass.dispatchWorkgroups(1); + clearPass.end(); + + // Histogram (persistent buffer) + const histGroup = this.device.createBindGroup({ + layout: this.histPipeline!.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: { buffer: slot.paramsBuffer } }, + { binding: 1, resource: { buffer: slot.dataBuffer } }, + { binding: 2, resource: { buffer: slot.histBinsBuffer } }, + ], + }); + const histPass = encoder.beginComputePass(); + histPass.setPipeline(this.histPipeline!); + histPass.setBindGroup(0, histGroup); + histPass.dispatchWorkgroups(Math.ceil(slot.width / 16), Math.ceil(slot.height / 16)); + histPass.end(); + + encoder.copyBufferToBuffer(slot.histBinsBuffer, 0, slot.histReadBuffer, 0, 256 * 4); + activeSlots.push({ k, slot }); + } + + this.device.queue.submit([encoder.finish()]); + await Promise.all(activeSlots.map(s => s.slot.histReadBuffer.mapAsync(GPUMapMode.READ))); + + const results: number[][] = []; + for (const s of activeSlots) { + const rawBins = new Uint32Array(s.slot.histReadBuffer.getMappedRange().slice(0)); + s.slot.histReadBuffer.unmap(); + + let maxCount = 0; + for (let j = 0; j < 256; j++) if (rawBins[j] > maxCount) maxCount = rawBins[j]; + const norm = new Array(256); + for (let j = 0; j < 256; j++) norm[j] = maxCount > 0 ? rawBins[j] / maxCount : 0; + results.push(norm); + } + return results; + } + + /** + * Compute a 256-bin histogram for slot `idx` on GPU, given known data range. + * Returns normalized bins (0–1) matching `computeHistogramFromBytes`. + */ + async computeHistogramWithRange( + idx: number, dmin: number, dmax: number, logScale: boolean = false, + ): Promise { + this.ensureHistPipeline(); + const slot = this.slots[idx]; + if (!slot || !this.histPipeline || !this.histClearPipeline) return new Array(256).fill(0); + if (dmin === dmax) return new Array(256).fill(0); + + const binsBuffer = this.device.createBuffer({ + size: 256 * 4, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC, + }); + const readBuffer = this.device.createBuffer({ + size: 256 * 4, + usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST, + }); + const paramsBuf = this.device.createBuffer({ + size: 24, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, + }); + + const params = new ArrayBuffer(24); + const pu = new Uint32Array(params); + const pf = new Float32Array(params); + pu[0] = slot.width; pu[1] = slot.height; + pf[2] = dmin; pf[3] = dmax; + pu[4] = logScale ? 1 : 0; pu[5] = 0; + this.device.queue.writeBuffer(paramsBuf, 0, params); + + const encoder = this.device.createCommandEncoder(); + + // Clear bins + const clearGroup = this.device.createBindGroup({ + layout: this.histClearPipeline.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: { buffer: paramsBuf } }, + { binding: 1, resource: { buffer: slot.dataBuffer } }, + { binding: 2, resource: { buffer: binsBuffer } }, + ], + }); + const clearPass = encoder.beginComputePass(); + clearPass.setPipeline(this.histClearPipeline); + clearPass.setBindGroup(0, clearGroup); + clearPass.dispatchWorkgroups(1); + clearPass.end(); + + // Histogram + const histGroup = this.device.createBindGroup({ + layout: this.histPipeline.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: { buffer: paramsBuf } }, + { binding: 1, resource: { buffer: slot.dataBuffer } }, + { binding: 2, resource: { buffer: binsBuffer } }, + ], + }); + const histPass = encoder.beginComputePass(); + histPass.setPipeline(this.histPipeline); + histPass.setBindGroup(0, histGroup); + histPass.dispatchWorkgroups(Math.ceil(slot.width / 16), Math.ceil(slot.height / 16)); + histPass.end(); + + encoder.copyBufferToBuffer(binsBuffer, 0, readBuffer, 0, 256 * 4); + this.device.queue.submit([encoder.finish()]); + + await readBuffer.mapAsync(GPUMapMode.READ); + const rawBins = new Uint32Array(readBuffer.getMappedRange().slice(0)); + readBuffer.unmap(); + binsBuffer.destroy(); + readBuffer.destroy(); + paramsBuf.destroy(); + + // Normalize (match CPU: divide by max count) + let maxCount = 0; + for (let i = 0; i < 256; i++) if (rawBins[i] > maxCount) maxCount = rawBins[i]; + const result = new Array(256); + if (maxCount > 0) { + for (let i = 0; i < 256; i++) result[i] = rawBins[i] / maxCount; + } else { + for (let i = 0; i < 256; i++) result[i] = 0; + } + return result; + } +} + +let gpuColormapEngine: GPUColormapEngine | null = null; + +/** Get or create the singleton GPU colormap engine. Returns null if WebGPU unavailable. */ +export async function getGPUColormapEngine(): Promise { + if (gpuColormapEngine) return gpuColormapEngine; + // Reuse the GPU device from webgpu-fft + try { + const { getGPUDevice } = await import("./webgpu-fft"); + const device = await getGPUDevice(); + if (!device) return null; + gpuColormapEngine = new GPUColormapEngine(device); + return gpuColormapEngine; + } catch { + return null; + } +} + +/** Query the GPU's max buffer size in bytes. Returns 0 if WebGPU unavailable. */ +export async function getGPUMaxBufferSize(): Promise { + try { + if (!navigator.gpu) return 0; + const adapter = await navigator.gpu.requestAdapter(); + if (!adapter) return 0; + return adapter.limits.maxStorageBufferBindingSize || adapter.limits.maxBufferSize || 0; + } catch { + return 0; + } +} diff --git a/widget/js/control-customizer.tsx b/widget/js/control-customizer.tsx new file mode 100644 index 00000000..3ca9602d --- /dev/null +++ b/widget/js/control-customizer.tsx @@ -0,0 +1,174 @@ +import * as React from "react"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import Switch from "@mui/material/Switch"; +import Tooltip from "@mui/material/Tooltip"; +import Divider from "@mui/material/Divider"; +import IconButton from "@mui/material/IconButton"; +import Button from "@mui/material/Button"; +import Menu from "@mui/material/Menu"; +import TuneIcon from "@mui/icons-material/Tune"; + +import { + addToolGroup, + compactToolLabel, + computeToolVisibility, + getControlPresetIds, + getControlPresetLabel, + getWidgetToolGroups, + removeToolGroup, + resolvePresetHiddenTools, +} from "./tool-parity"; + +type ToolSetter = React.Dispatch>; + +type ThemeColors = { + controlBg: string; + text: string; + border: string; + textMuted?: string; + accent?: string; +}; + +type ControlCustomizerProps = { + widgetName: string; + hiddenTools: string[]; + setHiddenTools: ToolSetter; + disabledTools: string[]; + setDisabledTools: ToolSetter; + themeColors: ThemeColors; + labelOverrides?: Record; +}; + +const switchStyles = { + small: { + "& .MuiSwitch-thumb": { width: 12, height: 12 }, + "& .MuiSwitch-switchBase": { padding: "4px" }, + }, +}; + +const presetButton = { + fontSize: 10, + py: 0.25, + px: 1, + minWidth: 0, +}; + +export function ControlCustomizer({ + widgetName, + hiddenTools, + setHiddenTools, + disabledTools, + setDisabledTools, + themeColors, + labelOverrides, +}: ControlCustomizerProps) { + const [anchor, setAnchor] = React.useState(null); + const groups = React.useMemo( + () => getWidgetToolGroups(widgetName).filter((group) => group !== "all"), + [widgetName], + ); + const visibility = React.useMemo( + () => computeToolVisibility(widgetName, disabledTools, hiddenTools), + [widgetName, disabledTools, hiddenTools], + ); + + const setGroupVisible = React.useCallback((group: string, visible: boolean) => { + setHiddenTools((prev) => { + if (visible) return removeToolGroup(widgetName, prev, group); + return addToolGroup(widgetName, prev, group); + }); + }, [setHiddenTools, widgetName]); + + const setGroupLocked = React.useCallback((group: string, locked: boolean) => { + setDisabledTools((prev) => { + if (locked) return addToolGroup(widgetName, prev, group); + return removeToolGroup(widgetName, prev, group); + }); + }, [setDisabledTools, widgetName]); + + const applyPreset = React.useCallback((presetId: string) => { + setHiddenTools(resolvePresetHiddenTools(widgetName, presetId)); + }, [setHiddenTools, widgetName]); + + return ( + <> + + setAnchor(e.currentTarget)} + sx={{ p: 0.25, ml: 0.5, color: themeColors.text }} + > + + + + setAnchor(null)} + anchorOrigin={{ vertical: "bottom", horizontal: "right" }} + transformOrigin={{ vertical: "top", horizontal: "right" }} + PaperProps={{ + sx: { + bgcolor: themeColors.controlBg, + color: themeColors.text, + border: `1px solid ${themeColors.border}`, + p: 0.5, + minWidth: 280, + }, + }} + > + + Presets + + {getControlPresetIds().map((presetId) => ( + + ))} + + + + + Per-group + {groups.map((group) => { + const label = labelOverrides?.[group] ?? compactToolLabel(group); + const hidden = visibility.isHidden(group); + const locked = visibility.isLocked(group); + return ( + + {label} + + Show + setGroupVisible(group, e.target.checked)} + inputProps={{ "aria-label": `show-${group}` }} + sx={switchStyles.small} + /> + Lock + setGroupLocked(group, e.target.checked)} + inputProps={{ "aria-label": `lock-${group}` }} + sx={switchStyles.small} + disabled={hidden} + /> + + + ); + })} + + + + ); +} diff --git a/widget/js/format.ts b/widget/js/format.ts new file mode 100644 index 00000000..31f2c4ca --- /dev/null +++ b/widget/js/format.ts @@ -0,0 +1,40 @@ +/** Convert anywidget DataView/ArrayBuffer to Uint8Array. */ +export function extractBytes(dataView: DataView | ArrayBuffer | Uint8Array): Uint8Array { + if (dataView instanceof Uint8Array) return dataView; + if (dataView instanceof ArrayBuffer) return new Uint8Array(dataView); + if (dataView && "buffer" in dataView) { + return new Uint8Array(dataView.buffer, dataView.byteOffset, dataView.byteLength); + } + return new Uint8Array(0); +} + +/** Extract Float32Array from anywidget DataView. Returns null if empty. */ +export function extractFloat32(dataView: DataView | ArrayBuffer | Uint8Array): Float32Array | null { + const bytes = extractBytes(dataView); + if (bytes.length === 0) return null; + return new Float32Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 4); +} + +/** Download a Blob as a file. */ +export function downloadBlob(blob: Blob, filename: string): void { + const link = document.createElement("a"); + link.download = filename; + const url = URL.createObjectURL(blob); + link.href = url; + link.click(); + // Defer revocation to ensure browser has time to start the download + setTimeout(() => URL.revokeObjectURL(url), 60000); +} + +/** Download a DataView as a file (e.g. GIF/ZIP from Python). */ +export function downloadDataView(dataView: DataView, filename: string, mimeType: string): void { + const buf = new Uint8Array(dataView.buffer as ArrayBuffer, dataView.byteOffset, dataView.byteLength); + downloadBlob(new Blob([buf as BlobPart], { type: mimeType }), filename); +} + +/** Format number with exponential notation for large/small values. */ +export function formatNumber(val: number, decimals: number = 2): string { + if (val === 0) return "0"; + if (Math.abs(val) >= 1000 || Math.abs(val) < 0.01) return val.toExponential(decimals); + return val.toFixed(decimals); +} diff --git a/widget/js/histogram.ts b/widget/js/histogram.ts new file mode 100644 index 00000000..c2f96a61 --- /dev/null +++ b/widget/js/histogram.ts @@ -0,0 +1,19 @@ +/** Compute normalized histogram bins from Float32Array. Returns array of 0-1 values. */ +export function computeHistogramFromBytes(data: Float32Array | null, numBins = 256): number[] { + if (!data || data.length === 0) return new Array(numBins).fill(0); + const bins = new Array(numBins).fill(0); + let min = Infinity, max = -Infinity; + for (let i = 0; i < data.length; i++) { + const v = data[i]; + if (isFinite(v)) { if (v < min) min = v; if (v > max) max = v; } + } + if (!isFinite(min) || !isFinite(max) || min === max) return bins; + const range = max - min; + for (let i = 0; i < data.length; i++) { + const v = data[i]; + if (isFinite(v)) bins[Math.min(numBins - 1, Math.floor(((v - min) / range) * numBins))]++; + } + const maxCount = Math.max(...bins); + if (maxCount > 0) for (let i = 0; i < numBins; i++) bins[i] /= maxCount; + return bins; +} diff --git a/widget/js/index.jsx b/widget/js/index.jsx deleted file mode 100644 index a3341f63..00000000 --- a/widget/js/index.jsx +++ /dev/null @@ -1,33 +0,0 @@ -import * as React from "react"; -import * as ReactDOM from "react-dom/client"; - -function Widget({ model }) { - const [count, setCount] = React.useState(model.get("count")); - - React.useEffect(() => { - const onChange = () => setCount(model.get("count")); - model.on("change:count", onChange); - return () => model.off("change:count", onChange); - }, [model]); - - const handleClick = () => { - model.set("count", count + 1); - model.save_changes(); - }; - - return ( -
-

quantem.widget

-

Count: {count}

- -
- ); -} - -function render({ model, el }) { - const root = ReactDOM.createRoot(el); - root.render(); - return () => root.unmount(); -} - -export default { render }; diff --git a/widget/js/scalebar.ts b/widget/js/scalebar.ts new file mode 100644 index 00000000..041b4754 --- /dev/null +++ b/widget/js/scalebar.ts @@ -0,0 +1,444 @@ +/** + * Shared scale bar, colorbar, and overlay utilities for all canvas-based widgets. + * Provides HiDPI-aware rendering with automatic unit conversion. + */ + +import { formatNumber } from "./format"; + +export type ScaleUnit = "Å" | "mrad" | "px" | "Å⁻¹"; + +/** Round a physical value to a "nice" number (1, 2, 5, 10, 20, 50, ...) */ +export function roundToNiceValue(value: number): number { + if (value <= 0) return 1; + const magnitude = Math.pow(10, Math.floor(Math.log10(value))); + const normalized = value / magnitude; + if (normalized < 1.5) return magnitude; + if (normalized < 3.5) return 2 * magnitude; + if (normalized < 7.5) return 5 * magnitude; + return 10 * magnitude; +} + +/** Format scale bar label with appropriate unit and auto-conversion (Å→nm, mrad→rad, Å⁻¹→nm⁻¹) */ +export function formatScaleLabel(value: number, unit: ScaleUnit): string { + const nice = roundToNiceValue(value); + if (unit === "Å") { + if (nice >= 10) return `${Math.round(nice / 10)} nm`; + return nice >= 1 ? `${Math.round(nice)} Å` : `${nice.toFixed(2)} Å`; + } + if (unit === "Å⁻¹") { + // 10 Å⁻¹ = 1 nm⁻¹ + if (nice >= 10) return `${Math.round(nice / 10)} nm⁻¹`; + return nice >= 1 ? `${Math.round(nice)} Å⁻¹` : `${nice.toFixed(2)} Å⁻¹`; + } + if (unit === "px") { + return nice >= 1 ? `${Math.round(nice)} px` : `${nice.toFixed(1)} px`; + } + if (nice >= 1000) return `${Math.round(nice / 1000)} rad`; + return nice >= 1 ? `${Math.round(nice)} mrad` : `${nice.toFixed(2)} mrad`; +} + +const FONT = "-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif"; + +/** + * Draw scale bar and zoom indicator on a high-DPI UI canvas. + * Renders crisp text/lines independent of the image resolution. + */ +export function drawScaleBarHiDPI( + canvas: HTMLCanvasElement, + dpr: number, + zoom: number, + pixelSize: number, + unit: "Å" | "mrad" | "px", + imageWidth: number, +) { + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.save(); + ctx.scale(dpr, dpr); + + const cssWidth = canvas.width / dpr; + const cssHeight = canvas.height / dpr; + const scaleX = cssWidth / imageWidth; + const effectiveZoom = zoom * scaleX; + + const targetBarPx = 60; + const barThickness = 5; + const fontSize = 16; + const margin = 12; + + const targetPhysical = (targetBarPx / effectiveZoom) * pixelSize; + const nicePhysical = roundToNiceValue(targetPhysical); + const barPx = (nicePhysical / pixelSize) * effectiveZoom; + + const barY = cssHeight - margin; + const barX = cssWidth - barPx - margin; + + ctx.shadowColor = "rgba(0, 0, 0, 0.5)"; + ctx.shadowBlur = 2; + ctx.shadowOffsetX = 1; + ctx.shadowOffsetY = 1; + + ctx.fillStyle = "white"; + ctx.fillRect(barX, barY, barPx, barThickness); + + const label = formatScaleLabel(nicePhysical, unit); + ctx.font = `${fontSize}px ${FONT}`; + ctx.fillStyle = "white"; + ctx.textAlign = "center"; + ctx.textBaseline = "bottom"; + ctx.fillText(label, barX + barPx / 2, barY - 4); + + ctx.textAlign = "left"; + ctx.textBaseline = "bottom"; + ctx.fillText(`${zoom.toFixed(1)}×`, margin, cssHeight - margin + barThickness); + + ctx.restore(); +} + +/** + * Draw reciprocal-space scale bar on an FFT overlay canvas. + * Only draws when fftPixelSize > 0 (i.e. real-space calibration is available). + */ +export function drawFFTScaleBarHiDPI( + canvas: HTMLCanvasElement, + dpr: number, + fftZoom: number, + fftPixelSize: number, + imageWidth: number, +) { + const ctx = canvas.getContext("2d"); + if (!ctx || fftPixelSize <= 0) return; + + ctx.save(); + ctx.scale(dpr, dpr); + + const cssWidth = canvas.width / dpr; + const cssHeight = canvas.height / dpr; + const scaleX = cssWidth / imageWidth; + const effectiveZoom = fftZoom * scaleX; + + const targetBarPx = 60; + const barThickness = 5; + const fontSize = 16; + const margin = 12; + + const targetPhysical = (targetBarPx / effectiveZoom) * fftPixelSize; + const nicePhysical = roundToNiceValue(targetPhysical); + const barPx = (nicePhysical / fftPixelSize) * effectiveZoom; + + const barY = cssHeight - margin; + const barX = cssWidth - barPx - margin; + + ctx.shadowColor = "rgba(0, 0, 0, 0.5)"; + ctx.shadowBlur = 2; + ctx.shadowOffsetX = 1; + ctx.shadowOffsetY = 1; + + ctx.fillStyle = "white"; + ctx.fillRect(barX, barY, barPx, barThickness); + + const label = formatScaleLabel(nicePhysical, "Å⁻¹"); + ctx.font = `${fontSize}px ${FONT}`; + ctx.fillStyle = "white"; + ctx.textAlign = "center"; + ctx.textBaseline = "bottom"; + ctx.fillText(label, barX + barPx / 2, barY - 4); + + ctx.textAlign = "left"; + ctx.textBaseline = "bottom"; + ctx.fillText(`${fftZoom.toFixed(1)}×`, margin, cssHeight - margin + barThickness); + + ctx.restore(); +} + +/** + * Draw a vertical colorbar on a canvas context (already DPR-scaled by caller). + * Gradient strip on right edge with vmin/vmax labels and optional log indicator. + */ +export function drawColorbar( + ctx: CanvasRenderingContext2D, + cssW: number, + cssH: number, + lut: Uint8Array, + vmin: number, + vmax: number, + logScale: boolean, +) { + const barW = 12; + const barH = Math.round(cssH * 0.6); + const barX = cssW - barW - 12; + const barY = Math.round((cssH - barH) / 2); + + // Gradient strip (bottom=vmin, top=vmax) + for (let row = 0; row < barH; row++) { + const t = 1 - row / (barH - 1); + const lutIdx = Math.round(t * 255); + const r = lut[lutIdx * 3]; + const g = lut[lutIdx * 3 + 1]; + const b = lut[lutIdx * 3 + 2]; + ctx.fillStyle = `rgb(${r},${g},${b})`; + ctx.fillRect(barX, barY + row, barW, 1); + } + + // Border + ctx.strokeStyle = "rgba(255,255,255,0.5)"; + ctx.lineWidth = 1; + ctx.strokeRect(barX, barY, barW, barH); + + // Labels with drop shadow + ctx.shadowColor = "rgba(0, 0, 0, 0.7)"; + ctx.shadowBlur = 2; + ctx.shadowOffsetX = 1; + ctx.shadowOffsetY = 1; + ctx.font = `11px ${FONT}`; + ctx.fillStyle = "white"; + ctx.textAlign = "right"; + ctx.textBaseline = "bottom"; + ctx.fillText(formatNumber(vmax), barX - 4, barY + 6); + ctx.textBaseline = "top"; + ctx.fillText(formatNumber(vmin), barX - 4, barY + barH - 4); + if (logScale) { + ctx.textBaseline = "middle"; + ctx.fillText("log", barX - 4, barY + barH / 2); + } +} + +// ============================================================================ +// Publication-quality figure export +// ============================================================================ + +export interface ExportFigureOptions { + /** Colormapped image canvas at native resolution (no zoom/pan). */ + imageCanvas: HTMLCanvasElement; + /** Figure title drawn above the image. */ + title?: string; + /** Colormap LUT (256 × 3 bytes) for the colorbar. */ + lut?: Uint8Array; + /** Data range for colorbar labels. */ + vmin?: number; + vmax?: number; + logScale?: boolean; + /** Pixel size in Å (for scale bar computation). */ + pixelSize?: number; + showColorbar?: boolean; + showScaleBar?: boolean; + /** Upscale factor for high-resolution output (default 4). Image pixels use nearest-neighbor for sharp edges. */ + scale?: number; + /** Callback to draw annotations (ROI, profile, markers) on the image. ctx is pre-translated to image origin and scaled. */ + drawAnnotations?: (ctx: CanvasRenderingContext2D) => void; +} + +/** + * Create a publication-quality figure canvas with title, scale bar, colorbar, + * and baked-in annotations. Returns an HTMLCanvasElement — caller can toBlob() + download. + */ +export function exportFigure(options: ExportFigureOptions): HTMLCanvasElement { + const { + imageCanvas, + title, + lut, + vmin = 0, + vmax = 1, + logScale = false, + pixelSize = 0, + showColorbar = true, + showScaleBar = true, + scale: s = 4, + drawAnnotations, + } = options; + + const imgW = imageCanvas.width; + const imgH = imageCanvas.height; + + // Layout (in logical coordinates — scaled to canvas pixels by ctx.scale) + const pad = 20; + const titleH = title ? 28 : 0; + const titleGap = title ? 8 : 0; + const hasCb = showColorbar && lut && vmin !== vmax; + const cbWidth = hasCb ? 20 : 0; + const cbGap = hasCb ? 12 : 0; + const cbLabelW = hasCb ? 60 : 0; + + const totalW = pad + imgW + cbGap + cbWidth + cbLabelW + pad; + const totalH = pad + titleH + titleGap + imgH + pad; + + const canvas = document.createElement("canvas"); + canvas.width = totalW * s; + canvas.height = totalH * s; + const ctx = canvas.getContext("2d")!; + + // Scale all drawing operations + ctx.scale(s, s); + + // White background + ctx.fillStyle = "white"; + ctx.fillRect(0, 0, totalW, totalH); + + // Title + if (title) { + ctx.fillStyle = "black"; + ctx.font = `bold 18px ${FONT}`; + ctx.textAlign = "left"; + ctx.textBaseline = "top"; + ctx.fillText(title, pad, pad); + } + + const imgX = pad; + const imgY = pad + titleH + titleGap; + + // Image (nearest-neighbor for sharp pixels) + ctx.imageSmoothingEnabled = false; + ctx.drawImage(imageCanvas, imgX, imgY, imgW, imgH); + ctx.imageSmoothingEnabled = true; + + // Annotations + if (drawAnnotations) { + ctx.save(); + ctx.translate(imgX, imgY); + drawAnnotations(ctx); + ctx.restore(); + } + + // Scale bar (white with drop shadow, positioned at bottom-right of image) + if (showScaleBar && pixelSize > 0) { + const targetBarPx = Math.max(60, imgW * 0.15); + const barThickness = Math.max(4, Math.round(imgH * 0.012)); + const fontSize = Math.max(14, Math.round(imgH * 0.04)); + const margin = Math.max(12, Math.round(imgW * 0.03)); + + const targetPhysical = targetBarPx * pixelSize; + const nicePhysical = roundToNiceValue(targetPhysical); + const barPx = nicePhysical / pixelSize; + + const barY = imgY + imgH - margin; + const barX = imgX + imgW - barPx - margin; + + ctx.shadowColor = "rgba(0, 0, 0, 0.5)"; + ctx.shadowBlur = 2; + ctx.shadowOffsetX = 1; + ctx.shadowOffsetY = 1; + + ctx.fillStyle = "white"; + ctx.fillRect(barX, barY, barPx, barThickness); + + const label = formatScaleLabel(nicePhysical, "Å"); + ctx.font = `bold ${fontSize}px ${FONT}`; + ctx.fillStyle = "white"; + ctx.textAlign = "center"; + ctx.textBaseline = "bottom"; + ctx.fillText(label, barX + barPx / 2, barY - 4); + + ctx.shadowColor = "transparent"; + ctx.shadowBlur = 0; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 0; + } + + // Colorbar (vertical gradient strip to the right of image) + if (hasCb && lut) { + const cbX = imgX + imgW + cbGap; + const cbY = imgY; + const cbH = imgH; + + for (let row = 0; row < cbH; row++) { + const t = 1 - row / (cbH - 1); + const lutIdx = Math.round(t * 255); + const r = lut[lutIdx * 3]; + const g = lut[lutIdx * 3 + 1]; + const b = lut[lutIdx * 3 + 2]; + ctx.fillStyle = `rgb(${r},${g},${b})`; + ctx.fillRect(cbX, cbY + row, cbWidth, 1); + } + + ctx.strokeStyle = "black"; + ctx.lineWidth = 1; + ctx.strokeRect(cbX, cbY, cbWidth, cbH); + + ctx.fillStyle = "black"; + ctx.font = `12px ${FONT}`; + ctx.textAlign = "left"; + ctx.textBaseline = "top"; + ctx.fillText(formatNumber(vmax), cbX + cbWidth + 4, cbY); + ctx.textBaseline = "bottom"; + ctx.fillText(formatNumber(vmin), cbX + cbWidth + 4, cbY + cbH); + if (logScale) { + ctx.textBaseline = "middle"; + ctx.fillText("log", cbX + cbWidth + 4, cbY + cbH / 2); + } + } + + return canvas; +} + +/** + * Convert a canvas to a PDF blob by embedding JPEG data in a minimal PDF. + * Zero external dependencies — uses the DCTDecode filter (native JPEG in PDF). + */ +export async function canvasToPDF(canvas: HTMLCanvasElement, quality = 0.95): Promise { + const jpegBlob = await new Promise((resolve) => + canvas.toBlob((b) => resolve(b!), "image/jpeg", quality)); + const jpegBytes = new Uint8Array(await jpegBlob.arrayBuffer()); + const w = canvas.width; + const h = canvas.height; + + // Build PDF objects + const contentStream = `q ${w} 0 0 ${h} 0 0 cm /I0 Do Q`; + const objects: string[] = []; + const offsets: number[] = []; + + // Helper to track object positions + let pdf = "%PDF-1.4\n"; + + // Object 1: Catalog + offsets.push(pdf.length); + objects.push("1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n"); + pdf += objects[0]; + + // Object 2: Pages + offsets.push(pdf.length); + objects.push("2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n"); + pdf += objects[1]; + + // Object 3: Page + offsets.push(pdf.length); + objects.push(`3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 ${w} ${h}] /Contents 4 0 R /Resources << /XObject << /I0 5 0 R >> >> >>\nendobj\n`); + pdf += objects[2]; + + // Object 4: Content stream + offsets.push(pdf.length); + objects.push(`4 0 obj\n<< /Length ${contentStream.length} >>\nstream\n${contentStream}\nendstream\nendobj\n`); + pdf += objects[3]; + + // Object 5: Image (JPEG) — build as binary + const imgHeader = `5 0 obj\n<< /Type /XObject /Subtype /Image /Width ${w} /Height ${h} /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length ${jpegBytes.length} >>\nstream\n`; + const imgFooter = "\nendstream\nendobj\n"; + + // Convert text part to bytes + const encoder = new TextEncoder(); + const headerBytes = encoder.encode(pdf + imgHeader); + const footerBytes = encoder.encode(imgFooter); + + // Build xref + const imgOffset = pdf.length; + offsets.push(imgOffset); + const afterImage = headerBytes.length + jpegBytes.length + footerBytes.length; + + const xrefOffset = afterImage; + let xref = `xref\n0 6\n0000000000 65535 f \n`; + for (let i = 0; i < offsets.length; i++) { + xref += `${String(offsets[i]).padStart(10, "0")} 00000 n \n`; + } + xref += `trailer\n<< /Size 6 /Root 1 0 R >>\nstartxref\n${xrefOffset}\n%%EOF\n`; + const xrefBytes = encoder.encode(xref); + + // Combine all parts + const result = new Uint8Array(headerBytes.length + jpegBytes.length + footerBytes.length + xrefBytes.length); + result.set(headerBytes, 0); + result.set(jpegBytes, headerBytes.length); + result.set(footerBytes, headerBytes.length + jpegBytes.length); + result.set(xrefBytes, headerBytes.length + jpegBytes.length + footerBytes.length); + + return new Blob([result], { type: "application/pdf" }); +} diff --git a/widget/js/show2d/index.tsx b/widget/js/show2d/index.tsx new file mode 100644 index 00000000..aacf5c43 --- /dev/null +++ b/widget/js/show2d/index.tsx @@ -0,0 +1,4185 @@ +/** + * Show2D - Static 2D image viewer with gallery support. + * + * Features: + * - Single image or gallery mode with configurable columns + * - Scroll to zoom, double-click to reset + * - WebGPU-accelerated FFT with default 3x zoom + * - Equal-sized FFT and histogram panels + * - Click to select image in gallery mode + */ + +import * as React from "react"; +import { createRender, useModelState } from "@anywidget/react"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import Stack from "@mui/material/Stack"; +import Select from "@mui/material/Select"; +import MenuItem from "@mui/material/MenuItem"; +import Menu from "@mui/material/Menu"; +import Switch from "@mui/material/Switch"; +import Slider from "@mui/material/Slider"; +import Button from "@mui/material/Button"; +import Tooltip from "@mui/material/Tooltip"; +import { useTheme } from "../theme"; +import { drawScaleBarHiDPI, drawFFTScaleBarHiDPI, drawColorbar, roundToNiceValue, exportFigure, canvasToPDF } from "../scalebar"; +import JSZip from "jszip"; +import { extractFloat32, formatNumber, downloadBlob } from "../format"; +import { computeHistogramFromBytes } from "../histogram"; +import { findDataRange, applyLogScale, percentileClip, sliderRange, computeStats } from "../stats"; +import { ControlCustomizer } from "../control-customizer"; +import { computeToolVisibility } from "../tool-parity"; + +function InfoTooltip({ text, theme = "dark" }: { text: React.ReactNode; theme?: "light" | "dark" }) { + const isDark = theme === "dark"; + const content = typeof text === "string" + ? {text} + : text; + return ( + + + + ); +} + +function KeyboardShortcuts({ items }: { items: [string, string][] }) { + return ( + + + {items.map(([key, desc], i) => ( + {key}{desc} + ))} + + + ); +} + +const upwardMenuProps = { + anchorOrigin: { vertical: "top" as const, horizontal: "left" as const }, + transformOrigin: { vertical: "bottom" as const, horizontal: "left" as const }, + sx: { zIndex: 9999 }, +}; +import { getWebGPUFFT, WebGPUFFT, fft2d, fft2dAsync, fftshift, computeMagnitude, autoEnhanceFFT, nextPow2, applyHannWindow2D, getGPUInfo } from "../webgpu-fft"; +import { COLORMAPS, COLORMAP_NAMES, renderToOffscreen, renderToOffscreenReuse, GPUColormapEngine, getGPUColormapEngine, getGPUMaxBufferSize } from "../colormaps"; +import "./show2d.css"; + +const MIN_ZOOM = 0.5; +const MAX_ZOOM = 20; + +const DPR = window.devicePixelRatio || 1; + +interface HistogramProps { + data: Float32Array | null; + precomputedBins?: number[] | null; // GPU-computed bins bypass computeHistogramFromBytes + vminPct: number; + vmaxPct: number; + onRangeChange: (min: number, max: number) => void; + width?: number; + height?: number; + theme?: "light" | "dark"; + dataMin?: number; + dataMax?: number; +} + +function Histogram({ data, precomputedBins, vminPct, vmaxPct, onRangeChange, width = 110, height = 40, theme = "dark", dataMin = 0, dataMax = 1 }: HistogramProps) { + const canvasRef = React.useRef(null); + const cpuBins = React.useMemo(() => precomputedBins ? null : computeHistogramFromBytes(data), [data, precomputedBins]); + const bins = precomputedBins || cpuBins || new Array(256).fill(0); + const isDark = theme === "dark"; + const colors = isDark ? { bg: "#1a1a1a", barActive: "#888", barInactive: "#444", border: "#333" } : { bg: "#f0f0f0", barActive: "#666", barInactive: "#bbb", border: "#ccc" }; + + React.useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return; + const ctx = canvas.getContext("2d"); + if (!ctx) return; + const dpr = window.devicePixelRatio || 1; + canvas.width = width * dpr; + canvas.height = height * dpr; + ctx.scale(dpr, dpr); + ctx.fillStyle = colors.bg; + ctx.fillRect(0, 0, width, height); + const displayBins = 64; + const binRatio = Math.floor(bins.length / displayBins); + const reducedBins: number[] = []; + for (let i = 0; i < displayBins; i++) { + let sum = 0; + for (let j = 0; j < binRatio; j++) sum += bins[i * binRatio + j] || 0; + reducedBins.push(sum / binRatio); + } + const maxVal = Math.max(...reducedBins, 0.001); + const barWidth = width / displayBins; + const vminBin = Math.floor((vminPct / 100) * displayBins); + const vmaxBin = Math.floor((vmaxPct / 100) * displayBins); + for (let i = 0; i < displayBins; i++) { + const barHeight = (reducedBins[i] / maxVal) * (height - 2); + ctx.fillStyle = (i >= vminBin && i <= vmaxBin) ? colors.barActive : colors.barInactive; + ctx.fillRect(i * barWidth + 0.5, height - barHeight, Math.max(1, barWidth - 1), barHeight); + } + }, [bins, vminPct, vmaxPct, width, height, colors]); + + return ( + + + { const [newMin, newMax] = v as number[]; onRangeChange(Math.min(newMin, newMax - 1), Math.max(newMax, newMin + 1)); }} + min={0} max={100} size="small" valueLabelDisplay="auto" + valueLabelFormat={(pct) => { const val = dataMin + (pct / 100) * (dataMax - dataMin); return val >= 1000 ? val.toExponential(1) : val.toFixed(1); }} + sx={{ width, py: 0, "& .MuiSlider-thumb": { width: 8, height: 8 }, "& .MuiSlider-rail": { height: 2 }, "& .MuiSlider-track": { height: 2 }, "& .MuiSlider-valueLabel": { fontSize: 10, padding: "2px 4px" } }} + /> + {(() => { const v = dataMin + (vminPct / 100) * (dataMax - dataMin); return v >= 1000 ? v.toExponential(1) : v.toFixed(1); })()}{(() => { const v = dataMin + (vmaxPct / 100) * (dataMax - dataMin); return v >= 1000 ? v.toExponential(1) : v.toFixed(1); })()} + + ); +} + +// ============================================================================ +// Line profile sampling (bilinear interpolation along line) +// ============================================================================ +function sampleLineProfile(data: Float32Array, w: number, h: number, row0: number, col0: number, row1: number, col1: number): Float32Array { + const dc = col1 - col0; + const dr = row1 - row0; + const len = Math.sqrt(dc * dc + dr * dr); + const n = Math.max(2, Math.ceil(len)); + const out = new Float32Array(n); + for (let i = 0; i < n; i++) { + const t = i / (n - 1); + const c = col0 + t * dc; + const r = row0 + t * dr; + const ci = Math.floor(c), ri = Math.floor(r); + const cf = c - ci, rf = r - ri; + const c0c = Math.max(0, Math.min(w - 1, ci)); + const c1c = Math.max(0, Math.min(w - 1, ci + 1)); + const r0c = Math.max(0, Math.min(h - 1, ri)); + const r1c = Math.max(0, Math.min(h - 1, ri + 1)); + out[i] = data[r0c * w + c0c] * (1 - cf) * (1 - rf) + + data[r0c * w + c1c] * cf * (1 - rf) + + data[r1c * w + c0c] * (1 - cf) * rf + + data[r1c * w + c1c] * cf * rf; + } + return out; +} + +function pointToSegmentDistance(col: number, row: number, col0: number, row0: number, col1: number, row1: number): number { + const dc = col1 - col0; + const dr = row1 - row0; + const lenSq = dc * dc + dr * dr; + if (lenSq <= 1e-12) return Math.sqrt((col - col0) ** 2 + (row - row0) ** 2); + const tRaw = ((col - col0) * dc + (row - row0) * dr) / lenSq; + const t = Math.max(0, Math.min(1, tRaw)); + const projCol = col0 + t * dc; + const projRow = row0 + t * dr; + return Math.sqrt((col - projCol) ** 2 + (row - projRow) ** 2); +} + +// ============================================================================ +// FFT peak finder (snap to Bragg spot with sub-pixel centroid refinement) +// ============================================================================ +function findFFTPeak(mag: Float32Array, width: number, height: number, col: number, row: number, radius: number): { row: number; col: number } { + // Find brightest pixel in search window + const c0 = Math.max(0, Math.floor(col) - radius); + const r0 = Math.max(0, Math.floor(row) - radius); + const c1 = Math.min(width - 1, Math.floor(col) + radius); + const r1 = Math.min(height - 1, Math.floor(row) + radius); + let bestCol = Math.round(col), bestRow = Math.round(row), bestVal = -Infinity; + for (let ir = r0; ir <= r1; ir++) { + for (let ic = c0; ic <= c1; ic++) { + const val = mag[ir * width + ic]; + if (val > bestVal) { bestVal = val; bestCol = ic; bestRow = ir; } + } + } + // Sub-pixel refinement via weighted centroid in 3×3 window + const wc0 = Math.max(0, bestCol - 1), wc1 = Math.min(width - 1, bestCol + 1); + const wr0 = Math.max(0, bestRow - 1), wr1 = Math.min(height - 1, bestRow + 1); + let sumW = 0, sumWC = 0, sumWR = 0; + for (let ir = wr0; ir <= wr1; ir++) { + for (let ic = wc0; ic <= wc1; ic++) { + const w = mag[ir * width + ic]; + sumW += w; sumWC += w * ic; sumWR += w * ir; + } + } + if (sumW > 0) return { row: sumWR / sumW, col: sumWC / sumW }; + return { row: bestRow, col: bestCol }; +} + +const FFT_SNAP_RADIUS = 5; + +// ============================================================================ +// Types +// ============================================================================ +type ZoomState = { zoom: number; panX: number; panY: number }; + +// ============================================================================ +// Constants +// ============================================================================ +const SINGLE_IMAGE_TARGET = 500; +const GALLERY_IMAGE_TARGET = 300; +const DEFAULT_FFT_ZOOM = 3; +const PROFILE_COLORS = ["#4fc3f7", "#81c784", "#ffb74d", "#ce93d8", "#ef5350", "#ffd54f", "#90a4ae", "#a1887f"]; +type ROIItem = { row: number; col: number; shape: string; radius: number; radius_inner: number; width: number; height: number; color: string; line_width: number; highlight: boolean }; +const ROI_COLORS = ["#4fc3f7", "#81c784", "#ffb74d", "#ce93d8", "#ef5350", "#ffd54f", "#90a4ae", "#a1887f"]; +const RESIZE_HIT_AREA_PX = 10; + +function drawROI( + ctx: CanvasRenderingContext2D, + x: number, y: number, + shape: "circle" | "square" | "rectangle" | "annular", + radius: number, w: number, h: number, + activeColor: string, inactiveColor: string, + active: boolean = false, innerRadius: number = 0 +): void { + const strokeColor = active ? activeColor : inactiveColor; + ctx.strokeStyle = strokeColor; + if (shape === "circle") { + ctx.beginPath(); ctx.arc(x, y, radius, 0, Math.PI * 2); ctx.stroke(); + } else if (shape === "square") { + ctx.strokeRect(x - radius, y - radius, radius * 2, radius * 2); + } else if (shape === "rectangle") { + ctx.strokeRect(x - w / 2, y - h / 2, w, h); + } else if (shape === "annular") { + ctx.beginPath(); ctx.arc(x, y, radius, 0, Math.PI * 2); ctx.stroke(); + ctx.strokeStyle = active ? "#0ff" : inactiveColor; + ctx.beginPath(); ctx.arc(x, y, innerRadius, 0, Math.PI * 2); ctx.stroke(); + ctx.fillStyle = (active ? activeColor : inactiveColor) + "15"; + ctx.beginPath(); ctx.arc(x, y, radius, 0, Math.PI * 2); ctx.arc(x, y, innerRadius, 0, Math.PI * 2, true); ctx.fill(); + ctx.strokeStyle = strokeColor; + } + if (active) { + ctx.beginPath(); + ctx.moveTo(x - 5, y); ctx.lineTo(x + 5, y); + ctx.moveTo(x, y - 5); ctx.lineTo(x, y + 5); + ctx.stroke(); + } +} + +// ============================================================================ +// Crop ROI region from raw float32 data for ROI-scoped FFT +// ============================================================================ +function cropROIRegion( + data: Float32Array, imgW: number, imgH: number, + roi: ROIItem, +): { cropped: Float32Array; cropW: number; cropH: number } | null { + const shape = roi.shape || "circle"; + let x0: number, y0: number, x1: number, y1: number; + + if (shape === "rectangle") { + const hw = roi.width / 2; + const hh = roi.height / 2; + x0 = Math.max(0, Math.floor(roi.col - hw)); + y0 = Math.max(0, Math.floor(roi.row - hh)); + x1 = Math.min(imgW, Math.ceil(roi.col + hw)); + y1 = Math.min(imgH, Math.ceil(roi.row + hh)); + } else { + const r = roi.radius; + x0 = Math.max(0, Math.floor(roi.col - r)); + y0 = Math.max(0, Math.floor(roi.row - r)); + x1 = Math.min(imgW, Math.ceil(roi.col + r)); + y1 = Math.min(imgH, Math.ceil(roi.row + r)); + } + + const cropW = x1 - x0; + const cropH = y1 - y0; + if (cropW < 2 || cropH < 2) return null; + + const cropped = new Float32Array(cropW * cropH); + + if (shape === "circle" || shape === "annular") { + const r = roi.radius; + const rSq = r * r; + for (let dy = 0; dy < cropH; dy++) { + for (let dx = 0; dx < cropW; dx++) { + const imgX = x0 + dx; + const imgY = y0 + dy; + const distSq = (imgX - roi.col) * (imgX - roi.col) + (imgY - roi.row) * (imgY - roi.row); + cropped[dy * cropW + dx] = distSq <= rSq ? data[imgY * imgW + imgX] : 0; + } + } + } else { + for (let dy = 0; dy < cropH; dy++) { + const srcOffset = (y0 + dy) * imgW + x0; + cropped.set(data.subarray(srcOffset, srcOffset + cropW), dy * cropW); + } + } + + return { cropped, cropW, cropH }; +} + +// ============================================================================ +// Main Component +// ============================================================================ +// Show4DSTEM-style UI constants +const typography = { + label: { fontSize: 11 }, + labelSmall: { fontSize: 10 }, + value: { fontSize: 10, fontFamily: "monospace" }, +}; +const SPACING = { XS: 4, SM: 8, MD: 12, LG: 16 }; +const controlRow = { + display: "flex", + alignItems: "center", + gap: `${SPACING.SM}px`, + px: 1, + py: 0.5, + width: "fit-content", +}; +const compactButton = { + fontSize: 10, + py: 0.25, + px: 1, + minWidth: 0, + "&.Mui-disabled": { + color: "#666", + borderColor: "#444", + }, +}; +const switchStyles = { + small: { "& .MuiSwitch-thumb": { width: 12, height: 12 }, "& .MuiSwitch-switchBase": { padding: "4px" } }, +}; +const sliderStyles = { + small: { py: 0, "& .MuiSlider-thumb": { width: 10, height: 10 }, "& .MuiSlider-rail": { height: 2 }, "& .MuiSlider-track": { height: 2 } }, +}; + +function Show2D() { + // Theme + const { themeInfo, colors: tc } = useTheme(); + const themeColors = { + ...tc, + accentGreen: themeInfo.theme === "dark" ? "#0f0" : "#1a7a1a", + }; + + const themedSelect = { + fontSize: 10, + bgcolor: themeColors.controlBg, + color: themeColors.text, + "& .MuiSelect-select": { py: 0.5 }, + "& .MuiOutlinedInput-notchedOutline": { borderColor: themeColors.border }, + "&:hover .MuiOutlinedInput-notchedOutline": { borderColor: themeColors.accent }, + }; + + const themedMenuProps = { + ...upwardMenuProps, + PaperProps: { sx: { bgcolor: themeColors.controlBg, color: themeColors.text, border: `1px solid ${themeColors.border}` } }, + }; + + // Model state + const [nImages] = useModelState("n_images"); + const [width] = useModelState("width"); + const [height] = useModelState("height"); + const [frameBytes] = useModelState("frame_bytes"); + const [labels] = useModelState("labels"); + const [title] = useModelState("title"); + const [displayBinFactor] = useModelState("_display_bin_factor"); + const [, setGpuMaxBufferMB] = useModelState("_gpu_max_buffer_mb"); + const [widgetVersion] = useModelState("widget_version"); + const [cmap, setCmap] = useModelState("cmap"); + const [ncols] = useModelState("ncols"); + + // Display options + const [logScale, setLogScale] = useModelState("log_scale"); + const [autoContrast, setAutoContrast] = useModelState("auto_contrast"); + const [traitVmin] = useModelState("vmin"); + const [traitVmax] = useModelState("vmax"); + const [traitVmins] = useModelState<(number | null)[] | null>("vmins"); + const [traitVmaxs] = useModelState<(number | null)[] | null>("vmaxs"); + const [zoomRowTrait] = useModelState("zoom_row"); + const [zoomColTrait] = useModelState("zoom_col"); + const [diffMode, setDiffMode] = useModelState("diff_mode"); + const [diffReference] = useModelState("diff_reference"); + // Align removed — diff = A − B (no shift). Drift correction happens upstream. + const alignDy = 0; + const alignDx = 0; + + // Customization + const [canvasSizeTrait] = useModelState("size"); + const [smooth, setSmooth] = useModelState("smooth"); + const imageRenderingStyle = smooth ? "auto" : "pixelated"; + + // Scale bar + const [pixelSize] = useModelState("pixel_size"); + const [scaleBarVisible] = useModelState("scale_bar_visible"); + + // UI visibility + const [showControls] = useModelState("show_controls"); + const [showStats] = useModelState("show_stats"); + const [disabledTools, setDisabledTools] = useModelState("disabled_tools"); + const [hiddenTools, setHiddenTools] = useModelState("hidden_tools"); + const [statsMean] = useModelState("stats_mean"); + const [statsMin] = useModelState("stats_min"); + const [statsMax] = useModelState("stats_max"); + const [statsStd] = useModelState("stats_std"); + + // Analysis Panels (FFT + Histogram) + const [showFft, setShowFft] = useModelState("show_fft"); + const [fftWindow, setFftWindow] = useModelState("fft_window"); + + // Selection + const [selectedIdx, setSelectedIdx] = useModelState("selected_idx"); + + // ROI + const [roiActive, setRoiActive] = useModelState("roi_active"); + const [roiList, setRoiList] = useModelState("roi_list"); + const [roiSelectedIdx, setRoiSelectedIdx] = useModelState("roi_selected_idx"); + const [imageRotations, setImageRotations] = useModelState("image_rotations"); + const [isDraggingROI, setIsDraggingROI] = React.useState(false); + const [isDraggingResize, setIsDraggingResize] = React.useState(false); + const [isDraggingResizeInner, setIsDraggingResizeInner] = React.useState(false); + const [isHoveringResize, setIsHoveringResize] = React.useState(false); + const [isHoveringResizeInner, setIsHoveringResizeInner] = React.useState(false); + const resizeAspectRef = React.useRef(null); + const [newRoiShape, setNewRoiShape] = React.useState<"circle" | "square" | "rectangle" | "annular">("square"); + const [exportAnchor, setExportAnchor] = React.useState(null); + const selectedRoi = roiSelectedIdx >= 0 && roiSelectedIdx < (roiList?.length ?? 0) ? roiList[roiSelectedIdx] : null; + + const toolVisibility = React.useMemo( + () => computeToolVisibility("Show2D", disabledTools, hiddenTools), + [disabledTools, hiddenTools], + ); + const hideDisplay = toolVisibility.isHidden("display"); + const hideHistogram = toolVisibility.isHidden("histogram"); + const hideStats = toolVisibility.isHidden("stats"); + const hideView = toolVisibility.isHidden("view"); + const hideExport = toolVisibility.isHidden("export"); + const hideRoi = toolVisibility.isHidden("roi"); + const hideProfile = toolVisibility.isHidden("profile"); + + const lockDisplay = toolVisibility.isLocked("display"); + const lockHistogram = toolVisibility.isLocked("histogram"); + const lockStats = toolVisibility.isLocked("stats"); + const lockNavigation = toolVisibility.isLocked("navigation"); + const lockView = toolVisibility.isLocked("view"); + const lockExport = toolVisibility.isLocked("export"); + const lockRoi = toolVisibility.isLocked("roi"); + const lockProfile = toolVisibility.isLocked("profile"); + const effectiveShowFft = showFft && !hideDisplay; + + const updateSelectedRoi = (updates: Partial) => { + if (lockRoi) return; + if (roiSelectedIdx < 0 || !roiList) return; + const newList = [...roiList]; + newList[roiSelectedIdx] = { ...newList[roiSelectedIdx], ...updates }; + setRoiList(newList); + }; + + React.useEffect(() => { + if (hideRoi && roiActive) { + setRoiActive(false); + setRoiSelectedIdx(-1); + } + }, [hideRoi, roiActive, setRoiActive, setRoiSelectedIdx]); + + // Canvas refs + const canvasRefs = React.useRef<(HTMLCanvasElement | null)[]>([]); + const overlayRefs = React.useRef<(HTMLCanvasElement | null)[]>([]); + const imageContainerRefs = React.useRef<(HTMLDivElement | null)[]>([]); + const fftContainerRefs = React.useRef<(HTMLDivElement | null)[]>([]); + const singleFftContainerRef = React.useRef(null); + const fftCanvasRef = React.useRef(null); + const [canvasReady, setCanvasReady] = React.useState(0); // Trigger re-render when refs attached + + // Zoom/Pan state - per-image when not linked, shared when linked + const [initialZoom] = useModelState("initial_zoom"); + const [linkPan, setLinkPan] = useModelState("link_pan"); + const [imgHeight] = useModelState("height"); + const [imgWidth] = useModelState("width"); + // Note: pan derived from zoom_row/zoom_col is applied via a useEffect AFTER canvasW/canvasH + // are computed (see "Initial pan from zoom_row/zoom_col" effect below). + const initialZoomState: ZoomState = React.useMemo( + () => ({ zoom: Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, initialZoom || 1)), panX: 0, panY: 0 }), + [initialZoom] + ); + void linkPan; void setLinkPan; void imgWidth; void imgHeight; + const [zoomStates, setZoomStates] = React.useState>(new Map()); + const [linkedZoomState, setLinkedZoomState] = React.useState(initialZoomState); + const [linkedZoom, setLinkedZoom] = useModelState("link_zoom"); + const [isDraggingPan, setIsDraggingPan] = React.useState(false); + const [panStart, setPanStart] = React.useState<{ x: number, y: number, pX: number, pY: number } | null>(null); + + // Helper to get zoom state for an image. zoom and pan link independently: + // zoom from linkedZoomState if linkedZoom else per-image + // pan from linkedZoomState if linkPan else per-image + const getZoomState = React.useCallback((idx: number): ZoomState => { + const per = zoomStates.get(idx) || initialZoomState; + return { + zoom: linkedZoom ? linkedZoomState.zoom : per.zoom, + panX: linkPan ? linkedZoomState.panX : per.panX, + panY: linkPan ? linkedZoomState.panY : per.panY, + }; + }, [linkedZoom, linkPan, linkedZoomState, zoomStates, initialZoomState]); + + // Helper to set zoom state for an image. zoom and pan honored independently: + // zoom: writes to linkedZoomState if linkedZoom, else per-image + // pan: writes to linkedZoomState if linkPan, else per-image + const setZoomState = React.useCallback((idx: number, state: ZoomState) => { + if (linkedZoom || linkPan) { + setLinkedZoomState(prev => ({ + zoom: linkedZoom ? state.zoom : prev.zoom, + panX: linkPan ? state.panX : prev.panX, + panY: linkPan ? state.panY : prev.panY, + })); + } + if (!linkedZoom || !linkPan) { + setZoomStates(prev => { + const m = new Map(prev); + const cur = m.get(idx) || initialZoomState; + m.set(idx, { + zoom: linkedZoom ? cur.zoom : state.zoom, + panX: linkPan ? cur.panX : state.panX, + panY: linkPan ? cur.panY : state.panY, + }); + return m; + }); + } + }, [linkedZoom, linkPan, initialZoomState]); + + // FFT zoom/pan state (single mode) + const [fftZoom, setFftZoom] = React.useState(DEFAULT_FFT_ZOOM); + const [fftPanX, setFftPanX] = React.useState(0); + const [fftPanY, setFftPanY] = React.useState(0); + const [isDraggingFftPan, setIsDraggingFftPan] = React.useState(false); + const [fftPanStart, setFftPanStart] = React.useState<{ x: number, y: number, pX: number, pY: number } | null>(null); + + // Histogram state — per-image contrast ranges (gallery) or single (one image) + const [linkedContrast, setLinkedContrast] = useModelState("link_contrast"); + const [linkedContrastState, setLinkedContrastState] = React.useState<{ vminPct: number; vmaxPct: number }>({ vminPct: 0, vmaxPct: 100 }); + const [contrastStates, setContrastStates] = React.useState>(new Map()); + // Ref mirror for fast slider path (bypass React effect batching) + const contrastRef = React.useRef<{ linked: { vminPct: number; vmaxPct: number }; perImage: Map }>({ linked: { vminPct: 0, vmaxPct: 100 }, perImage: new Map() }); + const sliderRafRef = React.useRef(0); + const getContrastState = React.useCallback((idx: number) => { + if (linkedContrast) return linkedContrastState; + return contrastStates.get(idx) || { vminPct: 0, vmaxPct: 100 }; + }, [linkedContrast, linkedContrastState, contrastStates]); + const setContrastState = React.useCallback((idx: number, state: { vminPct: number; vmaxPct: number }) => { + // Update ref immediately (for fast rAF render) + if (linkedContrast) { + contrastRef.current.linked = state; + setLinkedContrastState(state); + } else { + contrastRef.current.perImage.set(idx, state); + setContrastStates(prev => new Map(prev).set(idx, state)); + } + // Fast path: direct GPU render via rAF, bypassing React effect batching + const engine = gpuCmapRef.current; + if (engine && gpuCmapReadyRef.current && engine.slotCount >= nImages) { + cancelAnimationFrame(sliderRafRef.current); + sliderRafRef.current = requestAnimationFrame(() => { + const cachedRanges = dataRangesRef.current; + if (cachedRanges.length === 0) return; + const lut = COLORMAPS[cmapRef.current] || COLORMAPS.inferno; + engine.uploadLUT(cmapRef.current, lut); + const indices = Array.from({ length: nImages }, (_, i) => i); + const ranges: { vmin: number; vmax: number }[] = []; + for (let i = 0; i < nImages; i++) { + const cs = linkedContrast ? contrastRef.current.linked : (contrastRef.current.perImage.get(i) || { vminPct: 0, vmaxPct: 100 }); + let cr = cachedRanges[i]; + if (!cr || cr.min === cr.max) { + if (rawDataRef.current && rawDataRef.current[i]) cr = findDataRange(rawDataRef.current[i]); + } + cr = cr || { min: 0, max: 1 }; + if (cs.vminPct > 0 || cs.vmaxPct < 100) { + ranges.push(sliderRange(cr.min, cr.max, cs.vminPct, cs.vmaxPct)); + } else { + ranges.push({ vmin: cr.min, vmax: cr.max }); + } + } + const ls = logScaleRef.current ?? false; + const bitmaps = engine.renderSlotsToImageBitmap(indices, ranges, ls); + if (bitmaps && bitmaps[0]) { + for (let i = 0; i < bitmaps.length; i++) { + const offscreen = mainOffscreensRef.current[i]; + if (offscreen && bitmaps[i]) offscreen.getContext("2d")?.drawImage(bitmaps[i], 0, 0); + } + setOffscreenVersion(v => v + 1); + } + }); + } + }, [linkedContrast, nImages]); + // Convenience accessors for active image + const activeContrastIdx = nImages > 1 ? selectedIdx : 0; + const imageVminPct = getContrastState(activeContrastIdx).vminPct; + const imageVmaxPct = getContrastState(activeContrastIdx).vmaxPct; + + const [imageHistogramData, setImageHistogramData] = React.useState(null); + const [imageHistogramBins, setImageHistogramBins] = React.useState(null); + const [imageDataRange, setImageDataRange] = React.useState<{ min: number; max: number }>({ min: 0, max: 1 }); + + // FFT display state (single mode) + const [fftVminPct, setFftVminPct] = React.useState(0); + const [fftVmaxPct, setFftVmaxPct] = React.useState(100); + const [fftHistogramData, setFftHistogramData] = React.useState(null); + const [fftDataRange, setFftDataRange] = React.useState<{ min: number; max: number }>({ min: 0, max: 1 }); + const [fftColormap, setFftColormap] = React.useState("inferno"); + const [fftScaleMode, setFftScaleMode] = React.useState<"linear" | "log" | "power">("linear"); + const [fftAuto, setFftAuto] = React.useState(true); + const [fftStats, setFftStats] = React.useState(null); + const [fftShowColorbar, setFftShowColorbar] = React.useState(false); + + // FFT loading state — shown as a pulsing overlay while FFT computes + const [fftComputing, setFftComputing] = React.useState(false); + const [fftProgress, setFftProgress] = React.useState(""); + + // Cursor readout state + const [cursorInfo, setCursorInfo] = React.useState<{ row: number; col: number; value: number } | null>(null); + + // Colorbar state (single image mode only) + const [showColorbar, setShowColorbar] = React.useState(false); + + // Inset magnifier state + const [showLens, setShowLens] = React.useState(false); + const [lensPos, setLensPos] = React.useState<{ row: number; col: number } | null>(null); + const [lensMag, setLensMag] = React.useState(4); // magnification 2×–8× + const [lensDisplaySize, setLensDisplaySize] = React.useState(128); // CSS px 64–256 + const [lensAnchor, setLensAnchor] = React.useState<{ x: number; y: number } | null>(null); // custom position (CSS px from top-left of canvas) + const [isDraggingLens, setIsDraggingLens] = React.useState(false); + const [isResizingLens, setIsResizingLens] = React.useState(false); + const [isHoveringLensEdge, setIsHoveringLensEdge] = React.useState(false); + const lensDragStartRef = React.useRef<{ mx: number; my: number; ax: number; ay: number } | null>(null); + const lensResizeStartRef = React.useRef<{ my: number; startSize: number } | null>(null); + const lensCanvasRef = React.useRef(null); + + // FFT d-spacing measurement + const [fftClickInfo, setFftClickInfo] = React.useState<{ + row: number; col: number; distPx: number; + spatialFreq: number | null; dSpacing: number | null; + } | null>(null); + const fftClickStartRef = React.useRef<{ x: number; y: number } | null>(null); + const fftOverlayRef = React.useRef(null); + + // Line profile state + const [profileActive, setProfileActive] = React.useState(false); + const [profileLine, setProfileLine] = useModelState<{ row: number; col: number }[]>("profile_line"); + const [profileDataAll, setProfileDataAll] = React.useState<(Float32Array | null)[]>([]); + React.useEffect(() => { + if (hideProfile && profileActive) { + setProfileActive(false); + } + }, [hideProfile, profileActive]); + const profileCanvasRef = React.useRef(null); + const profileBaseImageRef = React.useRef(null); + const profileLayoutRef = React.useRef<{ padLeft: number; plotW: number; padTop: number; plotH: number; gMin: number; gMax: number; totalDist: number; xUnit: string } | null>(null); + + // Sync profile points from model state + const profilePoints = profileLine || []; + const setProfilePoints = (pts: { row: number; col: number }[]) => setProfileLine(pts); + + // Distance measurement state (JS-only, not persisted) + const [measureActive, setMeasureActive] = React.useState(false); + const [measurePoints, setMeasurePoints] = React.useState<{row: number; col: number}[]>([]); + + // FFT zoom/pan state (gallery mode — per-image or linked) + const [galleryFftStates, setGalleryFftStates] = React.useState>(new Map()); + const [linkedFftZoomState, setLinkedFftZoomState] = React.useState({ zoom: DEFAULT_FFT_ZOOM, panX: 0, panY: 0 }); + const [fftPanningIdx, setFftPanningIdx] = React.useState(null); + const getGalleryFftState = React.useCallback((idx: number) => { + if (linkedZoom) return linkedFftZoomState; + return galleryFftStates.get(idx) || { zoom: DEFAULT_FFT_ZOOM, panX: 0, panY: 0 }; + }, [linkedZoom, linkedFftZoomState, galleryFftStates]); + const setGalleryFftState = React.useCallback((idx: number, state: ZoomState) => { + if (linkedZoom) { + setLinkedFftZoomState(state); + } else { + setGalleryFftStates(prev => new Map(prev).set(idx, state)); + } + }, [linkedZoom]); + + // Resizable state (gallery starts smaller) + const [canvasSize, setCanvasSize] = React.useState(nImages > 1 ? GALLERY_IMAGE_TARGET : SINGLE_IMAGE_TARGET); + + // Sync initial sizes from traits + React.useEffect(() => { + if (canvasSizeTrait > 0) setCanvasSize(canvasSizeTrait); + }, [canvasSizeTrait]); + + const [isResizingCanvas, setIsResizingCanvas] = React.useState(false); + const [resizeStart, setResizeStart] = React.useState<{ x: number, y: number, size: number } | null>(null); + + // Profile height resize + const [profileHeight, setProfileHeight] = React.useState(76); + const [isResizingProfile, setIsResizingProfile] = React.useState(false); + const [profileResizeStart, setProfileResizeStart] = React.useState<{ y: number; height: number } | null>(null); + + // WebGPU FFT + const gpuFFTRef = React.useRef(null); + const gpuReadyRef = React.useRef(false); + const rawDataRef = React.useRef(null); + const diffCanvasRefs = React.useRef<(HTMLCanvasElement | null)[]>([]); + const diffFftCanvasRef = React.useRef(null); + const diffFftMagRef = React.useRef(null); + + // WebGPU colormap engine — uses refs (not state) to avoid re-triggering + // effects when GPU initializes. Effects check refs opportunistically: + // on first render they use CPU, on subsequent renders (data/slider change) + // they use GPU if available. No double computation. + const gpuCmapRef = React.useRef(null); + const gpuCmapReadyRef = React.useRef(false); + + // Cached offscreen canvases for main image rendering (avoids per-zoom/pan recompute) + const mainOffscreensRef = React.useRef([]); + const mainImgDatasRef = React.useRef([]); + const logBufferRef = React.useRef(null); + const colorbarVminRef = React.useRef(0); + const colorbarVmaxRef = React.useRef(1); + const [offscreenVersion, setOffscreenVersion] = React.useState(0); + + // Truthful first-render signal: flipped ONCE after the first colormap pass has + // actually painted. Python side observes `_js_rendered` and prints the real + // end-to-end wall clock. Two rAFs ensure the browser has composited before we + // fire, so the printed time reflects "user can see the widget," not "data arrived." + const [, setJsRendered] = useModelState("_js_rendered"); + const firstRenderFiredRef = React.useRef(false); + React.useEffect(() => { + if (firstRenderFiredRef.current) return; + if (offscreenVersion === 0) return; + firstRenderFiredRef.current = true; + requestAnimationFrame(() => requestAnimationFrame(() => setJsRendered(true))); + }, [offscreenVersion, setJsRendered]); + + // Inline FFT refs for gallery mode + const fftCanvasRefs = React.useRef<(HTMLCanvasElement | null)[]>([]); + const fftOffscreensRef = React.useRef<(HTMLCanvasElement | null)[]>([]); + const fftMagCacheGalleryRef = React.useRef<(Float32Array | null)[]>([]); + const galleryFftDimsRef = React.useRef<{ w: number; h: number } | null>(null); + const [galleryFftMagVersion, setGalleryFftMagVersion] = React.useState(0); + + // Cached FFT magnitude for single image mode (avoids recomputing on zoom/pan) + const fftMagCacheRef = React.useRef(null); + const [fftMagVersion, setFftMagVersion] = React.useState(0); + // Generation counter for FFT — coalesces rapid ROI drag events to ≤1 FFT/frame + const fftGenRef = React.useRef(0); + + // Cached FFT offscreen canvas for single mode (avoids reprocessing on zoom/pan) + const fftOffscreenRef = React.useRef(null); + const [fftOffscreenVersion, setFftOffscreenVersion] = React.useState(0); + + // ROI FFT state: when ROI + FFT are both active, compute FFT of cropped ROI region + const [fftCropDims, setFftCropDims] = React.useState<{ cropWidth: number; cropHeight: number; fftWidth: number; fftHeight: number } | null>(null); + + // Layout calculations + const isGallery = nImages > 1; + const showDiffPanel = diffMode && nImages >= 2; + const diffPanelCount = showDiffPanel ? Math.max(0, nImages - 1) : 0; + const effectiveNcols = Math.min(ncols, nImages) + diffPanelCount; + const diffOtherIndices = React.useMemo( + () => Array.from({ length: nImages }, (_, i) => i).filter(i => i !== diffReference), + [nImages, diffReference] + ); + const displayScale = canvasSize / Math.max(width, height); + const canvasW = Math.round(width * displayScale); + const canvasH = Math.round(height * displayScale); + + // Initial pan from zoom_row/zoom_col — runs once after first render with valid canvas dims. + // panX/panY computed so target image (zoomRow, zoomCol) lands at canvas center after transform: + // ctx.translate(cx+panX, cy+panY) ⋅ scale(zoom) ⋅ translate(-cx,-cy) + // target screen = cx + panX + zoom * (target_canvas - cx) = cx + // ⟹ panX = zoom * (cx - target_canvas) = zoom * canvasW * (0.5 - col/width) + const initialPanAppliedRef = React.useRef(false); + React.useEffect(() => { + if (initialPanAppliedRef.current) return; + if (zoomRowTrait == null && zoomColTrait == null) return; + if (canvasW <= 0 || canvasH <= 0 || width <= 0 || height <= 0) return; + const z = initialZoomState.zoom; + const panX = zoomColTrait != null ? z * canvasW * (0.5 - zoomColTrait / width) : 0; + const panY = zoomRowTrait != null ? z * canvasH * (0.5 - zoomRowTrait / height) : 0; + setLinkedZoomState({ zoom: z, panX, panY }); + setZoomStates(prev => { + const m = new Map(prev); + for (let i = 0; i < nImages; i++) m.set(i, { zoom: z, panX, panY }); + return m; + }); + initialPanAppliedRef.current = true; + }, [zoomRowTrait, zoomColTrait, canvasW, canvasH, width, height, nImages, initialZoomState.zoom]); + const floatsPerImage = width * height; + const galleryGridWidth = isGallery ? effectiveNcols * canvasW + (effectiveNcols - 1) * 8 : canvasW; + const profileCanvasWidth = galleryGridWidth; + + // ROI FFT active: both ROI and FFT on, with a selected ROI + const roiFftActive = effectiveShowFft && roiActive && roiSelectedIdx >= 0 && roiSelectedIdx < (roiList?.length ?? 0); + + // Stable key for ROI geometry — only changes when the selected ROI's geometry changes, + // not when other ROIs move or roiList gets a new reference from unrelated edits. + // Shared by both ROI FFT and preview panel to avoid redundant recomputes. + const selectedRoiKey = React.useMemo(() => { + if (!roiList || roiSelectedIdx < 0 || roiSelectedIdx >= roiList.length) return ""; + const r = roiList[roiSelectedIdx]; + return `${r.row},${r.col},${r.radius},${r.radius_inner},${r.width},${r.height},${r.shape}`; + }, [roiList, roiSelectedIdx]); + const roiFftKey = roiFftActive ? selectedRoiKey : ""; + + // Extract raw float32 bytes and parse into Float32Arrays + const allFloats = React.useMemo(() => extractFloat32(frameBytes), [frameBytes]); + + // Initialize WebGPU FFT + colormap engine on mount. + // Sets refs (not state) — no effect re-triggers on GPU init. + // Effects pick up GPU on their next natural re-run (data/slider change). + React.useEffect(() => { + getWebGPUFFT().then(fft => { + if (fft) { + gpuFFTRef.current = fft; + gpuReadyRef.current = true; + const info = getGPUInfo(); + console.log(`[Show2D] WebGPU FFT initialized — ${info || "GPU"}`); + } else { + console.log("[Show2D] WebGPU unavailable — using CPU Worker fallback"); + } + }); + getGPUColormapEngine().then(engine => { + if (engine) { + gpuCmapRef.current = engine; + gpuCmapReadyRef.current = true; + console.log("[Show2D] WebGPU colormap engine initialized"); + // Report GPU memory to Python for auto-bin budget + getGPUMaxBufferSize().then(bytes => { + if (bytes > 0) setGpuMaxBufferMB(Math.floor(bytes / (1024 * 1024))); + }); + // Upload data if already parsed (GPU init may be slower than data arrival). + // Do NOT call setState — that would re-trigger effects and cause double + // computation. Instead, upload data and do a warm-up render via rAF. + // This compiles the GPU pipeline in the background so the first user + // interaction is fast (~100ms instead of ~750ms cold start). + if (rawDataRef.current && rawDataRef.current.length > 0) { + const nImg = rawDataRef.current.length; + for (let i = 0; i < nImg; i++) { + const d = rawDataRef.current[i]; + if (d) engine.uploadData(i, d, width, height); + } + const lut = COLORMAPS[cmap] || COLORMAPS.inferno; + engine.uploadLUT(cmap, lut); + gpuDataVersionRef.current++; + // Warm-up: render once to compile GPU pipeline + fill canvases. + // Uses full data range (no slider adjustment) for the initial frame. + requestAnimationFrame(async () => { + const offscreens = mainOffscreensRef.current; + const imgDatas = mainImgDatasRef.current; + if (offscreens.length === 0 || imgDatas.length === 0) return; + const cachedRanges = dataRangesRef.current; + if (cachedRanges.length === 0) return; + const indices = Array.from({ length: nImg }, (_, i) => i); + const ranges = cachedRanges.map(r => ({ vmin: r.min, vmax: r.max })); + const ofs = indices.map(i => offscreens[i] || null); + const ids = indices.map(i => imgDatas[i] || null); + const logSc = logScaleRef.current ?? false; + await engine.renderSlots(indices, ranges, ofs, ids, logSc); + setOffscreenVersion(v => v + 1); + }); + } + } + }); + }, []); + + const [dataVersion, setDataVersion] = React.useState(0); + + // Keep inline FFT ref arrays in sync with nImages + React.useEffect(() => { + fftCanvasRefs.current = fftCanvasRefs.current.slice(0, nImages); + fftOffscreensRef.current = fftOffscreensRef.current.slice(0, nImages); + }, [nImages]); + + // FFT of diff (n=2 only). Computes A − B in JS at full image resolution from rawDataRef, + // feeds to FFT pipeline. Recomputes when raw data changes. + React.useEffect(() => { + if (!effectiveShowFft || !showDiffPanel || nImages !== 2) return; + const raw = rawDataRef.current; + if (!raw || raw.length < 2 || !raw[0] || !raw[1]) return; + const a = raw[0], b = raw[1]; + const bytes = new Float32Array(width * height); + for (let i = 0; i < bytes.length; i++) bytes[i] = a[i] - b[i]; + const canvas = diffFftCanvasRef.current; + if (!canvas) return; + const fftW = nextPow2(width), fftH = nextPow2(height); + const real = new Float32Array(fftW * fftH); + const imag = new Float32Array(fftW * fftH); + const src = new Float32Array(bytes); + if (fftWindow) applyHannWindow2D(src, width, height); + const padR = Math.floor((fftH - height) / 2), padC = Math.floor((fftW - width) / 2); + for (let r = 0; r < height; r++) { + for (let c = 0; c < width; c++) real[(r + padR) * fftW + c + padC] = src[r * width + c]; + } + let cancelled = false; + (async () => { + const result = await fft2dAsync(real, imag, fftW, fftH, false); + if (cancelled) return; + const mag = computeMagnitude(result.real, result.imag); + fftshift(mag, fftW, fftH); + diffFftMagRef.current = mag; + const { min, max } = autoEnhanceFFT(mag, fftW, fftH); + const off = renderToOffscreen(mag, fftW, fftH, COLORMAPS[fftColormap] || COLORMAPS.inferno, min, max); + if (!off) return; + const ctx = canvas.getContext("2d"); + if (!ctx) return; + ctx.imageSmoothingEnabled = fftW < canvasW || fftH < canvasH; + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(off, 0, 0, fftW, fftH, 0, 0, canvasW, canvasH); + })(); + return () => { cancelled = true; }; + }, [effectiveShowFft, showDiffPanel, nImages, dataVersion, width, height, fftWindow, fftColormap, canvasW, canvasH]); + + // Diff panels render — DYNAMIC. One per non-reference image: image[ref] − image[i]. + // Computed at canvas resolution from raw float data, re-running on zoom/pan/align change. + // For n=2: alignDy/dx applied to non-ref image. For n>2: no align (per-pair align not yet supported). + React.useEffect(() => { + if (!showDiffPanel) return; + const raw = rawDataRef.current; + if (!raw || raw.length < 2) return; + const ref = diffReference; + const a = raw[ref]; + if (!a) return; + diffOtherIndices.forEach((otherIdx, slot) => { + renderDiffPanel(slot, a, raw[otherIdx], otherIdx); + }); + // forEach inlines below — extracted as effect helper. + function renderDiffPanel(slot: number, refData: Float32Array, otherData: Float32Array | undefined, otherIdx: number) { + if (!otherData) return; + const canvas = diffCanvasRefs.current[slot]; + if (!canvas) return; + const ctx = canvas.getContext("2d"); + if (!ctx) return; + const zs0 = getZoomState(ref); + const zs1 = getZoomState(otherIdx); + const useAlign = nImages === 2; + const adY = useAlign ? alignDy : 0; + const adX = useAlign ? alignDx : 0; + const a = refData, b = otherData; + const cw = canvasW, ch = canvasH; + const cx = cw / 2, cy = ch / 2; + const sx = width / cw, sy = height / ch; + const diff = new Float32Array(cw * ch); + let mn = Infinity, mx = -Infinity; + // Smooth: bilinear (slower, sub-pixel correct). !Smooth: nearest neighbor (faster, pixelated). + const Hm1 = height - 1, Wm1 = width - 1; + const a_panX = zs0.panX, a_panY = zs0.panY, a_zoom = zs0.zoom; + const b_panX = zs1.panX, b_panY = zs1.panY, b_zoom = zs1.zoom; + if (smooth) { + for (let y = 0; y < ch; y++) { + const ayu = (y - cy - a_panY) / a_zoom + cy; + const byu = (y - cy - b_panY) / b_zoom + cy; + const aRowF = ayu * sy; + const bRowF = byu * sy - adY; + const aR0 = aRowF | 0, bR0 = bRowF | 0; + const aFr = aRowF - aR0, bFr = bRowF - bR0; + const aRowOOB = aR0 < 0 || aR0 >= Hm1; + const bRowOOB = bR0 < 0 || bR0 >= Hm1; + const aRowOff = aR0 * width; + const bRowOff = bR0 * width; + const rowOff = y * cw; + for (let x = 0; x < cw; x++) { + const axu = (x - cx - a_panX) / a_zoom + cx; + const bxu = (x - cx - b_panX) / b_zoom + cx; + const aColF = axu * sx; + const bColF = bxu * sx - adX; + const aC0 = aColF | 0, bC0 = bColF | 0; + let v = 0; + if (!aRowOOB && !bRowOOB && aC0 >= 0 && aC0 < Wm1 && bC0 >= 0 && bC0 < Wm1) { + const aFc = aColF - aC0, bFc = bColF - bC0; + const ai = aRowOff + aC0; + const bi = bRowOff + bC0; + const aV = (a[ai] * (1 - aFc) + a[ai + 1] * aFc) * (1 - aFr) + + (a[ai + width] * (1 - aFc) + a[ai + width + 1] * aFc) * aFr; + const bV = (b[bi] * (1 - bFc) + b[bi + 1] * bFc) * (1 - bFr) + + (b[bi + width] * (1 - bFc) + b[bi + width + 1] * bFc) * bFr; + v = aV - bV; + } + diff[rowOff + x] = v; + if (v < mn) mn = v; + if (v > mx) mx = v; + } + } + } else { + for (let y = 0; y < ch; y++) { + const ayu = (y - cy - a_panY) / a_zoom + cy; + const byu = (y - cy - b_panY) / b_zoom + cy; + const aRow = (ayu * sy + 0.5) | 0; + const bRow = (byu * sy - adY + 0.5) | 0; + const aRowOK = aRow >= 0 && aRow < height; + const bRowOK = bRow >= 0 && bRow < height; + const aRowOff = aRow * width; + const bRowOff = bRow * width; + const rowOff = y * cw; + for (let x = 0; x < cw; x++) { + const axu = (x - cx - a_panX) / a_zoom + cx; + const bxu = (x - cx - b_panX) / b_zoom + cx; + const aCol = (axu * sx + 0.5) | 0; + const bCol = (bxu * sx - adX + 0.5) | 0; + let v = 0; + if (aRowOK && bRowOK && aCol >= 0 && aCol < width && bCol >= 0 && bCol < width) { + v = a[aRowOff + aCol] - b[bRowOff + bCol]; + } + diff[rowOff + x] = v; + if (v < mn) mn = v; + if (v > mx) mx = v; + } + } + } + const sym = Math.max(Math.abs(mn), Math.abs(mx)); + // Diff is signed-around-zero — use diverging cmap (RdBu) if user picked a sequential one. + const sequentialCmaps = new Set(["inferno", "viridis", "plasma", "magma", "hot", "gray", "turbo"]); + const diffCmap = sequentialCmaps.has(cmap) ? "RdBu" : cmap; + const off = renderToOffscreen(diff, cw, ch, COLORMAPS[diffCmap] || COLORMAPS.RdBu, -sym, sym); + if (!off) return; + ctx.imageSmoothingEnabled = smooth; + if (smooth) ctx.imageSmoothingQuality = "high"; + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(off, 0, 0); + } + }, [showDiffPanel, diffOtherIndices, diffReference, nImages, dataVersion, width, height, cmap, smooth, canvasW, canvasH, + alignDy, alignDx, getZoomState, linkedZoom, linkPan, linkedZoomState, zoomStates]); + + React.useEffect(() => { + if (!allFloats || allFloats.length === 0) return; + const dataArrays: Float32Array[] = []; + for (let i = 0; i < nImages; i++) { + const start = i * floatsPerImage; + const imageData = allFloats.subarray(start, start + floatsPerImage); + dataArrays.push(new Float32Array(imageData)); + } + rawDataRef.current = dataArrays; + // Upload to GPU colormap engine if available (ref check, no state trigger) + const engine = gpuCmapRef.current; + if (engine && gpuCmapReadyRef.current) { + for (let i = 0; i < dataArrays.length; i++) engine.uploadData(i, dataArrays[i], width, height); + gpuDataVersionRef.current++; + } + setDataVersion(v => v + 1); + }, [allFloats, nImages, floatsPerImage]); + + // Initialize reusable offscreen canvases (one per image, resized when dimensions change) + React.useEffect(() => { + if (width <= 0 || height <= 0 || nImages <= 0) return; + const canvases: HTMLCanvasElement[] = []; + const imgDatas: ImageData[] = []; + for (let i = 0; i < nImages; i++) { + const canvas = document.createElement("canvas"); + canvas.width = width; + canvas.height = height; + canvases.push(canvas); + imgDatas.push(canvas.getContext("2d")!.createImageData(width, height)); + } + mainOffscreensRef.current = canvases; + mainImgDatasRef.current = imgDatas; + logBufferRef.current = new Float32Array(width * height); + }, [width, height, nImages]); + + // Compute histogram data for the displayed image (reflects log scale) + // GPU path: uses persistent per-slot histogram buffers — no CPU data scan + // CPU fallback: computeHistogramFromBytes (before GPU ready) + React.useEffect(() => { + if (!rawDataRef.current) return; + const idx = nImages > 1 ? selectedIdx : 0; + const raw = rawDataRef.current[idx]; + if (!raw) return; + + // Use cached ranges (no CPU findDataRange scan) + const cachedRaw = rawRangesRef.current[idx]; + const rawRange = cachedRaw || findDataRange(raw); // fallback if cache miss + const range = logScale + ? { min: Math.log1p(Math.max(rawRange.min, 0)), max: Math.log1p(Math.max(rawRange.max, 0)) } + : rawRange; + setImageDataRange(range); + + const engine = gpuCmapRef.current; + if (engine && gpuCmapReadyRef.current && engine.slotCount > idx) { + // GPU histogram — single image, persistent buffers + engine.computeHistogramWithRange(idx, range.min, range.max, logScale).then(bins => { + setImageHistogramBins(bins); + setImageHistogramData(null); + }); + } else { + // CPU fallback (before GPU ready) + const d = logScale ? applyLogScale(raw) : raw; + setImageHistogramBins(null); + setImageHistogramData(d); + } + }, [allFloats, nImages, floatsPerImage, logScale, selectedIdx]); + + // Prevent page scroll when scrolling on canvases (must use native listener with passive: false) + // In gallery mode, only block scroll on the selected image (or all if linkedZoom) + React.useEffect(() => { + const preventDefault = (e: WheelEvent) => e.preventDefault(); + const elements: (HTMLElement | null)[] = isGallery + ? (linkedZoom + ? [ + ...imageContainerRefs.current, + ...(effectiveShowFft ? fftContainerRefs.current : []), + ] + : [ + imageContainerRefs.current[selectedIdx], + ...(effectiveShowFft ? [fftContainerRefs.current[selectedIdx]] : []), + ]) + : [ + imageContainerRefs.current[0], + ...(effectiveShowFft ? [singleFftContainerRef.current] : []), + ]; + elements.forEach(el => el?.addEventListener("wheel", preventDefault, { passive: false })); + return () => elements.forEach(el => el?.removeEventListener("wheel", preventDefault)); + }, [canvasReady, effectiveShowFft, isGallery, selectedIdx, linkedZoom]); + + const gpuDataVersionRef = React.useRef(0); + // Generation counter for colormap — coalesces rapid slider events to ≤1 render per frame + // Cached per-image data ranges — only recomputed when data or logScale changes, NOT on slider drag + const dataRangesRef = React.useRef<{ min: number; max: number }[]>([]); + // Cached log-transformed data — avoids 12×16M log1p calls per slider tick + const logDataCacheRef = React.useRef([]); + // Ref mirrors for async GPU callbacks (avoid stale closures) + const logScaleRef = React.useRef(logScale); + logScaleRef.current = logScale; + const cmapRef = React.useRef(cmap); + cmapRef.current = cmap; + // Auto-contrast cache: GPU-computed percentile ranges per image + const autoContrastCacheRef = React.useRef<{ vmin: number; vmax: number }[]>([]); + + // Cache per-image data ranges (raw AND log) on data change only. + // Log ranges are derived mathematically: log1p(rawMin), log1p(rawMax). + // NO applyLogScale here — GPU shader handles log1p per pixel. + // Log toggle is now free: just pick the right cached ranges. + const rawRangesRef = React.useRef<{ min: number; max: number }[]>([]); + React.useEffect(() => { + if (!rawDataRef.current || rawDataRef.current.length === 0) return; + const engine = gpuCmapRef.current; + const nImg = rawDataRef.current.length; + + if (engine && gpuCmapReadyRef.current && engine.slotCount >= nImg) { + // GPU path: batch compute min/max on GPU (async, updates refs when done) + const indices = Array.from({ length: nImg }, (_, i) => i); + engine.computeRangeBatch(indices).then(rawRanges => { + rawRangesRef.current = rawRanges; + const logRanges = rawRanges.map(r => ({ + min: Math.log1p(Math.max(r.min, 0)), + max: Math.log1p(Math.max(r.max, 0)), + })); + dataRangesRef.current = logScaleRef.current ? logRanges : rawRanges; + }); + } else { + // CPU fallback: scan each image for min/max + const rawRanges: { min: number; max: number }[] = []; + for (let i = 0; i < nImg; i++) { + const rawData = rawDataRef.current[i]; + if (!rawData) { rawRanges.push({ min: 0, max: 1 }); continue; } + rawRanges.push(findDataRange(rawData)); + } + rawRangesRef.current = rawRanges; + const logRanges = rawRanges.map(r => ({ + min: Math.log1p(Math.max(r.min, 0)), + max: Math.log1p(Math.max(r.max, 0)), + })); + dataRangesRef.current = logScale ? logRanges : rawRanges; + } + logDataCacheRef.current = rawDataRef.current.slice(); + }, [dataVersion]); + + // When logScale toggles, just swap cached ranges (no data scan) + React.useEffect(() => { + if (rawRangesRef.current.length === 0) return; + const logRanges = rawRangesRef.current.map(r => ({ + min: Math.log1p(Math.max(r.min, 0)), + max: Math.log1p(Math.max(r.max, 0)), + })); + dataRangesRef.current = logScale ? logRanges : rawRangesRef.current; + }, [logScale]); + + // GPU auto-contrast: batch-compute percentile ranges from GPU histograms. + // One GPU submission for all images. Caches results for synchronous use in render. + React.useEffect(() => { + if (!autoContrast) { autoContrastCacheRef.current = []; return; } + const engine = gpuCmapRef.current; + if (!engine || !gpuCmapReadyRef.current || !rawDataRef.current) return; + const cachedRanges = dataRangesRef.current; + if (cachedRanges.length === 0) return; + const ls = logScale; + const nImg = Math.min(rawDataRef.current.length, engine.slotCount); + if (nImg === 0) return; + + (async () => { + const indices = Array.from({ length: nImg }, (_, i) => i); + const histRanges = indices.map(i => cachedRanges[i] || { min: 0, max: 1 }); + const allBins = await engine.computeHistogramBatch(indices, histRanges, ls); + + const pLow = 2, pHigh = 98; + const acRanges: { vmin: number; vmax: number }[] = []; + for (let k = 0; k < allBins.length; k++) { + const bins = allBins[k]; + const cr = histRanges[k]; + // Percentile from normalized histogram CDF + let sum = 0; + for (let b = 0; b < 256; b++) sum += bins[b]; + let binLow = 0, binHigh = 255; + const targetLow = sum * pLow / 100; + const targetHigh = sum * pHigh / 100; + let running = 0; + for (let b = 0; b < 256; b++) { + running += bins[b]; + if (running >= targetLow && binLow === 0) binLow = b; + if (running >= targetHigh) { binHigh = b; break; } + } + const range = cr.max - cr.min; + acRanges.push({ vmin: cr.min + (binLow / 255) * range, vmax: cr.min + (binHigh / 255) * range }); + } + autoContrastCacheRef.current = acRanges; + console.log(`[Show2D] GPU auto-contrast: ${nImg} images, ${allBins.length} histograms`); + setOffscreenVersion(v => v + 1); + })(); + }, [autoContrast, dataVersion, logScale]); + + // ------------------------------------------------------------------------- + // Data effect: normalize + colormap → reusable offscreen canvases + // GPU path: runs compute shader for all images in one submission + // CPU fallback: per-image applyColormap loop + // (does NOT depend on zoom/pan — avoids recomputing 16M pixels on every pan/zoom) + // ------------------------------------------------------------------------- + React.useEffect(() => { + if (!dataVersion || !rawDataRef.current || rawDataRef.current.length === 0) return; + if (mainOffscreensRef.current.length === 0 || mainImgDatasRef.current.length === 0) return; + + const lut = COLORMAPS[cmap] || COLORMAPS.inferno; + + // Compute per-image vmin/vmax from CACHED data ranges (no findDataRange per tick). + // dataRangesRef is precomputed when data or logScale changes. + const cachedRanges = dataRangesRef.current; + const hasAbsoluteRange = traitVmin != null && traitVmax != null; + const ranges: { vmin: number; vmax: number }[] = []; + for (let i = 0; i < nImages; i++) { + let vmin: number, vmax: number; + const cs = linkedContrast ? linkedContrastState : (contrastStates.get(i) || { vminPct: 0, vmaxPct: 100 }); + + // Per-image absolute range (vmins/vmaxs) takes precedence over scalar (vmin/vmax) + const perI_min = traitVmins && traitVmins[i] != null ? traitVmins[i] : null; + const perI_max = traitVmaxs && traitVmaxs[i] != null ? traitVmaxs[i] : null; + const hasPerImage = perI_min != null && perI_max != null; + const isDiffSlot = false; + const diffSym = 0; + + let rangeMin: number, rangeMax: number; + if (isDiffSlot) { + rangeMin = -diffSym; + rangeMax = diffSym; + } else if (hasPerImage) { + rangeMin = logScale ? Math.log1p(Math.max(perI_min!, 0)) : perI_min!; + rangeMax = logScale ? Math.log1p(Math.max(perI_max!, 0)) : perI_max!; + } else if (hasAbsoluteRange) { + rangeMin = logScale ? Math.log1p(Math.max(traitVmin!, 0)) : traitVmin!; + rangeMax = logScale ? Math.log1p(Math.max(traitVmax!, 0)) : traitVmax!; + } else { + // GPU range compute is async — when cache missing OR collapsed (min==max from race), + // sync findDataRange on raw data to ensure non-degenerate range. + let cached = cachedRanges[i]; + if (!cached || cached.min === cached.max) { + if (rawDataRef.current && rawDataRef.current[i]) { + cached = findDataRange(rawDataRef.current[i]); + } + } + cached = cached || { min: 0, max: 1 }; + rangeMin = cached.min; + rangeMax = cached.max; + } + + if (!hasAbsoluteRange && !hasPerImage && autoContrast) { + // Auto-contrast: use GPU-precomputed percentile ranges. + // If GPU cache not ready yet, use full data range as placeholder + // (GPU auto-contrast effect will fire async and trigger re-render). + const acCache = autoContrastCacheRef.current[i]; + if (acCache) { + vmin = acCache.vmin; vmax = acCache.vmax; + } else { + vmin = rangeMin; vmax = rangeMax; + } + } else if (rangeMin !== rangeMax && (cs.vminPct > 0 || cs.vmaxPct < 100)) { + ({ vmin, vmax } = sliderRange(rangeMin, rangeMax, cs.vminPct, cs.vmaxPct)); + } else { + vmin = rangeMin; vmax = rangeMax; + } + ranges.push({ vmin, vmax }); + } + + // Cache first image's vmin/vmax for colorbar/lens + if (ranges.length > 0) { + colorbarVminRef.current = ranges[0].vmin; + colorbarVmaxRef.current = ranges[0].vmax; + } + + // GPU colormap — first-class citizen. + // Try zero-copy path (OffscreenCanvas → ImageBitmap, no mapAsync). + // Falls back to renderSlots (mapAsync + putImageData) if zero-copy fails. + const engine = gpuCmapRef.current; + const gpuReady = engine && gpuCmapReadyRef.current && engine.slotCount >= nImages; + if (gpuReady) { + engine!.uploadLUT(cmap, lut); + const capturedRanges = ranges.slice(); + const capturedLogScale = logScale; + const capturedNImages = nImages; + requestAnimationFrame(async () => { + const indices = Array.from({ length: capturedNImages }, (_, i) => i); + + // Zero-copy path: GPU → OffscreenCanvas → ImageBitmap → drawImage + const bitmaps = engine!.renderSlotsToImageBitmap(indices, capturedRanges, capturedLogScale); + if (bitmaps && bitmaps.length > 0 && bitmaps[0]) { + for (let i = 0; i < bitmaps.length; i++) { + const offscreen = mainOffscreensRef.current[i]; + if (!offscreen || !bitmaps[i]) continue; + const ctx = offscreen.getContext("2d"); + if (ctx) ctx.drawImage(bitmaps[i], 0, 0); + } + setOffscreenVersion(v => v + 1); + return; + } + + // Fallback: renderSlots (mapAsync + copy to ImageData) + const offscreens = indices.map(i => mainOffscreensRef.current[i] || null); + const imgDatas = indices.map(i => mainImgDatasRef.current[i] || null); + const rendered = await engine!.renderSlots(indices, capturedRanges, offscreens, imgDatas, capturedLogScale); + if (rendered === 0) { + for (let i = 0; i < capturedNImages; i++) { + const offscreen = mainOffscreensRef.current[i]; + const imgData = mainImgDatasRef.current[i]; + if (!offscreen || !imgData) continue; + const raw = rawDataRef.current?.[i]; + if (!raw) continue; + const processed = capturedLogScale ? applyLogScale(raw) : raw; + renderToOffscreenReuse(processed, lut, capturedRanges[i].vmin, capturedRanges[i].vmax, offscreen, imgData); + } + } + setOffscreenVersion(v => v + 1); + }); + } else { + // CPU fallback: initial render or no WebGPU + // CPU must do log transform itself (GPU shader would handle it) + for (let i = 0; i < nImages; i++) { + const offscreen = mainOffscreensRef.current[i]; + const imgData = mainImgDatasRef.current[i]; + if (!offscreen || !imgData) continue; + const raw = rawDataRef.current?.[i]; + if (!raw) continue; + const processed = logScale ? applyLogScale(raw) : raw; + renderToOffscreenReuse(processed, lut, ranges[i].vmin, ranges[i].vmax, offscreen, imgData); + } + setOffscreenVersion(v => v + 1); + } + }, [dataVersion, nImages, width, height, cmap, logScale, autoContrast, linkedContrast, linkedContrastState, contrastStates, traitVmin, traitVmax, traitVmins, traitVmaxs, diffMode]); + + // ------------------------------------------------------------------------- + // Draw effect: zoom/pan changes — cheap, just drawImage from cached offscreens + // useLayoutEffect prevents black flash when canvas dimensions change (resize) + // ------------------------------------------------------------------------- + React.useLayoutEffect(() => { + if (mainOffscreensRef.current.length === 0) return; + + for (let i = 0; i < nImages; i++) { + const canvas = canvasRefs.current[i]; + const offscreen = mainOffscreensRef.current[i]; + if (!canvas || !offscreen) continue; + const ctx = canvas.getContext("2d"); + if (!ctx) continue; + + ctx.imageSmoothingEnabled = smooth; + if (smooth) ctx.imageSmoothingQuality = "high"; + ctx.clearRect(0, 0, canvas.width, canvas.height); + + const zs = getZoomState(i); + const { zoom, panX, panY } = zs; + + if (zoom !== 1 || panX !== 0 || panY !== 0) { + ctx.save(); + const cx = canvasW / 2; + const cy = canvasH / 2; + ctx.translate(cx + panX, cy + panY); + ctx.scale(zoom, zoom); + ctx.translate(-cx, -cy); + ctx.drawImage(offscreen, 0, 0, width, height, 0, 0, canvasW, canvasH); + ctx.restore(); + } else { + ctx.drawImage(offscreen, 0, 0, width, height, 0, 0, canvasW, canvasH); + } + } + }, [offscreenVersion, nImages, width, height, displayScale, canvasW, canvasH, canvasReady, linkedZoom, linkedZoomState, zoomStates, smooth]); + + // ------------------------------------------------------------------------- + // Render Overlays (scale bar, colorbar, zoom indicator) + // ------------------------------------------------------------------------- + React.useEffect(() => { + for (let i = 0; i < nImages; i++) { + const overlay = overlayRefs.current[i]; + if (!overlay) continue; + const ctx = overlay.getContext("2d"); + if (!ctx) continue; + + if (scaleBarVisible) { + const zs = getZoomState(i); + const unit = pixelSize > 0 ? "Å" as const : "px" as const; + const pxSize = pixelSize > 0 ? pixelSize : 1; + drawScaleBarHiDPI(overlay, DPR, zs.zoom, pxSize, unit, width); + } else { + ctx.clearRect(0, 0, overlay.width, overlay.height); + } + + // Colorbar (single image mode only) — uses cached vmin/vmax from data effect + if (!hideDisplay && showColorbar && !isGallery) { + const lut = COLORMAPS[cmap] || COLORMAPS.inferno; + const cssW = overlay.width / DPR; + const cssH = overlay.height / DPR; + const vmin = colorbarVminRef.current; + const vmax = colorbarVmaxRef.current; + + ctx.save(); + ctx.scale(DPR, DPR); + drawColorbar(ctx, cssW, cssH, lut, vmin, vmax, logScale); + ctx.restore(); + } + + // ROI overlay — draw all ROIs + if (!hideRoi && roiActive && roiList && roiList.length > 0) { + const zs = getZoomState(i); + const { zoom, panX, panY } = zs; + const cx = canvasW / 2; + const cy = canvasH / 2; + + // Highlight mask: dim everything outside highlighted ROIs + const highlightedRois = roiList.filter(r => r.highlight); + if (highlightedRois.length > 0) { + ctx.save(); + ctx.scale(DPR, DPR); + ctx.fillStyle = "rgba(0,0,0,0.6)"; + ctx.fillRect(0, 0, canvasW, canvasH); + ctx.globalCompositeOperation = "destination-out"; + for (const roi of highlightedRois) { + const sx = (roi.col * displayScale - cx) * zoom + cx + panX; + const sy = (roi.row * displayScale - cy) * zoom + cy + panY; + const sr = roi.radius * displayScale * zoom; + const shape = roi.shape || "circle"; + ctx.fillStyle = "rgba(0,0,0,1)"; + if (shape === "circle") { + ctx.beginPath(); ctx.arc(sx, sy, sr, 0, Math.PI * 2); ctx.fill(); + } else if (shape === "square") { + ctx.fillRect(sx - sr, sy - sr, sr * 2, sr * 2); + } else if (shape === "rectangle") { + const sw = roi.width * displayScale * zoom; + const sh = roi.height * displayScale * zoom; + ctx.fillRect(sx - sw / 2, sy - sh / 2, sw, sh); + } else if (shape === "annular") { + ctx.beginPath(); ctx.arc(sx, sy, sr, 0, Math.PI * 2); ctx.fill(); + // Re-darken inner ring + ctx.globalCompositeOperation = "source-over"; + ctx.fillStyle = "rgba(0,0,0,0.6)"; + const sir = roi.radius_inner * displayScale * zoom; + ctx.beginPath(); ctx.arc(sx, sy, sir, 0, Math.PI * 2); ctx.fill(); + ctx.globalCompositeOperation = "destination-out"; + } + } + ctx.restore(); + } + + ctx.save(); + ctx.scale(DPR, DPR); + for (let ri = 0; ri < roiList.length; ri++) { + const roi = roiList[ri]; + const isSelected = ri === roiSelectedIdx; + const screenX = (roi.col * displayScale - cx) * zoom + cx + panX; + const screenY = (roi.row * displayScale - cy) * zoom + cy + panY; + const screenRadius = roi.radius * displayScale * zoom; + const screenW = roi.width * displayScale * zoom; + const screenH = roi.height * displayScale * zoom; + const screenRadiusInner = roi.radius_inner * displayScale * zoom; + const shape = (roi.shape || "circle") as "circle" | "square" | "rectangle" | "annular"; + ctx.lineWidth = roi.line_width || 2; + drawROI(ctx, screenX, screenY, shape, screenRadius, screenW, screenH, roi.color || ROI_COLORS[ri % ROI_COLORS.length], roi.color || ROI_COLORS[ri % ROI_COLORS.length], isSelected && isDraggingROI, screenRadiusInner); + if (isSelected) { + ctx.setLineDash([4, 3]); + ctx.strokeStyle = "#fff"; + ctx.lineWidth = 1; + if (shape === "circle" || shape === "annular") { + ctx.beginPath(); ctx.arc(screenX, screenY, screenRadius + 3, 0, Math.PI * 2); ctx.stroke(); + } else if (shape === "square") { + ctx.strokeRect(screenX - screenRadius - 3, screenY - screenRadius - 3, (screenRadius + 3) * 2, (screenRadius + 3) * 2); + } else if (shape === "rectangle") { + ctx.strokeRect(screenX - screenW / 2 - 3, screenY - screenH / 2 - 3, screenW + 6, screenH + 6); + } + ctx.setLineDash([]); + } + } + ctx.restore(); + } + + // Line profile overlay + if (!hideProfile && profileActive && profilePoints.length > 0) { + const zs = getZoomState(i); + const { zoom, panX, panY } = zs; + ctx.save(); + ctx.scale(DPR, DPR); + + // Transform image coords to screen coords + const cx = canvasW / 2; + const cy = canvasH / 2; + const toScreenX = (ix: number) => (ix * displayScale - cx) * zoom + cx + panX; + const toScreenY = (iy: number) => (iy * displayScale - cy) * zoom + cy + panY; + + // Draw point A + const ax = toScreenX(profilePoints[0].col); + const ay = toScreenY(profilePoints[0].row); + ctx.fillStyle = themeColors.accent; + ctx.beginPath(); + ctx.arc(ax, ay, 4, 0, Math.PI * 2); + ctx.fill(); + + // Draw line and point B if complete + if (profilePoints.length === 2) { + const bx = toScreenX(profilePoints[1].col); + const by = toScreenY(profilePoints[1].row); + + ctx.strokeStyle = themeColors.accent; + ctx.lineWidth = 1.5; + ctx.setLineDash([4, 3]); + ctx.beginPath(); + ctx.moveTo(ax, ay); + ctx.lineTo(bx, by); + ctx.stroke(); + ctx.setLineDash([]); + ctx.fillStyle = themeColors.accent; + ctx.beginPath(); + ctx.arc(bx, by, 4, 0, Math.PI * 2); + ctx.fill(); + } + + ctx.restore(); + } + + // Distance measurement overlay + if (measureActive && measurePoints.length >= 1) { + const zs = getZoomState(i); + const { zoom, panX, panY } = zs; + ctx.save(); + ctx.scale(DPR, DPR); + const cx = canvasW / 2; + const cy = canvasH / 2; + const toSX = (ix: number) => (ix * displayScale - cx) * zoom + cx + panX; + const toSY = (iy: number) => (iy * displayScale - cy) * zoom + cy + panY; + + ctx.shadowColor = "rgba(0,0,0,0.6)"; + ctx.shadowBlur = 3; + + // Endpoint A + const ax = toSX(measurePoints[0].col); + const ay = toSY(measurePoints[0].row); + ctx.fillStyle = "#fff"; + ctx.beginPath(); + ctx.arc(ax, ay, 4, 0, Math.PI * 2); + ctx.fill(); + + if (measurePoints.length === 2) { + const bx = toSX(measurePoints[1].col); + const by = toSY(measurePoints[1].row); + + // Solid white line (distinct from profile's dashed accent line) + ctx.strokeStyle = "#fff"; + ctx.lineWidth = 1.5; + ctx.beginPath(); + ctx.moveTo(ax, ay); + ctx.lineTo(bx, by); + ctx.stroke(); + + // Endpoint B + ctx.beginPath(); + ctx.arc(bx, by, 4, 0, Math.PI * 2); + ctx.fill(); + + // Distance label + const dc = measurePoints[1].col - measurePoints[0].col; + const dr = measurePoints[1].row - measurePoints[0].row; + const distPx = Math.sqrt(dc * dc + dr * dr); + let label: string; + if (pixelSize > 0) { + const distA = distPx * pixelSize; + label = distA >= 10 ? `${(distA / 10).toFixed(2)} nm` : `${distA.toFixed(2)} Å`; + } else { + label = `${distPx.toFixed(1)} px`; + } + + const mx = (ax + bx) / 2; + const my = (ay + by) / 2; + ctx.font = "bold 13px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif"; + ctx.textAlign = "center"; + ctx.textBaseline = "bottom"; + ctx.fillStyle = "#fff"; + ctx.fillText(label, mx, my - 8); + } + + ctx.shadowBlur = 0; + ctx.restore(); + } + } + }, [nImages, pixelSize, scaleBarVisible, selectedIdx, isGallery, canvasW, canvasH, width, displayScale, linkedZoom, linkedZoomState, zoomStates, dataVersion, showColorbar, cmap, offscreenVersion, logScale, profileActive, profilePoints, roiActive, roiList, roiSelectedIdx, isDraggingROI, themeColors, hideDisplay, hideRoi, hideProfile, measureActive, measurePoints]); + + // ------------------------------------------------------------------------- + // Inset magnifier (lens) — renders magnified region at cursor in bottom-left + // ------------------------------------------------------------------------- + React.useEffect(() => { + const lensCanvas = lensCanvasRef.current; + if (lensCanvas) { + const lctx = lensCanvas.getContext("2d"); + if (lctx) lctx.clearRect(0, 0, lensCanvas.width, lensCanvas.height); + } + if (!showLens || lockDisplay || isGallery || !lensPos || !rawDataRef.current?.[0]) return; + if (!lensCanvas) return; + const ctx = lensCanvas.getContext("2d"); + if (!ctx) return; + + const raw = rawDataRef.current[0]; + const lut = COLORMAPS[cmap] || COLORMAPS.inferno; + // Use cached vmin/vmax from data effect (avoids full-image applyLogScale + findDataRange) + const vmin = colorbarVminRef.current; + const vmax = colorbarVmaxRef.current; + + // Extract region around cursor — regionSize = displaySize / magnification + const regionSize = Math.max(4, Math.round(lensDisplaySize / lensMag)); + const lensSize = lensDisplaySize; + const margin = 12; + const half = Math.floor(regionSize / 2); + const r0 = lensPos.row - half; + const c0 = lensPos.col - half; + + // Create small offscreen canvas for the region + const regionCanvas = document.createElement("canvas"); + regionCanvas.width = regionSize; + regionCanvas.height = regionSize; + const rctx = regionCanvas.getContext("2d"); + if (!rctx) return; + const imgData = rctx.createImageData(regionSize, regionSize); + const range = vmax - vmin || 1; + for (let dr = 0; dr < regionSize; dr++) { + for (let dc = 0; dc < regionSize; dc++) { + const sr = r0 + dr; + const sc = c0 + dc; + const idx = (dr * regionSize + dc) * 4; + if (sr < 0 || sr >= height || sc < 0 || sc >= width) { + imgData.data[idx] = 0; imgData.data[idx + 1] = 0; imgData.data[idx + 2] = 0; imgData.data[idx + 3] = 255; + } else { + // Apply log scale inline per-pixel (only for the small region, not full image) + const rawVal = raw[sr * width + sc]; + const val = logScale ? Math.log1p(rawVal) : rawVal; + const t = Math.max(0, Math.min(1, (val - vmin) / range)); + const li = Math.round(t * 255); + imgData.data[idx] = lut[li * 3]; imgData.data[idx + 1] = lut[li * 3 + 1]; imgData.data[idx + 2] = lut[li * 3 + 2]; imgData.data[idx + 3] = 255; + } + } + } + rctx.putImageData(imgData, 0, 0); + + // Draw lens inset on overlay — use custom anchor or default bottom-left + ctx.save(); + ctx.scale(DPR, DPR); + const lx = lensAnchor ? lensAnchor.x : margin; + const ly = lensAnchor ? lensAnchor.y : canvasH - lensSize - margin - 20; + ctx.imageSmoothingEnabled = false; + ctx.drawImage(regionCanvas, lx, ly, lensSize, lensSize); + ctx.strokeStyle = themeColors.accent; + ctx.lineWidth = 2; + ctx.strokeRect(lx, ly, lensSize, lensSize); + // Crosshair at center + const cx = lx + lensSize / 2; + const cy = ly + lensSize / 2; + ctx.strokeStyle = "rgba(255,255,255,0.5)"; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.moveTo(cx - 8, cy); ctx.lineTo(cx + 8, cy); + ctx.moveTo(cx, cy - 8); ctx.lineTo(cx, cy + 8); + ctx.stroke(); + // Magnification label + ctx.fillStyle = "rgba(255,255,255,0.7)"; + ctx.font = "10px monospace"; + ctx.fillText(`${lensMag}×`, lx + 4, ly + lensSize - 4); + ctx.restore(); + }, [showLens, lockDisplay, lensPos, isGallery, cmap, logScale, offscreenVersion, width, height, canvasH, themeColors, lensMag, lensDisplaySize, lensAnchor]); + + // ------------------------------------------------------------------------- + // Auto-compute profile when profile_line is set (e.g. from Python) + // ------------------------------------------------------------------------- + React.useEffect(() => { + if (hideProfile) return; + if (profilePoints.length === 2 && rawDataRef.current) { + const p0 = profilePoints[0], p1 = profilePoints[1]; + const allProfiles: (Float32Array | null)[] = []; + for (let i = 0; i < rawDataRef.current.length; i++) { + const raw = rawDataRef.current[i]; + allProfiles.push(raw ? sampleLineProfile(raw, width, height, p0.row, p0.col, p1.row, p1.col) : null); + } + setProfileDataAll(allProfiles); + if (!profileActive) setProfileActive(true); + } + }, [profilePoints, dataVersion, hideProfile, profileActive]); + + // ------------------------------------------------------------------------- + // Render sparkline for line profile + // ------------------------------------------------------------------------- + React.useEffect(() => { + const canvas = profileCanvasRef.current; + if (!canvas) return; + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + const dpr = window.devicePixelRatio || 1; + const cssW = profileCanvasWidth; + const cssH = profileHeight; + canvas.width = cssW * dpr; + canvas.height = cssH * dpr; + ctx.scale(dpr, dpr); + + const isDark = themeInfo.theme === "dark"; + ctx.fillStyle = isDark ? "#1a1a1a" : "#f0f0f0"; + ctx.fillRect(0, 0, cssW, cssH); + + const hasData = profileDataAll.some(d => d && d.length >= 2); + if (!hasData) { + ctx.font = "10px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif"; + ctx.fillStyle = isDark ? "#555" : "#999"; + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.fillText("Click two points on the image to draw a profile", cssW / 2, cssH / 2); + return; + } + + const padLeft = 40; + const padRight = 8; + const padTop = 6; + const padBottom = 18; + const plotW = cssW - padLeft - padRight; + const plotH = cssH - padTop - padBottom; + + // Find global min/max across all profiles + let gMin = Infinity, gMax = -Infinity; + for (const d of profileDataAll) { + if (!d) continue; + for (let i = 0; i < d.length; i++) { + if (d[i] < gMin) gMin = d[i]; + if (d[i] > gMax) gMax = d[i]; + } + } + const range = gMax - gMin || 1; + + // Draw each profile + const colors = profileDataAll.length === 1 ? [themeColors.accent] : PROFILE_COLORS; + for (let pIdx = 0; pIdx < profileDataAll.length; pIdx++) { + const d = profileDataAll[pIdx]; + if (!d || d.length < 2) continue; + ctx.strokeStyle = colors[pIdx % colors.length]; + ctx.lineWidth = pIdx === selectedIdx || profileDataAll.length === 1 ? 1.5 : 1; + ctx.globalAlpha = pIdx === selectedIdx || profileDataAll.length === 1 ? 1 : 0.5; + ctx.beginPath(); + for (let i = 0; i < d.length; i++) { + const x = padLeft + (i / (d.length - 1)) * plotW; + const y = padTop + plotH - ((d[i] - gMin) / range) * plotH; + if (i === 0) ctx.moveTo(x, y); + else ctx.lineTo(x, y); + } + ctx.stroke(); + } + ctx.globalAlpha = 1; + + // Compute total distance for x-axis + const firstProfile = profileDataAll.find(d => d); + let totalDist = (firstProfile?.length ?? 2) - 1; + let xUnit = "px"; + if (profilePoints.length === 2) { + const dx = profilePoints[1].col - profilePoints[0].col; + const dy = profilePoints[1].row - profilePoints[0].row; + const distPx = Math.sqrt(dx * dx + dy * dy); + if (pixelSize > 0) { + const distA = distPx * pixelSize; + if (distA >= 10) { totalDist = distA / 10; xUnit = "nm"; } + else { totalDist = distA; xUnit = "Å"; } + } else { + totalDist = distPx; + } + } + + // Draw x-axis ticks + const tickY = padTop + plotH; + ctx.strokeStyle = isDark ? "#555" : "#bbb"; + ctx.lineWidth = 0.5; + const idealTicks = Math.max(2, Math.floor(plotW / 70)); + const tickStep = roundToNiceValue(totalDist / idealTicks); + ctx.font = "9px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif"; + ctx.fillStyle = isDark ? "#888" : "#666"; + ctx.textBaseline = "top"; + const ticks: number[] = []; + for (let v = 0; v <= totalDist + tickStep * 0.01; v += tickStep) { + if (v > totalDist * 1.001) break; + ticks.push(v); + } + for (let i = 0; i < ticks.length; i++) { + const v = ticks[i]; + const frac = totalDist > 0 ? v / totalDist : 0; + const x = padLeft + frac * plotW; + ctx.beginPath(); ctx.moveTo(x, tickY); ctx.lineTo(x, tickY + 3); ctx.stroke(); + ctx.textAlign = frac < 0.05 ? "left" : frac > 0.95 ? "right" : "center"; + const valStr = v % 1 === 0 ? v.toFixed(0) : v.toFixed(1); + ctx.fillText(i === ticks.length - 1 ? `${valStr} ${xUnit}` : valStr, x, tickY + 4); + } + + // Draw y-axis min/max labels + ctx.font = "9px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif"; + ctx.fillStyle = isDark ? "#888" : "#666"; + ctx.textAlign = "right"; + ctx.textBaseline = "top"; + ctx.fillText(formatNumber(gMax), padLeft - 3, padTop); + ctx.textBaseline = "bottom"; + ctx.fillText(formatNumber(gMin), padLeft - 3, padTop + plotH); + + // Draw axis lines + ctx.strokeStyle = isDark ? "#555" : "#bbb"; + ctx.lineWidth = 0.5; + ctx.beginPath(); + ctx.moveTo(padLeft, padTop); + ctx.lineTo(padLeft, padTop + plotH); + ctx.lineTo(padLeft + plotW, padTop + plotH); + ctx.stroke(); + + // Legend (gallery mode with multiple images) + if (profileDataAll.length > 1) { + ctx.textAlign = "right"; + ctx.textBaseline = "top"; + ctx.font = "9px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif"; + let legendX = cssW - 4; + for (let pIdx = profileDataAll.length - 1; pIdx >= 0; pIdx--) { + if (!profileDataAll[pIdx]) continue; + const label = labels?.[pIdx] || `#${pIdx + 1}`; + const color = colors[pIdx % colors.length]; + const textW = ctx.measureText(label).width; + ctx.globalAlpha = pIdx === selectedIdx ? 1 : 0.5; + ctx.fillStyle = color; + ctx.fillRect(legendX - textW - 10, 2, 6, 6); + ctx.fillStyle = isDark ? "#aaa" : "#555"; + ctx.fillText(label, legendX, 1); + legendX -= textW + 16; + } + ctx.globalAlpha = 1; + } + + // Save base rendering + layout for hover overlay + profileBaseImageRef.current = ctx.getImageData(0, 0, canvas.width, canvas.height); + profileLayoutRef.current = { padLeft, plotW, padTop, plotH, gMin, gMax, totalDist, xUnit }; + }, [profileDataAll, themeInfo.theme, themeColors.accent, profilePoints, pixelSize, selectedIdx, labels, profileCanvasWidth, profileHeight]); + + // Profile hover handler — draws crosshair + value readout + const handleProfileMouseMove = React.useCallback((e: React.MouseEvent) => { + const canvas = profileCanvasRef.current; + const base = profileBaseImageRef.current; + const layout = profileLayoutRef.current; + if (!canvas || !base || !layout) return; + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + const rect = canvas.getBoundingClientRect(); + const cssX = e.clientX - rect.left; + const { padLeft, plotW, padTop, plotH, gMin, gMax, totalDist, xUnit } = layout; + const range = gMax - gMin || 1; + + // Restore base image + ctx.putImageData(base, 0, 0); + + if (cssX < padLeft || cssX > padLeft + plotW) return; + const frac = (cssX - padLeft) / plotW; + + const dpr = window.devicePixelRatio || 1; + ctx.save(); + ctx.setTransform(dpr, 0, 0, dpr, 0, 0); + + // Vertical crosshair + ctx.strokeStyle = themeInfo.theme === "dark" ? "rgba(255,255,255,0.3)" : "rgba(0,0,0,0.3)"; + ctx.lineWidth = 1; + ctx.setLineDash([2, 2]); + ctx.beginPath(); + ctx.moveTo(cssX, padTop); + ctx.lineTo(cssX, padTop + plotH); + ctx.stroke(); + ctx.setLineDash([]); + + // Dot on each profile line + collect values + const colors = profileDataAll.length === 1 ? [themeColors.accent] : PROFILE_COLORS; + const activeIdx = isGallery ? selectedIdx : 0; + let displayVal: number | null = null; + for (let pIdx = 0; pIdx < profileDataAll.length; pIdx++) { + const d = profileDataAll[pIdx]; + if (!d || d.length < 2) continue; + const dataIdx = Math.min(d.length - 1, Math.max(0, Math.round(frac * (d.length - 1)))); + const val = d[dataIdx]; + const y = padTop + plotH - ((val - gMin) / range) * plotH; + ctx.fillStyle = colors[pIdx % colors.length]; + ctx.globalAlpha = pIdx === activeIdx || profileDataAll.length === 1 ? 1 : 0.5; + ctx.beginPath(); + ctx.arc(cssX, y, 3, 0, Math.PI * 2); + ctx.fill(); + if (pIdx === activeIdx || profileDataAll.length === 1) displayVal = val; + } + ctx.globalAlpha = 1; + + // Value readout label + if (displayVal !== null) { + const dist = frac * totalDist; + const label = `${formatNumber(displayVal)} @ ${dist.toFixed(1)} ${xUnit}`; + const isDark = themeInfo.theme === "dark"; + ctx.font = "bold 9px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif"; + const textW = ctx.measureText(label).width; + const labelX = Math.min(cssX + 6, padLeft + plotW - textW - 2); + const labelY = padTop + 2; + ctx.fillStyle = isDark ? "rgba(0,0,0,0.7)" : "rgba(255,255,255,0.8)"; + ctx.fillRect(labelX - 2, labelY - 1, textW + 4, 11); + ctx.fillStyle = isDark ? "#fff" : "#000"; + ctx.textAlign = "left"; + ctx.textBaseline = "top"; + ctx.fillText(label, labelX, labelY); + } + + ctx.restore(); + }, [profileDataAll, themeInfo.theme, themeColors.accent, isGallery, selectedIdx]); + + const handleProfileMouseLeave = React.useCallback(() => { + const canvas = profileCanvasRef.current; + const base = profileBaseImageRef.current; + if (!canvas || !base) return; + const ctx = canvas.getContext("2d"); + if (!ctx) return; + ctx.putImageData(base, 0, 0); + }, []); + + // ------------------------------------------------------------------------- + // Compute FFT magnitude (cached — only recomputes when data changes) + // Supports ROI-scoped FFT: when ROI is active with a selected ROI, compute + // FFT of the cropped region instead of the full image. + // ------------------------------------------------------------------------- + React.useEffect(() => { + if (!effectiveShowFft || isGallery || !rawDataRef.current) return; + if (!rawDataRef.current[selectedIdx]) return; + // Generation counter: coalesces rapid ROI drag events so at most one + // FFT runs per animation frame. The rAF yield lets the browser paint + // the ROI position update before the (potentially blocking) FFT runs. + const gen = ++fftGenRef.current; + + const doCompute = async () => { + // Yield to next animation frame — browser paints updated ROI first, + // and stale requests (from earlier drag events) are discarded below. + await new Promise(r => requestAnimationFrame(() => r())); + if (gen !== fftGenRef.current) return; + + const backend = gpuFFTRef.current && gpuReadyRef.current ? "WebGPU" : "CPU Worker"; + setFftComputing(true); + setFftProgress(`Computing FFT… (${backend})`); + const t0 = performance.now(); + const data = rawDataRef.current![selectedIdx]; + let fftW = width; + let fftH = height; + let inputData = data; + + // ROI crop: extract bounding box and optionally zero-mask outside radius + let origCropW = 0, origCropH = 0; + if (roiFftActive && roiList && roiSelectedIdx >= 0 && roiSelectedIdx < roiList.length) { + const roi = roiList[roiSelectedIdx]; + const crop = cropROIRegion(data, width, height, roi); + if (crop) { + origCropW = crop.cropW; + origCropH = crop.cropH; + // Apply Hann window to crop at native dimensions BEFORE zero-padding + if (fftWindow) applyHannWindow2D(crop.cropped, crop.cropW, crop.cropH); + // Pad to next power-of-2 so fft2d doesn't truncate frequency data + const padW = nextPow2(crop.cropW); + const padH = nextPow2(crop.cropH); + const padded = new Float32Array(padW * padH); + for (let y = 0; y < crop.cropH; y++) { + for (let x = 0; x < crop.cropW; x++) { + padded[y * padW + x] = crop.cropped[y * crop.cropW + x]; + } + } + inputData = padded; + fftW = padW; + fftH = padH; + } + } + + // Pre-pad non-power-of-2 full images so fft2d doesn't truncate frequency data + if (origCropW === 0) { + const padW = nextPow2(fftW); + const padH = nextPow2(fftH); + if (padW !== fftW || padH !== fftH) { + const padded = new Float32Array(padW * padH); + for (let y = 0; y < fftH; y++) { + for (let x = 0; x < fftW; x++) { + padded[y * padW + x] = inputData[y * fftW + x]; + } + } + inputData = padded; + fftW = padW; + fftH = padH; + } + } + + const tCrop = performance.now(); + const real = inputData.slice(); + const imag = new Float32Array(inputData.length); + + if (gpuFFTRef.current && gpuReadyRef.current) { + const result = await gpuFFTRef.current.fft2D(real, imag, fftW, fftH, false); + if (gen !== fftGenRef.current) return; + const tGpu = performance.now(); + fftshift(result.real, fftW, fftH); + fftshift(result.imag, fftW, fftH); + fftMagCacheRef.current = computeMagnitude(result.real, result.imag); + console.log(`[Show2D FFT] GPU ${fftW}×${fftH}: crop=${(tCrop-t0).toFixed(1)}ms gpu=${(tGpu-tCrop).toFixed(1)}ms post=${(performance.now()-tGpu).toFixed(1)}ms`); + } else { + // CPU fallback: run in Web Worker to avoid blocking the main thread + const result = await fft2dAsync(real, imag, fftW, fftH, false); + if (gen !== fftGenRef.current) return; + fftMagCacheRef.current = result.magnitude; + console.log(`[Show2D FFT] Worker ${fftW}×${fftH}: crop=${(tCrop-t0).toFixed(1)}ms worker=${(performance.now()-tCrop).toFixed(1)}ms`); + } + // Track FFT dimensions when they differ from image dimensions (ROI crop or non-pow2 padding) + if (origCropW > 0) { + setFftCropDims({ cropWidth: origCropW, cropHeight: origCropH, fftWidth: fftW, fftHeight: fftH }); + } else if (fftW !== width || fftH !== height) { + setFftCropDims({ cropWidth: width, cropHeight: height, fftWidth: fftW, fftHeight: fftH }); + } else { + setFftCropDims(null); + } + setFftMagVersion(v => v + 1); + setFftComputing(false); + setFftProgress(""); + }; + + doCompute(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [effectiveShowFft, isGallery, selectedIdx, width, height, dataVersion, roiFftKey, fftWindow]); + + // Clear FFT measurement when image, FFT state, or ROI changes + React.useEffect(() => { setFftClickInfo(null); }, [selectedIdx, effectiveShowFft, roiFftActive, roiSelectedIdx]); + + // ------------------------------------------------------------------------- + // FFT data effect: normalize + colormap → cached offscreen canvas + // (does NOT depend on fftZoom/fftPanX/fftPanY — avoids reprocessing on zoom/pan) + // ------------------------------------------------------------------------- + React.useEffect(() => { + if (!effectiveShowFft || isGallery || !fftMagCacheRef.current) return; + + const fftMag = fftMagCacheRef.current; + const lut = COLORMAPS[fftColormap] || COLORMAPS.inferno; + + // Use crop dimensions when ROI FFT is active + const fftW = fftCropDims?.fftWidth ?? width; + const fftH = fftCropDims?.fftHeight ?? height; + + // Apply scale mode + const magnitude = new Float32Array(fftMag.length); + for (let i = 0; i < fftMag.length; i++) { + if (fftScaleMode === "log") { + magnitude[i] = Math.log1p(fftMag[i]); + } else if (fftScaleMode === "power") { + magnitude[i] = Math.pow(fftMag[i], 0.5); + } else { + magnitude[i] = fftMag[i]; + } + } + + let displayMin: number, displayMax: number; + if (fftAuto) { + ({ min: displayMin, max: displayMax } = autoEnhanceFFT(magnitude, fftW, fftH)); + } else { + ({ min: displayMin, max: displayMax } = findDataRange(magnitude)); + } + + const { mean, std } = computeStats(magnitude); + setFftStats([mean, displayMin, displayMax, std]); + + // Store histogram data + setFftHistogramData(magnitude.slice()); + setFftDataRange({ min: displayMin, max: displayMax }); + + // Apply histogram slider clipping and render to cached offscreen + const { vmin, vmax } = sliderRange(displayMin, displayMax, fftVminPct, fftVmaxPct); + const offscreen = renderToOffscreen(magnitude, fftW, fftH, lut, vmin, vmax); + if (!offscreen) return; + fftOffscreenRef.current = offscreen; + setFftOffscreenVersion(v => v + 1); + }, [effectiveShowFft, isGallery, fftMagVersion, fftVminPct, fftVmaxPct, fftColormap, fftScaleMode, fftAuto, width, height, fftCropDims]); + + // ------------------------------------------------------------------------- + // FFT draw effect: cheap drawImage from cached offscreen (zoom/pan changes) + // ------------------------------------------------------------------------- + React.useLayoutEffect(() => { + if (!effectiveShowFft || isGallery || !fftCanvasRef.current || !fftOffscreenRef.current) return; + + const canvas = fftCanvasRef.current; + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + const offscreen = fftOffscreenRef.current; + const fftW = offscreen.width; + const fftH = offscreen.height; + + // Use bilinear smoothing when FFT is smaller than canvas (avoids blocky upscaling) + ctx.imageSmoothingEnabled = fftW < canvasW || fftH < canvasH; + ctx.clearRect(0, 0, canvasW, canvasH); + ctx.save(); + + const centerOffsetX = (canvasW - canvasW * fftZoom) / 2 + fftPanX; + const centerOffsetY = (canvasH - canvasH * fftZoom) / 2 + fftPanY; + + ctx.translate(centerOffsetX, centerOffsetY); + ctx.scale(fftZoom, fftZoom); + // Stretch cropped FFT to fill the full canvas (no layout change during drag) + ctx.drawImage(offscreen, 0, 0, fftW, fftH, 0, 0, canvasW, canvasH); + ctx.restore(); + }, [effectiveShowFft, isGallery, fftOffscreenVersion, canvasW, canvasH, fftZoom, fftPanX, fftPanY]); + + // ------------------------------------------------------------------------- + // Render FFT overlay (scale bar + colorbar + d-spacing marker) + // ------------------------------------------------------------------------- + React.useEffect(() => { + const overlay = fftOverlayRef.current; + if (!overlay || !effectiveShowFft || isGallery) return; + const ctx = overlay.getContext("2d"); + if (!ctx) return; + ctx.clearRect(0, 0, overlay.width, overlay.height); + + // Use crop dimensions for reciprocal-space calculations + const fftW = fftCropDims?.fftWidth ?? width; + + // Reciprocal-space scale bar + if (pixelSize > 0) { + const fftPixelSize = 1 / (fftW * pixelSize); + drawFFTScaleBarHiDPI(overlay, DPR, fftZoom, fftPixelSize, fftW); + } + + // FFT colorbar + if (fftShowColorbar && fftDataRange.min !== fftDataRange.max) { + const { vmin, vmax } = sliderRange(fftDataRange.min, fftDataRange.max, fftVminPct, fftVmaxPct); + const lut = COLORMAPS[fftColormap] || COLORMAPS.inferno; + ctx.save(); + ctx.scale(DPR, DPR); + const cssW = overlay.width / DPR; + const cssH = overlay.height / DPR; + drawColorbar(ctx, cssW, cssH, lut, vmin, vmax, fftScaleMode === "log"); + ctx.restore(); + } + + // D-spacing crosshair marker — use crop dims for coordinate mapping + const fftH = fftCropDims?.fftHeight ?? height; + if (fftClickInfo) { + ctx.save(); + ctx.scale(DPR, DPR); + const centerOffsetX = (canvasW - canvasW * fftZoom) / 2 + fftPanX; + const centerOffsetY = (canvasH - canvasH * fftZoom) / 2 + fftPanY; + const screenX = centerOffsetX + fftZoom * (fftClickInfo.col / fftW * canvasW); + const screenY = centerOffsetY + fftZoom * (fftClickInfo.row / fftH * canvasH); + ctx.strokeStyle = "rgba(255, 255, 255, 0.9)"; + ctx.shadowColor = "rgba(0, 0, 0, 0.6)"; + ctx.shadowBlur = 2; + ctx.lineWidth = 1.5; + const r = 8; + ctx.beginPath(); + ctx.moveTo(screenX - r, screenY); ctx.lineTo(screenX - 3, screenY); + ctx.moveTo(screenX + 3, screenY); ctx.lineTo(screenX + r, screenY); + ctx.moveTo(screenX, screenY - r); ctx.lineTo(screenX, screenY - 3); + ctx.moveTo(screenX, screenY + 3); ctx.lineTo(screenX, screenY + r); + ctx.stroke(); + ctx.beginPath(); + ctx.arc(screenX, screenY, 4, 0, Math.PI * 2); + ctx.stroke(); + if (fftClickInfo.dSpacing != null) { + const d = fftClickInfo.dSpacing; + const label = d >= 10 ? `d = ${(d / 10).toFixed(2)} nm` : `d = ${d.toFixed(2)} Å`; + ctx.font = "bold 11px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif"; + ctx.fillStyle = "white"; + ctx.textAlign = "left"; + ctx.textBaseline = "bottom"; + ctx.fillText(label, screenX + 10, screenY - 4); + } + ctx.restore(); + } + }, [effectiveShowFft, isGallery, fftClickInfo, canvasW, canvasH, fftZoom, fftPanX, fftPanY, width, height, pixelSize, fftDataRange, fftVminPct, fftVmaxPct, fftColormap, fftScaleMode, fftShowColorbar, fftCropDims]); + + // ------------------------------------------------------------------------- + // Compute FFT magnitudes for gallery mode (cache raw magnitudes) + // ------------------------------------------------------------------------- + React.useEffect(() => { + if (!effectiveShowFft || !isGallery || !rawDataRef.current) return; + if (rawDataRef.current.length === 0) return; + let cancelled = false; + + const computeAllFFTs = async () => { + // Initialize cache; preserve existing entries (only recompute missing) + if (fftMagCacheGalleryRef.current.length !== nImages) { + fftMagCacheGalleryRef.current = new Array(nImages).fill(null); + } + setFftComputing(true); + const useGPU = !!(gpuFFTRef.current && gpuReadyRef.current); + const backend = useGPU ? "WebGPU" : "CPU Worker"; + setFftProgress(`FFT (${backend})`); + await new Promise(r => requestAnimationFrame(() => r())); + if (cancelled) { setFftComputing(false); return; } + + const useRoiCrop = roiFftActive && roiList && roiSelectedIdx >= 0 && roiSelectedIdx < roiList.length; + const roi = useRoiCrop ? roiList[roiSelectedIdx] : null; + const t0 = performance.now(); + + // Helper: prep one image for FFT (crop, pad, window) + const prepOne = (idx: number): { real: Float32Array; imag: Float32Array; w: number; h: number } | null => { + const data = rawDataRef.current![idx]; + if (!data) return null; + let inputData = data; + let curW = width, curH = height; + if (roi) { + const crop = cropROIRegion(data, width, height, roi); + if (crop) { + if (fftWindow) applyHannWindow2D(crop.cropped, crop.cropW, crop.cropH); + const padW = nextPow2(crop.cropW), padH = nextPow2(crop.cropH); + const padded = new Float32Array(padW * padH); + for (let y = 0; y < crop.cropH; y++) + for (let x = 0; x < crop.cropW; x++) + padded[y * padW + x] = crop.cropped[y * crop.cropW + x]; + inputData = padded; curW = padW; curH = padH; + } + } else { + const padW = nextPow2(curW), padH = nextPow2(curH); + if (padW !== curW || padH !== curH) { + const padded = new Float32Array(padW * padH); + for (let y = 0; y < curH; y++) + for (let x = 0; x < curW; x++) + padded[y * padW + x] = inputData[y * curW + x]; + inputData = padded; curW = padW; curH = padH; + } + } + return { real: inputData.slice(), imag: new Float32Array(inputData.length), w: curW, h: curH }; + }; + + // ── Prep all images ── + const inputs: { real: Float32Array; imag: Float32Array }[] = []; + let fftW = width, fftH = height; + for (let idx = 0; idx < nImages; idx++) { + const input = prepOne(idx); + if (input) { + fftW = input.w; fftH = input.h; + inputs.push({ real: input.real, imag: input.imag }); + } else { + inputs.push({ real: new Float32Array(0), imag: new Float32Array(0) }); + } + } + galleryFftDimsRef.current = { w: fftW, h: fftH }; + const tPrep = performance.now() - t0; + if (cancelled) { setFftComputing(false); return; } + + // ── Batched progressive FFT: batch BATCH_SIZE at a time, display after each batch ── + const BATCH_SIZE = 4; + const tFFT0 = performance.now(); + for (let batchStart = 0; batchStart < nImages; batchStart += BATCH_SIZE) { + if (cancelled) { setFftComputing(false); return; } + const batchEnd = Math.min(batchStart + BATCH_SIZE, nImages); + const batchInputs = inputs.slice(batchStart, batchEnd).filter(inp => inp.real.length > 0); + setFftProgress(`FFT ${batchStart + 1}–${batchEnd}/${nImages} (${backend})`); + + if (useGPU && batchInputs.length > 1) { + // GPU batch: one submission for BATCH_SIZE images + const batchResults = await gpuFFTRef.current!.fft2DBatch(batchInputs, fftW, fftH); + if (cancelled) { setFftComputing(false); return; } + let ri = 0; + for (let idx = batchStart; idx < batchEnd; idx++) { + if (inputs[idx].real.length === 0) continue; + fftshift(batchResults[ri].real, fftW, fftH); + fftshift(batchResults[ri].imag, fftW, fftH); + fftMagCacheGalleryRef.current[idx] = computeMagnitude(batchResults[ri].real, batchResults[ri].imag); + ri++; + } + } else { + // CPU or single image + for (let idx = batchStart; idx < batchEnd; idx++) { + if (inputs[idx].real.length === 0) continue; + if (cancelled) { setFftComputing(false); return; } + const { real, imag } = inputs[idx]; + if (useGPU) { + const result = await gpuFFTRef.current!.fft2D(real, imag, fftW, fftH, false); + fftshift(result.real, fftW, fftH); + fftshift(result.imag, fftW, fftH); + fftMagCacheGalleryRef.current[idx] = computeMagnitude(result.real, result.imag); + } else { + fft2d(real, imag, fftW, fftH, false); + fftshift(real, fftW, fftH); + fftshift(imag, fftW, fftH); + fftMagCacheGalleryRef.current[idx] = computeMagnitude(real, imag); + } + } + } + // Show this batch immediately (progressive top-to-bottom) + setGalleryFftMagVersion(v => v + 1); + // Yield to let the browser paint the batch + await new Promise(r => requestAnimationFrame(() => r())); + } + const tFFT = performance.now() - tFFT0; + const tTotal = performance.now() - t0; + if (!cancelled) { + console.log(`[Show2D FFT] Gallery ${nImages}×${fftW}×${fftH}: prep=${tPrep.toFixed(0)}ms fft=${tFFT.toFixed(0)}ms total=${tTotal.toFixed(0)}ms (${backend} batch=${BATCH_SIZE})`); + } + setFftComputing(false); + setFftProgress(""); + }; + + computeAllFFTs(); + + return () => { cancelled = true; setFftComputing(false); }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [effectiveShowFft, isGallery, nImages, width, height, dataVersion, roiFftKey, fftWindow]); + + // Gallery FFT data effect: normalize + colormap → cached offscreen canvases + // (does NOT depend on gallery zoom/pan states) + const [galleryFftOffscreenVersion, setGalleryFftOffscreenVersion] = React.useState(0); + React.useEffect(() => { + if (!effectiveShowFft || !isGallery) return; + const lut = COLORMAPS[fftColormap] || COLORMAPS.inferno; + const fftW = galleryFftDimsRef.current?.w ?? width; + const fftH = galleryFftDimsRef.current?.h ?? height; + + for (let idx = 0; idx < nImages; idx++) { + const magnitude = fftMagCacheGalleryRef.current[idx]; + if (!magnitude) continue; + + // Apply scale transform (same logic as single mode) + let displayData: Float32Array; + let displayMin: number, displayMax: number; + if (fftScaleMode === "log") { + displayData = applyLogScale(magnitude); + } else if (fftScaleMode === "power") { + displayData = new Float32Array(magnitude.length); + for (let j = 0; j < magnitude.length; j++) displayData[j] = Math.sqrt(magnitude[j]); + } else { + displayData = magnitude; + } + if (fftAuto) { + ({ min: displayMin, max: displayMax } = autoEnhanceFFT(magnitude, fftW, fftH)); + if (fftScaleMode === "log") { displayMin = Math.log1p(displayMin); displayMax = Math.log1p(displayMax); } + else if (fftScaleMode === "power") { displayMin = Math.sqrt(displayMin); displayMax = Math.sqrt(displayMax); } + } else { + ({ min: displayMin, max: displayMax } = findDataRange(displayData)); + } + const { vmin, vmax } = sliderRange(displayMin, displayMax, fftVminPct, fftVmaxPct); + + const offscreen = renderToOffscreen(displayData, fftW, fftH, lut, vmin, vmax); + if (!offscreen) continue; + fftOffscreensRef.current[idx] = offscreen; + } + + // Update FFT histogram from selected image + const selMag = fftMagCacheGalleryRef.current[selectedIdx]; + if (selMag) { + let histData: Float32Array; + if (fftScaleMode === "log") histData = applyLogScale(selMag); + else if (fftScaleMode === "power") { histData = new Float32Array(selMag.length); for (let j = 0; j < selMag.length; j++) histData[j] = Math.sqrt(selMag[j]); } + else histData = selMag; + setFftHistogramData(histData); + setFftDataRange(findDataRange(histData)); + } + setGalleryFftOffscreenVersion(v => v + 1); + }, [effectiveShowFft, isGallery, nImages, width, height, galleryFftMagVersion, fftColormap, fftScaleMode, fftAuto, fftVminPct, fftVmaxPct, selectedIdx]); + + // Gallery FFT draw effect: cheap drawImage from cached offscreens (zoom/pan changes) + React.useLayoutEffect(() => { + if (!effectiveShowFft || !isGallery) return; + const fftW = galleryFftDimsRef.current?.w ?? width; + const fftH = galleryFftDimsRef.current?.h ?? height; + + for (let idx = 0; idx < nImages; idx++) { + const offscreen = fftOffscreensRef.current[idx]; + const canvas = fftCanvasRefs.current[idx]; + if (!offscreen || !canvas) continue; + const ctx = canvas.getContext("2d"); + if (!ctx) continue; + + const { zoom, panX, panY } = getGalleryFftState(idx); + ctx.imageSmoothingEnabled = fftW < canvasW || fftH < canvasH; + ctx.clearRect(0, 0, canvasW, canvasH); + ctx.save(); + const cx = canvasW / 2; + const cy = canvasH / 2; + ctx.translate(cx + panX, cy + panY); + ctx.scale(zoom, zoom); + ctx.translate(-cx, -cy); + ctx.drawImage(offscreen, 0, 0, fftW, fftH, 0, 0, canvasW, canvasH); + ctx.restore(); + } + }, [effectiveShowFft, isGallery, nImages, canvasW, canvasH, width, height, galleryFftOffscreenVersion, galleryFftStates, linkedZoom, linkedFftZoomState]); + + // ------------------------------------------------------------------------- + // Mouse Handlers for Zoom/Pan + // ------------------------------------------------------------------------- + const handleWheel = (e: React.WheelEvent, idx: number) => { + if (lockView) return; + // In gallery mode, only allow zoom on the selected image (unless linked) + if (isGallery && idx !== selectedIdx && !linkedZoom) return; + e.preventDefault(); // Prevent page scroll when zooming + + const canvas = canvasRefs.current[idx]; + if (!canvas) return; + const rect = canvas.getBoundingClientRect(); + + // Get current zoom state + const zs = getZoomState(idx); + + // Mouse position relative to canvas (in canvas pixel coordinates) + const mouseCanvasX = (e.clientX - rect.left) * (canvas.width / rect.width); + const mouseCanvasY = (e.clientY - rect.top) * (canvas.height / rect.height); + + // Canvas center + const cx = canvas.width / 2; + const cy = canvas.height / 2; + + // Mouse position relative to the current view (accounting for pan and zoom) + // The transformation is: translate(cx + panX, cy + panY) -> scale(zoom) -> translate(-cx, -cy) + // So a point on screen at (screenX, screenY) maps to image space as: + // imageX = (screenX - cx - panX) / zoom + cx + const mouseImageX = (mouseCanvasX - cx - zs.panX) / zs.zoom + cx; + const mouseImageY = (mouseCanvasY - cy - zs.panY) / zs.zoom + cy; + + const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1; + const newZoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, zs.zoom * zoomFactor)); + + // Calculate new pan to keep the mouse position fixed on the same image point + // After zoom: screenX = (imageX - cx) * newZoom + cx + newPanX + // We want screenX to stay at mouseCanvasX, so: + // newPanX = mouseCanvasX - (imageX - cx) * newZoom - cx + const newPanX = mouseCanvasX - (mouseImageX - cx) * newZoom - cx; + const newPanY = mouseCanvasY - (mouseImageY - cy) * newZoom - cy; + + setZoomState(idx, { zoom: newZoom, panX: newPanX, panY: newPanY }); + }; + + const handleDoubleClick = (idx: number) => { + if (lockView) return; + setZoomState(idx, initialZoomState); + }; + + // Reset view (zoom/pan only — preserves profile, FFT state, etc.) + const handleResetAll = () => { + if (lockView) return; + setZoomStates(new Map()); + setLinkedZoomState(initialZoomState); + setGalleryFftStates(new Map()); + setLinkedFftZoomState({ zoom: DEFAULT_FFT_ZOOM, panX: 0, panY: 0 }); + setFftZoom(DEFAULT_FFT_ZOOM); + setFftPanX(0); + setFftPanY(0); + }; + + // FFT zoom/pan handlers + const handleFftWheel = (e: React.WheelEvent) => { + if (lockView) return; + e.preventDefault(); // Prevent page scroll when zooming FFT + const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1; + setFftZoom(Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, fftZoom * zoomFactor))); + }; + + const handleFftDoubleClick = () => { + if (lockView) return; + setFftZoom(DEFAULT_FFT_ZOOM); + setFftPanX(0); + setFftPanY(0); + setFftClickInfo(null); + }; + + // Convert FFT canvas mouse position to FFT image pixel coordinates + const fftScreenToImg = (e: React.MouseEvent): { col: number; row: number } | null => { + const canvas = fftCanvasRef.current; + if (!canvas) return null; + const rect = canvas.getBoundingClientRect(); + const mouseX = e.clientX - rect.left; + const mouseY = e.clientY - rect.top; + const cOffX = (canvasW - canvasW * fftZoom) / 2 + fftPanX; + const cOffY = (canvasH - canvasH * fftZoom) / 2 + fftPanY; + const fftW = fftCropDims?.fftWidth ?? width; + const fftH = fftCropDims?.fftHeight ?? height; + const imgCol = ((mouseX - cOffX) / fftZoom) / canvasW * fftW; + const imgRow = ((mouseY - cOffY) / fftZoom) / canvasH * fftH; + if (imgCol >= 0 && imgCol < fftW && imgRow >= 0 && imgRow < fftH) { + return { col: imgCol, row: imgRow }; + } + return null; + }; + + const handleFftMouseDown = (e: React.MouseEvent) => { + if (lockView) return; + fftClickStartRef.current = { x: e.clientX, y: e.clientY }; + setIsDraggingFftPan(true); + setFftPanStart({ x: e.clientX, y: e.clientY, pX: fftPanX, pY: fftPanY }); + }; + + const handleFftMouseMove = (e: React.MouseEvent) => { + if (!isDraggingFftPan || !fftPanStart) return; + const dx = e.clientX - fftPanStart.x; + const dy = e.clientY - fftPanStart.y; + setFftPanX(fftPanStart.pX + dx); + setFftPanY(fftPanStart.pY + dy); + }; + + const handleFftMouseUp = (e: React.MouseEvent) => { + // Click detection for d-spacing measurement + if (fftClickStartRef.current) { + const dx = e.clientX - fftClickStartRef.current.x; + const dy = e.clientY - fftClickStartRef.current.y; + if (Math.sqrt(dx * dx + dy * dy) < 3) { + const pos = fftScreenToImg(e); + if (pos) { + // Use crop dimensions when ROI FFT is active + const fftW = fftCropDims?.fftWidth ?? width; + const fftH = fftCropDims?.fftHeight ?? height; + let imgCol = pos.col; + let imgRow = pos.row; + // Snap to nearest Bragg spot (local max in FFT magnitude) + if (fftMagCacheRef.current) { + const snapped = findFFTPeak(fftMagCacheRef.current, fftW, fftH, imgCol, imgRow, FFT_SNAP_RADIUS); + imgCol = snapped.col; + imgRow = snapped.row; + } + const halfW = Math.floor(fftW / 2); + const halfH = Math.floor(fftH / 2); + const dcol = imgCol - halfW; + const drow = imgRow - halfH; + const distPx = Math.sqrt(dcol * dcol + drow * drow); + if (distPx < 1) { + setFftClickInfo(null); + } else { + let spatialFreq: number | null = null; + let dSpacing: number | null = null; + if (pixelSize > 0) { + const paddedW = nextPow2(fftW); + const paddedH = nextPow2(fftH); + const binC = ((Math.round(imgCol) - halfW) % fftW + fftW) % fftW; + const binR = ((Math.round(imgRow) - halfH) % fftH + fftH) % fftH; + const freqC = binC <= paddedW / 2 ? binC / (paddedW * pixelSize) : (binC - paddedW) / (paddedW * pixelSize); + const freqR = binR <= paddedH / 2 ? binR / (paddedH * pixelSize) : (binR - paddedH) / (paddedH * pixelSize); + spatialFreq = Math.sqrt(freqC * freqC + freqR * freqR); + dSpacing = spatialFreq > 0 ? 1 / spatialFreq : null; + } + setFftClickInfo({ row: imgRow, col: imgCol, distPx, spatialFreq, dSpacing }); + } + } + } + fftClickStartRef.current = null; + } + setIsDraggingFftPan(false); + setFftPanStart(null); + }; + + const handleFftMouseLeave = () => { + fftClickStartRef.current = null; + setIsDraggingFftPan(false); + setFftPanStart(null); + }; + + // Gallery FFT zoom/pan handlers (only selected image's FFT responds) + const handleGalleryFftWheel = (e: React.WheelEvent, idx: number) => { + if (lockView) return; + if (isGallery && idx !== selectedIdx && !linkedZoom) return; + e.preventDefault(); // Prevent page scroll when zooming FFT + const zs = getGalleryFftState(idx); + const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1; + setGalleryFftState(idx, { ...zs, zoom: Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, zs.zoom * zoomFactor)) }); + }; + + const handleGalleryFftMouseDown = (e: React.MouseEvent, idx: number) => { + if (isGallery && idx !== selectedIdx) { + if (lockNavigation) return; + setSelectedIdx(idx); + return; // Select first, don't start panning + } + if (lockView) return; + const zs = getGalleryFftState(idx); + setFftPanningIdx(idx); + setIsDraggingFftPan(true); + setFftPanStart({ x: e.clientX, y: e.clientY, pX: zs.panX, pY: zs.panY }); + }; + + const handleGalleryFftMouseMove = (e: React.MouseEvent, idx: number) => { + if (!isDraggingFftPan || !fftPanStart || fftPanningIdx !== idx) return; + const dx = e.clientX - fftPanStart.x; + const dy = e.clientY - fftPanStart.y; + const zs = getGalleryFftState(idx); + setGalleryFftState(idx, { ...zs, panX: fftPanStart.pX + dx, panY: fftPanStart.pY + dy }); + }; + + const handleGalleryFftMouseUp = () => { + setIsDraggingFftPan(false); + setFftPanStart(null); + setFftPanningIdx(null); + }; + + // Track which image is being panned + const [panningIdx, setPanningIdx] = React.useState(null); + const clickStartRef = React.useRef<{ x: number; y: number } | null>(null); + const [draggingProfileEndpoint, setDraggingProfileEndpoint] = React.useState<0 | 1 | null>(null); + const [isDraggingProfileLine, setIsDraggingProfileLine] = React.useState(false); + const [hoveredProfileEndpoint, setHoveredProfileEndpoint] = React.useState<0 | 1 | null>(null); + const [isHoveringProfileLine, setIsHoveringProfileLine] = React.useState(false); + const profileDragStartRef = React.useRef<{ row: number; col: number; p0: { row: number; col: number }; p1: { row: number; col: number } } | null>(null); + + const screenToImg = (e: React.MouseEvent, idx: number): { imgCol: number; imgRow: number } => { + const canvas = canvasRefs.current[idx]; + if (!canvas) return { imgCol: 0, imgRow: 0 }; + const rect = canvas.getBoundingClientRect(); + const mouseCanvasX = (e.clientX - rect.left) * (canvas.width / rect.width); + const mouseCanvasY = (e.clientY - rect.top) * (canvas.height / rect.height); + const zs = getZoomState(idx); + const cx = canvasW / 2; + const cy = canvasH / 2; + return { + imgCol: ((mouseCanvasX - cx - zs.panX) / zs.zoom + cx) / displayScale, + imgRow: ((mouseCanvasY - cy - zs.panY) / zs.zoom + cy) / displayScale, + }; + }; + + const updateAllProfileData = (p0: { row: number; col: number }, p1: { row: number; col: number }) => { + if (!rawDataRef.current) return; + const allProfiles: (Float32Array | null)[] = []; + for (let j = 0; j < rawDataRef.current.length; j++) { + const raw = rawDataRef.current[j]; + allProfiles.push(raw ? sampleLineProfile(raw, width, height, p0.row, p0.col, p1.row, p1.col) : null); + } + setProfileDataAll(allProfiles); + }; + + const updateROI = (e: React.MouseEvent, idx: number) => { + const { imgCol, imgRow } = screenToImg(e, idx); + updateSelectedRoi({ col: Math.max(0, Math.min(width - 1, Math.floor(imgCol))), row: Math.max(0, Math.min(height - 1, Math.floor(imgRow))) }); + }; + + const hitTestROI = (imgCol: number, imgRow: number): number => { + if (!roiActive || !roiList) return -1; + for (let ri = roiList.length - 1; ri >= 0; ri--) { + const roi = roiList[ri]; + const shape = roi.shape || "circle"; + if (shape === "circle" || shape === "annular") { + if (Math.sqrt((imgCol - roi.col) ** 2 + (imgRow - roi.row) ** 2) <= roi.radius) return ri; + } else if (shape === "square") { + if (Math.abs(imgCol - roi.col) <= roi.radius && Math.abs(imgRow - roi.row) <= roi.radius) return ri; + } else if (shape === "rectangle") { + if (Math.abs(imgCol - roi.col) <= roi.width / 2 && Math.abs(imgRow - roi.row) <= roi.height / 2) return ri; + } + } + return -1; + }; + + const getHitArea = () => { + const zoom = (getZoomState(selectedIdx)).zoom; + return RESIZE_HIT_AREA_PX / (displayScale * zoom); + }; + + const isNearEdge = (imgCol: number, imgRow: number, roi: ROIItem): boolean => { + const hitArea = getHitArea(); + const shape = roi.shape || "circle"; + if (shape === "circle" || shape === "annular") { + const dist = Math.sqrt((imgCol - roi.col) ** 2 + (imgRow - roi.row) ** 2); + return Math.abs(dist - roi.radius) < hitArea; + } + if (shape === "square") { + const dx = Math.abs(imgCol - roi.col); + const dy = Math.abs(imgRow - roi.row); + const r = roi.radius; + return (dx <= r + hitArea && dy <= r + hitArea) && (Math.abs(dx - r) < hitArea || Math.abs(dy - r) < hitArea); + } + if (shape === "rectangle") { + const dx = Math.abs(imgCol - roi.col); + const dy = Math.abs(imgRow - roi.row); + const hw = roi.width / 2; + const hh = roi.height / 2; + return (dx <= hw + hitArea && dy <= hh + hitArea) && (Math.abs(dx - hw) < hitArea || Math.abs(dy - hh) < hitArea); + } + return false; + }; + + const isNearResizeHandle = (imgCol: number, imgRow: number): boolean => { + if (!roiActive || !selectedRoi) return false; + return isNearEdge(imgCol, imgRow, selectedRoi); + }; + + const isNearAnyEdge = (imgCol: number, imgRow: number): boolean => { + if (!roiActive || !roiList) return false; + return roiList.some(roi => isNearEdge(imgCol, imgRow, roi)); + }; + + const isNearResizeHandleInner = (imgCol: number, imgRow: number): boolean => { + if (!roiActive || !selectedRoi || selectedRoi.shape !== "annular") return false; + const hitArea = getHitArea(); + const dist = Math.sqrt((imgCol - selectedRoi.col) ** 2 + (imgRow - selectedRoi.row) ** 2); + return Math.abs(dist - selectedRoi.radius_inner) < hitArea; + }; + + const handleMouseDown = (e: React.MouseEvent, idx: number) => { + const zs = getZoomState(idx); + if (isGallery && idx !== selectedIdx) { + if (lockNavigation) return; + setSelectedIdx(idx); + // Continue to pan setup so click-drag on unselected panel pans immediately + // (no double-click required to select first then drag). + } + // Check if click is on the lens inset — edge = resize, interior = drag + if (!lockDisplay && showLens && !isGallery && idx === 0) { + const canvas = canvasRefs.current[0]; + if (canvas) { + const rect = canvas.getBoundingClientRect(); + const cssX = e.clientX - rect.left; + const cssY = e.clientY - rect.top; + const margin = 12; + const lx = lensAnchor ? lensAnchor.x : margin; + const ly = lensAnchor ? lensAnchor.y : canvasH - lensDisplaySize - margin - 20; + if (cssX >= lx && cssX <= lx + lensDisplaySize && cssY >= ly && cssY <= ly + lensDisplaySize) { + const edgeHit = 8; + const nearEdge = cssX - lx < edgeHit || lx + lensDisplaySize - cssX < edgeHit || cssY - ly < edgeHit || ly + lensDisplaySize - cssY < edgeHit; + if (nearEdge) { + setIsResizingLens(true); + lensResizeStartRef.current = { my: e.clientY, startSize: lensDisplaySize }; + } else { + setIsDraggingLens(true); + lensDragStartRef.current = { mx: e.clientX, my: e.clientY, ax: lx, ay: ly }; + } + e.preventDefault(); + return; + } + } + } + clickStartRef.current = { x: e.clientX, y: e.clientY }; + if (profileActive && !lockProfile) { + const { imgCol, imgRow } = screenToImg(e, idx); + if (profilePoints.length === 2) { + const p0 = profilePoints[0]; + const p1 = profilePoints[1]; + const hitRadius = 10 / (displayScale * zs.zoom); + const d0 = Math.sqrt((imgCol - p0.col) ** 2 + (imgRow - p0.row) ** 2); + const d1 = Math.sqrt((imgCol - p1.col) ** 2 + (imgRow - p1.row) ** 2); + if (d0 <= hitRadius || d1 <= hitRadius) { + setDraggingProfileEndpoint(d0 <= d1 ? 0 : 1); + setIsDraggingPan(false); + setPanStart(null); + setPanningIdx(null); + return; + } + if (pointToSegmentDistance(imgCol, imgRow, p0.col, p0.row, p1.col, p1.row) <= hitRadius) { + setIsDraggingProfileLine(true); + profileDragStartRef.current = { + row: imgRow, + col: imgCol, + p0: { row: p0.row, col: p0.col }, + p1: { row: p1.row, col: p1.col }, + }; + setIsDraggingPan(false); + setPanStart(null); + setPanningIdx(null); + return; + } + } + if (!lockView) { + setIsDraggingPan(true); + setPanningIdx(idx); + setPanStart({ x: e.clientX, y: e.clientY, pX: zs.panX, pY: zs.panY }); + } + return; + } + if (roiActive) { + if (lockRoi) { + if (!lockView) { + setIsDraggingPan(true); + setPanningIdx(idx); + setPanStart({ x: e.clientX, y: e.clientY, pX: zs.panX, pY: zs.panY }); + } + return; + } + const { imgCol, imgRow } = screenToImg(e, idx); + // Check resize handles on selected ROI first + if (isNearResizeHandleInner(imgCol, imgRow)) { + setIsDraggingResizeInner(true); + return; + } + if (isNearResizeHandle(imgCol, imgRow)) { + e.preventDefault(); + resizeAspectRef.current = selectedRoi && (selectedRoi.shape === "rectangle") && selectedRoi.width > 0 && selectedRoi.height > 0 ? selectedRoi.width / selectedRoi.height : null; + setIsDraggingResize(true); + return; + } + // Check edge of any ROI — auto-select and start resize + if (roiList) { + for (let ri = 0; ri < roiList.length; ri++) { + if (isNearEdge(imgCol, imgRow, roiList[ri])) { + e.preventDefault(); + const roi = roiList[ri]; + resizeAspectRef.current = roi && (roi.shape === "rectangle") && roi.width > 0 && roi.height > 0 ? roi.width / roi.height : null; + setRoiSelectedIdx(ri); + setIsDraggingResize(true); + return; + } + } + } + // Hit-test existing ROIs (click inside to select + drag) + const hitIdx = hitTestROI(imgCol, imgRow); + if (hitIdx >= 0) { + setRoiSelectedIdx(hitIdx); + setIsDraggingROI(true); + return; + } + // Click on empty space — deselect and allow panning + setRoiSelectedIdx(-1); + } + // Start panning (works in both ROI-active and normal modes) + { + if (lockView) return; + setIsDraggingPan(true); + setPanningIdx(idx); + setPanStart({ x: e.clientX, y: e.clientY, pX: zs.panX, pY: zs.panY }); + } + }; + + const handleMouseMove = (e: React.MouseEvent, idx: number) => { + // Fast path: during pan drag, skip all cursor/hover/lens work — just update pan + if (isDraggingPan && panStart && panningIdx !== null && !lockView) { + const canvas = canvasRefs.current[idx]; + if (!canvas || idx !== panningIdx) return; + const rect = canvas.getBoundingClientRect(); + const scaleX = canvas.width / rect.width; + const scaleY = canvas.height / rect.height; + const dx = (e.clientX - panStart.x) * scaleX; + const dy = (e.clientY - panStart.y) * scaleY; + const zs = getZoomState(idx); + setZoomState(idx, { ...zs, panX: panStart.pX + dx, panY: panStart.pY + dy }); + return; + } + + // Cursor readout: convert screen position to image pixel coordinates + const canvas = canvasRefs.current[idx]; + if (canvas && rawDataRef.current) { + const rect = canvas.getBoundingClientRect(); + const mouseCanvasX = (e.clientX - rect.left) * (canvas.width / rect.width); + const mouseCanvasY = (e.clientY - rect.top) * (canvas.height / rect.height); + const zs = getZoomState(idx); + const cx = canvasW / 2; + const cy = canvasH / 2; + const imageCanvasX = (mouseCanvasX - cx - zs.panX) / zs.zoom + cx; + const imageCanvasY = (mouseCanvasY - cy - zs.panY) / zs.zoom + cy; + const imgX = Math.floor(imageCanvasX / displayScale); + const imgY = Math.floor(imageCanvasY / displayScale); + if (imgX >= 0 && imgX < width && imgY >= 0 && imgY < height) { + const rawData = rawDataRef.current[idx]; + if (rawData) setCursorInfo({ row: imgY, col: imgX, value: rawData[imgY * width + imgX] }); + if (!lockDisplay && showLens && !isGallery) setLensPos({ row: imgY, col: imgX }); + } else { + setCursorInfo(null); + // Don't clear lensPos — lens stays at last position when toggle is on + } + } + + // Lens drag + if (!lockDisplay && isDraggingLens && lensDragStartRef.current) { + const dx = e.clientX - lensDragStartRef.current.mx; + const dy = e.clientY - lensDragStartRef.current.my; + setLensAnchor({ x: lensDragStartRef.current.ax + dx, y: lensDragStartRef.current.ay + dy }); + return; + } + // Lens resize drag + if (!lockDisplay && isResizingLens && lensResizeStartRef.current) { + const dy = e.clientY - lensResizeStartRef.current.my; + setLensDisplaySize(Math.max(64, Math.min(256, lensResizeStartRef.current.startSize + dy))); + return; + } + + if (profileActive && !lockProfile && profilePoints.length === 2) { + const { imgCol, imgRow } = screenToImg(e, idx); + const p0 = profilePoints[0]; + const p1 = profilePoints[1]; + const activeZoom = linkedZoom ? linkedZoomState.zoom : (zoomStates.get(idx) || initialZoomState).zoom; + const hitRadius = 10 / (displayScale * activeZoom); + const d0 = Math.sqrt((imgCol - p0.col) ** 2 + (imgRow - p0.row) ** 2); + const d1 = Math.sqrt((imgCol - p1.col) ** 2 + (imgRow - p1.row) ** 2); + if (draggingProfileEndpoint !== null) { + const clampedRow = Math.max(0, Math.min(height - 1, imgRow)); + const clampedCol = Math.max(0, Math.min(width - 1, imgCol)); + const next = [ + draggingProfileEndpoint === 0 ? { row: clampedRow, col: clampedCol } : profilePoints[0], + draggingProfileEndpoint === 1 ? { row: clampedRow, col: clampedCol } : profilePoints[1], + ]; + setProfilePoints(next); + updateAllProfileData(next[0], next[1]); + return; + } + if (isDraggingProfileLine && profileDragStartRef.current) { + const drag = profileDragStartRef.current; + let deltaRow = imgRow - drag.row; + let deltaCol = imgCol - drag.col; + const minRow = Math.min(drag.p0.row, drag.p1.row); + const maxRow = Math.max(drag.p0.row, drag.p1.row); + const minCol = Math.min(drag.p0.col, drag.p1.col); + const maxCol = Math.max(drag.p0.col, drag.p1.col); + deltaRow = Math.max(deltaRow, -minRow); + deltaRow = Math.min(deltaRow, (height - 1) - maxRow); + deltaCol = Math.max(deltaCol, -minCol); + deltaCol = Math.min(deltaCol, (width - 1) - maxCol); + const next = [ + { row: drag.p0.row + deltaRow, col: drag.p0.col + deltaCol }, + { row: drag.p1.row + deltaRow, col: drag.p1.col + deltaCol }, + ]; + setProfilePoints(next); + updateAllProfileData(next[0], next[1]); + return; + } + const nextHoveredEndpoint: 0 | 1 | null = d0 <= hitRadius ? 0 : d1 <= hitRadius ? 1 : null; + const nextHoverLine = nextHoveredEndpoint === null && pointToSegmentDistance(imgCol, imgRow, p0.col, p0.row, p1.col, p1.row) <= hitRadius; + setHoveredProfileEndpoint(nextHoveredEndpoint); + setIsHoveringProfileLine(nextHoverLine); + } else { + if (hoveredProfileEndpoint !== null) setHoveredProfileEndpoint(null); + if (isHoveringProfileLine) setIsHoveringProfileLine(false); + } + + // ROI resize drag (inner annular ring) + if (!lockRoi && isDraggingResizeInner && selectedRoi) { + const { imgCol: ic, imgRow: ir } = screenToImg(e, idx); + const newR = Math.sqrt((ic - selectedRoi.col) ** 2 + (ir - selectedRoi.row) ** 2); + updateSelectedRoi({ radius_inner: Math.max(1, Math.min(selectedRoi.radius - 1, Math.round(newR))) }); + return; + } + // ROI resize drag (outer) + if (!lockRoi && isDraggingResize && selectedRoi) { + const { imgCol: ic, imgRow: ir } = screenToImg(e, idx); + const shape = selectedRoi.shape || "circle"; + if (shape === "rectangle") { + let newW = Math.max(2, Math.round(Math.abs(ic - selectedRoi.col) * 2)); + let newH = Math.max(2, Math.round(Math.abs(ir - selectedRoi.row) * 2)); + if (e.shiftKey && resizeAspectRef.current != null) { + const aspect = resizeAspectRef.current; + if (newW / newH > aspect) newH = Math.max(2, Math.round(newW / aspect)); + else newW = Math.max(2, Math.round(newH * aspect)); + } + updateSelectedRoi({ width: newW, height: newH }); + } else { + const newR = shape === "square" ? Math.max(Math.abs(ic - selectedRoi.col), Math.abs(ir - selectedRoi.row)) : Math.sqrt((ic - selectedRoi.col) ** 2 + (ir - selectedRoi.row) ** 2); + const minR = shape === "annular" ? selectedRoi.radius_inner + 1 : 1; + updateSelectedRoi({ radius: Math.max(minR, Math.round(newR)) }); + } + return; + } + // ROI drag (move center) + if (!lockRoi && isDraggingROI) { + updateROI(e, idx); + return; + } + // Lens edge hover detection + if (!lockDisplay && showLens && !isGallery && canvas) { + const rect = canvas.getBoundingClientRect(); + const cssX = e.clientX - rect.left; + const cssY = e.clientY - rect.top; + const margin = 12; + const lx = lensAnchor ? lensAnchor.x : margin; + const ly = lensAnchor ? lensAnchor.y : canvasH - lensDisplaySize - margin - 20; + const inside = cssX >= lx && cssX <= lx + lensDisplaySize && cssY >= ly && cssY <= ly + lensDisplaySize; + const edgeHit = 8; + const nearEdge = inside && (cssX - lx < edgeHit || lx + lensDisplaySize - cssX < edgeHit || cssY - ly < edgeHit || ly + lensDisplaySize - cssY < edgeHit); + setIsHoveringLensEdge(nearEdge); + } else { + setIsHoveringLensEdge(false); + } + // Hover detection for resize handles (show cursor on any ROI edge) + if (roiActive && !lockRoi && !isDraggingPan) { + const { imgCol: ic, imgRow: ir } = screenToImg(e, idx); + setIsHoveringResizeInner(isNearResizeHandleInner(ic, ir)); + setIsHoveringResize(isNearAnyEdge(ic, ir)); + } + + // Panning + if (lockView) return; + if (!isDraggingPan || !panStart || panningIdx === null) return; + if (idx !== panningIdx) return; + if (!canvas) return; + const rect2 = canvas.getBoundingClientRect(); + const scaleX = canvas.width / rect2.width; + const scaleY = canvas.height / rect2.height; + const dx = (e.clientX - panStart.x) * scaleX; + const dy = (e.clientY - panStart.y) * scaleY; + + const zs = getZoomState(idx); + setZoomState(idx, { ...zs, panX: panStart.pX + dx, panY: panStart.pY + dy }); + }; + + const handleMouseUp = (e: React.MouseEvent, idx: number) => { + if (isDraggingLens) { + setIsDraggingLens(false); + lensDragStartRef.current = null; + return; + } + if (isResizingLens) { + setIsResizingLens(false); + lensResizeStartRef.current = null; + return; + } + if (draggingProfileEndpoint !== null || isDraggingProfileLine) { + setDraggingProfileEndpoint(null); + setIsDraggingProfileLine(false); + profileDragStartRef.current = null; + clickStartRef.current = null; + setIsDraggingROI(false); + setIsDraggingResize(false); + setIsDraggingResizeInner(false); + setIsDraggingPan(false); + setPanStart(null); + setPanningIdx(null); + setHoveredProfileEndpoint(null); + setIsHoveringProfileLine(false); + return; + } + // Detect click (vs drag) for profile mode + if (profileActive && !lockProfile && clickStartRef.current) { + const dx = e.clientX - clickStartRef.current.x; + const dy = e.clientY - clickStartRef.current.y; + if (Math.sqrt(dx * dx + dy * dy) < 3) { + // It's a click — compute image coordinates + const canvas = canvasRefs.current[idx]; + if (canvas && rawDataRef.current) { + const rect = canvas.getBoundingClientRect(); + const mouseCanvasX = (e.clientX - rect.left) * (canvas.width / rect.width); + const mouseCanvasY = (e.clientY - rect.top) * (canvas.height / rect.height); + const zs = getZoomState(idx); + const cx = canvasW / 2; + const cy = canvasH / 2; + const imgX = ((mouseCanvasX - cx - zs.panX) / zs.zoom + cx) / displayScale; + const imgY = ((mouseCanvasY - cy - zs.panY) / zs.zoom + cy) / displayScale; + if (imgX >= 0 && imgX < width && imgY >= 0 && imgY < height) { + const pt = { row: imgY, col: imgX }; + if (profilePoints.length === 0 || profilePoints.length === 2) { + // Start new line + setProfilePoints([pt]); + setProfileDataAll([]); + } else { + // Complete the line + const p0 = profilePoints[0]; + setProfilePoints([p0, pt]); + updateAllProfileData(p0, pt); + } + } + } + } + } + // Detect click for measurement mode (only when profile is not active) + if (measureActive && !profileActive && clickStartRef.current) { + const dx = e.clientX - clickStartRef.current.x; + const dy = e.clientY - clickStartRef.current.y; + if (Math.sqrt(dx * dx + dy * dy) < 3) { + const canvas = canvasRefs.current[idx]; + if (canvas) { + const rect = canvas.getBoundingClientRect(); + const mouseCanvasX = (e.clientX - rect.left) * (canvas.width / rect.width); + const mouseCanvasY = (e.clientY - rect.top) * (canvas.height / rect.height); + const zs = getZoomState(idx); + const cx = canvasW / 2; + const cy = canvasH / 2; + const imgX = ((mouseCanvasX - cx - zs.panX) / zs.zoom + cx) / displayScale; + const imgY = ((mouseCanvasY - cy - zs.panY) / zs.zoom + cy) / displayScale; + if (imgX >= 0 && imgX < width && imgY >= 0 && imgY < height) { + const pt = { row: imgY, col: imgX }; + if (measurePoints.length < 2) { + setMeasurePoints([...measurePoints, pt]); + } else { + setMeasurePoints([pt]); + } + } + } + } + } + clickStartRef.current = null; + setDraggingProfileEndpoint(null); + setIsDraggingProfileLine(false); + profileDragStartRef.current = null; + setIsDraggingROI(false); + setIsDraggingResize(false); + setIsDraggingResizeInner(false); + setIsDraggingPan(false); + setPanStart(null); + setPanningIdx(null); + setHoveredProfileEndpoint(null); + setIsHoveringProfileLine(false); + }; + + const handleMouseLeave = (idx: number) => { + setCursorInfo(null); + // Don't clear lensPos — lens stays at last position when toggle is on + setIsDraggingLens(false); + setIsResizingLens(false); + lensDragStartRef.current = null; + lensResizeStartRef.current = null; + setIsHoveringLensEdge(false); + setIsDraggingROI(false); + setIsDraggingResize(false); + setIsDraggingResizeInner(false); + setDraggingProfileEndpoint(null); + setIsDraggingProfileLine(false); + setHoveredProfileEndpoint(null); + setIsHoveringProfileLine(false); + profileDragStartRef.current = null; + setIsHoveringResize(false); + setIsHoveringResizeInner(false); + if (panningIdx === idx) { + setIsDraggingPan(false); + setPanStart(null); + setPanningIdx(null); + } + }; + + // ------------------------------------------------------------------------- + // Copy to clipboard handler + const handleCopy = React.useCallback(async () => { + if (lockExport) return; + const canvas = canvasRefs.current[isGallery ? selectedIdx : 0]; + if (!canvas) return; + try { + const blob = await new Promise(resolve => canvas.toBlob(resolve, "image/png")); + if (!blob) return; + await navigator.clipboard.write([new ClipboardItem({ "image/png": blob })]); + } catch { + // Fallback: download if clipboard API unavailable + canvas.toBlob((b) => { if (b) downloadBlob(b, `show2d_${labels?.[selectedIdx] || "image"}.png`); }, "image/png"); + } + }, [isGallery, selectedIdx, labels, lockExport]); + + // Export publication-quality figure with scale bar, colorbar, annotations + const handleExportFigure = React.useCallback((withScaleBar: boolean, withColorbar: boolean) => { + if (lockExport) return; + setExportAnchor(null); + const idx = isGallery ? selectedIdx : 0; + const rawData = rawDataRef.current?.[idx]; + if (!rawData) return; + + const processed = logScale ? applyLogScale(rawData) : rawData; + const lut = COLORMAPS[cmap] || COLORMAPS.inferno; + + let vmin: number, vmax: number; + const hasAbsRange = traitVmin != null && traitVmax != null; + const rMin = hasAbsRange ? (logScale ? Math.log1p(Math.max(traitVmin!, 0)) : traitVmin!) : imageDataRange.min; + const rMax = hasAbsRange ? (logScale ? Math.log1p(Math.max(traitVmax!, 0)) : traitVmax!) : imageDataRange.max; + if (rMin !== rMax && (imageVminPct > 0 || imageVmaxPct < 100)) { + ({ vmin, vmax } = sliderRange(rMin, rMax, imageVminPct, imageVmaxPct)); + } else if (!hasAbsRange && autoContrast) { + ({ vmin, vmax } = percentileClip(processed, 2, 98)); + } else { + vmin = rMin; + vmax = rMax; + } + + const offscreen = renderToOffscreen(processed, width, height, lut, vmin, vmax); + if (!offscreen) return; + + const figCanvas = exportFigure({ + imageCanvas: offscreen, + title: title || undefined, + lut, + vmin, + vmax, + logScale, + pixelSize: pixelSize > 0 ? pixelSize : undefined, + showColorbar: withColorbar, + showScaleBar: withScaleBar && pixelSize > 0, + drawAnnotations: (ctx) => { + // ROI highlight mask + if (roiActive && roiList) { + const hlRois = roiList.filter(r => r.highlight); + if (hlRois.length > 0) { + ctx.save(); + ctx.fillStyle = "rgba(0,0,0,0.6)"; + ctx.fillRect(0, 0, width, height); + ctx.globalCompositeOperation = "destination-out"; + for (const roi of hlRois) { + ctx.fillStyle = "rgba(0,0,0,1)"; + const shape = roi.shape || "circle"; + if (shape === "circle") { ctx.beginPath(); ctx.arc(roi.col, roi.row, roi.radius, 0, Math.PI * 2); ctx.fill(); } + else if (shape === "square") { ctx.fillRect(roi.col - roi.radius, roi.row - roi.radius, roi.radius * 2, roi.radius * 2); } + else if (shape === "rectangle") { ctx.fillRect(roi.col - roi.width / 2, roi.row - roi.height / 2, roi.width, roi.height); } + else if (shape === "annular") { + ctx.beginPath(); ctx.arc(roi.col, roi.row, roi.radius, 0, Math.PI * 2); ctx.fill(); + ctx.globalCompositeOperation = "source-over"; + ctx.fillStyle = "rgba(0,0,0,0.6)"; + ctx.beginPath(); ctx.arc(roi.col, roi.row, roi.radius_inner, 0, Math.PI * 2); ctx.fill(); + ctx.globalCompositeOperation = "destination-out"; + } + } + ctx.restore(); + } + // ROI outlines + for (const roi of roiList) { + const shape = (roi.shape || "circle") as "circle" | "square" | "rectangle" | "annular"; + ctx.lineWidth = roi.line_width || 2; + drawROI(ctx, roi.col, roi.row, shape, roi.radius, roi.width, roi.height, roi.color, roi.color, false, roi.radius_inner); + } + } + // Profile line + if (profileActive && profilePoints.length === 2) { + ctx.strokeStyle = "#4fc3f7"; + ctx.lineWidth = 2; + ctx.setLineDash([4, 3]); + ctx.beginPath(); + ctx.moveTo(profilePoints[0].col, profilePoints[0].row); + ctx.lineTo(profilePoints[1].col, profilePoints[1].row); + ctx.stroke(); + ctx.setLineDash([]); + ctx.fillStyle = "#4fc3f7"; + ctx.beginPath(); + ctx.arc(profilePoints[0].col, profilePoints[0].row, 3, 0, Math.PI * 2); + ctx.fill(); + ctx.beginPath(); + ctx.arc(profilePoints[1].col, profilePoints[1].row, 3, 0, Math.PI * 2); + ctx.fill(); + } + }, + }); + + canvasToPDF(figCanvas).then((blob) => downloadBlob(blob, `show2d_figure_${labels?.[selectedIdx] || "image"}.pdf`)); + }, [isGallery, selectedIdx, labels, width, height, cmap, logScale, autoContrast, imageDataRange, imageVminPct, imageVmaxPct, pixelSize, title, roiActive, roiList, profileActive, profilePoints, lockExport]); + + // Export all variants (PNG + PDF) as zip + const handleExportAll = React.useCallback(async () => { + if (lockExport) return; + setExportAnchor(null); + const idx = isGallery ? selectedIdx : 0; + const rawData = rawDataRef.current?.[idx]; + if (!rawData) return; + + const processed = logScale ? applyLogScale(rawData) : rawData; + const lut = COLORMAPS[cmap] || COLORMAPS.inferno; + + let vmin: number, vmax: number; + const hasAbsRange2 = traitVmin != null && traitVmax != null; + const rMin2 = hasAbsRange2 ? (logScale ? Math.log1p(Math.max(traitVmin!, 0)) : traitVmin!) : imageDataRange.min; + const rMax2 = hasAbsRange2 ? (logScale ? Math.log1p(Math.max(traitVmax!, 0)) : traitVmax!) : imageDataRange.max; + if (rMin2 !== rMax2 && (imageVminPct > 0 || imageVmaxPct < 100)) { + ({ vmin, vmax } = sliderRange(rMin2, rMax2, imageVminPct, imageVmaxPct)); + } else if (!hasAbsRange2 && autoContrast) { + ({ vmin, vmax } = percentileClip(processed, 2, 98)); + } else { + vmin = rMin2; + vmax = rMax2; + } + + const offscreen = renderToOffscreen(processed, width, height, lut, vmin, vmax); + if (!offscreen) return; + + const drawAnnotations = (ctx: CanvasRenderingContext2D) => { + if (roiActive && roiList) { + const hlRois = roiList.filter(r => r.highlight); + if (hlRois.length > 0) { + ctx.save(); + ctx.fillStyle = "rgba(0,0,0,0.6)"; + ctx.fillRect(0, 0, width, height); + ctx.globalCompositeOperation = "destination-out"; + for (const roi of hlRois) { + ctx.fillStyle = "rgba(0,0,0,1)"; + const shape = roi.shape || "circle"; + if (shape === "circle") { ctx.beginPath(); ctx.arc(roi.col, roi.row, roi.radius, 0, Math.PI * 2); ctx.fill(); } + else if (shape === "square") { ctx.fillRect(roi.col - roi.radius, roi.row - roi.radius, roi.radius * 2, roi.radius * 2); } + else if (shape === "rectangle") { ctx.fillRect(roi.col - roi.width / 2, roi.row - roi.height / 2, roi.width, roi.height); } + else if (shape === "annular") { + ctx.beginPath(); ctx.arc(roi.col, roi.row, roi.radius, 0, Math.PI * 2); ctx.fill(); + ctx.globalCompositeOperation = "source-over"; + ctx.fillStyle = "rgba(0,0,0,0.6)"; + ctx.beginPath(); ctx.arc(roi.col, roi.row, roi.radius_inner, 0, Math.PI * 2); ctx.fill(); + ctx.globalCompositeOperation = "destination-out"; + } + } + ctx.restore(); + for (const roi of roiList) { + const shape = (roi.shape || "circle") as "circle" | "square" | "rectangle" | "annular"; + ctx.lineWidth = roi.line_width || 2; + drawROI(ctx, roi.col, roi.row, shape, roi.radius, roi.width, roi.height, roi.color, roi.color, false, roi.radius_inner); + } + } + } + if (profileActive && profilePoints.length === 2) { + ctx.strokeStyle = "#4fc3f7"; + ctx.lineWidth = 2; + ctx.setLineDash([4, 3]); + ctx.beginPath(); + ctx.moveTo(profilePoints[0].col, profilePoints[0].row); + ctx.lineTo(profilePoints[1].col, profilePoints[1].row); + ctx.stroke(); + ctx.setLineDash([]); + ctx.fillStyle = "#4fc3f7"; + ctx.beginPath(); ctx.arc(profilePoints[0].col, profilePoints[0].row, 3, 0, Math.PI * 2); ctx.fill(); + ctx.beginPath(); ctx.arc(profilePoints[1].col, profilePoints[1].row, 3, 0, Math.PI * 2); ctx.fill(); + } + }; + + const hasScale = pixelSize > 0; + const baseOpts = { + imageCanvas: offscreen, + title: title || undefined, + lut, + vmin, + vmax, + logScale, + pixelSize: hasScale ? pixelSize : undefined, + drawAnnotations, + }; + + const variants: { name: string; showScaleBar: boolean; showColorbar: boolean }[] = [ + { name: "figure", showScaleBar: false, showColorbar: false }, + { name: "figure_scalebar", showScaleBar: true, showColorbar: false }, + { name: "figure_scalebar_colorbar", showScaleBar: true, showColorbar: true }, + ]; + + const zip = new JSZip(); + const prefix = `show2d_${labels?.[selectedIdx] || "image"}`; + const metadata = { + metadata_version: "1.0", + widget_name: "Show2D", + widget_version: widgetVersion || "unknown", + exported_at: new Date().toISOString(), + format: "zip", + export_kind: "figure_variants", + selected_idx: idx, + image_shape: { rows: height, cols: width }, + display: { + cmap, + log_scale: logScale, + auto_contrast: autoContrast, + vmin_pct: imageVminPct, + vmax_pct: imageVmaxPct, + }, + variants, + }; + zip.file("metadata.json", JSON.stringify(metadata, null, 2)); + + for (const v of variants) { + const figCanvas = exportFigure({ ...baseOpts, showScaleBar: v.showScaleBar && hasScale, showColorbar: v.showColorbar }); + const pngBlob = await new Promise((resolve) => figCanvas.toBlob((b) => resolve(b!), "image/png")); + zip.file(`${prefix}_${v.name}.png`, pngBlob); + const pdfBlob = await canvasToPDF(figCanvas); + zip.file(`${prefix}_${v.name}.pdf`, pdfBlob); + } + + const blob = await zip.generateAsync({ type: "blob" }); + downloadBlob(blob, `${prefix}_all.zip`); + }, [isGallery, selectedIdx, labels, width, height, cmap, logScale, autoContrast, imageDataRange, imageVminPct, imageVmaxPct, pixelSize, title, roiActive, roiList, profileActive, profilePoints, widgetVersion, lockExport]); + + // Resize Handlers + // ------------------------------------------------------------------------- + const handleCanvasResizeStart = (e: React.MouseEvent) => { + if (lockView) return; + e.stopPropagation(); + e.preventDefault(); + setIsResizingCanvas(true); + setResizeStart({ x: e.clientX, y: e.clientY, size: canvasSize }); + }; + + React.useEffect(() => { + if (!isResizingCanvas) return; + let rafId = 0; + let latestSize = resizeStart ? resizeStart.size : canvasSize; + + const handleMouseMove = (e: MouseEvent) => { + if (!resizeStart) return; + const delta = Math.max(e.clientX - resizeStart.x, e.clientY - resizeStart.y); + latestSize = Math.max(200, resizeStart.size + delta); + if (!rafId) { + rafId = requestAnimationFrame(() => { + rafId = 0; + setCanvasSize(latestSize); + }); + } + }; + + const handleMouseUp = () => { + cancelAnimationFrame(rafId); + setCanvasSize(latestSize); + setIsResizingCanvas(false); + setResizeStart(null); + }; + + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + return () => { + cancelAnimationFrame(rafId); + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + }; + }, [isResizingCanvas, resizeStart]); + + // Profile height resize + React.useEffect(() => { + if (!isResizingProfile) return; + const handleMouseMove = (e: MouseEvent) => { + if (!profileResizeStart) return; + const delta = e.clientY - profileResizeStart.y; + setProfileHeight(Math.max(40, Math.min(300, profileResizeStart.height + delta))); + }; + const handleMouseUp = () => { + setIsResizingProfile(false); + setProfileResizeStart(null); + }; + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + return () => { + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + }; + }, [isResizingProfile, profileResizeStart]); + + // ------------------------------------------------------------------------- + // Keyboard shortcuts + // ------------------------------------------------------------------------- + const handleKeyDown = (e: React.KeyboardEvent) => { + // Number keys 1-9 select gallery images (avoids arrow key conflicts with Jupyter) + if (!lockNavigation && isGallery && e.key >= "1" && e.key <= "9") { + const idx = parseInt(e.key) - 1; + if (idx < nImages) { e.preventDefault(); setSelectedIdx(idx); } + return; + } + switch (e.key) { + case "ArrowLeft": + if (!lockNavigation && isGallery) { e.preventDefault(); setSelectedIdx(Math.max(0, selectedIdx - 1)); } + break; + case "ArrowRight": + if (!lockNavigation && isGallery) { e.preventDefault(); setSelectedIdx(Math.min(nImages - 1, selectedIdx + 1)); } + break; + case "r": + case "R": + if (!lockView) handleResetAll(); + break; + case "m": + case "M": + if (measureActive) { + setMeasureActive(false); + setMeasurePoints([]); + } else { + setMeasureActive(true); + setMeasurePoints([]); + } + break; + case "Escape": + if (measureActive) { + setMeasureActive(false); + setMeasurePoints([]); + } + break; + case "]": + if (!lockNavigation && !lockDisplay) { + e.preventDefault(); + const rIdx = isGallery ? selectedIdx : 0; + const rots = [...(imageRotations || [])]; + while (rots.length <= rIdx) rots.push(0); + rots[rIdx] = (rots[rIdx] + 3) % 4; + setImageRotations(rots); + } + break; + case "[": + if (!lockNavigation && !lockDisplay) { + e.preventDefault(); + const rIdx2 = isGallery ? selectedIdx : 0; + const rots2 = [...(imageRotations || [])]; + while (rots2.length <= rIdx2) rots2.push(0); + rots2[rIdx2] = (rots2[rIdx2] + 1) % 4; + setImageRotations(rots2); + } + break; + case "Delete": + case "Backspace": + if (!lockRoi && roiActive && roiSelectedIdx >= 0 && roiList && roiSelectedIdx < roiList.length) { + e.preventDefault(); + const newList = roiList.filter((_, i) => i !== roiSelectedIdx); + setRoiList(newList); + setRoiSelectedIdx(newList.length > 0 ? Math.min(roiSelectedIdx, newList.length - 1) : -1); + } + break; + } + }; + + // ------------------------------------------------------------------------- + // Render (Show3D-style layout) + // ------------------------------------------------------------------------- + const needsReset = getZoomState(isGallery ? selectedIdx : 0).zoom !== 1 || getZoomState(isGallery ? selectedIdx : 0).panX !== 0 || getZoomState(isGallery ? selectedIdx : 0).panY !== 0; + const statsIdx = isGallery ? selectedIdx : 0; + + // Calibrated cursor position + const calibratedUnit = pixelSize > 0 ? (Math.max(height, width) * pixelSize >= 10 ? "nm" : "Å") : ""; + const calibratedFactor = calibratedUnit === "nm" ? pixelSize / 10 : pixelSize; + + return ( + + + {/* Main panel */} + + {/* Title row */} + + {title || (isGallery ? "Gallery" : "Image")} + {displayBinFactor > 1 && ( + + {displayBinFactor}× binned + + )} + {(() => { const rk = (imageRotations?.[isGallery ? selectedIdx : 0] ?? 0) % 4; return rk !== 0 ? ( + { + if (lockDisplay) return; + const ri = isGallery ? selectedIdx : 0; + const rots = [...(imageRotations || [])]; + while (rots.length <= ri) rots.push(0); + rots[ri] = (rots[ri] + 3) % 4; + setImageRotations(rots); + }} + sx={{ ml: 0.5, color: themeColors.accent, cursor: lockDisplay ? "default" : "pointer", fontSize: "inherit", "&:hover": { opacity: lockDisplay ? 1 : 0.7 } }} + > + ({rk * 90}°) + + ) : null; })()} + + Controls + FFT: Show power spectrum (Fourier transform) alongside image. + Profile: Click two points on image to draw a line intensity profile. + ROI: Region of Interest — click to place, drag to move. + {!isGallery && Lens: Magnifier inset that follows the cursor.} + Auto: Percentile-based contrast (2nd–98th percentile). FFT Auto masks DC + clips to 99.9th. + {isGallery && Link Zoom / Contrast: Sync zoom or histogram range across all gallery images.} + Keyboard + + } theme={themeInfo.theme} /> + + + {/* Controls row: Profile, ROI, Lens, FFT, Export, Reset, Copy */} + + {!hideProfile && ( + <> + Profile: + { + if (lockProfile) return; + const on = e.target.checked; + setProfileActive(on); + if (on) { + if (!lockRoi) setRoiActive(false); + } else { + setProfilePoints([]); + setProfileDataAll([]); + setHoveredProfileEndpoint(null); + setIsHoveringProfileLine(false); + } + }} + size="small" + sx={switchStyles.small} + /> + + )} + {!hideRoi && !isGallery && ( + <> + ROI: + { + if (lockRoi) return; + const on = e.target.checked; + setRoiActive(on); + if (on) { + if (!lockProfile) setProfileActive(false); + setProfilePoints([]); + setProfileDataAll([]); + setHoveredProfileEndpoint(null); + setIsHoveringProfileLine(false); + } else { + setRoiSelectedIdx(-1); + } + }} + size="small" + sx={switchStyles.small} + /> + + )} + {!hideDisplay && ( + <> + {!isGallery && ( + <> + Lens: + { + if (lockDisplay) return; + if (!showLens) { + setShowLens(true); + setLensPos({ row: Math.floor(height / 2), col: Math.floor(width / 2) }); + } else { + setShowLens(false); + setLensPos(null); + } + }} + disabled={lockDisplay} + size="small" + sx={switchStyles.small} + /> + + )} + FFT: + { + if (lockDisplay) return; + const on = e.target.checked; + if (on && width * height > 2048 * 2048) { + console.warn(`Show2D: FFT on ${width}×${height} image (${(width * height / 1e6).toFixed(1)}M pixels) may be slow`); + } + setShowFft(on); + }} + disabled={lockDisplay} + size="small" + sx={switchStyles.small} + /> + {showFft && width * height > 2048 * 2048 && ( + slow ({width}×{height}) + )} + {nImages === 2 && ( + <> + Diff: + { if (!lockDisplay) setDiffMode(!diffMode); }} disabled={lockDisplay} size="small" sx={switchStyles.small} /> + + )} + + )} + + {!hideView && ( + + )} + {!hideExport && ( + <> + + setExportAnchor(null)} anchorOrigin={{ vertical: "bottom", horizontal: "left" }} transformOrigin={{ vertical: "top", horizontal: "left" }} sx={{ zIndex: 9999 }}> + handleExportFigure(true, true)} sx={{ fontSize: 12 }}>PDF + scalebar + colorbar + handleExportFigure(true, false)} sx={{ fontSize: 12 }}>PDF + scalebar + handleExportFigure(false, false)} sx={{ fontSize: 12 }}>PDF + All (PNG + PDF) + + + + )} + + + {isGallery ? ( + /* Gallery mode */ + + {Array.from({ length: nImages }).map((_, i) => ( + + { imageContainerRefs.current[i] = el; }} + sx={{ position: "relative", bgcolor: "#000", border: `2px solid ${i === selectedIdx ? themeColors.accent : themeColors.border}`, borderRadius: 0, width: canvasW, height: canvasH }} + onMouseDown={(e) => handleMouseDown(e, i)} + onMouseMove={(e) => handleMouseMove(e, i)} + onMouseUp={(e) => handleMouseUp(e, i)} + onMouseLeave={() => handleMouseLeave(i)} + onWheel={(i === selectedIdx || linkedZoom) ? (e) => handleWheel(e, i) : undefined} + onDoubleClick={() => handleDoubleClick(i)} + > + { if (el && canvasRefs.current[i] !== el) { canvasRefs.current[i] = el; setCanvasReady(c => c + 1); } }} + width={canvasW} height={canvasH} + style={{ width: canvasW, height: canvasH, imageRendering: imageRenderingStyle }} + /> + { overlayRefs.current[i] = el; }} + width={Math.round(canvasW * DPR)} height={Math.round(canvasH * DPR)} + style={{ position: "absolute", top: 0, left: 0, width: canvasW, height: canvasH, pointerEvents: "none" }} + /> + {!hideView && ( + + )} + + + {labels?.[i] || `Image ${i + 1}`} + {(imageRotations?.[i] ?? 0) % 4 !== 0 && ( + { + e.stopPropagation(); + if (lockDisplay) return; + const rots = [...(imageRotations || [])]; + while (rots.length <= i) rots.push(0); + rots[i] = (rots[i] + 3) % 4; + setImageRotations(rots); + }} + sx={{ ml: 0.5, color: themeColors.accent, cursor: lockDisplay ? "default" : "pointer", "&:hover": { opacity: lockDisplay ? 1 : 0.7 } }} + > + ({(imageRotations[i] % 4) * 90}°) + + )} + + {effectiveShowFft && ( + { fftContainerRefs.current[i] = el; }} + sx={{ mt: 0.5, position: "relative", border: `2px solid ${i === selectedIdx ? themeColors.accent : themeColors.border}`, borderRadius: 0, bgcolor: "#000", cursor: lockView ? "default" : "grab" }} + onWheel={(i === selectedIdx || linkedZoom) ? (e) => handleGalleryFftWheel(e, i) : undefined} + onDoubleClick={() => setGalleryFftState(i, { zoom: DEFAULT_FFT_ZOOM, panX: 0, panY: 0 })} + onMouseDown={(e) => handleGalleryFftMouseDown(e, i)} + onMouseMove={(e) => handleGalleryFftMouseMove(e, i)} + onMouseUp={handleGalleryFftMouseUp} + onMouseLeave={handleGalleryFftMouseUp} + > + { fftCanvasRefs.current[i] = el; }} + width={canvasW} height={canvasH} + style={{ width: canvasW, height: canvasH, imageRendering: imageRenderingStyle, display: "block" }} + /> + {fftComputing && !fftMagCacheGalleryRef.current[i] && ( + + FFT… + + )} + + )} + + ))} + {showDiffPanel && diffOtherIndices.map((otherIdx, slot) => ( + + + { diffCanvasRefs.current[slot] = el; }} + width={canvasW} height={canvasH} + style={{ width: canvasW, height: canvasH, imageRendering: imageRenderingStyle }} + /> + + + {nImages === 2 ? "Diff (A − B)" : `Diff (#${diffReference + 1} − #${otherIdx + 1})`} + + {/* FFT of diff (n=2 only) */} + {effectiveShowFft && nImages === 2 && slot === 0 && ( + + { diffFftCanvasRef.current = el; }} + width={canvasW} height={canvasH} + style={{ width: canvasW, height: canvasH, imageRendering: imageRenderingStyle, display: "block" }} + /> + + )} + + ))} + + ) : ( + /* Single image mode */ + { imageContainerRefs.current[0] = el; }} + sx={{ position: "relative", bgcolor: "#000", border: `1px solid ${themeColors.border}`, width: canvasW, height: canvasH, cursor: isHoveringLensEdge ? "nwse-resize" : isDraggingROI ? "move" : (isDraggingResize || isDraggingResizeInner || isHoveringResize || isHoveringResizeInner) ? "nwse-resize" : (draggingProfileEndpoint !== null || isDraggingProfileLine) ? "grabbing" : (profileActive && (hoveredProfileEndpoint !== null || isHoveringProfileLine)) ? "grab" : (profileActive || roiActive || measureActive) ? "crosshair" : "grab" }} + onMouseDown={(e) => handleMouseDown(e, 0)} + onMouseMove={(e) => handleMouseMove(e, 0)} + onMouseUp={(e) => handleMouseUp(e, 0)} + onMouseLeave={() => handleMouseLeave(0)} + onWheel={(e) => handleWheel(e, 0)} + onDoubleClick={() => handleDoubleClick(0)} + > + { if (el && canvasRefs.current[0] !== el) { canvasRefs.current[0] = el; setCanvasReady(c => c + 1); } }} + width={canvasW} height={canvasH} + style={{ width: canvasW, height: canvasH, imageRendering: imageRenderingStyle }} + /> + { overlayRefs.current[0] = el; }} + width={Math.round(canvasW * DPR)} height={Math.round(canvasH * DPR)} + style={{ position: "absolute", top: 0, left: 0, width: canvasW, height: canvasH, pointerEvents: "none" }} + /> + + {cursorInfo && ( + + + ({cursorInfo.row}, {cursorInfo.col}){pixelSize > 0 ? ` = (${(cursorInfo.row * calibratedFactor).toFixed(1)}, ${(cursorInfo.col * calibratedFactor).toFixed(1)} ${calibratedUnit})` : ""} {formatNumber(cursorInfo.value)} + + + )} + {!hideView && ( + + )} + + )} + + {/* Stats bar - right below canvas (Show3D style) */} + {!hideStats && showStats && ( + + {isGallery && ( + {labels?.[statsIdx] || `#${statsIdx + 1}`} + )} + Mean {formatNumber(statsMean?.[statsIdx] ?? 0)} + Min {formatNumber(statsMin?.[statsIdx] ?? 0)} + Max {formatNumber(statsMax?.[statsIdx] ?? 0)} + Std {formatNumber(statsStd?.[statsIdx] ?? 0)} + {measureActive && ( + <> + + Measuring + + )} + + )} + + {/* Gallery FFT Controls - below gallery grid */} + {effectiveShowFft && isGallery && ( + + + + FFT Scale: + + Auto: + { if (!lockDisplay) setFftAuto(e.target.checked); }} disabled={lockDisplay} size="small" sx={switchStyles.small} /> + {roiFftActive && fftCropDims && ( + <> + Win: + { if (!lockDisplay) setFftWindow(e.target.checked); }} disabled={lockDisplay} size="small" sx={switchStyles.small} /> + + )} + Color: + + + + {!hideHistogram && ( + + {fftHistogramData && ( + { if (!lockHistogram) { setFftVminPct(min); setFftVmaxPct(max); } }} width={110} height={58} theme={themeInfo.theme === "dark" ? "dark" : "light"} dataMin={fftDataRange.min} dataMax={fftDataRange.max} /> + )} + + )} + + )} + + {/* Line profile sparkline — always reserve space when profile is active */} + {!hideProfile && profileActive && ( + + +
{ + if (lockProfile) return; + e.preventDefault(); + setIsResizingProfile(true); + setProfileResizeStart({ y: e.clientY, height: profileHeight }); + }} + style={{ width: profileCanvasWidth, height: 4, cursor: lockProfile ? "default" : "ns-resize", borderLeft: `1px solid ${themeColors.border}`, borderRight: `1px solid ${themeColors.border}`, borderBottom: `1px solid ${themeColors.border}`, background: `linear-gradient(to bottom, ${themeColors.border}, transparent)`, opacity: lockProfile ? 0.5 : 1, pointerEvents: lockProfile ? "none" : "auto" }} + /> + + )} + + {/* Controls: two rows left + histogram right, ROI below */} + {showControls && ( + + {/* Top: control rows + histogram side by side */} + + + {/* Row 1: Scale + Color */} + {!hideDisplay && ( + + Scale: + + Color: + + {!isGallery && ( + <> + Colorbar: + { if (!lockDisplay) setShowColorbar(!showColorbar); }} disabled={lockDisplay} size="small" sx={switchStyles.small} /> + + )} + + )} + {/* Row 2: Auto + Lens settings + Link Zoom (gallery) + zoom indicator */} + {!hideDisplay && ( + + Auto: + { if (!lockDisplay) setAutoContrast(!autoContrast); }} disabled={lockDisplay} size="small" sx={switchStyles.small} /> + Smooth: + { if (!lockDisplay) setSmooth(!smooth); }} disabled={lockDisplay} size="small" sx={switchStyles.small} /> + {!isGallery && showLens && ( + <> + Lens {lensMag}× + setLensMag(v as number)} size="small" sx={{ ...sliderStyles.small, width: 35 }} /> + {lensDisplaySize}px + setLensDisplaySize(v as number)} size="small" sx={{ ...sliderStyles.small, width: 35 }} /> + + )} + {isGallery && ( + <> + Link: + Zoom + { if (!lockDisplay) setLinkedZoom(!linkedZoom); }} disabled={lockDisplay} size="small" sx={switchStyles.small} /> + Pan + { if (!lockDisplay) setLinkPan(!linkPan); }} disabled={lockDisplay} size="small" sx={switchStyles.small} /> + Contrast + { if (!lockDisplay) setLinkedContrast(!linkedContrast); }} disabled={lockDisplay} size="small" sx={switchStyles.small} /> + + )} + {getZoomState(isGallery ? selectedIdx : 0).zoom !== 1 && ( + {getZoomState(isGallery ? selectedIdx : 0).zoom.toFixed(1)}x + )} + + )} + + {/* Right: Histogram aligned to the two rows. When unlinked + gallery: stack one per image. */} + {!hideHistogram && (imageHistogramData || imageHistogramBins) && ( + + {(!linkedContrast && isGallery && rawDataRef.current) ? ( + Array.from({ length: nImages }).map((_, i) => { + const cs = contrastStates.get(i) || { vminPct: 0, vmaxPct: 100 }; + const raw = rawDataRef.current?.[i] || null; + return ( + { if (!lockHistogram) setContrastState(i, { vminPct: min, vmaxPct: max }); }} + width={110} height={36} theme={themeInfo.theme === "dark" ? "dark" : "light"} + dataMin={dataRangesRef.current[i]?.min ?? imageDataRange.min} + dataMax={dataRangesRef.current[i]?.max ?? imageDataRange.max} /> + ); + }) + ) : ( + { if (!lockHistogram) setContrastState(activeContrastIdx, { vminPct: min, vmaxPct: max }); }} width={110} height={58} theme={themeInfo.theme === "dark" ? "dark" : "light"} dataMin={traitVmin != null && traitVmax != null ? (logScale ? Math.log1p(Math.max(traitVmin, 0)) : traitVmin) : imageDataRange.min} dataMax={traitVmin != null && traitVmax != null ? (logScale ? Math.log1p(Math.max(traitVmax, 0)) : traitVmax) : imageDataRange.max} /> + )} + + )} + + {/* ROI Section (own box, below control rows) */} + {!hideRoi && roiActive && ( + + {/* ROI: shape + ADD + CLEAR */} + + ROI: + + + + + + {/* Selected ROI details */} + {selectedRoi && ( + + #{roiSelectedIdx + 1}/{roiList?.length ?? 0} + + {selectedRoi.shape === "rectangle" && ( + <> + W + updateSelectedRoi({ width: v as number })} size="small" sx={{ ...sliderStyles.small, width: 40 }} /> + H + updateSelectedRoi({ height: v as number })} size="small" sx={{ ...sliderStyles.small, width: 40 }} /> + + )} + {selectedRoi.shape === "annular" && ( + <> + Inner + updateSelectedRoi({ radius_inner: v as number })} size="small" sx={{ ...sliderStyles.small, width: 40 }} /> + Outer + updateSelectedRoi({ radius: v as number })} size="small" sx={{ ...sliderStyles.small, width: 40 }} /> + + )} + {selectedRoi.shape !== "rectangle" && selectedRoi.shape !== "annular" && ( + <> + Size + updateSelectedRoi({ radius: v as number })} size="small" sx={{ ...sliderStyles.small, width: 50 }} /> + + )} + + {ROI_COLORS.map(c => ( + updateSelectedRoi({ color: c })} sx={{ width: 12, height: 12, bgcolor: c, cursor: "pointer", border: c === selectedRoi.color ? `2px solid ${themeColors.text}` : "1px solid transparent", "&:hover": { opacity: 0.8 } }} /> + ))} + + Border + updateSelectedRoi({ line_width: v as number })} size="small" sx={{ ...sliderStyles.small, width: 30 }} /> + updateSelectedRoi({ highlight: !selectedRoi.highlight })} + sx={{ cursor: "pointer", fontSize: 10, color: selectedRoi.highlight ? themeColors.accentGreen : themeColors.textMuted, "&:hover": { opacity: 0.8 } }} + title="Focus (dim outside)" + >{selectedRoi.highlight ? "\u25C9 Focus" : "\u25CB Focus"} + + + )} + {/* ROI list */} + {roiList && roiList.length > 0 && ( + + {roiList.map((roi, i) => { + const c = roi.color || ROI_COLORS[i % ROI_COLORS.length]; + const isSelected = i === roiSelectedIdx; + const shapeLabel = roi.shape === "rectangle" ? `${roi.width}×${roi.height}` : roi.shape === "annular" ? `r${roi.radius_inner}-${roi.radius}` : `r${roi.radius}`; + return ( + setRoiSelectedIdx(i)} sx={{ display: "flex", alignItems: "center", gap: "3px", lineHeight: 1.6, cursor: "pointer", "&:hover .roi-delete": { opacity: 1 } }}> + + + {i + 1}{" "} + {roi.shape} ({roi.row}, {roi.col}) {shapeLabel} + + { e.stopPropagation(); const newList = roiList.map((r, j) => ({ ...r, highlight: j === i ? !r.highlight : false })); setRoiList(newList); }} + sx={{ cursor: "pointer", fontSize: 10, color: roi.highlight ? themeColors.accentGreen : themeColors.textMuted, lineHeight: 1, opacity: roi.highlight ? 1 : 0.5, "&:hover": { opacity: 1 } }} + title="Focus (dim outside)" + >{roi.highlight ? "\u25C9" : "\u25CB"} + { e.stopPropagation(); const newList = roiList.filter((_, j) => j !== i); setRoiList(newList); setRoiSelectedIdx(newList.length > 0 ? Math.min(roiSelectedIdx, newList.length - 1) : -1); }} + sx={{ opacity: 0, cursor: "pointer", fontSize: 10, color: themeColors.textMuted, ml: 0.5, lineHeight: 1, "&:hover": { color: "#f44336" } }} + >× + + ); + })} + + )} + + )} + + )} + + + {/* FFT Panel - canvas + stats (single mode only) */} + {effectiveShowFft && !isGallery && ( + + {/* Spacer — matches main panel title row height for canvas alignment */} + + {/* Controls row — matches main panel controls row height */} + + {fftComputing ? ( + + {fftProgress || "Computing FFT…"} + ) : roiFftActive && fftCropDims ? ( + + ROI FFT ({fftCropDims.cropWidth}×{fftCropDims.cropHeight}) + + ) : } + {!hideView && ( + + )} + + + + + {fftComputing && ( + + + {fftProgress || "Computing FFT…"} + + + )} + {!hideView && ( + + )} + + {/* FFT Stats Bar */} + {!hideStats && fftStats && fftStats.length === 4 && ( + + Mean {formatNumber(fftStats[0])} + Min {formatNumber(fftStats[1])} + Max {formatNumber(fftStats[2])} + Std {formatNumber(fftStats[3])} + {fftClickInfo && ( + <> + + + {fftClickInfo.dSpacing != null ? ( + <>d = {fftClickInfo.dSpacing >= 10 ? `${(fftClickInfo.dSpacing / 10).toFixed(2)} nm` : `${fftClickInfo.dSpacing.toFixed(2)} Å`}{" | |g| = "}{fftClickInfo.spatialFreq!.toFixed(4)} Å⁻¹ + ) : ( + <>dist = {fftClickInfo.distPx.toFixed(1)} px + )} + + + )} + + )} + {/* FFT Controls - two rows + histogram (matching main panel layout) */} + + + + {/* Row 1: Scale + Color + Colorbar */} + + Scale: + + Color: + + Colorbar: + { if (!lockDisplay) setFftShowColorbar(e.target.checked); }} disabled={lockDisplay} size="small" sx={switchStyles.small} /> + + {/* Row 2: Auto + zoom indicator */} + + Auto: + { if (!lockDisplay) setFftAuto(e.target.checked); }} disabled={lockDisplay} size="small" sx={switchStyles.small} /> + {fftCropDims && ( + <> + Win: + { if (!lockDisplay) setFftWindow(e.target.checked); }} disabled={lockDisplay} size="small" sx={switchStyles.small} /> + + )} + {fftZoom !== DEFAULT_FFT_ZOOM && ( + {fftZoom.toFixed(1)}x + )} + + + {/* Right: FFT Histogram */} + {!hideHistogram && ( + + {fftHistogramData && ( + { if (!lockHistogram) { setFftVminPct(min); setFftVmaxPct(max); } }} width={110} height={58} theme={themeInfo.theme === "dark" ? "dark" : "light"} dataMin={fftDataRange.min} dataMax={fftDataRange.max} /> + )} + + )} + + + + )} + + + ); +} + +export const render = createRender(Show2D); diff --git a/widget/js/show2d/show2d.css b/widget/js/show2d/show2d.css new file mode 100644 index 00000000..0e285789 --- /dev/null +++ b/widget/js/show2d/show2d.css @@ -0,0 +1,9 @@ +/* show2d.css - Minimal CSS for Show2D */ + +.show2d-root { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; +} + +.show2d-root canvas { + display: block; +} diff --git a/widget/js/show4dstem/index.tsx b/widget/js/show4dstem/index.tsx new file mode 100644 index 00000000..8ece614d --- /dev/null +++ b/widget/js/show4dstem/index.tsx @@ -0,0 +1,4259 @@ +/// +import * as React from "react"; +import { createRender, useModelState, useModel } from "@anywidget/react"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import Stack from "@mui/material/Stack"; +import Select from "@mui/material/Select"; +import MenuItem from "@mui/material/MenuItem"; +import Menu from "@mui/material/Menu"; +import Slider from "@mui/material/Slider"; +import Button from "@mui/material/Button"; +import Switch from "@mui/material/Switch"; +import Tooltip from "@mui/material/Tooltip"; +import IconButton from "@mui/material/IconButton"; +import PlayArrowIcon from "@mui/icons-material/PlayArrow"; +import PauseIcon from "@mui/icons-material/Pause"; +import StopIcon from "@mui/icons-material/Stop"; +import FastRewindIcon from "@mui/icons-material/FastRewind"; +import FastForwardIcon from "@mui/icons-material/FastForward"; +import JSZip from "jszip"; +import "./styles.css"; +import { useTheme } from "../theme"; +import { COLORMAPS, applyColormap, renderToOffscreen } from "../colormaps"; +import { WebGPUFFT, getWebGPUFFT, fft2d, fftshift, autoEnhanceFFT, nextPow2, applyHannWindow2D } from "../webgpu-fft"; +import { drawScaleBarHiDPI, drawColorbar, roundToNiceValue, exportFigure, canvasToPDF } from "../scalebar"; +import { findDataRange, sliderRange, computeStats, applyLogScale } from "../stats"; +import { downloadBlob, formatNumber, downloadDataView } from "../format"; +import { computeHistogramFromBytes } from "../histogram"; +import { ControlCustomizer } from "../control-customizer"; +import { computeToolVisibility } from "../tool-parity"; + +const MIN_ZOOM = 0.5; +const MAX_ZOOM = 10; + +// ============================================================================ +// UI Styles - component styling helpers +// ============================================================================ +const typography = { + label: { fontSize: 11 }, + labelSmall: { fontSize: 10 }, + value: { fontSize: 10, fontFamily: "monospace" }, + title: { fontWeight: "bold" as const }, +}; + +const controlPanel = { + select: { minWidth: 90, fontSize: 11, "& .MuiSelect-select": { py: 0.5 } }, +}; + +const container = { + root: { p: 2, bgcolor: "transparent", color: "inherit", fontFamily: "monospace", overflow: "visible" }, + imageBox: { bgcolor: "#000", border: "1px solid #444", overflow: "hidden", position: "relative" as const }, +}; + +const upwardMenuProps = { + anchorOrigin: { vertical: "top" as const, horizontal: "left" as const }, + transformOrigin: { vertical: "bottom" as const, horizontal: "left" as const }, + sx: { zIndex: 9999 }, +}; + +const switchStyles = { + small: { '& .MuiSwitch-thumb': { width: 12, height: 12 }, '& .MuiSwitch-switchBase': { padding: '4px' } }, + medium: { '& .MuiSwitch-thumb': { width: 14, height: 14 }, '& .MuiSwitch-switchBase': { padding: '4px' } }, +}; + +const sliderStyles = { + small: { + "& .MuiSlider-thumb": { width: 12, height: 12 }, + "& .MuiSlider-rail": { height: 3 }, + "& .MuiSlider-track": { height: 3 }, + }, +}; + +// ============================================================================ +// Layout Constants - consistent spacing throughout +// ============================================================================ +const SPACING = { + XS: 4, // Extra small gap + SM: 8, // Small gap (default between elements) + MD: 12, // Medium gap (between control groups) + LG: 16, // Large gap (between major sections) +}; + +const CANVAS_SIZE = 450; // Both DP and VI canvases + +// Theme-aware ROI colors for DP detector overlay +interface RoiColors { + stroke: string; + strokeDragging: string; + fill: string; + fillDragging: string; + handleFill: string; + innerStroke: string; + innerStrokeDragging: string; + innerHandleFill: string; + textColor: string; +} +const DARK_ROI_COLORS: RoiColors = { + stroke: "rgba(0, 255, 0, 0.9)", + strokeDragging: "rgba(255, 255, 0, 0.9)", + fill: "rgba(0, 255, 0, 0.12)", + fillDragging: "rgba(255, 255, 0, 0.12)", + handleFill: "rgba(0, 255, 0, 0.8)", + innerStroke: "rgba(0, 220, 255, 0.9)", + innerStrokeDragging: "rgba(255, 200, 0, 0.9)", + innerHandleFill: "rgba(0, 220, 255, 0.8)", + textColor: "#0f0", +}; +const LIGHT_ROI_COLORS: RoiColors = { + stroke: "rgba(0, 140, 0, 0.9)", + strokeDragging: "rgba(200, 160, 0, 0.9)", + fill: "rgba(0, 140, 0, 0.15)", + fillDragging: "rgba(200, 160, 0, 0.15)", + handleFill: "rgba(0, 140, 0, 0.85)", + innerStroke: "rgba(0, 160, 200, 0.9)", + innerStrokeDragging: "rgba(200, 160, 0, 0.9)", + innerHandleFill: "rgba(0, 160, 200, 0.85)", + textColor: "#0a0", +}; + +// Interaction constants +const RESIZE_HIT_AREA_PX = 10; +const CIRCLE_HANDLE_ANGLE = 0.707; // cos(45°) +// Compact button style for Reset/Export +const compactButton = { + fontSize: 10, + py: 0.25, + px: 1, + minWidth: 0, + "&.Mui-disabled": { + color: "#666", + borderColor: "#444", + }, +}; + +// Control row style - bordered container for each row +const controlRow = { + display: "flex", + alignItems: "center", + gap: `${SPACING.SM}px`, + px: 1, + py: 0.5, + width: "fit-content", +}; + +/** Format stat value for display (compact scientific notation for small values) */ +function formatStat(value: number): string { + if (value === 0) return "0"; + const abs = Math.abs(value); + if (abs < 0.001 || abs >= 10000) { + return value.toExponential(2); + } + if (abs < 0.01) return value.toFixed(4); + if (abs < 1) return value.toFixed(3); + return value.toFixed(2); +} + + +// ============================================================================ +// FFT peak finder (snap to Bragg spot with sub-pixel centroid refinement) +// ============================================================================ +function findFFTPeak(mag: Float32Array, width: number, height: number, col: number, row: number, radius: number): { row: number; col: number } { + const c0 = Math.max(0, Math.floor(col) - radius); + const r0 = Math.max(0, Math.floor(row) - radius); + const c1 = Math.min(width - 1, Math.floor(col) + radius); + const r1 = Math.min(height - 1, Math.floor(row) + radius); + let bestCol = Math.round(col), bestRow = Math.round(row), bestVal = -Infinity; + for (let ir = r0; ir <= r1; ir++) { + for (let ic = c0; ic <= c1; ic++) { + const val = mag[ir * width + ic]; + if (val > bestVal) { bestVal = val; bestCol = ic; bestRow = ir; } + } + } + const wc0 = Math.max(0, bestCol - 1), wc1 = Math.min(width - 1, bestCol + 1); + const wr0 = Math.max(0, bestRow - 1), wr1 = Math.min(height - 1, bestRow + 1); + let sumW = 0, sumWC = 0, sumWR = 0; + for (let ir = wr0; ir <= wr1; ir++) { + for (let ic = wc0; ic <= wc1; ic++) { + const w = mag[ir * width + ic]; + sumW += w; sumWC += w * ic; sumWR += w * ir; + } + } + if (sumW > 0) return { row: sumWR / sumW, col: sumWC / sumW }; + return { row: bestRow, col: bestCol }; +} +const FFT_SNAP_RADIUS = 5; + +/** + * Draw VI crosshair on high-DPI canvas (crisp regardless of image resolution) + * Note: Does NOT clear canvas - should be called after drawScaleBarHiDPI + */ +function drawViPositionMarker( + canvas: HTMLCanvasElement, + dpr: number, + posRow: number, // Position in image coordinates + posCol: number, + zoom: number, + panX: number, + panY: number, + imageWidth: number, + imageHeight: number, + isDragging: boolean +) { + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + ctx.save(); + ctx.scale(dpr, dpr); + + const cssWidth = canvas.width / dpr; + const cssHeight = canvas.height / dpr; + const scaleX = cssWidth / imageWidth; + const scaleY = cssHeight / imageHeight; + + // Convert image coordinates to CSS pixel coordinates + const screenX = posCol * zoom * scaleX + panX * scaleX; + const screenY = posRow * zoom * scaleY + panY * scaleY; + + // Simple crosshair (no circle) + const crosshairSize = 12; + const lineWidth = 1.5; + + ctx.shadowColor = "rgba(0, 0, 0, 0.5)"; + ctx.shadowBlur = 2; + ctx.shadowOffsetX = 1; + ctx.shadowOffsetY = 1; + + ctx.strokeStyle = isDragging ? "rgba(255, 255, 0, 0.9)" : "rgba(255, 100, 100, 0.9)"; + ctx.lineWidth = lineWidth; + + // Draw crosshair lines only + ctx.beginPath(); + ctx.moveTo(screenX - crosshairSize, screenY); + ctx.lineTo(screenX + crosshairSize, screenY); + ctx.moveTo(screenX, screenY - crosshairSize); + ctx.lineTo(screenX, screenY + crosshairSize); + ctx.stroke(); + + ctx.restore(); +} + +/** + * Draw VI ROI overlay on high-DPI canvas for real-space region selection + * Note: Does NOT clear canvas - should be called after drawViPositionMarker + */ +function drawViRoiOverlayHiDPI( + canvas: HTMLCanvasElement, + dpr: number, + roiMode: string, + centerRow: number, + centerCol: number, + radius: number, + roiWidth: number, + roiHeight: number, + zoom: number, + panX: number, + panY: number, + imageWidth: number, + imageHeight: number, + isDragging: boolean, + isDraggingResize: boolean, + isHoveringResize: boolean +) { + if (roiMode === "off") return; + + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + ctx.save(); + ctx.scale(dpr, dpr); + + const cssWidth = canvas.width / dpr; + const cssHeight = canvas.height / dpr; + const scaleX = cssWidth / imageWidth; + const scaleY = cssHeight / imageHeight; + + // Convert image coordinates to screen coordinates (row→screenY, col→screenX) + const screenX = centerCol * zoom * scaleX + panX * scaleX; + const screenY = centerRow * zoom * scaleY + panY * scaleY; + + const lineWidth = 2.5; + const crosshairSize = 10; + const handleRadius = 6; + + ctx.shadowColor = "rgba(0, 0, 0, 0.4)"; + ctx.shadowBlur = 2; + ctx.shadowOffsetX = 1; + ctx.shadowOffsetY = 1; + + // Helper to draw resize handle (purple color for VI ROI to differentiate from DP) + const drawResizeHandle = (handleX: number, handleY: number) => { + let handleFill: string; + let handleStroke: string; + + if (isDraggingResize) { + handleFill = "rgba(180, 100, 255, 1)"; + handleStroke = "rgba(255, 255, 255, 1)"; + } else if (isHoveringResize) { + handleFill = "rgba(220, 150, 255, 1)"; + handleStroke = "rgba(255, 255, 255, 1)"; + } else { + handleFill = "rgba(160, 80, 255, 0.8)"; + handleStroke = "rgba(255, 255, 255, 0.8)"; + } + ctx.beginPath(); + ctx.arc(handleX, handleY, handleRadius, 0, 2 * Math.PI); + ctx.fillStyle = handleFill; + ctx.fill(); + ctx.strokeStyle = handleStroke; + ctx.lineWidth = 1.5; + ctx.stroke(); + }; + + // Helper to draw center crosshair (purple/magenta for VI ROI) + const drawCenterCrosshair = () => { + ctx.strokeStyle = isDragging ? "rgba(255, 200, 0, 0.9)" : "rgba(180, 80, 255, 0.9)"; + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(screenX - crosshairSize, screenY); + ctx.lineTo(screenX + crosshairSize, screenY); + ctx.moveTo(screenX, screenY - crosshairSize); + ctx.lineTo(screenX, screenY + crosshairSize); + ctx.stroke(); + }; + + // Purple/magenta color for VI ROI to differentiate from green DP detector + const strokeColor = isDragging ? "rgba(255, 200, 0, 0.9)" : "rgba(180, 80, 255, 0.9)"; + const fillColor = isDragging ? "rgba(255, 200, 0, 0.15)" : "rgba(180, 80, 255, 0.15)"; + + if (roiMode === "circle" && radius > 0) { + const screenRadiusX = radius * zoom * scaleX; + const screenRadiusY = radius * zoom * scaleY; + + ctx.strokeStyle = strokeColor; + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.ellipse(screenX, screenY, screenRadiusX, screenRadiusY, 0, 0, 2 * Math.PI); + ctx.stroke(); + + ctx.fillStyle = fillColor; + ctx.fill(); + + drawCenterCrosshair(); + + // Resize handle at 45° diagonal + const handleOffsetX = screenRadiusX * CIRCLE_HANDLE_ANGLE; + const handleOffsetY = screenRadiusY * CIRCLE_HANDLE_ANGLE; + drawResizeHandle(screenX + handleOffsetX, screenY + handleOffsetY); + + } else if (roiMode === "square" && radius > 0) { + // Square uses radius as half-size + const screenHalfW = radius * zoom * scaleX; + const screenHalfH = radius * zoom * scaleY; + const left = screenX - screenHalfW; + const top = screenY - screenHalfH; + + ctx.strokeStyle = strokeColor; + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.rect(left, top, screenHalfW * 2, screenHalfH * 2); + ctx.stroke(); + + ctx.fillStyle = fillColor; + ctx.fill(); + + drawCenterCrosshair(); + drawResizeHandle(screenX + screenHalfW, screenY + screenHalfH); + + } else if (roiMode === "rect" && roiWidth > 0 && roiHeight > 0) { + const screenHalfW = (roiWidth / 2) * zoom * scaleX; + const screenHalfH = (roiHeight / 2) * zoom * scaleY; + const left = screenX - screenHalfW; + const top = screenY - screenHalfH; + + ctx.strokeStyle = strokeColor; + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.rect(left, top, screenHalfW * 2, screenHalfH * 2); + ctx.stroke(); + + ctx.fillStyle = fillColor; + ctx.fill(); + + drawCenterCrosshair(); + drawResizeHandle(screenX + screenHalfW, screenY + screenHalfH); + } + + ctx.restore(); +} + +/** + * Draw DP crosshair on high-DPI canvas (crisp regardless of detector resolution) + * Note: Does NOT clear canvas - should be called after drawScaleBarHiDPI + */ +function drawDpCrosshairHiDPI( + canvas: HTMLCanvasElement, + dpr: number, + kCol: number, // Column position in detector coordinates + kRow: number, // Row position in detector coordinates + zoom: number, + panX: number, + panY: number, + detWidth: number, + detHeight: number, + isDragging: boolean, + roiColors: RoiColors = DARK_ROI_COLORS +) { + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + ctx.save(); + ctx.scale(dpr, dpr); + + const cssWidth = canvas.width / dpr; + const cssHeight = canvas.height / dpr; + // Use separate X/Y scale factors (canvas stretches to fill container) + const scaleX = cssWidth / detWidth; + const scaleY = cssHeight / detHeight; + + // Convert detector coordinates to CSS pixel coordinates + const screenX = kCol * zoom * scaleX + panX * scaleX; + const screenY = kRow * zoom * scaleY + panY * scaleY; + + // Fixed UI sizes in CSS pixels (consistent with VI crosshair) + const crosshairSize = 18; + const lineWidth = 3; + const dotRadius = 6; + + ctx.shadowColor = "rgba(0, 0, 0, 0.5)"; + ctx.shadowBlur = 2; + ctx.shadowOffsetX = 1; + ctx.shadowOffsetY = 1; + + ctx.strokeStyle = isDragging ? roiColors.strokeDragging : roiColors.stroke; + ctx.lineWidth = lineWidth; + + // Draw crosshair + ctx.beginPath(); + ctx.moveTo(screenX - crosshairSize, screenY); + ctx.lineTo(screenX + crosshairSize, screenY); + ctx.moveTo(screenX, screenY - crosshairSize); + ctx.lineTo(screenX, screenY + crosshairSize); + ctx.stroke(); + + // Draw center dot + ctx.beginPath(); + ctx.arc(screenX, screenY, dotRadius, 0, 2 * Math.PI); + ctx.stroke(); + + ctx.restore(); +} + +/** + * Draw ROI overlay (circle, square, rect, annular) on high-DPI canvas + * Note: Does NOT clear canvas - should be called after drawScaleBarHiDPI + */ +function drawRoiOverlayHiDPI( + canvas: HTMLCanvasElement, + dpr: number, + roiMode: string, + centerCol: number, + centerRow: number, + radius: number, + radiusInner: number, + roiWidth: number, + roiHeight: number, + zoom: number, + panX: number, + panY: number, + detWidth: number, + detHeight: number, + isDragging: boolean, + isDraggingResize: boolean, + isDraggingResizeInner: boolean, + isHoveringResize: boolean, + isHoveringResizeInner: boolean, + roiColors: RoiColors = DARK_ROI_COLORS +) { + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + ctx.save(); + ctx.scale(dpr, dpr); + + const cssWidth = canvas.width / dpr; + const cssHeight = canvas.height / dpr; + // Use separate X/Y scale factors (canvas stretches to fill container) + const scaleX = cssWidth / detWidth; + const scaleY = cssHeight / detHeight; + + // Convert detector coordinates to CSS pixel coordinates + const screenX = centerCol * zoom * scaleX + panX * scaleX; + const screenY = centerRow * zoom * scaleY + panY * scaleY; + + // Fixed UI sizes in CSS pixels + const lineWidth = 2.5; + const crosshairSizeSmall = 10; + const handleRadius = 6; + + ctx.shadowColor = "rgba(0, 0, 0, 0.4)"; + ctx.shadowBlur = 2; + ctx.shadowOffsetX = 1; + ctx.shadowOffsetY = 1; + + // Helper to draw resize handle + const drawResizeHandle = (handleX: number, handleY: number, isInner: boolean = false) => { + let handleFill: string; + let handleStroke: string; + const dragging = isInner ? isDraggingResizeInner : isDraggingResize; + const hovering = isInner ? isHoveringResizeInner : isHoveringResize; + + if (dragging) { + handleFill = "rgba(0, 200, 255, 1)"; + handleStroke = "rgba(255, 255, 255, 1)"; + } else if (hovering) { + handleFill = "rgba(255, 100, 100, 1)"; + handleStroke = "rgba(255, 255, 255, 1)"; + } else { + handleFill = isInner ? roiColors.innerHandleFill : roiColors.handleFill; + handleStroke = "rgba(255, 255, 255, 0.8)"; + } + ctx.beginPath(); + ctx.arc(handleX, handleY, handleRadius, 0, 2 * Math.PI); + ctx.fillStyle = handleFill; + ctx.fill(); + ctx.strokeStyle = handleStroke; + ctx.lineWidth = 1.5; + ctx.stroke(); + }; + + // Helper to draw center crosshair + const drawCenterCrosshair = () => { + ctx.strokeStyle = isDragging ? roiColors.strokeDragging : roiColors.stroke; + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(screenX - crosshairSizeSmall, screenY); + ctx.lineTo(screenX + crosshairSizeSmall, screenY); + ctx.moveTo(screenX, screenY - crosshairSizeSmall); + ctx.lineTo(screenX, screenY + crosshairSizeSmall); + ctx.stroke(); + }; + + if (roiMode === "circle" && radius > 0) { + // Use separate X/Y radii for ellipse (handles non-square detectors) + const screenRadiusX = radius * zoom * scaleX; + const screenRadiusY = radius * zoom * scaleY; + + // Draw ellipse (becomes circle if scaleX === scaleY) + ctx.strokeStyle = isDragging ? roiColors.strokeDragging : roiColors.stroke; + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.ellipse(screenX, screenY, screenRadiusX, screenRadiusY, 0, 0, 2 * Math.PI); + ctx.stroke(); + + // Semi-transparent fill + ctx.fillStyle = isDragging ? roiColors.fillDragging : roiColors.fill; + ctx.fill(); + + drawCenterCrosshair(); + + // Resize handle at 45° diagonal + const handleOffsetX = screenRadiusX * CIRCLE_HANDLE_ANGLE; + const handleOffsetY = screenRadiusY * CIRCLE_HANDLE_ANGLE; + drawResizeHandle(screenX + handleOffsetX, screenY + handleOffsetY); + + } else if (roiMode === "square" && radius > 0) { + // Square in detector space uses same half-size in both dimensions + const screenHalfW = radius * zoom * scaleX; + const screenHalfH = radius * zoom * scaleY; + const left = screenX - screenHalfW; + const top = screenY - screenHalfH; + + ctx.strokeStyle = isDragging ? roiColors.strokeDragging : roiColors.stroke; + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.rect(left, top, screenHalfW * 2, screenHalfH * 2); + ctx.stroke(); + + ctx.fillStyle = isDragging ? roiColors.fillDragging : roiColors.fill; + ctx.fill(); + + drawCenterCrosshair(); + drawResizeHandle(screenX + screenHalfW, screenY + screenHalfH); + + } else if (roiMode === "rect" && roiWidth > 0 && roiHeight > 0) { + const screenHalfW = (roiWidth / 2) * zoom * scaleX; + const screenHalfH = (roiHeight / 2) * zoom * scaleY; + const left = screenX - screenHalfW; + const top = screenY - screenHalfH; + + ctx.strokeStyle = isDragging ? roiColors.strokeDragging : roiColors.stroke; + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.rect(left, top, screenHalfW * 2, screenHalfH * 2); + ctx.stroke(); + + ctx.fillStyle = isDragging ? roiColors.fillDragging : roiColors.fill; + ctx.fill(); + + drawCenterCrosshair(); + drawResizeHandle(screenX + screenHalfW, screenY + screenHalfH); + + } else if (roiMode === "annular" && radius > 0) { + // Use separate X/Y radii for ellipses + const screenRadiusOuterX = radius * zoom * scaleX; + const screenRadiusOuterY = radius * zoom * scaleY; + const screenRadiusInnerX = (radiusInner || 0) * zoom * scaleX; + const screenRadiusInnerY = (radiusInner || 0) * zoom * scaleY; + + // Outer ellipse + ctx.strokeStyle = isDragging ? roiColors.strokeDragging : roiColors.stroke; + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.ellipse(screenX, screenY, screenRadiusOuterX, screenRadiusOuterY, 0, 0, 2 * Math.PI); + ctx.stroke(); + + // Inner ellipse + ctx.strokeStyle = isDragging ? roiColors.innerStrokeDragging : roiColors.innerStroke; + ctx.beginPath(); + ctx.ellipse(screenX, screenY, screenRadiusInnerX, screenRadiusInnerY, 0, 0, 2 * Math.PI); + ctx.stroke(); + + // Fill annular region + ctx.fillStyle = isDragging ? roiColors.fillDragging : roiColors.fill; + ctx.beginPath(); + ctx.ellipse(screenX, screenY, screenRadiusOuterX, screenRadiusOuterY, 0, 0, 2 * Math.PI); + ctx.ellipse(screenX, screenY, screenRadiusInnerX, screenRadiusInnerY, 0, 0, 2 * Math.PI, true); + ctx.fill(); + + drawCenterCrosshair(); + + // Outer handle at 45° diagonal + const handleOffsetOuterX = screenRadiusOuterX * CIRCLE_HANDLE_ANGLE; + const handleOffsetOuterY = screenRadiusOuterY * CIRCLE_HANDLE_ANGLE; + drawResizeHandle(screenX + handleOffsetOuterX, screenY + handleOffsetOuterY); + + // Inner handle at 45° diagonal + const handleOffsetInnerX = screenRadiusInnerX * CIRCLE_HANDLE_ANGLE; + const handleOffsetInnerY = screenRadiusInnerY * CIRCLE_HANDLE_ANGLE; + drawResizeHandle(screenX + handleOffsetInnerX, screenY + handleOffsetInnerY, true); + } + + ctx.restore(); +} + +// ============================================================================ +// Histogram Component +// ============================================================================ + +interface HistogramProps { + data: Float32Array | null; + vminPct: number; + vmaxPct: number; + onRangeChange: (min: number, max: number) => void; + width?: number; + height?: number; + theme?: "light" | "dark"; + dataMin?: number; + dataMax?: number; +} + +/** + * Info tooltip component - small ⓘ icon with hover tooltip + */ +function InfoTooltip({ text, theme = "dark" }: { text: React.ReactNode; theme?: "light" | "dark" }) { + const isDark = theme === "dark"; + const content = typeof text === "string" + ? {text} + : text; + return ( + + + ⓘ + + + ); +} + +function KeyboardShortcuts({ items }: { items: [string, string][] }) { + return ( + + + {items.map(([key, desc], i) => ( + {key}{desc} + ))} + + + ); +} + +/** + * Histogram component with integrated vmin/vmax slider and statistics. + * Shows data distribution with adjustable clipping. + */ +function Histogram({ + data, + vminPct, + vmaxPct, + onRangeChange, + width = 120, + height = 40, + theme = "dark", + dataMin = 0, + dataMax = 1, +}: HistogramProps) { + const canvasRef = React.useRef(null); + const bins = React.useMemo(() => computeHistogramFromBytes(data), [data]); + + // Theme-aware colors + const colors = theme === "dark" ? { + bg: "#1a1a1a", + barActive: "#888", + barInactive: "#444", + border: "#333", + } : { + bg: "#f0f0f0", + barActive: "#666", + barInactive: "#bbb", + border: "#ccc", + }; + + // Draw histogram (vertical gray bars) + React.useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return; + + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + const dpr = window.devicePixelRatio || 1; + canvas.width = width * dpr; + canvas.height = height * dpr; + ctx.scale(dpr, dpr); + + // Clear with theme background + ctx.fillStyle = colors.bg; + ctx.fillRect(0, 0, width, height); + + // Reduce to fewer bins for cleaner display + const displayBins = 64; + const binRatio = Math.floor(bins.length / displayBins); + const reducedBins: number[] = []; + for (let i = 0; i < displayBins; i++) { + let sum = 0; + for (let j = 0; j < binRatio; j++) { + sum += bins[i * binRatio + j] || 0; + } + reducedBins.push(sum / binRatio); + } + + // Normalize + const maxVal = Math.max(...reducedBins, 0.001); + const barWidth = width / displayBins; + + // Calculate which bins are in the clipped range + const vminBin = Math.floor((vminPct / 100) * displayBins); + const vmaxBin = Math.floor((vmaxPct / 100) * displayBins); + + // Draw histogram bars + for (let i = 0; i < displayBins; i++) { + const barHeight = (reducedBins[i] / maxVal) * (height - 2); + const x = i * barWidth; + + // Bars inside range are highlighted, outside are dimmed + const inRange = i >= vminBin && i <= vmaxBin; + ctx.fillStyle = inRange ? colors.barActive : colors.barInactive; + ctx.fillRect(x + 0.5, height - barHeight, Math.max(1, barWidth - 1), barHeight); + } + + }, [bins, vminPct, vmaxPct, width, height, colors]); + + return ( + + + { + const [newMin, newMax] = v as number[]; + onRangeChange(Math.min(newMin, newMax - 1), Math.max(newMax, newMin + 1)); + }} + min={0} + max={100} + size="small" + valueLabelDisplay="auto" + valueLabelFormat={(pct) => { + const val = dataMin + (pct / 100) * (dataMax - dataMin); + return val >= 1000 ? val.toExponential(1) : val.toFixed(1); + }} + sx={{ + width, + py: 0, + "& .MuiSlider-thumb": { width: 8, height: 8 }, + "& .MuiSlider-rail": { height: 2 }, + "& .MuiSlider-track": { height: 2 }, + "& .MuiSlider-valueLabel": { fontSize: 10, padding: "2px 4px" }, + }} + /> + {(() => { const v = dataMin + (vminPct / 100) * (dataMax - dataMin); return v >= 1000 ? v.toExponential(1) : v.toFixed(1); })()}{(() => { const v = dataMin + (vmaxPct / 100) * (dataMax - dataMin); return v >= 1000 ? v.toExponential(1) : v.toFixed(1); })()} + + ); +} + +// ============================================================================ +// Line Profile Sampling +// ============================================================================ + +function sampleSingleLine(data: Float32Array, w: number, h: number, row0: number, col0: number, row1: number, col1: number): Float32Array { + const dc = col1 - col0; + const dr = row1 - row0; + const len = Math.sqrt(dc * dc + dr * dr); + const n = Math.max(2, Math.ceil(len)); + const out = new Float32Array(n); + for (let i = 0; i < n; i++) { + const t = i / (n - 1); + const c = col0 + t * dc; + const r = row0 + t * dr; + const ci = Math.floor(c), ri = Math.floor(r); + const cf = c - ci, rf = r - ri; + const c0c = Math.max(0, Math.min(w - 1, ci)); + const c1c = Math.max(0, Math.min(w - 1, ci + 1)); + const r0c = Math.max(0, Math.min(h - 1, ri)); + const r1c = Math.max(0, Math.min(h - 1, ri + 1)); + out[i] = data[r0c * w + c0c] * (1 - cf) * (1 - rf) + + data[r0c * w + c1c] * cf * (1 - rf) + + data[r1c * w + c0c] * (1 - cf) * rf + + data[r1c * w + c1c] * cf * rf; + } + return out; +} + +function sampleLineProfile(data: Float32Array, w: number, h: number, row0: number, col0: number, row1: number, col1: number, profileWidth: number = 1): Float32Array { + if (profileWidth <= 1) return sampleSingleLine(data, w, h, row0, col0, row1, col1); + const dc = col1 - col0; + const dr = row1 - row0; + const len = Math.sqrt(dc * dc + dr * dr); + if (len < 1e-8) return sampleSingleLine(data, w, h, row0, col0, row1, col1); + const perpR = -dc / len; + const perpC = dr / len; + const half = (profileWidth - 1) / 2; + let accumulated: Float32Array | null = null; + for (let k = 0; k < profileWidth; k++) { + const off = -half + k; + const vals = sampleSingleLine(data, w, h, row0 + off * perpR, col0 + off * perpC, row1 + off * perpR, col1 + off * perpC); + if (!accumulated) { + accumulated = vals; + } else { + for (let i = 0; i < vals.length; i++) accumulated[i] += vals[i]; + } + } + if (accumulated) for (let i = 0; i < accumulated.length; i++) accumulated[i] /= profileWidth; + return accumulated || new Float32Array(0); +} + +function pointToSegmentDistance(col: number, row: number, col0: number, row0: number, col1: number, row1: number): number { + const dc = col1 - col0; + const dr = row1 - row0; + const lenSq = dc * dc + dr * dr; + if (lenSq <= 1e-12) return Math.sqrt((col - col0) ** 2 + (row - row0) ** 2); + const tRaw = ((col - col0) * dc + (row - row0) * dr) / lenSq; + const t = Math.max(0, Math.min(1, tRaw)); + const projCol = col0 + t * dc; + const projRow = row0 + t * dr; + return Math.sqrt((col - projCol) ** 2 + (row - projRow) ** 2); +} + +// ============================================================================ +// Crop single-mode ROI region from raw float32 data for ROI-scoped FFT +// ============================================================================ +function cropSingleROI( + data: Float32Array, imgW: number, imgH: number, + mode: string, centerRow: number, centerCol: number, + radius: number, roiW: number, roiH: number, +): { cropped: Float32Array; cropW: number; cropH: number } | null { + if (mode === "off") return null; + let x0: number, y0: number, x1: number, y1: number; + + if (mode === "rect") { + const hw = roiW / 2, hh = roiH / 2; + x0 = Math.max(0, Math.floor(centerCol - hw)); + y0 = Math.max(0, Math.floor(centerRow - hh)); + x1 = Math.min(imgW, Math.ceil(centerCol + hw)); + y1 = Math.min(imgH, Math.ceil(centerRow + hh)); + } else { + x0 = Math.max(0, Math.floor(centerCol - radius)); + y0 = Math.max(0, Math.floor(centerRow - radius)); + x1 = Math.min(imgW, Math.ceil(centerCol + radius)); + y1 = Math.min(imgH, Math.ceil(centerRow + radius)); + } + + const cropW = x1 - x0, cropH = y1 - y0; + if (cropW < 2 || cropH < 2) return null; + + const cropped = new Float32Array(cropW * cropH); + if (mode === "circle") { + const rSq = radius * radius; + for (let dy = 0; dy < cropH; dy++) { + for (let dx = 0; dx < cropW; dx++) { + const ix = x0 + dx, iy = y0 + dy; + const distSq = (ix - centerCol) * (ix - centerCol) + (iy - centerRow) * (iy - centerRow); + cropped[dy * cropW + dx] = distSq <= rSq ? data[iy * imgW + ix] : 0; + } + } + } else { + for (let dy = 0; dy < cropH; dy++) { + const srcOff = (y0 + dy) * imgW + x0; + cropped.set(data.subarray(srcOff, srcOff + cropW), dy * cropW); + } + } + return { cropped, cropW, cropH }; +} + +// ============================================================================ +// Main Component +// ============================================================================ +function Show4DSTEM() { + // Direct model access for batched updates + const model = useModel(); + + // ───────────────────────────────────────────────────────────────────────── + // Model State (synced with Python) + // ───────────────────────────────────────────────────────────────────────── + const [shapeRows] = useModelState("shape_rows"); + const [shapeCols] = useModelState("shape_cols"); + const [detRows] = useModelState("det_rows"); + const [detCols] = useModelState("det_cols"); + + const [posRow, setPosRow] = useModelState("pos_row"); + const [posCol, setPosCol] = useModelState("pos_col"); + const [roiCenterCol, setRoiCenterCol] = useModelState("roi_center_col"); + const [roiCenterRow, setRoiCenterRow] = useModelState("roi_center_row"); + const [pixelSize] = useModelState("pixel_size"); + const [kPixelSize] = useModelState("k_pixel_size"); + const [kCalibrated] = useModelState("k_calibrated"); + const [widgetVersion] = useModelState("widget_version"); + const [title] = useModelState("title"); + + const [frameBytes] = useModelState("frame_bytes"); + const [virtualImageBytes] = useModelState("virtual_image_bytes"); + + // ROI state + const [roiRadius, setRoiRadius] = useModelState("roi_radius"); + const [roiRadiusInner, setRoiRadiusInner] = useModelState("roi_radius_inner"); + const [roiMode, setRoiMode] = useModelState("roi_mode"); + const [roiWidth, setRoiWidth] = useModelState("roi_width"); + const [roiHeight, setRoiHeight] = useModelState("roi_height"); + + // Global min/max for DP normalization (from Python) + const [dpGlobalMin] = useModelState("dp_global_min"); + const [dpGlobalMax] = useModelState("dp_global_max"); + + // VI min/max for normalization (from Python) + const [viDataMin] = useModelState("vi_data_min"); + const [viDataMax] = useModelState("vi_data_max"); + + // Detector calibration (for presets) + const [bfRadius] = useModelState("bf_radius"); + const [centerCol] = useModelState("center_col"); + const [centerRow] = useModelState("center_row"); + + // Path animation state + const [pathPlaying, setPathPlaying] = useModelState("path_playing"); + const [pathIndex, setPathIndex] = useModelState("path_index"); + const [pathLength] = useModelState("path_length"); + const [pathIntervalMs] = useModelState("path_interval_ms"); + const [pathLoop] = useModelState("path_loop"); + + // Frame animation state (5D time/tilt series) + const [frameIdx, setFrameIdx] = useModelState("frame_idx"); + const [nFrames] = useModelState("n_frames"); + const [frameDimLabel] = useModelState("frame_dim_label"); + const [frameLabels] = useModelState("frame_labels"); + const [framePlaying, setFramePlaying] = useModelState("frame_playing"); + const [frameLoop, setFrameLoop] = useModelState("frame_loop"); + const [frameFps, setFrameFps] = useModelState("frame_fps"); + const [frameReverse, setFrameReverse] = useModelState("frame_reverse"); + const [frameBoomerang, setFrameBoomerang] = useModelState("frame_boomerang"); + + // Profile line state (synced with Python) + const [profileLine, setProfileLine] = useModelState<{row: number; col: number}[]>("profile_line"); + const [profileWidth] = useModelState("profile_width"); + + // Auto-detection trigger + // ───────────────────────────────────────────────────────────────────────── + // Local State (UI-only, not synced to Python) + // ───────────────────────────────────────────────────────────────────────── + const [localKCol, setLocalKCol] = React.useState(roiCenterCol); + const [localKRow, setLocalKRow] = React.useState(roiCenterRow); + const [localPosRow, setLocalPosRow] = React.useState(posRow); + const [localPosCol, setLocalPosCol] = React.useState(posCol); + const [isDraggingDP, setIsDraggingDP] = React.useState(false); + const [isDraggingVI, setIsDraggingVI] = React.useState(false); + const [isDraggingFFT, setIsDraggingFFT] = React.useState(false); + const [fftDragStart, setFftDragStart] = React.useState<{ x: number, y: number, panX: number, panY: number } | null>(null); + const [isDraggingResize, setIsDraggingResize] = React.useState(false); + const [isDraggingResizeInner, setIsDraggingResizeInner] = React.useState(false); // For annular inner handle + const [isHoveringResize, setIsHoveringResize] = React.useState(false); + const [isHoveringResizeInner, setIsHoveringResizeInner] = React.useState(false); + const resizeAspectRef = React.useRef(null); + // VI ROI drag/resize states (same pattern as DP) + const [isDraggingViRoi, setIsDraggingViRoi] = React.useState(false); + const [isDraggingViRoiResize, setIsDraggingViRoiResize] = React.useState(false); + const [isHoveringViRoiResize, setIsHoveringViRoiResize] = React.useState(false); + // Independent colormaps for DP and VI panels + const [showDpColorbar, setShowDpColorbar] = useModelState("dp_show_colorbar"); + const [dpColormap, setDpColormap] = useModelState("dp_colormap"); + const [viColormap, setViColormap] = useModelState("vi_colormap"); + // vmin/vmax percentile clipping (0-100) + const [dpVminPct, setDpVminPct] = useModelState("dp_vmin_pct"); + const [dpVmaxPct, setDpVmaxPct] = useModelState("dp_vmax_pct"); + const [viVminPct, setViVminPct] = useModelState("vi_vmin_pct"); + const [viVmaxPct, setViVmaxPct] = useModelState("vi_vmax_pct"); + // Absolute intensity bounds (override percentile sliders when both set) + const [traitDpVmin] = useModelState("dp_vmin"); + const [traitDpVmax] = useModelState("dp_vmax"); + const [traitViVmin] = useModelState("vi_vmin"); + const [traitViVmax] = useModelState("vi_vmax"); + // Scale mode: "linear" | "log" | "power" + const [dpScaleMode, setDpScaleMode] = useModelState<"linear" | "log" | "power">("dp_scale_mode"); + const [dpPowerExp] = useModelState("dp_power_exp"); + const [viScaleMode, setViScaleMode] = useModelState<"linear" | "log" | "power">("vi_scale_mode"); + const [viPowerExp] = useModelState("vi_power_exp"); + + // VI ROI state (real-space region selection for summed DP) - synced with Python + const [viRoiMode, setViRoiMode] = useModelState("vi_roi_mode"); + const [viRoiCenterRow, setViRoiCenterRow] = useModelState("vi_roi_center_row"); + const [viRoiCenterCol, setViRoiCenterCol] = useModelState("vi_roi_center_col"); + const [viRoiRadius, setViRoiRadius] = useModelState("vi_roi_radius"); + const [viRoiWidth, setViRoiWidth] = useModelState("vi_roi_width"); + const [viRoiHeight, setViRoiHeight] = useModelState("vi_roi_height"); + // Local VI ROI center for smooth dragging + const [localViRoiCenterRow, setLocalViRoiCenterRow] = React.useState(viRoiCenterRow || 0); + const [localViRoiCenterCol, setLocalViRoiCenterCol] = React.useState(viRoiCenterCol || 0); + const [summedDpBytes] = useModelState("summed_dp_bytes"); + const [summedDpCount] = useModelState("summed_dp_count"); + const [dpStats] = useModelState("dp_stats"); // [mean, min, max, std] + const [viStats] = useModelState("vi_stats"); // [mean, min, max, std] + const [showFft, setShowFft] = useModelState("show_fft"); + const [fftWindow, setFftWindow] = useModelState("fft_window"); + const [disabledTools, setDisabledTools] = useModelState("disabled_tools"); + const [hiddenTools, setHiddenTools] = useModelState("hidden_tools"); + const [showControls] = useModelState("show_controls"); + + const toolVisibility = React.useMemo( + () => computeToolVisibility("Show4DSTEM", disabledTools, hiddenTools), + [disabledTools, hiddenTools], + ); + + const hideDisplay = toolVisibility.isHidden("display"); + const hideHistogram = toolVisibility.isHidden("histogram"); + const hideStats = toolVisibility.isHidden("stats"); + const hidePlayback = toolVisibility.isHidden("playback"); + const hideView = toolVisibility.isHidden("view"); + const hideExport = toolVisibility.isHidden("export"); + const hideRoi = toolVisibility.isHidden("roi"); + const hideProfile = toolVisibility.isHidden("profile"); + const hideVirtual = toolVisibility.isHidden("virtual"); + const hideFrame = toolVisibility.isHidden("frame"); + const hideFft = toolVisibility.isHidden("fft") || hideVirtual; + + const lockDisplay = toolVisibility.isLocked("display"); + const lockHistogram = toolVisibility.isLocked("histogram"); + const lockStats = toolVisibility.isLocked("stats"); + const lockNavigation = toolVisibility.isLocked("navigation"); + const lockPlayback = toolVisibility.isLocked("playback"); + const lockView = toolVisibility.isLocked("view"); + const lockExport = toolVisibility.isLocked("export"); + const lockRoi = toolVisibility.isLocked("roi"); + const lockProfile = toolVisibility.isLocked("profile"); + const lockVirtual = toolVisibility.isLocked("virtual"); + const lockFrame = toolVisibility.isLocked("frame"); + const lockFft = toolVisibility.isLocked("fft") || lockVirtual; + const effectiveShowFft = showFft && !hideFft; + + // ROI FFT state (VI ROI crops virtual image for FFT) + const [fftCropDims, setFftCropDims] = React.useState<{ cropWidth: number; cropHeight: number; fftWidth: number; fftHeight: number } | null>(null); + const roiFftActive = effectiveShowFft && viRoiMode !== "off"; + + // Canvas resize state + const [canvasSize, setCanvasSize] = React.useState(CANVAS_SIZE); + const [isResizingCanvas, setIsResizingCanvas] = React.useState(false); + const [resizeCanvasStart, setResizeCanvasStart] = React.useState<{ x: number; y: number; size: number } | null>(null); + + // Export + const [, setGifExportRequested] = useModelState("_gif_export_requested"); + const [gifData] = useModelState("_gif_data"); + const [gifMetadataJson] = useModelState("_gif_metadata_json"); + const [exporting, setExporting] = React.useState(false); + const [dpExportAnchor, setDpExportAnchor] = React.useState(null); + const [viExportAnchor, setViExportAnchor] = React.useState(null); + + // Cursor readout state + const [cursorInfo, setCursorInfo] = React.useState<{ row: number; col: number; value: number; panel: string } | null>(null); + + // DP Line profile state + const [profileActive, setProfileActive] = React.useState(false); + const [profileData, setProfileData] = React.useState(null); + const [profileHeight, setProfileHeight] = React.useState(76); + const [isResizingProfile, setIsResizingProfile] = React.useState(false); + const profileResizeStart = React.useRef<{ startY: number; startHeight: number } | null>(null); + const profileCanvasRef = React.useRef(null); + const profileBaseImageRef = React.useRef(null); + const profileLayoutRef = React.useRef<{ padLeft: number; plotW: number; padTop: number; plotH: number; gMin: number; gMax: number; totalDist: number; xUnit: string } | null>(null); + const profilePoints = profileLine || []; + const rawDpDataRef = React.useRef(null); + const dpClickStartRef = React.useRef<{ x: number; y: number } | null>(null); + const [draggingDpProfileEndpoint, setDraggingDpProfileEndpoint] = React.useState<0 | 1 | null>(null); + const [isDraggingDpProfileLine, setIsDraggingDpProfileLine] = React.useState(false); + const [hoveredDpProfileEndpoint, setHoveredDpProfileEndpoint] = React.useState<0 | 1 | null>(null); + const [isHoveringDpProfileLine, setIsHoveringDpProfileLine] = React.useState(false); + const dpProfileDragStartRef = React.useRef<{ row: number; col: number; p0: { row: number; col: number }; p1: { row: number; col: number } } | null>(null); + const dpDragOffsetRef = React.useRef<{ dRow: number; dCol: number }>({ dRow: 0, dCol: 0 }); + + // VI Line profile state + const [viProfileActive, setViProfileActive] = React.useState(false); + const [viProfileData, setViProfileData] = React.useState(null); + const [viProfilePoints, setViProfilePoints] = React.useState>([]); + const [viProfileHeight, setViProfileHeight] = React.useState(76); + const [isResizingViProfile, setIsResizingViProfile] = React.useState(false); + const viProfileResizeStart = React.useRef<{ startY: number; startHeight: number } | null>(null); + const viProfileCanvasRef = React.useRef(null); + const viProfileBaseImageRef = React.useRef(null); + const viProfileLayoutRef = React.useRef<{ padLeft: number; plotW: number; padTop: number; plotH: number; gMin: number; gMax: number; totalDist: number; xUnit: string } | null>(null); + const rawViDataRef = React.useRef(null); + const viClickStartRef = React.useRef<{ x: number; y: number } | null>(null); + const [draggingViProfileEndpoint, setDraggingViProfileEndpoint] = React.useState<0 | 1 | null>(null); + const [isDraggingViProfileLine, setIsDraggingViProfileLine] = React.useState(false); + const [hoveredViProfileEndpoint, setHoveredViProfileEndpoint] = React.useState<0 | 1 | null>(null); + const [isHoveringViProfileLine, setIsHoveringViProfileLine] = React.useState(false); + const viProfileDragStartRef = React.useRef<{ row: number; col: number; p0: { row: number; col: number }; p1: { row: number; col: number } } | null>(null); + const viRoiDragOffsetRef = React.useRef<{ dRow: number; dCol: number }>({ dRow: 0, dCol: 0 }); + + // Theme detection + const { themeInfo, colors: themeColors } = useTheme(); + const roiColors = themeInfo.theme === "dark" ? DARK_ROI_COLORS : LIGHT_ROI_COLORS; + const accentGreen = themeInfo.theme === "dark" ? "#0f0" : "#1a7a1a"; + + // Themed typography — applies theme colors to module-level font sizes + const typo = React.useMemo(() => ({ + label: { ...typography.label, color: themeColors.textMuted }, + labelSmall: { ...typography.labelSmall, color: themeColors.textMuted }, + value: { ...typography.value, color: themeColors.textMuted }, + title: { ...typography.title, color: themeColors.accent }, + }), [themeColors]); + + // Compute VI canvas dimensions to respect aspect ratio of rectangular scans + const viCanvasWidth = shapeRows > shapeCols ? Math.round(canvasSize * (shapeCols / shapeRows)) : canvasSize; + const viCanvasHeight = shapeCols > shapeRows ? Math.round(canvasSize * (shapeRows / shapeCols)) : canvasSize; + + // Histogram data - use state to ensure re-renders (both are Float32Array now) + const [dpHistogramData, setDpHistogramData] = React.useState(null); + const [viHistogramData, setViHistogramData] = React.useState(null); + + // Parse DP frame bytes for histogram (float32 now) + React.useEffect(() => { + if (!frameBytes) return; + // Parse as Float32Array since Python now sends raw float32 + const rawData = new Float32Array(frameBytes.buffer, frameBytes.byteOffset, frameBytes.byteLength / 4); + // Store raw data for profile sampling + if (!rawDpDataRef.current || rawDpDataRef.current.length !== rawData.length) { + rawDpDataRef.current = new Float32Array(rawData.length); + } + rawDpDataRef.current.set(rawData); + // Apply scale transformation for histogram display + const scaledData = new Float32Array(rawData.length); + if (dpScaleMode === "log") { + for (let i = 0; i < rawData.length; i++) { + scaledData[i] = Math.log1p(Math.max(0, rawData[i])); + } + } else if (dpScaleMode === "power") { + for (let i = 0; i < rawData.length; i++) { + scaledData[i] = Math.pow(Math.max(0, rawData[i]), dpPowerExp); + } + } else { + scaledData.set(rawData); + } + setDpHistogramData(scaledData); + }, [frameBytes, dpScaleMode, dpPowerExp]); + + // GPU FFT state + const gpuFFTRef = React.useRef(null); + const [gpuReady, setGpuReady] = React.useState(false); + + // Path animation timer + React.useEffect(() => { + if (!pathPlaying || pathLength === 0) return; + + const timer = setInterval(() => { + setPathIndex((prev: number) => { + const next = prev + 1; + if (next >= pathLength) { + if (pathLoop) { + return 0; // Loop back to start + } else { + setPathPlaying(false); // Stop at end + return prev; + } + } + return next; + }); + }, pathIntervalMs); + + return () => clearInterval(timer); + }, [pathPlaying, pathLength, pathIntervalMs, pathLoop, setPathIndex, setPathPlaying]); + + // Frame animation timer (5D time/tilt series) + const frameBounceDir = React.useRef(1); + React.useEffect(() => { + frameBounceDir.current = frameReverse ? -1 : 1; + }, [frameReverse]); + + React.useEffect(() => { + if (!framePlaying || nFrames <= 1) return; + + const intervalMs = 1000 / Math.max(0.1, frameFps); + const timer = setInterval(() => { + setFrameIdx((prev: number) => { + let next: number; + if (frameBoomerang) { + next = prev + frameBounceDir.current; + if (next >= nFrames) { frameBounceDir.current = -1; next = nFrames - 2; } + if (next < 0) { frameBounceDir.current = 1; next = 1; } + next = Math.max(0, Math.min(nFrames - 1, next)); + } else { + next = prev + (frameReverse ? -1 : 1); + if (next >= nFrames) { + if (frameLoop) return 0; + setFramePlaying(false); + return prev; + } + if (next < 0) { + if (frameLoop) return nFrames - 1; + setFramePlaying(false); + return prev; + } + } + return next; + }); + }, intervalMs); + + return () => clearInterval(timer); + }, [framePlaying, nFrames, frameFps, frameLoop, frameReverse, frameBoomerang, setFrameIdx, setFramePlaying]); + + // Initialize WebGPU FFT on mount + React.useEffect(() => { + getWebGPUFFT().then(fft => { + if (fft) { + gpuFFTRef.current = fft; + setGpuReady(true); + } + }); + }, []); + + // Root element ref (theme-aware styling handled via CSS variables) + const rootRef = React.useRef(null); + + // Zoom state + const [dpZoom, setDpZoom] = React.useState(1); + const [dpPanX, setDpPanX] = React.useState(0); + const [dpPanY, setDpPanY] = React.useState(0); + const [viZoom, setViZoom] = React.useState(1); + const [viPanX, setViPanX] = React.useState(0); + const [viPanY, setViPanY] = React.useState(0); + const [fftZoom, setFftZoom] = React.useState(1); + const [fftPanX, setFftPanX] = React.useState(0); + const [fftPanY, setFftPanY] = React.useState(0); + const [fftScaleMode, setFftScaleMode] = useModelState<"linear" | "log" | "power">("fft_scale_mode"); + const [fftPowerExp] = useModelState("fft_power_exp"); + const [fftColormap, setFftColormap] = useModelState("fft_colormap"); + const [fftAuto, setFftAuto] = useModelState("fft_auto"); + const [fftVminPct, setFftVminPct] = useModelState("fft_vmin_pct"); + const [fftVmaxPct, setFftVmaxPct] = useModelState("fft_vmax_pct"); + const [fftStats, setFftStats] = React.useState(null); // [mean, min, max, std] + const [fftHistogramData, setFftHistogramData] = React.useState(null); + const [fftDataMin, setFftDataMin] = React.useState(0); + const [fftDataMax, setFftDataMax] = React.useState(1); + const [fftClickInfo, setFftClickInfo] = React.useState<{ + row: number; col: number; distPx: number; + spatialFreq: number | null; dSpacing: number | null; + } | null>(null); + const fftClickStartRef = React.useRef<{ x: number; y: number } | null>(null); + + const isTypingTarget = React.useCallback((target: EventTarget | null): boolean => { + if (!(target instanceof HTMLElement)) return false; + if (target.isContentEditable) return true; + return target.closest("input, textarea, select, [role='textbox'], [contenteditable='true']") !== null; + }, []); + + const handleRootMouseDownCapture = React.useCallback((e: React.MouseEvent) => { + const target = e.target as HTMLElement | null; + if (target?.closest("canvas")) rootRef.current?.focus(); + }, []); + + const handleKeyDown = React.useCallback((e: React.KeyboardEvent) => { + if (isTypingTarget(e.target)) return; + + const step = e.shiftKey ? 10 : 1; + let handled = false; + + switch (e.key) { + case "ArrowUp": + if (!lockNavigation) { + setPosRow(Math.max(0, posRow - step)); + handled = true; + } + break; + case "ArrowDown": + if (!lockNavigation) { + setPosRow(Math.min(shapeRows - 1, posRow + step)); + handled = true; + } + break; + case "ArrowLeft": + if (!lockNavigation) { + setPosCol(Math.max(0, posCol - step)); + handled = true; + } + break; + case "ArrowRight": + if (!lockNavigation) { + setPosCol(Math.min(shapeCols - 1, posCol + step)); + handled = true; + } + break; + case " ": // Space bar + if (!lockPlayback && pathLength > 0) { + setPathPlaying(!pathPlaying); + handled = true; + } + break; + case "r": + case "R": + if (!lockView) { + setDpZoom(1); setDpPanX(0); setDpPanY(0); + setViZoom(1); setViPanX(0); setViPanY(0); + setFftZoom(1); setFftPanX(0); setFftPanY(0); + handled = true; + } + break; + case "[": + if (!lockPlayback && !lockFrame && nFrames > 1) { + setFrameIdx(Math.max(0, frameIdx - 1)); + handled = true; + } + break; + case "]": + if (!lockPlayback && !lockFrame && nFrames > 1) { + setFrameIdx(Math.min(nFrames - 1, frameIdx + 1)); + handled = true; + } + break; + case "Escape": + rootRef.current?.blur(); + handled = true; + break; + } + + if (handled) { + e.preventDefault(); + e.stopPropagation(); + } + }, [ + frameIdx, isTypingTarget, lockFrame, lockNavigation, lockPlayback, lockView, nFrames, pathLength, + pathPlaying, posCol, posRow, setFrameIdx, setPathPlaying, setPosCol, setPosRow, shapeCols, shapeRows, + ]); + + React.useEffect(() => { + if (hideFft && showFft) { + setShowFft(false); + } + }, [hideFft, showFft, setShowFft]); + + React.useEffect(() => { + if (lockPlayback && pathPlaying) { + setPathPlaying(false); + } + }, [lockPlayback, pathPlaying, setPathPlaying]); + + React.useEffect(() => { + if ((lockPlayback || lockFrame) && framePlaying) { + setFramePlaying(false); + } + }, [lockFrame, lockPlayback, framePlaying, setFramePlaying]); + + React.useEffect(() => { + if (hideRoi) { + if (roiMode !== "point") setRoiMode("point"); + if (viRoiMode !== "off") setViRoiMode("off"); + } + }, [hideRoi, roiMode, viRoiMode, setRoiMode, setViRoiMode]); + + React.useEffect(() => { + if (hideProfile) { + if (profileActive) setProfileActive(false); + if (viProfileActive) setViProfileActive(false); + if (profileLine.length > 0) setProfileLine([]); + if (profileData) setProfileData(null); + if (viProfilePoints.length > 0) setViProfilePoints([]); + if (viProfileData) setViProfileData(null); + setHoveredDpProfileEndpoint(null); + setIsHoveringDpProfileLine(false); + setHoveredViProfileEndpoint(null); + setIsHoveringViProfileLine(false); + } + }, [ + hideProfile, profileActive, profileLine, profileData, setProfileLine, viProfileActive, + viProfilePoints, viProfileData, + ]); + + // Sync local state + React.useEffect(() => { + if (!isDraggingDP && !isDraggingResize) { setLocalKCol(roiCenterCol); setLocalKRow(roiCenterRow); } + }, [roiCenterCol, roiCenterRow, isDraggingDP, isDraggingResize]); + + React.useEffect(() => { + if (!isDraggingVI) { setLocalPosRow(posRow); setLocalPosCol(posCol); } + }, [posRow, posCol, isDraggingVI]); + + // Sync VI ROI local state + React.useEffect(() => { + if (!isDraggingViRoi && !isDraggingViRoiResize) { + setLocalViRoiCenterRow(viRoiCenterRow || shapeRows / 2); + setLocalViRoiCenterCol(viRoiCenterCol || shapeCols / 2); + } + }, [viRoiCenterRow, viRoiCenterCol, isDraggingViRoi, isDraggingViRoiResize, shapeRows, shapeCols]); + + // Canvas refs + const dpCanvasRef = React.useRef(null); + const dpOverlayRef = React.useRef(null); + const dpUiRef = React.useRef(null); // High-DPI UI overlay for scale bar + const dpOffscreenRef = React.useRef(null); + const dpImageDataRef = React.useRef(null); + const virtualCanvasRef = React.useRef(null); + const virtualOverlayRef = React.useRef(null); + const viUiRef = React.useRef(null); // High-DPI UI overlay for scale bar + const viOffscreenRef = React.useRef(null); + const viImageDataRef = React.useRef(null); + const fftCanvasRef = React.useRef(null); + const fftOverlayRef = React.useRef(null); + const fftOffscreenRef = React.useRef(null); + const fftImageDataRef = React.useRef(null); + + // Offscreen version counters — bump when colormap/data changes, cheap draw effects depend on these + const [dpOffscreenVersion, setDpOffscreenVersion] = React.useState(0); + const [viOffscreenVersion, setViOffscreenVersion] = React.useState(0); + const [fftOffscreenVersion, setFftOffscreenVersion] = React.useState(0); + + // Cached colorbar vmin/vmax — computed in expensive DP effect, reused in UI overlay without recomputing + const dpColorbarVminRef = React.useRef(0); + const dpColorbarVmaxRef = React.useRef(1); + + // Device pixel ratio for high-DPI UI overlays + const DPR = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1; + + // ───────────────────────────────────────────────────────────────────────── + // Effects: Canvas Rendering & Animation + // ───────────────────────────────────────────────────────────────────────── + + // Prevent page scroll when scrolling on canvases + // Re-run when showFft changes since FFT canvas is conditionally rendered + React.useEffect(() => { + const preventDefault = (e: WheelEvent) => e.preventDefault(); + const overlays = [dpOverlayRef.current, virtualOverlayRef.current, fftOverlayRef.current]; + overlays.forEach(el => el?.addEventListener("wheel", preventDefault, { passive: false })); + return () => overlays.forEach(el => el?.removeEventListener("wheel", preventDefault)); + }, [effectiveShowFft]); + + // Store raw data for filtering/FFT + const rawVirtualImageRef = React.useRef(null); + const fftWorkRealRef = React.useRef(null); + const fftWorkImagRef = React.useRef(null); + const fftMagnitudeRef = React.useRef(null); + const fftMagCacheRef = React.useRef(null); + + // Parse virtual image bytes into Float32Array and apply scale for histogram + React.useEffect(() => { + if (!virtualImageBytes) return; + // Parse as Float32Array + const numFloats = virtualImageBytes.byteLength / 4; + const rawData = new Float32Array(virtualImageBytes.buffer, virtualImageBytes.byteOffset, numFloats); + + // Store a copy for filtering/FFT (rawData is a view, we need a copy) + let storedData = rawVirtualImageRef.current; + if (!storedData || storedData.length !== numFloats) { + storedData = new Float32Array(numFloats); + rawVirtualImageRef.current = storedData; + } + storedData.set(rawData); + + // Also store for VI profile sampling + if (!rawViDataRef.current || rawViDataRef.current.length !== numFloats) { + rawViDataRef.current = new Float32Array(numFloats); + } + rawViDataRef.current.set(rawData); + + // Apply scale transformation for histogram display + const scaledData = new Float32Array(numFloats); + if (viScaleMode === "log") { + for (let i = 0; i < numFloats; i++) { + scaledData[i] = Math.log1p(Math.max(0, rawData[i])); + } + } else if (viScaleMode === "power") { + for (let i = 0; i < numFloats; i++) { + scaledData[i] = Math.pow(Math.max(0, rawData[i]), viPowerExp); + } + } else { + scaledData.set(rawData); + } + setViHistogramData(scaledData); + }, [virtualImageBytes, viScaleMode, viPowerExp]); + + // Render DP with zoom (use summed DP when VI ROI is active) + // Expensive: colormap + data processing → cached offscreen canvas + React.useEffect(() => { + // Determine which bytes to display: summed DP (if VI ROI active) or single frame + const usesSummedDp = viRoiMode && viRoiMode !== "off" && summedDpBytes && summedDpBytes.byteLength > 0; + const sourceBytes = usesSummedDp ? summedDpBytes : frameBytes; + if (!sourceBytes) return; + + const lut = COLORMAPS[dpColormap] || COLORMAPS.inferno; + + // Parse raw float32 data and apply scale transformation + const rawData = new Float32Array(sourceBytes.buffer, sourceBytes.byteOffset, sourceBytes.byteLength / 4); + let scaled: Float32Array; + if (dpScaleMode === "log") { + scaled = new Float32Array(rawData.length); + for (let i = 0; i < rawData.length; i++) { + scaled[i] = Math.log1p(Math.max(0, rawData[i])); + } + } else if (dpScaleMode === "power") { + scaled = new Float32Array(rawData.length); + for (let i = 0; i < rawData.length; i++) { + scaled[i] = Math.pow(Math.max(0, rawData[i]), dpPowerExp); + } + } else { + scaled = rawData; + } + + // Compute actual min/max of scaled data for normalization + const { min: dataMin, max: dataMax } = findDataRange(scaled); + + // Apply absolute bounds or percentile clipping + let vmin: number, vmax: number; + if (traitDpVmin != null && traitDpVmax != null) { + if (dpScaleMode === "log") { + vmin = Math.log1p(Math.max(traitDpVmin, 0)); + vmax = Math.log1p(Math.max(traitDpVmax, 0)); + } else if (dpScaleMode === "power") { + vmin = Math.pow(Math.max(traitDpVmin, 0), dpPowerExp); + vmax = Math.pow(Math.max(traitDpVmax, 0), dpPowerExp); + } else { + vmin = traitDpVmin; + vmax = traitDpVmax; + } + } else { + ({ vmin, vmax } = sliderRange(dataMin, dataMax, dpVminPct, dpVmaxPct)); + } + + let offscreen = dpOffscreenRef.current; + if (!offscreen) { + offscreen = document.createElement("canvas"); + dpOffscreenRef.current = offscreen; + } + const sizeChanged = offscreen.width !== detCols || offscreen.height !== detRows; + if (sizeChanged) { + offscreen.width = detCols; + offscreen.height = detRows; + dpImageDataRef.current = null; + } + const offCtx = offscreen.getContext("2d"); + if (!offCtx) return; + + let imgData = dpImageDataRef.current; + if (!imgData) { + imgData = offCtx.createImageData(detCols, detRows); + dpImageDataRef.current = imgData; + } + applyColormap(scaled, imgData.data, lut, vmin, vmax); + offCtx.putImageData(imgData, 0, 0); + // Cache colorbar range for the UI overlay (avoids recomputing findDataRange on every zoom/pan) + dpColorbarVminRef.current = vmin; + dpColorbarVmaxRef.current = vmax; + setDpOffscreenVersion(v => v + 1); + }, [frameBytes, summedDpBytes, viRoiMode, detRows, detCols, dpColormap, dpVminPct, dpVmaxPct, dpScaleMode, dpPowerExp, traitDpVmin, traitDpVmax]); + + // Cheap: zoom/pan redraw — just drawImage from cached offscreen + // useLayoutEffect prevents black flash when canvas dimensions change (resize) + React.useLayoutEffect(() => { + const offscreen = dpOffscreenRef.current; + if (!offscreen || !dpCanvasRef.current) return; + const canvas = dpCanvasRef.current; + const ctx = canvas.getContext("2d"); + if (!ctx) return; + ctx.imageSmoothingEnabled = false; + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.save(); + ctx.translate(dpPanX, dpPanY); + ctx.scale(dpZoom, dpZoom); + ctx.drawImage(offscreen, 0, 0); + ctx.restore(); + }, [dpOffscreenVersion, dpZoom, dpPanX, dpPanY]); + + // Render DP overlay - just clear (ROI shapes now drawn on high-DPI UI canvas) + React.useEffect(() => { + if (!dpOverlayRef.current) return; + const canvas = dpOverlayRef.current; + const ctx = canvas.getContext("2d"); + if (!ctx) return; + ctx.clearRect(0, 0, canvas.width, canvas.height); + // All visual overlays (crosshair, ROI shapes, scale bar) are now on dpUiRef for crisp rendering + }, [localKCol, localKRow, isDraggingDP, isDraggingResize, isDraggingResizeInner, isHoveringResize, isHoveringResizeInner, dpZoom, dpPanX, dpPanY, roiMode, roiRadius, roiRadiusInner, roiWidth, roiHeight, detRows, detCols]); + + // Expensive: VI colormap + data processing → cached offscreen canvas + React.useEffect(() => { + if (!rawVirtualImageRef.current) return; + + const width = shapeCols; + const height = shapeRows; + const filtered = rawVirtualImageRef.current; + + // Apply scale transformation first + let scaled = filtered; + if (viScaleMode === "log") { + scaled = new Float32Array(filtered.length); + for (let i = 0; i < filtered.length; i++) { + scaled[i] = Math.log1p(Math.max(0, filtered[i])); + } + } else if (viScaleMode === "power") { + scaled = new Float32Array(filtered.length); + for (let i = 0; i < filtered.length; i++) { + scaled[i] = Math.pow(Math.max(0, filtered[i]), viPowerExp); + } + } + + // Use Python's pre-computed min/max when valid, fallback to computing from data + let dataMin: number, dataMax: number; + const hasValidMinMax = viDataMin !== undefined && viDataMax !== undefined && viDataMax > viDataMin; + if (hasValidMinMax) { + // Apply scale transform to Python's values + if (viScaleMode === "log") { + dataMin = Math.log1p(Math.max(0, viDataMin)); + dataMax = Math.log1p(Math.max(0, viDataMax)); + } else if (viScaleMode === "power") { + dataMin = Math.pow(Math.max(0, viDataMin), viPowerExp); + dataMax = Math.pow(Math.max(0, viDataMax), viPowerExp); + } else { + dataMin = viDataMin; + dataMax = viDataMax; + } + } else { + // Fallback: compute from scaled data + const r = findDataRange(scaled); + dataMin = r.min; + dataMax = r.max; + } + + // Apply absolute bounds or percentile clipping + let vmin: number, vmax: number; + if (traitViVmin != null && traitViVmax != null) { + if (viScaleMode === "log") { + vmin = Math.log1p(Math.max(traitViVmin, 0)); + vmax = Math.log1p(Math.max(traitViVmax, 0)); + } else if (viScaleMode === "power") { + vmin = Math.pow(Math.max(traitViVmin, 0), viPowerExp); + vmax = Math.pow(Math.max(traitViVmax, 0), viPowerExp); + } else { + vmin = traitViVmin; + vmax = traitViVmax; + } + } else { + ({ vmin, vmax } = sliderRange(dataMin, dataMax, viVminPct, viVmaxPct)); + } + + const lut = COLORMAPS[viColormap] || COLORMAPS.inferno; + let offscreen = viOffscreenRef.current; + if (!offscreen) { + offscreen = document.createElement("canvas"); + viOffscreenRef.current = offscreen; + } + const sizeChanged = offscreen.width !== width || offscreen.height !== height; + if (sizeChanged) { + offscreen.width = width; + offscreen.height = height; + viImageDataRef.current = null; + } + const offCtx = offscreen.getContext("2d"); + if (!offCtx) return; + + let imageData = viImageDataRef.current; + if (!imageData) { + imageData = offCtx.createImageData(width, height); + viImageDataRef.current = imageData; + } + applyColormap(scaled, imageData.data, lut, vmin, vmax); + offCtx.putImageData(imageData, 0, 0); + setViOffscreenVersion(v => v + 1); + // Note: viDataMin/viDataMax intentionally not in deps - they arrive with virtualImageBytes + // and we have a fallback if they're stale + }, [virtualImageBytes, shapeRows, shapeCols, viColormap, viVminPct, viVmaxPct, viScaleMode, viPowerExp, traitViVmin, traitViVmax]); + + // Cheap: VI zoom/pan redraw — just drawImage from cached offscreen + React.useLayoutEffect(() => { + const offscreen = viOffscreenRef.current; + if (!offscreen || !virtualCanvasRef.current) return; + const canvas = virtualCanvasRef.current; + const ctx = canvas.getContext("2d"); + if (!ctx) return; + ctx.imageSmoothingEnabled = false; + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.save(); + ctx.translate(viPanX, viPanY); + ctx.scale(viZoom, viZoom); + ctx.drawImage(offscreen, 0, 0); + ctx.restore(); + }, [viOffscreenVersion, viZoom, viPanX, viPanY]); + + // Render virtual image overlay (just clear - crosshair drawn on high-DPI UI canvas) + React.useEffect(() => { + if (!virtualOverlayRef.current) return; + const canvas = virtualOverlayRef.current; + const ctx = canvas.getContext("2d"); + if (!ctx) return; + ctx.clearRect(0, 0, canvas.width, canvas.height); + // Crosshair and scale bar now drawn on high-DPI UI canvas (viUiRef) + }, [localPosRow, localPosCol, isDraggingVI, viZoom, viPanX, viPanY, pixelSize, shapeRows, shapeCols]); + + // Compute FFT (expensive, async — only re-run on data/GPU changes) + const fftRealRef = React.useRef(null); + const fftImagRef = React.useRef(null); + const [fftVersion, setFftVersion] = React.useState(0); + + React.useEffect(() => { + if (!rawVirtualImageRef.current || !effectiveShowFft) { setFftCropDims(null); return; } + let cancelled = false; + let width = shapeCols; + let height = shapeRows; + let sourceData = rawVirtualImageRef.current; + let origCropW = 0, origCropH = 0; + + // ROI FFT: crop virtual image to VI ROI region and pre-pad to power-of-2 + if (roiFftActive) { + const crop = cropSingleROI(sourceData, shapeCols, shapeRows, viRoiMode, viRoiCenterRow, viRoiCenterCol, viRoiRadius, viRoiWidth, viRoiHeight); + if (crop) { + origCropW = crop.cropW; + origCropH = crop.cropH; + // Apply Hann window to crop at native dimensions BEFORE zero-padding + if (fftWindow) applyHannWindow2D(crop.cropped, crop.cropW, crop.cropH); + const padW = nextPow2(crop.cropW); + const padH = nextPow2(crop.cropH); + const padded = new Float32Array(padW * padH); + for (let y = 0; y < crop.cropH; y++) { + for (let x = 0; x < crop.cropW; x++) { + padded[y * padW + x] = crop.cropped[y * crop.cropW + x]; + } + } + sourceData = padded; + width = padW; + height = padH; + } + } + + // Pre-pad non-power-of-2 full images so fft2d doesn't truncate frequency data + if (!roiFftActive) { + const padW = nextPow2(width); + const padH = nextPow2(height); + if (padW !== width || padH !== height) { + const padded = new Float32Array(padW * padH); + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + padded[y * padW + x] = sourceData[y * width + x]; + } + } + sourceData = padded; + width = padW; + height = padH; + } + } + + const fftW = width, fftH = height; + if (gpuFFTRef.current && gpuReady) { + const runGpuFFT = async () => { + const real = sourceData.slice(); + const imag = new Float32Array(real.length); + const { real: fReal, imag: fImag } = await gpuFFTRef.current!.fft2D(real, imag, fftW, fftH, false); + if (cancelled) return; + fftshift(fReal, fftW, fftH); + fftshift(fImag, fftW, fftH); + fftRealRef.current = fReal; + fftImagRef.current = fImag; + if (origCropW > 0) { + setFftCropDims({ cropWidth: origCropW, cropHeight: origCropH, fftWidth: fftW, fftHeight: fftH }); + } else if (fftW !== shapeCols || fftH !== shapeRows) { + setFftCropDims({ cropWidth: shapeCols, cropHeight: shapeRows, fftWidth: fftW, fftHeight: fftH }); + } else { + setFftCropDims(null); + } + setFftVersion(v => v + 1); + }; + runGpuFFT(); + return () => { cancelled = true; }; + } else { + const len = sourceData.length; + let real = fftWorkRealRef.current; + if (!real || real.length !== len) { real = new Float32Array(len); fftWorkRealRef.current = real; } + real.set(sourceData); + let imag = fftWorkImagRef.current; + if (!imag || imag.length !== len) { imag = new Float32Array(len); fftWorkImagRef.current = imag; } else { imag.fill(0); } + fft2d(real, imag, fftW, fftH, false); + fftshift(real, fftW, fftH); + fftshift(imag, fftW, fftH); + fftRealRef.current = real; + fftImagRef.current = imag; + if (origCropW > 0) { + setFftCropDims({ cropWidth: origCropW, cropHeight: origCropH, fftWidth: fftW, fftHeight: fftH }); + } else if (fftW !== shapeCols || fftH !== shapeRows) { + setFftCropDims({ cropWidth: shapeCols, cropHeight: shapeRows, fftWidth: fftW, fftHeight: fftH }); + } else { + setFftCropDims(null); + } + setFftVersion(v => v + 1); + } + }, [virtualImageBytes, shapeRows, shapeCols, gpuReady, effectiveShowFft, roiFftActive, viRoiMode, viRoiCenterRow, viRoiCenterCol, viRoiRadius, viRoiWidth, viRoiHeight, fftWindow]); + + // Expensive: FFT magnitude + histogram + colormap → cached offscreen canvas + React.useEffect(() => { + if (!fftRealRef.current || !fftImagRef.current) return; + if (!effectiveShowFft) return; + + const width = fftCropDims?.fftWidth ?? shapeCols; + const height = fftCropDims?.fftHeight ?? shapeRows; + const real = fftRealRef.current; + const imag = fftImagRef.current; + const lut = COLORMAPS[fftColormap] || COLORMAPS.inferno; + + // Compute magnitude with scale mode + let magnitude = fftMagnitudeRef.current; + if (!magnitude || magnitude.length !== real.length) { + magnitude = new Float32Array(real.length); + fftMagnitudeRef.current = magnitude; + } + // Cache raw magnitude for peak-snap before applying scale transform + let rawMag = fftMagCacheRef.current; + if (!rawMag || rawMag.length !== real.length) { + rawMag = new Float32Array(real.length); + fftMagCacheRef.current = rawMag; + } + for (let i = 0; i < real.length; i++) { + const mag = Math.sqrt(real[i] * real[i] + imag[i] * imag[i]); + rawMag[i] = mag; + if (fftScaleMode === "log") { magnitude[i] = Math.log1p(mag); } + else if (fftScaleMode === "power") { magnitude[i] = Math.pow(mag, fftPowerExp); } + else { magnitude[i] = mag; } + } + + let displayMin: number, displayMax: number; + if (fftAuto) { + ({ min: displayMin, max: displayMax } = autoEnhanceFFT(magnitude, width, height)); + } else { + ({ min: displayMin, max: displayMax } = findDataRange(magnitude)); + } + setFftDataMin(displayMin); + setFftDataMax(displayMax); + const magStats = computeStats(magnitude); + setFftStats([magStats.mean, displayMin, displayMax, magStats.std]); + setFftHistogramData(magnitude.slice()); + + // Render to offscreen canvas + let offscreen = fftOffscreenRef.current; + if (!offscreen) { offscreen = document.createElement("canvas"); fftOffscreenRef.current = offscreen; } + if (offscreen.width !== width || offscreen.height !== height) { + offscreen.width = width; offscreen.height = height; fftImageDataRef.current = null; + } + const offCtx = offscreen.getContext("2d"); + if (!offCtx) return; + let imgData = fftImageDataRef.current; + if (!imgData) { imgData = offCtx.createImageData(width, height); fftImageDataRef.current = imgData; } + + const { vmin, vmax } = sliderRange(displayMin, displayMax, fftVminPct, fftVmaxPct); + applyColormap(magnitude, imgData.data, lut, vmin, vmax); + offCtx.putImageData(imgData, 0, 0); + setFftOffscreenVersion(v => v + 1); + }, [effectiveShowFft, fftVersion, fftScaleMode, fftPowerExp, fftAuto, fftVminPct, fftVmaxPct, fftColormap, shapeRows, shapeCols, fftCropDims]); + + // Cheap: FFT zoom/pan redraw — just drawImage from cached offscreen + React.useLayoutEffect(() => { + if (!fftCanvasRef.current) return; + const canvas = fftCanvasRef.current; + const ctx = canvas.getContext("2d"); + if (!ctx) return; + const offscreen = fftOffscreenRef.current; + if (!offscreen || !effectiveShowFft) { ctx.clearRect(0, 0, canvas.width, canvas.height); return; } + const fftW = offscreen.width; + const fftH = offscreen.height; + const canvasW = canvas.width; + const canvasH = canvas.height; + // Use bilinear smoothing when FFT dims differ from canvas (non-pow2 padding or ROI crop) + ctx.imageSmoothingEnabled = fftW !== canvasW || fftH !== canvasH; + ctx.clearRect(0, 0, canvasW, canvasH); + ctx.save(); + ctx.translate(fftPanX, fftPanY); + ctx.scale(fftZoom, fftZoom); + ctx.drawImage(offscreen, 0, 0); + ctx.restore(); + }, [fftOffscreenVersion, fftZoom, fftPanX, fftPanY, effectiveShowFft]); + + // Render FFT overlay with d-spacing crosshair marker + React.useEffect(() => { + if (!fftOverlayRef.current) return; + const canvas = fftOverlayRef.current; + const ctx = canvas.getContext("2d"); + if (!ctx) return; + ctx.clearRect(0, 0, canvas.width, canvas.height); + + // D-spacing crosshair marker + if (fftClickInfo && effectiveShowFft) { + const fftW = fftCropDims?.fftWidth ?? shapeCols; + const fftH = fftCropDims?.fftHeight ?? shapeRows; + ctx.save(); + // Convert FFT image coords to canvas coords via zoom/pan transform + const screenX = fftPanX + fftZoom * fftClickInfo.col; + const screenY = fftPanY + fftZoom * fftClickInfo.row; + ctx.strokeStyle = "rgba(255, 255, 255, 0.9)"; + ctx.shadowColor = "rgba(0, 0, 0, 0.6)"; + ctx.shadowBlur = 2; + ctx.lineWidth = 1.5; + // Scale crosshair size relative to canvas (not zoom-dependent) + const r = 8 * Math.max(fftW, fftH) / 450; + const gap = 3 * Math.max(fftW, fftH) / 450; + const dotR = 4 * Math.max(fftW, fftH) / 450; + ctx.beginPath(); + ctx.moveTo(screenX - r, screenY); ctx.lineTo(screenX - gap, screenY); + ctx.moveTo(screenX + gap, screenY); ctx.lineTo(screenX + r, screenY); + ctx.moveTo(screenX, screenY - r); ctx.lineTo(screenX, screenY - gap); + ctx.moveTo(screenX, screenY + gap); ctx.lineTo(screenX, screenY + r); + ctx.stroke(); + ctx.beginPath(); + ctx.arc(screenX, screenY, dotR, 0, Math.PI * 2); + ctx.stroke(); + if (fftClickInfo.dSpacing != null) { + const d = fftClickInfo.dSpacing; + const label = d >= 10 ? `d = ${(d / 10).toFixed(2)} nm` : `d = ${d.toFixed(2)} \u00C5`; + const fontSize = Math.max(10, Math.round(11 * Math.max(fftW, fftH) / 450)); + ctx.font = `bold ${fontSize}px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif`; + ctx.fillStyle = "white"; + ctx.textAlign = "left"; + ctx.textBaseline = "bottom"; + ctx.fillText(label, screenX + r + 4, screenY - gap); + } + ctx.restore(); + } + }, [fftZoom, fftPanX, fftPanY, effectiveShowFft, fftClickInfo, shapeCols, shapeRows, fftCropDims]); + + // Clear FFT click info when virtual image changes (scan position, VI ROI, etc.) + React.useEffect(() => { + setFftClickInfo(null); + }, [virtualImageBytes]); + + // ───────────────────────────────────────────────────────────────────────── + // High-DPI Scale Bar UI Overlays + // ───────────────────────────────────────────────────────────────────────── + + // DP scale bar + crosshair + ROI overlay + profile line (high-DPI) + React.useEffect(() => { + if (!dpUiRef.current) return; + // Draw scale bar first (clears canvas) + const kUnit = kCalibrated ? "mrad" : "px"; + drawScaleBarHiDPI(dpUiRef.current, DPR, dpZoom, kPixelSize || 1, kUnit, detCols); + // Draw ROI overlay (circle, square, rect, annular) or point crosshair + if (roiMode === "point") { + drawDpCrosshairHiDPI(dpUiRef.current, DPR, localKCol, localKRow, dpZoom, dpPanX, dpPanY, detCols, detRows, isDraggingDP, roiColors); + } else { + drawRoiOverlayHiDPI( + dpUiRef.current, DPR, roiMode, + localKCol, localKRow, roiRadius, roiRadiusInner, roiWidth, roiHeight, + dpZoom, dpPanX, dpPanY, detCols, detRows, + isDraggingDP, isDraggingResize, isDraggingResizeInner, isHoveringResize, isHoveringResizeInner, + roiColors + ); + } + + // Profile line overlay + if (profileActive && profilePoints.length > 0) { + const canvas = dpUiRef.current; + const ctx = canvas.getContext("2d"); + if (ctx) { + ctx.save(); + ctx.scale(DPR, DPR); + const cssW = canvas.width / DPR; + const cssH = canvas.height / DPR; + const scaleX = cssW / detCols; + const scaleY = cssH / detRows; + const toScreenX = (col: number) => col * dpZoom * scaleX + dpPanX * scaleX; + const toScreenY = (row: number) => row * dpZoom * scaleY + dpPanY * scaleY; + + // Draw point A + const ax = toScreenX(profilePoints[0].col); + const ay = toScreenY(profilePoints[0].row); + ctx.fillStyle = themeColors.accent; + ctx.beginPath(); + ctx.arc(ax, ay, 4, 0, Math.PI * 2); + ctx.fill(); + + if (profilePoints.length === 2) { + const bx = toScreenX(profilePoints[1].col); + const by = toScreenY(profilePoints[1].row); + + // Draw band when profile width > 1 + if (profileWidth > 1) { + const dc = profilePoints[1].col - profilePoints[0].col; + const dr = profilePoints[1].row - profilePoints[0].row; + const lineLen = Math.sqrt(dc * dc + dr * dr); + if (lineLen > 0) { + const halfW = (profileWidth - 1) / 2; + const perpR = -dc / lineLen * halfW; + const perpC = dr / lineLen * halfW; + ctx.fillStyle = themeColors.accent + "20"; + ctx.strokeStyle = themeColors.accent; + ctx.lineWidth = 1; + ctx.setLineDash([3, 3]); + ctx.beginPath(); + ctx.moveTo(toScreenX(profilePoints[0].col + perpC), toScreenY(profilePoints[0].row + perpR)); + ctx.lineTo(toScreenX(profilePoints[1].col + perpC), toScreenY(profilePoints[1].row + perpR)); + ctx.lineTo(toScreenX(profilePoints[1].col - perpC), toScreenY(profilePoints[1].row - perpR)); + ctx.lineTo(toScreenX(profilePoints[0].col - perpC), toScreenY(profilePoints[0].row - perpR)); + ctx.closePath(); + ctx.fill(); + ctx.stroke(); + ctx.setLineDash([]); + } + } + + // Draw line A->B + ctx.strokeStyle = themeColors.accent; + ctx.lineWidth = 1.5; + ctx.beginPath(); + ctx.moveTo(ax, ay); + ctx.lineTo(bx, by); + ctx.stroke(); + + // Draw point B + ctx.fillStyle = themeColors.accent; + ctx.beginPath(); + ctx.arc(bx, by, 4, 0, Math.PI * 2); + ctx.fill(); + } + ctx.restore(); + } + } + + // Colorbar overlay — uses cached vmin/vmax from the expensive DP offscreen effect + if (showDpColorbar) { + const canvas = dpUiRef.current; + const ctx = canvas.getContext("2d"); + if (ctx) { + ctx.save(); + ctx.scale(DPR, DPR); + const cssW = canvas.width / DPR; + const cssH = canvas.height / DPR; + const lut = COLORMAPS[dpColormap] || COLORMAPS.inferno; + drawColorbar(ctx, cssW, cssH, lut, dpColorbarVminRef.current, dpColorbarVmaxRef.current, dpScaleMode === "log"); + ctx.restore(); + } + } + }, [dpZoom, dpPanX, dpPanY, kPixelSize, kCalibrated, detRows, detCols, roiMode, roiRadius, roiRadiusInner, roiWidth, roiHeight, localKCol, localKRow, isDraggingDP, isDraggingResize, isDraggingResizeInner, isHoveringResize, isHoveringResizeInner, + profileActive, profilePoints, profileWidth, themeColors, showDpColorbar, dpColormap, dpScaleMode, dpVminPct, dpVmaxPct, canvasSize, roiColors]); + + // VI scale bar + crosshair + ROI + profile lines (high-DPI) + React.useEffect(() => { + if (!viUiRef.current) return; + // Draw scale bar first (clears canvas) + drawScaleBarHiDPI(viUiRef.current, DPR, viZoom, pixelSize || 1, "Å", shapeCols); + // Draw crosshair only when ROI is off (ROI replaces the crosshair) + if (!viRoiMode || viRoiMode === "off") { + drawViPositionMarker(viUiRef.current, DPR, localPosRow, localPosCol, viZoom, viPanX, viPanY, shapeCols, shapeRows, isDraggingVI); + } else { + // Draw VI ROI instead of crosshair + drawViRoiOverlayHiDPI( + viUiRef.current, DPR, viRoiMode, + localViRoiCenterRow, localViRoiCenterCol, viRoiRadius || 5, viRoiWidth || 10, viRoiHeight || 10, + viZoom, viPanX, viPanY, shapeCols, shapeRows, + isDraggingViRoi, isDraggingViRoiResize, isHoveringViRoiResize + ); + } + // Draw VI profile lines + if (viProfileActive && viProfilePoints.length > 0) { + const canvas = viUiRef.current; + const ctx = canvas.getContext("2d"); + if (ctx) { + const cssW = canvas.width / DPR; + const cssH = canvas.height / DPR; + const scaleX = cssW / shapeCols; + const scaleY = cssH / shapeRows; + ctx.save(); + ctx.scale(DPR, DPR); + ctx.strokeStyle = "#a0f"; + ctx.lineWidth = 2; + ctx.shadowColor = "rgba(0,0,0,0.5)"; + ctx.shadowBlur = 2; + if (viProfilePoints.length >= 1) { + const p0 = viProfilePoints[0]; + const x0 = p0.col * viZoom * scaleX + viPanX * scaleX; + const y0 = p0.row * viZoom * scaleY + viPanY * scaleY; + ctx.beginPath(); + ctx.arc(x0, y0, 4, 0, Math.PI * 2); + ctx.fill(); + ctx.fillStyle = "#fff"; + ctx.fillText("1", x0 + 6, y0 - 6); + } + if (viProfilePoints.length === 2) { + const p0 = viProfilePoints[0], p1 = viProfilePoints[1]; + const x0 = p0.col * viZoom * scaleX + viPanX * scaleX; + const y0 = p0.row * viZoom * scaleY + viPanY * scaleY; + const x1 = p1.col * viZoom * scaleX + viPanX * scaleX; + const y1 = p1.row * viZoom * scaleY + viPanY * scaleY; + ctx.beginPath(); + ctx.moveTo(x0, y0); + ctx.lineTo(x1, y1); + ctx.stroke(); + ctx.beginPath(); + ctx.arc(x1, y1, 4, 0, Math.PI * 2); + ctx.fill(); + ctx.fillStyle = "#fff"; + ctx.fillText("2", x1 + 6, y1 - 6); + } + ctx.restore(); + } + } + }, [viZoom, viPanX, viPanY, pixelSize, shapeRows, shapeCols, localPosRow, localPosCol, isDraggingVI, + viRoiMode, localViRoiCenterRow, localViRoiCenterCol, viRoiRadius, viRoiWidth, viRoiHeight, + isDraggingViRoi, isDraggingViRoiResize, isHoveringViRoiResize, canvasSize, viProfileActive, viProfilePoints]); + + // ── DP Profile computation ── + React.useEffect(() => { + if (profilePoints.length === 2 && rawDpDataRef.current) { + const p0 = profilePoints[0], p1 = profilePoints[1]; + setProfileData(sampleLineProfile(rawDpDataRef.current, detCols, detRows, p0.row, p0.col, p1.row, p1.col, profileWidth)); + if (!profileActive) setProfileActive(true); + } else { + setProfileData(null); + } + }, [profilePoints, profileWidth, frameBytes]); + + // ── VI Profile computation ── + React.useEffect(() => { + if (viProfilePoints.length === 2 && rawViDataRef.current && shapeCols > 0 && shapeRows > 0) { + const p0 = viProfilePoints[0], p1 = viProfilePoints[1]; + setViProfileData(sampleLineProfile(rawViDataRef.current, shapeCols, shapeRows, p0.row, p0.col, p1.row, p1.col, 1)); + } else { + setViProfileData(null); + } + }, [viProfilePoints, virtualImageBytes, shapeCols, shapeRows]); + + // ── Profile sparkline rendering ── + React.useEffect(() => { + const canvas = profileCanvasRef.current; + if (!canvas) return; + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + const dpr = window.devicePixelRatio || 1; + const cssW = canvasSize; + const cssH = profileHeight; + canvas.width = cssW * dpr; + canvas.height = cssH * dpr; + ctx.scale(dpr, dpr); + + const isDark = themeInfo.theme === "dark"; + ctx.fillStyle = isDark ? "#1a1a1a" : "#f0f0f0"; + ctx.fillRect(0, 0, cssW, cssH); + + if (!profileData || profileData.length < 2) { + ctx.font = "10px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif"; + ctx.fillStyle = isDark ? "#555" : "#999"; + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.fillText("Click two points on the DP to draw a profile", cssW / 2, cssH / 2); + profileBaseImageRef.current = null; + profileLayoutRef.current = null; + return; + } + + const padLeft = 40; + const padRight = 8; + const padTop = 6; + const padBottom = 18; + const plotW = cssW - padLeft - padRight; + const plotH = cssH - padTop - padBottom; + + let gMin = Infinity, gMax = -Infinity; + for (let i = 0; i < profileData.length; i++) { + if (profileData[i] < gMin) gMin = profileData[i]; + if (profileData[i] > gMax) gMax = profileData[i]; + } + const range = gMax - gMin || 1; + + // X-axis: calibrated distance + let totalDist = profileData.length - 1; + let xUnit = "px"; + if (profilePoints.length === 2) { + const dx = profilePoints[1].col - profilePoints[0].col; + const dy = profilePoints[1].row - profilePoints[0].row; + const distPx = Math.sqrt(dx * dx + dy * dy); + if (kCalibrated && kPixelSize > 0) { + totalDist = distPx * kPixelSize; + xUnit = "mrad"; + } else { + totalDist = distPx; + } + } + + // Draw axes + ctx.strokeStyle = isDark ? "#555" : "#bbb"; + ctx.lineWidth = 0.5; + ctx.beginPath(); + ctx.moveTo(padLeft, padTop); + ctx.lineTo(padLeft, padTop + plotH); + ctx.lineTo(padLeft + plotW, padTop + plotH); + ctx.stroke(); + + // Draw profile curve + ctx.strokeStyle = themeColors.accent; + ctx.lineWidth = 1.5; + ctx.beginPath(); + for (let i = 0; i < profileData.length; i++) { + const x = padLeft + (i / (profileData.length - 1)) * plotW; + const y = padTop + plotH - ((profileData[i] - gMin) / range) * plotH; + if (i === 0) ctx.moveTo(x, y); + else ctx.lineTo(x, y); + } + ctx.stroke(); + + // Draw x-axis ticks + const tickY = padTop + plotH; + ctx.strokeStyle = isDark ? "#555" : "#bbb"; + ctx.lineWidth = 0.5; + const idealTicks = Math.max(2, Math.floor(plotW / 70)); + const tickStep = roundToNiceValue(totalDist / idealTicks); + ctx.font = "9px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif"; + ctx.fillStyle = isDark ? "#888" : "#666"; + ctx.textBaseline = "top"; + const ticks: number[] = []; + for (let v = 0; v <= totalDist + tickStep * 0.01; v += tickStep) { + if (v > totalDist * 1.001) break; + ticks.push(v); + } + for (let i = 0; i < ticks.length; i++) { + const v = ticks[i]; + const frac = totalDist > 0 ? v / totalDist : 0; + const x = padLeft + frac * plotW; + ctx.beginPath(); ctx.moveTo(x, tickY); ctx.lineTo(x, tickY + 3); ctx.stroke(); + ctx.textAlign = frac < 0.05 ? "left" : frac > 0.95 ? "right" : "center"; + const label = v % 1 === 0 ? v.toFixed(0) : v.toFixed(1); + ctx.fillText(i === ticks.length - 1 ? `${label} ${xUnit}` : label, x, tickY + 4); + } + + // Y-axis min/max labels (left margin) + ctx.font = "9px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif"; + ctx.fillStyle = isDark ? "#888" : "#666"; + ctx.textAlign = "left"; + ctx.textBaseline = "top"; + ctx.fillText(formatNumber(gMax), 2, padTop); + ctx.textBaseline = "bottom"; + ctx.fillText(formatNumber(gMin), 2, padTop + plotH); + + // Save base image and layout for hover + profileBaseImageRef.current = ctx.getImageData(0, 0, canvas.width, canvas.height); + profileLayoutRef.current = { padLeft, plotW, padTop, plotH, gMin, gMax, totalDist, xUnit }; + }, [profileData, profilePoints, kPixelSize, kCalibrated, themeInfo.theme, themeColors.accent, canvasSize, profileHeight]); + + // DP Profile hover handlers + const handleProfileMouseMove = React.useCallback((e: React.MouseEvent) => { + const canvas = profileCanvasRef.current; + const base = profileBaseImageRef.current; + const layout = profileLayoutRef.current; + if (!canvas || !base || !layout || !profileData) return; + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + const rect = canvas.getBoundingClientRect(); + const cssX = e.clientX - rect.left; + const { padLeft, plotW, padTop, plotH, gMin, gMax, totalDist, xUnit } = layout; + const range = gMax - gMin || 1; + + // Restore base image + ctx.putImageData(base, 0, 0); + + if (cssX < padLeft || cssX > padLeft + plotW) return; + const frac = (cssX - padLeft) / plotW; + + const dpr = window.devicePixelRatio || 1; + ctx.save(); + ctx.setTransform(dpr, 0, 0, dpr, 0, 0); + + // Vertical crosshair + const isDark = themeInfo.theme === "dark"; + ctx.strokeStyle = isDark ? "rgba(255,255,255,0.3)" : "rgba(0,0,0,0.3)"; + ctx.lineWidth = 1; + ctx.setLineDash([2, 2]); + ctx.beginPath(); + ctx.moveTo(cssX, padTop); + ctx.lineTo(cssX, padTop + plotH); + ctx.stroke(); + ctx.setLineDash([]); + + // Dot on curve + value + const dataIdx = Math.min(profileData.length - 1, Math.max(0, Math.round(frac * (profileData.length - 1)))); + const val = profileData[dataIdx]; + const y = padTop + plotH - ((val - gMin) / range) * plotH; + ctx.fillStyle = themeColors.accent; + ctx.beginPath(); + ctx.arc(cssX, y, 3, 0, Math.PI * 2); + ctx.fill(); + + // Value readout label + const dist = frac * totalDist; + const label = `${formatNumber(val)} @ ${dist.toFixed(1)} ${xUnit}`; + ctx.font = "bold 9px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif"; + const textW = ctx.measureText(label).width; + const labelX = Math.min(cssX + 6, padLeft + plotW - textW - 2); + const labelY = padTop + 2; + ctx.fillStyle = isDark ? "rgba(0,0,0,0.7)" : "rgba(255,255,255,0.8)"; + ctx.fillRect(labelX - 2, labelY - 1, textW + 4, 11); + ctx.fillStyle = isDark ? "#fff" : "#000"; + ctx.textAlign = "left"; + ctx.textBaseline = "top"; + ctx.fillText(label, labelX, labelY); + + ctx.restore(); + }, [profileData, themeInfo.theme, themeColors.accent]); + + const handleProfileMouseLeave = React.useCallback(() => { + const canvas = profileCanvasRef.current; + const base = profileBaseImageRef.current; + if (!canvas || !base) return; + const ctx = canvas.getContext("2d"); + if (!ctx) return; + ctx.putImageData(base, 0, 0); + }, []); + + // DP Profile resize handlers + React.useEffect(() => { + if (!isResizingProfile) return; + const handleMouseMove = (e: MouseEvent) => { + if (!profileResizeStart.current) return; + const deltaY = e.clientY - profileResizeStart.current.startY; + const newHeight = Math.max(40, Math.min(300, profileResizeStart.current.startHeight + deltaY)); + setProfileHeight(newHeight); + }; + const handleMouseUp = () => { + setIsResizingProfile(false); + profileResizeStart.current = null; + }; + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + return () => { + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + }; + }, [isResizingProfile]); + + // ── VI Profile sparkline rendering ── + React.useEffect(() => { + const canvas = viProfileCanvasRef.current; + if (!canvas) return; + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + const dpr = window.devicePixelRatio || 1; + const cssW = viCanvasWidth; + const cssH = viProfileHeight; + canvas.width = cssW * dpr; + canvas.height = cssH * dpr; + ctx.scale(dpr, dpr); + + const isDark = themeInfo.theme === "dark"; + ctx.fillStyle = isDark ? "#1a1a1a" : "#f0f0f0"; + ctx.fillRect(0, 0, cssW, cssH); + + if (!viProfileData || viProfileData.length < 2) { + ctx.font = "10px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif"; + ctx.fillStyle = isDark ? "#555" : "#999"; + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.fillText("Click two points on the VI to draw a profile", cssW / 2, cssH / 2); + viProfileBaseImageRef.current = null; + viProfileLayoutRef.current = null; + return; + } + + const padLeft = 40; + const padRight = 8; + const padTop = 6; + const padBottom = 18; + const plotW = cssW - padLeft - padRight; + const plotH = cssH - padTop - padBottom; + + let gMin = Infinity, gMax = -Infinity; + for (let i = 0; i < viProfileData.length; i++) { + if (viProfileData[i] < gMin) gMin = viProfileData[i]; + if (viProfileData[i] > gMax) gMax = viProfileData[i]; + } + const range = gMax - gMin || 1; + + // X-axis: calibrated distance + let totalDist = viProfileData.length - 1; + let xUnit = "px"; + if (viProfilePoints.length === 2 && pixelSize > 0) { + const dx = viProfilePoints[1].col - viProfilePoints[0].col; + const dy = viProfilePoints[1].row - viProfilePoints[0].row; + const distPx = Math.sqrt(dx * dx + dy * dy); + totalDist = distPx * pixelSize; + xUnit = pixelSize >= 10 ? "nm" : "Å"; + if (xUnit === "nm") totalDist /= 10; + } + + // Draw axes + ctx.strokeStyle = isDark ? "#555" : "#bbb"; + ctx.lineWidth = 0.5; + ctx.beginPath(); + ctx.moveTo(padLeft, padTop); + ctx.lineTo(padLeft, padTop + plotH); + ctx.lineTo(padLeft + plotW, padTop + plotH); + ctx.stroke(); + + // Draw profile curve + ctx.strokeStyle = themeColors.accent; + ctx.lineWidth = 1.5; + ctx.beginPath(); + for (let i = 0; i < viProfileData.length; i++) { + const x = padLeft + (i / (viProfileData.length - 1)) * plotW; + const y = padTop + plotH - ((viProfileData[i] - gMin) / range) * plotH; + if (i === 0) ctx.moveTo(x, y); + else ctx.lineTo(x, y); + } + ctx.stroke(); + + // Draw x-axis ticks + const tickY = padTop + plotH; + ctx.strokeStyle = isDark ? "#555" : "#bbb"; + ctx.lineWidth = 0.5; + const idealTicks = Math.max(2, Math.floor(plotW / 70)); + const tickStep = roundToNiceValue(totalDist / idealTicks); + ctx.font = "9px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif"; + ctx.fillStyle = isDark ? "#888" : "#666"; + ctx.textBaseline = "top"; + const ticks: number[] = []; + for (let v = 0; v <= totalDist + tickStep * 0.01; v += tickStep) { + if (v > totalDist * 1.001) break; + ticks.push(v); + } + for (let i = 0; i < ticks.length; i++) { + const v = ticks[i]; + const frac = totalDist > 0 ? v / totalDist : 0; + const x = padLeft + frac * plotW; + ctx.beginPath(); ctx.moveTo(x, tickY); ctx.lineTo(x, tickY + 3); ctx.stroke(); + ctx.textAlign = frac < 0.05 ? "left" : frac > 0.95 ? "right" : "center"; + const label = v % 1 === 0 ? v.toFixed(0) : v.toFixed(1); + ctx.fillText(i === ticks.length - 1 ? `${label} ${xUnit}` : label, x, tickY + 4); + } + + // Y-axis min/max labels (left margin) + ctx.font = "9px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif"; + ctx.fillStyle = isDark ? "#888" : "#666"; + ctx.textAlign = "left"; + ctx.textBaseline = "top"; + ctx.fillText(formatNumber(gMax), 2, padTop); + ctx.textBaseline = "bottom"; + ctx.fillText(formatNumber(gMin), 2, padTop + plotH); + + // Save base image and layout for hover + viProfileBaseImageRef.current = ctx.getImageData(0, 0, canvas.width, canvas.height); + viProfileLayoutRef.current = { padLeft, plotW, padTop, plotH, gMin, gMax, totalDist, xUnit }; + }, [viProfileData, viProfilePoints, pixelSize, themeInfo.theme, themeColors.accent, viCanvasWidth, viProfileHeight]); + + // VI Profile hover handlers + const handleViProfileMouseMove = React.useCallback((e: React.MouseEvent) => { + const canvas = viProfileCanvasRef.current; + const base = viProfileBaseImageRef.current; + const layout = viProfileLayoutRef.current; + if (!canvas || !base || !layout || !viProfileData) return; + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + const rect = canvas.getBoundingClientRect(); + const cssX = e.clientX - rect.left; + const { padLeft, plotW, padTop, plotH, gMin, gMax, totalDist, xUnit } = layout; + const range = gMax - gMin || 1; + + // Restore base image + ctx.putImageData(base, 0, 0); + + if (cssX < padLeft || cssX > padLeft + plotW) return; + const frac = (cssX - padLeft) / plotW; + + const dpr = window.devicePixelRatio || 1; + ctx.save(); + ctx.setTransform(dpr, 0, 0, dpr, 0, 0); + + // Vertical crosshair + const isDark = themeInfo.theme === "dark"; + ctx.strokeStyle = isDark ? "rgba(255,255,255,0.3)" : "rgba(0,0,0,0.3)"; + ctx.lineWidth = 1; + ctx.setLineDash([2, 2]); + ctx.beginPath(); + ctx.moveTo(cssX, padTop); + ctx.lineTo(cssX, padTop + plotH); + ctx.stroke(); + ctx.setLineDash([]); + + // Dot on curve + value + const dataIdx = Math.min(viProfileData.length - 1, Math.max(0, Math.round(frac * (viProfileData.length - 1)))); + const val = viProfileData[dataIdx]; + const y = padTop + plotH - ((val - gMin) / range) * plotH; + ctx.fillStyle = themeColors.accent; + ctx.beginPath(); + ctx.arc(cssX, y, 3, 0, Math.PI * 2); + ctx.fill(); + + // Value readout label + const dist = frac * totalDist; + const label = `${formatNumber(val)} @ ${dist.toFixed(1)} ${xUnit}`; + ctx.font = "bold 9px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif"; + const textW = ctx.measureText(label).width; + const labelX = Math.min(cssX + 6, padLeft + plotW - textW - 2); + const labelY = padTop + 2; + ctx.fillStyle = isDark ? "rgba(0,0,0,0.7)" : "rgba(255,255,255,0.8)"; + ctx.fillRect(labelX - 2, labelY - 1, textW + 4, 11); + ctx.fillStyle = isDark ? "#fff" : "#000"; + ctx.textAlign = "left"; + ctx.textBaseline = "top"; + ctx.fillText(label, labelX, labelY); + + ctx.restore(); + }, [viProfileData, themeInfo.theme, themeColors.accent]); + + const handleViProfileMouseLeave = React.useCallback(() => { + const canvas = viProfileCanvasRef.current; + const base = viProfileBaseImageRef.current; + if (!canvas || !base) return; + const ctx = canvas.getContext("2d"); + if (!ctx) return; + ctx.putImageData(base, 0, 0); + }, []); + + // VI Profile resize handlers + React.useEffect(() => { + if (!isResizingViProfile) return; + const handleMouseMove = (e: MouseEvent) => { + if (!viProfileResizeStart.current) return; + const deltaY = e.clientY - viProfileResizeStart.current.startY; + const newHeight = Math.max(40, Math.min(300, viProfileResizeStart.current.startHeight + deltaY)); + setViProfileHeight(newHeight); + }; + const handleMouseUp = () => { + setIsResizingViProfile(false); + viProfileResizeStart.current = null; + }; + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + return () => { + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + }; + }, [isResizingViProfile]); + + // Generic zoom handler + const createZoomHandler = ( + setZoom: React.Dispatch>, + setPanX: React.Dispatch>, + setPanY: React.Dispatch>, + zoom: number, panX: number, panY: number, + canvasRef: React.RefObject, + locked: boolean = false, + ) => (e: React.WheelEvent) => { + if (locked) return; + e.preventDefault(); + const canvas = canvasRef.current; + if (!canvas) return; + const rect = canvas.getBoundingClientRect(); + const mouseX = (e.clientX - rect.left) * (canvas.width / rect.width); + const mouseY = (e.clientY - rect.top) * (canvas.height / rect.height); + const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1; + const newZoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, zoom * zoomFactor)); + const zoomRatio = newZoom / zoom; + setZoom(newZoom); + setPanX(mouseX - (mouseX - panX) * zoomRatio); + setPanY(mouseY - (mouseY - panY) * zoomRatio); + }; + + // ───────────────────────────────────────────────────────────────────────── + // Mouse Handlers + // ───────────────────────────────────────────────────────────────────────── + + // Helper: convert screen-pixel hit radius to image-pixel radius + // handleRadius=6 CSS px drawn, hit area ~10 CSS px → convert to image coords + const dpHitRadius = RESIZE_HIT_AREA_PX * Math.max(detCols, detRows) / canvasSize / dpZoom; + + // Helper: check if point is near the outer resize handle + const isNearResizeHandle = (imgX: number, imgY: number): boolean => { + if (roiMode === "rect") { + // For rectangle, check near bottom-right corner + const handleX = roiCenterCol + roiWidth / 2; + const handleY = roiCenterRow + roiHeight / 2; + const dist = Math.sqrt((imgX - handleX) ** 2 + (imgY - handleY) ** 2); + return dist < dpHitRadius; + } + if ((roiMode !== "circle" && roiMode !== "square" && roiMode !== "annular") || !roiRadius) return false; + const offset = roiMode === "square" ? roiRadius : roiRadius * CIRCLE_HANDLE_ANGLE; + const handleX = roiCenterCol + offset; + const handleY = roiCenterRow + offset; + const dist = Math.sqrt((imgX - handleX) ** 2 + (imgY - handleY) ** 2); + return dist < dpHitRadius; + }; + + // Helper: check if point is near the inner resize handle (annular mode only) + const isNearResizeHandleInner = (imgX: number, imgY: number): boolean => { + if (roiMode !== "annular" || !roiRadiusInner) return false; + const offset = roiRadiusInner * CIRCLE_HANDLE_ANGLE; + const handleX = roiCenterCol + offset; + const handleY = roiCenterRow + offset; + const dist = Math.sqrt((imgX - handleX) ** 2 + (imgY - handleY) ** 2); + return dist < dpHitRadius; + }; + + // Helper: check if point is near VI ROI resize handle (same logic as DP) + // Hit area is capped to avoid overlap with center for small ROIs + const viHitRadius = RESIZE_HIT_AREA_PX * Math.max(shapeRows, shapeCols) / canvasSize / viZoom; + const isNearViRoiResizeHandle = (imgX: number, imgY: number): boolean => { + if (!viRoiMode || viRoiMode === "off") return false; + if (viRoiMode === "rect") { + const halfH = (viRoiHeight || 10) / 2; + const halfW = (viRoiWidth || 10) / 2; + const handleX = localViRoiCenterRow + halfH; + const handleY = localViRoiCenterCol + halfW; + const dist = Math.sqrt((imgX - handleX) ** 2 + (imgY - handleY) ** 2); + const cornerDist = Math.sqrt(halfW ** 2 + halfH ** 2); + const hitArea = Math.min(viHitRadius, cornerDist * 0.5); + return dist < hitArea; + } + if (viRoiMode === "circle" || viRoiMode === "square") { + const radius = viRoiRadius || 5; + const offset = viRoiMode === "square" ? radius : radius * CIRCLE_HANDLE_ANGLE; + const handleX = localViRoiCenterRow + offset; + const handleY = localViRoiCenterCol + offset; + const dist = Math.sqrt((imgX - handleX) ** 2 + (imgY - handleY) ** 2); + // Cap hit area to 50% of radius so center remains draggable + const hitArea = Math.min(viHitRadius, radius * 0.5); + return dist < hitArea; + } + return false; + }; + + // Helper: check if point is inside the DP ROI area + const isInsideDpRoi = (imgX: number, imgY: number): boolean => { + if (roiMode === "point") return false; + const dx = imgX - roiCenterCol; + const dy = imgY - roiCenterRow; + if (roiMode === "circle") return Math.sqrt(dx * dx + dy * dy) <= (roiRadius || 5); + if (roiMode === "square") return Math.abs(dx) <= (roiRadius || 5) && Math.abs(dy) <= (roiRadius || 5); + if (roiMode === "annular") { const d = Math.sqrt(dx * dx + dy * dy); return d <= (roiRadius || 20) && d >= (roiRadiusInner || 5); } + if (roiMode === "rect") return Math.abs(dx) <= (roiWidth || 10) / 2 && Math.abs(dy) <= (roiHeight || 10) / 2; + return false; + }; + + // Helper: check if point is inside the VI ROI area + const isInsideViRoi = (imgX: number, imgY: number): boolean => { + if (!viRoiMode || viRoiMode === "off") return false; + const dx = imgY - localViRoiCenterCol; + const dy = imgX - localViRoiCenterRow; + if (viRoiMode === "circle") return Math.sqrt(dx * dx + dy * dy) <= (viRoiRadius || 5); + if (viRoiMode === "square") return Math.abs(dx) <= (viRoiRadius || 5) && Math.abs(dy) <= (viRoiRadius || 5); + if (viRoiMode === "rect") return Math.abs(dx) <= (viRoiWidth || 10) / 2 && Math.abs(dy) <= (viRoiHeight || 10) / 2; + return false; + }; + + // Mouse handlers + const handleDpMouseDown = (e: React.MouseEvent) => { + if (profileActive && lockProfile) return; + if (!profileActive && lockRoi) return; + dpClickStartRef.current = { x: e.clientX, y: e.clientY }; + const canvas = dpOverlayRef.current; + if (!canvas) return; + const rect = canvas.getBoundingClientRect(); + const screenX = (e.clientX - rect.left) * (canvas.width / rect.width); + const screenY = (e.clientY - rect.top) * (canvas.height / rect.height); + const imgX = (screenX - dpPanX) / dpZoom; + const imgY = (screenY - dpPanY) / dpZoom; + + // When profile mode is active, use profile interactions only + if (profileActive) { + if (profilePoints.length === 2) { + const p0 = profilePoints[0]; + const p1 = profilePoints[1]; + const hitRadius = 10 / dpZoom; + const d0 = Math.sqrt((imgX - p0.col) ** 2 + (imgY - p0.row) ** 2); + const d1 = Math.sqrt((imgX - p1.col) ** 2 + (imgY - p1.row) ** 2); + if (d0 <= hitRadius || d1 <= hitRadius) { + setDraggingDpProfileEndpoint(d0 <= d1 ? 0 : 1); + setIsDraggingDP(false); + return; + } + if (pointToSegmentDistance(imgX, imgY, p0.col, p0.row, p1.col, p1.row) <= hitRadius) { + setIsDraggingDpProfileLine(true); + dpProfileDragStartRef.current = { + row: imgY, + col: imgX, + p0: { row: p0.row, col: p0.col }, + p1: { row: p1.row, col: p1.col }, + }; + setIsDraggingDP(false); + return; + } + } + setIsDraggingDP(false); + return; + } + + // Check if clicking on resize handle (inner first, then outer) + if (isNearResizeHandleInner(imgX, imgY)) { + setIsDraggingResizeInner(true); + return; + } + if (isNearResizeHandle(imgX, imgY)) { + e.preventDefault(); + resizeAspectRef.current = roiMode === "rect" && roiWidth > 0 && roiHeight > 0 ? roiWidth / roiHeight : null; + setIsDraggingResize(true); + return; + } + + setIsDraggingDP(true); + // If clicking inside the ROI, drag with offset (grab-and-drag) + if (roiMode !== "off" && roiMode !== "point" && isInsideDpRoi(imgX, imgY)) { + dpDragOffsetRef.current = { dRow: imgY - roiCenterRow, dCol: imgX - roiCenterCol }; + return; + } + // Clicking outside ROI — teleport center to click position + dpDragOffsetRef.current = { dRow: 0, dCol: 0 }; + setLocalKCol(imgX); setLocalKRow(imgY); + // Use compound roi_center trait [row, col] - single observer fires in Python + const newCol = Math.round(Math.max(0, Math.min(detCols - 1, imgX))); + const newRow = Math.round(Math.max(0, Math.min(detRows - 1, imgY))); + model.set("roi_active", true); + model.set("roi_center", [newRow, newCol]); + model.save_changes(); + }; + + const handleDpMouseMove = (e: React.MouseEvent) => { + const canvas = dpOverlayRef.current; + if (!canvas) return; + const rect = canvas.getBoundingClientRect(); + const screenX = (e.clientX - rect.left) * (canvas.width / rect.width); + const screenY = (e.clientY - rect.top) * (canvas.height / rect.height); + const imgX = (screenX - dpPanX) / dpZoom; + const imgY = (screenY - dpPanY) / dpZoom; + + // Fast path: skip cursor readout during any active drag — avoids setCursorInfo re-renders + const anyDrag = isDraggingDP || isDraggingResize || isDraggingResizeInner + || draggingDpProfileEndpoint !== null || isDraggingDpProfileLine; + + // Cursor readout: look up raw DP value at pixel position + if (!anyDrag) { + const pxCol = Math.floor(imgX); + const pxRow = Math.floor(imgY); + if (pxCol >= 0 && pxCol < detCols && pxRow >= 0 && pxRow < detRows && frameBytes) { + const usesSummedDp = viRoiMode && viRoiMode !== "off" && summedDpBytes && summedDpBytes.byteLength > 0; + const sourceBytes = usesSummedDp ? summedDpBytes : frameBytes; + const raw = new Float32Array(sourceBytes.buffer, sourceBytes.byteOffset, sourceBytes.byteLength / 4); + setCursorInfo({ row: pxRow, col: pxCol, value: raw[pxRow * detCols + pxCol], panel: "DP" }); + } else { + setCursorInfo(null); + } + } + + if (profileActive && lockProfile) return; + + if (profileActive && profilePoints.length === 2) { + const p0 = profilePoints[0]; + const p1 = profilePoints[1]; + const hitRadius = 10 / dpZoom; + const d0 = Math.sqrt((imgX - p0.col) ** 2 + (imgY - p0.row) ** 2); + const d1 = Math.sqrt((imgX - p1.col) ** 2 + (imgY - p1.row) ** 2); + if (draggingDpProfileEndpoint !== null) { + if (!rawDpDataRef.current) return; + const clampedRow = Math.max(0, Math.min(detRows - 1, imgY)); + const clampedCol = Math.max(0, Math.min(detCols - 1, imgX)); + const next = [ + draggingDpProfileEndpoint === 0 ? { row: clampedRow, col: clampedCol } : profilePoints[0], + draggingDpProfileEndpoint === 1 ? { row: clampedRow, col: clampedCol } : profilePoints[1], + ]; + setProfileLine(next); + setProfileData(sampleLineProfile(rawDpDataRef.current, detCols, detRows, next[0].row, next[0].col, next[1].row, next[1].col, profileWidth)); + return; + } + if (isDraggingDpProfileLine && dpProfileDragStartRef.current) { + if (!rawDpDataRef.current) return; + const drag = dpProfileDragStartRef.current; + let deltaRow = imgY - drag.row; + let deltaCol = imgX - drag.col; + const minRow = Math.min(drag.p0.row, drag.p1.row); + const maxRow = Math.max(drag.p0.row, drag.p1.row); + const minCol = Math.min(drag.p0.col, drag.p1.col); + const maxCol = Math.max(drag.p0.col, drag.p1.col); + deltaRow = Math.max(deltaRow, -minRow); + deltaRow = Math.min(deltaRow, (detRows - 1) - maxRow); + deltaCol = Math.max(deltaCol, -minCol); + deltaCol = Math.min(deltaCol, (detCols - 1) - maxCol); + const next = [ + { row: drag.p0.row + deltaRow, col: drag.p0.col + deltaCol }, + { row: drag.p1.row + deltaRow, col: drag.p1.col + deltaCol }, + ]; + setProfileLine(next); + setProfileData(sampleLineProfile(rawDpDataRef.current, detCols, detRows, next[0].row, next[0].col, next[1].row, next[1].col, profileWidth)); + return; + } + const nextHoveredEndpoint: 0 | 1 | null = d0 <= hitRadius ? 0 : d1 <= hitRadius ? 1 : null; + const nextHoverLine = nextHoveredEndpoint === null && pointToSegmentDistance(imgX, imgY, p0.col, p0.row, p1.col, p1.row) <= hitRadius; + setHoveredDpProfileEndpoint(nextHoveredEndpoint); + setIsHoveringDpProfileLine(nextHoverLine); + return; + } else { + if (hoveredDpProfileEndpoint !== null) setHoveredDpProfileEndpoint(null); + if (isHoveringDpProfileLine) setIsHoveringDpProfileLine(false); + } + + // Handle inner resize dragging (annular mode) + if (isDraggingResizeInner) { + if (lockRoi) return; + const dx = Math.abs(imgX - roiCenterCol); + const dy = Math.abs(imgY - roiCenterRow); + const newRadius = Math.sqrt(dx ** 2 + dy ** 2); + // Inner radius must be less than outer radius + setRoiRadiusInner(Math.max(1, Math.min(roiRadius - 1, Math.round(newRadius)))); + return; + } + + // Handle outer resize dragging - use model state center, not local values + if (isDraggingResize) { + if (lockRoi) return; + const dx = Math.abs(imgX - roiCenterCol); + const dy = Math.abs(imgY - roiCenterRow); + if (roiMode === "rect") { + let newW = Math.max(2, Math.round(dx * 2)); + let newH = Math.max(2, Math.round(dy * 2)); + if (e.shiftKey && resizeAspectRef.current != null) { + const aspect = resizeAspectRef.current; + if (newW / newH > aspect) newH = Math.max(2, Math.round(newW / aspect)); + else newW = Math.max(2, Math.round(newH * aspect)); + } + setRoiWidth(newW); + setRoiHeight(newH); + } else { + const newRadius = roiMode === "square" ? Math.max(dx, dy) : Math.sqrt(dx ** 2 + dy ** 2); + // For annular mode, outer radius must be greater than inner radius + const minRadius = roiMode === "annular" ? (roiRadiusInner || 0) + 1 : 1; + setRoiRadius(Math.max(minRadius, Math.round(newRadius))); + } + return; + } + + // Check hover state for resize handles + if (!isDraggingDP) { + if (!lockRoi) { + setIsHoveringResizeInner(isNearResizeHandleInner(imgX, imgY)); + setIsHoveringResize(isNearResizeHandle(imgX, imgY)); + } else { + setIsHoveringResizeInner(false); + setIsHoveringResize(false); + } + return; + } + + if (lockRoi) return; + const centerCol = imgX - dpDragOffsetRef.current.dCol; + const centerRow = imgY - dpDragOffsetRef.current.dRow; + setLocalKCol(centerCol); setLocalKRow(centerRow); + // Use compound roi_center trait [row, col] - single observer fires in Python + const newCol = Math.round(Math.max(0, Math.min(detCols - 1, centerCol))); + const newRow = Math.round(Math.max(0, Math.min(detRows - 1, centerRow))); + model.set("roi_center", [newRow, newCol]); + model.save_changes(); + }; + + const handleDpMouseUp = (e: React.MouseEvent) => { + if (draggingDpProfileEndpoint !== null || isDraggingDpProfileLine) { + setDraggingDpProfileEndpoint(null); + setIsDraggingDpProfileLine(false); + dpProfileDragStartRef.current = null; + dpClickStartRef.current = null; + setIsDraggingDP(false); + setIsDraggingResize(false); + setIsDraggingResizeInner(false); + setHoveredDpProfileEndpoint(null); + setIsHoveringDpProfileLine(false); + return; + } + + // Profile click capture + if (profileActive && dpClickStartRef.current) { + const dx = e.clientX - dpClickStartRef.current.x; + const dy = e.clientY - dpClickStartRef.current.y; + if (Math.sqrt(dx * dx + dy * dy) < 3) { + const canvas = dpOverlayRef.current; + if (canvas && rawDpDataRef.current) { + const rect = canvas.getBoundingClientRect(); + const screenX = (e.clientX - rect.left) * (canvas.width / rect.width); + const screenY = (e.clientY - rect.top) * (canvas.height / rect.height); + const imgCol = (screenX - dpPanX) / dpZoom; + const imgRow = (screenY - dpPanY) / dpZoom; + if (imgCol >= 0 && imgCol < detCols && imgRow >= 0 && imgRow < detRows) { + const pt = { row: imgRow, col: imgCol }; + if (profilePoints.length === 0 || profilePoints.length === 2) { + setProfileLine([pt]); + setProfileData(null); + } else { + const p0 = profilePoints[0]; + setProfileLine([p0, pt]); + setProfileData(sampleLineProfile(rawDpDataRef.current, detCols, detRows, p0.row, p0.col, pt.row, pt.col, profileWidth)); + } + } + } + } + } + dpClickStartRef.current = null; + setIsDraggingDP(false); setIsDraggingResize(false); setIsDraggingResizeInner(false); + setDraggingDpProfileEndpoint(null); + setIsDraggingDpProfileLine(false); + setHoveredDpProfileEndpoint(null); + setIsHoveringDpProfileLine(false); + dpProfileDragStartRef.current = null; + }; + const handleDpMouseLeave = () => { + dpClickStartRef.current = null; + setIsDraggingDP(false); setIsDraggingResize(false); setIsDraggingResizeInner(false); + setDraggingDpProfileEndpoint(null); + setIsDraggingDpProfileLine(false); + setHoveredDpProfileEndpoint(null); + setIsHoveringDpProfileLine(false); + dpProfileDragStartRef.current = null; + setIsHoveringResize(false); setIsHoveringResizeInner(false); + setCursorInfo(prev => prev?.panel === "DP" ? null : prev); + }; + const handleDpDoubleClick = () => { + if (lockView) return; + setDpZoom(1); + setDpPanX(0); + setDpPanY(0); + }; + + const handleViMouseDown = (e: React.MouseEvent) => { + if (viProfileActive && lockProfile) return; + const canvas = virtualOverlayRef.current; + if (!canvas) return; + const rect = canvas.getBoundingClientRect(); + const screenX = (e.clientX - rect.left) * (canvas.width / rect.width); + const screenY = (e.clientY - rect.top) * (canvas.height / rect.height); + const imgX = (screenY - viPanY) / viZoom; + const imgY = (screenX - viPanX) / viZoom; + + // VI Profile mode - click to set points + if (viProfileActive) { + viClickStartRef.current = { x: screenX, y: screenY }; + if (viProfilePoints.length === 2) { + const p0 = viProfilePoints[0]; + const p1 = viProfilePoints[1]; + const hitRadius = 10 / viZoom; + const d0 = Math.sqrt((imgY - p0.col) ** 2 + (imgX - p0.row) ** 2); + const d1 = Math.sqrt((imgY - p1.col) ** 2 + (imgX - p1.row) ** 2); + if (d0 <= hitRadius || d1 <= hitRadius) { + setDraggingViProfileEndpoint(d0 <= d1 ? 0 : 1); + setIsDraggingVI(false); + return; + } + if (pointToSegmentDistance(imgY, imgX, p0.col, p0.row, p1.col, p1.row) <= hitRadius) { + setIsDraggingViProfileLine(true); + viProfileDragStartRef.current = { + row: imgX, + col: imgY, + p0: { row: p0.row, col: p0.col }, + p1: { row: p1.row, col: p1.col }, + }; + setIsDraggingVI(false); + return; + } + } + return; + } + + // Check if VI ROI mode is active - same logic as DP + if (viRoiMode && viRoiMode !== "off") { + if (lockRoi) return; + // Check if clicking on resize handle + if (isNearViRoiResizeHandle(imgX, imgY)) { + setIsDraggingViRoiResize(true); + return; + } + + // Grab-and-drag if clicking inside VI ROI, otherwise teleport + setIsDraggingViRoi(true); + if (isInsideViRoi(imgX, imgY)) { + viRoiDragOffsetRef.current = { dRow: imgX - localViRoiCenterRow, dCol: imgY - localViRoiCenterCol }; + } else { + viRoiDragOffsetRef.current = { dRow: 0, dCol: 0 }; + setLocalViRoiCenterRow(imgX); + setLocalViRoiCenterCol(imgY); + setViRoiCenterRow(Math.round(Math.max(0, Math.min(shapeRows - 1, imgX)))); + setViRoiCenterCol(Math.round(Math.max(0, Math.min(shapeCols - 1, imgY)))); + } + return; + } + + // Regular position selection (when ROI is off) + if (lockNavigation || lockVirtual) return; + setIsDraggingVI(true); + setLocalPosRow(imgX); setLocalPosCol(imgY); + // Batch X and Y updates into a single sync + const newX = Math.round(Math.max(0, Math.min(shapeRows - 1, imgX))); + const newY = Math.round(Math.max(0, Math.min(shapeCols - 1, imgY))); + model.set("pos_row", newX); + model.set("pos_col", newY); + model.save_changes(); + }; + + const handleViMouseMove = (e: React.MouseEvent) => { + const canvas = virtualOverlayRef.current; + if (!canvas) return; + const rect = canvas.getBoundingClientRect(); + const screenX = (e.clientX - rect.left) * (canvas.width / rect.width); + const screenY = (e.clientY - rect.top) * (canvas.height / rect.height); + const imgX = (screenY - viPanY) / viZoom; + const imgY = (screenX - viPanX) / viZoom; + + // Fast path: skip cursor readout during any active drag — avoids setCursorInfo re-renders + const anyViDrag = isDraggingVI || isDraggingViRoi || isDraggingViRoiResize + || draggingViProfileEndpoint !== null || isDraggingViProfileLine; + + // Cursor readout: look up raw VI value at pixel position + // imgX = row, imgY = col (swapped coordinate convention) + if (!anyViDrag) { + const pxRow = Math.floor(imgX); + const pxCol = Math.floor(imgY); + if (pxRow >= 0 && pxRow < shapeRows && pxCol >= 0 && pxCol < shapeCols && rawVirtualImageRef.current) { + const raw = rawVirtualImageRef.current; + setCursorInfo({ row: pxRow, col: pxCol, value: raw[pxRow * shapeCols + pxCol], panel: "VI" }); + } else { + setCursorInfo(prev => prev?.panel === "VI" ? null : prev); + } + } + + if (viProfileActive && lockProfile) return; + + if (viProfileActive && viProfilePoints.length === 2) { + const p0 = viProfilePoints[0]; + const p1 = viProfilePoints[1]; + const hitRadius = 10 / viZoom; + const d0 = Math.sqrt((imgY - p0.col) ** 2 + (imgX - p0.row) ** 2); + const d1 = Math.sqrt((imgY - p1.col) ** 2 + (imgX - p1.row) ** 2); + if (draggingViProfileEndpoint !== null) { + const clampedRow = Math.max(0, Math.min(shapeRows - 1, imgX)); + const clampedCol = Math.max(0, Math.min(shapeCols - 1, imgY)); + const next = [ + draggingViProfileEndpoint === 0 ? { row: clampedRow, col: clampedCol } : viProfilePoints[0], + draggingViProfileEndpoint === 1 ? { row: clampedRow, col: clampedCol } : viProfilePoints[1], + ]; + setViProfilePoints(next); + return; + } + if (isDraggingViProfileLine && viProfileDragStartRef.current) { + const drag = viProfileDragStartRef.current; + let deltaRow = imgX - drag.row; + let deltaCol = imgY - drag.col; + const minRow = Math.min(drag.p0.row, drag.p1.row); + const maxRow = Math.max(drag.p0.row, drag.p1.row); + const minCol = Math.min(drag.p0.col, drag.p1.col); + const maxCol = Math.max(drag.p0.col, drag.p1.col); + deltaRow = Math.max(deltaRow, -minRow); + deltaRow = Math.min(deltaRow, (shapeRows - 1) - maxRow); + deltaCol = Math.max(deltaCol, -minCol); + deltaCol = Math.min(deltaCol, (shapeCols - 1) - maxCol); + const next = [ + { row: drag.p0.row + deltaRow, col: drag.p0.col + deltaCol }, + { row: drag.p1.row + deltaRow, col: drag.p1.col + deltaCol }, + ]; + setViProfilePoints(next); + return; + } + const nextHoveredEndpoint: 0 | 1 | null = d0 <= hitRadius ? 0 : d1 <= hitRadius ? 1 : null; + const nextHoverLine = nextHoveredEndpoint === null && pointToSegmentDistance(imgY, imgX, p0.col, p0.row, p1.col, p1.row) <= hitRadius; + setHoveredViProfileEndpoint(nextHoveredEndpoint); + setIsHoveringViProfileLine(nextHoverLine); + return; + } else { + if (hoveredViProfileEndpoint !== null) setHoveredViProfileEndpoint(null); + if (isHoveringViProfileLine) setIsHoveringViProfileLine(false); + } + + // Handle VI ROI resize dragging (same pattern as DP) + if (isDraggingViRoiResize) { + if (lockRoi) return; + const dx = Math.abs(imgX - localViRoiCenterRow); + const dy = Math.abs(imgY - localViRoiCenterCol); + if (viRoiMode === "rect") { + setViRoiWidth(Math.max(2, Math.round(dy * 2))); + setViRoiHeight(Math.max(2, Math.round(dx * 2))); + } else if (viRoiMode === "square") { + const newHalfSize = Math.max(dx, dy); + setViRoiRadius(Math.max(1, Math.round(newHalfSize))); + } else { + // circle + const newRadius = Math.sqrt(dx ** 2 + dy ** 2); + setViRoiRadius(Math.max(1, Math.round(newRadius))); + } + return; + } + + // Check hover state for resize handles (same as DP) + if (!isDraggingViRoi) { + if (!lockRoi) { + setIsHoveringViRoiResize(isNearViRoiResizeHandle(imgX, imgY)); + } else { + setIsHoveringViRoiResize(false); + } + if (viRoiMode && viRoiMode !== "off") return; // Don't update position when ROI active + } + + // Handle VI ROI center dragging (same as DP — with offset) + if (isDraggingViRoi) { + if (lockRoi) return; + const centerRow = imgX - viRoiDragOffsetRef.current.dRow; + const centerCol = imgY - viRoiDragOffsetRef.current.dCol; + setLocalViRoiCenterRow(centerRow); + setLocalViRoiCenterCol(centerCol); + // Batch VI ROI center updates + const newViX = Math.round(Math.max(0, Math.min(shapeRows - 1, centerRow))); + const newViY = Math.round(Math.max(0, Math.min(shapeCols - 1, centerCol))); + model.set("vi_roi_center_row", newViX); + model.set("vi_roi_center_col", newViY); + model.save_changes(); + return; + } + + // Handle regular position dragging (when ROI is off) + if (!isDraggingVI) return; + if (lockNavigation || lockVirtual) return; + setLocalPosRow(imgX); setLocalPosCol(imgY); + // Batch position updates into a single sync + const newX = Math.round(Math.max(0, Math.min(shapeRows - 1, imgX))); + const newY = Math.round(Math.max(0, Math.min(shapeCols - 1, imgY))); + model.set("pos_row", newX); + model.set("pos_col", newY); + model.save_changes(); + }; + + const handleViMouseUp = (e: React.MouseEvent) => { + if (draggingViProfileEndpoint !== null || isDraggingViProfileLine) { + setDraggingViProfileEndpoint(null); + setIsDraggingViProfileLine(false); + viProfileDragStartRef.current = null; + viClickStartRef.current = null; + setIsDraggingVI(false); + setIsDraggingViRoi(false); + setIsDraggingViRoiResize(false); + setHoveredViProfileEndpoint(null); + setIsHoveringViProfileLine(false); + return; + } + + // VI Profile mode - complete point selection + if (viProfileActive && viClickStartRef.current) { + const canvas = virtualOverlayRef.current; + if (canvas) { + const rect = canvas.getBoundingClientRect(); + const endX = (e.clientX - rect.left) * (canvas.width / rect.width); + const endY = (e.clientY - rect.top) * (canvas.height / rect.height); + const dx = endX - viClickStartRef.current.x; + const dy = endY - viClickStartRef.current.y; + const wasDrag = Math.sqrt(dx * dx + dy * dy) > 3; + + if (!wasDrag) { + // Click to add point + const imgX = (endY - viPanY) / viZoom; + const imgY = (endX - viPanX) / viZoom; + const pt = { row: Math.round(Math.max(0, Math.min(shapeRows - 1, imgX))), col: Math.round(Math.max(0, Math.min(shapeCols - 1, imgY))) }; + if (viProfilePoints.length < 2) { + setViProfilePoints([...viProfilePoints, pt]); + } else { + setViProfilePoints([pt]); + } + } + } + viClickStartRef.current = null; + } + + setDraggingViProfileEndpoint(null); + setIsDraggingViProfileLine(false); + setHoveredViProfileEndpoint(null); + setIsHoveringViProfileLine(false); + viProfileDragStartRef.current = null; + setIsDraggingVI(false); + setIsDraggingViRoi(false); + setIsDraggingViRoiResize(false); + }; + const handleViMouseLeave = () => { + viClickStartRef.current = null; + setDraggingViProfileEndpoint(null); + setIsDraggingViProfileLine(false); + setHoveredViProfileEndpoint(null); + setIsHoveringViProfileLine(false); + viProfileDragStartRef.current = null; + setIsDraggingVI(false); + setIsDraggingViRoi(false); + setIsDraggingViRoiResize(false); + setIsHoveringViRoiResize(false); + setCursorInfo(prev => prev?.panel === "VI" ? null : prev); + }; + const handleViDoubleClick = () => { + if (lockView || lockVirtual) return; + setViZoom(1); + setViPanX(0); + setViPanY(0); + }; + const handleFftDoubleClick = () => { + if (lockView || lockFft) return; + setFftZoom(1); + setFftPanX(0); + setFftPanY(0); + setFftClickInfo(null); + }; + + // FFT drag-to-pan handlers + const handleFftMouseDown = (e: React.MouseEvent) => { + if (lockView || lockFft) return; + fftClickStartRef.current = { x: e.clientX, y: e.clientY }; + setIsDraggingFFT(true); + setFftDragStart({ x: e.clientX, y: e.clientY, panX: fftPanX, panY: fftPanY }); + }; + + const handleFftMouseMove = (e: React.MouseEvent) => { + if (lockView || lockFft) return; + if (!isDraggingFFT || !fftDragStart) return; + const canvas = fftOverlayRef.current; + if (!canvas) return; + const rect = canvas.getBoundingClientRect(); + const scaleX = canvas.width / rect.width; + const scaleY = canvas.height / rect.height; + const dx = (e.clientX - fftDragStart.x) * scaleX; + const dy = (e.clientY - fftDragStart.y) * scaleY; + setFftPanX(fftDragStart.panX + dx); + setFftPanY(fftDragStart.panY + dy); + }; + + const handleFftMouseUp = (e: React.MouseEvent) => { + // Click detection for d-spacing measurement + if (fftClickStartRef.current) { + const dx = e.clientX - fftClickStartRef.current.x; + const dy = e.clientY - fftClickStartRef.current.y; + if (Math.sqrt(dx * dx + dy * dy) < 3) { + // Convert screen coords to FFT image coords + const canvas = fftOverlayRef.current; + if (canvas) { + const rect = canvas.getBoundingClientRect(); + const scaleX = canvas.width / rect.width; + const scaleY = canvas.height / rect.height; + const canvasX = (e.clientX - rect.left) * scaleX; + const canvasY = (e.clientY - rect.top) * scaleY; + const fftW = fftCropDims?.fftWidth ?? shapeCols; + const fftH = fftCropDims?.fftHeight ?? shapeRows; + // Reverse the zoom/pan transform: canvas coords -> image coords + // The FFT render uses: ctx.translate(fftPanX, fftPanY); ctx.scale(fftZoom, fftZoom); ctx.drawImage(offscreen, 0, 0) + let imgCol = (canvasX - fftPanX) / fftZoom; + let imgRow = (canvasY - fftPanY) / fftZoom; + // Bounds check + if (imgCol >= 0 && imgCol < fftW && imgRow >= 0 && imgRow < fftH) { + // Snap to nearest peak in FFT magnitude + if (fftMagCacheRef.current) { + const snapped = findFFTPeak(fftMagCacheRef.current, fftW, fftH, imgCol, imgRow, FFT_SNAP_RADIUS); + imgCol = snapped.col; + imgRow = snapped.row; + } + const halfW = Math.floor(fftW / 2); + const halfH = Math.floor(fftH / 2); + const dcol = imgCol - halfW; + const drow = imgRow - halfH; + const distPx = Math.sqrt(dcol * dcol + drow * drow); + if (distPx < 1) { + setFftClickInfo(null); // Clicked on DC center + } else { + let spatialFreq: number | null = null; + let dSpacing: number | null = null; + if (pixelSize > 0) { + const paddedW = nextPow2(fftW); + const paddedH = nextPow2(fftH); + const binC = ((Math.round(imgCol) - halfW) % fftW + fftW) % fftW; + const binR = ((Math.round(imgRow) - halfH) % fftH + fftH) % fftH; + const freqC = binC <= paddedW / 2 ? binC / (paddedW * pixelSize) : (binC - paddedW) / (paddedW * pixelSize); + const freqR = binR <= paddedH / 2 ? binR / (paddedH * pixelSize) : (binR - paddedH) / (paddedH * pixelSize); + spatialFreq = Math.sqrt(freqC * freqC + freqR * freqR); + dSpacing = spatialFreq > 0 ? 1 / spatialFreq : null; + } + setFftClickInfo({ row: imgRow, col: imgCol, distPx, spatialFreq, dSpacing }); + } + } + } + } + fftClickStartRef.current = null; + } + setIsDraggingFFT(false); + setFftDragStart(null); + }; + const handleFftMouseLeave = () => { fftClickStartRef.current = null; setIsDraggingFFT(false); setFftDragStart(null); }; + + // ── Canvas resize handlers ── + const handleCanvasResizeStart = (e: React.MouseEvent) => { + if (lockView) return; + e.stopPropagation(); + e.preventDefault(); + setIsResizingCanvas(true); + setResizeCanvasStart({ x: e.clientX, y: e.clientY, size: canvasSize }); + }; + + React.useEffect(() => { + if (!isResizingCanvas) return; + let rafId = 0; + let latestSize = resizeCanvasStart ? resizeCanvasStart.size : canvasSize; + const handleMouseMove = (e: MouseEvent) => { + if (!resizeCanvasStart) return; + const delta = Math.max(e.clientX - resizeCanvasStart.x, e.clientY - resizeCanvasStart.y); + latestSize = Math.max(CANVAS_SIZE, resizeCanvasStart.size + delta); + if (!rafId) { + rafId = requestAnimationFrame(() => { + rafId = 0; + setCanvasSize(latestSize); + }); + } + }; + const handleMouseUp = () => { + cancelAnimationFrame(rafId); + setCanvasSize(latestSize); + setIsResizingCanvas(false); + setResizeCanvasStart(null); + }; + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + return () => { + cancelAnimationFrame(rafId); + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + }; + }, [isResizingCanvas, resizeCanvasStart]); + + // ───────────────────────────────────────────────────────────────────────── + // Render + // ───────────────────────────────────────────────────────────────────────── + + // Export DP handler + const handleExportDP = async () => { + if (lockExport) return; + const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19); + const zip = new JSZip(); + const metadata = { + metadata_version: "1.0", + widget_name: "Show4DSTEM", + widget_version: widgetVersion || "unknown", + exported_at: new Date().toISOString(), + view: "diffraction", + format: "zip", + export_kind: "single_view_png_zip", + position: { row: posRow, col: posCol }, + frame_idx: frameIdx, + n_frames: nFrames, + scan_shape: { rows: shapeRows, cols: shapeCols }, + detector_shape: { rows: detRows, cols: detCols }, + roi: { + active: roiMode !== "off", + mode: roiMode, + center_row: roiCenterRow, + center_col: roiCenterCol, + radius: roiRadius, + radius_inner: roiRadiusInner, + width: roiWidth, + height: roiHeight, + }, + vi_roi: { + mode: viRoiMode, + center_row: viRoiCenterRow, + center_col: viRoiCenterCol, + radius: viRoiRadius, + width: viRoiWidth, + height: viRoiHeight, + }, + calibration: { + pixel_size_angstrom: pixelSize, + pixel_size_unit: "Å/px", + k_pixel_size: kPixelSize, + k_pixel_size_unit: kCalibrated ? "mrad/px" : "px/px", + k_calibrated: kCalibrated, + center_row: centerRow, + center_col: centerCol, + bf_radius: bfRadius, + }, + display: { + diffraction: { + colormap: dpColormap, + scale_mode: dpScaleMode, + vmin_pct: dpVminPct, + vmax_pct: dpVmaxPct, + }, + }, + }; + zip.file("metadata.json", JSON.stringify(metadata, null, 2)); + const canvasToBlob = (canvas: HTMLCanvasElement): Promise => new Promise((resolve) => canvas.toBlob((blob) => resolve(blob!), 'image/png')); + if (dpCanvasRef.current) zip.file("diffraction_pattern.png", await canvasToBlob(dpCanvasRef.current)); + const zipBlob = await zip.generateAsync({ type: "blob" }); + downloadBlob(zipBlob, `dp_export_${timestamp}.zip`); + }; + + // Export VI handler + const handleExportVI = async () => { + if (lockExport) return; + const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19); + const zip = new JSZip(); + const metadata = { + metadata_version: "1.0", + widget_name: "Show4DSTEM", + widget_version: widgetVersion || "unknown", + exported_at: new Date().toISOString(), + view: "all", + format: "zip", + export_kind: "multi_panel_png_zip", + position: { row: posRow, col: posCol }, + frame_idx: frameIdx, + n_frames: nFrames, + scan_shape: { rows: shapeRows, cols: shapeCols }, + detector_shape: { rows: detRows, cols: detCols }, + roi: { + active: roiMode !== "off", + mode: roiMode, + center_row: roiCenterRow, + center_col: roiCenterCol, + radius: roiRadius, + radius_inner: roiRadiusInner, + width: roiWidth, + height: roiHeight, + }, + vi_roi: { + mode: viRoiMode, + center_row: viRoiCenterRow, + center_col: viRoiCenterCol, + radius: viRoiRadius, + width: viRoiWidth, + height: viRoiHeight, + }, + calibration: { + pixel_size_angstrom: pixelSize, + pixel_size_unit: "Å/px", + k_pixel_size: kPixelSize, + k_pixel_size_unit: kCalibrated ? "mrad/px" : "px/px", + k_calibrated: kCalibrated, + center_row: centerRow, + center_col: centerCol, + bf_radius: bfRadius, + }, + display: { + diffraction: { + colormap: dpColormap, + scale_mode: dpScaleMode, + vmin_pct: dpVminPct, + vmax_pct: dpVmaxPct, + }, + virtual: { + colormap: viColormap, + scale_mode: viScaleMode, + vmin_pct: viVminPct, + vmax_pct: viVmaxPct, + }, + fft: { + colormap: fftColormap, + scale_mode: fftScaleMode, + auto: fftAuto, + vmin_pct: fftVminPct, + vmax_pct: fftVmaxPct, + }, + }, + }; + zip.file("metadata.json", JSON.stringify(metadata, null, 2)); + const canvasToBlob = (canvas: HTMLCanvasElement): Promise => new Promise((resolve) => canvas.toBlob((blob) => resolve(blob!), 'image/png')); + if (virtualCanvasRef.current) zip.file("virtual_image.png", await canvasToBlob(virtualCanvasRef.current)); + if (dpCanvasRef.current) zip.file("diffraction_pattern.png", await canvasToBlob(dpCanvasRef.current)); + if (fftCanvasRef.current) zip.file("fft.png", await canvasToBlob(fftCanvasRef.current)); + const zipBlob = await zip.generateAsync({ type: "blob" }); + downloadBlob(zipBlob, `4dstem_export_${timestamp}.zip`); + }; + + // ── DP Figure Export ── + const handleDpExportFigure = (withColorbar: boolean) => { + if (lockExport) return; + setDpExportAnchor(null); + const frameData = rawDpDataRef.current; + if (!frameData) return; + const processed = dpScaleMode === "log" ? applyLogScale(frameData) : frameData; + const lut = COLORMAPS[dpColormap] || COLORMAPS.inferno; + const { min: dMin, max: dMax } = findDataRange(processed); + let vmin: number, vmax: number; + if (traitDpVmin != null && traitDpVmax != null) { + if (dpScaleMode === "log") { + vmin = Math.log1p(Math.max(traitDpVmin, 0)); + vmax = Math.log1p(Math.max(traitDpVmax, 0)); + } else if (dpScaleMode === "power") { + vmin = Math.pow(Math.max(traitDpVmin, 0), dpPowerExp); + vmax = Math.pow(Math.max(traitDpVmax, 0), dpPowerExp); + } else { + vmin = traitDpVmin; + vmax = traitDpVmax; + } + } else { + ({ vmin, vmax } = sliderRange(dMin, dMax, dpVminPct, dpVmaxPct)); + } + const offscreen = renderToOffscreen(processed, detCols, detRows, lut, vmin, vmax); + if (!offscreen) return; + const kPxAngstrom = kPixelSize > 0 && kCalibrated ? kPixelSize : 0; + const figCanvas = exportFigure({ + imageCanvas: offscreen, + title: `DP at (${posRow}, ${posCol})`, + lut, + vmin, + vmax, + logScale: dpScaleMode === "log", + pixelSize: kPxAngstrom > 0 ? kPxAngstrom : undefined, + showColorbar: withColorbar, + showScaleBar: kPxAngstrom > 0, + }); + canvasToPDF(figCanvas).then((blob) => downloadBlob(blob, "show4dstem_dp_figure.pdf")).catch(console.error); + }; + + const handleDpExportPng = () => { + if (lockExport) return; + setDpExportAnchor(null); + if (!dpCanvasRef.current) return; + dpCanvasRef.current.toBlob((b) => { if (b) downloadBlob(b, "show4dstem_dp.png"); }, "image/png"); + }; + + const handleDpExportGif = () => { + if (lockExport) return; + setDpExportAnchor(null); + setExporting(true); + setGifExportRequested(true); + }; + + // ── VI Figure Export ── + const handleViExportFigure = (withColorbar: boolean) => { + if (lockExport) return; + setViExportAnchor(null); + if (!virtualCanvasRef.current) return; + const viCanvas = virtualCanvasRef.current; + const pixelSizeAngstrom = pixelSize > 0 ? pixelSize : 0; + const figCanvas = exportFigure({ + imageCanvas: viCanvas, + title: "Virtual Image", + showColorbar: withColorbar, + showScaleBar: pixelSizeAngstrom > 0, + pixelSize: pixelSizeAngstrom > 0 ? pixelSizeAngstrom : undefined, + }); + canvasToPDF(figCanvas).then((blob) => downloadBlob(blob, "show4dstem_vi_figure.pdf")).catch(console.error); + }; + + const handleViExportPng = () => { + if (lockExport) return; + setViExportAnchor(null); + if (!virtualCanvasRef.current) return; + virtualCanvasRef.current.toBlob((b) => { if (b) downloadBlob(b, "show4dstem_vi.png"); }, "image/png"); + }; + + // Download GIF when data arrives from Python + React.useEffect(() => { + if (!gifData || gifData.byteLength === 0) return; + downloadDataView(gifData, "show4dstem_dp_animation.gif", "image/gif"); + const metaText = (gifMetadataJson || "").trim(); + if (metaText) { + downloadBlob(new Blob([metaText], { type: "application/json" }), "show4dstem_dp_animation.json"); + } + setExporting(false); + }, [gifData, gifMetadataJson]); + + + // Theme-aware select style + const themedSelect = { + ...controlPanel.select, + bgcolor: themeColors.controlBg, + color: themeColors.text, + "& .MuiSelect-select": { py: 0.5 }, + "& .MuiOutlinedInput-notchedOutline": { borderColor: themeColors.border }, + "&:hover .MuiOutlinedInput-notchedOutline": { borderColor: themeColors.accent }, + }; + + const themedMenuProps = { + ...upwardMenuProps, + PaperProps: { sx: { bgcolor: themeColors.controlBg, color: themeColors.text, border: `1px solid ${themeColors.border}` } }, + }; + + const keyboardShortcutItems: [string, string][] = [ + ["↑ / ↓", "Move scan row"], + ["← / →", "Move scan col"], + ["Shift+Arrows", "Move ×10"], + ...(nFrames > 1 ? [["[ / ]", `Prev / next ${frameDimLabel.toLowerCase()}`] as [string, string]] : []), + ["Space", "Play / pause"], + ["R", "Reset all zoom/pan"], + ["Esc", "Release keyboard focus"], + ["Scroll", "Zoom"], + ["Dbl-click", "Reset view"], + ]; + + return ( + + {/* HEADER */} + + {title || "4D-STEM Explorer"} + {nFrames > 1 && ({frameLabels && frameLabels.length > frameIdx ? frameLabels[frameIdx] : `${frameDimLabel} ${frameIdx + 1}/${nFrames}`})} + + Controls + DP: Diffraction pattern I(kx,ky) at scan position. Drag to move ROI center. + Detector: ROI mask shape — defines which DP pixels are integrated for the virtual image. + BF/ABF/ADF: Preset detector configurations (bright-field, annular bright-field, annular dark-field). + Image: Virtual image — integrated intensity within detector ROI at each scan position. + FFT: Spatial frequency content of the virtual image. Auto masks DC + clips to 99.9th percentile. + Profile: Click two points on DP to draw a line intensity profile. + {nFrames > 1 && <> + Frame Playback ({frameDimLabel}) + Loop: Loop playback. Bounce: Ping-pong — alternates forward and reverse. + FPS: Adjust playback speed (1–30 frames per second). + } + Keyboard + + } theme={themeInfo.theme} /> + + + + {/* MAIN CONTENT: DP | VI | FFT (three columns when FFT shown) */} + + {/* LEFT COLUMN: DP Panel */} + + {/* DP Header */} + + + DP at ({Math.round(localPosRow)}, {Math.round(localPosCol)}) + {!hideRoi && k: ({Math.round(localKRow)}, {Math.round(localKCol)})} + + + {!hideProfile && ( + <> + Profile: + { + if (lockProfile) return; + const on = e.target.checked; + setProfileActive(on); + if (!on) { + setProfileLine([]); + setProfileData(null); + setHoveredDpProfileEndpoint(null); + setIsHoveringDpProfileLine(false); + } + }} disabled={lockProfile} size="small" sx={switchStyles.small} /> + + )} + {!hideView && ( + + )} + {!hideExport && ( + + )} + {!hideExport && ( + + )} + {!hideExport && ( + setDpExportAnchor(null)} anchorOrigin={{ vertical: "bottom", horizontal: "left" }} transformOrigin={{ vertical: "top", horizontal: "left" }} sx={{ zIndex: 9999 }}> + handleDpExportFigure(true)} sx={{ fontSize: 12 }}>PDF + colorbar + handleDpExportFigure(false)} sx={{ fontSize: 12 }}>PDF + PNG + { if (!lockExport) { setDpExportAnchor(null); handleExportDP(); } }} sx={{ fontSize: 12 }}>ZIP (PNG + metadata) + {pathLength > 0 && GIF (path animation)} + + )} + + + + {/* DP Canvas */} + + + + + {cursorInfo && cursorInfo.panel === "DP" && ( + + + ({cursorInfo.row}, {cursorInfo.col}) {formatNumber(cursorInfo.value)} + + + )} + {!hideView && ( + + )} + + + {/* DP Stats Bar */} + {!hideStats && dpStats && dpStats.length === 4 && ( + + Mean {formatStat(dpStats[0])} + Min {formatStat(dpStats[1])} + Max {formatStat(dpStats[2])} + Std {formatStat(dpStats[3])} + {!hideRoi && ( + <> + + { if (!lockRoi) { setRoiMode("circle"); setRoiRadius(bfRadius || 10); setRoiCenterCol(centerCol); setRoiCenterRow(centerRow); } }} sx={{ color: roiColors.textColor, fontSize: 11, fontWeight: "bold", cursor: lockRoi ? "default" : "pointer", opacity: lockRoi ? 0.6 : 1, "&:hover": { textDecoration: lockRoi ? "none" : "underline" } }}>BF + { if (!lockRoi) { setRoiMode("annular"); setRoiRadiusInner((bfRadius || 10) * 0.5); setRoiRadius(bfRadius || 10); setRoiCenterCol(centerCol); setRoiCenterRow(centerRow); } }} sx={{ color: "#4af", fontSize: 11, fontWeight: "bold", cursor: lockRoi ? "default" : "pointer", opacity: lockRoi ? 0.6 : 1, "&:hover": { textDecoration: lockRoi ? "none" : "underline" } }}>ABF + { if (!lockRoi) { setRoiMode("annular"); setRoiRadiusInner(bfRadius || 10); setRoiRadius(Math.min((bfRadius || 10) * 3, Math.min(detRows, detCols) / 2 - 2)); setRoiCenterCol(centerCol); setRoiCenterRow(centerRow); } }} sx={{ color: "#fa4", fontSize: 11, fontWeight: "bold", cursor: lockRoi ? "default" : "pointer", opacity: lockRoi ? 0.6 : 1, "&:hover": { textDecoration: lockRoi ? "none" : "underline" } }}>ADF + + )} + + )} + + {/* Profile sparkline */} + {profileActive && !hideProfile && ( + + + { + if (lockProfile) return; + setIsResizingProfile(true); + profileResizeStart.current = { startY: e.clientY, startHeight: profileHeight }; + }} + sx={{ width: canvasSize, height: 4, cursor: lockProfile ? "default" : "ns-resize", borderTop: `1px solid ${themeColors.border}`, borderLeft: `1px solid ${themeColors.border}`, borderRight: `1px solid ${themeColors.border}`, borderBottom: `1px solid ${themeColors.border}`, bgcolor: themeColors.controlBg, "&:hover": { bgcolor: lockProfile ? themeColors.controlBg : themeColors.accent } }} + /> + + )} + + {/* DP Controls - two rows with histogram on right */} + {showControls && (!hideRoi || !hideDisplay || !hideHistogram) && ( + + {/* Left: two rows of controls */} + + {/* Row 1: Detector + slider */} + {!hideRoi && ( + + Detector: + + {(roiMode === "circle" || roiMode === "square" || roiMode === "annular") && ( + <> + { + if (lockRoi) return; + if (roiMode === "annular") { + const [inner, outer] = v as number[]; + setRoiRadiusInner(Math.min(inner, outer - 1)); + setRoiRadius(Math.max(outer, inner + 1)); + } else { + const next = Array.isArray(v) ? v[0] : v; + setRoiRadius(next); + } + }} + min={1} + max={Math.min(detRows, detCols) / 2} + size="small" + sx={{ + width: roiMode === "annular" ? 100 : 70, + mx: 1, + "& .MuiSlider-thumb": { width: 14, height: 14 } + }} + /> + + {roiMode === "annular" ? `${Math.round(roiRadiusInner)}-${Math.round(roiRadius)}px` : `${Math.round(roiRadius)}px`} + + + )} + + )} + {/* Row 2: Color + Scale + Colorbar */} + {!hideDisplay && ( + + Color: + + Scale: + + Colorbar: + { if (!lockDisplay) setShowDpColorbar(e.target.checked); }} disabled={lockDisplay} size="small" sx={switchStyles.small} /> + + )} + + {/* Right: Histogram spanning both rows */} + {!hideHistogram && ( + + { if (!lockHistogram) { setDpVminPct(min); setDpVmaxPct(max); } }} width={110} height={58} theme={themeInfo.theme} dataMin={dpGlobalMin} dataMax={dpGlobalMax} /> + + )} + + )} + + + {/* SECOND COLUMN: VI Panel */} + {!hideVirtual && ( + + {/* VI Header */} + + + {shapeRows}×{shapeCols} | {detRows}×{detCols} + + + {!hideFft && ( + <> + FFT: + { if (!lockFft) setShowFft(e.target.checked); }} disabled={lockFft} size="small" sx={switchStyles.small} /> + + )} + {!hideProfile && ( + <> + Profile: + { + if (lockProfile) return; + const on = e.target.checked; + setViProfileActive(on); + if (!on) { + setViProfilePoints([]); + setHoveredViProfileEndpoint(null); + setIsHoveringViProfileLine(false); + } + }} disabled={lockProfile} size="small" sx={switchStyles.small} /> + + )} + {!hideView && ( + + )} + {!hideExport && ( + + )} + {!hideExport && ( + + )} + {!hideExport && ( + setViExportAnchor(null)} anchorOrigin={{ vertical: "bottom", horizontal: "left" }} transformOrigin={{ vertical: "top", horizontal: "left" }} sx={{ zIndex: 9999 }}> + handleViExportFigure(true)} sx={{ fontSize: 12 }}>PDF + colorbar + handleViExportFigure(false)} sx={{ fontSize: 12 }}>PDF + PNG + { if (!lockExport && !lockVirtual) { setViExportAnchor(null); handleExportVI(); } }} sx={{ fontSize: 12 }}>ZIP (all panels + metadata) + + )} + + + + {/* VI Canvas */} + + + + + {cursorInfo && cursorInfo.panel === "VI" && ( + + + ({cursorInfo.row}, {cursorInfo.col}) {formatNumber(cursorInfo.value)} + + + )} + {!hideView && ( + + )} + + + {/* VI Stats Bar */} + {!hideStats && viStats && viStats.length === 4 && ( + + Mean {formatStat(viStats[0])} + Min {formatStat(viStats[1])} + Max {formatStat(viStats[2])} + Std {formatStat(viStats[3])} + + )} + + {/* VI Profile sparkline */} + {viProfileActive && !hideProfile && ( + + + { + if (lockProfile) return; + setIsResizingViProfile(true); + viProfileResizeStart.current = { startY: e.clientY, startHeight: viProfileHeight }; + }} + sx={{ width: viCanvasWidth, height: 4, cursor: lockProfile ? "default" : "ns-resize", borderTop: `1px solid ${themeColors.border}`, borderLeft: `1px solid ${themeColors.border}`, borderRight: `1px solid ${themeColors.border}`, borderBottom: `1px solid ${themeColors.border}`, bgcolor: themeColors.controlBg, "&:hover": { bgcolor: lockProfile ? themeColors.controlBg : themeColors.accent } }} + /> + + )} + + {/* VI Controls - Two rows with histogram on right */} + {showControls && (!hideRoi || !hideDisplay || !hideHistogram) && ( + + {/* Left: Two rows of controls */} + + {/* Row 1: ROI selector */} + {!hideRoi && ( + + ROI: + + {viRoiMode && viRoiMode !== "off" && ( + <> + {(viRoiMode === "circle" || viRoiMode === "square") && ( + <> + { if (!lockRoi) setViRoiRadius(v as number); }} + min={1} + max={Math.min(shapeRows, shapeCols) / 2} + size="small" + sx={{ width: 80, mx: 1 }} + /> + + {Math.round(viRoiRadius || 5)}px + + + )} + {summedDpCount > 0 && ( + + {summedDpCount} pos + + )} + + )} + + )} + {/* Row 2: Color + Scale */} + {!hideDisplay && ( + + Color: + + Scale: + + + )} + + {/* Right: Histogram spanning both rows */} + {!hideHistogram && ( + + { if (!lockHistogram) { setViVminPct(min); setViVmaxPct(max); } }} width={110} height={58} theme={themeInfo.theme} dataMin={viDataMin} dataMax={viDataMax} /> + + )} + + )} + + )} + + {/* THIRD COLUMN: FFT Panel (conditionally shown) */} + {effectiveShowFft && ( + + {/* FFT Header */} + + {roiFftActive && fftCropDims ? `ROI FFT (${fftCropDims.cropWidth}\u00D7${fftCropDims.cropHeight})` : "FFT"} + + {!hideView && ( + + )} + + + + {/* FFT Canvas */} + + + + {!hideView && ( + + )} + + + {/* FFT Stats Bar */} + {!hideStats && fftStats && fftStats.length === 4 && ( + + Mean {formatStat(fftStats[0])} + Min {formatStat(fftStats[1])} + Max {formatStat(fftStats[2])} + Std {formatStat(fftStats[3])} + + )} + + {/* FFT D-spacing readout */} + {fftClickInfo && ( + + + Spot ({fftClickInfo.row.toFixed(1)}, {fftClickInfo.col.toFixed(1)}) + + + dist {fftClickInfo.distPx.toFixed(1)} px + + {fftClickInfo.dSpacing != null && ( + + d = {fftClickInfo.dSpacing >= 10 ? `${(fftClickInfo.dSpacing / 10).toFixed(2)} nm` : `${fftClickInfo.dSpacing.toFixed(2)} \u00C5`} + + )} + {fftClickInfo.spatialFreq != null && ( + + q = {fftClickInfo.spatialFreq.toFixed(4)} {"\u00C5\u207B\u00B9"} + + )} + + )} + + {/* FFT Controls - Two rows with histogram on right */} + {showControls && (!hideDisplay || !hideHistogram) && ( + + {/* Left: Two rows of controls */} + {!hideDisplay && ( + + {/* Row 1: Scale + Clip */} + + Scale: + + Auto: + { if (!lockDisplay && !lockFft) setFftAuto(e.target.checked); }} disabled={lockDisplay || lockFft} size="small" sx={switchStyles.small} /> + {fftCropDims && ( + <> + Win: + { if (!lockDisplay && !lockFft) setFftWindow(e.target.checked); }} disabled={lockDisplay || lockFft} size="small" sx={switchStyles.small} /> + + )} + + {/* Row 2: Color */} + + Color: + + + + )} + {/* Right: Histogram spanning both rows */} + {!hideHistogram && ( + + {fftHistogramData && ( + { if (!lockHistogram && !lockFft) { setFftVminPct(min); setFftVmaxPct(max); } }} width={110} height={58} theme={themeInfo.theme} dataMin={fftDataMin} dataMax={fftDataMax} /> + )} + + )} + + )} + + )} + + + {/* BOTTOM CONTROLS */} + + {/* Frame controls (5D time/tilt series) — matches Show3D playback */} + {showControls && nFrames > 1 && !hidePlayback && !hideFrame && (<> + + {frameDimLabel}: + + { if (!lockFrame && !lockPlayback) { setFrameReverse(true); setFramePlaying(true); } }} sx={{ color: frameReverse && framePlaying ? themeColors.accent : themeColors.textMuted, p: 0.25 }}> + + + { if (!lockFrame && !lockPlayback) setFramePlaying(!framePlaying); }} sx={{ color: themeColors.accent, p: 0.25 }}> + {framePlaying ? : } + + { if (!lockFrame && !lockPlayback) { setFrameReverse(false); setFramePlaying(true); } }} sx={{ color: !frameReverse && framePlaying ? themeColors.accent : themeColors.textMuted, p: 0.25 }}> + + + { if (!lockFrame && !lockPlayback) { setFramePlaying(false); setFrameIdx(0); } }} sx={{ color: themeColors.textMuted, p: 0.25 }}> + + + + { if (!lockFrame && !lockPlayback) { setFramePlaying(false); setFrameIdx(v as number); } }} min={0} max={Math.max(0, nFrames - 1)} size="small" sx={{ flex: 1, minWidth: 60, "& .MuiSlider-thumb": { width: 10, height: 10 } }} /> + {frameLabels && frameLabels.length > frameIdx ? frameLabels[frameIdx] : `${frameIdx + 1}/${nFrames}`} + + + fps + { if (!lockFrame && !lockPlayback) setFrameFps(v as number); }} size="small" sx={{ ...sliderStyles.small, width: 35, flexShrink: 0 }} /> + {Math.round(frameFps)} + Loop + { if (!lockFrame && !lockPlayback) setFrameLoop(!frameLoop); }} disabled={lockFrame || lockPlayback} sx={{ ...switchStyles.small, flexShrink: 0 }} /> + Bounce + { if (!lockFrame && !lockPlayback) setFrameBoomerang(!frameBoomerang); }} disabled={lockFrame || lockPlayback} sx={{ ...switchStyles.small, flexShrink: 0 }} /> + + )} + + {/* Path animation slider */} + {showControls && !hidePlayback && pathLength > 0 && ( + + + { if (!lockPlayback) setPathPlaying(!pathPlaying); }} sx={{ color: themeColors.accent, p: 0.25 }}> + {pathPlaying ? : } + + { if (!lockPlayback) { setPathPlaying(false); setPathIndex(0); } }} sx={{ color: themeColors.textMuted, p: 0.25 }}> + + + + { if (!lockPlayback) { setPathPlaying(false); setPathIndex(v as number); } }} min={0} max={Math.max(0, pathLength - 1)} size="small" sx={{ flex: 1, minWidth: 60, "& .MuiSlider-thumb": { width: 10, height: 10 } }} /> + {pathIndex + 1}/{pathLength} + Loop: + { if (!lockPlayback) { model.set("path_loop", v); model.save_changes(); } }} disabled={lockPlayback} size="small" sx={switchStyles.small} /> + + )} + + ); +} + +export const render = createRender(Show4DSTEM); diff --git a/widget/js/show4dstem/styles.css b/widget/js/show4dstem/styles.css new file mode 100644 index 00000000..61876cde --- /dev/null +++ b/widget/js/show4dstem/styles.css @@ -0,0 +1,5 @@ +/* Theme-aware styles - minimal, let JS handle most theming */ +.show4dstem-root { + border-radius: 2px; + padding: 16px; +} diff --git a/widget/js/stats.ts b/widget/js/stats.ts new file mode 100644 index 00000000..b71c45aa --- /dev/null +++ b/widget/js/stats.ts @@ -0,0 +1,101 @@ +/** Find min/max range of a Float32Array, filtering out NaN and Infinity. */ +export function findDataRange(data: Float32Array): { min: number; max: number } { + let min = Infinity, max = -Infinity; + for (let i = 0; i < data.length; i++) { + const v = data[i]; + if (!isFinite(v)) continue; + if (v < min) min = v; + if (v > max) max = v; + } + // If no finite values found, return zeros + if (min === Infinity) return { min: 0, max: 0 }; + return { min, max }; +} + +/** Apply log1p scale: result[i] = log(1 + max(0, data[i])). Returns a new array. */ +export function applyLogScale(data: Float32Array): Float32Array { + const result = new Float32Array(data.length); + for (let i = 0; i < data.length; i++) { + result[i] = Math.log1p(Math.max(0, data[i])); + } + return result; +} + +/** Apply log1p scale into a pre-allocated buffer. Avoids per-frame allocation. */ +export function applyLogScaleInPlace(data: Float32Array, out: Float32Array): Float32Array { + for (let i = 0; i < data.length; i++) { + out[i] = Math.log1p(Math.max(0, data[i])); + } + return out; +} + +/** Percentile-based clipping using O(n) histogram approach. + * Also returns data min/max so callers can skip a redundant findDataRange scan. */ +export function percentileClip( + data: Float32Array, pLow: number, pHigh: number, +): { vmin: number; vmax: number; min: number; max: number } { + const len = data.length; + if (len === 0) return { vmin: 0, vmax: 0, min: 0, max: 0 }; + + // Pass 1: find min/max + let min = Infinity, max = -Infinity; + for (let i = 0; i < len; i++) { + const v = data[i]; + if (v < min) min = v; + if (v > max) max = v; + } + if (min === max) return { vmin: min, vmax: max, min, max }; + + // Pass 2: build histogram + const NUM_BINS = 1024; + const bins = new Uint32Array(NUM_BINS); + const range = max - min; + const scale = (NUM_BINS - 1) / range; + for (let i = 0; i < len; i++) { + bins[Math.floor((data[i] - min) * scale)]++; + } + + // Walk cumulative histogram to find percentile values + const lowCount = Math.floor(len * (pLow / 100)); + const highCount = Math.ceil(len * (pHigh / 100)); + let cumSum = 0; + let vmin = min, vmax = max; + for (let i = 0; i < NUM_BINS; i++) { + cumSum += bins[i]; + if (cumSum >= lowCount) { vmin = min + (i / (NUM_BINS - 1)) * range; break; } + } + cumSum = 0; + for (let i = 0; i < NUM_BINS; i++) { + cumSum += bins[i]; + if (cumSum >= highCount) { vmax = min + (i / (NUM_BINS - 1)) * range; break; } + } + return { vmin, vmax, min, max }; +} + +/** Compute mean, min, max, and standard deviation of a Float32Array. */ +export function computeStats(data: Float32Array): { mean: number; min: number; max: number; std: number } { + if (data.length === 0) return { mean: 0, min: 0, max: 0, std: 0 }; + let sum = 0, min = Infinity, max = -Infinity; + for (let i = 0; i < data.length; i++) { + const v = data[i]; + sum += v; + if (v < min) min = v; + if (v > max) max = v; + } + const mean = sum / data.length; + let variance = 0; + for (let i = 0; i < data.length; i++) variance += (data[i] - mean) ** 2; + const std = Math.sqrt(variance / data.length); + return { mean, min, max, std }; +} + +/** Convert histogram slider percentages (0-100) to vmin/vmax in data space. */ +export function sliderRange( + dataMin: number, dataMax: number, vminPct: number, vmaxPct: number, +): { vmin: number; vmax: number } { + const range = dataMax - dataMin; + return { + vmin: dataMin + (vminPct / 100) * range, + vmax: dataMin + (vmaxPct / 100) * range, + }; +} diff --git a/widget/js/theme.ts b/widget/js/theme.ts new file mode 100644 index 00000000..f13123d5 --- /dev/null +++ b/widget/js/theme.ts @@ -0,0 +1,149 @@ +/** + * Shared theme detection and color system for all widgets. + * Detects JupyterLab, VS Code, Colab, Classic Jupyter, and OS preferences. + */ + +import { useState, useEffect, useMemo } from "react"; + +// ============================================================================ +// Types +// ============================================================================ +export type Environment = "jupyterlab" | "vscode" | "colab" | "jupyter-classic" | "unknown"; +export type Theme = "light" | "dark"; + +export interface ThemeInfo { + environment: Environment; + theme: Theme; +} + +export interface ThemeColors { + bg: string; + bgAlt: string; + text: string; + textMuted: string; + border: string; + controlBg: string; + accent: string; +} + +// ============================================================================ +// Color palettes +// ============================================================================ +export const DARK_COLORS: ThemeColors = { + bg: "#1e1e1e", + bgAlt: "#1a1a1a", + text: "#e0e0e0", + textMuted: "#888", + border: "#3a3a3a", + controlBg: "#252525", + accent: "#5af", +}; + +export const LIGHT_COLORS: ThemeColors = { + bg: "#ffffff", + bgAlt: "#f5f5f5", + text: "#1e1e1e", + textMuted: "#666", + border: "#ccc", + controlBg: "#f0f0f0", + accent: "#0066cc", +}; + +export function getThemeColors(theme: Theme): ThemeColors { + return theme === "dark" ? DARK_COLORS : LIGHT_COLORS; +} + +// ============================================================================ +// Theme detection +// ============================================================================ + +/** Check if a CSS color string is dark (luminance < 0.5) */ +export function isColorDark(color: string): boolean { + const match = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/); + if (!match) return true; + const [, r, g, b] = match.map(Number); + const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255; + return luminance < 0.5; +} + +export function detectTheme(): ThemeInfo { + // 1. JupyterLab - has data-jp-theme-light attribute + const jpThemeLight = document.body.dataset.jpThemeLight; + if (jpThemeLight !== undefined) { + return { + environment: "jupyterlab", + theme: jpThemeLight === "true" ? "light" : "dark", + }; + } + + // 2. VS Code - has vscode-* classes on body or html + const bodyClasses = document.body.className; + const htmlClasses = document.documentElement.className; + if (bodyClasses.includes("vscode-") || htmlClasses.includes("vscode-")) { + const isDark = bodyClasses.includes("vscode-dark") || htmlClasses.includes("vscode-dark"); + return { + environment: "vscode", + theme: isDark ? "dark" : "light", + }; + } + + // 3. Google Colab - has specific markers + if (document.querySelector('colab-shaded-scroller') || document.body.classList.contains('colaboratory')) { + const bg = getComputedStyle(document.body).backgroundColor; + return { + environment: "colab", + theme: isColorDark(bg) ? "dark" : "light", + }; + } + + // 4. Classic Jupyter Notebook - has #notebook element + if (document.getElementById('notebook')) { + const bodyBg = getComputedStyle(document.body).backgroundColor; + return { + environment: "jupyter-classic", + theme: isColorDark(bodyBg) ? "dark" : "light", + }; + } + + // 5. Fallback: check OS preference, then computed background + const prefersDark = window.matchMedia?.('(prefers-color-scheme: dark)')?.matches; + if (prefersDark !== undefined) { + return { + environment: "unknown", + theme: prefersDark ? "dark" : "light", + }; + } + + // Final fallback: check body background luminance + const bg = getComputedStyle(document.body).backgroundColor; + return { + environment: "unknown", + theme: isColorDark(bg) ? "dark" : "light", + }; +} + +// ============================================================================ +// React hook +// ============================================================================ +export function useTheme(): { themeInfo: ThemeInfo; colors: ThemeColors } { + const [themeInfo, setThemeInfo] = useState(() => detectTheme()); + + useEffect(() => { + const mediaQuery = window.matchMedia?.('(prefers-color-scheme: dark)'); + const handleChange = () => setThemeInfo(detectTheme()); + mediaQuery?.addEventListener?.('change', handleChange); + + const observer = new MutationObserver(() => setThemeInfo(detectTheme())); + observer.observe(document.body, { attributes: true, attributeFilter: ['data-jp-theme-light', 'class'] }); + + return () => { + mediaQuery?.removeEventListener?.('change', handleChange); + observer.disconnect(); + }; + }, []); + + // Memoize by theme string so `colors` is referentially stable across renders — + // effects/components that depend on `colors` only re-run when the theme flips. + const colors = useMemo(() => getThemeColors(themeInfo.theme), [themeInfo.theme]); + return { themeInfo, colors }; +} diff --git a/widget/js/tool-parity.ts b/widget/js/tool-parity.ts new file mode 100644 index 00000000..8e318e6c --- /dev/null +++ b/widget/js/tool-parity.ts @@ -0,0 +1,156 @@ +import registryJson from "../src/quantem/widget/tool_parity.json"; + +type ToolInput = string | string[] | null | undefined; + +type WidgetConfig = { + tool_groups: string[]; + aliases?: Record; +}; + +type ControlPreset = { + label: string; + show_groups: string[]; +}; + +type ToolParityRegistry = { + widgets: Record; + control_presets: Record; + viewer_widgets?: string[]; +}; + +const REGISTRY = registryJson as ToolParityRegistry; + +function getWidgetConfig(widgetName: string): WidgetConfig { + const cfg = REGISTRY.widgets[widgetName]; + if (!cfg) { + const supported = Object.keys(REGISTRY.widgets).sort().join(", "); + throw new Error(`Unknown widget '${widgetName}'. Supported widgets: ${supported}.`); + } + return cfg; +} + +function toValues(values: ToolInput): string[] { + if (values == null) return []; + if (typeof values === "string") return [values]; + return [...values]; +} + +function toCanonical(widgetName: string, value: string): string { + const cfg = getWidgetConfig(widgetName); + const aliases = cfg.aliases ?? {}; + const key = value.trim().toLowerCase(); + return aliases[key] ?? key; +} + +export function getWidgetToolGroups(widgetName: string): string[] { + return [...getWidgetConfig(widgetName).tool_groups]; +} + +export function normalizeToolGroups(widgetName: string, values: ToolInput): string[] { + const groups = getWidgetToolGroups(widgetName); + const groupSet = new Set(groups); + const out: string[] = []; + const seen = new Set(); + for (const raw of toValues(values)) { + const canonical = toCanonical(widgetName, String(raw)); + if (!canonical) continue; + if (!groupSet.has(canonical)) { + const supported = groups.map((g) => `"${g}"`).join(", "); + throw new Error(`Unknown tool group '${raw}'. Supported values: ${supported}.`); + } + if (canonical === "all") return ["all"]; + if (!seen.has(canonical)) { + seen.add(canonical); + out.push(canonical); + } + } + return out; +} + +function orderedWithoutAll(widgetName: string, values: Set): string[] { + return getWidgetToolGroups(widgetName).filter((group) => group !== "all" && values.has(group)); +} + +export function expandToolGroups(widgetName: string, values: ToolInput): string[] { + const normalized = normalizeToolGroups(widgetName, values); + if (!normalized.includes("all")) return normalized; + return getWidgetToolGroups(widgetName).filter((group) => group !== "all"); +} + +export function compactToolLabel(key: string): string { + return key + .replace(/_/g, " ") + .replace(/\b\w/g, (m) => m.toUpperCase()); +} + +export function getControlPresetIds(): string[] { + return Object.keys(REGISTRY.control_presets); +} + +export function getControlPresetLabel(presetId: string): string { + const preset = REGISTRY.control_presets[presetId]; + return preset?.label ?? presetId; +} + +export function resolvePresetHiddenTools(widgetName: string, presetId: string): string[] { + const preset = REGISTRY.control_presets[presetId]; + if (!preset) { + const supported = Object.keys(REGISTRY.control_presets).sort().join(", "); + throw new Error(`Unknown control preset '${presetId}'. Supported presets: ${supported}.`); + } + const supportedGroups = getWidgetToolGroups(widgetName).filter((group) => group !== "all"); + if (preset.show_groups.includes("*")) return []; + const show = new Set(preset.show_groups.map((g) => toCanonical(widgetName, g))); + const hidden = supportedGroups.filter((group) => !show.has(group)); + return normalizeToolGroups(widgetName, hidden); +} + +export type ToolVisibilityState = { + hideAll: boolean; + lockAll: boolean; + isHidden: (group: string) => boolean; + isLocked: (group: string) => boolean; + hiddenSet: Set; + disabledSet: Set; +}; + +export function computeToolVisibility( + widgetName: string, + disabledTools: ToolInput, + hiddenTools: ToolInput, +): ToolVisibilityState { + const hidden = normalizeToolGroups(widgetName, hiddenTools); + const disabled = normalizeToolGroups(widgetName, disabledTools); + const hiddenSet = new Set(hidden); + const disabledSet = new Set(disabled); + const hideAll = hiddenSet.has("all"); + const lockAll = hideAll || disabledSet.has("all"); + + const isHidden = (group: string): boolean => { + const canonical = toCanonical(widgetName, group); + if (canonical === "all") return hideAll; + return hideAll || hiddenSet.has(canonical); + }; + + const isLocked = (group: string): boolean => { + const canonical = toCanonical(widgetName, group); + if (canonical === "all") return lockAll; + return lockAll || isHidden(canonical) || disabledSet.has(canonical); + }; + + return { hideAll, lockAll, isHidden, isLocked, hiddenSet, disabledSet }; +} + +export function addToolGroup(widgetName: string, current: ToolInput, group: string): string[] { + const merged = new Set(expandToolGroups(widgetName, current)); + const canonical = toCanonical(widgetName, group); + if (canonical === "all") return ["all"]; + merged.add(canonical); + return orderedWithoutAll(widgetName, merged); +} + +export function removeToolGroup(widgetName: string, current: ToolInput, group: string): string[] { + const merged = new Set(expandToolGroups(widgetName, current)); + merged.delete(toCanonical(widgetName, group)); + return orderedWithoutAll(widgetName, merged); +} diff --git a/widget/js/webgpu-fft.ts b/widget/js/webgpu-fft.ts new file mode 100644 index 00000000..2498b755 --- /dev/null +++ b/widget/js/webgpu-fft.ts @@ -0,0 +1,509 @@ +/// + +/** + * WebGPU FFT — shared 2D FFT with GPU acceleration and CPU fallback. + * Handles non-power-of-2 dimensions via zero-padding. + */ + +// ============================================================================ +// CPU FFT fallback +// ============================================================================ + +export function nextPow2(n: number): number { return Math.pow(2, Math.ceil(Math.log2(n))); } + +function fft1d(real: Float32Array, imag: Float32Array, inverse: boolean = false) { + const n = real.length; + if (n <= 1) return; + let j = 0; + for (let i = 0; i < n - 1; i++) { + if (i < j) { [real[i], real[j]] = [real[j], real[i]]; [imag[i], imag[j]] = [imag[j], imag[i]]; } + let k = n >> 1; + while (k <= j) { j -= k; k >>= 1; } + j += k; + } + const sign = inverse ? 1 : -1; + for (let len = 2; len <= n; len <<= 1) { + const halfLen = len >> 1; + const angle = (sign * 2 * Math.PI) / len; + const wReal = Math.cos(angle), wImag = Math.sin(angle); + for (let i = 0; i < n; i += len) { + let curReal = 1, curImag = 0; + for (let k = 0; k < halfLen; k++) { + const evenIdx = i + k, oddIdx = i + k + halfLen; + const tReal = curReal * real[oddIdx] - curImag * imag[oddIdx]; + const tImag = curReal * imag[oddIdx] + curImag * real[oddIdx]; + real[oddIdx] = real[evenIdx] - tReal; imag[oddIdx] = imag[evenIdx] - tImag; + real[evenIdx] += tReal; imag[evenIdx] += tImag; + const newReal = curReal * wReal - curImag * wImag; + curImag = curReal * wImag + curImag * wReal; curReal = newReal; + } + } + } + if (inverse) { for (let i = 0; i < n; i++) { real[i] /= n; imag[i] /= n; } } +} + +export function fft2d(real: Float32Array, imag: Float32Array, width: number, height: number, inverse: boolean = false) { + const paddedW = nextPow2(width), paddedH = nextPow2(height); + const needsPadding = paddedW !== width || paddedH !== height; + let workReal: Float32Array, workImag: Float32Array; + if (needsPadding) { + workReal = new Float32Array(paddedW * paddedH); workImag = new Float32Array(paddedW * paddedH); + for (let y = 0; y < height; y++) for (let x = 0; x < width; x++) { + workReal[y * paddedW + x] = real[y * width + x]; workImag[y * paddedW + x] = imag[y * width + x]; + } + } else { workReal = real; workImag = imag; } + const rowReal = new Float32Array(paddedW), rowImag = new Float32Array(paddedW); + for (let y = 0; y < paddedH; y++) { + const offset = y * paddedW; + for (let x = 0; x < paddedW; x++) { rowReal[x] = workReal[offset + x]; rowImag[x] = workImag[offset + x]; } + fft1d(rowReal, rowImag, inverse); + for (let x = 0; x < paddedW; x++) { workReal[offset + x] = rowReal[x]; workImag[offset + x] = rowImag[x]; } + } + const colReal = new Float32Array(paddedH), colImag = new Float32Array(paddedH); + for (let x = 0; x < paddedW; x++) { + for (let y = 0; y < paddedH; y++) { colReal[y] = workReal[y * paddedW + x]; colImag[y] = workImag[y * paddedW + x]; } + fft1d(colReal, colImag, inverse); + for (let y = 0; y < paddedH; y++) { workReal[y * paddedW + x] = colReal[y]; workImag[y * paddedW + x] = colImag[y]; } + } + if (needsPadding) { + for (let y = 0; y < height; y++) for (let x = 0; x < width; x++) { + real[y * width + x] = workReal[y * paddedW + x]; imag[y * width + x] = workImag[y * paddedW + x]; + } + } +} + +export function fftshift(data: Float32Array, width: number, height: number): void { + const halfW = width >> 1, halfH = height >> 1; + const temp = new Float32Array(width * height); + for (let y = 0; y < height; y++) for (let x = 0; x < width; x++) { + temp[((y + halfH) % height) * width + ((x + halfW) % width)] = data[y * width + x]; + } + data.set(temp); +} + +// ============================================================================ +// CPU FFT Web Worker — runs fft2d + fftshift + computeMagnitude off main thread +// ============================================================================ + +const FFT_WORKER_CODE = ` +function nextPow2(n) { return Math.pow(2, Math.ceil(Math.log2(n))); } +function fft1d(real, imag, inverse) { + var n = real.length; if (n <= 1) return; + var j = 0; + for (var i = 0; i < n - 1; i++) { + if (i < j) { var t = real[i]; real[i] = real[j]; real[j] = t; t = imag[i]; imag[i] = imag[j]; imag[j] = t; } + var k = n >> 1; while (k <= j) { j -= k; k >>= 1; } j += k; + } + var sign = inverse ? 1 : -1; + for (var len = 2; len <= n; len <<= 1) { + var halfLen = len >> 1, angle = (sign * 2 * Math.PI) / len; + var wR = Math.cos(angle), wI = Math.sin(angle); + for (var i = 0; i < n; i += len) { + var cR = 1, cI = 0; + for (var k = 0; k < halfLen; k++) { + var eI = i + k, oI = i + k + halfLen; + var tR = cR * real[oI] - cI * imag[oI], tI = cR * imag[oI] + cI * real[oI]; + real[oI] = real[eI] - tR; imag[oI] = imag[eI] - tI; + real[eI] += tR; imag[eI] += tI; + var nR = cR * wR - cI * wI; cI = cR * wI + cI * wR; cR = nR; + } + } + } + if (inverse) { for (var i = 0; i < n; i++) { real[i] /= n; imag[i] /= n; } } +} +function fft2d(real, imag, width, height, inverse) { + var pW = nextPow2(width), pH = nextPow2(height), pad = pW !== width || pH !== height; + var wR, wI; + if (pad) { + wR = new Float32Array(pW * pH); wI = new Float32Array(pW * pH); + for (var y = 0; y < height; y++) for (var x = 0; x < width; x++) { wR[y*pW+x] = real[y*width+x]; wI[y*pW+x] = imag[y*width+x]; } + } else { wR = real; wI = imag; } + var rR = new Float32Array(pW), rI = new Float32Array(pW); + for (var y = 0; y < pH; y++) { + var o = y * pW; for (var x = 0; x < pW; x++) { rR[x] = wR[o+x]; rI[x] = wI[o+x]; } + fft1d(rR, rI, inverse); for (var x = 0; x < pW; x++) { wR[o+x] = rR[x]; wI[o+x] = rI[x]; } + } + var cR = new Float32Array(pH), cI = new Float32Array(pH); + for (var x = 0; x < pW; x++) { + for (var y = 0; y < pH; y++) { cR[y] = wR[y*pW+x]; cI[y] = wI[y*pW+x]; } + fft1d(cR, cI, inverse); for (var y = 0; y < pH; y++) { wR[y*pW+x] = cR[y]; wI[y*pW+x] = cI[y]; } + } + if (pad) { for (var y = 0; y < height; y++) for (var x = 0; x < width; x++) { real[y*width+x] = wR[y*pW+x]; imag[y*width+x] = wI[y*pW+x]; } } +} +function fftshift(data, width, height) { + var hW = width >> 1, hH = height >> 1, temp = new Float32Array(width * height); + for (var y = 0; y < height; y++) for (var x = 0; x < width; x++) temp[((y+hH)%height)*width+((x+hW)%width)] = data[y*width+x]; + data.set(temp); +} +self.onmessage = function(e) { + var d = e.data, real = d.real, imag = d.imag, w = d.width, h = d.height; + fft2d(real, imag, w, h, d.inverse); + fftshift(real, w, h); fftshift(imag, w, h); + var n = real.length, mag = new Float32Array(n); + for (var i = 0; i < n; i++) mag[i] = Math.sqrt(real[i]*real[i] + imag[i]*imag[i]); + self.postMessage({ id: d.id, magnitude: mag, real: real, imag: imag }, [mag.buffer, real.buffer, imag.buffer]); +}; +`; + +let _fftWorker: Worker | null = null; +const _fftCallbacks = new Map void>(); +let _fftWorkerId = 0; + +function getFFTWorker(): Worker { + if (!_fftWorker) { + const blob = new Blob([FFT_WORKER_CODE], { type: 'application/javascript' }); + _fftWorker = new Worker(URL.createObjectURL(blob)); + _fftWorker.onmessage = (e: MessageEvent) => { + const cb = _fftCallbacks.get(e.data.id); + if (cb) { + _fftCallbacks.delete(e.data.id); + cb(e.data); + } + }; + } + return _fftWorker; +} + +/** + * CPU FFT in a Web Worker — does fft2d + fftshift + computeMagnitude off main thread. + * Transfers Float32Arrays to the worker (zero-copy) so the main thread is never blocked. + * The input arrays become detached after this call — pass copies if you need to keep them. + */ +export function fft2dAsync( + real: Float32Array, imag: Float32Array, + width: number, height: number, + inverse: boolean = false, +): Promise<{ magnitude: Float32Array; real: Float32Array; imag: Float32Array }> { + const worker = getFFTWorker(); + const id = ++_fftWorkerId; + return new Promise((resolve) => { + _fftCallbacks.set(id, resolve); + worker.postMessage( + { id, real, imag, width, height, inverse }, + [real.buffer, imag.buffer], + ); + }); +} + +// ============================================================================ +// WebGPU FFT — GPU-accelerated 2D FFT +// ============================================================================ + +const FFT_2D_SHADER = /* wgsl */` +fn cmul(a: vec2, b: vec2) -> vec2 { return vec2(a.x*b.x-a.y*b.y, a.x*b.y+a.y*b.x); } +fn twiddle(k: u32, N: u32, inverse: f32) -> vec2 { let angle = inverse * 2.0 * 3.14159265359 * f32(k) / f32(N); return vec2(cos(angle), sin(angle)); } +fn bitReverse(x: u32, log2N: u32) -> u32 { var result: u32 = 0u; var val = x; for (var i: u32 = 0u; i < log2N; i = i + 1u) { result = (result << 1u) | (val & 1u); val = val >> 1u; } return result; } +struct FFT2DParams { width: u32, height: u32, log2Size: u32, stage: u32, inverse: f32, isRowWise: u32, } +@group(0) @binding(0) var params: FFT2DParams; +@group(0) @binding(1) var data: array>; +fn getIndex(row: u32, col: u32) -> u32 { return row * params.width + col; } +@compute @workgroup_size(16, 16) fn bitReverseRows(@builtin(global_invocation_id) gid: vec3) { let row = gid.y; let col = gid.x; if (row >= params.height || col >= params.width) { return; } let rev = bitReverse(col, params.log2Size); if (col < rev) { let idx1 = getIndex(row, col); let idx2 = getIndex(row, rev); let temp = data[idx1]; data[idx1] = data[idx2]; data[idx2] = temp; } } +@compute @workgroup_size(16, 16) fn bitReverseCols(@builtin(global_invocation_id) gid: vec3) { let row = gid.y; let col = gid.x; if (row >= params.height || col >= params.width) { return; } let rev = bitReverse(row, params.log2Size); if (row < rev) { let idx1 = getIndex(row, col); let idx2 = getIndex(rev, col); let temp = data[idx1]; data[idx1] = data[idx2]; data[idx2] = temp; } } +@compute @workgroup_size(16, 16) fn butterflyRows(@builtin(global_invocation_id) gid: vec3) { let row = gid.y; let idx = gid.x; if (row >= params.height || idx >= params.width / 2u) { return; } let stage = params.stage; let halfSize = 1u << stage; let fullSize = halfSize << 1u; let group = idx / halfSize; let pos = idx % halfSize; let col_i = group * fullSize + pos; let col_j = col_i + halfSize; if (col_j >= params.width) { return; } let w = twiddle(pos, fullSize, params.inverse); let i = getIndex(row, col_i); let j = getIndex(row, col_j); let u = data[i]; let t = cmul(w, data[j]); data[i] = u + t; data[j] = u - t; } +@compute @workgroup_size(16, 16) fn butterflyCols(@builtin(global_invocation_id) gid: vec3) { let col = gid.x; let idx = gid.y; if (col >= params.width || idx >= params.height / 2u) { return; } let stage = params.stage; let halfSize = 1u << stage; let fullSize = halfSize << 1u; let group = idx / halfSize; let pos = idx % halfSize; let row_i = group * fullSize + pos; let row_j = row_i + halfSize; if (row_j >= params.height) { return; } let w = twiddle(pos, fullSize, params.inverse); let i = getIndex(row_i, col); let j = getIndex(row_j, col); let u = data[i]; let t = cmul(w, data[j]); data[i] = u + t; data[j] = u - t; } +@compute @workgroup_size(16, 16) fn normalize2D(@builtin(global_invocation_id) gid: vec3) { let row = gid.y; let col = gid.x; if (row >= params.height || col >= params.width) { return; } let idx = getIndex(row, col); let scale = 1.0 / f32(params.width * params.height); data[idx] = data[idx] * scale; }`; + +export class WebGPUFFT { + private device: GPUDevice; + private pipelines2D: { bitReverseRows: GPUComputePipeline; bitReverseCols: GPUComputePipeline; butterflyRows: GPUComputePipeline; butterflyCols: GPUComputePipeline; normalize: GPUComputePipeline } | null = null; + private initialized = false; + constructor(device: GPUDevice) { this.device = device; } + async init(): Promise { + if (this.initialized) return; + const module2D = this.device.createShaderModule({ code: FFT_2D_SHADER }); + this.pipelines2D = { + bitReverseRows: this.device.createComputePipeline({ layout: 'auto', compute: { module: module2D, entryPoint: 'bitReverseRows' } }), + bitReverseCols: this.device.createComputePipeline({ layout: 'auto', compute: { module: module2D, entryPoint: 'bitReverseCols' } }), + butterflyRows: this.device.createComputePipeline({ layout: 'auto', compute: { module: module2D, entryPoint: 'butterflyRows' } }), + butterflyCols: this.device.createComputePipeline({ layout: 'auto', compute: { module: module2D, entryPoint: 'butterflyCols' } }), + normalize: this.device.createComputePipeline({ layout: 'auto', compute: { module: module2D, entryPoint: 'normalize2D' } }) + }; + this.initialized = true; + } + async fft2D(realData: Float32Array, imagData: Float32Array, width: number, height: number, inverse: boolean = false): Promise<{ real: Float32Array, imag: Float32Array }> { + await this.init(); + const paddedWidth = nextPow2(width), paddedHeight = nextPow2(height); + const needsPadding = paddedWidth !== width || paddedHeight !== height; + const log2Width = Math.log2(paddedWidth), log2Height = Math.log2(paddedHeight); + const paddedSize = paddedWidth * paddedHeight, originalSize = width * height; + let workReal: Float32Array, workImag: Float32Array; + if (needsPadding) { + workReal = new Float32Array(paddedSize); workImag = new Float32Array(paddedSize); + for (let y = 0; y < height; y++) for (let x = 0; x < width; x++) { workReal[y * paddedWidth + x] = realData[y * width + x]; workImag[y * paddedWidth + x] = imagData[y * width + x]; } + } else { workReal = realData; workImag = imagData; } + const complexData = new Float32Array(paddedSize * 2); + for (let i = 0; i < paddedSize; i++) { complexData[i * 2] = workReal[i]; complexData[i * 2 + 1] = workImag[i]; } + const dataBuffer = this.device.createBuffer({ size: complexData.byteLength, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST }); + this.device.queue.writeBuffer(dataBuffer, 0, complexData); + const paramsBuffer = this.device.createBuffer({ size: 24, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST }); + const readBuffer = this.device.createBuffer({ size: complexData.byteLength, usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST }); + const inverseVal = inverse ? 1.0 : -1.0; + const workgroupsX = Math.ceil(paddedWidth / 16), workgroupsY = Math.ceil(paddedHeight / 16); + const runPass = (pipeline: GPUComputePipeline) => { + const bindGroup = this.device.createBindGroup({ layout: pipeline.getBindGroupLayout(0), entries: [{ binding: 0, resource: { buffer: paramsBuffer } }, { binding: 1, resource: { buffer: dataBuffer } }] }); + const encoder = this.device.createCommandEncoder(); const pass = encoder.beginComputePass(); + pass.setPipeline(pipeline); pass.setBindGroup(0, bindGroup); pass.dispatchWorkgroups(workgroupsX, workgroupsY); pass.end(); + this.device.queue.submit([encoder.finish()]); + }; + const params = new ArrayBuffer(24); const paramsU32 = new Uint32Array(params); const paramsF32 = new Float32Array(params); + paramsU32[0] = paddedWidth; paramsU32[1] = paddedHeight; paramsU32[2] = log2Width; paramsU32[3] = 0; paramsF32[4] = inverseVal; paramsU32[5] = 1; + this.device.queue.writeBuffer(paramsBuffer, 0, params); runPass(this.pipelines2D!.bitReverseRows); + for (let stage = 0; stage < log2Width; stage++) { paramsU32[3] = stage; this.device.queue.writeBuffer(paramsBuffer, 0, params); runPass(this.pipelines2D!.butterflyRows); } + paramsU32[2] = log2Height; paramsU32[3] = 0; paramsU32[5] = 0; + this.device.queue.writeBuffer(paramsBuffer, 0, params); runPass(this.pipelines2D!.bitReverseCols); + for (let stage = 0; stage < log2Height; stage++) { paramsU32[3] = stage; this.device.queue.writeBuffer(paramsBuffer, 0, params); runPass(this.pipelines2D!.butterflyCols); } + if (inverse) runPass(this.pipelines2D!.normalize); + const encoder = this.device.createCommandEncoder(); encoder.copyBufferToBuffer(dataBuffer, 0, readBuffer, 0, complexData.byteLength); + this.device.queue.submit([encoder.finish()]); await readBuffer.mapAsync(GPUMapMode.READ); + const result = new Float32Array(readBuffer.getMappedRange().slice(0)); readBuffer.unmap(); + dataBuffer.destroy(); paramsBuffer.destroy(); readBuffer.destroy(); + if (needsPadding) { + const realResult = new Float32Array(originalSize), imagResult = new Float32Array(originalSize); + for (let y = 0; y < height; y++) for (let x = 0; x < width; x++) { realResult[y * width + x] = result[(y * paddedWidth + x) * 2]; imagResult[y * width + x] = result[(y * paddedWidth + x) * 2 + 1]; } + return { real: realResult, imag: imagResult }; + } + const realResult = new Float32Array(paddedSize), imagResult = new Float32Array(paddedSize); + for (let i = 0; i < paddedSize; i++) { realResult[i] = result[i * 2]; imagResult[i] = result[i * 2 + 1]; } + return { real: realResult, imag: imagResult }; + } + /** + * Batched 2D FFT: compute N forward FFTs with pipelined GPU submissions. + * All images must have the same dimensions. Each image gets its own + * submit (required because the params uniform changes per-pass), but + * all readbacks are batched into a single Promise.all at the end. + */ + async fft2DBatch( + images: { real: Float32Array; imag: Float32Array }[], + width: number, height: number, + ): Promise<{ real: Float32Array; imag: Float32Array }[]> { + await this.init(); + const n = images.length; + if (n === 0) return []; + const paddedWidth = nextPow2(width), paddedHeight = nextPow2(height); + const needsPadding = paddedWidth !== width || paddedHeight !== height; + const log2Width = Math.log2(paddedWidth), log2Height = Math.log2(paddedHeight); + const paddedSize = paddedWidth * paddedHeight; + const originalSize = width * height; + const byteSize = paddedSize * 2 * 4; + const workgroupsX = Math.ceil(paddedWidth / 16), workgroupsY = Math.ceil(paddedHeight / 16); + const inverseVal = -1.0; + + // Shared params buffer — safe because we submit per-image + const paramsBuffer = this.device.createBuffer({ size: 24, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST }); + + const readBuffers: GPUBuffer[] = []; + const dataBuffers: GPUBuffer[] = []; + + // Submit all FFTs — GPU pipelines them internally + for (let i = 0; i < n; i++) { + const { real: realData, imag: imagData } = images[i]; + let workReal: Float32Array, workImag: Float32Array; + if (needsPadding) { + workReal = new Float32Array(paddedSize); workImag = new Float32Array(paddedSize); + for (let y = 0; y < height; y++) for (let x = 0; x < width; x++) { + workReal[y * paddedWidth + x] = realData[y * width + x]; + workImag[y * paddedWidth + x] = imagData[y * width + x]; + } + } else { workReal = realData; workImag = imagData; } + + const complexData = new Float32Array(paddedSize * 2); + for (let j = 0; j < paddedSize; j++) { complexData[j * 2] = workReal[j]; complexData[j * 2 + 1] = workImag[j]; } + + const dataBuffer = this.device.createBuffer({ size: byteSize, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST }); + this.device.queue.writeBuffer(dataBuffer, 0, complexData); + dataBuffers.push(dataBuffer); + + const readBuffer = this.device.createBuffer({ size: byteSize, usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST }); + readBuffers.push(readBuffer); + + // Run FFT passes — each runPass does writeBuffer+submit atomically + const runPass = (pipeline: GPUComputePipeline) => { + const bindGroup = this.device.createBindGroup({ + layout: pipeline.getBindGroupLayout(0), + entries: [{ binding: 0, resource: { buffer: paramsBuffer } }, { binding: 1, resource: { buffer: dataBuffer } }], + }); + const enc = this.device.createCommandEncoder(); + const pass = enc.beginComputePass(); + pass.setPipeline(pipeline); pass.setBindGroup(0, bindGroup); + pass.dispatchWorkgroups(workgroupsX, workgroupsY); pass.end(); + this.device.queue.submit([enc.finish()]); + }; + + const params = new ArrayBuffer(24); + const paramsU32 = new Uint32Array(params); + const paramsF32 = new Float32Array(params); + + paramsU32[0] = paddedWidth; paramsU32[1] = paddedHeight; paramsU32[2] = log2Width; + paramsU32[3] = 0; paramsF32[4] = inverseVal; paramsU32[5] = 1; + this.device.queue.writeBuffer(paramsBuffer, 0, params); runPass(this.pipelines2D!.bitReverseRows); + for (let stage = 0; stage < log2Width; stage++) { paramsU32[3] = stage; this.device.queue.writeBuffer(paramsBuffer, 0, params); runPass(this.pipelines2D!.butterflyRows); } + + paramsU32[2] = log2Height; paramsU32[3] = 0; paramsU32[5] = 0; + this.device.queue.writeBuffer(paramsBuffer, 0, params); runPass(this.pipelines2D!.bitReverseCols); + for (let stage = 0; stage < log2Height; stage++) { paramsU32[3] = stage; this.device.queue.writeBuffer(paramsBuffer, 0, params); runPass(this.pipelines2D!.butterflyCols); } + + // Copy to read buffer + const copyEnc = this.device.createCommandEncoder(); + copyEnc.copyBufferToBuffer(dataBuffer, 0, readBuffer, 0, byteSize); + this.device.queue.submit([copyEnc.finish()]); + } + + // Batched readback — one sync point for all images + await Promise.all(readBuffers.map(buf => buf.mapAsync(GPUMapMode.READ))); + + const results: { real: Float32Array; imag: Float32Array }[] = []; + for (let i = 0; i < n; i++) { + const result = new Float32Array(readBuffers[i].getMappedRange().slice(0)); + readBuffers[i].unmap(); + dataBuffers[i].destroy(); + readBuffers[i].destroy(); + + if (needsPadding) { + const realResult = new Float32Array(originalSize), imagResult = new Float32Array(originalSize); + for (let y = 0; y < height; y++) for (let x = 0; x < width; x++) { + realResult[y * width + x] = result[(y * paddedWidth + x) * 2]; + imagResult[y * width + x] = result[(y * paddedWidth + x) * 2 + 1]; + } + results.push({ real: realResult, imag: imagResult }); + } else { + const realResult = new Float32Array(paddedSize), imagResult = new Float32Array(paddedSize); + for (let i2 = 0; i2 < paddedSize; i2++) { realResult[i2] = result[i2 * 2]; imagResult[i2] = result[i2 * 2 + 1]; } + results.push({ real: realResult, imag: imagResult }); + } + } + + paramsBuffer.destroy(); + return results; + } + + destroy(): void { this.initialized = false; } +} + +// ============================================================================ +// FFT pre-processing helpers +// ============================================================================ + +/** + * Apply 2D Hann window in-place to reduce spectral leakage in ROI FFT. + * + * When an ROI is cropped from an image, the sharp rectangular boundary acts as + * a rect window whose sinc sidelobes produce streak artifacts in the FFT, + * obscuring real spectral features (Bragg spots, lattice frequencies). + * The Hann window smoothly tapers data to zero at all edges, suppressing + * sidelobes by ~31 dB at the cost of a slightly wider main lobe. + * + * Separable: window2D = outer(hann_h, hann_w), applied as element-wise multiply. + * Symmetric formula: w(i) = 0.5*(1 - cos(2πi/(N-1))), matching np.hanning — + * both endpoints are exactly zero for seamless transition to zero-padded regions. + * (Periodic variant ÷N is for overlapping STFT windows, not for zero-padding.) + * + * IMPORTANT: Must be called on the crop at its native dimensions BEFORE + * zero-padding to power-of-2. Window-then-pad ensures no discontinuity at the + * crop/pad boundary. Pad-then-window applies the wrong taper and reintroduces + * leakage. Validated against np.hanning in test_widget_show2d.py. + */ +export function applyHannWindow2D(data: Float32Array, width: number, height: number): void { + const hannW = new Float32Array(width); + const hannH = new Float32Array(height); + const wDenom = width > 1 ? width - 1 : 1; + const hDenom = height > 1 ? height - 1 : 1; + for (let i = 0; i < width; i++) hannW[i] = 0.5 * (1 - Math.cos((2 * Math.PI * i) / wDenom)); + for (let i = 0; i < height; i++) hannH[i] = 0.5 * (1 - Math.cos((2 * Math.PI * i) / hDenom)); + for (let r = 0; r < height; r++) { + const hr = hannH[r]; + const offset = r * width; + for (let c = 0; c < width; c++) data[offset + c] *= hr * hannW[c]; + } +} + +// ============================================================================ +// FFT post-processing helpers +// ============================================================================ + +/** Compute magnitude from complex FFT output: sqrt(real² + imag²). */ +export function computeMagnitude(real: Float32Array, imag: Float32Array): Float32Array { + const mag = new Float32Array(real.length); + for (let i = 0; i < mag.length; i++) { + mag[i] = Math.sqrt(real[i] * real[i] + imag[i] * imag[i]); + } + return mag; +} + +/** Mask DC component (center pixel) and return 99.9% percentile-clipped range. Mutates `mag`. */ +export function autoEnhanceFFT( + mag: Float32Array, width: number, height: number, +): { min: number; max: number } { + const centerIdx = Math.floor(height / 2) * width + Math.floor(width / 2); + const neighbors = [ + mag[Math.max(0, centerIdx - 1)], + mag[Math.min(mag.length - 1, centerIdx + 1)], + mag[Math.max(0, centerIdx - width)], + mag[Math.min(mag.length - 1, centerIdx + width)], + ]; + mag[centerIdx] = neighbors.reduce((a, b) => a + b, 0) / 4; + // Use O(n) histogram approach instead of O(n log n) sort + const len = mag.length; + if (len === 0) return { min: 0, max: 0 }; + let dMin = Infinity, dMax = -Infinity; + for (let i = 0; i < len; i++) { + const v = mag[i]; + if (v < dMin) dMin = v; + if (v > dMax) dMax = v; + } + if (dMin === dMax) return { min: dMin, max: dMax }; + const NUM_BINS = 1024; + const bins = new Uint32Array(NUM_BINS); + const range = dMax - dMin; + const scale = (NUM_BINS - 1) / range; + for (let i = 0; i < len; i++) bins[Math.floor((mag[i] - dMin) * scale)]++; + // Find 99.9th percentile + const target = Math.ceil(len * 0.999); + let cumSum = 0; + let pMax = dMax; + for (let i = 0; i < NUM_BINS; i++) { + cumSum += bins[i]; + if (cumSum >= target) { pMax = dMin + (i / (NUM_BINS - 1)) * range; break; } + } + // If percentile collapsed to min (sparse spectra), fall back to actual max + if (pMax <= dMin) pMax = dMax; + return { min: dMin, max: pMax }; +} + +// ============================================================================ +// Singleton +// ============================================================================ + +let gpuFFT: WebGPUFFT | null = null; +let gpuDevice: GPUDevice | null = null; +let gpuInfo = "GPU"; + +export async function getGPUDevice(): Promise { + if (gpuDevice) return gpuDevice; + if (!navigator.gpu) return null; + try { + const adapter = await navigator.gpu.requestAdapter(); + if (!adapter) return null; + try { + // @ts-ignore - requestAdapterInfo is not yet in all type definitions + const info = await adapter.requestAdapterInfo?.(); + if (info) { + gpuInfo = info.description || `${info.vendor} ${info.architecture || ""} ${info.device || ""}`.trim() || "Generic WebGPU Adapter"; + } + } catch (_e) { /* adapter info not available */ } + gpuDevice = await adapter.requestDevice(); + return gpuDevice; + } catch { return null; } +} + +export async function getWebGPUFFT(): Promise { + if (gpuFFT) return gpuFFT; + const device = await getGPUDevice(); + if (!device) { console.warn('WebGPU not supported, falling back to CPU FFT'); return null; } + try { + gpuFFT = new WebGPUFFT(device); + await gpuFFT.init(); + return gpuFFT; + } catch (e) { console.warn('WebGPU init failed:', e); return null; } +} + +export function getGPUInfo(): string { return gpuInfo; } diff --git a/widget/package-lock.json b/widget/package-lock.json index 4e039394..cf4e386a 100644 --- a/widget/package-lock.json +++ b/widget/package-lock.json @@ -6,22 +6,32 @@ "": { "name": "quantem-widget-frontend", "dependencies": { - "@anywidget/react": "^0.1.0", - "react": "^18.2.0", - "react-dom": "^18.2.0" + "@anywidget/react": "^0.2.0", + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@mui/icons-material": "^7.3.7", + "@mui/material": "^7.3.6", + "jszip": "^3.10.1", + "react": "^19.1.0", + "react-dom": "^19.1.0" }, "devDependencies": { "@anywidget/vite": "^0.2.0", + "@types/react": "^19.1.3", + "@types/react-dom": "^19.1.4", "@vitejs/plugin-react": "^4.3.0", + "@webgpu/types": "^0.1.68", + "typescript": "^5.8.3", "vite": "^5.2.0" } }, "node_modules/@anywidget/react": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@anywidget/react/-/react-0.1.0.tgz", - "integrity": "sha512-Hh6wbMGXsgTz2xz1I9/h40M3b6uDWLjmh3sJ4tNjfppDl5y9Sw1jyuQGhNzwViOtszmyjYEJZ4kWHdPFUXUanw==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@anywidget/react/-/react-0.2.2.tgz", + "integrity": "sha512-MjrbbUimaX72ATL9IrrZeMZL+IHVzeW5lMlgZywDAMj4/zUVQ7MeBSaijq7V9genze7sM3Y0WnUyB+Mc7KnWWw==", + "license": "MIT", "dependencies": { - "@anywidget/types": "^0.2.0" + "@anywidget/types": "^0.4.0" }, "peerDependencies": { "@types/react": "^18.0.0 || ^19.0.0", @@ -31,9 +41,10 @@ } }, "node_modules/@anywidget/types": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@anywidget/types/-/types-0.2.0.tgz", - "integrity": "sha512-+XtK4uwxRd4JpuevUMhirrbvC0V4yCA/i0lEjhmSAtOaxiXIg/vBKzaSonDuoZ1a9LEjUXTW2+m7w+ULgsJYvg==" + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@anywidget/types/-/types-0.4.0.tgz", + "integrity": "sha512-Qno/7V0lKHCMq3DJuSKKHMwilFKPSe8wFftL5xWmgaMCc938mNTtv+i19UrvDfpj9cQTnlPqyXy8t3JOgQ8laA==", + "license": "MIT" }, "node_modules/@anywidget/vite": { "version": "0.2.2", @@ -49,7 +60,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", @@ -76,7 +86,6 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -106,7 +115,6 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.28.5", @@ -140,7 +148,6 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -150,7 +157,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.27.1", @@ -192,7 +198,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -202,7 +207,6 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -236,7 +240,6 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.28.5" @@ -280,11 +283,19 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -299,7 +310,6 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -318,7 +328,6 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -328,6 +337,158 @@ "node": ">=6.9.0" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.14.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", + "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -723,7 +884,6 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -745,7 +905,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -755,20 +914,261 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mui/core-downloads-tracker": { + "version": "7.3.10", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.10.tgz", + "integrity": "sha512-vrOpWRmPJSuwLo23J62wggEm/jvGdzqctej+UOCtgDUz6nZJQuj3ByPccVyaa7eQmwAzUwKN56FQPMKkqbj1GA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "7.3.10", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.10.tgz", + "integrity": "sha512-Au0ma4NSKGKNiimukj8UT/W1x2Qx6Qwn2RvFGykiSqVLYBNlIOPbjnIMvrwLGLu89EEpTVdu/ys/OduZR+tWqw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^7.3.10", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "7.3.10", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.10.tgz", + "integrity": "sha512-cHvGOk2ZEfbQt3LnGe0ZKd/ETs9gsUpkW66DCO+GSjMZhpdKU4XsuIr7zJ/B/2XaN8ihxuzHfYAR4zPtCN4RYg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "@mui/core-downloads-tracker": "^7.3.10", + "@mui/system": "^7.3.10", + "@mui/types": "^7.4.12", + "@mui/utils": "^7.3.10", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.2.3", + "prop-types": "^15.8.1", + "react-is": "^19.2.3", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^7.3.10", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "7.3.10", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.10.tgz", + "integrity": "sha512-j3EZN+zOctxUISvJSmsEPo5o2F8zse4l5vRkBY+ps6UtnL6J7o14kUaI4w7gwo73id9e3cDNMVQK/9BVaMHVBw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "@mui/utils": "^7.3.10", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "7.3.10", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.10.tgz", + "integrity": "sha512-WxE9SiF8xskAQqGjsp0poXCkCqsoXFEsSr0HBXfApmGHR+DBnXRp+z46Vsltg4gpPM4Z96DeAQRpeAOnhNg7Ng==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.2.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "7.3.10", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.10.tgz", + "integrity": "sha512-/sfPpdpJaQn7BSF+avjIdHSYmxHp0UOBYNxSG9QGKfMOD6sLANCpRPCnanq1Pe0lFf0NHkO2iUk0TNzdWC1USQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "@mui/private-theming": "^7.3.10", + "@mui/styled-engine": "^7.3.10", + "@mui/types": "^7.4.12", + "@mui/utils": "^7.3.10", + "clsx": "^2.1.1", + "csstype": "^3.2.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.4.12", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.12.tgz", + "integrity": "sha512-iKNAF2u9PzSIj40CjvKJWxFXJo122jXVdrmdh0hMYd+FR+NuJMkr/L88XwWLCRiJ5P1j+uyac25+Kp6YC4hu6w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "7.3.10", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.10.tgz", + "integrity": "sha512-7y2eIfy0h7JPz+Yy4pS+wgV68d46PuuxDqKBN4Q8VlPQSsCAGwroMCV6xWyc7g9dvEp8ZNFsknc59GHWO+r6Ow==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "@mui/types": "^7.4.12", + "@types/prop-types": "^15.7.15", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.2.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", @@ -1178,12 +1578,23 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "license": "MIT" + }, "node_modules/@types/react": { "version": "19.2.8", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.8.tgz", "integrity": "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1193,11 +1604,19 @@ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, "node_modules/@vitejs/plugin-react": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", @@ -1219,6 +1638,28 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/@webgpu/types": { + "version": "0.1.69", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.69.tgz", + "integrity": "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, "node_modules/baseline-browser-mapping": { "version": "2.9.14", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz", @@ -1249,7 +1690,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -1264,6 +1704,15 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001764", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz", @@ -1285,6 +1734,15 @@ ], "license": "CC-BY-4.0" }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -1292,6 +1750,28 @@ "dev": true, "license": "MIT" }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -1302,7 +1782,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1316,6 +1795,16 @@ } } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.267", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", @@ -1323,6 +1812,24 @@ "dev": true, "license": "ISC" }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -1372,6 +1879,24 @@ "node": ">=6" } }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1387,6 +1912,15 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -1397,6 +1931,88 @@ "node": ">=6.9.0" } }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -1407,7 +2023,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -1416,6 +2031,12 @@ "node": ">=6" } }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -1429,6 +2050,33 @@ "node": ">=6" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -1455,7 +2103,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -1484,11 +2131,70 @@ "dev": true, "license": "MIT" }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/postcss": { @@ -1520,33 +2226,56 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "license": "MIT", - "peer": true, "dependencies": { - "loose-envify": "^1.1.0" - }, + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", + "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", "license": "MIT", - "peer": true, "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" + "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^18.3.1" + "react": "^19.2.5" } }, + "node_modules/react-is": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.5.tgz", + "integrity": "sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ==", + "license": "MIT" + }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", @@ -1557,6 +2286,67 @@ "node": ">=0.10.0" } }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/rollup": { "version": "4.55.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", @@ -1602,14 +2392,17 @@ "fsevents": "~2.3.2" } }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" }, "node_modules/semver": { "version": "6.3.1", @@ -1621,6 +2414,21 @@ "semver": "bin/semver.js" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -1631,6 +2439,47 @@ "node": ">=0.10.0" } }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -1662,13 +2511,18 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, "node_modules/vite": { "version": "5.4.21", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -1729,6 +2583,15 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true, "license": "ISC" + }, + "node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "license": "ISC", + "engines": { + "node": ">= 6" + } } } } diff --git a/widget/package.json b/widget/package.json index 4abc343d..3d0175ae 100644 --- a/widget/package.json +++ b/widget/package.json @@ -3,16 +3,26 @@ "type": "module", "scripts": { "dev": "vite build --watch", - "build": "vite build" + "build": "vite build", + "typecheck": "tsc --noEmit" }, "dependencies": { - "react": "^18.2.0", - "react-dom": "^18.2.0", - "@anywidget/react": "^0.1.0" + "@anywidget/react": "^0.2.0", + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@mui/icons-material": "^7.3.7", + "@mui/material": "^7.3.6", + "jszip": "^3.10.1", + "react": "^19.1.0", + "react-dom": "^19.1.0" }, "devDependencies": { - "vite": "^5.2.0", "@anywidget/vite": "^0.2.0", - "@vitejs/plugin-react": "^4.3.0" + "@types/react": "^19.1.3", + "@types/react-dom": "^19.1.4", + "@vitejs/plugin-react": "^4.3.0", + "@webgpu/types": "^0.1.68", + "typescript": "^5.8.3", + "vite": "^5.2.0" } } diff --git a/widget/pyproject.toml b/widget/pyproject.toml index c0fb7a63..738c7bc9 100644 --- a/widget/pyproject.toml +++ b/widget/pyproject.toml @@ -10,6 +10,11 @@ license = "MIT" requires-python = ">=3.11" dependencies = [ "anywidget>=0.9.0", + "numpy>=2.0.0", + "traitlets>=5.0.0", + "torch>=2.0.0", + "matplotlib>=3.7.0", + "Pillow>=10.0.0", ] [tool.hatch.build.targets.wheel] diff --git a/widget/src/quantem/widget/__init__.py b/widget/src/quantem/widget/__init__.py index d4ca85a7..dc98e36a 100644 --- a/widget/src/quantem/widget/__init__.py +++ b/widget/src/quantem/widget/__init__.py @@ -1,24 +1,7 @@ from importlib.metadata import version -import pathlib -import anywidget -import traitlets -__version__ = version("quantem.widget") - -_static = pathlib.Path(__file__).parent / "static" - - -class CounterWidget(anywidget.AnyWidget): - _esm = _static / "index.js" - - count = traitlets.Int(0).tag(sync=True) +from quantem.widget.show2d import Show2D +from quantem.widget.show4dstem import Show4DSTEM - -def show4dstem(): - # TODO: Implement 4D-STEM visualization widget - print("show4dstem: not yet implemented") - - -def counter(): - """Create a minimal counter widget for testing.""" - return CounterWidget() +__version__ = version("quantem.widget") +__all__ = ["Show2D", "Show4DSTEM"] diff --git a/widget/src/quantem/widget/array_utils.py b/widget/src/quantem/widget/array_utils.py new file mode 100644 index 00000000..e86633e6 --- /dev/null +++ b/widget/src/quantem/widget/array_utils.py @@ -0,0 +1,282 @@ +""" +Array utilities for handling NumPy, CuPy, and PyTorch arrays uniformly. + +This module provides utilities to convert arrays from different backends +into NumPy arrays for widget processing. +""" + +from typing import Any, Literal +import numpy as np + +try: + import torch + import torch.nn.functional as F + _HAS_TORCH = True +except ImportError: + _HAS_TORCH = False + + +ArrayBackend = Literal["numpy", "cupy", "torch", "unknown"] + + +def get_array_backend(data: Any) -> ArrayBackend: + """ + Detect the array backend of the input data. + + Parameters + ---------- + data : array-like + Input array (NumPy, CuPy, PyTorch, or other). + + Returns + ------- + str + One of: "numpy", "cupy", "torch", "unknown" + """ + # Check PyTorch first (has both .numpy and .detach methods) + if hasattr(data, "detach") and hasattr(data, "numpy"): + return "torch" + # Check CuPy (has .get() or __cuda_array_interface__) + if hasattr(data, "__cuda_array_interface__"): + return "cupy" + if hasattr(data, "get") and hasattr(data, "__array__"): + # CuPy arrays have .get() to transfer to CPU + type_name = type(data).__module__ + if "cupy" in type_name: + return "cupy" + # Check NumPy + if isinstance(data, np.ndarray): + return "numpy" + return "unknown" + + +def to_numpy(data: Any, dtype: np.dtype | None = None) -> np.ndarray: + """ + Convert any array-like (NumPy, CuPy, PyTorch) to a NumPy array. + + Parameters + ---------- + data : array-like + Input array from any supported backend. + dtype : np.dtype, optional + Target dtype for the output array. If None, preserves original dtype. + + Returns + ------- + np.ndarray + NumPy array with the same data. + + Examples + -------- + >>> import numpy as np + >>> from quantem.widget.array_utils import to_numpy + >>> + >>> # NumPy passthrough + >>> arr = np.random.rand(10, 10) + >>> result = to_numpy(arr) + >>> + >>> # CuPy conversion (if available) + >>> import cupy as cp + >>> gpu_arr = cp.random.rand(10, 10) + >>> cpu_arr = to_numpy(gpu_arr) + >>> + >>> # PyTorch conversion (if available) + >>> import torch + >>> tensor = torch.rand(10, 10) + >>> arr = to_numpy(tensor) + """ + backend = get_array_backend(data) + + if backend == "torch": + # PyTorch tensor: detach from graph, move to CPU, convert to numpy + result = data.detach().cpu().numpy() + + elif backend == "cupy": + # CuPy array: use .get() to transfer to CPU + if hasattr(data, "get"): + result = data.get() + else: + # Fallback for __cuda_array_interface__ + import cupy as cp + + result = cp.asnumpy(data) + + elif backend == "numpy": + # NumPy array: passthrough (may copy if dtype changes) + result = data + + else: + # Unknown backend: try np.asarray as fallback + result = np.asarray(data) + + # Apply dtype conversion if specified + if dtype is not None: + result = np.asarray(result, dtype=dtype) + + return result + + +def bin2d(data, factor: int = 2, mode: str = "mean", edge_mode: str = "crop") -> np.ndarray: + """ + Spatial binning for 2D or 3D arrays. + + Uses torch GPU (MPS/CUDA) when available for large arrays (~5× faster on 4K data). + + Parameters + ---------- + data : array-like + Input array with shape ``(H, W)`` or ``(N, H, W)``. + factor : int, default 2 + Bin factor. + mode : str, default "mean" + Reduction mode: ``"mean"`` or ``"sum"``. + edge_mode : str, default "crop" + How to handle dimensions not divisible by *factor*: + ``"crop"`` trims extra pixels, ``"pad"`` zero-pads to the next + multiple (output shape uses ``ceil(dim / factor)``). + + Returns + ------- + np.ndarray + Binned array, dtype float32. + """ + arr = to_numpy(data) + if arr.dtype != np.float32: + arr = arr.astype(np.float32) + + # Torch GPU fast path: only for arrays between 1M and 500M elements. + # Larger arrays hit MPS memory transfer bottleneck (>2 GB transfer > CPU compute). + import torch + if 1_000_000 < arr.size < 500_000_000 and (torch.backends.mps.is_available() or torch.cuda.is_available()): + dev = torch.device("mps" if torch.backends.mps.is_available() else "cuda") + t = torch.from_numpy(arr).to(dev) + if t.ndim == 2: + h, w = t.shape + oh = h // factor * factor + ow = w // factor * factor + t = t[:oh, :ow].reshape(oh // factor, factor, ow // factor, factor) + t = t.sum(dim=(1, 3)) if mode == "sum" else t.mean(dim=(1, 3)) + elif t.ndim == 3: + n, h, w = t.shape + oh = h // factor * factor + ow = w // factor * factor + t = t[:, :oh, :ow].reshape(n, oh // factor, factor, ow // factor, factor) + t = t.sum(dim=(2, 4)) if mode == "sum" else t.mean(dim=(2, 4)) + return t.cpu().numpy().astype(np.float32) + + # CPU fallback (no GPU available or small array) + reduce = np.ndarray.sum if mode == "sum" else np.ndarray.mean + if arr.ndim == 2: + arr = _pad_or_crop_2d(arr, factor, edge_mode) + h, w = arr.shape + oh, ow = h // factor, w // factor + return reduce(arr.reshape(oh, factor, ow, factor), axis=(1, 3)).astype(np.float32) + # 3D: (N, H, W) + arr = _pad_or_crop_3d(arr, factor, edge_mode) + n, h, w = arr.shape + oh, ow = h // factor, w // factor + return reduce(arr.reshape(n, oh, factor, ow, factor), axis=(2, 4)).astype(np.float32) + + +def _pad_or_crop_2d(arr: np.ndarray, factor: int, edge_mode: str) -> np.ndarray: + h, w = arr.shape + if edge_mode == "pad": + pad_h = (factor - h % factor) % factor + pad_w = (factor - w % factor) % factor + if pad_h or pad_w: + arr = np.pad(arr, ((0, pad_h), (0, pad_w)), mode="constant") + else: + oh, ow = h // factor, w // factor + arr = arr[:oh * factor, :ow * factor] + return arr + + +def _pad_or_crop_3d(arr: np.ndarray, factor: int, edge_mode: str) -> np.ndarray: + _, h, w = arr.shape + if edge_mode == "pad": + pad_h = (factor - h % factor) % factor + pad_w = (factor - w % factor) % factor + if pad_h or pad_w: + arr = np.pad(arr, ((0, 0), (0, pad_h), (0, pad_w)), mode="constant") + else: + oh, ow = h // factor, w // factor + arr = arr[:, :oh * factor, :ow * factor] + return arr + + +def apply_shift(img: np.ndarray, dy: float, dx: float) -> np.ndarray: + """ + Apply sub-pixel shift using bilinear interpolation. + + Uses ``torch.nn.functional.grid_sample`` on GPU when torch is available, + falls back to numpy bilinear interpolation otherwise. + + Parameters + ---------- + img : np.ndarray + 2D image, float32. + dy : float + Shift in y (rows). + dx : float + Shift in x (columns). + + Returns + ------- + np.ndarray + Shifted image, same shape, float32. Out-of-bounds pixels are zero. + """ + if _HAS_TORCH: + h, w = img.shape + device = torch.device("mps" if torch.backends.mps.is_available() else "cuda" if torch.cuda.is_available() else "cpu") + t = torch.as_tensor(img, dtype=torch.float32, device=device).unsqueeze(0).unsqueeze(0) + base_y = torch.linspace(-1, 1, h, device=device) + base_x = torch.linspace(-1, 1, w, device=device) + gy, gx = torch.meshgrid(base_y, base_x, indexing="ij") + grid = torch.stack([gx - dx * 2.0 / w, gy - dy * 2.0 / h], dim=-1).unsqueeze(0) + result = F.grid_sample(t, grid, mode="bilinear", padding_mode="zeros", align_corners=True) + return result.squeeze().cpu().numpy() + h, w = img.shape + y_src = np.arange(h, dtype=np.float64) - dy + x_src = np.arange(w, dtype=np.float64) - dx + yy, xx = np.meshgrid(y_src, x_src, indexing="ij") + y0 = np.floor(yy).astype(int) + x0 = np.floor(xx).astype(int) + fy = (yy - y0).astype(np.float32) + fx = (xx - x0).astype(np.float32) + valid = (y0 >= 0) & (y0 + 1 < h) & (x0 >= 0) & (x0 + 1 < w) + y0c = np.clip(y0, 0, h - 2) + x0c = np.clip(x0, 0, w - 2) + result = (img[y0c, x0c] * (1 - fy) * (1 - fx) + + img[y0c, x0c + 1] * (1 - fy) * fx + + img[y0c + 1, x0c] * fy * (1 - fx) + + img[y0c + 1, x0c + 1] * fy * fx) + result[~valid] = 0.0 + return result.astype(np.float32) + + +def _resize_image(img: np.ndarray, target_h: int, target_w: int) -> np.ndarray: + """Resize image using bilinear interpolation (pure numpy, no scipy).""" + h, w = img.shape + + if h == target_h and w == target_w: + return img + + y_new = np.linspace(0, h - 1, target_h) + x_new = np.linspace(0, w - 1, target_w) + x_grid, y_grid = np.meshgrid(x_new, y_new) + + y0 = np.floor(y_grid).astype(int) + x0 = np.floor(x_grid).astype(int) + y1 = np.minimum(y0 + 1, h - 1) + x1 = np.minimum(x0 + 1, w - 1) + + fy = y_grid - y0 + fx = x_grid - x0 + + result = ( + img[y0, x0] * (1 - fy) * (1 - fx) + + img[y0, x1] * (1 - fy) * fx + + img[y1, x0] * fy * (1 - fx) + + img[y1, x1] * fy * fx + ) + return result.astype(img.dtype) diff --git a/widget/src/quantem/widget/json_state.py b/widget/src/quantem/widget/json_state.py new file mode 100644 index 00000000..4874981f --- /dev/null +++ b/widget/src/quantem/widget/json_state.py @@ -0,0 +1,47 @@ +import importlib.metadata +import json +import pathlib +from typing import Any + + +JSON_METADATA_VERSION = "1.0" + + +def resolve_widget_version() -> str: + try: + return importlib.metadata.version("quantem-widget") + except importlib.metadata.PackageNotFoundError: + return "unknown" + except Exception: + return "unknown" + + +def build_json_header(widget_name: str) -> dict[str, Any]: + return { + "metadata_version": JSON_METADATA_VERSION, + "widget_name": widget_name, + "widget_version": resolve_widget_version(), + } + + +def wrap_state_dict(widget_name: str, state: dict[str, Any]) -> dict[str, Any]: + envelope = build_json_header(widget_name) + envelope["state"] = state + return envelope + + +def unwrap_state_payload(payload: dict[str, Any], *, require_envelope: bool = False) -> dict[str, Any]: + if not isinstance(payload, dict): + raise ValueError("State payload must be a dict.") + if "state" in payload: + state = payload["state"] + if not isinstance(state, dict): + raise ValueError("State envelope field 'state' must be a dict.") + return state + if require_envelope: + raise ValueError("State JSON file must be a versioned envelope with top-level 'state'.") + return payload + + +def save_state_file(path: str | pathlib.Path, widget_name: str, state: dict[str, Any]) -> None: + pathlib.Path(path).write_text(json.dumps(wrap_state_dict(widget_name, state), indent=2)) diff --git a/widget/src/quantem/widget/show2d.py b/widget/src/quantem/widget/show2d.py new file mode 100644 index 00000000..08031ac7 --- /dev/null +++ b/widget/src/quantem/widget/show2d.py @@ -0,0 +1,1309 @@ +""" +show2d: Static 2D image viewer with optional FFT and histogram analysis. + +For displaying a single image or a static gallery of multiple images. +Unlike Show3D (interactive), Show2D focuses on static visualization. +""" + +import json +import os +import pathlib +import io +import base64 +import math +import warnings +from enum import StrEnum +from typing import Optional, Union, List, Self + +import anywidget +import matplotlib +import matplotlib.patheffects +import matplotlib.pyplot as plt +import numpy as np +import traitlets + +from quantem.widget.array_utils import to_numpy, _resize_image +from quantem.widget.json_state import resolve_widget_version, save_state_file, unwrap_state_payload +from quantem.widget.tool_parity import ( + bind_tool_runtime_api, + build_tool_groups, + normalize_tool_groups, +) + + + +def _reject_unknown_kwargs(cls, kwargs: dict) -> None: + """Raise TypeError if kwargs contains any key that isn't a declared trait. + + anywidget/traitlets silently accept unknown keys, which let stale notebooks + pass obsolete params like ``pixel_size_angstrom=0.5`` with no warning. This + helper catches typos and renamed-trait references at construction time. + """ + traits = set(cls.class_trait_names()) + unknown = [k for k in kwargs if k not in traits] + if unknown: + key = sorted(unknown)[0] + raise TypeError( + f"{cls.__name__}() got unexpected keyword argument {key!r}. " + f"Check for typos or a renamed parameter (e.g. canvas_size → size, " + f"image_width_px → size, pixel_size_angstrom → pixel_size)." + ) + + +def _round_to_nice(value: float) -> float: + """Round a physical length to a 'nice' value (1, 2, 5, 10, 20, 50, ...).""" + if value <= 0: + return 1.0 + exp = math.floor(math.log10(value)) + base = 10 ** exp + mantissa = value / base + if mantissa < 1.5: + return base + elif mantissa < 3.5: + return 2 * base + elif mantissa < 7.5: + return 5 * base + else: + return 10 * base + + +class Colormap(StrEnum): + INFERNO = "inferno" + VIRIDIS = "viridis" + MAGMA = "magma" + PLASMA = "plasma" + GRAY = "gray" + + +class Show2D(anywidget.AnyWidget): + """ + Static 2D image viewer with optional FFT and histogram analysis. + + Display a single image or multiple images in a gallery layout. + For interactive stack viewing with playback, use Show3D instead. + + Parameters + ---------- + data : array_like + 2D array (height, width) for single image, or + 3D array (N, height, width) for multiple images displayed as gallery. + labels : list of str, optional + Labels for each image in gallery mode. + title : str, optional + Title to display above the image(s). + cmap : str, default "inferno" + Colormap name ("magma", "viridis", "gray", "inferno", "plasma"). + pixel_size : float, optional + Pixel size in angstroms for scale bar display. + show_fft : bool, default False + Show FFT and histogram panels. + show_stats : bool, default True + Show statistics (mean, min, max, std). + log_scale : bool, default False + Use log scale for intensity mapping. + auto_contrast : bool, default False + Use percentile-based contrast. + vmin : float, optional + Absolute minimum intensity for color mapping. When both vmin and vmax + are set, all gallery images share the same intensity scale — essential + for A/B visual comparison. + vmax : float, optional + Absolute maximum intensity for color mapping. + ncols : int, default 3 + Number of columns in gallery mode. + size : int, default 0 + Canvas rendering size in CSS pixels (the on-screen width of each image). + ``0`` uses the frontend default: 500 px for a single image, 300 px per + image in gallery mode. Pass e.g. ``size=800`` to enlarge for a + presentation, or ``size=200`` to compress alongside a control panel. + This controls **display only** — the underlying image resolution is + never resampled; zooming into a 4K image preserves every pixel. + disabled_tools : list of str, optional + Tool groups to lock while still showing controls. Supported: + ``"display"``, ``"histogram"``, ``"stats"``, ``"navigation"``, + ``"view"``, ``"export"``, ``"roi"``, ``"profile"``, ``"all"``. + disable_* : bool, optional + Convenience flags (``disable_display``, ``disable_histogram``, + ``disable_stats``, ``disable_navigation``, ``disable_view``, + ``disable_export``, ``disable_roi``, ``disable_profile``, + ``disable_all``) equivalent to adding those keys to + ``disabled_tools``. + hidden_tools : list of str, optional + Tool groups to hide from the UI. Uses the same keys as + ``disabled_tools``. + hide_* : bool, optional + Convenience flags mirroring ``disable_*`` for ``hidden_tools``. + + Attributes + ---------- + render_total_ms : int or None + End-to-end wall clock from constructor start to first browser paint, + populated by a JS→Python round-trip after the first canvas render. + ``None`` until the browser has actually painted; also printed to stdout + when it fires. Use to triage "is it Python, wire, or the browser?" + during live acquisitions. + render_python_build_ms : int or None + Subset of ``render_total_ms`` covering Python ``__init__`` only. + render_wire_js_ms : int or None + Subset covering everything after Python returns: Comm transfer, JS + decode, colormap, and canvas paint. + + Examples + -------- + >>> import numpy as np + >>> from quantem.widget import Show2D + >>> + >>> # Single image with FFT + >>> Show2D(image, title="HRTEM Image", show_fft=True, pixel_size=1.0) + >>> + >>> # Gallery of multiple images + >>> labels = ["Raw", "Filtered", "FFT"] + >>> Show2D([img1, img2, img3], labels=labels, ncols=3) + """ + + _esm = pathlib.Path(__file__).parent / "static" / "show2d.js" + _css = pathlib.Path(__file__).parent / "static" / "show2d.css" + + # ========================================================================= + # Core State + # GPU memory budget for display buffers (MB). Each 4K image needs ~192 MB. + # 12×4K = 2304 MB fits. 16+ triggers auto-bin. + _GPU_DISPLAY_BUDGET_MB = 2500 + + # ========================================================================= + widget_version = traitlets.Unicode("unknown").tag(sync=True) + n_images = traitlets.Int(1).tag(sync=True) + height = traitlets.Int(1).tag(sync=True) + width = traitlets.Int(1).tag(sync=True) + _display_bin_factor = traitlets.Int(1).tag(sync=True) # 1 = full-res, 2/4/8 = binned + _gpu_max_buffer_mb = traitlets.Int(0).tag(sync=True) # GPU reports maxBufferSize (JS→Python) + # Flipped True by JS after the first colormap pass has painted to canvas. + # Used by the Python-side truthful timing print (end-to-end wall clock, not just __init__). + _js_rendered = traitlets.Bool(False).tag(sync=True) + frame_bytes = traitlets.Bytes(b"").tag(sync=True) + labels = traitlets.List(traitlets.Unicode()).tag(sync=True) + title = traitlets.Unicode("").tag(sync=True) + cmap = traitlets.Unicode("inferno").tag(sync=True) + ncols = traitlets.Int(3).tag(sync=True) + + # ========================================================================= + # Display Options + # ========================================================================= + log_scale = traitlets.Bool(False).tag(sync=True) + auto_contrast = traitlets.Bool(False).tag(sync=True) + vmin = traitlets.Float(None, allow_none=True).tag(sync=True) + vmax = traitlets.Float(None, allow_none=True).tag(sync=True) + vmins = traitlets.List(trait=traitlets.Float(allow_none=True), allow_none=True, default_value=None).tag(sync=True) + vmaxs = traitlets.List(trait=traitlets.Float(allow_none=True), allow_none=True, default_value=None).tag(sync=True) + + # ========================================================================= + # Scale Bar + # ========================================================================= + pixel_size = traitlets.Float(0.0).tag(sync=True) + scale_bar_visible = traitlets.Bool(True).tag(sync=True) + size = traitlets.Int(0).tag(sync=True) # Canvas rendering size in CSS pixels; 0 = frontend default + smooth = traitlets.Bool(False).tag(sync=True) + initial_zoom = traitlets.Float(1.0).tag(sync=True) + zoom_row = traitlets.Float(None, allow_none=True).tag(sync=True) + zoom_col = traitlets.Float(None, allow_none=True).tag(sync=True) + link_zoom = traitlets.Bool(False).tag(sync=True) + link_pan = traitlets.Bool(False).tag(sync=True) + link_contrast = traitlets.Bool(True).tag(sync=True) + diff_mode = traitlets.Bool(False).tag(sync=True) + diff_reference = traitlets.Int(0).tag(sync=True) + + # ========================================================================= + # UI Visibility + # ========================================================================= + show_controls = traitlets.Bool(True).tag(sync=True) + show_stats = traitlets.Bool(True).tag(sync=True) + disabled_tools = traitlets.List(traitlets.Unicode()).tag(sync=True) + hidden_tools = traitlets.List(traitlets.Unicode()).tag(sync=True) + stats_mean = traitlets.List(traitlets.Float()).tag(sync=True) + stats_min = traitlets.List(traitlets.Float()).tag(sync=True) + stats_max = traitlets.List(traitlets.Float()).tag(sync=True) + stats_std = traitlets.List(traitlets.Float()).tag(sync=True) + + # ========================================================================= + # Analysis Panels (FFT + Histogram shown together) + # ========================================================================= + show_fft = traitlets.Bool(False).tag(sync=True) + fft_window = traitlets.Bool(True).tag(sync=True) + + # ========================================================================= + # Selected Image (for single-image analysis display) + # ========================================================================= + selected_idx = traitlets.Int(0).tag(sync=True) + + # ========================================================================= + # ROI Selection + # ========================================================================= + roi_active = traitlets.Bool(False).tag(sync=True) + roi_list = traitlets.List([]).tag(sync=True) + roi_selected_idx = traitlets.Int(-1).tag(sync=True) + + # ========================================================================= + # Line Profile + # ========================================================================= + profile_line = traitlets.List(traitlets.Dict()).tag(sync=True) + + # ========================================================================= + # Per-Image Rotation + # ========================================================================= + image_rotations = traitlets.List(traitlets.Int(), []).tag(sync=True) + + @classmethod + def _normalize_tool_groups(cls, tool_groups) -> List[str]: + return normalize_tool_groups("Show2D", tool_groups) + + @classmethod + def _build_disabled_tools( + cls, + disabled_tools=None, + disable_display: bool = False, + disable_histogram: bool = False, + disable_stats: bool = False, + disable_navigation: bool = False, + disable_view: bool = False, + disable_export: bool = False, + disable_roi: bool = False, + disable_profile: bool = False, + disable_all: bool = False, + ) -> List[str]: + return build_tool_groups( + "Show2D", + tool_groups=disabled_tools, + all_flag=disable_all, + flag_map={ + "display": disable_display, + "histogram": disable_histogram, + "stats": disable_stats, + "navigation": disable_navigation, + "view": disable_view, + "export": disable_export, + "roi": disable_roi, + "profile": disable_profile, + }, + ) + + @classmethod + def _build_hidden_tools( + cls, + hidden_tools=None, + hide_display: bool = False, + hide_histogram: bool = False, + hide_stats: bool = False, + hide_navigation: bool = False, + hide_view: bool = False, + hide_export: bool = False, + hide_roi: bool = False, + hide_profile: bool = False, + hide_all: bool = False, + ) -> List[str]: + return build_tool_groups( + "Show2D", + tool_groups=hidden_tools, + all_flag=hide_all, + flag_map={ + "display": hide_display, + "histogram": hide_histogram, + "stats": hide_stats, + "navigation": hide_navigation, + "view": hide_view, + "export": hide_export, + "roi": hide_roi, + "profile": hide_profile, + }, + ) + + @traitlets.validate("disabled_tools") + def _validate_disabled_tools(self, proposal): + return self._normalize_tool_groups(proposal["value"]) + + @traitlets.validate("hidden_tools") + def _validate_hidden_tools(self, proposal): + return self._normalize_tool_groups(proposal["value"]) + + def __init__( + self, + data: Union[np.ndarray, List[np.ndarray]], + labels: Optional[List[str]] = None, + title: str = "", + cmap: Union[str, Colormap] = Colormap.INFERNO, + pixel_size: float = 0.0, + scale_bar_visible: bool = True, + show_fft: bool = False, + fft_window: bool = True, + show_controls: bool = True, + show_stats: bool = True, + log_scale: bool = False, + auto_contrast: bool = False, + vmin: float | list | None = None, + vmax: float | list | None = None, + disabled_tools: Optional[List[str]] = None, + disable_display: bool = False, + disable_histogram: bool = False, + disable_stats: bool = False, + disable_navigation: bool = False, + disable_view: bool = False, + disable_export: bool = False, + disable_roi: bool = False, + disable_profile: bool = False, + disable_all: bool = False, + hidden_tools: Optional[List[str]] = None, + hide_display: bool = False, + hide_histogram: bool = False, + hide_stats: bool = False, + hide_navigation: bool = False, + hide_view: bool = False, + hide_export: bool = False, + hide_roi: bool = False, + hide_profile: bool = False, + hide_all: bool = False, + ncols: int = 3, + size: int = 0, + smooth: bool = False, + zoom: float = 1.0, + zoom_row: float | None = None, + zoom_col: float | None = None, + link_zoom: bool = False, + link_pan: bool = False, + link_contrast: bool = True, + diff_mode: bool = False, + view_box: tuple | list | None = None, + display_bin: Union[int, str] = "auto", + state=None, + **kwargs, + ): + import time as _time + _t0 = _time.perf_counter() + # Reject typos and stale kwargs (e.g. image_width_px, pixel_size_angstrom). + # anywidget/traitlets silently ignores unknown keys, which hid the + # pixel_size_angstrom bug in show2d_all_features.ipynb for months. + _reject_unknown_kwargs(type(self), kwargs) + super().__init__(**kwargs) + # hold_sync() batches ALL traitlet assignments into a single comm message + # sent when the context manager exits. Without this, each self.x = y + # fires a separate round-trip over the ZMQ/websocket channel, which + # can add 20+ seconds for a 30-image gallery in VS Code Jupyter. + with self.hold_sync(): + self._init_sync( + data=data, labels=labels, title=title, cmap=cmap, + pixel_size=pixel_size, scale_bar_visible=scale_bar_visible, + show_fft=show_fft, fft_window=fft_window, + show_controls=show_controls, show_stats=show_stats, + log_scale=log_scale, auto_contrast=auto_contrast, + vmin=vmin, vmax=vmax, + disabled_tools=disabled_tools, + disable_display=disable_display, + disable_histogram=disable_histogram, + disable_stats=disable_stats, + disable_navigation=disable_navigation, + disable_view=disable_view, + disable_export=disable_export, + disable_roi=disable_roi, + disable_profile=disable_profile, + disable_all=disable_all, + hidden_tools=hidden_tools, + hide_display=hide_display, + hide_histogram=hide_histogram, + hide_stats=hide_stats, + hide_navigation=hide_navigation, + hide_view=hide_view, + hide_export=hide_export, + hide_roi=hide_roi, + hide_profile=hide_profile, + hide_all=hide_all, + ncols=ncols, size=size, smooth=smooth, zoom=zoom, + zoom_row=zoom_row, zoom_col=zoom_col, + link_zoom=link_zoom, link_pan=link_pan, link_contrast=link_contrast, + diff_mode=diff_mode, view_box=view_box, + display_bin=display_bin, state=state, _t0=_t0) + + def _init_sync(self, *, data, labels, title, cmap, pixel_size, + scale_bar_visible, show_fft, fft_window, + show_controls, show_stats, log_scale, auto_contrast, + vmin, vmax, disabled_tools, + disable_display, disable_histogram, disable_stats, + disable_navigation, disable_view, disable_export, + disable_roi, disable_profile, disable_all, + hidden_tools, hide_display, hide_histogram, hide_stats, + hide_navigation, hide_view, hide_export, hide_roi, + hide_profile, hide_all, + ncols, size, smooth, zoom, zoom_row, zoom_col, + link_zoom, link_pan, link_contrast, diff_mode, view_box, + display_bin, state, _t0): + import time as _time + self.widget_version = resolve_widget_version() + self._display_data = None # initialized after data setup + self._display_bin = 1 + + # Check if data is a Dataset2d and extract metadata + if hasattr(data, "array") and hasattr(data, "name") and hasattr(data, "sampling"): + if not title and data.name: + title = data.name + if pixel_size == 0.0 and hasattr(data, "units"): + units = list(data.units) + sampling_val = float(data.sampling[-1]) + if units[-1] in ("nm",): + pixel_size = sampling_val * 10 # nm → Å + elif units[-1] in ("Å", "angstrom", "A"): + pixel_size = sampling_val + data = data.array + + # Convert input to NumPy (handles NumPy, CuPy, PyTorch) + if isinstance(data, list): + images = [to_numpy(d) for d in data] + + # Check if all images have the same shape + shapes = [img.shape for img in images] + if len(set(shapes)) > 1: + # Different sizes - resize all to the largest + max_h = max(s[0] for s in shapes) + max_w = max(s[1] for s in shapes) + images = [_resize_image(img, max_h, max_w) for img in images] + + data = np.stack(images) + else: + data = to_numpy(data) + + # Ensure 3D shape (N, H, W) + if data.ndim == 2: + data = data[np.newaxis, ...] + + # Avoid redundant copy: np.asarray is a no-op when already float32 + contiguous + if data.dtype == np.float32: + self._data = np.array(data, dtype=np.float32, copy=True) + else: + self._data = np.asarray(data, dtype=np.float32) + # Store originals for rotation reset — views into _data (no copy). + # Only materialized as independent copies when a rotation is applied. + self._data_original = [self._data[i] for i in range(self._data.shape[0])] + self._originals_are_views = True + self.n_images = int(data.shape[0]) + self.height = int(data.shape[1]) + self.width = int(data.shape[2]) + self.image_rotations = [0] * self.n_images + + # Labels + if labels is None: + self.labels = [f"Image {i+1}" for i in range(self.n_images)] + else: + self.labels = list(labels) + + # Options + self.title = title + self.cmap = cmap + self.pixel_size = pixel_size + self.scale_bar_visible = scale_bar_visible + self.size = size + self.smooth = smooth + # view_box sugar: sets zoom + zoom_row/col to center on box + if view_box is not None: + r0, r1, c0, c1 = [float(v) for v in view_box] + box_h = max(1.0, r1 - r0) + box_w = max(1.0, c1 - c0) + zoom = float(min(self.height / box_h, self.width / box_w)) + zoom_row = (r0 + r1) / 2 + zoom_col = (c0 + c1) / 2 + self.initial_zoom = zoom + self.zoom_row = zoom_row + self.zoom_col = zoom_col + self.link_zoom = link_zoom + self.link_pan = link_pan + self.link_contrast = link_contrast + self.diff_mode = diff_mode if self.n_images >= 2 else False + if show_fft and self.height * self.width > 2048 * 2048: + warnings.warn( + f"FFT on {self.height}×{self.width} image ({self.height * self.width / 1e6:.1f}M pixels) " + f"may be slow. Consider using ROI FFT for a sub-region.", + stacklevel=2, + ) + self.show_fft = show_fft + self.fft_window = fft_window + self.show_controls = show_controls + self.show_stats = show_stats + self.log_scale = log_scale + self.auto_contrast = auto_contrast + # Accept scalar OR list for vmin/vmax. List → per-image (vmins/vmaxs). + if isinstance(vmin, (list, tuple)) or isinstance(vmax, (list, tuple)): + n = self.n_images + def _expand(v): + if v is None: return [None] * n + if isinstance(v, (list, tuple)): + if len(v) != n: + raise ValueError(f"vmin/vmax list length {len(v)} != n_images {n}") + return [None if x is None else float(x) for x in v] + return [float(v)] * n + self.vmins = _expand(vmin) + self.vmaxs = _expand(vmax) + self.vmin = None + self.vmax = None + else: + self.vmin = vmin + self.vmax = vmax + self.disabled_tools = self._build_disabled_tools( + disabled_tools=disabled_tools, + disable_display=disable_display, + disable_histogram=disable_histogram, + disable_stats=disable_stats, + disable_navigation=disable_navigation, + disable_view=disable_view, + disable_export=disable_export, + disable_roi=disable_roi, + disable_profile=disable_profile, + disable_all=disable_all, + ) + self.hidden_tools = self._build_hidden_tools( + hidden_tools=hidden_tools, + hide_display=hide_display, + hide_histogram=hide_histogram, + hide_stats=hide_stats, + hide_navigation=hide_navigation, + hide_view=hide_view, + hide_export=hide_export, + hide_roi=hide_roi, + hide_profile=hide_profile, + hide_all=hide_all, + ) + self.ncols = ncols + + # Auto-bin for display: keep full-res in _data, send binned to JS. + # GPU memory budget: ~2 GB for display buffers (128 MB per image at 4K). + # At 4K: max ~16 full-res. Beyond that, auto-downsample. + if display_bin == "auto": + # Each 4K image needs ~192 MB GPU buffers (float32 + RGBA + read) + # Tested: 12×4K (2.3 GB) works, 24×4K (4.6 GB) OOMs + # Budget: 2.5 GB allows 12×4K full-res, bins above that + gpu_budget_mb = self._GPU_DISPLAY_BUDGET_MB + per_image_mb = (self.height * self.width * 4 * 3) / (1024 * 1024) # 3 buffers + total_mb = self.n_images * per_image_mb + if total_mb > gpu_budget_mb: + # Find minimum bin factor to fit + for bf in [2, 4, 8]: + binned_mb = self.n_images * per_image_mb / (bf * bf) + if binned_mb <= gpu_budget_mb: + self._display_bin = bf + break + else: + self._display_bin = 8 + elif isinstance(display_bin, int) and display_bin > 1: + self._display_bin = display_bin + + if self._display_bin > 1: + from quantem.widget.array_utils import bin2d + orig_h, orig_w = self._data.shape[1], self._data.shape[2] + self._display_data = bin2d(self._data, factor=self._display_bin, mode="mean") + self.height = int(self._display_data.shape[1]) + self.width = int(self._display_data.shape[2]) + if pixel_size > 0: + self.pixel_size = pixel_size * self._display_bin + self._display_bin_factor = self._display_bin + print(f" Display bin {self._display_bin}×: {orig_h}×{orig_w} → {self.height}×{self.width} ({self._display_data.nbytes // 1024 // 1024} MB)") + else: + self._display_data = self._data + self._display_bin_factor = 1 + + # Compute initial stats (from full-res data) + self._compute_all_stats() + + # Send display data to JS (possibly binned) + self._update_all_frames() + + self.selected_idx = 0 + + if state is not None: + if isinstance(state, (str, pathlib.Path)): + state = unwrap_state_payload( + json.loads(pathlib.Path(state).read_text()), + require_envelope=True, + ) + else: + state = unwrap_state_payload(state) + self.load_state_dict(state) + + # Stash wall-clock start on the instance; the observer below prints the + # TRUE end-to-end time after JS signals first paint. The Python-only + # __init__ number is misleading for widget UX — a widget is not "done" + # until the browser has painted its first frame. + self._init_t0 = _t0 + self._init_py_elapsed_ms = (_time.perf_counter() - _t0) * 1000 + self.observe(self._on_first_render, names=["_js_rendered"]) + + def _on_first_render(self, change): + import time as _time + if not change.get("new"): + return + total_ms = (_time.perf_counter() - self._init_t0) * 1000 + py_ms = self._init_py_elapsed_ms + shape = (f"{self.n_images}×{self.height}×{self.width}" + if self.n_images > 1 else f"{self.height}×{self.width}") + mem = self._data.nbytes + mem_str = f"{mem / (1 << 20):.0f} MB" if mem >= 1 << 20 else f"{mem / (1 << 10):.0f} KB" + # Expose as attributes so tests and notebooks can assert on them. + # These are the ground truth for "did JS actually paint" — if they're + # None, the JS side never signaled first render. + self.render_total_ms = int(total_ms) + self.render_python_build_ms = int(py_ms) + self.render_wire_js_ms = int(total_ms - py_ms) + print( + f"Show2D: {shape} {mem_str} — " + f"rendered in {total_ms:.0f} ms (Python build {py_ms:.0f} ms, " + f"wire+JS {total_ms - py_ms:.0f} ms)", + flush=True, + ) + # Detach observer: one-shot, we only care about the first paint. + try: + self.unobserve(self._on_first_render, names=["_js_rendered"]) + except Exception: + pass + + def set_image(self, data, labels=None): + """Replace the displayed image(s). Preserves all display settings.""" + if hasattr(data, "array") and hasattr(data, "name") and hasattr(data, "sampling"): + data = data.array + if isinstance(data, list): + images = [to_numpy(d) for d in data] + shapes = [img.shape for img in images] + if len(set(shapes)) > 1: + max_h = max(s[0] for s in shapes) + max_w = max(s[1] for s in shapes) + images = [_resize_image(img, max_h, max_w) for img in images] + data = np.stack(images) + else: + data = to_numpy(data) + if data.ndim == 2: + data = data[np.newaxis, ...] + if data.dtype == np.float32: + self._data = np.array(data, dtype=np.float32, copy=True) + else: + self._data = np.asarray(data, dtype=np.float32) + self._data_original = [self._data[i] for i in range(self._data.shape[0])] + self._originals_are_views = True + self.n_images = int(data.shape[0]) + + # Auto-bin for display (reuse existing _display_bin or recompute) + gpu_budget_mb = 2500 + per_image_mb = (data.shape[1] * data.shape[2] * 4 * 3) / (1024 * 1024) + total_mb = self.n_images * per_image_mb + self._display_bin = 1 + if total_mb > gpu_budget_mb: + for bf in [2, 4, 8]: + if total_mb / (bf * bf) <= gpu_budget_mb: + self._display_bin = bf + break + else: + self._display_bin = 8 + + if self._display_bin > 1: + from quantem.widget.array_utils import bin2d + self._display_data = bin2d(self._data, factor=self._display_bin, mode="mean") + self.height = int(self._display_data.shape[1]) + self.width = int(self._display_data.shape[2]) + self._display_bin_factor = self._display_bin + print(f" Display bin {self._display_bin}×: {data.shape[1]}×{data.shape[2]} → {self.height}×{self.width}") + else: + self._display_data = self._data + self.height = int(data.shape[1]) + self.width = int(data.shape[2]) + self._display_bin_factor = 1 + + self.image_rotations = [0] * self.n_images + if labels is not None: + self.labels = list(labels) + else: + self.labels = [f"Image {i+1}" for i in range(self.n_images)] + self.selected_idx = 0 + self._compute_all_stats() + self._update_all_frames() + + def __repr__(self) -> str: + if self.n_images > 1: + shape = f"{self.n_images}×{self.height}×{self.width}" + return f"Show2D({shape}, idx={self.selected_idx}, cmap={self.cmap})" + return f"Show2D({self.height}×{self.width}, cmap={self.cmap})" + + def _repr_mimebundle_(self, **kwargs): + """Return widget view + (optionally) static PNG fallback. + + Live Jupyter renders the interactive widget; the PNG fallback is only + consumed by nbsphinx / GitHub / nbviewer when the widget view cannot be + rendered. Building the fallback runs matplotlib over every gallery image + (~1.7 s for a 30×512² stack) and that cost pays off only in static builds. + Gate it behind ``QUANTEM_WIDGET_STATIC_FALLBACK=1`` so interactive sessions + return immediately. + """ + bundle = super()._repr_mimebundle_(**kwargs) + if not os.environ.get("QUANTEM_WIDGET_STATIC_FALLBACK"): + return bundle + data_dict = bundle[0] if isinstance(bundle, tuple) else bundle + n = self.n_images + ncols = min(self.ncols, n) + nrows = math.ceil(n / ncols) + cell = 4 + fig, axes = plt.subplots( + nrows, ncols, + figsize=(cell * ncols, cell * nrows), + squeeze=False, + ) + max_preview = 256 + for i in range(nrows * ncols): + r, c = divmod(i, ncols) + ax = axes[r][c] + if i < n: + img = self._data[i] + h, w = img.shape + if h > max_preview or w > max_preview: + step = max(h // max_preview, w // max_preview, 1) + img = img[::step, ::step] + ax.imshow(img, cmap=self.cmap, origin="upper") + ax.set_title(self.labels[i], fontsize=10) + ax.axis("off") + if self.title: + fig.suptitle(self.title, fontsize=12) + fig.tight_layout() + buf = io.BytesIO() + fig.savefig(buf, format="png", dpi=120, bbox_inches="tight") + plt.close(fig) + data_dict["image/png"] = base64.b64encode(buf.getvalue()).decode("ascii") + if isinstance(bundle, tuple): + return (data_dict, bundle[1]) + return data_dict + + def _normalize_frame(self, frame: np.ndarray) -> np.ndarray: + if self.log_scale: + frame = np.log1p(np.maximum(frame, 0)) + if self.vmin is not None and self.vmax is not None: + vmin = float(self.vmin) + vmax = float(self.vmax) + if self.log_scale: + vmin = float(np.log1p(max(vmin, 0))) + vmax = float(np.log1p(max(vmax, 0))) + elif self.auto_contrast: + vmin = float(np.percentile(frame, 2)) + vmax = float(np.percentile(frame, 98)) + else: + vmin = float(frame.min()) + vmax = float(frame.max()) + if vmax > vmin: + normalized = np.clip((frame - vmin) / (vmax - vmin) * 255, 0, 255) + return normalized.astype(np.uint8) + return np.zeros(frame.shape, dtype=np.uint8) + + def save_image( + self, + path: str | pathlib.Path, + *, + idx: int | None = None, + format: str | None = None, + dpi: int = 150, + title: bool | str = False, + colorbar: bool = False, + scalebar: bool = False, + ) -> pathlib.Path: + """Save current image as PNG, PDF, or TIFF. + + When ``title``, ``colorbar``, or ``scalebar`` are enabled, the output + is a publication-quality figure rendered via matplotlib. Otherwise a + raw colormapped image is saved directly (faster, exact pixel output). + + Parameters + ---------- + path : str or pathlib.Path + Output file path. + idx : int, optional + Image index in gallery mode. Defaults to current selected_idx. + format : str, optional + 'png', 'pdf', or 'tiff'. If omitted, inferred from file extension. + dpi : int, default 150 + Output DPI. + title : bool or str, default False + ``True`` uses the widget title, a string sets a custom title. + colorbar : bool, default False + Include a colorbar showing the intensity mapping. + scalebar : bool, default False + Include a scale bar (requires ``pixel_size > 0``). + + Returns + ------- + pathlib.Path + The written file path. + """ + from matplotlib import colormaps + from PIL import Image + + path = pathlib.Path(path) + fmt = (format or path.suffix.lstrip(".").lower() or "png").lower() + if fmt not in ("png", "pdf", "tiff", "tif"): + raise ValueError(f"Unsupported format: {fmt!r}. Use 'png', 'pdf', or 'tiff'.") + + i = idx if idx is not None else self.selected_idx + if i < 0 or i >= self.n_images: + raise IndexError(f"Image index {i} out of range [0, {self.n_images})") + + frame = self._data[i] + normalized = self._normalize_frame(frame) + cmap_fn = colormaps.get_cmap(self.cmap) + path.parent.mkdir(parents=True, exist_ok=True) + + use_figure = title or colorbar or scalebar + if not use_figure: + rgba = (cmap_fn(normalized / 255.0) * 255).astype(np.uint8) + img = Image.fromarray(rgba) + if fmt == "pdf": + Image.init() + img = img.convert("RGB") + img.save(str(path), dpi=(dpi, dpi)) + return path + + # Publication-quality figure via matplotlib + h, w = frame.shape + aspect = h / w + fig_w = 6 + fig, ax = plt.subplots(figsize=(fig_w, fig_w * aspect)) + im = ax.imshow(normalized, cmap=cmap_fn, vmin=0, vmax=255, origin="upper") + ax.axis("off") + + if title: + label = title if isinstance(title, str) else self.title + if label: + ax.set_title(label, fontsize=14, fontweight="bold", pad=8) + + if colorbar: + # Map 0–255 back to data-space values for tick labels + if self.log_scale: + frame_proc = np.log1p(np.maximum(frame, 0)) + else: + frame_proc = frame + if self.vmin is not None and self.vmax is not None: + dmin = float(self.vmin) + dmax = float(self.vmax) + if self.log_scale: + dmin = float(np.log1p(max(dmin, 0))) + dmax = float(np.log1p(max(dmax, 0))) + elif self.auto_contrast: + dmin = float(np.percentile(frame_proc, 2)) + dmax = float(np.percentile(frame_proc, 98)) + else: + dmin = float(frame_proc.min()) + dmax = float(frame_proc.max()) + cb = fig.colorbar(im, ax=ax, fraction=0.046, pad=0.04) + n_ticks = 5 + tick_positions = np.linspace(0, 255, n_ticks) + tick_labels = [f"{dmin + (dmax - dmin) * t / 255:.4g}" for t in tick_positions] + cb.set_ticks(tick_positions) + cb.set_ticklabels(tick_labels) + + if scalebar and self.pixel_size > 0: + from matplotlib.patches import FancyBboxPatch + # Compute a nice scale bar length + target_frac = 0.2 # ~20% of image width + raw_length_px = target_frac * w + raw_length_phys = raw_length_px * self.pixel_size # in Å + nice = _round_to_nice(raw_length_phys) + bar_px = nice / self.pixel_size + if nice >= 10: + label_text = f"{nice / 10:.4g} nm" + else: + label_text = f"{nice:.4g} Å" + margin = 0.03 + bar_y = h * (1 - margin) - 2 + bar_x = w * (1 - margin) - bar_px + ax.plot([bar_x, bar_x + bar_px], [bar_y, bar_y], + color="white", linewidth=3, solid_capstyle="butt") + ax.plot([bar_x, bar_x + bar_px], [bar_y, bar_y], + color="black", linewidth=1, solid_capstyle="butt") + ax.text(bar_x + bar_px / 2, bar_y - h * 0.02, label_text, + color="white", fontsize=10, fontweight="bold", + ha="center", va="bottom", + path_effects=[ + matplotlib.patheffects.withStroke(linewidth=2, foreground="black") + ]) + + fig.savefig(str(path), dpi=dpi, bbox_inches="tight", + facecolor="white", pad_inches=0.1) + plt.close(fig) + return path + + def state_dict(self): + return { + "title": self.title, + "cmap": self.cmap, + "log_scale": self.log_scale, + "auto_contrast": self.auto_contrast, + "vmin": self.vmin, + "vmax": self.vmax, + "show_stats": self.show_stats, + "show_fft": self.show_fft, + "fft_window": self.fft_window, + "show_controls": self.show_controls, + "disabled_tools": self.disabled_tools, + "hidden_tools": self.hidden_tools, + "pixel_size": self.pixel_size, + "scale_bar_visible": self.scale_bar_visible, + "size": self.size, + "smooth": self.smooth, + "initial_zoom": self.initial_zoom, + "vmins": self.vmins, + "vmaxs": self.vmaxs, + "link_zoom": self.link_zoom, + "link_pan": self.link_pan, + "link_contrast": self.link_contrast, + "zoom_row": self.zoom_row, + "zoom_col": self.zoom_col, + "diff_mode": self.diff_mode, + "ncols": self.ncols, + "selected_idx": self.selected_idx, + "roi_active": self.roi_active, + "roi_list": self.roi_list, + "roi_selected_idx": self.roi_selected_idx, + "profile_line": self.profile_line, + "image_rotations": list(self.image_rotations), + "display_bin": self._display_bin, + } + + def save(self, path: str): + save_state_file(path, "Show2D", self.state_dict()) + + def load_state_dict(self, state): + for key, val in state.items(): + # Silent migrations for renamed keys in older saved state files. + if key == "pixel_size_angstrom": + key = "pixel_size" + elif key == "canvas_size": + key = "size" + if key == "display_bin": + self._display_bin = val + continue + if hasattr(self, key): + setattr(self, key, val) + + def summary(self): + lines = [self.title or "Show2D", "═" * 32] + if self.n_images > 1: + lines.append(f"Image: {self.n_images}×{self.height}×{self.width} ({self.ncols} cols)") + else: + lines.append(f"Image: {self.height}×{self.width}") + if self.pixel_size > 0: + ps = self.pixel_size + if ps >= 10: + lines[-1] += f" ({ps / 10:.2f} nm/px)" + else: + lines[-1] += f" ({ps:.2f} Å/px)" + if hasattr(self, "_data") and self._data is not None: + arr = self._data + lines.append(f"Data: min={float(arr.min()):.4g} max={float(arr.max()):.4g} mean={float(arr.mean()):.4g}") + cmap = self.cmap + scale = "log" if self.log_scale else "linear" + if self.vmin is not None and self.vmax is not None: + contrast = f"vmin={self.vmin:.4g}, vmax={self.vmax:.4g}" + elif self.auto_contrast: + contrast = "auto contrast" + else: + contrast = "manual contrast" + display = f"{cmap} | {contrast} | {scale}" + if self.show_fft: + display += " | FFT" + if not self.fft_window: + display += " (no window)" + lines.append(f"Display: {display}") + if self.disabled_tools: + lines.append(f"Locked: {', '.join(self.disabled_tools)}") + if self.hidden_tools: + lines.append(f"Hidden: {', '.join(self.hidden_tools)}") + if self.roi_active and self.roi_list: + lines.append(f"ROI: {len(self.roi_list)} region(s)") + if self.profile_line: + p0, p1 = self.profile_line[0], self.profile_line[1] + lines.append(f"Profile: ({p0['row']:.0f}, {p0['col']:.0f}) → ({p1['row']:.0f}, {p1['col']:.0f})") + non_zero = [(i, r * 90) for i, r in enumerate(self.image_rotations) if r % 4 != 0] + if non_zero: + parts = [f"#{i}={deg}°" for i, deg in non_zero] + lines.append(f"Rotated: {', '.join(parts)}") + rt = getattr(self, "render_total_ms", None) + if rt is not None: + pb = getattr(self, "render_python_build_ms", 0) + wj = getattr(self, "render_wire_js_ms", 0) + lines.append(f"Rendered: {rt} ms total (Python build {pb} ms, wire+JS {wj} ms)") + else: + lines.append("Rendered: (pending first browser paint)") + print("\n".join(lines)) + + def _compute_all_stats(self): + """Compute statistics for all images (vectorized over all frames).""" + # Vectorized reduction over (H, W) is faster than per-image loops + # for large galleries (e.g. 12×4096×4096: 164ms vs 191ms). + axes = (1, 2) if self._data.ndim == 3 else None + self.stats_mean = np.mean(self._data, axis=axes).ravel().tolist() + self.stats_min = np.min(self._data, axis=axes).ravel().tolist() + self.stats_max = np.max(self._data, axis=axes).ravel().tolist() + self.stats_std = np.std(self._data, axis=axes).ravel().tolist() + + def _update_all_frames(self): + """Send display data to JS (possibly binned for large galleries).""" + data = self._display_data if self._display_data is not None else self._data + self.frame_bytes = data.tobytes() + + def _apply_rotations(self): + # Materialize originals as independent copies only when a non-zero + # rotation exists (they start as views into _data to avoid 800MB copy at init) + has_rotation = any( + (self.image_rotations[i] if i < len(self.image_rotations) else 0) % 4 != 0 + for i in range(len(self._data_original)) + ) + # No-rotation fast path: skip 30+ MB of redundant tobytes + stats recomputation + # on every widget init. The observer fires once when image_rotations = [0]*n + # is assigned in __init__; without this guard that triggered a full frame + # rebuild + stats recompute for a no-op. + if not has_rotation and self._originals_are_views: + return + if self._originals_are_views and has_rotation: + self._data_original = [img.copy() for img in self._data_original] + self._originals_are_views = False + rotated = [] + for i, orig in enumerate(self._data_original): + k = self.image_rotations[i] if i < len(self.image_rotations) else 0 + k = k % 4 + if k == 0: + rotated.append(orig) + else: + rotated.append(np.rot90(orig, k=k)) + # If shapes differ after rotation, center-pad all to max dims + shapes = [img.shape for img in rotated] + if len(set(shapes)) > 1: + max_h = max(s[0] for s in shapes) + max_w = max(s[1] for s in shapes) + padded = [] + for img in rotated: + h, w = img.shape + pad_top = (max_h - h) // 2 + pad_bot = max_h - h - pad_top + pad_left = (max_w - w) // 2 + pad_right = max_w - w - pad_left + padded.append(np.pad(img, ((pad_top, pad_bot), (pad_left, pad_right)), mode="constant", constant_values=0)) + rotated = padded + self._data = np.stack(rotated).astype(np.float32) + # Recompute display data if binning is active + if self._display_bin > 1: + from quantem.widget.array_utils import bin2d + self._display_data = bin2d(self._data, factor=self._display_bin, mode="mean") + else: + self._display_data = self._data + display = self._display_data if self._display_data is not None else self._data + self.height = int(display.shape[1]) + self.width = int(display.shape[2]) + self._compute_all_stats() + self._update_all_frames() + + @traitlets.observe("image_rotations") + def _on_image_rotations_changed(self, change): + if hasattr(self, "_data_original"): + self._apply_rotations() + + def rotate(self, idx: int, angle: int) -> Self: + """Rotate image ``idx`` by ``angle`` degrees (CCW-positive, matches np.rot90). + + Rotation convention follows ``np.rot90``:: + + angle | image_rotations | np.rot90 k | direction + ------+-----------------+------------+---------- + 90 | 1 | 1 | 90° CCW + 180 | 2 | 2 | 180° + -90 | 3 | 3 | 90° CW + 360 | 0 | 0 | identity + + Parameters + ---------- + idx : int + Image index in the gallery (0-based). + angle : int + Rotation angle in degrees (must be a multiple of 90). + Positive = counter-clockwise, negative = clockwise. + + Returns + ------- + Self + """ + if angle % 90 != 0: + raise ValueError(f"Rotation angle must be a multiple of 90°, got {angle}") + if idx < 0 or idx >= self.n_images: + raise IndexError(f"Image index {idx} out of range [0, {self.n_images})") + k = (angle // 90) % 4 + rots = list(self.image_rotations) + while len(rots) < self.n_images: + rots.append(0) + rots[idx] = (rots[idx] + k) % 4 + self.image_rotations = rots + return self + + def _sample_profile(self, row0, col0, row1, col1): + img = self._data[self.selected_idx] + h, w = img.shape + dc, dr = col1 - col0, row1 - row0 + length = (dc**2 + dr**2) ** 0.5 + n = max(2, int(np.ceil(length))) + t = np.linspace(0, 1, n) + cs = col0 + t * dc + rs = row0 + t * dr + ci = np.floor(cs).astype(int) + ri = np.floor(rs).astype(int) + cf = cs - ci + rf = rs - ri + c0c = np.clip(ci, 0, w - 1) + c1c = np.clip(ci + 1, 0, w - 1) + r0c = np.clip(ri, 0, h - 1) + r1c = np.clip(ri + 1, 0, h - 1) + return (img[r0c, c0c] * (1 - cf) * (1 - rf) + + img[r0c, c1c] * cf * (1 - rf) + + img[r1c, c0c] * (1 - cf) * rf + + img[r1c, c1c] * cf * rf).astype(np.float32) + + def set_profile(self, start: tuple, end: tuple): + """Set a line profile between two points (image pixel coordinates). + + Parameters + ---------- + start : tuple of (row, col) + Start point in pixel coordinates. + end : tuple of (row, col) + End point in pixel coordinates. + """ + row0, col0 = start + row1, col1 = end + self.profile_line = [ + {"row": float(row0), "col": float(col0)}, + {"row": float(row1), "col": float(col1)}, + ] + + def clear_profile(self): + """Clear the current line profile.""" + self.profile_line = [] + + def _upsert_selected_roi(self, updates: dict): + rois = list(self.roi_list) + color_cycle = ["#4fc3f7", "#81c784", "#ffb74d", "#ce93d8", "#ef5350", "#ffd54f", "#90a4ae", "#a1887f"] + defaults = { + "shape": "square", + "row": int(self.height // 2), + "col": int(self.width // 2), + "radius": 10, + "radius_inner": 5, + "width": 20, + "height": 20, + "line_width": 2, + "highlight": False, + "visible": True, + "locked": False, + } + if self.roi_selected_idx >= 0 and self.roi_selected_idx < len(rois): + current = {**defaults, **rois[self.roi_selected_idx]} + if not current.get("color"): + current["color"] = color_cycle[self.roi_selected_idx % len(color_cycle)] + rois[self.roi_selected_idx] = {**current, **updates} + else: + rois.append({**defaults, "color": color_cycle[len(rois) % len(color_cycle)], **updates}) + self.roi_selected_idx = len(rois) - 1 + self.roi_list = rois + self.roi_active = True + + def add_roi(self, row: int | None = None, col: int | None = None, shape: str = "square") -> Self: + with self.hold_sync(): + self.roi_selected_idx = -1 + self._upsert_selected_roi({ + "shape": shape, + "row": int(self.height // 2 if row is None else row), + "col": int(self.width // 2 if col is None else col), + }) + return self + + def clear_rois(self) -> Self: + with self.hold_sync(): + self.roi_list = [] + self.roi_selected_idx = -1 + self.roi_active = False + return self + + def delete_selected_roi(self) -> Self: + idx = int(self.roi_selected_idx) + if idx < 0 or idx >= len(self.roi_list): + return self + with self.hold_sync(): + rois = [roi for i, roi in enumerate(self.roi_list) if i != idx] + self.roi_list = rois + self.roi_selected_idx = min(idx, len(rois) - 1) if rois else -1 + if not rois: + self.roi_active = False + return self + + def set_roi(self, row: int, col: int, radius: int = 10) -> Self: + with self.hold_sync(): + self._upsert_selected_roi({"shape": "circle", "row": int(row), "col": int(col), "radius": int(radius)}) + return self + + def roi_circle(self, radius: int = 10) -> Self: + with self.hold_sync(): + self._upsert_selected_roi({"shape": "circle", "radius": int(radius)}) + return self + + def roi_square(self, half_size: int = 10) -> Self: + with self.hold_sync(): + self._upsert_selected_roi({"shape": "square", "radius": int(half_size)}) + return self + + def roi_rectangle(self, width: int = 20, height: int = 10) -> Self: + with self.hold_sync(): + self._upsert_selected_roi({"shape": "rectangle", "width": int(width), "height": int(height)}) + return self + + def roi_annular(self, inner: int = 5, outer: int = 10) -> Self: + with self.hold_sync(): + self._upsert_selected_roi({"shape": "annular", "radius_inner": int(inner), "radius": int(outer)}) + return self + + @property + def profile(self): + """Get profile line endpoints as [(row0, col0), (row1, col1)] or []. + + Returns + ------- + list of tuple + Line endpoints in pixel coordinates, or empty list if no profile. + """ + return [(p["row"], p["col"]) for p in self.profile_line] + + @property + def profile_values(self): + """Get intensity values along the profile line as a numpy array. + + Returns + ------- + np.ndarray or None + Float32 array of sampled intensities, or None if no profile. + """ + if len(self.profile_line) < 2: + return None + p0, p1 = self.profile_line + return self._sample_profile(p0["row"], p0["col"], p1["row"], p1["col"]) + + @property + def profile_distance(self): + """Get total distance of the profile line in calibrated units. + + Returns + ------- + float or None + Distance in angstroms (if pixel_size > 0) or pixels. + None if no profile line is set. + """ + if len(self.profile_line) < 2: + return None + p0, p1 = self.profile_line + dc = p1["col"] - p0["col"] + dr = p1["row"] - p0["row"] + dist_px = (dc**2 + dr**2) ** 0.5 + if self.pixel_size > 0: + return dist_px * self.pixel_size + return dist_px + + +bind_tool_runtime_api(Show2D, "Show2D") diff --git a/widget/src/quantem/widget/show4dstem.py b/widget/src/quantem/widget/show4dstem.py new file mode 100644 index 00000000..1fe94273 --- /dev/null +++ b/widget/src/quantem/widget/show4dstem.py @@ -0,0 +1,4337 @@ +""" +show4dstem: Fast interactive 4D-STEM viewer widget. + +Apple MPS GPU limit: PyTorch's MPS backend (Apple Silicon) has a hard limit +of ~2.1 billion elements (INT_MAX = 2^31 - 1) per tensor. Datasets exceeding +this automatically fall back to CPU, which is still fast on Apple Silicon +thanks to unified memory (CPU and GPU share the same RAM). + +CUDA GPUs do not have this limit. + +Common 4D-STEM sizes (float32): + + Scan Detector Elements Size MPS? + 128×128 128×128 268M 1.0 GB yes + 128×128 256×256 1,074M 4.0 GB yes + 256×256 128×128 1,074M 4.0 GB yes + 256×256 192×192 2,416M 9.0 GB no (auto CPU, still fast) + 256×256 256×256 4,295M 16.0 GB no (auto CPU, still fast) + 512×512 256×256 17,180M 64.0 GB no (auto CPU) + +To reduce data size, bin k-space at the dataset level before viewing: + + dataset = dataset.bin(2, axes=(2, 3)) # 2x2 k-space binning + widget = Show4DSTEM(dataset) +""" + +import hashlib +import json +import math +import pathlib +import time +from datetime import datetime, timezone +from typing import Any, Self +from uuid import uuid4 + +import anywidget +import numpy as np +import torch +import traitlets + +from quantem.core.config import validate_device +from quantem.widget.array_utils import to_numpy +from quantem.widget.json_state import ( + build_json_header, + resolve_widget_version, + save_state_file, + unwrap_state_payload, +) +from quantem.widget.tool_parity import ( + bind_tool_runtime_api, + build_tool_groups, + normalize_tool_groups, +) + + +def _format_memory(nbytes: int) -> str: + if nbytes >= 1 << 30: return f"{nbytes / (1 << 30):.1f} GB" + if nbytes >= 1 << 20: return f"{nbytes / (1 << 20):.0f} MB" + if nbytes >= 1 << 10: return f"{nbytes / (1 << 10):.0f} KB" + return f"{nbytes} B" + + +# ============================================================================ +# Constants +# ============================================================================ +DEFAULT_BF_RATIO = 0.125 # BF disk radius as fraction of detector size (1/8) +SPARSE_MASK_THRESHOLD = 0.2 # Use sparse indexing below this mask coverage +MIN_LOG_VALUE = 1e-10 # Minimum value for log scale to avoid log(0) +DEFAULT_VI_ROI_RATIO = 0.15 # Default VI ROI size as fraction of scan dimension + +class Show4DSTEM(anywidget.AnyWidget): + """ + Fast interactive 4D-STEM viewer with advanced features. + + Optimized for speed with binary transfer and pre-normalization. + Works with NumPy and PyTorch arrays. + + Parameters + ---------- + data : Dataset4dstem or array_like + Dataset4dstem object (calibration auto-extracted), 4D array + of shape (scan_rows, scan_cols, det_rows, det_cols), or 5D array + of shape (n_frames, scan_rows, scan_cols, det_rows, det_cols) + for time-series or tilt-series data. + scan_shape : tuple, optional + If data is flattened (N, det_rows, det_cols), provide scan dimensions. + pixel_size : float, optional + Pixel size in Å (real-space). Used for scale bar. + Auto-extracted from Dataset4dstem if not provided. + k_pixel_size : float, optional + Detector pixel size in mrad (k-space). Used for scale bar. + Auto-extracted from Dataset4dstem if not provided. + center : tuple[float, float], optional + (center_row, center_col) of the diffraction pattern in pixels. + If not provided, defaults to detector center. + bf_radius : float, optional + Bright field disk radius in pixels. If not provided, estimated as 1/8 of detector size. + precompute_virtual_images : bool, default True + Precompute BF/ABF/LAADF/HAADF virtual images for preset switching. + frame_dim_label : str, optional + Label for the frame dimension when 5D data is provided. + Defaults to "Frame". Common values: "Tilt", "Time", "Focus". + disabled_tools : list of str, optional + Tool groups to lock while still showing controls. Supported: + ``"display"``, ``"histogram"``, ``"stats"``, ``"navigation"``, + ``"playback"``, ``"view"``, ``"export"``, ``"roi"``, + ``"profile"``, ``"fft"``, ``"virtual"``, ``"frame"``, ``"all"``. + disable_* : bool, optional + Convenience flags mirroring ``disabled_tools`` for each tool group, + plus ``disable_all``. + hidden_tools : list of str, optional + Tool groups to hide from the UI. Uses the same keys as + ``disabled_tools``. + hide_* : bool, optional + Convenience flags mirroring ``disable_*`` for ``hidden_tools``. + + Examples + -------- + >>> # From Dataset4dstem (calibration auto-extracted) + >>> from quantem.core.io.file_readers import read_emdfile_to_4dstem + >>> dataset = read_emdfile_to_4dstem("data.h5") + >>> Show4DSTEM(dataset) + + >>> # From raw array with manual calibration + >>> import numpy as np + >>> data = np.random.rand(64, 64, 128, 128) + >>> Show4DSTEM(data, pixel_size=2.39, k_pixel_size=0.46) + + >>> # With raster animation + >>> widget = Show4DSTEM(dataset) + >>> widget.raster(step=2, interval_ms=50) + + >>> # 5D time-series or tilt-series data + >>> data_5d = np.random.rand(20, 64, 64, 128, 128) # 20 frames + >>> Show4DSTEM(data_5d, frame_dim_label="Tilt") + """ + + _esm = pathlib.Path(__file__).parent / "static" / "show4dstem.js" + _css = pathlib.Path(__file__).parent / "static" / "show4dstem.css" + + # Position in scan space + widget_version = traitlets.Unicode("unknown").tag(sync=True) + title = traitlets.Unicode("").tag(sync=True) + pos_row = traitlets.Int(0).tag(sync=True) + pos_col = traitlets.Int(0).tag(sync=True) + + # Shape of scan space (for slider bounds) + shape_rows = traitlets.Int(1).tag(sync=True) + shape_cols = traitlets.Int(1).tag(sync=True) + + # Detector shape for frontend + det_rows = traitlets.Int(1).tag(sync=True) + det_cols = traitlets.Int(1).tag(sync=True) + + # Raw float32 frame as bytes (JS handles scale/colormap for real-time interactivity) + frame_bytes = traitlets.Bytes(b"").tag(sync=True) + + # Global min/max for DP normalization (computed once from sampled frames) + dp_global_min = traitlets.Float(0.0).tag(sync=True) + dp_global_max = traitlets.Float(1.0).tag(sync=True) + + # ========================================================================= + # Detector Calibration (for presets and scale bar) + # ========================================================================= + center_col = traitlets.Float(0.0).tag(sync=True) # Detector center col + center_row = traitlets.Float(0.0).tag(sync=True) # Detector center row + bf_radius = traitlets.Float(0.0).tag(sync=True) # BF disk radius (pixels) + + # ========================================================================= + # ROI Drawing (for virtual imaging) + # roi_radius is multi-purpose by mode: + # - circle: radius of circle + # - square: half-size (distance from center to edge) + # - annular: outer radius (roi_radius_inner = inner radius) + # - rect: uses roi_width/roi_height instead + # ========================================================================= + roi_active = traitlets.Bool(False).tag(sync=True) + roi_mode = traitlets.Unicode("point").tag(sync=True) + roi_center_col = traitlets.Float(0.0).tag(sync=True) + roi_center_row = traitlets.Float(0.0).tag(sync=True) + # Compound trait for batched row+col updates (JS sends both at once, 1 observer fires) + roi_center = traitlets.List(traitlets.Float(), default_value=[0.0, 0.0]).tag(sync=True) + roi_radius = traitlets.Float(10.0).tag(sync=True) + roi_radius_inner = traitlets.Float(5.0).tag(sync=True) + roi_width = traitlets.Float(20.0).tag(sync=True) + roi_height = traitlets.Float(10.0).tag(sync=True) + + # ========================================================================= + # Virtual Image (ROI-based, updates as you drag ROI on DP) + # ========================================================================= + virtual_image_bytes = traitlets.Bytes(b"").tag(sync=True) # Raw float32 + vi_data_min = traitlets.Float(0.0).tag(sync=True) # Min of current VI for normalization + vi_data_max = traitlets.Float(1.0).tag(sync=True) # Max of current VI for normalization + + # ========================================================================= + # VI ROI (real-space region selection for summed DP) + # ========================================================================= + vi_roi_mode = traitlets.Unicode("off").tag(sync=True) # "off", "circle", "rect" + vi_roi_center_row = traitlets.Float(0.0).tag(sync=True) + vi_roi_center_col = traitlets.Float(0.0).tag(sync=True) + vi_roi_radius = traitlets.Float(5.0).tag(sync=True) + vi_roi_width = traitlets.Float(10.0).tag(sync=True) + vi_roi_height = traitlets.Float(10.0).tag(sync=True) + summed_dp_bytes = traitlets.Bytes(b"").tag(sync=True) # Summed DP from VI ROI + summed_dp_count = traitlets.Int(0).tag(sync=True) # Number of positions summed + + # ========================================================================= + # Scale Bar + # ========================================================================= + pixel_size = traitlets.Float(1.0).tag(sync=True) # Å per pixel (real-space) + k_pixel_size = traitlets.Float(1.0).tag(sync=True) # mrad per pixel (k-space) + k_calibrated = traitlets.Bool(False).tag(sync=True) # True if k-space has mrad calibration + + # ========================================================================= + # Path Animation (programmatic crosshair control) + # ========================================================================= + path_playing = traitlets.Bool(False).tag(sync=True) + path_index = traitlets.Int(0).tag(sync=True) + path_length = traitlets.Int(0).tag(sync=True) + path_interval_ms = traitlets.Int(100).tag(sync=True) # ms between frames + path_loop = traitlets.Bool(True).tag(sync=True) # loop when reaching end + + # ========================================================================= + # Auto-detection trigger (frontend sets to True, backend resets to False) + # ========================================================================= + auto_detect_trigger = traitlets.Bool(False).tag(sync=True) + + # ========================================================================= + # Statistics for display (mean, min, max, std) + # ========================================================================= + dp_stats = traitlets.List(traitlets.Float(), default_value=[0.0, 0.0, 0.0, 0.0]).tag(sync=True) + vi_stats = traitlets.List(traitlets.Float(), default_value=[0.0, 0.0, 0.0, 0.0]).tag(sync=True) + mask_dc = traitlets.Bool(True).tag(sync=True) # Mask center pixel for DP stats + + # ========================================================================= + # Display settings (synced for programmatic export parity) + # ========================================================================= + dp_colormap = traitlets.Unicode("inferno").tag(sync=True) + vi_colormap = traitlets.Unicode("inferno").tag(sync=True) + fft_colormap = traitlets.Unicode("inferno").tag(sync=True) + + dp_scale_mode = traitlets.Unicode("linear").tag(sync=True) # "linear" | "log" | "power" + vi_scale_mode = traitlets.Unicode("linear").tag(sync=True) # "linear" | "log" | "power" + fft_scale_mode = traitlets.Unicode("linear").tag(sync=True) # "linear" | "log" | "power" + + dp_power_exp = traitlets.Float(0.5).tag(sync=True) + vi_power_exp = traitlets.Float(0.5).tag(sync=True) + fft_power_exp = traitlets.Float(0.5).tag(sync=True) + + dp_vmin_pct = traitlets.Float(0.0).tag(sync=True) + dp_vmax_pct = traitlets.Float(100.0).tag(sync=True) + vi_vmin_pct = traitlets.Float(0.0).tag(sync=True) + vi_vmax_pct = traitlets.Float(100.0).tag(sync=True) + fft_vmin_pct = traitlets.Float(0.0).tag(sync=True) + fft_vmax_pct = traitlets.Float(100.0).tag(sync=True) + + # Absolute intensity bounds (override percentile sliders when both set) + dp_vmin = traitlets.Float(None, allow_none=True).tag(sync=True) + dp_vmax = traitlets.Float(None, allow_none=True).tag(sync=True) + vi_vmin = traitlets.Float(None, allow_none=True).tag(sync=True) + vi_vmax = traitlets.Float(None, allow_none=True).tag(sync=True) + + fft_auto = traitlets.Bool(True).tag(sync=True) + show_fft = traitlets.Bool(False).tag(sync=True) + fft_window = traitlets.Bool(True).tag(sync=True) + show_controls = traitlets.Bool(True).tag(sync=True) + dp_show_colorbar = traitlets.Bool(False).tag(sync=True) + export_default_view = traitlets.Unicode("all").tag(sync=True) + export_default_format = traitlets.Unicode("png").tag(sync=True) + export_include_overlays = traitlets.Bool(True).tag(sync=True) + export_include_scalebar = traitlets.Bool(True).tag(sync=True) + export_default_dpi = traitlets.Int(300).tag(sync=True) + + # ========================================================================= + # Frame Animation (5D time/tilt series) + # ========================================================================= + frame_idx = traitlets.Int(0).tag(sync=True) + n_frames = traitlets.Int(1).tag(sync=True) + frame_dim_label = traitlets.Unicode("Frame").tag(sync=True) + frame_labels = traitlets.List(traitlets.Unicode(), []).tag(sync=True) + frame_playing = traitlets.Bool(False).tag(sync=True) + frame_loop = traitlets.Bool(True).tag(sync=True) + frame_fps = traitlets.Float(5.0).tag(sync=True) + frame_reverse = traitlets.Bool(False).tag(sync=True) + frame_boomerang = traitlets.Bool(False).tag(sync=True) + + # Export (GIF) + _gif_export_requested = traitlets.Bool(False).tag(sync=True) + _gif_data = traitlets.Bytes(b"").tag(sync=True) + _gif_metadata_json = traitlets.Unicode("").tag(sync=True) + + # Line Profile (for DP panel) + profile_line = traitlets.List(traitlets.Dict()).tag(sync=True) + profile_width = traitlets.Int(1).tag(sync=True) + + # ========================================================================= + # Tool visibility / locking + # ========================================================================= + disabled_tools = traitlets.List(traitlets.Unicode()).tag(sync=True) + hidden_tools = traitlets.List(traitlets.Unicode()).tag(sync=True) + + @classmethod + def _normalize_tool_groups(cls, tool_groups) -> list[str]: + return normalize_tool_groups("Show4DSTEM", tool_groups) + + @classmethod + def _build_disabled_tools( + cls, + disabled_tools=None, + disable_display: bool = False, + disable_histogram: bool = False, + disable_stats: bool = False, + disable_navigation: bool = False, + disable_playback: bool = False, + disable_view: bool = False, + disable_export: bool = False, + disable_roi: bool = False, + disable_profile: bool = False, + disable_fft: bool = False, + disable_virtual: bool = False, + disable_frame: bool = False, + disable_all: bool = False, + ) -> list[str]: + return build_tool_groups( + "Show4DSTEM", + tool_groups=disabled_tools, + all_flag=disable_all, + flag_map={ + "display": disable_display, + "histogram": disable_histogram, + "stats": disable_stats, + "navigation": disable_navigation, + "playback": disable_playback, + "view": disable_view, + "export": disable_export, + "roi": disable_roi, + "profile": disable_profile, + "fft": disable_fft, + "virtual": disable_virtual, + "frame": disable_frame, + }, + ) + + @classmethod + def _build_hidden_tools( + cls, + hidden_tools=None, + hide_display: bool = False, + hide_histogram: bool = False, + hide_stats: bool = False, + hide_navigation: bool = False, + hide_playback: bool = False, + hide_view: bool = False, + hide_export: bool = False, + hide_roi: bool = False, + hide_profile: bool = False, + hide_fft: bool = False, + hide_virtual: bool = False, + hide_frame: bool = False, + hide_all: bool = False, + ) -> list[str]: + return build_tool_groups( + "Show4DSTEM", + tool_groups=hidden_tools, + all_flag=hide_all, + flag_map={ + "display": hide_display, + "histogram": hide_histogram, + "stats": hide_stats, + "navigation": hide_navigation, + "playback": hide_playback, + "view": hide_view, + "export": hide_export, + "roi": hide_roi, + "profile": hide_profile, + "fft": hide_fft, + "virtual": hide_virtual, + "frame": hide_frame, + }, + ) + + @traitlets.validate("disabled_tools") + def _validate_disabled_tools(self, proposal): + return self._normalize_tool_groups(proposal["value"]) + + @traitlets.validate("hidden_tools") + def _validate_hidden_tools(self, proposal): + return self._normalize_tool_groups(proposal["value"]) + + def __init__( + self, + data: "Dataset4dstem | np.ndarray", + scan_shape: tuple[int, int] | None = None, + pixel_size: float | None = None, + k_pixel_size: float | None = None, + center: tuple[float, float] | None = None, + bf_radius: float | None = None, + precompute_virtual_images: bool = False, + frame_dim_label: str | None = None, + frame_labels: list[str] | None = None, + title: str = "", + disabled_tools: list[str] | None = None, + disable_display: bool = False, + disable_histogram: bool = False, + disable_stats: bool = False, + disable_navigation: bool = False, + disable_playback: bool = False, + disable_view: bool = False, + disable_export: bool = False, + disable_roi: bool = False, + disable_profile: bool = False, + disable_fft: bool = False, + disable_virtual: bool = False, + disable_frame: bool = False, + disable_all: bool = False, + hidden_tools: list[str] | None = None, + hide_display: bool = False, + hide_histogram: bool = False, + hide_stats: bool = False, + hide_navigation: bool = False, + hide_playback: bool = False, + hide_view: bool = False, + hide_export: bool = False, + hide_roi: bool = False, + hide_profile: bool = False, + hide_fft: bool = False, + hide_virtual: bool = False, + hide_frame: bool = False, + hide_all: bool = False, + show_fft: bool = False, + fft_window: bool = True, + show_controls: bool = True, + dp_vmin: float | None = None, + dp_vmax: float | None = None, + vi_vmin: float | None = None, + vi_vmax: float | None = None, + verbose: bool = True, + state=None, + **kwargs, + ): + super().__init__(**kwargs) + self.widget_version = resolve_widget_version() + _t0 = time.perf_counter() + _verbose = verbose + + _io_labels = None + + # Extract calibration from Dataset4dstem if provided + k_calibrated = False + if hasattr(data, "sampling") and hasattr(data, "array"): + # Dataset4dstem: extract calibration and array + # sampling = [scan_rows, scan_cols, det_rows, det_cols] + if not title and hasattr(data, "name") and data.name: + title = str(data.name) + units = getattr(data, "units", ["pixels"] * 4) + if pixel_size is None and units[0] in ("Å", "angstrom", "A", "nm"): + pixel_size = float(data.sampling[0]) + if units[0] == "nm": + pixel_size *= 10 # Convert nm to Å + if k_pixel_size is None and units[2] in ("mrad", "1/Å", "1/A"): + k_pixel_size = float(data.sampling[2]) + k_calibrated = True + data = data.array + + self.title = title + # Store calibration values (default to 1.0 if not provided) + self.pixel_size = pixel_size if pixel_size is not None else 1.0 + self.k_pixel_size = k_pixel_size if k_pixel_size is not None else 1.0 + self.k_calibrated = k_calibrated or (k_pixel_size is not None) + self.disabled_tools = self._build_disabled_tools( + disabled_tools=disabled_tools, + disable_display=disable_display, + disable_histogram=disable_histogram, + disable_stats=disable_stats, + disable_navigation=disable_navigation, + disable_playback=disable_playback, + disable_view=disable_view, + disable_export=disable_export, + disable_roi=disable_roi, + disable_profile=disable_profile, + disable_fft=disable_fft, + disable_virtual=disable_virtual, + disable_frame=disable_frame, + disable_all=disable_all, + ) + self.hidden_tools = self._build_hidden_tools( + hidden_tools=hidden_tools, + hide_display=hide_display, + hide_histogram=hide_histogram, + hide_stats=hide_stats, + hide_navigation=hide_navigation, + hide_playback=hide_playback, + hide_view=hide_view, + hide_export=hide_export, + hide_roi=hide_roi, + hide_profile=hide_profile, + hide_fft=hide_fft, + hide_virtual=hide_virtual, + hide_frame=hide_frame, + hide_all=hide_all, + ) + self.show_fft = show_fft + self.fft_window = fft_window + self.show_controls = show_controls + self.dp_vmin = dp_vmin + self.dp_vmax = dp_vmax + self.vi_vmin = vi_vmin + self.vi_vmax = vi_vmax + # Path animation (configured via set_path() or raster()) + self._path_points: list[tuple[int, int]] = [] + # Named user presets saved during this session + self._named_presets: dict[str, dict[str, Any]] = {} + # Session-scoped reproducibility log for all export calls + self._export_session_id = uuid4().hex + self._export_session_started_utc = datetime.now(timezone.utc).isoformat() + self._export_log: list[dict[str, Any]] = [] + # Sparse sampling state (for streaming/adaptive acquisition workflows) + self._sparse_samples: dict[tuple[int, int, int], np.ndarray] = {} + self._sparse_order: list[tuple[int, int, int]] = [] + # Convert to NumPy then PyTorch tensor using quantem device config + data_np = to_numpy(data) + device_str, _ = validate_device(None) # Get device from quantem config + self._device = torch.device(device_str) + # Remove saturated hot pixels in numpy (before any torch conversion) + saturated_value = 65535.0 if data_np.dtype == np.uint16 else 255.0 if data_np.dtype == np.uint8 else None + if data_np.dtype != np.float32: + _tc = time.perf_counter() + data_np = data_np.astype(np.float32) + if _verbose: + print(f" astype float32: {time.perf_counter() - _tc:.2f}s") + if saturated_value is not None: + data_np[data_np >= saturated_value] = 0 + # Handle dimensionality — 5D loads eagerly for instant frame switching + ndim = data_np.ndim + _tc = time.perf_counter() + if ndim == 5: + self.n_frames = data_np.shape[0] + self._scan_shape = (data_np.shape[1], data_np.shape[2]) + self._det_shape = (data_np.shape[3], data_np.shape[4]) + if data_np.size > 2**31 - 1 and device_str == "mps": + self._device = torch.device("cpu") + self._data = torch.from_numpy(data_np).to(self._device) + elif ndim == 3: + self.n_frames = 1 + if scan_shape is not None: + self._scan_shape = scan_shape + else: + n = data_np.shape[0] + side = int(n ** 0.5) + if side * side != n: + raise ValueError( + f"Cannot infer square scan_shape from N={n}. " + f"Provide scan_shape explicitly." + ) + self._scan_shape = (side, side) + self._det_shape = (data_np.shape[1], data_np.shape[2]) + # MPS backend can't handle tensors >INT_MAX elements; fall back to CPU + if data_np.size > 2**31 - 1 and device_str == "mps": + self._device = torch.device("cpu") + self._data = torch.from_numpy(data_np).to(self._device) + elif ndim == 4: + self.n_frames = 1 + self._scan_shape = (data_np.shape[0], data_np.shape[1]) + self._det_shape = (data_np.shape[2], data_np.shape[3]) + if data_np.size > 2**31 - 1 and device_str == "mps": + self._device = torch.device("cpu") + self._data = torch.from_numpy(data_np).to(self._device) + else: + raise ValueError(f"Expected 3D, 4D, or 5D array, got {ndim}D") + if _verbose: + if str(self._device) == "mps": + torch.mps.synchronize() + print(f" to {self._device}: {time.perf_counter() - _tc:.2f}s ({data_np.nbytes / 1e9:.1f} GB)") + + self.shape_rows = self._scan_shape[0] + self.shape_cols = self._scan_shape[1] + self.det_rows = self._det_shape[0] + self.det_cols = self._det_shape[1] + # Initial position at center + self.pos_row = self.shape_rows // 2 + self.pos_col = self.shape_cols // 2 + # Frame dimension label (for 5D time/tilt series UI) + self.frame_dim_label = frame_dim_label if frame_dim_label is not None else "Frame" + # Per-frame labels: explicit param > inferred > empty + resolved_labels = frame_labels or _io_labels or [] + self._frame_labels = resolved_labels + if resolved_labels: + self.frame_labels = list(resolved_labels) + # Histogram axis range — first frame is enough (JS does per-frame percentile clipping) + first_frame = self._data[0] if self._data.ndim == 5 else self._data + self.dp_global_min = max(float(first_frame.min()), MIN_LOG_VALUE) + self.dp_global_max = float(first_frame.max()) + # Cache coordinate tensors for mask creation (avoid repeated torch.arange) + self._det_row_coords = torch.arange(self.det_rows, device=self._device, dtype=torch.float32)[:, None] + self._det_col_coords = torch.arange(self.det_cols, device=self._device, dtype=torch.float32)[None, :] + self._scan_row_coords = torch.arange(self.shape_rows, device=self._device, dtype=torch.float32)[:, None] + self._scan_col_coords = torch.arange(self.shape_cols, device=self._device, dtype=torch.float32)[None, :] + self._sparse_mask = np.zeros((self.n_frames, self.shape_rows, self.shape_cols), dtype=bool) + self._dose_map = np.zeros((self.n_frames, self.shape_rows, self.shape_cols), dtype=np.float32) + # Setup center and BF radius + det_size = min(self.det_rows, self.det_cols) + if center is not None and bf_radius is not None: + self.center_row = float(center[0]) + self.center_col = float(center[1]) + self.bf_radius = float(bf_radius) + elif center is not None: + self.center_row = float(center[0]) + self.center_col = float(center[1]) + self.bf_radius = det_size * DEFAULT_BF_RATIO + elif bf_radius is not None: + self.center_col = float(self.det_cols / 2) + self.center_row = float(self.det_rows / 2) + self.bf_radius = float(bf_radius) + else: + # Neither provided - auto-detect from data + # Set defaults first (will be overwritten by auto-detect) + self.center_col = float(self.det_cols / 2) + self.center_row = float(self.det_rows / 2) + self.bf_radius = det_size * DEFAULT_BF_RATIO + # Auto-detect center and bf_radius from the data + _tc = time.perf_counter() + self.auto_detect_center(update_roi=False) + if _verbose: + print(f" auto_detect_center: {time.perf_counter() - _tc:.2f}s") + + # Pre-compute and cache common virtual images (BF, ABF, ADF) + # Each cache stores (bytes, stats) tuple + self._cached_bf_virtual = None + self._cached_abf_virtual = None + self._cached_adf_virtual = None + if precompute_virtual_images and self.n_frames == 1: + self._precompute_common_virtual_images() + + # Update frame when position changes (scale/colormap handled in JS) + self.observe(self._update_frame, names=["pos_row", "pos_col"]) + # Observe individual ROI params + self.observe(self._on_roi_change, names=[ + "roi_center_col", "roi_center_row", "roi_radius", "roi_radius_inner", + "roi_active", "roi_mode", "roi_width", "roi_height" + ]) + # Observe compound roi_center for batched updates from JS + self.observe(self._on_roi_center_change, names=["roi_center"]) + + # Initialize default ROI at BF center — batch to avoid redundant observer callbacks + with self.hold_trait_notifications(): + self.roi_center_col = self.center_col + self.roi_center_row = self.center_row + self.roi_center = [self.center_row, self.center_col] + self.roi_radius = self.bf_radius * 0.5 # Start with half BF radius + self.roi_active = True + + # Compute initial virtual image and frame (once, after all ROI traits are set) + _tc = time.perf_counter() + self._compute_virtual_image_from_roi() + self._update_frame() + if _verbose: + print(f" virtual image + frame: {time.perf_counter() - _tc:.2f}s") + + # Path animation: observe index changes from frontend + self.observe(self._on_path_index_change, names=["path_index"]) + self.observe(self._on_gif_export, names=["_gif_export_requested"]) + + # Frame animation (5D): observe frame_idx changes from frontend + self.observe(self._on_frame_idx_change, names=["frame_idx"]) + + # Auto-detect trigger: observe changes from frontend + self.observe(self._on_auto_detect_trigger, names=["auto_detect_trigger"]) + + # VI ROI: observe changes for summed DP computation + # Initialize VI ROI center to scan center with reasonable default sizes + self.vi_roi_center_row = float(self.shape_rows / 2) + self.vi_roi_center_col = float(self.shape_cols / 2) + # Set initial ROI size based on scan dimension + default_roi_size = max(3, min(self.shape_rows, self.shape_cols) * DEFAULT_VI_ROI_RATIO) + self.vi_roi_radius = float(default_roi_size) + self.vi_roi_width = float(default_roi_size * 2) + self.vi_roi_height = float(default_roi_size) + self.observe(self._on_vi_roi_change, names=[ + "vi_roi_mode", "vi_roi_center_row", "vi_roi_center_col", + "vi_roi_radius", "vi_roi_width", "vi_roi_height" + ]) + + if state is not None: + if isinstance(state, (str, pathlib.Path)): + state = unwrap_state_payload( + json.loads(pathlib.Path(state).read_text()), + require_envelope=True, + ) + else: + state = unwrap_state_payload(state) + self.load_state_dict(state) + + if _verbose: + shape = "x".join(str(s) for s in self._data.shape) + print(f"Show4DSTEM: {shape} {self._device}, {time.perf_counter() - _t0:.2f}s total") + + def set_image(self, data, scan_shape=None): + """Replace the 4D-STEM data. Preserves all display and ROI settings.""" + if hasattr(data, "sampling") and hasattr(data, "array"): + data = data.array + data_np = to_numpy(data) + saturated_value = 65535.0 if data_np.dtype == np.uint16 else 255.0 if data_np.dtype == np.uint8 else None + if data_np.dtype != np.float32: + data_np = data_np.astype(np.float32) + if saturated_value is not None: + data_np[data_np >= saturated_value] = 0 + if data_np.ndim == 5: + self.n_frames = data_np.shape[0] + self._scan_shape = (data_np.shape[1], data_np.shape[2]) + self._det_shape = (data_np.shape[3], data_np.shape[4]) + if data_np.size > 2**31 - 1 and str(self._device) == "mps": + self._device = torch.device("cpu") + self._data = torch.from_numpy(data_np).to(self._device) + elif data_np.ndim == 3: + self.n_frames = 1 + if scan_shape is not None: + self._scan_shape = scan_shape + else: + n = data_np.shape[0] + side = int(n ** 0.5) + if side * side != n: + raise ValueError(f"Cannot infer square scan_shape from N={n}. Provide scan_shape explicitly.") + self._scan_shape = (side, side) + self._det_shape = (data_np.shape[1], data_np.shape[2]) + self._data = torch.from_numpy(data_np).to(self._device) + elif data_np.ndim == 4: + self.n_frames = 1 + self._scan_shape = (data_np.shape[0], data_np.shape[1]) + self._det_shape = (data_np.shape[2], data_np.shape[3]) + self._data = torch.from_numpy(data_np).to(self._device) + else: + raise ValueError(f"Expected 3D, 4D, or 5D array, got {data_np.ndim}D") + self.frame_idx = 0 + self.shape_rows = self._scan_shape[0] + self.shape_cols = self._scan_shape[1] + self.det_rows = self._det_shape[0] + self.det_cols = self._det_shape[1] + first_frame = self._data[0] if self._data.ndim == 5 else self._data + self.dp_global_min = max(float(first_frame.min()), MIN_LOG_VALUE) + self.dp_global_max = float(first_frame.max()) + self._det_row_coords = torch.arange(self.det_rows, device=self._device, dtype=torch.float32)[:, None] + self._det_col_coords = torch.arange(self.det_cols, device=self._device, dtype=torch.float32)[None, :] + self._scan_row_coords = torch.arange(self.shape_rows, device=self._device, dtype=torch.float32)[:, None] + self._scan_col_coords = torch.arange(self.shape_cols, device=self._device, dtype=torch.float32)[None, :] + self._sparse_mask = np.zeros((self.n_frames, self.shape_rows, self.shape_cols), dtype=bool) + self._dose_map = np.zeros((self.n_frames, self.shape_rows, self.shape_cols), dtype=np.float32) + self._sparse_samples = {} + self._sparse_order = [] + self._cached_bf_virtual = None + self._cached_abf_virtual = None + self._cached_adf_virtual = None + with self.hold_trait_notifications(): + self.pos_row = min(self.pos_row, self.shape_rows - 1) + self.pos_col = min(self.pos_col, self.shape_cols - 1) + self._compute_virtual_image_from_roi() + self._update_frame() + + def __repr__(self) -> str: + k_unit = "mrad" if self.k_calibrated else "px" + shape = ( + f"({self.n_frames}, {self.shape_rows}, {self.shape_cols}, {self.det_rows}, {self.det_cols})" + if self.n_frames > 1 + else f"({self.shape_rows}, {self.shape_cols}, {self.det_rows}, {self.det_cols})" + ) + frame_info = f", {self.frame_dim_label.lower()}={self.frame_idx}" if self.n_frames > 1 else "" + title_info = f", title='{self.title}'" if self.title else "" + return ( + f"Show4DSTEM(shape={shape}, " + f"sampling=({self.pixel_size} Å, {self.k_pixel_size} {k_unit}), " + f"pos=({self.pos_row}, {self.pos_col}){frame_info}{title_info})" + ) + + def state_dict(self): + return { + "title": self.title, + "pos_row": self.pos_row, + "pos_col": self.pos_col, + "pixel_size": self.pixel_size, + "k_pixel_size": self.k_pixel_size, + "k_calibrated": self.k_calibrated, + "center_row": self.center_row, + "center_col": self.center_col, + "bf_radius": self.bf_radius, + "roi_active": self.roi_active, + "roi_mode": self.roi_mode, + "roi_center_row": self.roi_center_row, + "roi_center_col": self.roi_center_col, + "roi_radius": self.roi_radius, + "roi_radius_inner": self.roi_radius_inner, + "roi_width": self.roi_width, + "roi_height": self.roi_height, + "vi_roi_mode": self.vi_roi_mode, + "vi_roi_center_row": self.vi_roi_center_row, + "vi_roi_center_col": self.vi_roi_center_col, + "vi_roi_radius": self.vi_roi_radius, + "vi_roi_width": self.vi_roi_width, + "vi_roi_height": self.vi_roi_height, + "mask_dc": self.mask_dc, + "dp_colormap": self.dp_colormap, + "vi_colormap": self.vi_colormap, + "fft_colormap": self.fft_colormap, + "dp_scale_mode": self.dp_scale_mode, + "vi_scale_mode": self.vi_scale_mode, + "fft_scale_mode": self.fft_scale_mode, + "dp_power_exp": self.dp_power_exp, + "vi_power_exp": self.vi_power_exp, + "fft_power_exp": self.fft_power_exp, + "dp_vmin_pct": self.dp_vmin_pct, + "dp_vmax_pct": self.dp_vmax_pct, + "vi_vmin_pct": self.vi_vmin_pct, + "vi_vmax_pct": self.vi_vmax_pct, + "fft_vmin_pct": self.fft_vmin_pct, + "fft_vmax_pct": self.fft_vmax_pct, + "dp_vmin": self.dp_vmin, + "dp_vmax": self.dp_vmax, + "vi_vmin": self.vi_vmin, + "vi_vmax": self.vi_vmax, + "fft_auto": self.fft_auto, + "show_fft": self.show_fft, + "fft_window": self.fft_window, + "show_controls": self.show_controls, + "dp_show_colorbar": self.dp_show_colorbar, + "export_default_view": self.export_default_view, + "export_default_format": self.export_default_format, + "export_include_overlays": self.export_include_overlays, + "export_include_scalebar": self.export_include_scalebar, + "export_default_dpi": self.export_default_dpi, + "path_interval_ms": self.path_interval_ms, + "path_loop": self.path_loop, + "profile_line": self.profile_line, + "profile_width": self.profile_width, + "frame_idx": self.frame_idx, + "frame_dim_label": self.frame_dim_label, + "frame_labels": list(self.frame_labels), + "frame_loop": self.frame_loop, + "frame_fps": self.frame_fps, + "frame_reverse": self.frame_reverse, + "frame_boomerang": self.frame_boomerang, + "disabled_tools": self.disabled_tools, + "hidden_tools": self.hidden_tools, + } + + def save(self, path: str): + save_state_file(path, "Show4DSTEM", self.state_dict()) + + def load_state_dict(self, state): + allowed_keys = set(self.state_dict().keys()) + pending_pos_row = state.get("pos_row", None) + pending_pos_col = state.get("pos_col", None) + pending_frame_idx = state.get("frame_idx", None) + for key, val in state.items(): + if key in {"pos_row", "pos_col", "frame_idx"}: + continue + if key in allowed_keys: + setattr(self, key, val) + if pending_frame_idx is not None: + self.frame_idx = int(max(0, min(int(pending_frame_idx), self.n_frames - 1))) + if pending_pos_row is not None or pending_pos_col is not None: + row = int(self.pos_row if pending_pos_row is None else pending_pos_row) + col = int(self.pos_col if pending_pos_col is None else pending_pos_col) + self.pos_row = int(max(0, min(row, self.shape_rows - 1))) + self.pos_col = int(max(0, min(col, self.shape_cols - 1))) + + def free(self): + """Free GPU memory held by this widget. + + Deletes the internal data tensor, runs garbage collection, and + flushes the MPS allocator cache. Call this before loading a new + dataset to avoid running out of GPU memory. + + Examples + -------- + >>> w.free() # release ~9 GB of MPS memory + >>> del result # free the source numpy array + """ + import gc + + device = str(self._device) if hasattr(self, "_device") else "" + nbytes = self._data.nbytes if hasattr(self._data, "nbytes") else 0 + self._data = None + gc.collect() + if device == "mps": + try: + import torch + torch.mps.empty_cache() + except Exception: + pass + elif device.startswith("cuda"): + try: + import torch + torch.cuda.empty_cache() + except Exception: + pass + if nbytes > 0: + print(f"freed {_format_memory(nbytes)} ({device})") + + def summary(self): + name = self.title if self.title else "Show4DSTEM" + lines = [name, "═" * 32] + if self.n_frames > 1: + parts = [f"{self.n_frames} ({self.frame_dim_label}), current: {self.frame_idx}"] + parts.append(f"{self.frame_fps} fps") + if self.frame_loop: + parts.append("loop") + if self.frame_reverse: + parts.append("reverse") + if self.frame_boomerang: + parts.append("bounce") + lines.append(f"Frames: {' | '.join(parts)}") + if self._frame_labels: + if len(self._frame_labels) <= 4: + lines.append(f"Labels: {self._frame_labels}") + else: + lines.append(f"Labels: {self._frame_labels[:3]} ... ({len(self._frame_labels)} total)") + lines.append(f"Scan: {self.shape_rows}×{self.shape_cols} ({self.pixel_size:.2f} Å/px)") + k_unit = "mrad" if self.k_calibrated else "px" + lines.append(f"Detector: {self.det_rows}×{self.det_cols} ({self.k_pixel_size:.4f} {k_unit}/px)") + lines.append(f"Position: ({self.pos_row}, {self.pos_col})") + lines.append(f"Center: ({self.center_row:.1f}, {self.center_col:.1f}) BF r={self.bf_radius:.1f} px") + display_parts = [] + if self.mask_dc: + display_parts.append("DC masked") + lines.append(f"Display: {', '.join(display_parts) if display_parts else 'default'}") + if self.roi_active: + lines.append(f"ROI: {self.roi_mode} at ({self.roi_center_row:.1f}, {self.roi_center_col:.1f}) r={self.roi_radius:.1f}") + if self.vi_roi_mode != "off": + lines.append(f"VI ROI: {self.vi_roi_mode} at ({self.vi_roi_center_row:.1f}, {self.vi_roi_center_col:.1f}) r={self.vi_roi_radius:.1f}") + dp_contrast = f"{self.dp_vmin_pct:.1f}-{self.dp_vmax_pct:.1f}%" + if self.dp_vmin is not None and self.dp_vmax is not None: + dp_contrast += f", dp_vmin={self.dp_vmin:.4g}, dp_vmax={self.dp_vmax:.4g}" + lines.append( + f"DP view: {self.dp_colormap}, {self.dp_scale_mode}, {dp_contrast}" + ) + vi_contrast = f"{self.vi_vmin_pct:.1f}-{self.vi_vmax_pct:.1f}%" + if self.vi_vmin is not None and self.vi_vmax is not None: + vi_contrast += f", vi_vmin={self.vi_vmin:.4g}, vi_vmax={self.vi_vmax:.4g}" + lines.append( + f"VI view: {self.vi_colormap}, {self.vi_scale_mode}, {vi_contrast}" + ) + if self.show_fft: + fft_parts = [f"{self.fft_colormap}, {self.fft_scale_mode}, {self.fft_vmin_pct:.1f}-{self.fft_vmax_pct:.1f}%, auto={self.fft_auto}"] + if not self.fft_window: + fft_parts.append("no window") + lines.append(f"FFT view: {', '.join(fft_parts)}") + if self.profile_line and len(self.profile_line) == 2: + p0, p1 = self.profile_line[0], self.profile_line[1] + lines.append(f"Profile: ({p0['row']:.0f}, {p0['col']:.0f}) -> ({p1['row']:.0f}, {p1['col']:.0f}) width={self.profile_width}") + if self.disabled_tools: + lines.append(f"Locked: {', '.join(self.disabled_tools)}") + if self.hidden_tools: + lines.append(f"Hidden: {', '.join(self.hidden_tools)}") + print("\n".join(lines)) + + # ========================================================================= + # Convenience Properties + # ========================================================================= + + @property + def position(self) -> tuple[int, int]: + """Current scan position as (row, col) tuple.""" + return (self.pos_row, self.pos_col) + + @position.setter + def position(self, value: tuple[int, int]) -> None: + """Set scan position from (row, col) tuple.""" + self.pos_row, self.pos_col = value + + @property + def scan_shape(self) -> tuple[int, int]: + """Scan dimensions as (rows, cols) tuple.""" + return (self.shape_rows, self.shape_cols) + + @property + def detector_shape(self) -> tuple[int, int]: + """Detector dimensions as (rows, cols) tuple.""" + return (self.det_rows, self.det_cols) + + @property + def _frame_data(self) -> torch.Tensor: + """Per-frame data (4D or 3D flattened), accounting for 5D time/tilt series.""" + if self.n_frames > 1: + return self._data[self.frame_idx] + return self._data + + # ========================================================================= + # Line Profile + # ========================================================================= + + def set_profile(self, start: tuple, end: tuple) -> Self: + row0, col0 = start + row1, col1 = end + self.profile_line = [ + {"row": float(row0), "col": float(col0)}, + {"row": float(row1), "col": float(col1)}, + ] + return self + + def clear_profile(self) -> Self: + self.profile_line = [] + return self + + @property + def profile(self) -> list[tuple[float, float]]: + if len(self.profile_line) == 2: + p0, p1 = self.profile_line[0], self.profile_line[1] + return [(p0["row"], p0["col"]), (p1["row"], p1["col"])] + return [] + + @property + def profile_values(self): + if len(self.profile_line) != 2: + return None + p0, p1 = self.profile_line[0], self.profile_line[1] + frame = self._get_frame(self.pos_row, self.pos_col) + return self._sample_line(frame, p0["row"], p0["col"], p1["row"], p1["col"]) + + @property + def profile_distance(self) -> float: + if len(self.profile_line) != 2: + return 0.0 + p0, p1 = self.profile_line[0], self.profile_line[1] + dist_px = np.sqrt((p1["row"] - p0["row"]) ** 2 + (p1["col"] - p0["col"]) ** 2) + if self.k_calibrated: + return float(dist_px * self.k_pixel_size) + return float(dist_px) + + def _sample_line(self, frame, row0, col0, row1, col1): + h, w = frame.shape[:2] + dc = col1 - col0 + dr = row1 - row0 + length = np.sqrt(dc * dc + dr * dr) + n = max(2, int(np.ceil(length))) + t = np.linspace(0.0, 1.0, n) + c = col0 + t * dc + r = row0 + t * dr + ci = np.floor(c).astype(np.intp) + ri = np.floor(r).astype(np.intp) + cf = c - ci + rf = r - ri + c0 = np.clip(ci, 0, w - 1) + c1 = np.clip(ci + 1, 0, w - 1) + r0 = np.clip(ri, 0, h - 1) + r1 = np.clip(ri + 1, 0, h - 1) + return ( + frame[r0, c0] * (1 - cf) * (1 - rf) + + frame[r0, c1] * cf * (1 - rf) + + frame[r1, c0] * (1 - cf) * rf + + frame[r1, c1] * cf * rf + ).astype(np.float32) + + # ========================================================================= + # Path Animation Methods + # ========================================================================= + + def set_path( + self, + points: list[tuple[int, int]], + interval_ms: int = 100, + loop: bool = True, + autoplay: bool = True, + ) -> Self: + """ + Set a custom path of scan positions to animate through. + + Parameters + ---------- + points : list[tuple[int, int]] + List of (row, col) scan positions to visit. + interval_ms : int, default 100 + Time between frames in milliseconds. + loop : bool, default True + Whether to loop when reaching end. + autoplay : bool, default True + Start playing immediately. + + Returns + ------- + Show4DSTEM + Self for method chaining. + + Examples + -------- + >>> widget.set_path([(0, 0), (10, 10), (20, 20), (30, 30)]) + >>> widget.set_path([(i, i) for i in range(48)], interval_ms=50) + """ + self._path_points = list(points) + self.path_length = len(self._path_points) + self.path_index = 0 + self.path_interval_ms = interval_ms + self.path_loop = loop + if autoplay and self.path_length > 0: + self.path_playing = True + return self + + def play(self) -> Self: + """Start playing the path animation.""" + if self.path_length > 0: + self.path_playing = True + return self + + def pause(self) -> Self: + """Pause the path animation.""" + self.path_playing = False + return self + + def stop(self) -> Self: + """Stop and reset path animation to beginning.""" + self.path_playing = False + self.path_index = 0 + return self + + def goto(self, index: int) -> Self: + """Jump to a specific index in the path.""" + if 0 <= index < self.path_length: + self.path_index = index + return self + + def _on_path_index_change(self, change): + """Called when path_index changes (from frontend timer).""" + idx = change["new"] + if 0 <= idx < len(self._path_points): + row, col = self._path_points[idx] + # Clamp to valid range + self.pos_row = max(0, min(self.shape_rows - 1, row)) + self.pos_col = max(0, min(self.shape_cols - 1, col)) + + def _on_auto_detect_trigger(self, change): + """Called when auto_detect_trigger is set to True from frontend.""" + if change["new"]: + self.auto_detect_center() + # Reset trigger to allow re-triggering + self.auto_detect_trigger = False + + def _on_frame_idx_change(self, change=None): + """Called when frame_idx changes (5D time/tilt series). + + Recomputes virtual image and diffraction pattern for the new frame. + Invalidates precomputed caches since they are per-frame. + """ + if self.n_frames <= 1: + return + # Invalidate precomputed caches (they were for a different frame) + self._cached_bf_virtual = None + self._cached_abf_virtual = None + self._cached_adf_virtual = None + # Recompute virtual image and displayed frame + self._compute_virtual_image_from_roi() + self._update_frame() + # Recompute summed DP if VI ROI is active + if self.vi_roi_mode != "off": + self._compute_summed_dp_from_vi_roi() + + # ========================================================================= + # Path Animation Patterns + # ========================================================================= + + def raster( + self, + step: int = 1, + bidirectional: bool = False, + interval_ms: int = 100, + loop: bool = True, + ) -> Self: + """ + Play a raster scan path (row by row, left to right). + + This mimics real STEM scanning: left→right, step down, left→right, etc. + + Parameters + ---------- + step : int, default 1 + Step size between positions. + bidirectional : bool, default False + If True, use snake/boustrophedon pattern (alternating direction). + If False (default), always scan left→right like real STEM. + interval_ms : int, default 100 + Time between frames in milliseconds. + loop : bool, default True + Whether to loop when reaching the end. + + Returns + ------- + Show4DSTEM + Self for method chaining. + """ + points = [] + for r in range(0, self.shape_rows, step): + cols = list(range(0, self.shape_cols, step)) + if bidirectional and (r // step % 2 == 1): + cols = cols[::-1] # Alternate direction for snake pattern + for c in cols: + points.append((r, c)) + return self.set_path(points=points, interval_ms=interval_ms, loop=loop) + + # ========================================================================= + # ROI Mode Methods + # ========================================================================= + + def roi_circle(self, radius: float | None = None) -> Self: + """ + Switch to circle ROI mode for virtual imaging. + + In circle mode, the virtual image integrates over a circular region + centered at the current ROI position (like a virtual bright field detector). + + Parameters + ---------- + radius : float, optional + Radius of the circle in pixels. If not provided, uses current value + or defaults to half the BF radius. + + Returns + ------- + Show4DSTEM + Self for method chaining. + + Examples + -------- + >>> widget.roi_circle(20) # 20px radius circle + >>> widget.roi_circle() # Use default radius + """ + self.roi_mode = "circle" + if radius is not None: + self.roi_radius = float(radius) + return self + + def roi_point(self) -> Self: + """ + Switch to point ROI mode (single-pixel indexing). + + In point mode, the virtual image shows intensity at the exact ROI position. + This is the default mode. + + Returns + ------- + Show4DSTEM + Self for method chaining. + """ + self.roi_mode = "point" + return self + + def roi_square(self, half_size: float | None = None) -> Self: + """ + Switch to square ROI mode for virtual imaging. + + In square mode, the virtual image integrates over a square region + centered at the current ROI position. + + Parameters + ---------- + half_size : float, optional + Half-size of the square in pixels (distance from center to edge). + A half_size of 15 creates a 30x30 pixel square. + If not provided, uses current roi_radius value. + + Returns + ------- + Show4DSTEM + Self for method chaining. + + Examples + -------- + >>> widget.roi_square(15) # 30x30 pixel square (half_size=15) + >>> widget.roi_square() # Use default size + """ + self.roi_mode = "square" + if half_size is not None: + self.roi_radius = float(half_size) + return self + + def roi_annular( + self, inner_radius: float | None = None, outer_radius: float | None = None + ) -> Self: + """ + Set ROI mode to annular (donut-shaped) for ADF/HAADF imaging. + + Parameters + ---------- + inner_radius : float, optional + Inner radius in pixels. If not provided, uses current roi_radius_inner. + outer_radius : float, optional + Outer radius in pixels. If not provided, uses current roi_radius. + + Returns + ------- + Show4DSTEM + Self for method chaining. + + Examples + -------- + >>> widget.roi_annular(20, 50) # ADF: inner=20px, outer=50px + >>> widget.roi_annular(30, 80) # HAADF: larger angles + """ + self.roi_mode = "annular" + if inner_radius is not None: + self.roi_radius_inner = float(inner_radius) + if outer_radius is not None: + self.roi_radius = float(outer_radius) + return self + + def roi_rect( + self, width: float | None = None, height: float | None = None + ) -> Self: + """ + Set ROI mode to rectangular. + + Parameters + ---------- + width : float, optional + Width in pixels. If not provided, uses current roi_width. + height : float, optional + Height in pixels. If not provided, uses current roi_height. + + Returns + ------- + Show4DSTEM + Self for method chaining. + + Examples + -------- + >>> widget.roi_rect(30, 20) # 30px wide, 20px tall + >>> widget.roi_rect(40, 40) # 40x40 rectangle + """ + self.roi_mode = "rect" + if width is not None: + self.roi_width = float(width) + if height is not None: + self.roi_height = float(height) + return self + + def auto_detect_center(self, update_roi: bool = True) -> Self: + """ + Automatically detect BF disk center and radius using centroid. + + This method analyzes the summed diffraction pattern to find the + bright field disk center and estimate its radius. The detected + values are applied to the widget's calibration (center_row, center_col, + bf_radius). + + Parameters + ---------- + update_roi : bool, default True + If True, also update ROI center and recompute cached virtual images. + Set to False during __init__ when ROI is not yet initialized. + + Returns + ------- + Show4DSTEM + Self for method chaining. + + Examples + -------- + >>> widget = Show4DSTEM(data) + >>> widget.auto_detect_center() # Auto-detect and apply + """ + # Sum all diffraction patterns to get average (PyTorch) + if self._data.ndim == 5: + summed_dp = self._data.sum(dim=(0, 1, 2)) + elif self._data.ndim == 4: + summed_dp = self._data.sum(dim=(0, 1)) + else: + summed_dp = self._data.sum(dim=0) + + # Threshold at mean + std to isolate BF disk + threshold = summed_dp.mean() + summed_dp.std() + mask = summed_dp > threshold + + # Avoid division by zero + total = mask.sum() + if total == 0: + return self + + # Calculate centroid using cached coordinate grids + cx = float((self._det_col_coords * mask).sum() / total) + cy = float((self._det_row_coords * mask).sum() / total) + + # Estimate radius from mask area (A = pi*r^2) + radius = float(torch.sqrt(total / torch.pi)) + + # Apply detected values + self.center_col = cx + self.center_row = cy + self.bf_radius = radius + + if update_roi: + # Also update ROI to center + self.roi_center_col = cx + self.roi_center_row = cy + # Recompute cached virtual images with new calibration + self._precompute_common_virtual_images() + + return self + + def _get_frame(self, row: int, col: int) -> np.ndarray: + """Get single diffraction frame at position (row, col) as numpy array.""" + if self._data is None: + return np.zeros((self.det_rows, self.det_cols), dtype=np.float32) + data = self._frame_data + if data.ndim == 3: + idx = row * self.shape_cols + col + return data[idx].cpu().numpy() + else: + return data[row, col].cpu().numpy() + + def _apply_scale_mode( + self, + data: np.ndarray, + mode: str, + power_exp: float = 0.5, + ) -> np.ndarray: + arr = np.asarray(data, dtype=np.float32) + if mode == "log": + return np.log1p(np.maximum(arr, 0.0)).astype(np.float32) + if mode == "power": + return np.power(np.maximum(arr, 0.0), float(power_exp)).astype(np.float32) + return arr.astype(np.float32) + + def _slider_range( + self, + data_min: float, + data_max: float, + vmin_pct: float, + vmax_pct: float, + ) -> tuple[float, float]: + v0 = float(max(0.0, min(100.0, vmin_pct))) + v1 = float(max(0.0, min(100.0, vmax_pct))) + if v1 < v0: + v0, v1 = v1, v0 + rng = float(data_max - data_min) + return ( + float(data_min + (v0 / 100.0) * rng), + float(data_min + (v1 / 100.0) * rng), + ) + + def _render_colormap_rgb( + self, + data: np.ndarray, + cmap_name: str, + vmin: float, + vmax: float, + ) -> np.ndarray: + from matplotlib import colormaps + + arr = np.asarray(data, dtype=np.float32) + if vmax <= vmin: + normalized = np.zeros_like(arr, dtype=np.float32) + else: + normalized = np.clip((arr - vmin) / (vmax - vmin), 0.0, 1.0) + rgba = colormaps.get_cmap(cmap_name)(normalized) + return (rgba[..., :3] * 255).astype(np.uint8) + + def _get_virtual_image_array(self) -> np.ndarray: + if not self.virtual_image_bytes: + return np.zeros((self.shape_rows, self.shape_cols), dtype=np.float32) + arr = np.frombuffer(self.virtual_image_bytes, dtype=np.float32) + expected = self.shape_rows * self.shape_cols + if arr.size != expected: + return np.zeros((self.shape_rows, self.shape_cols), dtype=np.float32) + return arr.reshape(self.shape_rows, self.shape_cols).copy() + + def _get_summed_dp_array(self) -> np.ndarray | None: + if self.vi_roi_mode == "off": + return None + self._compute_summed_dp_from_vi_roi() + if not self.summed_dp_bytes: + return None + arr = np.frombuffer(self.summed_dp_bytes, dtype=np.float32) + expected = self.det_rows * self.det_cols + if arr.size != expected: + return None + return arr.reshape(self.det_rows, self.det_cols).copy() + + def _fft_enhanced_range(self, mag: np.ndarray) -> tuple[float, float]: + arr = np.asarray(mag, dtype=np.float32).copy() + if arr.size == 0: + return 0.0, 0.0 + center_row = arr.shape[0] // 2 + center_col = arr.shape[1] // 2 + neighbors = [] + if center_col - 1 >= 0: + neighbors.append(arr[center_row, center_col - 1]) + if center_col + 1 < arr.shape[1]: + neighbors.append(arr[center_row, center_col + 1]) + if center_row - 1 >= 0: + neighbors.append(arr[center_row - 1, center_col]) + if center_row + 1 < arr.shape[0]: + neighbors.append(arr[center_row + 1, center_col]) + if neighbors: + arr[center_row, center_col] = float(np.mean(neighbors)) + dmin = float(arr.min()) + dmax = float(arr.max()) + if dmax <= dmin: + return dmin, dmax + pmax = float(np.percentile(arr, 99.9)) + if pmax <= dmin: + pmax = dmax + return dmin, pmax + + def _render_dp_rgb(self) -> tuple[np.ndarray, dict]: + summed_dp = self._get_summed_dp_array() + if summed_dp is not None: + raw = summed_dp + source = "summed_dp" + else: + raw = self._get_frame(self.pos_row, self.pos_col).astype(np.float32) + source = "single_frame" + + scale_mode = self.dp_scale_mode + scaled = self._apply_scale_mode(raw, scale_mode, self.dp_power_exp) + data_min = float(scaled.min()) if scaled.size else 0.0 + data_max = float(scaled.max()) if scaled.size else 0.0 + if self.dp_vmin is not None and self.dp_vmax is not None: + vmin = float(self._apply_scale_mode( + np.array([max(self.dp_vmin, 0)], dtype=np.float32), scale_mode, self.dp_power_exp + )[0]) + vmax = float(self._apply_scale_mode( + np.array([max(self.dp_vmax, 0)], dtype=np.float32), scale_mode, self.dp_power_exp + )[0]) + else: + vmin, vmax = self._slider_range(data_min, data_max, self.dp_vmin_pct, self.dp_vmax_pct) + rgb = self._render_colormap_rgb(scaled, self.dp_colormap, vmin, vmax) + metadata = { + "source": source, + "colormap": self.dp_colormap, + "scale_mode": scale_mode, + "vmin_pct": float(self.dp_vmin_pct), + "vmax_pct": float(self.dp_vmax_pct), + "vmin": float(vmin), + "vmax": float(vmax), + } + return rgb, metadata + + def _render_virtual_rgb(self) -> tuple[np.ndarray, dict]: + raw = self._get_virtual_image_array() + scaled = self._apply_scale_mode(raw, self.vi_scale_mode, self.vi_power_exp) + data_min = float(scaled.min()) if scaled.size else 0.0 + data_max = float(scaled.max()) if scaled.size else 0.0 + if self.vi_vmin is not None and self.vi_vmax is not None: + vmin = float(self._apply_scale_mode( + np.array([max(self.vi_vmin, 0)], dtype=np.float32), self.vi_scale_mode, self.vi_power_exp + )[0]) + vmax = float(self._apply_scale_mode( + np.array([max(self.vi_vmax, 0)], dtype=np.float32), self.vi_scale_mode, self.vi_power_exp + )[0]) + else: + vmin, vmax = self._slider_range(data_min, data_max, self.vi_vmin_pct, self.vi_vmax_pct) + rgb = self._render_colormap_rgb(scaled, self.vi_colormap, vmin, vmax) + metadata = { + "colormap": self.vi_colormap, + "scale_mode": self.vi_scale_mode, + "vmin_pct": float(self.vi_vmin_pct), + "vmax_pct": float(self.vi_vmax_pct), + "vmin": float(vmin), + "vmax": float(vmax), + } + return rgb, metadata + + def _render_fft_rgb(self) -> tuple[np.ndarray, dict]: + virtual_raw = self._get_virtual_image_array() + fft = np.fft.fftshift(np.fft.fft2(virtual_raw)) + mag = np.abs(fft).astype(np.float32) + scaled = self._apply_scale_mode(mag, self.fft_scale_mode, self.fft_power_exp) + if self.fft_auto: + display_min, display_max = self._fft_enhanced_range(scaled) + else: + display_min = float(scaled.min()) if scaled.size else 0.0 + display_max = float(scaled.max()) if scaled.size else 0.0 + vmin, vmax = self._slider_range(display_min, display_max, self.fft_vmin_pct, self.fft_vmax_pct) + rgb = self._render_colormap_rgb(scaled, self.fft_colormap, vmin, vmax) + metadata = { + "colormap": self.fft_colormap, + "scale_mode": self.fft_scale_mode, + "auto": bool(self.fft_auto), + "vmin_pct": float(self.fft_vmin_pct), + "vmax_pct": float(self.fft_vmax_pct), + "vmin": float(vmin), + "vmax": float(vmax), + } + return rgb, metadata + + def list_export_views(self) -> tuple[str, ...]: + return ("diffraction", "virtual", "fft", "all") + + def list_export_formats(self) -> tuple[str, ...]: + return ("png", "pdf") + + def list_figure_templates(self) -> tuple[str, ...]: + return ("dp_vi", "dp_vi_fft", "publication_dp_vi", "publication_dp_vi_fft") + + def list_presets(self) -> tuple[str, ...]: + builtin = ("bf", "abf", "adf", "haadf") + custom = tuple(sorted(self._named_presets.keys())) + return builtin + custom + + def _validate_export_view(self, view: str | None) -> str: + candidate = self.export_default_view if view is None else str(view) + view_key = str(candidate).strip().lower() + allowed = self.list_export_views() + if view_key not in allowed: + raise ValueError( + f"Unsupported view '{view}'. Supported: {', '.join(allowed)}" + ) + return view_key + + def _validate_frame_idx(self, frame_idx: int | None) -> int: + if frame_idx is None: + return int(self.frame_idx) + idx = int(frame_idx) + if idx < 0 or idx >= self.n_frames: + raise ValueError( + f"frame_idx={idx} is out of range [0, {self.n_frames - 1}]" + ) + return idx + + def _validate_position(self, position: tuple[int, int] | None) -> tuple[int, int]: + if position is None: + return int(self.pos_row), int(self.pos_col) + if len(position) != 2: + raise ValueError( + "position must be a (row, col) tuple with exactly two values" + ) + row = int(position[0]) + col = int(position[1]) + if row < 0 or row >= self.shape_rows or col < 0 or col >= self.shape_cols: + raise ValueError( + f"position=({row}, {col}) is out of range for " + f"scan_shape=({self.shape_rows}, {self.shape_cols})" + ) + return row, col + + def _resolve_export_format( + self, + path: pathlib.Path, + fmt: str | None, + ) -> str: + if fmt is not None and str(fmt).strip(): + resolved = str(fmt).strip().lower() + else: + from_path = path.suffix.lstrip(".").lower() + resolved = from_path if from_path else str(self.export_default_format).strip().lower() + allowed = self.list_export_formats() + if resolved not in allowed: + raise ValueError( + f"Unsupported format '{resolved}'. Supported: {', '.join(allowed)}" + ) + return resolved + + @staticmethod + def _round_to_nice_value(value: float) -> float: + if value <= 0: + return 1.0 + magnitude = 10 ** math.floor(math.log10(value)) + normalized = value / magnitude + if normalized < 1.5: + return float(magnitude) + if normalized < 3.5: + return float(2 * magnitude) + if normalized < 7.5: + return float(5 * magnitude) + return float(10 * magnitude) + + def _format_scale_label(self, value: float, unit: str) -> str: + nice = self._round_to_nice_value(value) + if unit == "Å": + if nice >= 10: + return f"{int(round(nice / 10))} nm" + if nice >= 1: + return f"{int(round(nice))} Å" + return f"{nice:.2f} Å" + if unit == "mrad": + if nice >= 1000: + return f"{int(round(nice / 1000))} rad" + if nice >= 1: + return f"{int(round(nice))} mrad" + return f"{nice:.2f} mrad" + if nice >= 1: + return f"{int(round(nice))} px" + return f"{nice:.1f} px" + + @staticmethod + def _draw_crosshair(draw, x: float, y: float, size: float, color, width: int) -> None: + draw.line([(x - size, y), (x + size, y)], fill=color, width=width) + draw.line([(x, y - size), (x, y + size)], fill=color, width=width) + + def _draw_scalebar_overlay(self, image, pixel_size: float, unit: str) -> None: + from PIL import ImageDraw, ImageFont + + if pixel_size <= 0: + return + + draw = ImageDraw.Draw(image, mode="RGBA") + font = ImageFont.load_default() + width, height = image.size + margin = max(8, int(min(width, height) * 0.04)) + thickness = max(2, int(height * 0.01)) + target_bar_px = max(36, int(width * 0.15)) + target_physical = float(target_bar_px) * float(pixel_size) + nice_physical = self._round_to_nice_value(target_physical) + bar_px = max(12, int(round(nice_physical / float(pixel_size)))) + bar_px = min(bar_px, max(12, int(width * 0.8))) + + x1 = width - margin + x0 = x1 - bar_px + y1 = height - margin + y0 = y1 - thickness + + draw.rectangle([(x0 + 1, y0 + 1), (x1 + 1, y1 + 1)], fill=(0, 0, 0, 180)) + draw.rectangle([(x0, y0), (x1, y1)], fill=(255, 255, 255, 255)) + + label = self._format_scale_label(nice_physical, unit) + label_bbox = draw.textbbox((0, 0), label, font=font) + label_w = label_bbox[2] - label_bbox[0] + label_h = label_bbox[3] - label_bbox[1] + tx = x0 + (bar_px - label_w) / 2 + ty = y0 - label_h - 4 + draw.text((tx + 1, ty + 1), label, fill=(0, 0, 0, 220), font=font) + draw.text((tx, ty), label, fill=(255, 255, 255, 255), font=font) + + zoom_label = "1.0x" + zoom_bbox = draw.textbbox((0, 0), zoom_label, font=font) + zoom_h = zoom_bbox[3] - zoom_bbox[1] + zx = margin + zy = height - margin - zoom_h + draw.text((zx + 1, zy + 1), zoom_label, fill=(0, 0, 0, 220), font=font) + draw.text((zx, zy), zoom_label, fill=(255, 255, 255, 255), font=font) + + def _draw_dp_overlays(self, image) -> None: + from PIL import ImageDraw + + draw = ImageDraw.Draw(image, mode="RGBA") + width, height = image.size + scale_x = float(width) / float(max(1, self.det_cols)) + scale_y = float(height) / float(max(1, self.det_rows)) + cx = float(self.roi_center_col) * scale_x + cy = float(self.roi_center_row) * scale_y + + if self.roi_active and self.roi_mode != "point": + stroke = (0, 220, 0, 240) + fill = (0, 220, 0, 45) + if self.roi_mode == "circle": + rx = float(self.roi_radius) * scale_x + ry = float(self.roi_radius) * scale_y + draw.ellipse([(cx - rx, cy - ry), (cx + rx, cy + ry)], outline=stroke, fill=fill, width=2) + elif self.roi_mode == "square": + rx = float(self.roi_radius) * scale_x + ry = float(self.roi_radius) * scale_y + draw.rectangle([(cx - rx, cy - ry), (cx + rx, cy + ry)], outline=stroke, fill=fill, width=2) + elif self.roi_mode == "rect": + rx = (float(self.roi_width) / 2.0) * scale_x + ry = (float(self.roi_height) / 2.0) * scale_y + draw.rectangle([(cx - rx, cy - ry), (cx + rx, cy + ry)], outline=stroke, fill=fill, width=2) + elif self.roi_mode == "annular": + outer_rx = float(self.roi_radius) * scale_x + outer_ry = float(self.roi_radius) * scale_y + inner_rx = float(self.roi_radius_inner) * scale_x + inner_ry = float(self.roi_radius_inner) * scale_y + draw.ellipse( + [(cx - outer_rx, cy - outer_ry), (cx + outer_rx, cy + outer_ry)], + outline=stroke, + fill=fill, + width=2, + ) + draw.ellipse( + [(cx - inner_rx, cy - inner_ry), (cx + inner_rx, cy + inner_ry)], + outline=stroke, + fill=(0, 0, 0, 0), + width=2, + ) + + marker_color = (0, 220, 0, 255) if self.roi_active else (255, 100, 100, 255) + self._draw_crosshair(draw, cx, cy, size=max(6, int(min(width, height) * 0.03)), color=marker_color, width=2) + + if len(self.profile_line) == 2: + p0, p1 = self.profile_line[0], self.profile_line[1] + x0 = float(p0["col"]) * scale_x + y0 = float(p0["row"]) * scale_y + x1 = float(p1["col"]) * scale_x + y1 = float(p1["row"]) * scale_y + draw.line([(x0, y0), (x1, y1)], fill=(0, 200, 255, 240), width=max(1, int(self.profile_width))) + r = 3 + draw.ellipse([(x0 - r, y0 - r), (x0 + r, y0 + r)], fill=(0, 200, 255, 255)) + draw.ellipse([(x1 - r, y1 - r), (x1 + r, y1 + r)], fill=(0, 200, 255, 255)) + + def _draw_vi_overlays(self, image) -> None: + from PIL import ImageDraw + + draw = ImageDraw.Draw(image, mode="RGBA") + width, height = image.size + scale_x = float(width) / float(max(1, self.shape_cols)) + scale_y = float(height) / float(max(1, self.shape_rows)) + + px = float(self.pos_col) * scale_x + py = float(self.pos_row) * scale_y + self._draw_crosshair( + draw, + px, + py, + size=max(6, int(min(width, height) * 0.03)), + color=(255, 100, 100, 240), + width=2, + ) + + if self.vi_roi_mode == "off": + return + + cx = float(self.vi_roi_center_col) * scale_x + cy = float(self.vi_roi_center_row) * scale_y + stroke = (180, 80, 255, 240) + fill = (180, 80, 255, 45) + if self.vi_roi_mode == "circle": + rx = float(self.vi_roi_radius) * scale_x + ry = float(self.vi_roi_radius) * scale_y + draw.ellipse([(cx - rx, cy - ry), (cx + rx, cy + ry)], outline=stroke, fill=fill, width=2) + elif self.vi_roi_mode == "square": + rx = float(self.vi_roi_radius) * scale_x + ry = float(self.vi_roi_radius) * scale_y + draw.rectangle([(cx - rx, cy - ry), (cx + rx, cy + ry)], outline=stroke, fill=fill, width=2) + elif self.vi_roi_mode == "rect": + rx = (float(self.vi_roi_width) / 2.0) * scale_x + ry = (float(self.vi_roi_height) / 2.0) * scale_y + draw.rectangle([(cx - rx, cy - ry), (cx + rx, cy + ry)], outline=stroke, fill=fill, width=2) + + self._draw_crosshair( + draw, + cx, + cy, + size=max(6, int(min(width, height) * 0.03)), + color=(180, 80, 255, 240), + width=2, + ) + + def _decorate_panel( + self, + image, + panel_key: str, + include_overlays: bool, + include_scalebar: bool, + ): + out = image.copy() + if include_overlays: + if panel_key == "diffraction": + self._draw_dp_overlays(out) + elif panel_key == "virtual": + self._draw_vi_overlays(out) + if include_scalebar: + if panel_key == "diffraction": + unit = "mrad" if self.k_calibrated else "px" + self._draw_scalebar_overlay(out, float(self.k_pixel_size), unit) + elif panel_key == "virtual": + self._draw_scalebar_overlay(out, float(self.pixel_size), "Å") + return out + + def _render_panel_image( + self, + panel_key: str, + include_overlays: bool, + include_scalebar: bool, + ) -> tuple[Any, dict[str, Any]]: + from PIL import Image + + if panel_key == "diffraction": + rgb, render_meta = self._render_dp_rgb() + elif panel_key == "virtual": + rgb, render_meta = self._render_virtual_rgb() + elif panel_key == "fft": + rgb, render_meta = self._render_fft_rgb() + else: + raise ValueError(f"Unsupported panel '{panel_key}'") + + panel = Image.fromarray(rgb, mode="RGB") + panel = self._decorate_panel(panel, panel_key, include_overlays, include_scalebar) + return panel, render_meta + + def _compose_horizontal(self, panels: list[Any]): + from PIL import Image + + height = max(panel.height for panel in panels) + width = sum(panel.width for panel in panels) + composite = Image.new("RGB", (width, height), color=(0, 0, 0)) + x0 = 0 + for panel in panels: + composite.paste(panel, (x0, 0)) + x0 += panel.width + return composite + + def _calibration_metadata(self) -> dict[str, Any]: + return { + "pixel_size_angstrom": float(self.pixel_size), + "pixel_size_unit": "Å/px", + "k_pixel_size": float(self.k_pixel_size), + "k_pixel_size_unit": "mrad/px" if self.k_calibrated else "px/px", + "k_calibrated": bool(self.k_calibrated), + "center_row": float(self.center_row), + "center_col": float(self.center_col), + "bf_radius": float(self.bf_radius), + } + + def _roi_metadata(self) -> dict[str, Any]: + return { + "active": bool(self.roi_active), + "mode": self.roi_mode, + "center_row": float(self.roi_center_row), + "center_col": float(self.roi_center_col), + "radius": float(self.roi_radius), + "radius_inner": float(self.roi_radius_inner), + "width": float(self.roi_width), + "height": float(self.roi_height), + } + + def _vi_roi_metadata(self) -> dict[str, Any]: + return { + "mode": self.vi_roi_mode, + "center_row": float(self.vi_roi_center_row), + "center_col": float(self.vi_roi_center_col), + "radius": float(self.vi_roi_radius), + "width": float(self.vi_roi_width), + "height": float(self.vi_roi_height), + } + + def _export_settings_metadata(self) -> dict[str, Any]: + return { + "default_view": self.export_default_view, + "default_format": self.export_default_format, + "include_overlays": bool(self.export_include_overlays), + "include_scalebar": bool(self.export_include_scalebar), + "dpi": int(self.export_default_dpi), + } + + def _build_image_export_metadata( + self, + export_path: pathlib.Path, + view_key: str, + fmt: str, + render_meta: dict[str, Any], + include_overlays: bool, + include_scalebar: bool, + export_kind: str, + extra: dict[str, Any] | None = None, + ) -> dict[str, Any]: + metadata: dict[str, Any] = { + **build_json_header("Show4DSTEM"), + "view": view_key, + "format": fmt, + "export_kind": export_kind, + "path": str(export_path), + "position": {"row": int(self.pos_row), "col": int(self.pos_col)}, + "frame_idx": int(self.frame_idx), + "n_frames": int(self.n_frames), + "scan_shape": {"rows": int(self.shape_rows), "cols": int(self.shape_cols)}, + "detector_shape": {"rows": int(self.det_rows), "cols": int(self.det_cols)}, + "roi": self._roi_metadata(), + "vi_roi": self._vi_roi_metadata(), + "calibration": self._calibration_metadata(), + "display": render_meta, + "include_overlays": bool(include_overlays), + "include_scalebar": bool(include_scalebar), + "export_settings": self._export_settings_metadata(), + } + if extra: + metadata.update(extra) + return metadata + + @staticmethod + def _sha256_file(path: pathlib.Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as f: + while True: + chunk = f.read(1_048_576) + if not chunk: + break + digest.update(chunk) + return digest.hexdigest() + + def _build_file_record( + self, + path: pathlib.Path, + metadata_path: pathlib.Path | None = None, + index: int | None = None, + ) -> dict[str, Any]: + record: dict[str, Any] = { + "path": str(path), + "sha256": self._sha256_file(path), + "size_bytes": int(path.stat().st_size), + } + if metadata_path is not None and metadata_path.exists(): + record["metadata_path"] = str(metadata_path) + record["metadata_sha256"] = self._sha256_file(metadata_path) + record["metadata_size_bytes"] = int(metadata_path.stat().st_size) + if index is not None: + record["index"] = int(index) + return record + + def _record_export_event(self, event: dict[str, Any]) -> None: + payload = { + "session_id": self._export_session_id, + "timestamp_utc": datetime.now(timezone.utc).isoformat(), + } + payload.update(event) + self._export_log.append(payload) + + def _validate_sparse_frame_idx(self, frame_idx: int | None) -> int: + if self.n_frames <= 1: + return 0 + if frame_idx is None: + return int(self.frame_idx) + idx = int(frame_idx) + if idx < 0 or idx >= self.n_frames: + raise ValueError(f"frame_idx={idx} is out of range [0, {self.n_frames - 1}]") + return idx + + def _normalize_sparse_mask(self, mask: np.ndarray) -> np.ndarray: + arr = np.asarray(mask) + if self.n_frames <= 1: + if arr.shape == (self.shape_rows, self.shape_cols): + arr = arr[None, ...] + elif arr.shape != (1, self.shape_rows, self.shape_cols): + raise ValueError( + f"mask shape {arr.shape} does not match " + f"(scan_rows, scan_cols)=({self.shape_rows}, {self.shape_cols})" + ) + elif arr.shape != (self.n_frames, self.shape_rows, self.shape_cols): + raise ValueError( + f"mask shape {arr.shape} does not match " + f"(n_frames, scan_rows, scan_cols)=({self.n_frames}, {self.shape_rows}, {self.shape_cols})" + ) + return arr.astype(bool, copy=False) + + def _coerce_dp_array(self, dp: np.ndarray) -> np.ndarray: + arr = np.asarray(to_numpy(dp), dtype=np.float32) + if arr.shape != (self.det_rows, self.det_cols): + raise ValueError( + f"dp shape {arr.shape} does not match detector_shape " + f"({self.det_rows}, {self.det_cols})" + ) + return arr + + def _write_dp_to_data(self, frame_idx: int, row: int, col: int, dp_arr: np.ndarray) -> None: + dp_tensor = torch.from_numpy(dp_arr).to(device=self._device, dtype=torch.float32) + if self.n_frames > 1: + self._data[frame_idx, row, col] = dp_tensor + elif self._data.ndim == 4: + self._data[row, col] = dp_tensor + else: + flat_idx = row * self.shape_cols + col + self._data[flat_idx] = dp_tensor + + def _ingest_scan_point_core( + self, + row: int, + col: int, + dp: np.ndarray, + frame_idx: int, + dose: float, + refresh: bool, + ) -> None: + row_i, col_i = self._validate_position((row, col)) + frame_i = self._validate_sparse_frame_idx(frame_idx) + dp_arr = self._coerce_dp_array(dp) + dose_value = float(dose) + if not np.isfinite(dose_value) or dose_value < 0: + raise ValueError(f"dose must be finite and >= 0, got {dose}") + + key = (int(frame_i), int(row_i), int(col_i)) + if key not in self._sparse_samples: + self._sparse_order.append(key) + self._sparse_samples[key] = dp_arr.copy() + self._sparse_mask[frame_i, row_i, col_i] = True + self._dose_map[frame_i, row_i, col_i] += dose_value + + self._write_dp_to_data(frame_i, row_i, col_i, dp_arr) + self.dp_global_min = max(min(float(self.dp_global_min), float(dp_arr.min())), MIN_LOG_VALUE) + self.dp_global_max = max(float(self.dp_global_max), float(dp_arr.max())) + + if refresh: + self._compute_virtual_image_from_roi() + self._update_frame() + + def _detector_integration_kernel(self) -> tuple[np.ndarray | None, tuple[int, int] | None]: + cx, cy = float(self.roi_center_col), float(self.roi_center_row) + rr, cc = np.meshgrid( + np.arange(self.det_rows, dtype=np.float32), + np.arange(self.det_cols, dtype=np.float32), + indexing="ij", + ) + if self.roi_mode == "circle" and self.roi_radius > 0: + mask = (cc - cx) ** 2 + (rr - cy) ** 2 <= float(self.roi_radius) ** 2 + return mask.astype(np.float32, copy=False), None + if self.roi_mode == "square" and self.roi_radius > 0: + half = float(self.roi_radius) + mask = (np.abs(cc - cx) <= half) & (np.abs(rr - cy) <= half) + return mask.astype(np.float32, copy=False), None + if self.roi_mode == "annular" and self.roi_radius > 0: + outer = float(self.roi_radius) + inner = float(self.roi_radius_inner) + dist_sq = (cc - cx) ** 2 + (rr - cy) ** 2 + mask = (dist_sq >= inner**2) & (dist_sq <= outer**2) + return mask.astype(np.float32, copy=False), None + if self.roi_mode == "rect" and self.roi_width > 0 and self.roi_height > 0: + hw = float(self.roi_width) / 2.0 + hh = float(self.roi_height) / 2.0 + mask = (np.abs(cc - cx) <= hw) & (np.abs(rr - cy) <= hh) + return mask.astype(np.float32, copy=False), None + row = int(max(0, min(round(cy), self.det_rows - 1))) + col = int(max(0, min(round(cx), self.det_cols - 1))) + return None, (row, col) + + def _integrate_dp_value( + self, + dp: np.ndarray, + mask: np.ndarray | None, + point_idx: tuple[int, int] | None, + ) -> float: + arr = np.asarray(dp, dtype=np.float32) + if point_idx is not None: + row, col = point_idx + return float(arr[row, col]) + if mask is None: + return 0.0 + return float((arr * mask).sum()) + + def _virtual_image_from_frame_array(self, frame_data: np.ndarray) -> np.ndarray: + arr = np.asarray(frame_data, dtype=np.float32) + if arr.shape != (self.shape_rows, self.shape_cols, self.det_rows, self.det_cols): + raise ValueError( + f"frame_data shape {arr.shape} does not match " + f"({self.shape_rows}, {self.shape_cols}, {self.det_rows}, {self.det_cols})" + ) + mask, point_idx = self._detector_integration_kernel() + if point_idx is not None: + row, col = point_idx + return arr[:, :, row, col].astype(np.float32, copy=False) + return (arr * mask[None, None, :, :]).sum(axis=(2, 3)).astype(np.float32) + + @staticmethod + def _idw_reconstruct( + shape: tuple[int, int], + points: np.ndarray, + values: np.ndarray, + power: float = 2.0, + k_neighbors: int = 16, + ) -> np.ndarray: + if points.size == 0: + return np.zeros(shape, dtype=np.float32) + rr, cc = np.meshgrid( + np.arange(shape[0], dtype=np.float32), + np.arange(shape[1], dtype=np.float32), + indexing="ij", + ) + coords = np.stack([rr.reshape(-1), cc.reshape(-1)], axis=1) + dist_sq = ((coords[:, None, :] - points[None, :, :]) ** 2).sum(axis=2) + 1e-6 + + if k_neighbors > 0 and points.shape[0] > k_neighbors: + idx = np.argpartition(dist_sq, kth=k_neighbors - 1, axis=1)[:, :k_neighbors] + dist_sq = np.take_along_axis(dist_sq, idx, axis=1) + vals_local = values[idx] + else: + vals_local = np.broadcast_to(values[None, :], dist_sq.shape) + + weights = 1.0 / np.power(dist_sq, power / 2.0) + pred = (weights * vals_local).sum(axis=1) / np.maximum(weights.sum(axis=1), 1e-6) + return pred.reshape(shape).astype(np.float32, copy=False) + + def _resolve_reference_virtual_image( + self, + reference: str | np.ndarray, + frame_idx: int, + ) -> tuple[np.ndarray, str]: + if isinstance(reference, str): + key = reference.strip().lower() + if key != "full_raster": + raise ValueError("reference must be 'full_raster' or a NumPy array") + if self.n_frames > 1: + frame = self._data[frame_idx].detach().cpu().numpy() + elif self._data.ndim == 4: + frame = self._data.detach().cpu().numpy() + else: + frame = self._data.detach().cpu().numpy().reshape( + self.shape_rows, self.shape_cols, self.det_rows, self.det_cols + ) + return self._virtual_image_from_frame_array(frame), "full_raster" + + arr = np.asarray(to_numpy(reference), dtype=np.float32) + if arr.shape == (self.shape_rows, self.shape_cols): + return arr.astype(np.float32, copy=False), "virtual_image" + if arr.shape == (self.shape_rows, self.shape_cols, self.det_rows, self.det_cols): + return self._virtual_image_from_frame_array(arr), "frame_data" + if arr.shape == (self.n_frames, self.shape_rows, self.shape_cols, self.det_rows, self.det_cols): + return self._virtual_image_from_frame_array(arr[frame_idx]), "stack_frame_data" + raise ValueError( + "Unsupported reference shape. Expected one of: " + f"(scan_rows, scan_cols), " + f"(scan_rows, scan_cols, det_rows, det_cols), or " + f"(n_frames, scan_rows, scan_cols, det_rows, det_cols)." + ) + + def _extract_sparse_samples(self, frame_idx: int) -> tuple[np.ndarray, np.ndarray]: + mask = self._sparse_mask[frame_idx] + coords = np.argwhere(mask) + if coords.size == 0: + return ( + np.zeros((0, 2), dtype=np.float32), + np.zeros((0,), dtype=np.float32), + ) + + integ_mask, point_idx = self._detector_integration_kernel() + values = np.zeros((coords.shape[0],), dtype=np.float32) + for i, (row, col) in enumerate(coords): + key = (int(frame_idx), int(row), int(col)) + dp = self._sparse_samples.get(key) + if dp is None: + dp = self._get_frame(int(row), int(col)) + values[i] = self._integrate_dp_value(dp, integ_mask, point_idx) + points = coords.astype(np.float32, copy=False) + return points, values + + def ingest_scan_point( + self, + row: int, + col: int, + dp: np.ndarray, + frame_idx: int = 0, + dose: float | None = None, + ) -> Self: + """ + Ingest one scanned diffraction pattern into sparse acquisition state. + + Parameters + ---------- + row : int + Scan-space row index. + col : int + Scan-space column index. + dp : array_like + Diffraction pattern with shape ``(det_rows, det_cols)``. + frame_idx : int, default 0 + Frame index for 5D data. + dose : float, optional + Dose contribution for this acquisition event. Defaults to ``1.0``. + + Returns + ------- + Show4DSTEM + Self for method chaining. + """ + self._ingest_scan_point_core( + row=row, + col=col, + dp=dp, + frame_idx=frame_idx, + dose=1.0 if dose is None else float(dose), + refresh=True, + ) + self._record_export_event( + { + "export_kind": "ingest_scan_point", + "frame_idx": int(self._validate_sparse_frame_idx(frame_idx)), + "row": int(row), + "col": int(col), + "dose": float(1.0 if dose is None else dose), + } + ) + return self + + def ingest_scan_block( + self, + rows: list[int] | np.ndarray, + cols: list[int] | np.ndarray, + dp_block: np.ndarray, + frame_idx: int = 0, + ) -> Self: + """ + Ingest multiple scanned diffraction patterns in one call. + + Parameters + ---------- + rows : list[int] or np.ndarray + Row indices for each pattern in ``dp_block``. + cols : list[int] or np.ndarray + Column indices for each pattern in ``dp_block``. + dp_block : np.ndarray + Diffraction stack with shape ``(n_points, det_rows, det_cols)``. + frame_idx : int, default 0 + Frame index for 5D data. + + Returns + ------- + Show4DSTEM + Self for method chaining. + """ + rows_arr = np.asarray(rows, dtype=np.int64).reshape(-1) + cols_arr = np.asarray(cols, dtype=np.int64).reshape(-1) + if rows_arr.size != cols_arr.size: + raise ValueError("rows and cols must have the same length") + + block = np.asarray(to_numpy(dp_block), dtype=np.float32) + if block.ndim == 2: + block = block[None, ...] + if block.ndim != 3 or block.shape[1:] != (self.det_rows, self.det_cols): + raise ValueError( + f"dp_block shape must be (n_points, {self.det_rows}, {self.det_cols}), got {block.shape}" + ) + if block.shape[0] != rows_arr.size: + raise ValueError( + f"dp_block has {block.shape[0]} patterns but rows/cols specify {rows_arr.size} points" + ) + + frame_i = self._validate_sparse_frame_idx(frame_idx) + for idx in range(rows_arr.size): + self._ingest_scan_point_core( + row=int(rows_arr[idx]), + col=int(cols_arr[idx]), + dp=block[idx], + frame_idx=frame_i, + dose=1.0, + refresh=False, + ) + + self._compute_virtual_image_from_roi() + self._update_frame() + self._record_export_event( + { + "export_kind": "ingest_scan_block", + "frame_idx": int(frame_i), + "n_points": int(rows_arr.size), + } + ) + return self + + def get_sparse_state(self) -> dict[str, Any]: + """ + Return sparse acquisition state for checkpointing or replay. + + Returns + ------- + dict + Sparse state with sampling mask, sampled diffraction stack, + sampled-point coordinates, and dose map. + """ + coords = np.argwhere(self._sparse_mask) + sampled_points = [ + {"frame_idx": int(f), "row": int(r), "col": int(c)} + for (f, r, c) in coords + ] + if coords.size: + sampled_data = np.stack( + [ + self._sparse_samples.get((int(f), int(r), int(c)), self._get_frame(int(r), int(c))) + for (f, r, c) in coords + ], + axis=0, + ).astype(np.float32, copy=False) + else: + sampled_data = np.zeros((0, self.det_rows, self.det_cols), dtype=np.float32) + + mask_payload = self._sparse_mask[0].copy() if self.n_frames <= 1 else self._sparse_mask.copy() + dose_payload = self._dose_map[0].copy() if self.n_frames <= 1 else self._dose_map.copy() + return { + **build_json_header("Show4DSTEM"), + "format": "json", + "export_kind": "sparse_state_snapshot", + "frame_idx": int(self.frame_idx), + "scan_shape": {"rows": int(self.shape_rows), "cols": int(self.shape_cols)}, + "detector_shape": {"rows": int(self.det_rows), "cols": int(self.det_cols)}, + "mask": mask_payload, + "sampled_data": sampled_data, + "sampled_points": sampled_points, + "dose_map": dose_payload, + "n_sampled": int(len(sampled_points)), + "total_dose": float(self._dose_map.sum()), + } + + def set_sparse_state( + self, + mask: np.ndarray, + sampled_data: np.ndarray, + ) -> Self: + """ + Restore sparse acquisition state from mask + sampled data. + + Parameters + ---------- + mask : np.ndarray + Boolean scan mask. Shape ``(scan_rows, scan_cols)`` for 4D, + or ``(n_frames, scan_rows, scan_cols)`` for 5D. + sampled_data : np.ndarray + Either compact stack ``(n_sampled, det_rows, det_cols)`` + matching row-major ``mask`` order, or dense data aligned to mask: + ``(scan_rows, scan_cols, det_rows, det_cols)`` for 4D, + ``(n_frames, scan_rows, scan_cols, det_rows, det_cols)`` for 5D. + + Returns + ------- + Show4DSTEM + Self for method chaining. + """ + mask_3d = self._normalize_sparse_mask(mask) + coords = np.argwhere(mask_3d) + + payload = np.asarray(to_numpy(sampled_data), dtype=np.float32) + n_points = int(coords.shape[0]) + + if payload.ndim == 3: + if payload.shape[0] != n_points or payload.shape[1:] != (self.det_rows, self.det_cols): + raise ValueError( + f"Compact sampled_data must be (n_sampled, {self.det_rows}, {self.det_cols}); " + f"got {payload.shape} for n_sampled={n_points}" + ) + compact = payload + elif self.n_frames <= 1 and payload.shape == (self.shape_rows, self.shape_cols, self.det_rows, self.det_cols): + compact = np.stack( + [payload[int(r), int(c)] for (_, r, c) in coords], + axis=0, + ) if n_points else np.zeros((0, self.det_rows, self.det_cols), dtype=np.float32) + elif payload.shape == ( + self.n_frames, + self.shape_rows, + self.shape_cols, + self.det_rows, + self.det_cols, + ): + compact = np.stack( + [payload[int(f), int(r), int(c)] for (f, r, c) in coords], + axis=0, + ) if n_points else np.zeros((0, self.det_rows, self.det_cols), dtype=np.float32) + else: + raise ValueError( + "Unsupported sampled_data shape for set_sparse_state. " + "Use compact (n_sampled, det_rows, det_cols) or dense per-mask arrays." + ) + + self._sparse_samples = {} + self._sparse_order = [] + self._sparse_mask = np.zeros((self.n_frames, self.shape_rows, self.shape_cols), dtype=bool) + self._dose_map = np.zeros((self.n_frames, self.shape_rows, self.shape_cols), dtype=np.float32) + + for idx, (frame_idx, row, col) in enumerate(coords): + self._ingest_scan_point_core( + row=int(row), + col=int(col), + dp=compact[idx], + frame_idx=int(frame_idx), + dose=1.0, + refresh=False, + ) + + self._compute_virtual_image_from_roi() + self._update_frame() + self._record_export_event( + { + "export_kind": "set_sparse_state", + "n_sampled": int(n_points), + } + ) + return self + + def _resolve_proposal_count( + self, + k: int, + frame_idx: int, + budget: dict[str, Any] | None, + ) -> int: + count = int(k) + if count < 1: + raise ValueError(f"k must be >= 1, got {k}") + if budget is None: + return count + + existing_points = int(self._sparse_mask[frame_idx].sum()) + existing_dose = float(self._dose_map[frame_idx].sum()) + total_points = int(self.shape_rows * self.shape_cols) + + if "max_new_points" in budget: + count = min(count, int(budget["max_new_points"])) + if "max_total_points" in budget: + count = min(count, max(0, int(budget["max_total_points"]) - existing_points)) + if "max_total_fraction" in budget: + allowed_total = int(round(float(budget["max_total_fraction"]) * total_points)) + count = min(count, max(0, allowed_total - existing_points)) + if "max_total_dose" in budget: + dose_per_point = float(budget.get("dose_per_point", 1.0)) + if dose_per_point <= 0: + raise ValueError("budget['dose_per_point'] must be > 0") + remaining = float(budget["max_total_dose"]) - existing_dose + count = min(count, max(0, int(math.floor(remaining / dose_per_point)))) + return max(0, int(count)) + + def propose_next_points( + self, + k: int, + strategy: str = "adaptive", + budget: dict[str, Any] | None = None, + ) -> list[tuple[int, int]]: + """ + Propose next scan points from current sparse acquisition state. + + Parameters + ---------- + k : int + Maximum number of new points to propose. + strategy : str, default "adaptive" + Proposal strategy: ``"adaptive"``, ``"random"``, or ``"raster"``. + budget : dict, optional + Optional constraints and strategy parameters. Supported keys: + ``frame_idx``, ``max_new_points``, ``max_total_points``, + ``max_total_fraction``, ``max_total_dose``, ``dose_per_point``, + ``roi_mask``, ``seed``, ``min_spacing``, ``step``, + ``local_window``, ``dose_lambda``, ``weights``, ``bidirectional``. + + Returns + ------- + list[tuple[int, int]] + Proposed ``(row, col)`` scan coordinates. + """ + budget_dict = {} if budget is None else dict(budget) + strategy_key = str(strategy).strip().lower() + if strategy_key not in {"adaptive", "random", "raster"}: + raise ValueError("strategy must be one of: adaptive, random, raster") + + frame_idx = self._validate_sparse_frame_idx(budget_dict.get("frame_idx", self.frame_idx)) + n_select = self._resolve_proposal_count(int(k), frame_idx, budget_dict) + if n_select <= 0: + return [] + + sampled_mask = self._sparse_mask[frame_idx].copy() + allowed_mask = ~sampled_mask + roi_mask_raw = budget_dict.get("roi_mask", None) + if roi_mask_raw is not None: + roi_mask = np.asarray(roi_mask_raw, dtype=bool) + if roi_mask.shape != (self.shape_rows, self.shape_cols): + raise ValueError( + f"roi_mask shape {roi_mask.shape} must match " + f"scan_shape ({self.shape_rows}, {self.shape_cols})" + ) + allowed_mask &= roi_mask + + proposals: list[tuple[int, int]] = [] + if strategy_key == "adaptive": + local_window = int(budget_dict.get("local_window", 5)) + if local_window < 1: + raise ValueError("budget['local_window'] must be >= 1") + min_spacing = int(budget_dict.get("min_spacing", 2)) + if min_spacing < 0: + raise ValueError("budget['min_spacing'] must be >= 0") + dose_lambda = float(budget_dict.get("dose_lambda", 0.25)) + if not np.isfinite(dose_lambda): + raise ValueError("budget['dose_lambda'] must be finite") + + default_weights = { + "vi_gradient": 0.4, + "vi_local_std": 0.3, + "dp_variance": 0.3, + } + merged_weights = dict(default_weights) + raw_weights = budget_dict.get("weights", None) + if raw_weights is not None: + for key, value in dict(raw_weights).items(): + if key not in default_weights: + raise ValueError( + f"Unsupported adaptive weight '{key}'. " + f"Supported: {', '.join(default_weights.keys())}" + ) + merged_weights[key] = float(value) + weight_sum = sum(max(0.0, float(v)) for v in merged_weights.values()) + if weight_sum <= 0: + raise ValueError("At least one adaptive weight must be > 0") + weights = {k: max(0.0, float(v)) / weight_sum for k, v in merged_weights.items()} + + vi = self._virtual_image_for_frame(frame_idx) + grad_row, grad_col = np.gradient(vi) + vi_gradient = np.hypot(grad_row, grad_col).astype(np.float32) + mean_local = self._box_mean_map(vi, local_window) + mean_sq_local = self._box_mean_map(vi * vi, local_window) + vi_local_std = np.sqrt(np.maximum(mean_sq_local - mean_local * mean_local, 0.0)).astype(np.float32) + dp_variance = self._dp_variance_map(frame_idx=frame_idx) + + utility = ( + weights["vi_gradient"] * self._normalize_score_map(vi_gradient) + + weights["vi_local_std"] * self._normalize_score_map(vi_local_std) + + weights["dp_variance"] * self._normalize_score_map(dp_variance) + ).astype(np.float32) + + frame_dose = self._dose_map[frame_idx].astype(np.float32, copy=False) + if float(frame_dose.max()) > 0: + utility = utility - float(dose_lambda) * (frame_dose / float(frame_dose.max())) + + picks = self._select_spaced_topk( + scores=utility, + k=n_select, + min_spacing=min_spacing, + allowed_mask=allowed_mask, + excluded_mask=np.zeros_like(allowed_mask, dtype=bool), + ) + proposals = [(int(r), int(c)) for (r, c) in picks] + elif strategy_key == "random": + coords = np.argwhere(allowed_mask) + if coords.size: + seed = budget_dict.get("seed", None) + rng = np.random.default_rng(None if seed is None else int(seed)) + n_take = min(n_select, int(coords.shape[0])) + idx = rng.choice(coords.shape[0], size=n_take, replace=False) + chosen = coords[idx] + proposals = [(int(r), int(c)) for r, c in chosen] + else: + step = int(budget_dict.get("step", 1)) + if step < 1: + raise ValueError("budget['step'] must be >= 1") + bidirectional = bool(budget_dict.get("bidirectional", True)) + for row in range(0, self.shape_rows, step): + cols = list(range(0, self.shape_cols, step)) + if bidirectional and ((row // step) % 2 == 1): + cols.reverse() + for col in cols: + if allowed_mask[row, col]: + proposals.append((int(row), int(col))) + if len(proposals) >= n_select: + break + if len(proposals) >= n_select: + break + + self._record_export_event( + { + "export_kind": "propose_next_points", + "strategy": strategy_key, + "frame_idx": int(frame_idx), + "k_requested": int(k), + "k_returned": int(len(proposals)), + } + ) + return proposals + + def evaluate_against_reference( + self, + reference: str | np.ndarray = "full_raster", + metrics: list[str] | None = None, + ) -> dict[str, Any]: + """ + Evaluate sparse-sampled reconstruction against a reference image. + + Parameters + ---------- + reference : str or np.ndarray, default "full_raster" + Reference target. ``"full_raster"`` uses the current full dataset + and current ROI integration settings. Arrays are also accepted + (virtual image or full diffraction stack; see method docs). + metrics : list[str], optional + Metric names to compute. Supported: ``"rmse"``, ``"nrmse"``, + ``"mae"``, ``"psnr"``. + + Returns + ------- + dict + Evaluation summary including sampled fraction and metric values. + """ + metric_names = ( + ["rmse", "nrmse", "mae", "psnr"] + if metrics is None + else [str(name).strip().lower() for name in metrics] + ) + supported = {"rmse", "nrmse", "mae", "psnr"} + unknown = [name for name in metric_names if name not in supported] + if unknown: + raise ValueError(f"Unsupported metrics: {unknown}. Supported: {sorted(supported)}") + + frame_idx = int(self.frame_idx if self.n_frames <= 1 else self._validate_sparse_frame_idx(self.frame_idx)) + points, values = self._extract_sparse_samples(frame_idx) + if points.shape[0] == 0: + raise ValueError("No sparse samples available for evaluation. Ingest points first.") + + reference_vi, reference_kind = self._resolve_reference_virtual_image(reference, frame_idx) + reconstruction = self._idw_reconstruct( + shape=(self.shape_rows, self.shape_cols), + points=points, + values=values, + power=2.0, + k_neighbors=16, + ) + + ref = np.asarray(reference_vi, dtype=np.float32) + pred = np.asarray(reconstruction, dtype=np.float32) + diff = pred - ref + mse = float(np.mean(diff * diff)) + rmse = float(np.sqrt(mse)) + mae = float(np.mean(np.abs(diff))) + ref_range = float(ref.max() - ref.min()) + 1e-6 + nrmse = float(rmse / ref_range) + peak = float(max(float(ref.max()), 1e-6)) + psnr = 120.0 if mse <= 1e-12 else float(20.0 * np.log10(peak) - 10.0 * np.log10(mse)) + + metric_values = { + "rmse": rmse, + "nrmse": nrmse, + "mae": mae, + "psnr": psnr, + } + selected_metrics = {name: float(metric_values[name]) for name in metric_names} + + summary = { + "reference_kind": reference_kind, + "frame_idx": int(frame_idx), + "n_sampled": int(points.shape[0]), + "sampled_fraction": float(points.shape[0] / max(1, self.shape_rows * self.shape_cols)), + "metrics": selected_metrics, + "scan_shape": {"rows": int(self.shape_rows), "cols": int(self.shape_cols)}, + "detector_shape": {"rows": int(self.det_rows), "cols": int(self.det_cols)}, + } + self._record_export_event( + { + "export_kind": "evaluate_against_reference", + "reference_kind": reference_kind, + "frame_idx": int(frame_idx), + "n_sampled": int(points.shape[0]), + "sampled_fraction": float(summary["sampled_fraction"]), + "metrics": selected_metrics, + } + ) + return summary + + def export_session_bundle( + self, + path: str | pathlib.Path, + ) -> pathlib.Path: + """ + Export a reproducible session bundle for sparse/adaptive workflows. + + The bundle includes widget state, sparse-state arrays, a current view + image with metadata, and the reproducibility report. + + Parameters + ---------- + path : str or pathlib.Path + Output directory for bundle files. + + Returns + ------- + pathlib.Path + Path to the bundle manifest JSON. + """ + bundle_dir = pathlib.Path(path) + bundle_dir.mkdir(parents=True, exist_ok=True) + + state_path = bundle_dir / "widget_state.json" + self.save(state_path) + + sparse_state = self.get_sparse_state() + sparse_npz_path = bundle_dir / "sparse_state.npz" + np.savez_compressed( + sparse_npz_path, + mask=sparse_state["mask"], + sampled_data=sparse_state["sampled_data"], + dose_map=sparse_state["dose_map"], + ) + + sparse_points_path = bundle_dir / "sparse_points.json" + sparse_points_payload = { + **build_json_header("Show4DSTEM"), + "format": "json", + "export_kind": "sparse_points", + "n_sampled": int(sparse_state["n_sampled"]), + "sampled_points": sparse_state["sampled_points"], + } + sparse_points_path.write_text(json.dumps(sparse_points_payload, indent=2)) + + image_path = bundle_dir / "current_all.png" + image_written = self.save_image( + image_path, + view="all", + include_metadata=True, + include_overlays=True, + include_scalebar=True, + ) + image_meta_path = image_written.with_suffix(".json") + + report_path = self.save_reproducibility_report(bundle_dir / "reproducibility_report.json") + + manifest_path = bundle_dir / "session_bundle_manifest.json" + manifest_payload = { + **build_json_header("Show4DSTEM"), + "format": "json", + "export_kind": "session_bundle", + "bundle_path": str(bundle_dir), + "created_utc": datetime.now(timezone.utc).isoformat(), + "session_id": self._export_session_id, + "scan_shape": {"rows": int(self.shape_rows), "cols": int(self.shape_cols)}, + "detector_shape": {"rows": int(self.det_rows), "cols": int(self.det_cols)}, + "sparse_summary": { + "n_sampled": int(sparse_state["n_sampled"]), + "sampled_fraction": float( + sparse_state["n_sampled"] / max(1, self.shape_rows * self.shape_cols * self.n_frames) + ), + "total_dose": float(sparse_state["total_dose"]), + }, + "files": { + "state": str(state_path), + "sparse_npz": str(sparse_npz_path), + "sparse_points_json": str(sparse_points_path), + "image": str(image_written), + "image_metadata": str(image_meta_path), + "reproducibility_report": str(report_path), + }, + } + manifest_path.write_text(json.dumps(manifest_payload, indent=2)) + + self._record_export_event( + { + "export_kind": "session_bundle", + "n_sampled": int(sparse_state["n_sampled"]), + "outputs": [ + self._build_file_record(state_path), + self._build_file_record(sparse_npz_path), + self._build_file_record(sparse_points_path), + self._build_file_record(image_written, metadata_path=image_meta_path), + self._build_file_record(report_path), + self._build_file_record(manifest_path), + ], + } + ) + return manifest_path + + def _normalize_score_map(self, values: np.ndarray) -> np.ndarray: + arr = np.asarray(values, dtype=np.float32) + if arr.size == 0: + return np.zeros_like(arr, dtype=np.float32) + vmin = float(np.percentile(arr, 1.0)) + vmax = float(np.percentile(arr, 99.0)) + if vmax <= vmin: + return np.zeros_like(arr, dtype=np.float32) + return np.clip((arr - vmin) / (vmax - vmin), 0.0, 1.0).astype(np.float32) + + def _box_mean_map(self, values: np.ndarray, window: int) -> np.ndarray: + arr = np.asarray(values, dtype=np.float32) + win = int(window) + if win <= 1: + return arr.copy() + if win % 2 == 0: + win += 1 + pad = win // 2 + padded = np.pad(arr, ((pad, pad), (pad, pad)), mode="reflect") + integral = np.pad(padded, ((1, 0), (1, 0)), mode="constant").cumsum(axis=0).cumsum(axis=1) + sums = ( + integral[win:, win:] + - integral[:-win, win:] + - integral[win:, :-win] + + integral[:-win, :-win] + ) + return (sums / float(win * win)).astype(np.float32) + + def _dp_variance_map(self, frame_idx: int | None = None) -> np.ndarray: + if frame_idx is None or self.n_frames <= 1: + data = self._frame_data + else: + idx = self._validate_sparse_frame_idx(frame_idx) + data = self._data[idx] + if data.ndim == 4: + variance = data.var(dim=(2, 3), unbiased=False) + return variance.detach().cpu().numpy().astype(np.float32, copy=False) + variance = data.var(dim=(1, 2), unbiased=False) + return variance.detach().cpu().numpy().reshape(self.shape_rows, self.shape_cols).astype(np.float32, copy=False) + + def _build_coarse_points(self, step: int, bidirectional: bool) -> list[tuple[int, int]]: + points: list[tuple[int, int]] = [] + for r in range(0, self.shape_rows, step): + cols = list(range(0, self.shape_cols, step)) + if bidirectional and ((r // step) % 2 == 1): + cols.reverse() + for c in cols: + points.append((int(r), int(c))) + return points + + def _select_spaced_topk( + self, + scores: np.ndarray, + k: int, + min_spacing: int, + allowed_mask: np.ndarray, + excluded_mask: np.ndarray, + ) -> list[tuple[int, int]]: + work = np.asarray(scores, dtype=np.float32).copy() + work[~allowed_mask] = -np.inf + work[excluded_mask] = -np.inf + selected: list[tuple[int, int]] = [] + radius = max(0, int(min_spacing)) + + for _ in range(int(max(0, k))): + flat_idx = int(np.argmax(work)) + best_score = float(work.flat[flat_idx]) + if not np.isfinite(best_score): + break + row, col = np.unravel_index(flat_idx, work.shape) + selected.append((int(row), int(col))) + if radius == 0: + work[row, col] = -np.inf + continue + r0 = max(0, row - radius) + r1 = min(work.shape[0], row + radius + 1) + c0 = max(0, col - radius) + c1 = min(work.shape[1], col + radius + 1) + rr, cc = np.ogrid[r0:r1, c0:c1] + neighborhood = (rr - row) ** 2 + (cc - col) ** 2 <= radius ** 2 + block = work[r0:r1, c0:c1] + block[neighborhood] = -np.inf + return selected + + def _nearest_neighbor_order( + self, + points: list[tuple[int, int]], + start: tuple[int, int] | None = None, + ) -> list[tuple[int, int]]: + remaining = [tuple(map(int, pt)) for pt in points] + if not remaining: + return [] + + if start is None: + current = remaining.pop(0) + else: + sr, sc = int(start[0]), int(start[1]) + start_idx = min( + range(len(remaining)), + key=lambda i: (remaining[i][0] - sr) ** 2 + (remaining[i][1] - sc) ** 2, + ) + current = remaining.pop(start_idx) + + ordered = [current] + while remaining: + cr, cc = current + next_idx = min( + range(len(remaining)), + key=lambda i: (remaining[i][0] - cr) ** 2 + (remaining[i][1] - cc) ** 2, + ) + current = remaining.pop(next_idx) + ordered.append(current) + return ordered + + def save_image( + self, + path: str | pathlib.Path, + view: str | None = None, + position: tuple[int, int] | None = None, + frame_idx: int | None = None, + format: str | None = None, + include_metadata: bool = True, + metadata_path: str | pathlib.Path | None = None, + include_overlays: bool | None = None, + include_scalebar: bool | None = None, + restore_state: bool = True, + dpi: int | None = None, + ) -> pathlib.Path: + """ + Save the current visualization as PNG or PDF. + + Parameters + ---------- + path : str or pathlib.Path + Output image path. + view : str, optional + One of: "diffraction", "virtual", "fft", "all". + position : tuple[int, int], optional + Temporary scan position override as (row, col) for this export. + frame_idx : int, optional + Temporary frame index override for 5D data. + format : str, optional + "png" or "pdf". If omitted, inferred from file extension. + include_metadata : bool, default True + If True, writes JSON metadata next to the image. + metadata_path : str or pathlib.Path, optional + Override metadata JSON path. + include_overlays : bool, optional + Draw ROI/profile/crosshair overlays on exported panels. + Defaults to ``export_include_overlays``. + include_scalebar : bool, optional + Draw panel scale bars on exported panels. + Defaults to ``export_include_scalebar``. + restore_state : bool, default True + If True, temporary position/frame overrides are reverted after export. + dpi : int, optional + Export DPI metadata. + + Returns + ------- + pathlib.Path + The written image path. + """ + from PIL import Image + + export_path = pathlib.Path(path) + view_key = self._validate_export_view(view) + fmt = self._resolve_export_format(export_path, format) + dpi_value = int(self.export_default_dpi if dpi is None else dpi) + overlays_enabled = ( + bool(self.export_include_overlays) + if include_overlays is None + else bool(include_overlays) + ) + scalebar_enabled = ( + bool(self.export_include_scalebar) + if include_scalebar is None + else bool(include_scalebar) + ) + + if dpi_value <= 0: + raise ValueError(f"dpi must be > 0, got {dpi_value}") + + export_path.parent.mkdir(parents=True, exist_ok=True) + + prev_row, prev_col = self.pos_row, self.pos_col + prev_frame = self.frame_idx + meta_path: pathlib.Path | None = None + export_row = int(self.pos_row) + export_col = int(self.pos_col) + export_frame = int(self.frame_idx) + + try: + if frame_idx is not None: + self.frame_idx = self._validate_frame_idx(frame_idx) + if position is not None: + row, col = self._validate_position(position) + self.pos_row = row + self.pos_col = col + export_row = int(self.pos_row) + export_col = int(self.pos_col) + export_frame = int(self.frame_idx) + + if view_key == "diffraction": + image, dp_meta = self._render_panel_image( + "diffraction", overlays_enabled, scalebar_enabled + ) + render_meta = {"diffraction": dp_meta} + elif view_key == "virtual": + image, vi_meta = self._render_panel_image( + "virtual", overlays_enabled, scalebar_enabled + ) + render_meta = {"virtual": vi_meta} + elif view_key == "fft": + image, fft_meta = self._render_panel_image( + "fft", overlays_enabled, scalebar_enabled + ) + render_meta = {"fft": fft_meta} + else: + panel_images = [] + render_meta = {} + dp_img, dp_meta = self._render_panel_image( + "diffraction", overlays_enabled, scalebar_enabled + ) + vi_img, vi_meta = self._render_panel_image( + "virtual", overlays_enabled, scalebar_enabled + ) + panel_images.extend([dp_img, vi_img]) + render_meta = {"diffraction": dp_meta, "virtual": vi_meta} + if self.show_fft: + fft_img, fft_meta = self._render_panel_image( + "fft", overlays_enabled, scalebar_enabled + ) + panel_images.append(fft_img) + render_meta["fft"] = fft_meta + image = self._compose_horizontal(panel_images) + + if fmt == "pdf": + Image.init() + image = image.convert("RGB") + image.save(export_path, format="PDF", resolution=dpi_value) + else: + image.save(export_path, format="PNG", dpi=(dpi_value, dpi_value)) + + if include_metadata: + meta_path = ( + pathlib.Path(metadata_path) + if metadata_path is not None + else export_path.with_suffix(".json") + ) + metadata = self._build_image_export_metadata( + export_path=export_path, + view_key=view_key, + fmt=fmt, + render_meta=render_meta, + include_overlays=overlays_enabled, + include_scalebar=scalebar_enabled, + export_kind="single_view_image", + extra={"dpi": int(dpi_value)}, + ) + meta_path.write_text(json.dumps(metadata, indent=2)) + finally: + if restore_state: + self.frame_idx = prev_frame + self.pos_row = prev_row + self.pos_col = prev_col + + self._record_export_event( + { + "export_kind": "single_view_image", + "view": view_key, + "format": fmt, + "position": {"row": export_row, "col": export_col}, + "frame_idx": export_frame, + "include_overlays": bool(overlays_enabled), + "include_scalebar": bool(scalebar_enabled), + "dpi": int(dpi_value), + "outputs": [ + self._build_file_record(export_path, metadata_path=meta_path), + ], + } + ) + return export_path + + def _build_preset_payload(self) -> dict[str, Any]: + return { + "detector": { + "center_row": float(self.center_row), + "center_col": float(self.center_col), + "bf_radius": float(self.bf_radius), + "roi_active": bool(self.roi_active), + "roi_mode": self.roi_mode, + "roi_center_row": float(self.roi_center_row), + "roi_center_col": float(self.roi_center_col), + "roi_radius": float(self.roi_radius), + "roi_radius_inner": float(self.roi_radius_inner), + "roi_width": float(self.roi_width), + "roi_height": float(self.roi_height), + }, + "vi_roi": { + "mode": self.vi_roi_mode, + "center_row": float(self.vi_roi_center_row), + "center_col": float(self.vi_roi_center_col), + "radius": float(self.vi_roi_radius), + "width": float(self.vi_roi_width), + "height": float(self.vi_roi_height), + }, + "display": { + "mask_dc": bool(self.mask_dc), + "dp_colormap": self.dp_colormap, + "vi_colormap": self.vi_colormap, + "fft_colormap": self.fft_colormap, + "dp_scale_mode": self.dp_scale_mode, + "vi_scale_mode": self.vi_scale_mode, + "fft_scale_mode": self.fft_scale_mode, + "dp_power_exp": float(self.dp_power_exp), + "vi_power_exp": float(self.vi_power_exp), + "fft_power_exp": float(self.fft_power_exp), + "dp_vmin_pct": float(self.dp_vmin_pct), + "dp_vmax_pct": float(self.dp_vmax_pct), + "vi_vmin_pct": float(self.vi_vmin_pct), + "vi_vmax_pct": float(self.vi_vmax_pct), + "fft_vmin_pct": float(self.fft_vmin_pct), + "fft_vmax_pct": float(self.fft_vmax_pct), + "fft_auto": bool(self.fft_auto), + "show_fft": bool(self.show_fft), + "dp_show_colorbar": bool(self.dp_show_colorbar), + "profile_line": self.profile_line, + "profile_width": int(self.profile_width), + }, + "export": self._export_settings_metadata(), + } + + def _apply_preset_payload(self, preset: dict[str, Any]) -> None: + detector = preset.get("detector", {}) + vi_roi = preset.get("vi_roi", {}) + display = preset.get("display", {}) + export = preset.get("export", {}) + + detector_map = { + "center_row": "center_row", + "center_col": "center_col", + "bf_radius": "bf_radius", + "roi_active": "roi_active", + "roi_mode": "roi_mode", + "roi_center_row": "roi_center_row", + "roi_center_col": "roi_center_col", + "roi_radius": "roi_radius", + "roi_radius_inner": "roi_radius_inner", + "roi_width": "roi_width", + "roi_height": "roi_height", + } + for key, trait_name in detector_map.items(): + if key in detector and hasattr(self, trait_name): + setattr(self, trait_name, detector[key]) + + vi_roi_map = { + "mode": "vi_roi_mode", + "center_row": "vi_roi_center_row", + "center_col": "vi_roi_center_col", + "radius": "vi_roi_radius", + "width": "vi_roi_width", + "height": "vi_roi_height", + } + for key, trait_name in vi_roi_map.items(): + if key in vi_roi and hasattr(self, trait_name): + setattr(self, trait_name, vi_roi[key]) + + _display_keys = { + "dp_colormap", "vi_colormap", "fft_colormap", + "dp_scale_mode", "vi_scale_mode", "fft_scale_mode", + "dp_power_exp", "vi_power_exp", "fft_power_exp", + "dp_vmin_pct", "dp_vmax_pct", "vi_vmin_pct", "vi_vmax_pct", + "fft_vmin_pct", "fft_vmax_pct", "fft_auto", + "mask_dc", "dp_show_colorbar", "show_fft", "fft_window", + "show_controls", + } + for key, value in display.items(): + if key in _display_keys: + setattr(self, key, value) + + export_map = { + "default_view": "export_default_view", + "default_format": "export_default_format", + "include_overlays": "export_include_overlays", + "include_scalebar": "export_include_scalebar", + "dpi": "export_default_dpi", + } + for key, trait_name in export_map.items(): + if key in export and hasattr(self, trait_name): + setattr(self, trait_name, export[key]) + + def save_preset( + self, + name: str, + path: str | pathlib.Path | None = None, + ) -> dict[str, Any]: + preset_name = str(name).strip() + if not preset_name: + raise ValueError("Preset name must be non-empty.") + preset_key = preset_name.lower() + + payload = self._build_preset_payload() + self._named_presets[preset_key] = payload + + if path is not None: + out_path = pathlib.Path(path) + out_path.parent.mkdir(parents=True, exist_ok=True) + serialized = { + **build_json_header("Show4DSTEM"), + "format": "json", + "export_kind": "widget_preset", + "preset_name": preset_name, + "preset": payload, + } + out_path.write_text(json.dumps(serialized, indent=2)) + + return payload + + def load_preset( + self, + name: str, + path: str | pathlib.Path | None = None, + apply: bool = True, + ) -> dict[str, Any]: + preset_name = str(name).strip() + preset_key = preset_name.lower() + if path is not None: + payload = json.loads(pathlib.Path(path).read_text()) + if not isinstance(payload, dict): + raise ValueError("Preset file must contain a JSON object.") + if "preset" in payload: + preset = payload["preset"] + else: + preset = payload + if not isinstance(preset, dict): + raise ValueError("Preset payload must be a JSON object.") + if preset_name: + self._named_presets[preset_key] = preset + else: + if preset_key not in self._named_presets: + raise ValueError( + f"Preset '{preset_name}' not found. Available: {', '.join(self.list_presets())}" + ) + preset = self._named_presets[preset_key] + + if apply: + self._apply_preset_payload(preset) + return preset + + def apply_preset(self, name: str) -> Self: + preset_name = str(name).strip().lower() + if preset_name == "bf": + self.roi_active = True + self.roi_mode = "circle" + self.roi_center_row = float(self.center_row) + self.roi_center_col = float(self.center_col) + self.roi_radius = float(max(1.0, self.bf_radius)) + return self + if preset_name == "abf": + self.roi_active = True + self.roi_mode = "annular" + self.roi_center_row = float(self.center_row) + self.roi_center_col = float(self.center_col) + self.roi_radius_inner = float(max(0.5, self.bf_radius * 0.5)) + self.roi_radius = float(max(1.0, self.bf_radius)) + return self + if preset_name == "adf": + self.roi_active = True + self.roi_mode = "annular" + self.roi_center_row = float(self.center_row) + self.roi_center_col = float(self.center_col) + self.roi_radius_inner = float(max(1.0, self.bf_radius)) + self.roi_radius = float(max(self.roi_radius_inner + 1.0, self.bf_radius * 2.0)) + return self + if preset_name == "haadf": + self.roi_active = True + self.roi_mode = "annular" + self.roi_center_row = float(self.center_row) + self.roi_center_col = float(self.center_col) + self.roi_radius_inner = float(max(1.0, self.bf_radius * 2.0)) + self.roi_radius = float(max(self.roi_radius_inner + 1.0, self.bf_radius * 4.0)) + return self + + self.load_preset(preset_name, apply=True) + return self + + def _resolve_figure_template(self, template: str) -> tuple[str, list[str], bool]: + key = str(template).strip().lower() + mapping = { + "dp_vi": (["diffraction", "virtual"], False), + "dp_vi_fft": (["diffraction", "virtual", "fft"], False), + "publication_dp_vi": (["diffraction", "virtual"], True), + "publication_dp_vi_fft": (["diffraction", "virtual", "fft"], True), + } + if key not in mapping: + raise ValueError( + f"Unsupported template '{template}'. " + f"Supported: {', '.join(self.list_figure_templates())}" + ) + panels, publication = mapping[key] + return key, panels, publication + + def save_figure( + self, + path: str | pathlib.Path, + template: str = "dp_vi_fft", + position: tuple[int, int] | None = None, + frame_idx: int | None = None, + format: str | None = None, + include_metadata: bool = True, + metadata_path: str | pathlib.Path | None = None, + include_overlays: bool | None = None, + include_scalebar: bool | None = None, + restore_state: bool = True, + dpi: int | None = None, + title: str | None = None, + annotations: dict[str, str] | None = None, + ) -> pathlib.Path: + from PIL import Image, ImageDraw, ImageFont + + export_path = pathlib.Path(path) + template_key, panel_keys, publication_style = self._resolve_figure_template(template) + fmt = self._resolve_export_format(export_path, format) + dpi_value = int(self.export_default_dpi if dpi is None else dpi) + overlays_enabled = ( + bool(self.export_include_overlays) + if include_overlays is None + else bool(include_overlays) + ) + scalebar_enabled = ( + bool(self.export_include_scalebar) + if include_scalebar is None + else bool(include_scalebar) + ) + if dpi_value <= 0: + raise ValueError(f"dpi must be > 0, got {dpi_value}") + + export_path.parent.mkdir(parents=True, exist_ok=True) + font = ImageFont.load_default() + + prev_row, prev_col = self.pos_row, self.pos_col + prev_frame = self.frame_idx + meta_path: pathlib.Path | None = None + + try: + if frame_idx is not None: + self.frame_idx = self._validate_frame_idx(frame_idx) + if position is not None: + row, col = self._validate_position(position) + self.pos_row = row + self.pos_col = col + + panel_images: list[Any] = [] + render_meta: dict[str, Any] = {} + for panel_key in panel_keys: + panel, panel_meta = self._render_panel_image( + panel_key, + include_overlays=overlays_enabled, + include_scalebar=scalebar_enabled, + ) + panel_images.append(panel) + render_meta[panel_key] = panel_meta + + gap = 24 if publication_style else 8 + padding = 24 if publication_style else 10 + label_height = 22 if publication_style else 0 + title_text = title + if title_text is None and publication_style: + if self.n_frames > 1: + title_text = f"4D-STEM Figure ({self.frame_dim_label} {self.frame_idx})" + else: + title_text = "4D-STEM Figure" + title_height = 34 if title_text else 0 + + max_panel_height = max(panel.height for panel in panel_images) + total_width = padding * 2 + sum(panel.width for panel in panel_images) + gap * (len(panel_images) - 1) + total_height = padding * 2 + title_height + label_height + max_panel_height + + figure = Image.new("RGB", (total_width, total_height), color=(255, 255, 255)) + draw = ImageDraw.Draw(figure, mode="RGBA") + + y_title = padding + if title_text: + draw.text((padding, y_title), title_text, fill=(0, 0, 0, 255), font=font) + + y_panels = padding + title_height + if publication_style: + y_panels += label_height + + panel_names = { + "diffraction": "Diffraction", + "virtual": "Virtual", + "fft": "FFT", + } + annotation_map = annotations or {} + + x0 = padding + for idx, panel in enumerate(panel_images): + panel_key = panel_keys[idx] + if publication_style: + draw.text( + (x0, padding + title_height), + panel_names.get(panel_key, panel_key), + fill=(0, 0, 0, 255), + font=font, + ) + + figure.paste(panel, (x0, y_panels)) + + if publication_style: + draw.rectangle( + [(x0, y_panels), (x0 + panel.width - 1, y_panels + panel.height - 1)], + outline=(80, 80, 80, 255), + width=1, + ) + + if panel_key in annotation_map and str(annotation_map[panel_key]).strip(): + text = str(annotation_map[panel_key]).strip() + text_bbox = draw.textbbox((0, 0), text, font=font) + text_w = text_bbox[2] - text_bbox[0] + text_h = text_bbox[3] - text_bbox[1] + tx = x0 + 8 + ty = y_panels + 8 + draw.rectangle( + [(tx - 4, ty - 3), (tx + text_w + 4, ty + text_h + 3)], + fill=(0, 0, 0, 180), + ) + draw.text((tx, ty), text, fill=(255, 255, 255, 255), font=font) + + x0 += panel.width + gap + + if fmt == "pdf": + Image.init() + figure = figure.convert("RGB") + figure.save(export_path, format="PDF", resolution=dpi_value) + else: + figure.save(export_path, format="PNG", dpi=(dpi_value, dpi_value)) + + if include_metadata: + meta_path = ( + pathlib.Path(metadata_path) + if metadata_path is not None + else export_path.with_suffix(".json") + ) + metadata = self._build_image_export_metadata( + export_path=export_path, + view_key="figure", + fmt=fmt, + render_meta=render_meta, + include_overlays=overlays_enabled, + include_scalebar=scalebar_enabled, + export_kind="figure_template", + extra={ + "template": template_key, + "panels": panel_keys, + "publication_style": bool(publication_style), + "title": title_text or "", + "annotations": annotation_map, + "dpi": int(dpi_value), + }, + ) + meta_path.write_text(json.dumps(metadata, indent=2)) + finally: + if restore_state: + self.frame_idx = prev_frame + self.pos_row = prev_row + self.pos_col = prev_col + + self._record_export_event( + { + "export_kind": "figure_template", + "template": template_key, + "format": fmt, + "dpi": int(dpi_value), + "include_overlays": bool(overlays_enabled), + "include_scalebar": bool(scalebar_enabled), + "outputs": [ + self._build_file_record(export_path, metadata_path=meta_path), + ], + } + ) + return export_path + + def _resolve_frame_sequence( + self, + frame_indices: list[int] | None, + frame_range: tuple[int, int] | None, + ) -> list[int]: + if frame_indices is not None and frame_range is not None: + raise ValueError("Use either frame_indices or frame_range, not both.") + + if frame_indices is not None: + if len(frame_indices) == 0: + raise ValueError("frame_indices cannot be empty.") + return [self._validate_frame_idx(idx) for idx in frame_indices] + + if frame_range is not None: + if len(frame_range) != 2: + raise ValueError("frame_range must be a (start, end) tuple.") + start, end = int(frame_range[0]), int(frame_range[1]) + if start > end: + raise ValueError("frame_range start must be <= end.") + return [self._validate_frame_idx(idx) for idx in range(start, end + 1)] + + return [int(i) for i in range(self.n_frames)] + + def _resolve_position_sequence( + self, + mode: str, + path_points: list[tuple[int, int]] | None, + raster_step: int, + raster_bidirectional: bool, + ) -> list[tuple[int, int]]: + if mode == "path": + points = self._path_points if path_points is None else path_points + if not points: + raise ValueError( + "Path mode requires points via set_path(...) or path_points=..." + ) + return [self._validate_position((int(r), int(c))) for r, c in points] + + if mode == "raster": + step = int(raster_step) + if step < 1: + raise ValueError("raster_step must be >= 1") + points: list[tuple[int, int]] = [] + for r in range(0, self.shape_rows, step): + cols = list(range(0, self.shape_cols, step)) + if raster_bidirectional and ((r // step) % 2 == 1): + cols.reverse() + for c in cols: + points.append((int(r), int(c))) + return points + + raise ValueError(f"Unsupported position sequence mode '{mode}'") + + def suggest_adaptive_path( + self, + coarse_step: int = 4, + target_fraction: float = 0.25, + min_spacing: int = 2, + include_coarse: bool = True, + coarse_bidirectional: bool = True, + local_window: int = 5, + dose_lambda: float = 0.25, + weights: dict[str, float] | None = None, + roi_mask: np.ndarray | None = None, + update_widget_path: bool = True, + interval_ms: int | None = None, + loop: bool = False, + autoplay: bool = False, + return_maps: bool = False, + ) -> dict[str, Any]: + """ + Suggest a sparse adaptive scan path using coarse-to-fine utility ranking. + + The planner computes utility from current virtual-image and diffraction + statistics, then selects spatially distributed high-utility points. + + Parameters + ---------- + coarse_step : int, default 4 + Spacing of the initial coarse grid. + target_fraction : float, default 0.25 + Target total sampled fraction of scan positions in (0, 1]. + min_spacing : int, default 2 + Minimum pixel spacing between selected dense points. + include_coarse : bool, default True + If True, include coarse-grid points in the returned path. + coarse_bidirectional : bool, default True + Use snake ordering for coarse-grid traversal. + local_window : int, default 5 + Window size for local-std utility component. + dose_lambda : float, default 0.25 + Penalty weight for re-sampling coarse points. + weights : dict[str, float], optional + Utility weights for keys: ``vi_gradient``, ``vi_local_std``, ``dp_variance``. + roi_mask : np.ndarray, optional + Optional boolean mask of shape ``scan_shape`` restricting dense picks. + update_widget_path : bool, default True + If True, calls ``set_path(...)`` with the suggested path. + interval_ms : int, optional + Path interval when ``update_widget_path=True``. + loop : bool, default False + Path looping behavior when ``update_widget_path=True``. + autoplay : bool, default False + Start playback immediately when ``update_widget_path=True``. + return_maps : bool, default False + If True, include utility component maps in the returned dict. + + Returns + ------- + dict + Planning result with coarse points, dense points, and final path. + """ + step = int(coarse_step) + if step < 1: + raise ValueError(f"coarse_step must be >= 1, got {coarse_step}") + + frac = float(target_fraction) + if frac <= 0 or frac > 1: + raise ValueError(f"target_fraction must be in (0, 1], got {target_fraction}") + + spacing = int(min_spacing) + if spacing < 0: + raise ValueError(f"min_spacing must be >= 0, got {min_spacing}") + + if local_window < 1: + raise ValueError(f"local_window must be >= 1, got {local_window}") + + if not np.isfinite(float(dose_lambda)): + raise ValueError("dose_lambda must be finite") + + default_weights = { + "vi_gradient": 0.4, + "vi_local_std": 0.3, + "dp_variance": 0.3, + } + merged_weights = dict(default_weights) + if weights is not None: + for key, value in weights.items(): + if key not in default_weights: + raise ValueError( + f"Unsupported utility weight '{key}'. " + f"Supported: {', '.join(default_weights.keys())}" + ) + merged_weights[key] = float(value) + + weight_sum = sum(max(0.0, float(v)) for v in merged_weights.values()) + if weight_sum <= 0: + raise ValueError("At least one utility weight must be > 0.") + normalized_weights = { + key: max(0.0, float(value)) / weight_sum + for key, value in merged_weights.items() + } + + n_total = int(self.shape_rows * self.shape_cols) + target_count = int(max(1, round(frac * n_total))) + + coarse_points = self._build_coarse_points(step=step, bidirectional=bool(coarse_bidirectional)) + coarse_count = len(coarse_points) if include_coarse else 0 + if include_coarse and target_count < coarse_count: + raise ValueError( + f"target_fraction={target_fraction} gives {target_count} points, " + f"but coarse grid already has {coarse_count}. " + "Increase target_fraction or coarse_step." + ) + dense_count = target_count - coarse_count if include_coarse else target_count + dense_count = max(0, int(dense_count)) + + vi = self._get_virtual_image_array().astype(np.float32, copy=False) + grad_row, grad_col = np.gradient(vi) + vi_gradient = np.hypot(grad_row, grad_col).astype(np.float32) + + mean_local = self._box_mean_map(vi, local_window) + mean_sq_local = self._box_mean_map(vi * vi, local_window) + variance_local = np.maximum(mean_sq_local - mean_local * mean_local, 0.0) + vi_local_std = np.sqrt(variance_local).astype(np.float32) + + dp_variance = self._dp_variance_map() + + grad_score = self._normalize_score_map(vi_gradient) + local_std_score = self._normalize_score_map(vi_local_std) + dp_var_score = self._normalize_score_map(dp_variance) + + utility = ( + normalized_weights["vi_gradient"] * grad_score + + normalized_weights["vi_local_std"] * local_std_score + + normalized_weights["dp_variance"] * dp_var_score + ).astype(np.float32) + + dose_penalty = np.zeros_like(utility, dtype=np.float32) + for row, col in coarse_points: + dose_penalty[int(row), int(col)] = 1.0 + utility = utility - float(dose_lambda) * dose_penalty + + allowed_mask = np.ones((self.shape_rows, self.shape_cols), dtype=bool) + if roi_mask is not None: + mask = np.asarray(roi_mask) + if mask.shape != (self.shape_rows, self.shape_cols): + raise ValueError( + f"roi_mask shape {mask.shape} does not match scan_shape " + f"({self.shape_rows}, {self.shape_cols})" + ) + allowed_mask &= mask.astype(bool) + + excluded_mask = np.zeros_like(allowed_mask, dtype=bool) + for row, col in coarse_points: + excluded_mask[int(row), int(col)] = True + + dense_points = self._select_spaced_topk( + scores=utility, + k=dense_count, + min_spacing=spacing, + allowed_mask=allowed_mask, + excluded_mask=excluded_mask, + ) + + start_point = coarse_points[-1] if include_coarse and coarse_points else None + dense_path = self._nearest_neighbor_order(dense_points, start=start_point) + path_points = list(coarse_points) + dense_path if include_coarse else dense_path + + if update_widget_path and path_points: + interval_value = int(self.path_interval_ms if interval_ms is None else interval_ms) + if interval_value < 1: + raise ValueError(f"interval_ms must be >= 1, got {interval_value}") + self.set_path( + points=path_points, + interval_ms=interval_value, + loop=bool(loop), + autoplay=bool(autoplay), + ) + + result: dict[str, Any] = { + "target_fraction": float(frac), + "target_count": int(target_count), + "coarse_step": int(step), + "coarse_count": int(len(coarse_points)), + "dense_count": int(len(dense_points)), + "path_count": int(len(path_points)), + "weights": normalized_weights, + "dose_lambda": float(dose_lambda), + "coarse_points": coarse_points, + "dense_points": dense_points, + "path_points": path_points, + "selected_fraction": float(len(path_points) / max(1, n_total)), + } + if return_maps: + result["utility_map"] = utility + result["utility_components"] = { + "vi_gradient": grad_score, + "vi_local_std": local_std_score, + "dp_variance": dp_var_score, + "dose_penalty": dose_penalty, + } + + self._record_export_event( + { + "export_kind": "adaptive_path_suggestion", + "target_fraction": float(frac), + "target_count": int(target_count), + "coarse_step": int(step), + "coarse_count": int(len(coarse_points)), + "dense_count": int(len(dense_points)), + "path_count": int(len(path_points)), + "selected_fraction": float(len(path_points) / max(1, n_total)), + "weights": normalized_weights, + "dose_lambda": float(dose_lambda), + } + ) + return result + + def save_sequence( + self, + output_dir: str | pathlib.Path, + mode: str = "path", + view: str | None = None, + format: str | None = None, + include_metadata: bool = True, + include_overlays: bool | None = None, + include_scalebar: bool | None = None, + frame_idx: int | None = None, + position: tuple[int, int] | None = None, + path_points: list[tuple[int, int]] | None = None, + raster_step: int = 1, + raster_bidirectional: bool = False, + frame_indices: list[int] | None = None, + frame_range: tuple[int, int] | None = None, + filename_prefix: str | None = None, + manifest_name: str = "save_sequence_manifest.json", + restore_state: bool = True, + dpi: int | None = None, + ) -> pathlib.Path: + output_root = pathlib.Path(output_dir) + output_root.mkdir(parents=True, exist_ok=True) + mode_key = str(mode).strip().lower() + if mode_key not in {"path", "raster", "frames"}: + raise ValueError("mode must be one of: path, raster, frames") + + view_key = self._validate_export_view(view) + fmt = self._resolve_export_format(pathlib.Path(f"sequence.{self.export_default_format}"), format or self.export_default_format) + dpi_value = int(self.export_default_dpi if dpi is None else dpi) + overlays_enabled = ( + bool(self.export_include_overlays) + if include_overlays is None + else bool(include_overlays) + ) + scalebar_enabled = ( + bool(self.export_include_scalebar) + if include_scalebar is None + else bool(include_scalebar) + ) + if dpi_value <= 0: + raise ValueError(f"dpi must be > 0, got {dpi_value}") + + export_rows: list[dict[str, Any]] = [] + prefix = ( + str(filename_prefix).strip() + if filename_prefix is not None and str(filename_prefix).strip() + else f"{mode_key}_{view_key}" + ) + + prev_row, prev_col = self.pos_row, self.pos_col + prev_frame = self.frame_idx + frame_for_paths = self._validate_frame_idx(frame_idx) if frame_idx is not None else int(self.frame_idx) + + if mode_key == "frames": + row, col = self._validate_position(position) + frames = self._resolve_frame_sequence(frame_indices, frame_range) + jobs = [ + {"row": int(row), "col": int(col), "frame_idx": int(fi)} + for fi in frames + ] + else: + positions = self._resolve_position_sequence( + mode=mode_key, + path_points=path_points, + raster_step=raster_step, + raster_bidirectional=raster_bidirectional, + ) + jobs = [ + {"row": int(r), "col": int(c), "frame_idx": int(frame_for_paths)} + for r, c in positions + ] + + try: + for idx, job in enumerate(jobs): + row = int(job["row"]) + col = int(job["col"]) + fr = int(job["frame_idx"]) + basename = ( + f"{prefix}_{idx:04d}_f{fr:04d}_r{row:04d}_c{col:04d}.{fmt}" + ) + out_path = output_root / basename + out_meta = out_path.with_suffix(".json") if include_metadata else None + + self.save_image( + out_path, + view=view_key, + position=(row, col), + frame_idx=fr, + format=fmt, + include_metadata=include_metadata, + metadata_path=out_meta, + include_overlays=overlays_enabled, + include_scalebar=scalebar_enabled, + restore_state=False, + dpi=dpi_value, + ) + + record = { + "index": int(idx), + "row": row, + "col": col, + "frame_idx": fr, + } + record.update(self._build_file_record(out_path, metadata_path=out_meta, index=idx)) + export_rows.append(record) + finally: + if restore_state: + self.frame_idx = prev_frame + self.pos_row = prev_row + self.pos_col = prev_col + + manifest_path = output_root / str(manifest_name) + manifest_payload = { + **build_json_header("Show4DSTEM"), + "format": "json", + "export_kind": "sequence_batch", + "mode": mode_key, + "view": view_key, + "image_format": fmt, + "output_dir": str(output_root), + "filename_prefix": prefix, + "n_exports": int(len(export_rows)), + "include_overlays": bool(overlays_enabled), + "include_scalebar": bool(scalebar_enabled), + "dpi": int(dpi_value), + "scan_shape": {"rows": int(self.shape_rows), "cols": int(self.shape_cols)}, + "detector_shape": {"rows": int(self.det_rows), "cols": int(self.det_cols)}, + "exports": export_rows, + } + manifest_path.write_text(json.dumps(manifest_payload, indent=2)) + + manifest_record = self._build_file_record(manifest_path) + self._record_export_event( + { + "export_kind": "sequence_batch", + "mode": mode_key, + "view": view_key, + "format": fmt, + "n_exports": int(len(export_rows)), + "include_overlays": bool(overlays_enabled), + "include_scalebar": bool(scalebar_enabled), + "dpi": int(dpi_value), + "outputs": [manifest_record], + } + ) + return manifest_path + + def save_reproducibility_report( + self, + path: str | pathlib.Path, + ) -> pathlib.Path: + report_path = pathlib.Path(path) + report_path.parent.mkdir(parents=True, exist_ok=True) + payload = { + **build_json_header("Show4DSTEM"), + "format": "json", + "export_kind": "reproducibility_report", + "session_id": self._export_session_id, + "session_started_utc": self._export_session_started_utc, + "report_generated_utc": datetime.now(timezone.utc).isoformat(), + "scan_shape": {"rows": int(self.shape_rows), "cols": int(self.shape_cols)}, + "detector_shape": {"rows": int(self.det_rows), "cols": int(self.det_cols)}, + "n_exports": int(len(self._export_log)), + "exports": self._export_log, + } + report_path.write_text(json.dumps(payload, indent=2)) + return report_path + + def _normalize_frame(self, frame: np.ndarray) -> np.ndarray: + mode = self.dp_scale_mode + scaled = self._apply_scale_mode(frame, mode, self.dp_power_exp) + if self.dp_vmin is not None and self.dp_vmax is not None: + fmin = float(self._apply_scale_mode( + np.array([max(self.dp_vmin, 0)], dtype=np.float32), mode, self.dp_power_exp + )[0]) + fmax = float(self._apply_scale_mode( + np.array([max(self.dp_vmax, 0)], dtype=np.float32), mode, self.dp_power_exp + )[0]) + else: + fmin = float(scaled.min()) + fmax = float(scaled.max()) + fmin, fmax = self._slider_range(fmin, fmax, self.dp_vmin_pct, self.dp_vmax_pct) + if fmax > fmin: + return np.clip((scaled - fmin) / (fmax - fmin) * 255, 0, 255).astype(np.uint8) + return np.zeros(frame.shape, dtype=np.uint8) + + def _on_gif_export(self, change=None): + if not self._gif_export_requested: + return + self._gif_export_requested = False + self._generate_gif() + + def _generate_gif(self): + import io + + from matplotlib import colormaps + from PIL import Image + + if not self._path_points: + with self.hold_sync(): + self._gif_data = b"" + self._gif_metadata_json = "" + return + + cmap_fn = colormaps.get_cmap(self.dp_colormap) + duration_ms = max(10, self.path_interval_ms) + + pil_frames = [] + for row, col in self._path_points: + row = max(0, min(self.shape_rows - 1, row)) + col = max(0, min(self.shape_cols - 1, col)) + frame = self._get_frame(row, col).astype(np.float32) + normalized = self._normalize_frame(frame) + rgba = cmap_fn(normalized / 255.0) + rgb = (rgba[:, :, :3] * 255).astype(np.uint8) + pil_frames.append(Image.fromarray(rgb)) + + if not pil_frames: + return + + buf = io.BytesIO() + pil_frames[0].save( + buf, + format="GIF", + save_all=True, + append_images=pil_frames[1:], + duration=duration_ms, + loop=0, + ) + metadata = { + **build_json_header("Show4DSTEM"), + "view": "diffraction", + "format": "gif", + "export_kind": "path_animation", + "n_frames": int(len(pil_frames)), + "duration_ms": int(duration_ms), + "path_loop": bool(self.path_loop), + "path_points": [{"row": int(row), "col": int(col)} for row, col in self._path_points], + "frame_idx": int(self.frame_idx), + "n_frames_total": int(self.n_frames), + "scan_shape": {"rows": int(self.shape_rows), "cols": int(self.shape_cols)}, + "detector_shape": {"rows": int(self.det_rows), "cols": int(self.det_cols)}, + "calibration": self._calibration_metadata(), + "display": { + "diffraction": { + "colormap": self.dp_colormap, + "scale_mode": self.dp_scale_mode, + "vmin_pct": float(self.dp_vmin_pct), + "vmax_pct": float(self.dp_vmax_pct), + } + }, + } + with self.hold_sync(): + self._gif_metadata_json = json.dumps(metadata, indent=2) + self._gif_data = buf.getvalue() + + def _update_frame(self, change=None): + """Send raw float32 frame to frontend (JS handles scale/colormap).""" + if self._data is None: + return + # Get frame as tensor (stays on device) + data = self._frame_data + if data.ndim == 3: + idx = self.pos_row * self.shape_cols + self.pos_col + frame = data[idx] + else: + frame = data[self.pos_row, self.pos_col] + + # Compute stats from frame (optionally mask DC component) + if self.mask_dc and self.det_rows > 3 and self.det_cols > 3: + # Mask center 3x3 region for stats using detected center (not geometric center) + cr = int(round(self.center_row)) + cc = int(round(self.center_col)) + cr = max(1, min(self.det_rows - 2, cr)) + cc = max(1, min(self.det_cols - 2, cc)) + mask = torch.ones_like(frame, dtype=torch.bool) + mask[cr-1:cr+2, cc-1:cc+2] = False + masked_vals = frame[mask] + self.dp_stats = [ + float(masked_vals.mean()), + float(masked_vals.min()), + float(masked_vals.max()), + float(masked_vals.std()), + ] + else: + self.dp_stats = [ + float(frame.mean()), + float(frame.min()), + float(frame.max()), + float(frame.std()), + ] + + # Convert to numpy only for sending bytes to frontend + self.frame_bytes = frame.cpu().numpy().tobytes() + + def _on_roi_change(self, change=None): + """Recompute virtual image when individual ROI params change. + + High-frequency drag updates use the compound roi_center trait instead. + """ + if not self.roi_active: + return + self._compute_virtual_image_from_roi() + + def _on_roi_center_change(self, change=None): + """Handle batched roi_center updates from JS (single observer for row+col). + + This is the fast path for drag operations. JS sends [row, col] as a single + compound trait, so only one observer fires per mouse move. + """ + if not self.roi_active: + return + if change and "new" in change: + row, col = change["new"] + # Sync to individual traits (without triggering _on_roi_change observers) + self.unobserve(self._on_roi_change, names=["roi_center_col", "roi_center_row"]) + self.roi_center_row = row + self.roi_center_col = col + self.observe(self._on_roi_change, names=["roi_center_col", "roi_center_row"]) + self._compute_virtual_image_from_roi() + + def _on_vi_roi_change(self, change=None): + """Compute summed DP when VI ROI changes.""" + if self.vi_roi_mode == "off": + self.summed_dp_bytes = b"" + self.summed_dp_count = 0 + return + self._compute_summed_dp_from_vi_roi() + + def _compute_summed_dp_from_vi_roi(self): + """Sum diffraction patterns from positions inside VI ROI (PyTorch).""" + if self._data is None: + return + # Create mask in scan space using cached coordinates + if self.vi_roi_mode == "circle": + mask = (self._scan_row_coords - self.vi_roi_center_row) ** 2 + (self._scan_col_coords - self.vi_roi_center_col) ** 2 <= self.vi_roi_radius ** 2 + elif self.vi_roi_mode == "square": + half_size = self.vi_roi_radius + mask = (torch.abs(self._scan_row_coords - self.vi_roi_center_row) <= half_size) & (torch.abs(self._scan_col_coords - self.vi_roi_center_col) <= half_size) + elif self.vi_roi_mode == "rect": + half_w = self.vi_roi_width / 2 + half_h = self.vi_roi_height / 2 + mask = (torch.abs(self._scan_row_coords - self.vi_roi_center_row) <= half_h) & (torch.abs(self._scan_col_coords - self.vi_roi_center_col) <= half_w) + else: + return + + # Count positions in mask + n_positions = int(mask.sum()) + if n_positions == 0: + self.summed_dp_bytes = b"" + self.summed_dp_count = 0 + return + + self.summed_dp_count = n_positions + + # Compute average DP using masked sum (vectorized) + data = self._frame_data + if data.ndim == 4: + # (scan_rows, scan_cols, det_rows, det_cols) - sum over masked scan positions + avg_dp = data[mask].mean(dim=0) + else: + # Flattened: (N, det_rows, det_cols) - need to convert mask indices + flat_indices = torch.nonzero(mask.flatten(), as_tuple=True)[0] + avg_dp = data[flat_indices].mean(dim=0) + + # Send raw float32 (consistent with other data paths — JS handles normalization) + self.summed_dp_bytes = avg_dp.cpu().numpy().tobytes() + + def _create_circular_mask(self, cx: float, cy: float, radius: float): + """Create circular mask (boolean tensor on device).""" + mask = (self._det_col_coords - cx) ** 2 + (self._det_row_coords - cy) ** 2 <= radius ** 2 + return mask + + def _create_square_mask(self, cx: float, cy: float, half_size: float): + """Create square mask (boolean tensor on device).""" + mask = (torch.abs(self._det_col_coords - cx) <= half_size) & (torch.abs(self._det_row_coords - cy) <= half_size) + return mask + + def _create_annular_mask( + self, cx: float, cy: float, inner: float, outer: float + ): + """Create annular (donut) mask (boolean tensor on device).""" + dist_sq = (self._det_col_coords - cx) ** 2 + (self._det_row_coords - cy) ** 2 + mask = (dist_sq >= inner ** 2) & (dist_sq <= outer ** 2) + return mask + + def _create_rect_mask(self, cx: float, cy: float, half_width: float, half_height: float): + """Create rectangular mask (boolean tensor on device).""" + mask = (torch.abs(self._det_col_coords - cx) <= half_width) & (torch.abs(self._det_row_coords - cy) <= half_height) + return mask + + def _precompute_common_virtual_images(self): + """Pre-compute BF/ABF/ADF virtual images for instant preset switching.""" + cx, cy, bf = self.center_col, self.center_row, self.bf_radius + # Cache (bytes, stats, min, max) for each preset + bf_arr = self._fast_masked_sum(self._create_circular_mask(cx, cy, bf)) + abf_arr = self._fast_masked_sum(self._create_annular_mask(cx, cy, bf * 0.5, bf)) + adf_arr = self._fast_masked_sum(self._create_annular_mask(cx, cy, bf, bf * 4.0)) + + self._cached_bf_virtual = ( + self._to_float32_bytes(bf_arr, update_vi_stats=False), + [float(bf_arr.mean()), float(bf_arr.min()), float(bf_arr.max()), float(bf_arr.std())], + float(bf_arr.min()), float(bf_arr.max()) + ) + self._cached_abf_virtual = ( + self._to_float32_bytes(abf_arr, update_vi_stats=False), + [float(abf_arr.mean()), float(abf_arr.min()), float(abf_arr.max()), float(abf_arr.std())], + float(abf_arr.min()), float(abf_arr.max()) + ) + self._cached_adf_virtual = ( + self._to_float32_bytes(adf_arr, update_vi_stats=False), + [float(adf_arr.mean()), float(adf_arr.min()), float(adf_arr.max()), float(adf_arr.std())], + float(adf_arr.min()), float(adf_arr.max()) + ) + + def _get_cached_preset(self) -> tuple[bytes, list[float], float, float] | None: + """Check if current ROI matches a cached preset and return (bytes, stats, min, max) tuple.""" + # Must be centered on detector center + if abs(self.roi_center_col - self.center_col) >= 1 or abs(self.roi_center_row - self.center_row) >= 1: + return None + + bf = self.bf_radius + + # BF: circle at bf_radius + if (self.roi_mode == "circle" and abs(self.roi_radius - bf) < 1): + return self._cached_bf_virtual + + # ABF: annular at 0.5*bf to bf + if (self.roi_mode == "annular" and + abs(self.roi_radius_inner - bf * 0.5) < 1 and + abs(self.roi_radius - bf) < 1): + return self._cached_abf_virtual + + # ADF: annular at bf to 4*bf (combines LAADF + HAADF) + if (self.roi_mode == "annular" and + abs(self.roi_radius_inner - bf) < 1 and + abs(self.roi_radius - bf * 4.0) < 1): + return self._cached_adf_virtual + + return None + + def _virtual_image_for_frame(self, frame_idx: int) -> np.ndarray: + """Compute virtual image array for a specific frame without mutating traits.""" + data = self._data[frame_idx] if self.n_frames > 1 else self._data + cx, cy = self.roi_center_col, self.roi_center_row + if self.roi_mode == "circle" and self.roi_radius > 0: + mask = self._create_circular_mask(cx, cy, self.roi_radius) + elif self.roi_mode == "square" and self.roi_radius > 0: + mask = self._create_square_mask(cx, cy, self.roi_radius) + elif self.roi_mode == "annular" and self.roi_radius > 0: + mask = self._create_annular_mask(cx, cy, self.roi_radius_inner, self.roi_radius) + elif self.roi_mode == "rect" and self.roi_width > 0 and self.roi_height > 0: + mask = self._create_rect_mask(cx, cy, self.roi_width / 2, self.roi_height / 2) + else: + row = int(max(0, min(round(cy), self._det_shape[0] - 1))) + col = int(max(0, min(round(cx), self._det_shape[1] - 1))) + if data.ndim == 4: + vi = data[:, :, row, col] + else: + vi = data[:, row, col].reshape(self._scan_shape) + return vi.cpu().numpy().astype(np.float32, copy=False) + mask_float = mask.float() + n_det = self._det_shape[0] * self._det_shape[1] + n_nonzero = int(mask.sum()) + coverage = n_nonzero / n_det + if coverage < SPARSE_MASK_THRESHOLD: + indices = torch.nonzero(mask_float.flatten(), as_tuple=True)[0] + n_scan = self._scan_shape[0] * self._scan_shape[1] + data_flat = data.reshape(n_scan, n_det) + result = data_flat[:, indices].sum(dim=1).reshape(self._scan_shape) + else: + if data.ndim == 3: + data_4d = data.reshape(self._scan_shape[0], self._scan_shape[1], *self._det_shape) + else: + data_4d = data + result = torch.tensordot(data_4d, mask_float, dims=([2, 3], [0, 1])) + return result.cpu().numpy().astype(np.float32, copy=False) + + def _fast_masked_sum(self, mask: torch.Tensor) -> torch.Tensor: + """Compute masked sum using PyTorch. + + Uses sparse indexing for small masks (<20% coverage) which is faster + because it only processes non-zero pixels: + - r=10 (1%): ~0.8ms (sparse) vs ~13ms (full) + - r=30 (8%): ~4ms (sparse) vs ~13ms (full) + + For large masks (≥20%), uses full tensordot which has constant ~13ms. + """ + data = self._frame_data + mask_float = mask.float() + n_det = self._det_shape[0] * self._det_shape[1] + n_nonzero = int(mask.sum()) + coverage = n_nonzero / n_det + + if coverage < SPARSE_MASK_THRESHOLD: + # Sparse: faster for small masks + indices = torch.nonzero(mask_float.flatten(), as_tuple=True)[0] + n_scan = self._scan_shape[0] * self._scan_shape[1] + data_flat = data.reshape(n_scan, n_det) + result = data_flat[:, indices].sum(dim=1).reshape(self._scan_shape) + else: + # Tensordot: faster for large masks + # Reshape to 4D if needed (3D flattened data) + if data.ndim == 3: + data_4d = data.reshape(self._scan_shape[0], self._scan_shape[1], *self._det_shape) + else: + data_4d = data + result = torch.tensordot(data_4d, mask_float, dims=([2, 3], [0, 1])) + + return result + + def _to_float32_bytes(self, arr: torch.Tensor, update_vi_stats: bool = True) -> bytes: + """Convert tensor to float32 bytes.""" + # Compute min/max (fast on GPU) + vmin = float(arr.min()) + vmax = float(arr.max()) + + # Only update traits when requested (avoids side effects during precomputation) + if update_vi_stats: + self.vi_data_min = vmin + self.vi_data_max = vmax + self.vi_stats = [float(arr.mean()), vmin, vmax, float(arr.std())] + + return arr.cpu().numpy().tobytes() + + def _compute_virtual_image_from_roi(self): + """Compute virtual image based on ROI mode.""" + if self._data is None: + return + cached = self._get_cached_preset() + if cached is not None: + # Cached preset returns (bytes, stats, min, max) tuple + vi_bytes, vi_stats, vi_min, vi_max = cached + self.virtual_image_bytes = vi_bytes + self.vi_stats = vi_stats + self.vi_data_min = vi_min + self.vi_data_max = vi_max + return + + cx, cy = self.roi_center_col, self.roi_center_row + + if self.roi_mode == "circle" and self.roi_radius > 0: + mask = self._create_circular_mask(cx, cy, self.roi_radius) + elif self.roi_mode == "square" and self.roi_radius > 0: + mask = self._create_square_mask(cx, cy, self.roi_radius) + elif self.roi_mode == "annular" and self.roi_radius > 0: + mask = self._create_annular_mask(cx, cy, self.roi_radius_inner, self.roi_radius) + elif self.roi_mode == "rect" and self.roi_width > 0 and self.roi_height > 0: + mask = self._create_rect_mask(cx, cy, self.roi_width / 2, self.roi_height / 2) + else: + # Point mode: single-pixel indexing + row = int(max(0, min(round(cy), self._det_shape[0] - 1))) + col = int(max(0, min(round(cx), self._det_shape[1] - 1))) + data = self._frame_data + if data.ndim == 4: + virtual_image = data[:, :, row, col] + else: + virtual_image = data[:, row, col].reshape(self._scan_shape) + self.virtual_image_bytes = self._to_float32_bytes(virtual_image) + return + + self.virtual_image_bytes = self._to_float32_bytes(self._fast_masked_sum(mask)) + + +bind_tool_runtime_api(Show4DSTEM, "Show4DSTEM") diff --git a/widget/src/quantem/widget/tool_parity.json b/widget/src/quantem/widget/tool_parity.json new file mode 100644 index 00000000..4271533a --- /dev/null +++ b/widget/src/quantem/widget/tool_parity.json @@ -0,0 +1,93 @@ +{ + "widgets": { + "Show2D": { + "tool_groups": ["display", "histogram", "stats", "navigation", "view", "export", "roi", "profile", "all"], + "aliases": {} + }, + "Show3D": { + "tool_groups": ["display", "histogram", "stats", "playback", "view", "export", "roi", "profile", "all"], + "aliases": { + "navigation": "playback" + } + }, + "Show3DVolume": { + "tool_groups": ["display", "histogram", "playback", "fft", "navigation", "stats", "export", "view", "volume", "all"], + "aliases": {} + }, + "Show4D": { + "tool_groups": ["display", "roi", "histogram", "profile", "navigation", "playback", "stats", "export", "view", "fft", "all"], + "aliases": {} + }, + "Show4DSTEM": { + "tool_groups": ["display", "histogram", "stats", "navigation", "playback", "view", "export", "roi", "profile", "fft", "virtual", "frame", "all"], + "aliases": {} + }, + "ShowComplex2D": { + "tool_groups": ["display", "histogram", "fft", "roi", "stats", "export", "view", "all"], + "aliases": {} + }, + "Mark2D": { + "tool_groups": ["points", "roi", "profile", "display", "marker_style", "snap", "navigation", "view", "export", "all"], + "aliases": {} + }, + "Edit2D": { + "tool_groups": ["mode", "edit", "display", "histogram", "stats", "navigation", "export", "view", "all"], + "aliases": {} + }, + "Align2D": { + "tool_groups": ["alignment", "overlay", "display", "histogram", "stats", "export", "view", "all"], + "aliases": {} + }, + "Align2DBulk": { + "tool_groups": ["display", "histogram", "navigation", "stats", "view", "export", "all"], + "aliases": {} + }, + "Bin4D": { + "tool_groups": ["display", "binning", "mask", "preview", "stats", "export", "all"], + "aliases": {} + }, + "Browse": { + "tool_groups": ["navigation", "filter", "preview", "all"], + "aliases": {} + }, + "Bin2D": { + "tool_groups": ["display", "binning", "histogram", "stats", "navigation", "export", "all"], + "aliases": {} + }, + "Show1D": { + "tool_groups": ["display", "peaks", "stats", "export", "all"], + "aliases": {} + }, + "MetricExplorer": { + "tool_groups": ["display", "export", "all"], + "aliases": {} + }, + "ShowDiffraction": { + "tool_groups": ["display", "histogram", "stats", "navigation", "view", "export", "spots", "all"], + "aliases": {} + } + }, + "viewer_widgets": ["Show1D", "Show2D", "Show3D", "Show3DVolume", "Show4D", "Show4DSTEM", "ShowComplex2D"], + "control_presets": { + "all": { + "label": "All", + "show_groups": ["*"] + }, + "compact": { + "label": "Compact", + "show_groups": ["mode", "edit", "display", "navigation", "playback", "view", "export", "fft"] + }, + "mask_focus": { + "label": "Mask Focus", + "show_groups": ["edit", "display", "roi", "histogram", "stats", "navigation", "playback", "view", "export", "fft", "virtual", "frame"] + }, + "crop_focus": { + "label": "Crop Focus", + "show_groups": ["mode", "edit", "display", "histogram", "stats", "navigation", "view", "export"] + }, + "spectroscopy": { + "label": "Spectroscopy", + "show_groups": ["display", "peaks", "stats"] + } + } +} diff --git a/widget/src/quantem/widget/tool_parity.py b/widget/src/quantem/widget/tool_parity.py new file mode 100644 index 00000000..d5d4f84e --- /dev/null +++ b/widget/src/quantem/widget/tool_parity.py @@ -0,0 +1,184 @@ +"""Shared tool visibility/locking registry and helpers.""" + +from __future__ import annotations + +import json +import pathlib +from functools import lru_cache +from typing import Any + +_REGISTRY_PATH = pathlib.Path(__file__).with_name("tool_parity.json") + + +@lru_cache(maxsize=1) +def _load_registry() -> dict[str, Any]: + return json.loads(_REGISTRY_PATH.read_text()) + + +def get_widget_tool_groups(widget_name: str) -> tuple[str, ...]: + registry = _load_registry() + widgets = registry.get("widgets", {}) + if widget_name not in widgets: + supported = ", ".join(sorted(widgets)) + raise ValueError(f"Unknown widget {widget_name!r}. Supported widgets: {supported}.") + return tuple(str(v).strip().lower() for v in widgets[widget_name].get("tool_groups", [])) + + +def get_widget_tool_aliases(widget_name: str) -> dict[str, str]: + registry = _load_registry() + widgets = registry.get("widgets", {}) + if widget_name not in widgets: + supported = ", ".join(sorted(widgets)) + raise ValueError(f"Unknown widget {widget_name!r}. Supported widgets: {supported}.") + aliases = widgets[widget_name].get("aliases", {}) + return {str(k).strip().lower(): str(v).strip().lower() for k, v in aliases.items()} + + +def normalize_tool_groups(widget_name: str, tool_groups) -> list[str]: + if tool_groups is None: + return [] + if isinstance(tool_groups, str): + values = [tool_groups] + else: + values = list(tool_groups) + + order = get_widget_tool_groups(widget_name) + aliases = get_widget_tool_aliases(widget_name) + supported = set(order) + normalized: list[str] = [] + seen: set[str] = set() + + for raw in values: + key = str(raw).strip().lower() + if not key: + continue + key = aliases.get(key, key) + if key not in supported: + supported_values = ", ".join(f'"{k}"' for k in order) + raise ValueError( + f"Unknown tool group {raw!r}. Supported values: {supported_values}." + ) + if key == "all": + return ["all"] + if key not in seen: + seen.add(key) + normalized.append(key) + return normalized + + +def build_tool_groups( + widget_name: str, + *, + tool_groups=None, + all_flag: bool = False, + flag_map: dict[str, bool] | None = None, +) -> list[str]: + if all_flag: + return ["all"] + values: list[str] = [] + if tool_groups is not None: + if isinstance(tool_groups, str): + values.append(tool_groups) + else: + values.extend(tool_groups) + for key, enabled in (flag_map or {}).items(): + if enabled: + values.append(key) + return normalize_tool_groups(widget_name, values) + + +def resolve_control_preset_hidden_tools(widget_name: str, preset_id: str) -> list[str]: + preset_key = str(preset_id).strip().lower() + presets = _load_registry().get("control_presets", {}) + if preset_key not in presets: + supported = ", ".join(sorted(presets)) + raise ValueError(f"Unknown control preset {preset_id!r}. Supported presets: {supported}.") + + show_groups = [str(v).strip().lower() for v in presets[preset_key].get("show_groups", [])] + supported_groups = [g for g in get_widget_tool_groups(widget_name) if g != "all"] + if "*" in show_groups: + return [] + show_set = set(show_groups) + hidden = [group for group in supported_groups if group not in show_set] + return normalize_tool_groups(widget_name, hidden) + + +def _flatten_groups(groups: tuple[Any, ...]) -> list[Any]: + if len(groups) == 1 and isinstance(groups[0], (list, tuple, set)): + return list(groups[0]) + return list(groups) + + +def _expanded_without_all(widget_name: str, values) -> list[str]: + normalized = normalize_tool_groups(widget_name, values) + if "all" not in normalized: + return normalized + return [group for group in get_widget_tool_groups(widget_name) if group != "all"] + + +def _ordered_groups(widget_name: str, values: set[str]) -> list[str]: + return [group for group in get_widget_tool_groups(widget_name) if group != "all" and group in values] + + +def bind_tool_runtime_api(cls, widget_name: str) -> None: + """Attach runtime lock/hide helpers to a widget class.""" + + def set_disabled_tools(self, tool_groups) -> Any: + self.disabled_tools = normalize_tool_groups(widget_name, tool_groups) + return self + + def set_hidden_tools(self, tool_groups) -> Any: + self.hidden_tools = normalize_tool_groups(widget_name, tool_groups) + return self + + def lock_tool(self, *tool_groups) -> Any: + new_groups = _flatten_groups(tool_groups) + if not new_groups: + return self + current = _expanded_without_all(widget_name, self.disabled_tools) + requested = _expanded_without_all(widget_name, new_groups) + merged = set(current).union(requested) + self.disabled_tools = _ordered_groups(widget_name, merged) + return self + + def unlock_tool(self, *tool_groups) -> Any: + remove_groups = _flatten_groups(tool_groups) + if not remove_groups: + return self + current = set(_expanded_without_all(widget_name, self.disabled_tools)) + requested = set(_expanded_without_all(widget_name, remove_groups)) + current.difference_update(requested) + self.disabled_tools = _ordered_groups(widget_name, current) + return self + + def hide_tool(self, *tool_groups) -> Any: + new_groups = _flatten_groups(tool_groups) + if not new_groups: + return self + current = _expanded_without_all(widget_name, self.hidden_tools) + requested = _expanded_without_all(widget_name, new_groups) + merged = set(current).union(requested) + self.hidden_tools = _ordered_groups(widget_name, merged) + return self + + def show_tool(self, *tool_groups) -> Any: + remove_groups = _flatten_groups(tool_groups) + if not remove_groups: + return self + current = set(_expanded_without_all(widget_name, self.hidden_tools)) + requested = set(_expanded_without_all(widget_name, remove_groups)) + current.difference_update(requested) + self.hidden_tools = _ordered_groups(widget_name, current) + return self + + def apply_control_preset(self, preset: str) -> Any: + self.hidden_tools = resolve_control_preset_hidden_tools(widget_name, preset) + return self + + cls.set_disabled_tools = set_disabled_tools # type: ignore[attr-defined] + cls.set_hidden_tools = set_hidden_tools # type: ignore[attr-defined] + cls.lock_tool = lock_tool # type: ignore[attr-defined] + cls.unlock_tool = unlock_tool # type: ignore[attr-defined] + cls.hide_tool = hide_tool # type: ignore[attr-defined] + cls.show_tool = show_tool # type: ignore[attr-defined] + cls.apply_control_preset = apply_control_preset # type: ignore[attr-defined] diff --git a/widget/tsconfig.json b/widget/tsconfig.json new file mode 100644 index 00000000..8b4afe79 --- /dev/null +++ b/widget/tsconfig.json @@ -0,0 +1,25 @@ +{ + "include": [ + "js" + ], + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "lib": [ + "ES2020", + "DOM", + "DOM.Iterable" + ], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + } +} \ No newline at end of file diff --git a/widget/vite.config.js b/widget/vite.config.js index 8f303083..291b84aa 100644 --- a/widget/vite.config.js +++ b/widget/vite.config.js @@ -9,14 +9,17 @@ export default defineConfig({ }, build: { outDir: "src/quantem/widget/static", - lib: { - entry: "js/index.jsx", - formats: ["es"], - fileName: "index", - }, + emptyOutDir: true, rollupOptions: { + input: { + show2d: "js/show2d/index.tsx", + show4dstem: "js/show4dstem/index.tsx", + }, output: { - inlineDynamicImports: true, + entryFileNames: "[name].js", + assetFileNames: "[name][extname]", + format: "es", + inlineDynamicImports: false, }, }, }, From 9c3d93013973f9472b336f5def913142046cad6a Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 2 May 2026 14:29:29 -0700 Subject: [PATCH 270/335] refactor: modernize type hints in widget package (PEP 604/585) --- widget/src/quantem/widget/show2d.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/widget/src/quantem/widget/show2d.py b/widget/src/quantem/widget/show2d.py index 08031ac7..7c7ef16a 100644 --- a/widget/src/quantem/widget/show2d.py +++ b/widget/src/quantem/widget/show2d.py @@ -13,7 +13,7 @@ import math import warnings from enum import StrEnum -from typing import Optional, Union, List, Self +from typing import Self import anywidget import matplotlib @@ -253,7 +253,7 @@ class Show2D(anywidget.AnyWidget): image_rotations = traitlets.List(traitlets.Int(), []).tag(sync=True) @classmethod - def _normalize_tool_groups(cls, tool_groups) -> List[str]: + def _normalize_tool_groups(cls, tool_groups) -> list[str]: return normalize_tool_groups("Show2D", tool_groups) @classmethod @@ -269,7 +269,7 @@ def _build_disabled_tools( disable_roi: bool = False, disable_profile: bool = False, disable_all: bool = False, - ) -> List[str]: + ) -> list[str]: return build_tool_groups( "Show2D", tool_groups=disabled_tools, @@ -299,7 +299,7 @@ def _build_hidden_tools( hide_roi: bool = False, hide_profile: bool = False, hide_all: bool = False, - ) -> List[str]: + ) -> list[str]: return build_tool_groups( "Show2D", tool_groups=hidden_tools, @@ -326,10 +326,10 @@ def _validate_hidden_tools(self, proposal): def __init__( self, - data: Union[np.ndarray, List[np.ndarray]], - labels: Optional[List[str]] = None, + data: np.ndarray | list[np.ndarray], + labels: list[str | None] = None, title: str = "", - cmap: Union[str, Colormap] = Colormap.INFERNO, + cmap: str | Colormap = Colormap.INFERNO, pixel_size: float = 0.0, scale_bar_visible: bool = True, show_fft: bool = False, @@ -340,7 +340,7 @@ def __init__( auto_contrast: bool = False, vmin: float | list | None = None, vmax: float | list | None = None, - disabled_tools: Optional[List[str]] = None, + disabled_tools: list[str | None] = None, disable_display: bool = False, disable_histogram: bool = False, disable_stats: bool = False, @@ -350,7 +350,7 @@ def __init__( disable_roi: bool = False, disable_profile: bool = False, disable_all: bool = False, - hidden_tools: Optional[List[str]] = None, + hidden_tools: list[str | None] = None, hide_display: bool = False, hide_histogram: bool = False, hide_stats: bool = False, @@ -371,7 +371,7 @@ def __init__( link_contrast: bool = True, diff_mode: bool = False, view_box: tuple | list | None = None, - display_bin: Union[int, str] = "auto", + display_bin: int | str = "auto", state=None, **kwargs, ): From b245948629fc0ae3fb2d2ad21ee08cdce722c39f Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 2 May 2026 14:33:40 -0700 Subject: [PATCH 271/335] feat: first-class Dataset2d/Dataset3d support, drop cupy, improve errors --- widget/src/quantem/widget/array_utils.py | 291 +++++------------------ widget/src/quantem/widget/show2d.py | 15 +- widget/src/quantem/widget/show4dstem.py | 6 +- 3 files changed, 73 insertions(+), 239 deletions(-) diff --git a/widget/src/quantem/widget/array_utils.py b/widget/src/quantem/widget/array_utils.py index e86633e6..a38190e6 100644 --- a/widget/src/quantem/widget/array_utils.py +++ b/widget/src/quantem/widget/array_utils.py @@ -1,282 +1,111 @@ """ -Array utilities for handling NumPy, CuPy, and PyTorch arrays uniformly. - -This module provides utilities to convert arrays from different backends -into NumPy arrays for widget processing. +Array utilities for widgets. Supports NumPy + PyTorch input. """ -from typing import Any, Literal +from typing import Literal import numpy as np try: import torch - import torch.nn.functional as F _HAS_TORCH = True except ImportError: _HAS_TORCH = False -ArrayBackend = Literal["numpy", "cupy", "torch", "unknown"] - - -def get_array_backend(data: Any) -> ArrayBackend: - """ - Detect the array backend of the input data. +ArrayBackend = Literal["numpy", "torch", "unknown"] - Parameters - ---------- - data : array-like - Input array (NumPy, CuPy, PyTorch, or other). - Returns - ------- - str - One of: "numpy", "cupy", "torch", "unknown" - """ - # Check PyTorch first (has both .numpy and .detach methods) - if hasattr(data, "detach") and hasattr(data, "numpy"): +def get_array_backend(data) -> ArrayBackend: + """Detect array backend. Returns 'numpy', 'torch', or 'unknown'.""" + if _HAS_TORCH and isinstance(data, torch.Tensor): return "torch" - # Check CuPy (has .get() or __cuda_array_interface__) - if hasattr(data, "__cuda_array_interface__"): - return "cupy" - if hasattr(data, "get") and hasattr(data, "__array__"): - # CuPy arrays have .get() to transfer to CPU - type_name = type(data).__module__ - if "cupy" in type_name: - return "cupy" - # Check NumPy if isinstance(data, np.ndarray): return "numpy" return "unknown" -def to_numpy(data: Any, dtype: np.dtype | None = None) -> np.ndarray: - """ - Convert any array-like (NumPy, CuPy, PyTorch) to a NumPy array. +def to_numpy(data, dtype: np.dtype | None = None) -> np.ndarray: + """Convert NumPy or PyTorch array to NumPy. Parameters ---------- - data : array-like - Input array from any supported backend. + data : np.ndarray or torch.Tensor + Input array. dtype : np.dtype, optional - Target dtype for the output array. If None, preserves original dtype. + Target dtype. Returns ------- np.ndarray - NumPy array with the same data. Examples -------- >>> import numpy as np - >>> from quantem.widget.array_utils import to_numpy - >>> - >>> # NumPy passthrough - >>> arr = np.random.rand(10, 10) - >>> result = to_numpy(arr) - >>> - >>> # CuPy conversion (if available) - >>> import cupy as cp - >>> gpu_arr = cp.random.rand(10, 10) - >>> cpu_arr = to_numpy(gpu_arr) - >>> - >>> # PyTorch conversion (if available) + >>> to_numpy(np.zeros((4, 4))) >>> import torch - >>> tensor = torch.rand(10, 10) - >>> arr = to_numpy(tensor) + >>> to_numpy(torch.zeros(4, 4)) + + Raises + ------ + TypeError + If `data` is not a NumPy array or PyTorch tensor. """ backend = get_array_backend(data) - if backend == "torch": - # PyTorch tensor: detach from graph, move to CPU, convert to numpy result = data.detach().cpu().numpy() - - elif backend == "cupy": - # CuPy array: use .get() to transfer to CPU - if hasattr(data, "get"): - result = data.get() - else: - # Fallback for __cuda_array_interface__ - import cupy as cp - - result = cp.asnumpy(data) - elif backend == "numpy": - # NumPy array: passthrough (may copy if dtype changes) result = data - else: - # Unknown backend: try np.asarray as fallback - result = np.asarray(data) - - # Apply dtype conversion if specified + # Try np.asarray as last-resort fallback for things like Dataset arrays + try: + result = np.asarray(data) + except Exception as e: + raise TypeError( + f"to_numpy expected a NumPy array or PyTorch tensor, got {type(data).__name__}. " + f"Convert your input via np.asarray(...) or tensor.cpu().numpy() first." + ) from e if dtype is not None: result = np.asarray(result, dtype=dtype) - return result -def bin2d(data, factor: int = 2, mode: str = "mean", edge_mode: str = "crop") -> np.ndarray: - """ - Spatial binning for 2D or 3D arrays. - - Uses torch GPU (MPS/CUDA) when available for large arrays (~5× faster on 4K data). - - Parameters - ---------- - data : array-like - Input array with shape ``(H, W)`` or ``(N, H, W)``. - factor : int, default 2 - Bin factor. - mode : str, default "mean" - Reduction mode: ``"mean"`` or ``"sum"``. - edge_mode : str, default "crop" - How to handle dimensions not divisible by *factor*: - ``"crop"`` trims extra pixels, ``"pad"`` zero-pads to the next - multiple (output shape uses ``ceil(dim / factor)``). - - Returns - ------- - np.ndarray - Binned array, dtype float32. - """ - arr = to_numpy(data) - if arr.dtype != np.float32: - arr = arr.astype(np.float32) - - # Torch GPU fast path: only for arrays between 1M and 500M elements. - # Larger arrays hit MPS memory transfer bottleneck (>2 GB transfer > CPU compute). - import torch - if 1_000_000 < arr.size < 500_000_000 and (torch.backends.mps.is_available() or torch.cuda.is_available()): - dev = torch.device("mps" if torch.backends.mps.is_available() else "cuda") - t = torch.from_numpy(arr).to(dev) - if t.ndim == 2: - h, w = t.shape - oh = h // factor * factor - ow = w // factor * factor - t = t[:oh, :ow].reshape(oh // factor, factor, ow // factor, factor) - t = t.sum(dim=(1, 3)) if mode == "sum" else t.mean(dim=(1, 3)) - elif t.ndim == 3: - n, h, w = t.shape - oh = h // factor * factor - ow = w // factor * factor - t = t[:, :oh, :ow].reshape(n, oh // factor, factor, ow // factor, factor) - t = t.sum(dim=(2, 4)) if mode == "sum" else t.mean(dim=(2, 4)) - return t.cpu().numpy().astype(np.float32) - - # CPU fallback (no GPU available or small array) - reduce = np.ndarray.sum if mode == "sum" else np.ndarray.mean - if arr.ndim == 2: - arr = _pad_or_crop_2d(arr, factor, edge_mode) - h, w = arr.shape - oh, ow = h // factor, w // factor - return reduce(arr.reshape(oh, factor, ow, factor), axis=(1, 3)).astype(np.float32) - # 3D: (N, H, W) - arr = _pad_or_crop_3d(arr, factor, edge_mode) - n, h, w = arr.shape - oh, ow = h // factor, w // factor - return reduce(arr.reshape(n, oh, factor, ow, factor), axis=(2, 4)).astype(np.float32) - - -def _pad_or_crop_2d(arr: np.ndarray, factor: int, edge_mode: str) -> np.ndarray: - h, w = arr.shape - if edge_mode == "pad": - pad_h = (factor - h % factor) % factor - pad_w = (factor - w % factor) % factor - if pad_h or pad_w: - arr = np.pad(arr, ((0, pad_h), (0, pad_w)), mode="constant") - else: - oh, ow = h // factor, w // factor - arr = arr[:oh * factor, :ow * factor] - return arr - - -def _pad_or_crop_3d(arr: np.ndarray, factor: int, edge_mode: str) -> np.ndarray: - _, h, w = arr.shape - if edge_mode == "pad": - pad_h = (factor - h % factor) % factor - pad_w = (factor - w % factor) % factor - if pad_h or pad_w: - arr = np.pad(arr, ((0, 0), (0, pad_h), (0, pad_w)), mode="constant") - else: - oh, ow = h // factor, w // factor - arr = arr[:, :oh * factor, :ow * factor] - return arr - - -def apply_shift(img: np.ndarray, dy: float, dx: float) -> np.ndarray: - """ - Apply sub-pixel shift using bilinear interpolation. - - Uses ``torch.nn.functional.grid_sample`` on GPU when torch is available, - falls back to numpy bilinear interpolation otherwise. - - Parameters - ---------- - img : np.ndarray - 2D image, float32. - dy : float - Shift in y (rows). - dx : float - Shift in x (columns). - - Returns - ------- - np.ndarray - Shifted image, same shape, float32. Out-of-bounds pixels are zero. - """ - if _HAS_TORCH: - h, w = img.shape - device = torch.device("mps" if torch.backends.mps.is_available() else "cuda" if torch.cuda.is_available() else "cpu") - t = torch.as_tensor(img, dtype=torch.float32, device=device).unsqueeze(0).unsqueeze(0) - base_y = torch.linspace(-1, 1, h, device=device) - base_x = torch.linspace(-1, 1, w, device=device) - gy, gx = torch.meshgrid(base_y, base_x, indexing="ij") - grid = torch.stack([gx - dx * 2.0 / w, gy - dy * 2.0 / h], dim=-1).unsqueeze(0) - result = F.grid_sample(t, grid, mode="bilinear", padding_mode="zeros", align_corners=True) - return result.squeeze().cpu().numpy() - h, w = img.shape - y_src = np.arange(h, dtype=np.float64) - dy - x_src = np.arange(w, dtype=np.float64) - dx - yy, xx = np.meshgrid(y_src, x_src, indexing="ij") - y0 = np.floor(yy).astype(int) - x0 = np.floor(xx).astype(int) - fy = (yy - y0).astype(np.float32) - fx = (xx - x0).astype(np.float32) - valid = (y0 >= 0) & (y0 + 1 < h) & (x0 >= 0) & (x0 + 1 < w) - y0c = np.clip(y0, 0, h - 2) - x0c = np.clip(x0, 0, w - 2) - result = (img[y0c, x0c] * (1 - fy) * (1 - fx) - + img[y0c, x0c + 1] * (1 - fy) * fx - + img[y0c + 1, x0c] * fy * (1 - fx) - + img[y0c + 1, x0c + 1] * fy * fx) - result[~valid] = 0.0 - return result.astype(np.float32) - - def _resize_image(img: np.ndarray, target_h: int, target_w: int) -> np.ndarray: - """Resize image using bilinear interpolation (pure numpy, no scipy).""" - h, w = img.shape + """Center-pad an image to (target_h, target_w) with zeros. + Used to align gallery images of different shapes to a common canvas. + """ + h, w = img.shape[-2:] if h == target_h and w == target_w: return img + pad_top = (target_h - h) // 2 + pad_bot = target_h - h - pad_top + pad_left = (target_w - w) // 2 + pad_right = target_w - w - pad_left + return np.pad(img, ((pad_top, pad_bot), (pad_left, pad_right)), mode="constant", constant_values=0) - y_new = np.linspace(0, h - 1, target_h) - x_new = np.linspace(0, w - 1, target_w) - x_grid, y_grid = np.meshgrid(x_new, y_new) - - y0 = np.floor(y_grid).astype(int) - x0 = np.floor(x_grid).astype(int) - y1 = np.minimum(y0 + 1, h - 1) - x1 = np.minimum(x0 + 1, w - 1) - fy = y_grid - y0 - fx = x_grid - x0 - - result = ( - img[y0, x0] * (1 - fy) * (1 - fx) + - img[y0, x1] * (1 - fy) * fx + - img[y1, x0] * fy * (1 - fx) + - img[y1, x1] * fy * fx - ) - return result.astype(img.dtype) +def apply_shift(img: np.ndarray, dy: float, dx: float) -> np.ndarray: + """Sub-pixel image shift via bilinear interpolation. Used for diff alignment.""" + if not _HAS_TORCH: + # Fallback: integer roll only + return np.roll(img, (int(round(dy)), int(round(dx))), axis=(-2, -1)) + t = torch.from_numpy(img).float() + if t.ndim == 2: + t = t.unsqueeze(0).unsqueeze(0) + h, w = t.shape[-2:] + y = torch.arange(h, dtype=torch.float32) - dy + x = torch.arange(w, dtype=torch.float32) - dx + yy, xx = torch.meshgrid(y, x, indexing="ij") + grid = torch.stack(((xx / (w - 1)) * 2 - 1, (yy / (h - 1)) * 2 - 1), dim=-1).unsqueeze(0) + out = torch.nn.functional.grid_sample(t, grid, mode="bilinear", padding_mode="border", align_corners=True) + return out.squeeze().numpy() + + +def bin2d(img: np.ndarray, factor: int) -> np.ndarray: + """Reduce 2D image by integer binning factor. Mean of f×f blocks.""" + if factor <= 1: + return img + h, w = img.shape[-2:] + h2, w2 = h - h % factor, w - w % factor + img = img[..., :h2, :w2] + return img.reshape(*img.shape[:-2], h2 // factor, factor, w2 // factor, factor).mean(axis=(-3, -1)) diff --git a/widget/src/quantem/widget/show2d.py b/widget/src/quantem/widget/show2d.py index 7c7ef16a..cd2dff99 100644 --- a/widget/src/quantem/widget/show2d.py +++ b/widget/src/quantem/widget/show2d.py @@ -22,6 +22,7 @@ import numpy as np import traitlets +from quantem.core.datastructures import Dataset2d, Dataset3d from quantem.widget.array_utils import to_numpy, _resize_image from quantem.widget.json_state import resolve_widget_version, save_state_file, unwrap_state_payload from quantem.widget.tool_parity import ( @@ -438,8 +439,12 @@ def _init_sync(self, *, data, labels, title, cmap, pixel_size, self._display_data = None # initialized after data setup self._display_bin = 1 - # Check if data is a Dataset2d and extract metadata - if hasattr(data, "array") and hasattr(data, "name") and hasattr(data, "sampling"): + # First-class support for quantem Dataset2d / Dataset3d: + # extract array + auto-populate title, pixel_size from sampling+units. + # (Duck-typing fallback below covers any other object exposing the same API.) + if isinstance(data, (Dataset2d, Dataset3d)) or ( + hasattr(data, "array") and hasattr(data, "name") and hasattr(data, "sampling") + ): if not title and data.name: title = data.name if pixel_size == 0.0 and hasattr(data, "units"): @@ -451,7 +456,7 @@ def _init_sync(self, *, data, labels, title, cmap, pixel_size, pixel_size = sampling_val data = data.array - # Convert input to NumPy (handles NumPy, CuPy, PyTorch) + # Convert NumPy / PyTorch / list inputs to a NumPy array. if isinstance(data, list): images = [to_numpy(d) for d in data] @@ -532,7 +537,7 @@ def _expand(v): if v is None: return [None] * n if isinstance(v, (list, tuple)): if len(v) != n: - raise ValueError(f"vmin/vmax list length {len(v)} != n_images {n}") + raise ValueError(f"vmin/vmax list has length {len(v)} but n_images is {n}. Pass a list of length {n} or a scalar to apply uniformly.") return [None if x is None else float(x) for x in v] return [float(v)] * n self.vmins = _expand(vmin) @@ -1125,7 +1130,7 @@ def rotate(self, idx: int, angle: int) -> Self: Self """ if angle % 90 != 0: - raise ValueError(f"Rotation angle must be a multiple of 90°, got {angle}") + raise ValueError(f"Rotation angle must be a multiple of 90 (got {angle}). Use 0, 90, 180, 270, or -90, -180, -270.") if idx < 0 or idx >= self.n_images: raise IndexError(f"Image index {idx} out of range [0, {self.n_images})") k = (angle // 90) % 4 diff --git a/widget/src/quantem/widget/show4dstem.py b/widget/src/quantem/widget/show4dstem.py index 1fe94273..c514be2f 100644 --- a/widget/src/quantem/widget/show4dstem.py +++ b/widget/src/quantem/widget/show4dstem.py @@ -566,7 +566,7 @@ def __init__( self._device = torch.device("cpu") self._data = torch.from_numpy(data_np).to(self._device) else: - raise ValueError(f"Expected 3D, 4D, or 5D array, got {ndim}D") + raise ValueError(f"Show4DSTEM expects a 3D ((N, det_h, det_w) flat-scan), 4D ((scan_h, scan_w, det_h, det_w)), or 5D ((n_frames, scan_h, scan_w, det_h, det_w)) array. Got {ndim}D. Reshape with array.reshape((scan_h, scan_w, det_h, det_w)) or pass a Dataset4dstem.") if _verbose: if str(self._device) == "mps": torch.mps.synchronize() @@ -729,7 +729,7 @@ def set_image(self, data, scan_shape=None): self._det_shape = (data_np.shape[2], data_np.shape[3]) self._data = torch.from_numpy(data_np).to(self._device) else: - raise ValueError(f"Expected 3D, 4D, or 5D array, got {data_np.ndim}D") + raise ValueError(f"Show4DSTEM expects a 3D, 4D, or 5D array. Got {data_np.ndim}D. See documentation for accepted shapes.") self.frame_idx = 0 self.shape_rows = self._scan_shape[0] self.shape_cols = self._scan_shape[1] @@ -1865,7 +1865,7 @@ def _render_panel_image( elif panel_key == "fft": rgb, render_meta = self._render_fft_rgb() else: - raise ValueError(f"Unsupported panel '{panel_key}'") + raise ValueError(f"Unsupported panel {panel_key!r}. Valid options: 'diffraction', 'virtual', 'fft', 'all'.") panel = Image.fromarray(rgb, mode="RGB") panel = self._decorate_panel(panel, panel_key, include_overlays, include_scalebar) From 43b54b2ab6b63e9c11baa0ab3f1aefc334d09e7c Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 3 May 2026 14:24:45 -0700 Subject: [PATCH 272/335] initial demo of Show2D and Show4DSTEM --- .gitignore | 16 +- widget/js/colormaps.ts | 16 +- widget/js/control-customizer.tsx | 174 - widget/js/{webgpu-fft.ts => fft.ts} | 75 +- widget/js/{scalebar.ts => figure.ts} | 33 +- widget/js/histogram.ts | 19 - widget/js/show2d/index.tsx | 362 +- widget/js/show2d/show2d.css | 9 - widget/js/show4dstem/index.tsx | 989 +++--- widget/js/show4dstem/styles.css | 5 - widget/js/stats.ts | 20 + widget/js/tool-parity.ts | 156 - widget/package-lock.json | 1186 +------ widget/package.json | 10 +- widget/src/quantem/widget/__init__.py | 9 +- widget/src/quantem/widget/array_utils.py | 92 +- widget/src/quantem/widget/show2d.py | 316 +- widget/src/quantem/widget/show4dstem.py | 3046 +++-------------- .../widget/{json_state.py => state.py} | 2 - widget/src/quantem/widget/tool_parity.json | 93 - widget/src/quantem/widget/tool_parity.py | 184 - widget/tests/test_fft_parity.py | 200 ++ widget/tests/test_state_dict.py | 168 + widget/vite.config.js | 26 - 24 files changed, 1778 insertions(+), 5428 deletions(-) delete mode 100644 widget/js/control-customizer.tsx rename widget/js/{webgpu-fft.ts => fft.ts} (89%) rename widget/js/{scalebar.ts => figure.ts} (92%) delete mode 100644 widget/js/histogram.ts delete mode 100644 widget/js/show2d/show2d.css delete mode 100644 widget/js/show4dstem/styles.css delete mode 100644 widget/js/tool-parity.ts rename widget/src/quantem/widget/{json_state.py => state.py} (96%) delete mode 100644 widget/src/quantem/widget/tool_parity.json delete mode 100644 widget/src/quantem/widget/tool_parity.py create mode 100644 widget/tests/test_fft_parity.py create mode 100644 widget/tests/test_state_dict.py delete mode 100644 widget/vite.config.js diff --git a/.gitignore b/.gitignore index d87d85c8..92d69e39 100644 --- a/.gitignore +++ b/.gitignore @@ -185,9 +185,23 @@ ipynb-playground/ *.h5 *.npy -# cursor +# cursor/CLI .cursor +.claude +CLAUDE.md +AGENTS.md +AGENT.md # widget (JS build artifacts) node_modules/ widget/src/quantem/widget/static/ + +# widget — local-only (per-developer notebooks, docs scratch, build/test scripts). +# Track only src/, js/, tests/test_*.py for now. +widget/.gitignore +widget/docs/ +widget/notebooks/ +widget/scripts/ +widget/tests/integration/ +widget/tests/snapshots/ + diff --git a/widget/js/colormaps.ts b/widget/js/colormaps.ts index ba160698..40a940b2 100644 --- a/widget/js/colormaps.ts +++ b/widget/js/colormaps.ts @@ -1,3 +1,7 @@ +// ============================================================================ +// Color palettes (LUT control points) +// ============================================================================ + const COLORMAP_POINTS: Record = { inferno: [ [0, 0, 4], [40, 11, 84], [101, 21, 110], [159, 42, 99], @@ -58,6 +62,10 @@ export const COLORMAPS: Record = Object.fromEntries( Object.entries(COLORMAP_POINTS).map(([name, points]) => [name, createColormapLUT(points)]) ); +// ============================================================================ +// CPU colormap (Float32 -> RGBA via 256-entry LUT) +// ============================================================================ + /** Apply colormap LUT to float data, writing into an RGBA Uint8ClampedArray. */ export function applyColormap( data: Float32Array, @@ -119,6 +127,10 @@ export function renderToOffscreenReuse( // 2D dispatch (16×16 workgroups) to stay within WebGPU's 65535 workgroup limit. // 1D dispatch with wg=256 needs ceil(4096*4096/256)=65536 — exceeds the limit by 1. +// ============================================================================ +// WebGPU colormap engine (compute shader, ~300x faster than CPU loop on 4K data) +// ============================================================================ + const COLORMAP_SHADER = /* wgsl */ ` struct Params { width: u32, @@ -1061,9 +1073,9 @@ let gpuColormapEngine: GPUColormapEngine | null = null; /** Get or create the singleton GPU colormap engine. Returns null if WebGPU unavailable. */ export async function getGPUColormapEngine(): Promise { if (gpuColormapEngine) return gpuColormapEngine; - // Reuse the GPU device from webgpu-fft + // Reuse the GPU device from fft try { - const { getGPUDevice } = await import("./webgpu-fft"); + const { getGPUDevice } = await import("./fft"); const device = await getGPUDevice(); if (!device) return null; gpuColormapEngine = new GPUColormapEngine(device); diff --git a/widget/js/control-customizer.tsx b/widget/js/control-customizer.tsx deleted file mode 100644 index 3ca9602d..00000000 --- a/widget/js/control-customizer.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import * as React from "react"; -import Box from "@mui/material/Box"; -import Typography from "@mui/material/Typography"; -import Switch from "@mui/material/Switch"; -import Tooltip from "@mui/material/Tooltip"; -import Divider from "@mui/material/Divider"; -import IconButton from "@mui/material/IconButton"; -import Button from "@mui/material/Button"; -import Menu from "@mui/material/Menu"; -import TuneIcon from "@mui/icons-material/Tune"; - -import { - addToolGroup, - compactToolLabel, - computeToolVisibility, - getControlPresetIds, - getControlPresetLabel, - getWidgetToolGroups, - removeToolGroup, - resolvePresetHiddenTools, -} from "./tool-parity"; - -type ToolSetter = React.Dispatch>; - -type ThemeColors = { - controlBg: string; - text: string; - border: string; - textMuted?: string; - accent?: string; -}; - -type ControlCustomizerProps = { - widgetName: string; - hiddenTools: string[]; - setHiddenTools: ToolSetter; - disabledTools: string[]; - setDisabledTools: ToolSetter; - themeColors: ThemeColors; - labelOverrides?: Record; -}; - -const switchStyles = { - small: { - "& .MuiSwitch-thumb": { width: 12, height: 12 }, - "& .MuiSwitch-switchBase": { padding: "4px" }, - }, -}; - -const presetButton = { - fontSize: 10, - py: 0.25, - px: 1, - minWidth: 0, -}; - -export function ControlCustomizer({ - widgetName, - hiddenTools, - setHiddenTools, - disabledTools, - setDisabledTools, - themeColors, - labelOverrides, -}: ControlCustomizerProps) { - const [anchor, setAnchor] = React.useState(null); - const groups = React.useMemo( - () => getWidgetToolGroups(widgetName).filter((group) => group !== "all"), - [widgetName], - ); - const visibility = React.useMemo( - () => computeToolVisibility(widgetName, disabledTools, hiddenTools), - [widgetName, disabledTools, hiddenTools], - ); - - const setGroupVisible = React.useCallback((group: string, visible: boolean) => { - setHiddenTools((prev) => { - if (visible) return removeToolGroup(widgetName, prev, group); - return addToolGroup(widgetName, prev, group); - }); - }, [setHiddenTools, widgetName]); - - const setGroupLocked = React.useCallback((group: string, locked: boolean) => { - setDisabledTools((prev) => { - if (locked) return addToolGroup(widgetName, prev, group); - return removeToolGroup(widgetName, prev, group); - }); - }, [setDisabledTools, widgetName]); - - const applyPreset = React.useCallback((presetId: string) => { - setHiddenTools(resolvePresetHiddenTools(widgetName, presetId)); - }, [setHiddenTools, widgetName]); - - return ( - <> - - setAnchor(e.currentTarget)} - sx={{ p: 0.25, ml: 0.5, color: themeColors.text }} - > - - - - setAnchor(null)} - anchorOrigin={{ vertical: "bottom", horizontal: "right" }} - transformOrigin={{ vertical: "top", horizontal: "right" }} - PaperProps={{ - sx: { - bgcolor: themeColors.controlBg, - color: themeColors.text, - border: `1px solid ${themeColors.border}`, - p: 0.5, - minWidth: 280, - }, - }} - > - - Presets - - {getControlPresetIds().map((presetId) => ( - - ))} - - - - - Per-group - {groups.map((group) => { - const label = labelOverrides?.[group] ?? compactToolLabel(group); - const hidden = visibility.isHidden(group); - const locked = visibility.isLocked(group); - return ( - - {label} - - Show - setGroupVisible(group, e.target.checked)} - inputProps={{ "aria-label": `show-${group}` }} - sx={switchStyles.small} - /> - Lock - setGroupLocked(group, e.target.checked)} - inputProps={{ "aria-label": `lock-${group}` }} - sx={switchStyles.small} - disabled={hidden} - /> - - - ); - })} - - - - ); -} diff --git a/widget/js/webgpu-fft.ts b/widget/js/fft.ts similarity index 89% rename from widget/js/webgpu-fft.ts rename to widget/js/fft.ts index 2498b755..b2a72ea6 100644 --- a/widget/js/webgpu-fft.ts +++ b/widget/js/fft.ts @@ -85,63 +85,24 @@ export function fftshift(data: Float32Array, width: number, height: number): voi // CPU FFT Web Worker — runs fft2d + fftshift + computeMagnitude off main thread // ============================================================================ +// Build worker source by stringifying the same fft1d/fft2d/fftshift defined +// above. Single source of truth: fix a bug once, both paths get it. Pure +// functions only (no module-state closures), so .toString() captures the full +// behavior. Use Function.name in the onmessage body so minified names still +// match (esbuild may rename `fft2d` -> `a`; the .name property tracks rename). const FFT_WORKER_CODE = ` -function nextPow2(n) { return Math.pow(2, Math.ceil(Math.log2(n))); } -function fft1d(real, imag, inverse) { - var n = real.length; if (n <= 1) return; - var j = 0; - for (var i = 0; i < n - 1; i++) { - if (i < j) { var t = real[i]; real[i] = real[j]; real[j] = t; t = imag[i]; imag[i] = imag[j]; imag[j] = t; } - var k = n >> 1; while (k <= j) { j -= k; k >>= 1; } j += k; - } - var sign = inverse ? 1 : -1; - for (var len = 2; len <= n; len <<= 1) { - var halfLen = len >> 1, angle = (sign * 2 * Math.PI) / len; - var wR = Math.cos(angle), wI = Math.sin(angle); - for (var i = 0; i < n; i += len) { - var cR = 1, cI = 0; - for (var k = 0; k < halfLen; k++) { - var eI = i + k, oI = i + k + halfLen; - var tR = cR * real[oI] - cI * imag[oI], tI = cR * imag[oI] + cI * real[oI]; - real[oI] = real[eI] - tR; imag[oI] = imag[eI] - tI; - real[eI] += tR; imag[eI] += tI; - var nR = cR * wR - cI * wI; cI = cR * wI + cI * wR; cR = nR; - } - } - } - if (inverse) { for (var i = 0; i < n; i++) { real[i] /= n; imag[i] /= n; } } -} -function fft2d(real, imag, width, height, inverse) { - var pW = nextPow2(width), pH = nextPow2(height), pad = pW !== width || pH !== height; - var wR, wI; - if (pad) { - wR = new Float32Array(pW * pH); wI = new Float32Array(pW * pH); - for (var y = 0; y < height; y++) for (var x = 0; x < width; x++) { wR[y*pW+x] = real[y*width+x]; wI[y*pW+x] = imag[y*width+x]; } - } else { wR = real; wI = imag; } - var rR = new Float32Array(pW), rI = new Float32Array(pW); - for (var y = 0; y < pH; y++) { - var o = y * pW; for (var x = 0; x < pW; x++) { rR[x] = wR[o+x]; rI[x] = wI[o+x]; } - fft1d(rR, rI, inverse); for (var x = 0; x < pW; x++) { wR[o+x] = rR[x]; wI[o+x] = rI[x]; } - } - var cR = new Float32Array(pH), cI = new Float32Array(pH); - for (var x = 0; x < pW; x++) { - for (var y = 0; y < pH; y++) { cR[y] = wR[y*pW+x]; cI[y] = wI[y*pW+x]; } - fft1d(cR, cI, inverse); for (var y = 0; y < pH; y++) { wR[y*pW+x] = cR[y]; wI[y*pW+x] = cI[y]; } - } - if (pad) { for (var y = 0; y < height; y++) for (var x = 0; x < width; x++) { real[y*width+x] = wR[y*pW+x]; imag[y*width+x] = wI[y*pW+x]; } } -} -function fftshift(data, width, height) { - var hW = width >> 1, hH = height >> 1, temp = new Float32Array(width * height); - for (var y = 0; y < height; y++) for (var x = 0; x < width; x++) temp[((y+hH)%height)*width+((x+hW)%width)] = data[y*width+x]; - data.set(temp); -} +${nextPow2.toString()} +${fft1d.toString()} +${fft2d.toString()} +${fftshift.toString()} self.onmessage = function(e) { - var d = e.data, real = d.real, imag = d.imag, w = d.width, h = d.height; - fft2d(real, imag, w, h, d.inverse); - fftshift(real, w, h); fftshift(imag, w, h); - var n = real.length, mag = new Float32Array(n); - for (var i = 0; i < n; i++) mag[i] = Math.sqrt(real[i]*real[i] + imag[i]*imag[i]); - self.postMessage({ id: d.id, magnitude: mag, real: real, imag: imag }, [mag.buffer, real.buffer, imag.buffer]); + const d = e.data; + ${fft2d.name}(d.real, d.imag, d.width, d.height, d.inverse); + ${fftshift.name}(d.real, d.width, d.height); + ${fftshift.name}(d.imag, d.width, d.height); + const n = d.real.length, mag = new Float32Array(n); + for (let i = 0; i < n; i++) mag[i] = Math.sqrt(d.real[i]*d.real[i] + d.imag[i]*d.imag[i]); + self.postMessage({ id: d.id, magnitude: mag, real: d.real, imag: d.imag }, [mag.buffer, d.real.buffer, d.imag.buffer]); }; `; @@ -189,6 +150,10 @@ export function fft2dAsync( // WebGPU FFT — GPU-accelerated 2D FFT // ============================================================================ +// ============================================================================ +// WebGPU FFT (compute shader, GPU-resident) +// ============================================================================ + const FFT_2D_SHADER = /* wgsl */` fn cmul(a: vec2, b: vec2) -> vec2 { return vec2(a.x*b.x-a.y*b.y, a.x*b.y+a.y*b.x); } fn twiddle(k: u32, N: u32, inverse: f32) -> vec2 { let angle = inverse * 2.0 * 3.14159265359 * f32(k) / f32(N); return vec2(cos(angle), sin(angle)); } diff --git a/widget/js/scalebar.ts b/widget/js/figure.ts similarity index 92% rename from widget/js/scalebar.ts rename to widget/js/figure.ts index 041b4754..b1fd3f2f 100644 --- a/widget/js/scalebar.ts +++ b/widget/js/figure.ts @@ -5,8 +5,6 @@ import { formatNumber } from "./format"; -export type ScaleUnit = "Å" | "mrad" | "px" | "Å⁻¹"; - /** Round a physical value to a "nice" number (1, 2, 5, 10, 20, 50, ...) */ export function roundToNiceValue(value: number): number { if (value <= 0) return 1; @@ -18,23 +16,10 @@ export function roundToNiceValue(value: number): number { return 10 * magnitude; } -/** Format scale bar label with appropriate unit and auto-conversion (Å→nm, mrad→rad, Å⁻¹→nm⁻¹) */ -export function formatScaleLabel(value: number, unit: ScaleUnit): string { +/** Format scale bar label. Unit string is displayed verbatim - no conversion. */ +export function formatScaleLabel(value: number, unit: string): string { const nice = roundToNiceValue(value); - if (unit === "Å") { - if (nice >= 10) return `${Math.round(nice / 10)} nm`; - return nice >= 1 ? `${Math.round(nice)} Å` : `${nice.toFixed(2)} Å`; - } - if (unit === "Å⁻¹") { - // 10 Å⁻¹ = 1 nm⁻¹ - if (nice >= 10) return `${Math.round(nice / 10)} nm⁻¹`; - return nice >= 1 ? `${Math.round(nice)} Å⁻¹` : `${nice.toFixed(2)} Å⁻¹`; - } - if (unit === "px") { - return nice >= 1 ? `${Math.round(nice)} px` : `${nice.toFixed(1)} px`; - } - if (nice >= 1000) return `${Math.round(nice / 1000)} rad`; - return nice >= 1 ? `${Math.round(nice)} mrad` : `${nice.toFixed(2)} mrad`; + return nice >= 1 ? `${Math.round(nice)} ${unit}` : `${nice.toFixed(2)} ${unit}`; } const FONT = "-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif"; @@ -48,7 +33,7 @@ export function drawScaleBarHiDPI( dpr: number, zoom: number, pixelSize: number, - unit: "Å" | "mrad" | "px", + unit: string, imageWidth: number, ) { const ctx = canvas.getContext("2d"); @@ -107,6 +92,7 @@ export function drawFFTScaleBarHiDPI( fftZoom: number, fftPixelSize: number, imageWidth: number, + unit: string = "1/px", ) { const ctx = canvas.getContext("2d"); if (!ctx || fftPixelSize <= 0) return; @@ -139,7 +125,7 @@ export function drawFFTScaleBarHiDPI( ctx.fillStyle = "white"; ctx.fillRect(barX, barY, barPx, barThickness); - const label = formatScaleLabel(nicePhysical, "Å⁻¹"); + const label = formatScaleLabel(nicePhysical, unit); ctx.font = `${fontSize}px ${FONT}`; ctx.fillStyle = "white"; ctx.textAlign = "center"; @@ -220,8 +206,10 @@ export interface ExportFigureOptions { vmin?: number; vmax?: number; logScale?: boolean; - /** Pixel size in Å (for scale bar computation). */ + /** Pixel size in user-supplied unit (for scale bar computation). */ pixelSize?: number; + /** Unit string for the scale bar label (e.g. "A", "nm", "mrad"). */ + pixelUnit?: string; showColorbar?: boolean; showScaleBar?: boolean; /** Upscale factor for high-resolution output (default 4). Image pixels use nearest-neighbor for sharp edges. */ @@ -243,6 +231,7 @@ export function exportFigure(options: ExportFigureOptions): HTMLCanvasElement { vmax = 1, logScale = false, pixelSize = 0, + pixelUnit = "pixels", showColorbar = true, showScaleBar = true, scale: s = 4, @@ -323,7 +312,7 @@ export function exportFigure(options: ExportFigureOptions): HTMLCanvasElement { ctx.fillStyle = "white"; ctx.fillRect(barX, barY, barPx, barThickness); - const label = formatScaleLabel(nicePhysical, "Å"); + const label = formatScaleLabel(nicePhysical, pixelUnit); ctx.font = `bold ${fontSize}px ${FONT}`; ctx.fillStyle = "white"; ctx.textAlign = "center"; diff --git a/widget/js/histogram.ts b/widget/js/histogram.ts deleted file mode 100644 index c2f96a61..00000000 --- a/widget/js/histogram.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** Compute normalized histogram bins from Float32Array. Returns array of 0-1 values. */ -export function computeHistogramFromBytes(data: Float32Array | null, numBins = 256): number[] { - if (!data || data.length === 0) return new Array(numBins).fill(0); - const bins = new Array(numBins).fill(0); - let min = Infinity, max = -Infinity; - for (let i = 0; i < data.length; i++) { - const v = data[i]; - if (isFinite(v)) { if (v < min) min = v; if (v > max) max = v; } - } - if (!isFinite(min) || !isFinite(max) || min === max) return bins; - const range = max - min; - for (let i = 0; i < data.length; i++) { - const v = data[i]; - if (isFinite(v)) bins[Math.min(numBins - 1, Math.floor(((v - min) / range) * numBins))]++; - } - const maxCount = Math.max(...bins); - if (maxCount > 0) for (let i = 0; i < numBins; i++) bins[i] /= maxCount; - return bins; -} diff --git a/widget/js/show2d/index.tsx b/widget/js/show2d/index.tsx index aacf5c43..17120308 100644 --- a/widget/js/show2d/index.tsx +++ b/widget/js/show2d/index.tsx @@ -22,13 +22,10 @@ import Slider from "@mui/material/Slider"; import Button from "@mui/material/Button"; import Tooltip from "@mui/material/Tooltip"; import { useTheme } from "../theme"; -import { drawScaleBarHiDPI, drawFFTScaleBarHiDPI, drawColorbar, roundToNiceValue, exportFigure, canvasToPDF } from "../scalebar"; +import { drawScaleBarHiDPI, drawFFTScaleBarHiDPI, drawColorbar, roundToNiceValue, exportFigure, canvasToPDF } from "../figure"; import JSZip from "jszip"; import { extractFloat32, formatNumber, downloadBlob } from "../format"; -import { computeHistogramFromBytes } from "../histogram"; -import { findDataRange, applyLogScale, percentileClip, sliderRange, computeStats } from "../stats"; -import { ControlCustomizer } from "../control-customizer"; -import { computeToolVisibility } from "../tool-parity"; +import { computeHistogramFromBytes, findDataRange, applyLogScale, percentileClip, sliderRange, computeStats } from "../stats"; function InfoTooltip({ text, theme = "dark" }: { text: React.ReactNode; theme?: "light" | "dark" }) { const isDark = theme === "dark"; @@ -66,9 +63,8 @@ const upwardMenuProps = { transformOrigin: { vertical: "bottom" as const, horizontal: "left" as const }, sx: { zIndex: 9999 }, }; -import { getWebGPUFFT, WebGPUFFT, fft2d, fft2dAsync, fftshift, computeMagnitude, autoEnhanceFFT, nextPow2, applyHannWindow2D, getGPUInfo } from "../webgpu-fft"; +import { getWebGPUFFT, WebGPUFFT, fft2d, fft2dAsync, fftshift, computeMagnitude, autoEnhanceFFT, nextPow2, applyHannWindow2D, getGPUInfo } from "../fft"; import { COLORMAPS, COLORMAP_NAMES, renderToOffscreen, renderToOffscreenReuse, GPUColormapEngine, getGPUColormapEngine, getGPUMaxBufferSize } from "../colormaps"; -import "./show2d.css"; const MIN_ZOOM = 0.5; const MAX_ZOOM = 20; @@ -403,13 +399,12 @@ function Show2D() { // Scale bar const [pixelSize] = useModelState("pixel_size"); + const [pixelUnit] = useModelState("pixel_unit"); const [scaleBarVisible] = useModelState("scale_bar_visible"); // UI visibility const [showControls] = useModelState("show_controls"); const [showStats] = useModelState("show_stats"); - const [disabledTools, setDisabledTools] = useModelState("disabled_tools"); - const [hiddenTools, setHiddenTools] = useModelState("hidden_tools"); const [statsMean] = useModelState("stats_mean"); const [statsMin] = useModelState("stats_min"); const [statsMax] = useModelState("stats_max"); @@ -437,43 +432,15 @@ function Show2D() { const [exportAnchor, setExportAnchor] = React.useState(null); const selectedRoi = roiSelectedIdx >= 0 && roiSelectedIdx < (roiList?.length ?? 0) ? roiList[roiSelectedIdx] : null; - const toolVisibility = React.useMemo( - () => computeToolVisibility("Show2D", disabledTools, hiddenTools), - [disabledTools, hiddenTools], - ); - const hideDisplay = toolVisibility.isHidden("display"); - const hideHistogram = toolVisibility.isHidden("histogram"); - const hideStats = toolVisibility.isHidden("stats"); - const hideView = toolVisibility.isHidden("view"); - const hideExport = toolVisibility.isHidden("export"); - const hideRoi = toolVisibility.isHidden("roi"); - const hideProfile = toolVisibility.isHidden("profile"); - - const lockDisplay = toolVisibility.isLocked("display"); - const lockHistogram = toolVisibility.isLocked("histogram"); - const lockStats = toolVisibility.isLocked("stats"); - const lockNavigation = toolVisibility.isLocked("navigation"); - const lockView = toolVisibility.isLocked("view"); - const lockExport = toolVisibility.isLocked("export"); - const lockRoi = toolVisibility.isLocked("roi"); - const lockProfile = toolVisibility.isLocked("profile"); - const effectiveShowFft = showFft && !hideDisplay; + const effectiveShowFft = showFft; const updateSelectedRoi = (updates: Partial) => { - if (lockRoi) return; if (roiSelectedIdx < 0 || !roiList) return; const newList = [...roiList]; newList[roiSelectedIdx] = { ...newList[roiSelectedIdx], ...updates }; setRoiList(newList); }; - React.useEffect(() => { - if (hideRoi && roiActive) { - setRoiActive(false); - setRoiSelectedIdx(-1); - } - }, [hideRoi, roiActive, setRoiActive, setRoiSelectedIdx]); - // Canvas refs const canvasRefs = React.useRef<(HTMLCanvasElement | null)[]>([]); const overlayRefs = React.useRef<(HTMLCanvasElement | null)[]>([]); @@ -656,11 +623,6 @@ function Show2D() { const [profileActive, setProfileActive] = React.useState(false); const [profileLine, setProfileLine] = useModelState<{ row: number; col: number }[]>("profile_line"); const [profileDataAll, setProfileDataAll] = React.useState<(Float32Array | null)[]>([]); - React.useEffect(() => { - if (hideProfile && profileActive) { - setProfileActive(false); - } - }, [hideProfile, profileActive]); const profileCanvasRef = React.useRef(null); const profileBaseImageRef = React.useRef(null); const profileLayoutRef = React.useRef<{ padLeft: number; plotW: number; padTop: number; plotH: number; gMin: number; gMax: number; totalDist: number; xUnit: string } | null>(null); @@ -1408,7 +1370,7 @@ function Show2D() { if (scaleBarVisible) { const zs = getZoomState(i); - const unit = pixelSize > 0 ? "Å" as const : "px" as const; + const unit = pixelSize > 0 ? pixelUnit : "px"; const pxSize = pixelSize > 0 ? pixelSize : 1; drawScaleBarHiDPI(overlay, DPR, zs.zoom, pxSize, unit, width); } else { @@ -1416,7 +1378,7 @@ function Show2D() { } // Colorbar (single image mode only) — uses cached vmin/vmax from data effect - if (!hideDisplay && showColorbar && !isGallery) { + if (showColorbar && !isGallery) { const lut = COLORMAPS[cmap] || COLORMAPS.inferno; const cssW = overlay.width / DPR; const cssH = overlay.height / DPR; @@ -1430,7 +1392,7 @@ function Show2D() { } // ROI overlay — draw all ROIs - if (!hideRoi && roiActive && roiList && roiList.length > 0) { + if (roiActive && roiList && roiList.length > 0) { const zs = getZoomState(i); const { zoom, panX, panY } = zs; const cx = canvasW / 2; @@ -1503,7 +1465,7 @@ function Show2D() { } // Line profile overlay - if (!hideProfile && profileActive && profilePoints.length > 0) { + if (profileActive && profilePoints.length > 0) { const zs = getZoomState(i); const { zoom, panX, panY } = zs; ctx.save(); @@ -1609,7 +1571,7 @@ function Show2D() { ctx.restore(); } } - }, [nImages, pixelSize, scaleBarVisible, selectedIdx, isGallery, canvasW, canvasH, width, displayScale, linkedZoom, linkedZoomState, zoomStates, dataVersion, showColorbar, cmap, offscreenVersion, logScale, profileActive, profilePoints, roiActive, roiList, roiSelectedIdx, isDraggingROI, themeColors, hideDisplay, hideRoi, hideProfile, measureActive, measurePoints]); + }, [nImages, pixelSize, scaleBarVisible, selectedIdx, isGallery, canvasW, canvasH, width, displayScale, linkedZoom, linkedZoomState, zoomStates, dataVersion, showColorbar, cmap, offscreenVersion, logScale, profileActive, profilePoints, roiActive, roiList, roiSelectedIdx, isDraggingROI, themeColors, measureActive, measurePoints]); // ------------------------------------------------------------------------- // Inset magnifier (lens) — renders magnified region at cursor in bottom-left @@ -1620,7 +1582,7 @@ function Show2D() { const lctx = lensCanvas.getContext("2d"); if (lctx) lctx.clearRect(0, 0, lensCanvas.width, lensCanvas.height); } - if (!showLens || lockDisplay || isGallery || !lensPos || !rawDataRef.current?.[0]) return; + if (!showLens || isGallery || !lensPos || !rawDataRef.current?.[0]) return; if (!lensCanvas) return; const ctx = lensCanvas.getContext("2d"); if (!ctx) return; @@ -1690,13 +1652,12 @@ function Show2D() { ctx.font = "10px monospace"; ctx.fillText(`${lensMag}×`, lx + 4, ly + lensSize - 4); ctx.restore(); - }, [showLens, lockDisplay, lensPos, isGallery, cmap, logScale, offscreenVersion, width, height, canvasH, themeColors, lensMag, lensDisplaySize, lensAnchor]); + }, [showLens, lensPos, isGallery, cmap, logScale, offscreenVersion, width, height, canvasH, themeColors, lensMag, lensDisplaySize, lensAnchor]); // ------------------------------------------------------------------------- // Auto-compute profile when profile_line is set (e.g. from Python) // ------------------------------------------------------------------------- React.useEffect(() => { - if (hideProfile) return; if (profilePoints.length === 2 && rawDataRef.current) { const p0 = profilePoints[0], p1 = profilePoints[1]; const allProfiles: (Float32Array | null)[] = []; @@ -1707,7 +1668,7 @@ function Show2D() { setProfileDataAll(allProfiles); if (!profileActive) setProfileActive(true); } - }, [profilePoints, dataVersion, hideProfile, profileActive]); + }, [profilePoints, dataVersion, profileActive]); // ------------------------------------------------------------------------- // Render sparkline for line profile @@ -1785,9 +1746,8 @@ function Show2D() { const dy = profilePoints[1].row - profilePoints[0].row; const distPx = Math.sqrt(dx * dx + dy * dy); if (pixelSize > 0) { - const distA = distPx * pixelSize; - if (distA >= 10) { totalDist = distA / 10; xUnit = "nm"; } - else { totalDist = distA; xUnit = "Å"; } + totalDist = distPx * pixelSize; + xUnit = pixelUnit; } else { totalDist = distPx; } @@ -2408,7 +2368,6 @@ function Show2D() { // Mouse Handlers for Zoom/Pan // ------------------------------------------------------------------------- const handleWheel = (e: React.WheelEvent, idx: number) => { - if (lockView) return; // In gallery mode, only allow zoom on the selected image (unless linked) if (isGallery && idx !== selectedIdx && !linkedZoom) return; e.preventDefault(); // Prevent page scroll when zooming @@ -2449,13 +2408,11 @@ function Show2D() { }; const handleDoubleClick = (idx: number) => { - if (lockView) return; setZoomState(idx, initialZoomState); }; // Reset view (zoom/pan only — preserves profile, FFT state, etc.) const handleResetAll = () => { - if (lockView) return; setZoomStates(new Map()); setLinkedZoomState(initialZoomState); setGalleryFftStates(new Map()); @@ -2467,14 +2424,12 @@ function Show2D() { // FFT zoom/pan handlers const handleFftWheel = (e: React.WheelEvent) => { - if (lockView) return; e.preventDefault(); // Prevent page scroll when zooming FFT const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1; setFftZoom(Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, fftZoom * zoomFactor))); }; const handleFftDoubleClick = () => { - if (lockView) return; setFftZoom(DEFAULT_FFT_ZOOM); setFftPanX(0); setFftPanY(0); @@ -2501,7 +2456,6 @@ function Show2D() { }; const handleFftMouseDown = (e: React.MouseEvent) => { - if (lockView) return; fftClickStartRef.current = { x: e.clientX, y: e.clientY }; setIsDraggingFftPan(true); setFftPanStart({ x: e.clientX, y: e.clientY, pX: fftPanX, pY: fftPanY }); @@ -2572,7 +2526,6 @@ function Show2D() { // Gallery FFT zoom/pan handlers (only selected image's FFT responds) const handleGalleryFftWheel = (e: React.WheelEvent, idx: number) => { - if (lockView) return; if (isGallery && idx !== selectedIdx && !linkedZoom) return; e.preventDefault(); // Prevent page scroll when zooming FFT const zs = getGalleryFftState(idx); @@ -2582,11 +2535,9 @@ function Show2D() { const handleGalleryFftMouseDown = (e: React.MouseEvent, idx: number) => { if (isGallery && idx !== selectedIdx) { - if (lockNavigation) return; setSelectedIdx(idx); return; // Select first, don't start panning } - if (lockView) return; const zs = getGalleryFftState(idx); setFftPanningIdx(idx); setIsDraggingFftPan(true); @@ -2710,13 +2661,12 @@ function Show2D() { const handleMouseDown = (e: React.MouseEvent, idx: number) => { const zs = getZoomState(idx); if (isGallery && idx !== selectedIdx) { - if (lockNavigation) return; setSelectedIdx(idx); // Continue to pan setup so click-drag on unselected panel pans immediately // (no double-click required to select first then drag). } // Check if click is on the lens inset — edge = resize, interior = drag - if (!lockDisplay && showLens && !isGallery && idx === 0) { + if (showLens && !isGallery && idx === 0) { const canvas = canvasRefs.current[0]; if (canvas) { const rect = canvas.getBoundingClientRect(); @@ -2741,7 +2691,7 @@ function Show2D() { } } clickStartRef.current = { x: e.clientX, y: e.clientY }; - if (profileActive && !lockProfile) { + if (profileActive) { const { imgCol, imgRow } = screenToImg(e, idx); if (profilePoints.length === 2) { const p0 = profilePoints[0]; @@ -2770,22 +2720,12 @@ function Show2D() { return; } } - if (!lockView) { - setIsDraggingPan(true); - setPanningIdx(idx); - setPanStart({ x: e.clientX, y: e.clientY, pX: zs.panX, pY: zs.panY }); - } + setIsDraggingPan(true); + setPanningIdx(idx); + setPanStart({ x: e.clientX, y: e.clientY, pX: zs.panX, pY: zs.panY }); return; } if (roiActive) { - if (lockRoi) { - if (!lockView) { - setIsDraggingPan(true); - setPanningIdx(idx); - setPanStart({ x: e.clientX, y: e.clientY, pX: zs.panX, pY: zs.panY }); - } - return; - } const { imgCol, imgRow } = screenToImg(e, idx); // Check resize handles on selected ROI first if (isNearResizeHandleInner(imgCol, imgRow)) { @@ -2823,7 +2763,6 @@ function Show2D() { } // Start panning (works in both ROI-active and normal modes) { - if (lockView) return; setIsDraggingPan(true); setPanningIdx(idx); setPanStart({ x: e.clientX, y: e.clientY, pX: zs.panX, pY: zs.panY }); @@ -2832,7 +2771,7 @@ function Show2D() { const handleMouseMove = (e: React.MouseEvent, idx: number) => { // Fast path: during pan drag, skip all cursor/hover/lens work — just update pan - if (isDraggingPan && panStart && panningIdx !== null && !lockView) { + if (isDraggingPan && panStart && panningIdx !== null) { const canvas = canvasRefs.current[idx]; if (!canvas || idx !== panningIdx) return; const rect = canvas.getBoundingClientRect(); @@ -2861,7 +2800,7 @@ function Show2D() { if (imgX >= 0 && imgX < width && imgY >= 0 && imgY < height) { const rawData = rawDataRef.current[idx]; if (rawData) setCursorInfo({ row: imgY, col: imgX, value: rawData[imgY * width + imgX] }); - if (!lockDisplay && showLens && !isGallery) setLensPos({ row: imgY, col: imgX }); + if (showLens && !isGallery) setLensPos({ row: imgY, col: imgX }); } else { setCursorInfo(null); // Don't clear lensPos — lens stays at last position when toggle is on @@ -2869,20 +2808,20 @@ function Show2D() { } // Lens drag - if (!lockDisplay && isDraggingLens && lensDragStartRef.current) { + if (isDraggingLens && lensDragStartRef.current) { const dx = e.clientX - lensDragStartRef.current.mx; const dy = e.clientY - lensDragStartRef.current.my; setLensAnchor({ x: lensDragStartRef.current.ax + dx, y: lensDragStartRef.current.ay + dy }); return; } // Lens resize drag - if (!lockDisplay && isResizingLens && lensResizeStartRef.current) { + if (isResizingLens && lensResizeStartRef.current) { const dy = e.clientY - lensResizeStartRef.current.my; setLensDisplaySize(Math.max(64, Math.min(256, lensResizeStartRef.current.startSize + dy))); return; } - if (profileActive && !lockProfile && profilePoints.length === 2) { + if (profileActive && profilePoints.length === 2) { const { imgCol, imgRow } = screenToImg(e, idx); const p0 = profilePoints[0]; const p1 = profilePoints[1]; @@ -2931,14 +2870,14 @@ function Show2D() { } // ROI resize drag (inner annular ring) - if (!lockRoi && isDraggingResizeInner && selectedRoi) { + if (isDraggingResizeInner && selectedRoi) { const { imgCol: ic, imgRow: ir } = screenToImg(e, idx); const newR = Math.sqrt((ic - selectedRoi.col) ** 2 + (ir - selectedRoi.row) ** 2); updateSelectedRoi({ radius_inner: Math.max(1, Math.min(selectedRoi.radius - 1, Math.round(newR))) }); return; } // ROI resize drag (outer) - if (!lockRoi && isDraggingResize && selectedRoi) { + if (isDraggingResize && selectedRoi) { const { imgCol: ic, imgRow: ir } = screenToImg(e, idx); const shape = selectedRoi.shape || "circle"; if (shape === "rectangle") { @@ -2958,12 +2897,12 @@ function Show2D() { return; } // ROI drag (move center) - if (!lockRoi && isDraggingROI) { + if (isDraggingROI) { updateROI(e, idx); return; } // Lens edge hover detection - if (!lockDisplay && showLens && !isGallery && canvas) { + if (showLens && !isGallery && canvas) { const rect = canvas.getBoundingClientRect(); const cssX = e.clientX - rect.left; const cssY = e.clientY - rect.top; @@ -2978,14 +2917,13 @@ function Show2D() { setIsHoveringLensEdge(false); } // Hover detection for resize handles (show cursor on any ROI edge) - if (roiActive && !lockRoi && !isDraggingPan) { + if (roiActive && !isDraggingPan) { const { imgCol: ic, imgRow: ir } = screenToImg(e, idx); setIsHoveringResizeInner(isNearResizeHandleInner(ic, ir)); setIsHoveringResize(isNearAnyEdge(ic, ir)); } // Panning - if (lockView) return; if (!isDraggingPan || !panStart || panningIdx === null) return; if (idx !== panningIdx) return; if (!canvas) return; @@ -3026,7 +2964,7 @@ function Show2D() { return; } // Detect click (vs drag) for profile mode - if (profileActive && !lockProfile && clickStartRef.current) { + if (profileActive && clickStartRef.current) { const dx = e.clientX - clickStartRef.current.x; const dy = e.clientY - clickStartRef.current.y; if (Math.sqrt(dx * dx + dy * dy) < 3) { @@ -3125,7 +3063,6 @@ function Show2D() { // ------------------------------------------------------------------------- // Copy to clipboard handler const handleCopy = React.useCallback(async () => { - if (lockExport) return; const canvas = canvasRefs.current[isGallery ? selectedIdx : 0]; if (!canvas) return; try { @@ -3136,11 +3073,10 @@ function Show2D() { // Fallback: download if clipboard API unavailable canvas.toBlob((b) => { if (b) downloadBlob(b, `show2d_${labels?.[selectedIdx] || "image"}.png`); }, "image/png"); } - }, [isGallery, selectedIdx, labels, lockExport]); + }, [isGallery, selectedIdx, labels]); // Export publication-quality figure with scale bar, colorbar, annotations const handleExportFigure = React.useCallback((withScaleBar: boolean, withColorbar: boolean) => { - if (lockExport) return; setExportAnchor(null); const idx = isGallery ? selectedIdx : 0; const rawData = rawDataRef.current?.[idx]; @@ -3229,11 +3165,10 @@ function Show2D() { }); canvasToPDF(figCanvas).then((blob) => downloadBlob(blob, `show2d_figure_${labels?.[selectedIdx] || "image"}.pdf`)); - }, [isGallery, selectedIdx, labels, width, height, cmap, logScale, autoContrast, imageDataRange, imageVminPct, imageVmaxPct, pixelSize, title, roiActive, roiList, profileActive, profilePoints, lockExport]); + }, [isGallery, selectedIdx, labels, width, height, cmap, logScale, autoContrast, imageDataRange, imageVminPct, imageVmaxPct, pixelSize, title, roiActive, roiList, profileActive, profilePoints]); // Export all variants (PNG + PDF) as zip const handleExportAll = React.useCallback(async () => { - if (lockExport) return; setExportAnchor(null); const idx = isGallery ? selectedIdx : 0; const rawData = rawDataRef.current?.[idx]; @@ -3353,12 +3288,11 @@ function Show2D() { const blob = await zip.generateAsync({ type: "blob" }); downloadBlob(blob, `${prefix}_all.zip`); - }, [isGallery, selectedIdx, labels, width, height, cmap, logScale, autoContrast, imageDataRange, imageVminPct, imageVmaxPct, pixelSize, title, roiActive, roiList, profileActive, profilePoints, widgetVersion, lockExport]); + }, [isGallery, selectedIdx, labels, width, height, cmap, logScale, autoContrast, imageDataRange, imageVminPct, imageVmaxPct, pixelSize, title, roiActive, roiList, profileActive, profilePoints, widgetVersion]); // Resize Handlers // ------------------------------------------------------------------------- const handleCanvasResizeStart = (e: React.MouseEvent) => { - if (lockView) return; e.stopPropagation(); e.preventDefault(); setIsResizingCanvas(true); @@ -3423,21 +3357,21 @@ function Show2D() { // ------------------------------------------------------------------------- const handleKeyDown = (e: React.KeyboardEvent) => { // Number keys 1-9 select gallery images (avoids arrow key conflicts with Jupyter) - if (!lockNavigation && isGallery && e.key >= "1" && e.key <= "9") { + if (isGallery && e.key >= "1" && e.key <= "9") { const idx = parseInt(e.key) - 1; if (idx < nImages) { e.preventDefault(); setSelectedIdx(idx); } return; } switch (e.key) { case "ArrowLeft": - if (!lockNavigation && isGallery) { e.preventDefault(); setSelectedIdx(Math.max(0, selectedIdx - 1)); } + if (isGallery) { e.preventDefault(); setSelectedIdx(Math.max(0, selectedIdx - 1)); } break; case "ArrowRight": - if (!lockNavigation && isGallery) { e.preventDefault(); setSelectedIdx(Math.min(nImages - 1, selectedIdx + 1)); } + if (isGallery) { e.preventDefault(); setSelectedIdx(Math.min(nImages - 1, selectedIdx + 1)); } break; case "r": case "R": - if (!lockView) handleResetAll(); + handleResetAll(); break; case "m": case "M": @@ -3456,7 +3390,7 @@ function Show2D() { } break; case "]": - if (!lockNavigation && !lockDisplay) { + { e.preventDefault(); const rIdx = isGallery ? selectedIdx : 0; const rots = [...(imageRotations || [])]; @@ -3466,7 +3400,7 @@ function Show2D() { } break; case "[": - if (!lockNavigation && !lockDisplay) { + { e.preventDefault(); const rIdx2 = isGallery ? selectedIdx : 0; const rots2 = [...(imageRotations || [])]; @@ -3477,7 +3411,7 @@ function Show2D() { break; case "Delete": case "Backspace": - if (!lockRoi && roiActive && roiSelectedIdx >= 0 && roiList && roiSelectedIdx < roiList.length) { + if (roiActive && roiSelectedIdx >= 0 && roiList && roiSelectedIdx < roiList.length) { e.preventDefault(); const newList = roiList.filter((_, i) => i !== roiSelectedIdx); setRoiList(newList); @@ -3493,12 +3427,12 @@ function Show2D() { const needsReset = getZoomState(isGallery ? selectedIdx : 0).zoom !== 1 || getZoomState(isGallery ? selectedIdx : 0).panX !== 0 || getZoomState(isGallery ? selectedIdx : 0).panY !== 0; const statsIdx = isGallery ? selectedIdx : 0; - // Calibrated cursor position - const calibratedUnit = pixelSize > 0 ? (Math.max(height, width) * pixelSize >= 10 ? "nm" : "Å") : ""; - const calibratedFactor = calibratedUnit === "nm" ? pixelSize / 10 : pixelSize; + // Calibrated cursor position - unit is whatever the user passed via sampling/units. + const calibratedUnit = pixelSize > 0 ? pixelUnit : ""; + const calibratedFactor = pixelSize; return ( - + {/* Main panel */} @@ -3514,14 +3448,13 @@ function Show2D() { { - if (lockDisplay) return; const ri = isGallery ? selectedIdx : 0; const rots = [...(imageRotations || [])]; while (rots.length <= ri) rots.push(0); rots[ri] = (rots[ri] + 3) % 4; setImageRotations(rots); }} - sx={{ ml: 0.5, color: themeColors.accent, cursor: lockDisplay ? "default" : "pointer", fontSize: "inherit", "&:hover": { opacity: lockDisplay ? 1 : 0.7 } }} + sx={{ ml: 0.5, color: themeColors.accent, cursor: "pointer", fontSize: "inherit", "&:hover": { opacity: 0.7 } }} > ({rk * 90}°) @@ -3537,29 +3470,20 @@ function Show2D() { Keyboard } theme={themeInfo.theme} /> - {/* Controls row: Profile, ROI, Lens, FFT, Export, Reset, Copy */} - {!hideProfile && ( + {( <> Profile: { - if (lockProfile) return; const on = e.target.checked; setProfileActive(on); if (on) { - if (!lockRoi) setRoiActive(false); + setRoiActive(false); } else { setProfilePoints([]); setProfileDataAll([]); @@ -3572,18 +3496,17 @@ function Show2D() { /> )} - {!hideRoi && !isGallery && ( + {!isGallery && ( <> ROI: { - if (lockRoi) return; const on = e.target.checked; setRoiActive(on); if (on) { - if (!lockProfile) setProfileActive(false); + setProfileActive(false); setProfilePoints([]); setProfileDataAll([]); setHoveredProfileEndpoint(null); @@ -3597,7 +3520,7 @@ function Show2D() { /> )} - {!hideDisplay && ( + {( <> {!isGallery && ( <> @@ -3605,7 +3528,6 @@ function Show2D() { { - if (lockDisplay) return; if (!showLens) { setShowLens(true); setLensPos({ row: Math.floor(height / 2), col: Math.floor(width / 2) }); @@ -3614,7 +3536,7 @@ function Show2D() { setLensPos(null); } }} - disabled={lockDisplay} + size="small" sx={switchStyles.small} /> @@ -3624,14 +3546,13 @@ function Show2D() { { - if (lockDisplay) return; const on = e.target.checked; if (on && width * height > 2048 * 2048) { console.warn(`Show2D: FFT on ${width}×${height} image (${(width * height / 1e6).toFixed(1)}M pixels) may be slow`); } setShowFft(on); }} - disabled={lockDisplay} + size="small" sx={switchStyles.small} /> @@ -3641,25 +3562,25 @@ function Show2D() { {nImages === 2 && ( <> Diff: - { if (!lockDisplay) setDiffMode(!diffMode); }} disabled={lockDisplay} size="small" sx={switchStyles.small} /> + { setDiffMode(!diffMode); }} size="small" sx={switchStyles.small} /> )} )} - {!hideView && ( - + {( + )} - {!hideExport && ( + {( <> - + setExportAnchor(null)} anchorOrigin={{ vertical: "bottom", horizontal: "left" }} transformOrigin={{ vertical: "top", horizontal: "left" }} sx={{ zIndex: 9999 }}> - handleExportFigure(true, true)} sx={{ fontSize: 12 }}>PDF + scalebar + colorbar - handleExportFigure(true, false)} sx={{ fontSize: 12 }}>PDF + scalebar - handleExportFigure(false, false)} sx={{ fontSize: 12 }}>PDF - All (PNG + PDF) + handleExportFigure(true, true)} sx={{ fontSize: 12 }}>PDF + scalebar + colorbar + handleExportFigure(true, false)} sx={{ fontSize: 12 }}>PDF + scalebar + handleExportFigure(false, false)} sx={{ fontSize: 12 }}>PDF + All (PNG + PDF) - + )} @@ -3668,7 +3589,7 @@ function Show2D() { /* Gallery mode */ {Array.from({ length: nImages }).map((_, i) => ( - + { imageContainerRefs.current[i] = el; }} sx={{ position: "relative", bgcolor: "#000", border: `2px solid ${i === selectedIdx ? themeColors.accent : themeColors.border}`, borderRadius: 0, width: canvasW, height: canvasH }} @@ -3689,8 +3610,8 @@ function Show2D() { width={Math.round(canvasW * DPR)} height={Math.round(canvasH * DPR)} style={{ position: "absolute", top: 0, left: 0, width: canvasW, height: canvasH, pointerEvents: "none" }} /> - {!hideView && ( - + {( + )} @@ -3700,13 +3621,12 @@ function Show2D() { component="span" onClick={(e: React.MouseEvent) => { e.stopPropagation(); - if (lockDisplay) return; const rots = [...(imageRotations || [])]; while (rots.length <= i) rots.push(0); rots[i] = (rots[i] + 3) % 4; setImageRotations(rots); }} - sx={{ ml: 0.5, color: themeColors.accent, cursor: lockDisplay ? "default" : "pointer", "&:hover": { opacity: lockDisplay ? 1 : 0.7 } }} + sx={{ ml: 0.5, color: themeColors.accent, cursor: "pointer", "&:hover": { opacity: 0.7 } }} > ({(imageRotations[i] % 4) * 90}°) @@ -3715,7 +3635,7 @@ function Show2D() { {effectiveShowFft && ( { fftContainerRefs.current[i] = el; }} - sx={{ mt: 0.5, position: "relative", border: `2px solid ${i === selectedIdx ? themeColors.accent : themeColors.border}`, borderRadius: 0, bgcolor: "#000", cursor: lockView ? "default" : "grab" }} + sx={{ mt: 0.5, position: "relative", border: `2px solid ${i === selectedIdx ? themeColors.accent : themeColors.border}`, borderRadius: 0, bgcolor: "#000", cursor: "grab" }} onWheel={(i === selectedIdx || linkedZoom) ? (e) => handleGalleryFftWheel(e, i) : undefined} onDoubleClick={() => setGalleryFftState(i, { zoom: DEFAULT_FFT_ZOOM, panX: 0, panY: 0 })} onMouseDown={(e) => handleGalleryFftMouseDown(e, i)} @@ -3796,15 +3716,15 @@ function Show2D() { )} - {!hideView && ( - + {( + )} )} {/* Stats bar - right below canvas (Show3D style) */} - {!hideStats && showStats && ( - + {showStats && ( + {isGallery && ( {labels?.[statsIdx] || `#${statsIdx + 1}`} )} @@ -3824,32 +3744,32 @@ function Show2D() { {/* Gallery FFT Controls - below gallery grid */} {effectiveShowFft && isGallery && ( - - + + FFT Scale: - setFftScaleMode(e.target.value as "linear" | "log" | "power")} size="small" sx={{ ...themedSelect, minWidth: 50, fontSize: 10 }} MenuProps={themedMenuProps}> Lin Log Pow Auto: - { if (!lockDisplay) setFftAuto(e.target.checked); }} disabled={lockDisplay} size="small" sx={switchStyles.small} /> + { setFftAuto(e.target.checked); }} size="small" sx={switchStyles.small} /> {roiFftActive && fftCropDims && ( <> Win: - { if (!lockDisplay) setFftWindow(e.target.checked); }} disabled={lockDisplay} size="small" sx={switchStyles.small} /> + { setFftWindow(e.target.checked); }} size="small" sx={switchStyles.small} /> )} Color: - setFftColormap(String(e.target.value))} size="small" sx={{ ...themedSelect, minWidth: 65, fontSize: 10 }} MenuProps={themedMenuProps}> {COLORMAP_NAMES.map((name) => ({name.charAt(0).toUpperCase() + name.slice(1)}))} - {!hideHistogram && ( - + {( + {fftHistogramData && ( - { if (!lockHistogram) { setFftVminPct(min); setFftVmaxPct(max); } }} width={110} height={58} theme={themeInfo.theme === "dark" ? "dark" : "light"} dataMin={fftDataRange.min} dataMax={fftDataRange.max} /> + { setFftVminPct(min); setFftVmaxPct(max); }} width={110} height={58} theme={themeInfo.theme === "dark" ? "dark" : "light"} dataMin={fftDataRange.min} dataMax={fftDataRange.max} /> )} )} @@ -3857,7 +3777,7 @@ function Show2D() { )} {/* Line profile sparkline — always reserve space when profile is active */} - {!hideProfile && profileActive && ( + {profileActive && (
{ - if (lockProfile) return; e.preventDefault(); setIsResizingProfile(true); setProfileResizeStart({ y: e.clientY, height: profileHeight }); }} - style={{ width: profileCanvasWidth, height: 4, cursor: lockProfile ? "default" : "ns-resize", borderLeft: `1px solid ${themeColors.border}`, borderRight: `1px solid ${themeColors.border}`, borderBottom: `1px solid ${themeColors.border}`, background: `linear-gradient(to bottom, ${themeColors.border}, transparent)`, opacity: lockProfile ? 0.5 : 1, pointerEvents: lockProfile ? "none" : "auto" }} + style={{ width: profileCanvasWidth, height: 4, cursor: "ns-resize", borderLeft: `1px solid ${themeColors.border}`, borderRight: `1px solid ${themeColors.border}`, borderBottom: `1px solid ${themeColors.border}`, background: `linear-gradient(to bottom, ${themeColors.border}, transparent)`, opacity: 1, pointerEvents: "auto" }} /> )} @@ -3882,51 +3801,51 @@ function Show2D() { {/* Top: control rows + histogram side by side */} - + {/* Row 1: Scale + Color */} - {!hideDisplay && ( - + {( + Scale: - setLogScale(e.target.value === "log")} size="small" sx={{ ...themedSelect, minWidth: 45 }} MenuProps={themedMenuProps}> Lin Log Color: - setCmap(e.target.value)} MenuProps={themedMenuProps} sx={{ ...themedSelect, minWidth: 60 }}> {COLORMAP_NAMES.map((name) => ({name.charAt(0).toUpperCase() + name.slice(1)}))} {!isGallery && ( <> Colorbar: - { if (!lockDisplay) setShowColorbar(!showColorbar); }} disabled={lockDisplay} size="small" sx={switchStyles.small} /> + { setShowColorbar(!showColorbar); }} size="small" sx={switchStyles.small} /> )} )} {/* Row 2: Auto + Lens settings + Link Zoom (gallery) + zoom indicator */} - {!hideDisplay && ( - + {( + Auto: - { if (!lockDisplay) setAutoContrast(!autoContrast); }} disabled={lockDisplay} size="small" sx={switchStyles.small} /> + { setAutoContrast(!autoContrast); }} size="small" sx={switchStyles.small} /> Smooth: - { if (!lockDisplay) setSmooth(!smooth); }} disabled={lockDisplay} size="small" sx={switchStyles.small} /> + { setSmooth(!smooth); }} size="small" sx={switchStyles.small} /> {!isGallery && showLens && ( <> Lens {lensMag}× - setLensMag(v as number)} size="small" sx={{ ...sliderStyles.small, width: 35 }} /> + setLensMag(v as number)} size="small" sx={{ ...sliderStyles.small, width: 35 }} /> {lensDisplaySize}px - setLensDisplaySize(v as number)} size="small" sx={{ ...sliderStyles.small, width: 35 }} /> + setLensDisplaySize(v as number)} size="small" sx={{ ...sliderStyles.small, width: 35 }} /> )} {isGallery && ( <> Link: Zoom - { if (!lockDisplay) setLinkedZoom(!linkedZoom); }} disabled={lockDisplay} size="small" sx={switchStyles.small} /> + { setLinkedZoom(!linkedZoom); }} size="small" sx={switchStyles.small} /> Pan - { if (!lockDisplay) setLinkPan(!linkPan); }} disabled={lockDisplay} size="small" sx={switchStyles.small} /> + { setLinkPan(!linkPan); }} size="small" sx={switchStyles.small} /> Contrast - { if (!lockDisplay) setLinkedContrast(!linkedContrast); }} disabled={lockDisplay} size="small" sx={switchStyles.small} /> + { setLinkedContrast(!linkedContrast); }} size="small" sx={switchStyles.small} /> )} {getZoomState(isGallery ? selectedIdx : 0).zoom !== 1 && ( @@ -3935,30 +3854,33 @@ function Show2D() { )} - {/* Right: Histogram aligned to the two rows. When unlinked + gallery: stack one per image. */} - {!hideHistogram && (imageHistogramData || imageHistogramBins) && ( - + {/* Right: histograms. Unlinked + gallery → grid matching gallery layout + (same effectiveNcols × rows). Linked or single image → one histogram. */} + {(imageHistogramData || imageHistogramBins) && ( + {(!linkedContrast && isGallery && rawDataRef.current) ? ( - Array.from({ length: nImages }).map((_, i) => { - const cs = contrastStates.get(i) || { vminPct: 0, vmaxPct: 100 }; - const raw = rawDataRef.current?.[i] || null; - return ( - { if (!lockHistogram) setContrastState(i, { vminPct: min, vmaxPct: max }); }} - width={110} height={36} theme={themeInfo.theme === "dark" ? "dark" : "light"} - dataMin={dataRangesRef.current[i]?.min ?? imageDataRange.min} - dataMax={dataRangesRef.current[i]?.max ?? imageDataRange.max} /> - ); - }) + + {Array.from({ length: nImages }).map((_, i) => { + const cs = contrastStates.get(i) || { vminPct: 0, vmaxPct: 100 }; + const raw = rawDataRef.current?.[i] || null; + return ( + { setContrastState(i, { vminPct: min, vmaxPct: max }); }} + width={110} height={58} theme={themeInfo.theme === "dark" ? "dark" : "light"} + dataMin={dataRangesRef.current[i]?.min ?? imageDataRange.min} + dataMax={dataRangesRef.current[i]?.max ?? imageDataRange.max} /> + ); + })} + ) : ( - { if (!lockHistogram) setContrastState(activeContrastIdx, { vminPct: min, vmaxPct: max }); }} width={110} height={58} theme={themeInfo.theme === "dark" ? "dark" : "light"} dataMin={traitVmin != null && traitVmax != null ? (logScale ? Math.log1p(Math.max(traitVmin, 0)) : traitVmin) : imageDataRange.min} dataMax={traitVmin != null && traitVmax != null ? (logScale ? Math.log1p(Math.max(traitVmax, 0)) : traitVmax) : imageDataRange.max} /> + { setContrastState(activeContrastIdx, { vminPct: min, vmaxPct: max }); }} width={110} height={58} theme={themeInfo.theme === "dark" ? "dark" : "light"} dataMin={traitVmin != null && traitVmax != null ? (logScale ? Math.log1p(Math.max(traitVmin, 0)) : traitVmin) : imageDataRange.min} dataMax={traitVmin != null && traitVmax != null ? (logScale ? Math.log1p(Math.max(traitVmax, 0)) : traitVmax) : imageDataRange.max} /> )} )} {/* ROI Section (own box, below control rows) */} - {!hideRoi && roiActive && ( - + {roiActive && ( + {/* ROI: shape + ADD + CLEAR */} ROI: @@ -4083,18 +4005,18 @@ function Show2D() { ROI FFT ({fftCropDims.cropWidth}×{fftCropDims.cropHeight}) ) : } - {!hideView && ( - + {( + )} @@ -4106,12 +4028,12 @@ function Show2D() { )} - {!hideView && ( - + {( + )} {/* FFT Stats Bar */} - {!hideStats && fftStats && fftStats.length === 4 && ( + {fftStats && fftStats.length === 4 && ( Mean {formatNumber(fftStats[0])} Min {formatNumber(fftStats[1])} @@ -4134,30 +4056,30 @@ function Show2D() { {/* FFT Controls - two rows + histogram (matching main panel layout) */} - + {/* Row 1: Scale + Color + Colorbar */} - + Scale: - setFftScaleMode(e.target.value as "linear" | "log" | "power")} size="small" sx={{ ...themedSelect, minWidth: 50, fontSize: 10 }} MenuProps={themedMenuProps}> Lin Log Pow Color: - setFftColormap(String(e.target.value))} size="small" sx={{ ...themedSelect, minWidth: 65, fontSize: 10 }} MenuProps={themedMenuProps}> {COLORMAP_NAMES.map((name) => ({name.charAt(0).toUpperCase() + name.slice(1)}))} Colorbar: - { if (!lockDisplay) setFftShowColorbar(e.target.checked); }} disabled={lockDisplay} size="small" sx={switchStyles.small} /> + { setFftShowColorbar(e.target.checked); }} size="small" sx={switchStyles.small} /> {/* Row 2: Auto + zoom indicator */} - + Auto: - { if (!lockDisplay) setFftAuto(e.target.checked); }} disabled={lockDisplay} size="small" sx={switchStyles.small} /> + { setFftAuto(e.target.checked); }} size="small" sx={switchStyles.small} /> {fftCropDims && ( <> Win: - { if (!lockDisplay) setFftWindow(e.target.checked); }} disabled={lockDisplay} size="small" sx={switchStyles.small} /> + { setFftWindow(e.target.checked); }} size="small" sx={switchStyles.small} /> )} {fftZoom !== DEFAULT_FFT_ZOOM && ( @@ -4166,10 +4088,10 @@ function Show2D() { {/* Right: FFT Histogram */} - {!hideHistogram && ( - + {( + {fftHistogramData && ( - { if (!lockHistogram) { setFftVminPct(min); setFftVmaxPct(max); } }} width={110} height={58} theme={themeInfo.theme === "dark" ? "dark" : "light"} dataMin={fftDataRange.min} dataMax={fftDataRange.max} /> + { setFftVminPct(min); setFftVmaxPct(max); }} width={110} height={58} theme={themeInfo.theme === "dark" ? "dark" : "light"} dataMin={fftDataRange.min} dataMax={fftDataRange.max} /> )} )} diff --git a/widget/js/show2d/show2d.css b/widget/js/show2d/show2d.css deleted file mode 100644 index 0e285789..00000000 --- a/widget/js/show2d/show2d.css +++ /dev/null @@ -1,9 +0,0 @@ -/* show2d.css - Minimal CSS for Show2D */ - -.show2d-root { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; -} - -.show2d-root canvas { - display: block; -} diff --git a/widget/js/show4dstem/index.tsx b/widget/js/show4dstem/index.tsx index 8ece614d..2a78455b 100644 --- a/widget/js/show4dstem/index.tsx +++ b/widget/js/show4dstem/index.tsx @@ -18,16 +18,12 @@ import StopIcon from "@mui/icons-material/Stop"; import FastRewindIcon from "@mui/icons-material/FastRewind"; import FastForwardIcon from "@mui/icons-material/FastForward"; import JSZip from "jszip"; -import "./styles.css"; import { useTheme } from "../theme"; import { COLORMAPS, applyColormap, renderToOffscreen } from "../colormaps"; -import { WebGPUFFT, getWebGPUFFT, fft2d, fftshift, autoEnhanceFFT, nextPow2, applyHannWindow2D } from "../webgpu-fft"; -import { drawScaleBarHiDPI, drawColorbar, roundToNiceValue, exportFigure, canvasToPDF } from "../scalebar"; -import { findDataRange, sliderRange, computeStats, applyLogScale } from "../stats"; +import { WebGPUFFT, getWebGPUFFT, fft2d, fftshift, autoEnhanceFFT, nextPow2, applyHannWindow2D } from "../fft"; +import { drawScaleBarHiDPI, drawColorbar, roundToNiceValue, exportFigure, canvasToPDF } from "../figure"; +import { findDataRange, sliderRange, computeStats, applyLogScale, computeHistogramFromBytes, percentileClip } from "../stats"; import { downloadBlob, formatNumber, downloadDataView } from "../format"; -import { computeHistogramFromBytes } from "../histogram"; -import { ControlCustomizer } from "../control-customizer"; -import { computeToolVisibility } from "../tool-parity"; const MIN_ZOOM = 0.5; const MAX_ZOOM = 10; @@ -132,7 +128,7 @@ const compactButton = { }, }; -// Control row style - bordered container for each row +// Control row style — bordered container per row. const controlRow = { display: "flex", alignItems: "center", @@ -961,7 +957,9 @@ function Show4DSTEM() { const [roiCenterCol, setRoiCenterCol] = useModelState("roi_center_col"); const [roiCenterRow, setRoiCenterRow] = useModelState("roi_center_row"); const [pixelSize] = useModelState("pixel_size"); + const [pixelUnit] = useModelState("pixel_unit"); const [kPixelSize] = useModelState("k_pixel_size"); + const [kPixelUnit] = useModelState("k_pixel_unit"); const [kCalibrated] = useModelState("k_calibrated"); const [widgetVersion] = useModelState("widget_version"); const [title] = useModelState("title"); @@ -981,8 +979,9 @@ function Show4DSTEM() { const [dpGlobalMax] = useModelState("dp_global_max"); // VI min/max for normalization (from Python) - const [viDataMin] = useModelState("vi_data_min"); - const [viDataMax] = useModelState("vi_data_max"); + // viDataMin/viDataMax are derived JS-side from virtual_image_bytes (computed below). + // Keeping them out of Python traits avoids a comm-message ordering race where + // bytes from click N arrive with min/max from click N-1. // Detector calibration (for presets) const [bfRadius] = useModelState("bf_radius"); @@ -1020,6 +1019,26 @@ function Show4DSTEM() { const [localPosRow, setLocalPosRow] = React.useState(posRow); const [localPosCol, setLocalPosCol] = React.useState(posCol); const [isDraggingDP, setIsDraggingDP] = React.useState(false); + // rAF coalescing for ROI drag: collapse rapid mousemove events into ≤1 + // Python comm message per animation frame. Without this, drag fires 60+ + // events/sec at >100ms Python compute each → queue piles up → laggy UX. + const roiCenterPendingRef = React.useRef<[number, number] | null>(null); + const roiCenterRafRef = React.useRef(null); + const flushRoiCenter = React.useCallback(() => { + if (roiCenterPendingRef.current) { + const [r, c] = roiCenterPendingRef.current; + model.set("roi_center", [r, c]); + model.save_changes(); + roiCenterPendingRef.current = null; + } + roiCenterRafRef.current = null; + }, [model]); + const queueRoiCenter = React.useCallback((row: number, col: number) => { + roiCenterPendingRef.current = [row, col]; + if (roiCenterRafRef.current === null) { + roiCenterRafRef.current = requestAnimationFrame(flushRoiCenter); + } + }, [flushRoiCenter]); const [isDraggingVI, setIsDraggingVI] = React.useState(false); const [isDraggingFFT, setIsDraggingFFT] = React.useState(false); const [fftDragStart, setFftDragStart] = React.useState<{ x: number, y: number, panX: number, panY: number } | null>(null); @@ -1046,11 +1065,14 @@ function Show4DSTEM() { const [traitDpVmax] = useModelState("dp_vmax"); const [traitViVmin] = useModelState("vi_vmin"); const [traitViVmax] = useModelState("vi_vmax"); - // Scale mode: "linear" | "log" | "power" - const [dpScaleMode, setDpScaleMode] = useModelState<"linear" | "log" | "power">("dp_scale_mode"); - const [dpPowerExp] = useModelState("dp_power_exp"); - const [viScaleMode, setViScaleMode] = useModelState<"linear" | "log" | "power">("vi_scale_mode"); - const [viPowerExp] = useModelState("vi_power_exp"); + // Scale mode: "linear" | "log" + const [dpScaleMode, setDpScaleMode] = useModelState<"linear" | "log">("dp_scale_mode"); + const [viScaleMode, setViScaleMode] = useModelState<"linear" | "log">("vi_scale_mode"); + // VI auto-contrast (1st/99th percentile clip) + Smooth (CSS bilinear blit). + // DP doesn't need them — Bragg spots read best with the slider's percentile + // range and nearest-neighbor blit. + const [viAutoContrast, setViAutoContrast] = useModelState("vi_auto_contrast"); + const [viSmooth, setViSmooth] = useModelState("vi_smooth"); // VI ROI state (real-space region selection for summed DP) - synced with Python const [viRoiMode, setViRoiMode] = useModelState("vi_roi_mode"); @@ -1062,46 +1084,18 @@ function Show4DSTEM() { // Local VI ROI center for smooth dragging const [localViRoiCenterRow, setLocalViRoiCenterRow] = React.useState(viRoiCenterRow || 0); const [localViRoiCenterCol, setLocalViRoiCenterCol] = React.useState(viRoiCenterCol || 0); - const [summedDpBytes] = useModelState("summed_dp_bytes"); - const [summedDpCount] = useModelState("summed_dp_count"); - const [dpStats] = useModelState("dp_stats"); // [mean, min, max, std] - const [viStats] = useModelState("vi_stats"); // [mean, min, max, std] + const [viRoiDpBytes] = useModelState("vi_roi_dp_bytes"); + const [viRoiReduce, setViRoiReduce] = useModelState("vi_roi_reduce"); + // dp_stats are computed in JS from frameBytes (Python side no longer + // syncs a dp_stats trait — saves 4 trait sync round-trips per click). + const [viStats, setViStats] = React.useState([0, 0, 0, 0]); + const [viDataMin, setViDataMin] = React.useState(0); + const [viDataMax, setViDataMax] = React.useState(1); const [showFft, setShowFft] = useModelState("show_fft"); const [fftWindow, setFftWindow] = useModelState("fft_window"); - const [disabledTools, setDisabledTools] = useModelState("disabled_tools"); - const [hiddenTools, setHiddenTools] = useModelState("hidden_tools"); const [showControls] = useModelState("show_controls"); - const toolVisibility = React.useMemo( - () => computeToolVisibility("Show4DSTEM", disabledTools, hiddenTools), - [disabledTools, hiddenTools], - ); - - const hideDisplay = toolVisibility.isHidden("display"); - const hideHistogram = toolVisibility.isHidden("histogram"); - const hideStats = toolVisibility.isHidden("stats"); - const hidePlayback = toolVisibility.isHidden("playback"); - const hideView = toolVisibility.isHidden("view"); - const hideExport = toolVisibility.isHidden("export"); - const hideRoi = toolVisibility.isHidden("roi"); - const hideProfile = toolVisibility.isHidden("profile"); - const hideVirtual = toolVisibility.isHidden("virtual"); - const hideFrame = toolVisibility.isHidden("frame"); - const hideFft = toolVisibility.isHidden("fft") || hideVirtual; - - const lockDisplay = toolVisibility.isLocked("display"); - const lockHistogram = toolVisibility.isLocked("histogram"); - const lockStats = toolVisibility.isLocked("stats"); - const lockNavigation = toolVisibility.isLocked("navigation"); - const lockPlayback = toolVisibility.isLocked("playback"); - const lockView = toolVisibility.isLocked("view"); - const lockExport = toolVisibility.isLocked("export"); - const lockRoi = toolVisibility.isLocked("roi"); - const lockProfile = toolVisibility.isLocked("profile"); - const lockVirtual = toolVisibility.isLocked("virtual"); - const lockFrame = toolVisibility.isLocked("frame"); - const lockFft = toolVisibility.isLocked("fft") || lockVirtual; - const effectiveShowFft = showFft && !hideFft; + const effectiveShowFft = showFft; // ROI FFT state (VI ROI crops virtual image for FFT) const [fftCropDims, setFftCropDims] = React.useState<{ cropWidth: number; cropHeight: number; fftWidth: number; fftHeight: number } | null>(null); @@ -1182,6 +1176,10 @@ function Show4DSTEM() { const [dpHistogramData, setDpHistogramData] = React.useState(null); const [viHistogramData, setViHistogramData] = React.useState(null); + // DP stats computed JS-side from frame_bytes (was Python trait pre-refactor; + // moving to JS skips 4 sync trait round-trips per scan-position click). + const [dpStats, setDpStats] = React.useState([0, 0, 0, 0]); + // Parse DP frame bytes for histogram (float32 now) React.useEffect(() => { if (!frameBytes) return; @@ -1192,21 +1190,20 @@ function Show4DSTEM() { rawDpDataRef.current = new Float32Array(rawData.length); } rawDpDataRef.current.set(rawData); + // Compute stats JS-side (replaces removed Python dp_stats trait) + const s = computeStats(rawData); + setDpStats([s.mean, s.min, s.max, s.std]); // Apply scale transformation for histogram display const scaledData = new Float32Array(rawData.length); if (dpScaleMode === "log") { for (let i = 0; i < rawData.length; i++) { scaledData[i] = Math.log1p(Math.max(0, rawData[i])); } - } else if (dpScaleMode === "power") { - for (let i = 0; i < rawData.length; i++) { - scaledData[i] = Math.pow(Math.max(0, rawData[i]), dpPowerExp); - } } else { scaledData.set(rawData); } setDpHistogramData(scaledData); - }, [frameBytes, dpScaleMode, dpPowerExp]); + }, [frameBytes, dpScaleMode]); // GPU FFT state const gpuFFTRef = React.useRef(null); @@ -1295,8 +1292,7 @@ function Show4DSTEM() { const [fftZoom, setFftZoom] = React.useState(1); const [fftPanX, setFftPanX] = React.useState(0); const [fftPanY, setFftPanY] = React.useState(0); - const [fftScaleMode, setFftScaleMode] = useModelState<"linear" | "log" | "power">("fft_scale_mode"); - const [fftPowerExp] = useModelState("fft_power_exp"); + const [fftScaleMode, setFftScaleMode] = useModelState<"linear" | "log">("fft_scale_mode"); const [fftColormap, setFftColormap] = useModelState("fft_colormap"); const [fftAuto, setFftAuto] = useModelState("fft_auto"); const [fftVminPct, setFftVminPct] = useModelState("fft_vmin_pct"); @@ -1330,52 +1326,42 @@ function Show4DSTEM() { switch (e.key) { case "ArrowUp": - if (!lockNavigation) { - setPosRow(Math.max(0, posRow - step)); - handled = true; - } + setPosRow(Math.max(0, posRow - step)); + handled = true; break; case "ArrowDown": - if (!lockNavigation) { - setPosRow(Math.min(shapeRows - 1, posRow + step)); - handled = true; - } + setPosRow(Math.min(shapeRows - 1, posRow + step)); + handled = true; break; case "ArrowLeft": - if (!lockNavigation) { - setPosCol(Math.max(0, posCol - step)); - handled = true; - } + setPosCol(Math.max(0, posCol - step)); + handled = true; break; case "ArrowRight": - if (!lockNavigation) { - setPosCol(Math.min(shapeCols - 1, posCol + step)); - handled = true; - } + setPosCol(Math.min(shapeCols - 1, posCol + step)); + handled = true; break; case " ": // Space bar - if (!lockPlayback && pathLength > 0) { + if (pathLength > 0) { setPathPlaying(!pathPlaying); handled = true; } break; case "r": case "R": - if (!lockView) { - setDpZoom(1); setDpPanX(0); setDpPanY(0); - setViZoom(1); setViPanX(0); setViPanY(0); - setFftZoom(1); setFftPanX(0); setFftPanY(0); - handled = true; - } + setDpZoom(1); setDpPanX(0); setDpPanY(0); + setViZoom(1); setViPanX(0); setViPanY(0); + setFftZoom(1); setFftPanX(0); setFftPanY(0); + handled = true; break; case "[": - if (!lockPlayback && !lockFrame && nFrames > 1) { + if (nFrames > 1) { setFrameIdx(Math.max(0, frameIdx - 1)); handled = true; } break; case "]": - if (!lockPlayback && !lockFrame && nFrames > 1) { + if (nFrames > 1) { setFrameIdx(Math.min(nFrames - 1, frameIdx + 1)); handled = true; } @@ -1391,53 +1377,10 @@ function Show4DSTEM() { e.stopPropagation(); } }, [ - frameIdx, isTypingTarget, lockFrame, lockNavigation, lockPlayback, lockView, nFrames, pathLength, + frameIdx, isTypingTarget, nFrames, pathLength, pathPlaying, posCol, posRow, setFrameIdx, setPathPlaying, setPosCol, setPosRow, shapeCols, shapeRows, ]); - React.useEffect(() => { - if (hideFft && showFft) { - setShowFft(false); - } - }, [hideFft, showFft, setShowFft]); - - React.useEffect(() => { - if (lockPlayback && pathPlaying) { - setPathPlaying(false); - } - }, [lockPlayback, pathPlaying, setPathPlaying]); - - React.useEffect(() => { - if ((lockPlayback || lockFrame) && framePlaying) { - setFramePlaying(false); - } - }, [lockFrame, lockPlayback, framePlaying, setFramePlaying]); - - React.useEffect(() => { - if (hideRoi) { - if (roiMode !== "point") setRoiMode("point"); - if (viRoiMode !== "off") setViRoiMode("off"); - } - }, [hideRoi, roiMode, viRoiMode, setRoiMode, setViRoiMode]); - - React.useEffect(() => { - if (hideProfile) { - if (profileActive) setProfileActive(false); - if (viProfileActive) setViProfileActive(false); - if (profileLine.length > 0) setProfileLine([]); - if (profileData) setProfileData(null); - if (viProfilePoints.length > 0) setViProfilePoints([]); - if (viProfileData) setViProfileData(null); - setHoveredDpProfileEndpoint(null); - setIsHoveringDpProfileLine(false); - setHoveredViProfileEndpoint(null); - setIsHoveringViProfileLine(false); - } - }, [ - hideProfile, profileActive, profileLine, profileData, setProfileLine, viProfileActive, - viProfilePoints, viProfileData, - ]); - // Sync local state React.useEffect(() => { if (!isDraggingDP && !isDraggingResize) { setLocalKCol(roiCenterCol); setLocalKRow(roiCenterRow); } @@ -1524,28 +1467,33 @@ function Show4DSTEM() { } rawViDataRef.current.set(rawData); + // Compute stats + min/max JS-side (replaces removed Python vi_stats / vi_data_min / vi_data_max traits). + // Python sending bytes + 4 separate stat traits caused a comm-message ordering race on rapid + // preset clicks: bytes from click N could arrive with min/max from click N-1, normalizing + // the colormap to the wrong range and producing a uniform-color VI flash. + const s = computeStats(rawData); + setViStats([s.mean, s.min, s.max, s.std]); + setViDataMin(s.min); + setViDataMax(s.max); + // Apply scale transformation for histogram display const scaledData = new Float32Array(numFloats); if (viScaleMode === "log") { for (let i = 0; i < numFloats; i++) { scaledData[i] = Math.log1p(Math.max(0, rawData[i])); } - } else if (viScaleMode === "power") { - for (let i = 0; i < numFloats; i++) { - scaledData[i] = Math.pow(Math.max(0, rawData[i]), viPowerExp); - } } else { scaledData.set(rawData); } setViHistogramData(scaledData); - }, [virtualImageBytes, viScaleMode, viPowerExp]); + }, [virtualImageBytes, viScaleMode]); // Render DP with zoom (use summed DP when VI ROI is active) // Expensive: colormap + data processing → cached offscreen canvas React.useEffect(() => { // Determine which bytes to display: summed DP (if VI ROI active) or single frame - const usesSummedDp = viRoiMode && viRoiMode !== "off" && summedDpBytes && summedDpBytes.byteLength > 0; - const sourceBytes = usesSummedDp ? summedDpBytes : frameBytes; + const usesViRoiDp = viRoiMode && viRoiMode !== "off" && viRoiDpBytes && viRoiDpBytes.byteLength > 0; + const sourceBytes = usesViRoiDp ? viRoiDpBytes : frameBytes; if (!sourceBytes) return; const lut = COLORMAPS[dpColormap] || COLORMAPS.inferno; @@ -1558,27 +1506,17 @@ function Show4DSTEM() { for (let i = 0; i < rawData.length; i++) { scaled[i] = Math.log1p(Math.max(0, rawData[i])); } - } else if (dpScaleMode === "power") { - scaled = new Float32Array(rawData.length); - for (let i = 0; i < rawData.length; i++) { - scaled[i] = Math.pow(Math.max(0, rawData[i]), dpPowerExp); - } } else { scaled = rawData; } - // Compute actual min/max of scaled data for normalization const { min: dataMin, max: dataMax } = findDataRange(scaled); - // Apply absolute bounds or percentile clipping let vmin: number, vmax: number; if (traitDpVmin != null && traitDpVmax != null) { if (dpScaleMode === "log") { vmin = Math.log1p(Math.max(traitDpVmin, 0)); vmax = Math.log1p(Math.max(traitDpVmax, 0)); - } else if (dpScaleMode === "power") { - vmin = Math.pow(Math.max(traitDpVmin, 0), dpPowerExp); - vmax = Math.pow(Math.max(traitDpVmax, 0), dpPowerExp); } else { vmin = traitDpVmin; vmax = traitDpVmax; @@ -1612,7 +1550,7 @@ function Show4DSTEM() { dpColorbarVminRef.current = vmin; dpColorbarVmaxRef.current = vmax; setDpOffscreenVersion(v => v + 1); - }, [frameBytes, summedDpBytes, viRoiMode, detRows, detCols, dpColormap, dpVminPct, dpVmaxPct, dpScaleMode, dpPowerExp, traitDpVmin, traitDpVmax]); + }, [frameBytes, viRoiDpBytes, viRoiMode, detRows, detCols, dpColormap, dpVminPct, dpVmaxPct, dpScaleMode, traitDpVmin, traitDpVmax]); // Cheap: zoom/pan redraw — just drawImage from cached offscreen // useLayoutEffect prevents black flash when canvas dimensions change (resize) @@ -1649,41 +1587,23 @@ function Show4DSTEM() { const height = shapeRows; const filtered = rawVirtualImageRef.current; - // Apply scale transformation first let scaled = filtered; if (viScaleMode === "log") { scaled = new Float32Array(filtered.length); for (let i = 0; i < filtered.length; i++) { scaled[i] = Math.log1p(Math.max(0, filtered[i])); } - } else if (viScaleMode === "power") { - scaled = new Float32Array(filtered.length); - for (let i = 0; i < filtered.length; i++) { - scaled[i] = Math.pow(Math.max(0, filtered[i]), viPowerExp); - } } - // Use Python's pre-computed min/max when valid, fallback to computing from data - let dataMin: number, dataMax: number; - const hasValidMinMax = viDataMin !== undefined && viDataMax !== undefined && viDataMax > viDataMin; - if (hasValidMinMax) { - // Apply scale transform to Python's values - if (viScaleMode === "log") { - dataMin = Math.log1p(Math.max(0, viDataMin)); - dataMax = Math.log1p(Math.max(0, viDataMax)); - } else if (viScaleMode === "power") { - dataMin = Math.pow(Math.max(0, viDataMin), viPowerExp); - dataMax = Math.pow(Math.max(0, viDataMax), viPowerExp); - } else { - dataMin = viDataMin; - dataMax = viDataMax; - } - } else { - // Fallback: compute from scaled data - const r = findDataRange(scaled); - dataMin = r.min; - dataMax = r.max; - } + // Compute min/max from the data we just received. Do NOT use Python's + // viDataMin/viDataMax traits here: they arrive as separate comm messages + // and can be stale on rapid preset clicks (BF↔ABF), causing the render + // to apply the WRONG normalization range and produce a uniform white/black + // VI panel until comm catches up. findDataRange on a scan-shape buffer + // (~64K-256K floats) is sub-millisecond. + const r = findDataRange(scaled); + const dataMin = r.min; + const dataMax = r.max; // Apply absolute bounds or percentile clipping let vmin: number, vmax: number; @@ -1691,13 +1611,12 @@ function Show4DSTEM() { if (viScaleMode === "log") { vmin = Math.log1p(Math.max(traitViVmin, 0)); vmax = Math.log1p(Math.max(traitViVmax, 0)); - } else if (viScaleMode === "power") { - vmin = Math.pow(Math.max(traitViVmin, 0), viPowerExp); - vmax = Math.pow(Math.max(traitViVmax, 0), viPowerExp); } else { vmin = traitViVmin; vmax = traitViVmax; } + } else if (viAutoContrast) { + ({ vmin, vmax } = percentileClip(scaled, 1, 99)); } else { ({ vmin, vmax } = sliderRange(dataMin, dataMax, viVminPct, viVmaxPct)); } @@ -1725,9 +1644,7 @@ function Show4DSTEM() { applyColormap(scaled, imageData.data, lut, vmin, vmax); offCtx.putImageData(imageData, 0, 0); setViOffscreenVersion(v => v + 1); - // Note: viDataMin/viDataMax intentionally not in deps - they arrive with virtualImageBytes - // and we have a fallback if they're stale - }, [virtualImageBytes, shapeRows, shapeCols, viColormap, viVminPct, viVmaxPct, viScaleMode, viPowerExp, traitViVmin, traitViVmax]); + }, [virtualImageBytes, shapeRows, shapeCols, viColormap, viVminPct, viVmaxPct, viScaleMode, traitViVmin, traitViVmax, viAutoContrast]); // Cheap: VI zoom/pan redraw — just drawImage from cached offscreen React.useLayoutEffect(() => { @@ -1736,14 +1653,15 @@ function Show4DSTEM() { const canvas = virtualCanvasRef.current; const ctx = canvas.getContext("2d"); if (!ctx) return; - ctx.imageSmoothingEnabled = false; + ctx.imageSmoothingEnabled = viSmooth; + if (viSmooth) ctx.imageSmoothingQuality = "high"; ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.save(); ctx.translate(viPanX, viPanY); ctx.scale(viZoom, viZoom); ctx.drawImage(offscreen, 0, 0); ctx.restore(); - }, [viOffscreenVersion, viZoom, viPanX, viPanY]); + }, [viOffscreenVersion, viZoom, viPanX, viPanY, viSmooth]); // Render virtual image overlay (just clear - crosshair drawn on high-DPI UI canvas) React.useEffect(() => { @@ -1768,9 +1686,14 @@ function Show4DSTEM() { let sourceData = rawVirtualImageRef.current; let origCropW = 0, origCropH = 0; - // ROI FFT: crop virtual image to VI ROI region and pre-pad to power-of-2 + // ROI FFT: crop virtual image to VI ROI region and pre-pad to power-of-2. + // Use localViRoiCenter* (updated immediately on drag) instead of the synced + // model traits, which lag by one comm roundtrip after a compound trait write. + // Without this, FFT visibly stalls during rapid VI ROI drag. if (roiFftActive) { - const crop = cropSingleROI(sourceData, shapeCols, shapeRows, viRoiMode, viRoiCenterRow, viRoiCenterCol, viRoiRadius, viRoiWidth, viRoiHeight); + const cRow = localViRoiCenterRow ?? viRoiCenterRow; + const cCol = localViRoiCenterCol ?? viRoiCenterCol; + const crop = cropSingleROI(sourceData, shapeCols, shapeRows, viRoiMode, cRow, cCol, viRoiRadius, viRoiWidth, viRoiHeight); if (crop) { origCropW = crop.cropW; origCropH = crop.cropH; @@ -1850,7 +1773,7 @@ function Show4DSTEM() { } setFftVersion(v => v + 1); } - }, [virtualImageBytes, shapeRows, shapeCols, gpuReady, effectiveShowFft, roiFftActive, viRoiMode, viRoiCenterRow, viRoiCenterCol, viRoiRadius, viRoiWidth, viRoiHeight, fftWindow]); + }, [virtualImageBytes, shapeRows, shapeCols, gpuReady, effectiveShowFft, roiFftActive, viRoiMode, viRoiCenterRow, viRoiCenterCol, localViRoiCenterRow, localViRoiCenterCol, viRoiRadius, viRoiWidth, viRoiHeight, fftWindow]); // Expensive: FFT magnitude + histogram + colormap → cached offscreen canvas React.useEffect(() => { @@ -1879,7 +1802,6 @@ function Show4DSTEM() { const mag = Math.sqrt(real[i] * real[i] + imag[i] * imag[i]); rawMag[i] = mag; if (fftScaleMode === "log") { magnitude[i] = Math.log1p(mag); } - else if (fftScaleMode === "power") { magnitude[i] = Math.pow(mag, fftPowerExp); } else { magnitude[i] = mag; } } @@ -1910,7 +1832,7 @@ function Show4DSTEM() { applyColormap(magnitude, imgData.data, lut, vmin, vmax); offCtx.putImageData(imgData, 0, 0); setFftOffscreenVersion(v => v + 1); - }, [effectiveShowFft, fftVersion, fftScaleMode, fftPowerExp, fftAuto, fftVminPct, fftVmaxPct, fftColormap, shapeRows, shapeCols, fftCropDims]); + }, [effectiveShowFft, fftVersion, fftScaleMode, fftAuto, fftVminPct, fftVmaxPct, fftColormap, shapeRows, shapeCols, fftCropDims]); // Cheap: FFT zoom/pan redraw — just drawImage from cached offscreen React.useLayoutEffect(() => { @@ -1924,13 +1846,15 @@ function Show4DSTEM() { const fftH = offscreen.height; const canvasW = canvas.width; const canvasH = canvas.height; - // Use bilinear smoothing when FFT dims differ from canvas (non-pow2 padding or ROI crop) + // Use bilinear smoothing when FFT dims differ from canvas (non-pow2 padding or ROI crop). + // Stretch offscreen to fill canvas via the 9-arg drawImage form: ROI FFT crops produce a + // small offscreen (e.g. 64×64) that would otherwise blit at native size in the corner. ctx.imageSmoothingEnabled = fftW !== canvasW || fftH !== canvasH; ctx.clearRect(0, 0, canvasW, canvasH); ctx.save(); ctx.translate(fftPanX, fftPanY); ctx.scale(fftZoom, fftZoom); - ctx.drawImage(offscreen, 0, 0); + ctx.drawImage(offscreen, 0, 0, fftW, fftH, 0, 0, canvasW, canvasH); ctx.restore(); }, [fftOffscreenVersion, fftZoom, fftPanX, fftPanY, effectiveShowFft]); @@ -1947,9 +1871,9 @@ function Show4DSTEM() { const fftW = fftCropDims?.fftWidth ?? shapeCols; const fftH = fftCropDims?.fftHeight ?? shapeRows; ctx.save(); - // Convert FFT image coords to canvas coords via zoom/pan transform - const screenX = fftPanX + fftZoom * fftClickInfo.col; - const screenY = fftPanY + fftZoom * fftClickInfo.row; + // Forward mapping: image col/row → canvas x/y (matches stretched drawImage). + const screenX = fftPanX + fftZoom * (fftClickInfo.col * canvas.width / fftW); + const screenY = fftPanY + fftZoom * (fftClickInfo.row * canvas.height / fftH); ctx.strokeStyle = "rgba(255, 255, 255, 0.9)"; ctx.shadowColor = "rgba(0, 0, 0, 0.6)"; ctx.shadowBlur = 2; @@ -1994,7 +1918,7 @@ function Show4DSTEM() { React.useEffect(() => { if (!dpUiRef.current) return; // Draw scale bar first (clears canvas) - const kUnit = kCalibrated ? "mrad" : "px"; + const kUnit = kCalibrated ? kPixelUnit : "px"; drawScaleBarHiDPI(dpUiRef.current, DPR, dpZoom, kPixelSize || 1, kUnit, detCols); // Draw ROI overlay (circle, square, rect, annular) or point crosshair if (roiMode === "point") { @@ -2099,7 +2023,7 @@ function Show4DSTEM() { React.useEffect(() => { if (!viUiRef.current) return; // Draw scale bar first (clears canvas) - drawScaleBarHiDPI(viUiRef.current, DPR, viZoom, pixelSize || 1, "Å", shapeCols); + drawScaleBarHiDPI(viUiRef.current, DPR, viZoom, pixelSize || 1, pixelUnit || "px", shapeCols); // Draw crosshair only when ROI is off (ROI replaces the crosshair) if (!viRoiMode || viRoiMode === "off") { drawViPositionMarker(viUiRef.current, DPR, localPosRow, localPosCol, viZoom, viPanX, viPanY, shapeCols, shapeRows, isDraggingVI); @@ -2233,7 +2157,7 @@ function Show4DSTEM() { const distPx = Math.sqrt(dx * dx + dy * dy); if (kCalibrated && kPixelSize > 0) { totalDist = distPx * kPixelSize; - xUnit = "mrad"; + xUnit = kPixelUnit; } else { totalDist = distPx; } @@ -2440,8 +2364,7 @@ function Show4DSTEM() { const dy = viProfilePoints[1].row - viProfilePoints[0].row; const distPx = Math.sqrt(dx * dx + dy * dy); totalDist = distPx * pixelSize; - xUnit = pixelSize >= 10 ? "nm" : "Å"; - if (xUnit === "nm") totalDist /= 10; + xUnit = pixelUnit; } // Draw axes @@ -2601,9 +2524,7 @@ function Show4DSTEM() { setPanY: React.Dispatch>, zoom: number, panX: number, panY: number, canvasRef: React.RefObject, - locked: boolean = false, ) => (e: React.WheelEvent) => { - if (locked) return; e.preventDefault(); const canvas = canvasRef.current; if (!canvas) return; @@ -2706,8 +2627,6 @@ function Show4DSTEM() { // Mouse handlers const handleDpMouseDown = (e: React.MouseEvent) => { - if (profileActive && lockProfile) return; - if (!profileActive && lockRoi) return; dpClickStartRef.current = { x: e.clientX, y: e.clientY }; const canvas = dpOverlayRef.current; if (!canvas) return; @@ -2793,8 +2712,8 @@ function Show4DSTEM() { const pxCol = Math.floor(imgX); const pxRow = Math.floor(imgY); if (pxCol >= 0 && pxCol < detCols && pxRow >= 0 && pxRow < detRows && frameBytes) { - const usesSummedDp = viRoiMode && viRoiMode !== "off" && summedDpBytes && summedDpBytes.byteLength > 0; - const sourceBytes = usesSummedDp ? summedDpBytes : frameBytes; + const usesViRoiDp = viRoiMode && viRoiMode !== "off" && viRoiDpBytes && viRoiDpBytes.byteLength > 0; + const sourceBytes = usesViRoiDp ? viRoiDpBytes : frameBytes; const raw = new Float32Array(sourceBytes.buffer, sourceBytes.byteOffset, sourceBytes.byteLength / 4); setCursorInfo({ row: pxRow, col: pxCol, value: raw[pxRow * detCols + pxCol], panel: "DP" }); } else { @@ -2802,8 +2721,6 @@ function Show4DSTEM() { } } - if (profileActive && lockProfile) return; - if (profileActive && profilePoints.length === 2) { const p0 = profilePoints[0]; const p1 = profilePoints[1]; @@ -2855,7 +2772,6 @@ function Show4DSTEM() { // Handle inner resize dragging (annular mode) if (isDraggingResizeInner) { - if (lockRoi) return; const dx = Math.abs(imgX - roiCenterCol); const dy = Math.abs(imgY - roiCenterRow); const newRadius = Math.sqrt(dx ** 2 + dy ** 2); @@ -2866,7 +2782,6 @@ function Show4DSTEM() { // Handle outer resize dragging - use model state center, not local values if (isDraggingResize) { - if (lockRoi) return; const dx = Math.abs(imgX - roiCenterCol); const dy = Math.abs(imgY - roiCenterRow); if (roiMode === "rect") { @@ -2890,25 +2805,18 @@ function Show4DSTEM() { // Check hover state for resize handles if (!isDraggingDP) { - if (!lockRoi) { - setIsHoveringResizeInner(isNearResizeHandleInner(imgX, imgY)); - setIsHoveringResize(isNearResizeHandle(imgX, imgY)); - } else { - setIsHoveringResizeInner(false); - setIsHoveringResize(false); - } + setIsHoveringResizeInner(isNearResizeHandleInner(imgX, imgY)); + setIsHoveringResize(isNearResizeHandle(imgX, imgY)); return; } - if (lockRoi) return; const centerCol = imgX - dpDragOffsetRef.current.dCol; const centerRow = imgY - dpDragOffsetRef.current.dRow; setLocalKCol(centerCol); setLocalKRow(centerRow); - // Use compound roi_center trait [row, col] - single observer fires in Python + // rAF-coalesced — sends only the latest roi_center per frame. const newCol = Math.round(Math.max(0, Math.min(detCols - 1, centerCol))); const newRow = Math.round(Math.max(0, Math.min(detRows - 1, centerRow))); - model.set("roi_center", [newRow, newCol]); - model.save_changes(); + queueRoiCenter(newRow, newCol); }; const handleDpMouseUp = (e: React.MouseEvent) => { @@ -2971,14 +2879,12 @@ function Show4DSTEM() { setCursorInfo(prev => prev?.panel === "DP" ? null : prev); }; const handleDpDoubleClick = () => { - if (lockView) return; setDpZoom(1); setDpPanX(0); setDpPanY(0); }; const handleViMouseDown = (e: React.MouseEvent) => { - if (viProfileActive && lockProfile) return; const canvas = virtualOverlayRef.current; if (!canvas) return; const rect = canvas.getBoundingClientRect(); @@ -3018,7 +2924,6 @@ function Show4DSTEM() { // Check if VI ROI mode is active - same logic as DP if (viRoiMode && viRoiMode !== "off") { - if (lockRoi) return; // Check if clicking on resize handle if (isNearViRoiResizeHandle(imgX, imgY)) { setIsDraggingViRoiResize(true); @@ -3040,7 +2945,6 @@ function Show4DSTEM() { } // Regular position selection (when ROI is off) - if (lockNavigation || lockVirtual) return; setIsDraggingVI(true); setLocalPosRow(imgX); setLocalPosCol(imgY); // Batch X and Y updates into a single sync @@ -3077,8 +2981,6 @@ function Show4DSTEM() { } } - if (viProfileActive && lockProfile) return; - if (viProfileActive && viProfilePoints.length === 2) { const p0 = viProfilePoints[0]; const p1 = viProfilePoints[1]; @@ -3126,7 +3028,6 @@ function Show4DSTEM() { // Handle VI ROI resize dragging (same pattern as DP) if (isDraggingViRoiResize) { - if (lockRoi) return; const dx = Math.abs(imgX - localViRoiCenterRow); const dy = Math.abs(imgY - localViRoiCenterCol); if (viRoiMode === "rect") { @@ -3145,33 +3046,27 @@ function Show4DSTEM() { // Check hover state for resize handles (same as DP) if (!isDraggingViRoi) { - if (!lockRoi) { - setIsHoveringViRoiResize(isNearViRoiResizeHandle(imgX, imgY)); - } else { - setIsHoveringViRoiResize(false); - } + setIsHoveringViRoiResize(isNearViRoiResizeHandle(imgX, imgY)); if (viRoiMode && viRoiMode !== "off") return; // Don't update position when ROI active } // Handle VI ROI center dragging (same as DP — with offset) if (isDraggingViRoi) { - if (lockRoi) return; const centerRow = imgX - viRoiDragOffsetRef.current.dRow; const centerCol = imgY - viRoiDragOffsetRef.current.dCol; setLocalViRoiCenterRow(centerRow); setLocalViRoiCenterCol(centerCol); - // Batch VI ROI center updates + // Compound trait update — single observer fires Python-side; reduced DP is + // never computed against split-trait state (old col + new row, or vice versa). const newViX = Math.round(Math.max(0, Math.min(shapeRows - 1, centerRow))); const newViY = Math.round(Math.max(0, Math.min(shapeCols - 1, centerCol))); - model.set("vi_roi_center_row", newViX); - model.set("vi_roi_center_col", newViY); + model.set("vi_roi_center", [newViX, newViY]); model.save_changes(); return; } // Handle regular position dragging (when ROI is off) if (!isDraggingVI) return; - if (lockNavigation || lockVirtual) return; setLocalPosRow(imgX); setLocalPosCol(imgY); // Batch position updates into a single sync const newX = Math.round(Math.max(0, Math.min(shapeRows - 1, imgX))); @@ -3244,13 +3139,11 @@ function Show4DSTEM() { setCursorInfo(prev => prev?.panel === "VI" ? null : prev); }; const handleViDoubleClick = () => { - if (lockView || lockVirtual) return; setViZoom(1); setViPanX(0); setViPanY(0); }; const handleFftDoubleClick = () => { - if (lockView || lockFft) return; setFftZoom(1); setFftPanX(0); setFftPanY(0); @@ -3259,14 +3152,12 @@ function Show4DSTEM() { // FFT drag-to-pan handlers const handleFftMouseDown = (e: React.MouseEvent) => { - if (lockView || lockFft) return; fftClickStartRef.current = { x: e.clientX, y: e.clientY }; setIsDraggingFFT(true); setFftDragStart({ x: e.clientX, y: e.clientY, panX: fftPanX, panY: fftPanY }); }; const handleFftMouseMove = (e: React.MouseEvent) => { - if (lockView || lockFft) return; if (!isDraggingFFT || !fftDragStart) return; const canvas = fftOverlayRef.current; if (!canvas) return; @@ -3295,10 +3186,11 @@ function Show4DSTEM() { const canvasY = (e.clientY - rect.top) * scaleY; const fftW = fftCropDims?.fftWidth ?? shapeCols; const fftH = fftCropDims?.fftHeight ?? shapeRows; - // Reverse the zoom/pan transform: canvas coords -> image coords - // The FFT render uses: ctx.translate(fftPanX, fftPanY); ctx.scale(fftZoom, fftZoom); ctx.drawImage(offscreen, 0, 0) - let imgCol = (canvasX - fftPanX) / fftZoom; - let imgRow = (canvasY - fftPanY) / fftZoom; + // Reverse the render transform: canvas coords -> image coords. + // Render: translate(panX, panY); scale(zoom); drawImage(offscreen, 0,0,fftW,fftH, 0,0,canvasW,canvasH) + // So: canvasX = panX + zoom * (imgCol * canvasW / fftW) → imgCol = (canvasX - panX) / zoom * fftW / canvasW + let imgCol = ((canvasX - fftPanX) / fftZoom) * (fftW / canvas.width); + let imgRow = ((canvasY - fftPanY) / fftZoom) * (fftH / canvas.height); // Bounds check if (imgCol >= 0 && imgCol < fftW && imgRow >= 0 && imgRow < fftH) { // Snap to nearest peak in FFT magnitude @@ -3341,7 +3233,6 @@ function Show4DSTEM() { // ── Canvas resize handlers ── const handleCanvasResizeStart = (e: React.MouseEvent) => { - if (lockView) return; e.stopPropagation(); e.preventDefault(); setIsResizingCanvas(true); @@ -3384,7 +3275,6 @@ function Show4DSTEM() { // Export DP handler const handleExportDP = async () => { - if (lockExport) return; const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19); const zip = new JSZip(); const metadata = { @@ -3446,7 +3336,6 @@ function Show4DSTEM() { // Export VI handler const handleExportVI = async () => { - if (lockExport) return; const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19); const zip = new JSZip(); const metadata = { @@ -3523,7 +3412,6 @@ function Show4DSTEM() { // ── DP Figure Export ── const handleDpExportFigure = (withColorbar: boolean) => { - if (lockExport) return; setDpExportAnchor(null); const frameData = rawDpDataRef.current; if (!frameData) return; @@ -3535,9 +3423,6 @@ function Show4DSTEM() { if (dpScaleMode === "log") { vmin = Math.log1p(Math.max(traitDpVmin, 0)); vmax = Math.log1p(Math.max(traitDpVmax, 0)); - } else if (dpScaleMode === "power") { - vmin = Math.pow(Math.max(traitDpVmin, 0), dpPowerExp); - vmax = Math.pow(Math.max(traitDpVmax, 0), dpPowerExp); } else { vmin = traitDpVmin; vmax = traitDpVmax; @@ -3563,14 +3448,12 @@ function Show4DSTEM() { }; const handleDpExportPng = () => { - if (lockExport) return; setDpExportAnchor(null); if (!dpCanvasRef.current) return; dpCanvasRef.current.toBlob((b) => { if (b) downloadBlob(b, "show4dstem_dp.png"); }, "image/png"); }; const handleDpExportGif = () => { - if (lockExport) return; setDpExportAnchor(null); setExporting(true); setGifExportRequested(true); @@ -3578,7 +3461,6 @@ function Show4DSTEM() { // ── VI Figure Export ── const handleViExportFigure = (withColorbar: boolean) => { - if (lockExport) return; setViExportAnchor(null); if (!virtualCanvasRef.current) return; const viCanvas = virtualCanvasRef.current; @@ -3594,7 +3476,6 @@ function Show4DSTEM() { }; const handleViExportPng = () => { - if (lockExport) return; setViExportAnchor(null); if (!virtualCanvasRef.current) return; virtualCanvasRef.current.toBlob((b) => { if (b) downloadBlob(b, "show4dstem_vi.png"); }, "image/png"); @@ -3646,7 +3527,7 @@ function Show4DSTEM() { tabIndex={0} onKeyDown={handleKeyDown} onMouseDownCapture={handleRootMouseDownCapture} - sx={{ p: `${SPACING.LG}px`, bgcolor: themeColors.bg, color: themeColors.text, outline: "none" }} + sx={{ p: 2, bgcolor: themeColors.bg, color: themeColors.text, outline: "none", borderRadius: "2px" }} > {/* HEADER */} @@ -3659,6 +3540,8 @@ function Show4DSTEM() { BF/ABF/ADF: Preset detector configurations (bright-field, annular bright-field, annular dark-field). Image: Virtual image — integrated intensity within detector ROI at each scan position. FFT: Spatial frequency content of the virtual image. Auto masks DC + clips to 99.9th percentile. + Smooth: CSS bilinear blit on the VI canvas. No data change — browser smooths the upscale visually. Off = nearest-neighbor (sharp pixel boundaries). + Auto: Percentile contrast (1st–99th). Clips outliers automatically. Profile: Click two points on DP to draw a line intensity profile. {nFrames > 1 && <> Frame Playback ({frameDimLabel}) @@ -3668,14 +3551,6 @@ function Show4DSTEM() { Keyboard } theme={themeInfo.theme} /> - {/* MAIN CONTENT: DP | VI | FFT (three columns when FFT shown) */} @@ -3686,52 +3561,39 @@ function Show4DSTEM() { DP at ({Math.round(localPosRow)}, {Math.round(localPosCol)}) - {!hideRoi && k: ({Math.round(localKRow)}, {Math.round(localKCol)})} + k: ({Math.round(localKRow)}, {Math.round(localKCol)}) - {!hideProfile && ( - <> - Profile: - { - if (lockProfile) return; - const on = e.target.checked; - setProfileActive(on); - if (!on) { - setProfileLine([]); - setProfileData(null); - setHoveredDpProfileEndpoint(null); - setIsHoveringDpProfileLine(false); - } - }} disabled={lockProfile} size="small" sx={switchStyles.small} /> - - )} - {!hideView && ( - - )} - {!hideExport && ( - - )} - {!hideExport && ( - - )} - {!hideExport && ( - setDpExportAnchor(null)} anchorOrigin={{ vertical: "bottom", horizontal: "left" }} transformOrigin={{ vertical: "top", horizontal: "left" }} sx={{ zIndex: 9999 }}> - handleDpExportFigure(true)} sx={{ fontSize: 12 }}>PDF + colorbar - handleDpExportFigure(false)} sx={{ fontSize: 12 }}>PDF - PNG - { if (!lockExport) { setDpExportAnchor(null); handleExportDP(); } }} sx={{ fontSize: 12 }}>ZIP (PNG + metadata) - {pathLength > 0 && GIF (path animation)} - - )} + Profile: + { + const on = e.target.checked; + setProfileActive(on); + if (!on) { + setProfileLine([]); + setProfileData(null); + setHoveredDpProfileEndpoint(null); + setIsHoveringDpProfileLine(false); + } + }} size="small" sx={switchStyles.small} /> + + + + setDpExportAnchor(null)} anchorOrigin={{ vertical: "bottom", horizontal: "left" }} transformOrigin={{ vertical: "top", horizontal: "left" }} sx={{ zIndex: 9999 }}> + handleDpExportFigure(true)} sx={{ fontSize: 12 }}>PDF + colorbar + handleDpExportFigure(false)} sx={{ fontSize: 12 }}>PDF + PNG + { setDpExportAnchor(null); handleExportDP(); }} sx={{ fontSize: 12 }}>ZIP (PNG + metadata) + {pathLength > 0 && GIF (path animation)} + @@ -3742,21 +3604,19 @@ function Show4DSTEM() { ref={dpOverlayRef} width={detCols} height={detRows} onMouseDown={handleDpMouseDown} onMouseMove={handleDpMouseMove} onMouseUp={handleDpMouseUp} onMouseLeave={handleDpMouseLeave} - onWheel={createZoomHandler(setDpZoom, setDpPanX, setDpPanY, dpZoom, dpPanX, dpPanY, dpOverlayRef, lockView)} + onWheel={createZoomHandler(setDpZoom, setDpPanX, setDpPanY, dpZoom, dpPanX, dpPanY, dpOverlayRef)} onDoubleClick={handleDpDoubleClick} style={{ position: "absolute", width: "100%", height: "100%", - cursor: (profileActive && lockProfile) || (!profileActive && lockRoi) - ? "default" - : (draggingDpProfileEndpoint !== null || isDraggingDpProfileLine) - ? "grabbing" - : (profileActive && (hoveredDpProfileEndpoint !== null || isHoveringDpProfileLine)) - ? "grab" - : isHoveringResize || isDraggingResize - ? "nwse-resize" - : "crosshair", + cursor: (draggingDpProfileEndpoint !== null || isDraggingDpProfileLine) + ? "grabbing" + : (profileActive && (hoveredDpProfileEndpoint !== null || isHoveringDpProfileLine)) + ? "grab" + : isHoveringResize || isDraggingResize + ? "nwse-resize" + : "crosshair", }} /> @@ -3767,31 +3627,25 @@ function Show4DSTEM() { )} - {!hideView && ( - - )} + {/* DP Stats Bar */} - {!hideStats && dpStats && dpStats.length === 4 && ( - + {dpStats && dpStats.length === 4 && ( + Mean {formatStat(dpStats[0])} Min {formatStat(dpStats[1])} Max {formatStat(dpStats[2])} Std {formatStat(dpStats[3])} - {!hideRoi && ( - <> - - { if (!lockRoi) { setRoiMode("circle"); setRoiRadius(bfRadius || 10); setRoiCenterCol(centerCol); setRoiCenterRow(centerRow); } }} sx={{ color: roiColors.textColor, fontSize: 11, fontWeight: "bold", cursor: lockRoi ? "default" : "pointer", opacity: lockRoi ? 0.6 : 1, "&:hover": { textDecoration: lockRoi ? "none" : "underline" } }}>BF - { if (!lockRoi) { setRoiMode("annular"); setRoiRadiusInner((bfRadius || 10) * 0.5); setRoiRadius(bfRadius || 10); setRoiCenterCol(centerCol); setRoiCenterRow(centerRow); } }} sx={{ color: "#4af", fontSize: 11, fontWeight: "bold", cursor: lockRoi ? "default" : "pointer", opacity: lockRoi ? 0.6 : 1, "&:hover": { textDecoration: lockRoi ? "none" : "underline" } }}>ABF - { if (!lockRoi) { setRoiMode("annular"); setRoiRadiusInner(bfRadius || 10); setRoiRadius(Math.min((bfRadius || 10) * 3, Math.min(detRows, detCols) / 2 - 2)); setRoiCenterCol(centerCol); setRoiCenterRow(centerRow); } }} sx={{ color: "#fa4", fontSize: 11, fontWeight: "bold", cursor: lockRoi ? "default" : "pointer", opacity: lockRoi ? 0.6 : 1, "&:hover": { textDecoration: lockRoi ? "none" : "underline" } }}>ADF - - )} + + { model.set("_preset_request", "bf"); model.save_changes(); }} sx={{ color: roiColors.textColor, fontSize: 11, fontWeight: "bold", cursor: "pointer", "&:hover": { textDecoration: "underline" } }}>BF + { model.set("_preset_request", "abf"); model.save_changes(); }} sx={{ color: "#4af", fontSize: 11, fontWeight: "bold", cursor: "pointer", "&:hover": { textDecoration: "underline" } }}>ABF + { model.set("_preset_request", "adf"); model.save_changes(); }} sx={{ color: "#fa4", fontSize: 11, fontWeight: "bold", cursor: "pointer", "&:hover": { textDecoration: "underline" } }}>ADF )} {/* Profile sparkline */} - {profileActive && !hideProfile && ( + {profileActive && ( { - if (lockProfile) return; setIsResizingProfile(true); profileResizeStart.current = { startY: e.clientY, startHeight: profileHeight }; }} - sx={{ width: canvasSize, height: 4, cursor: lockProfile ? "default" : "ns-resize", borderTop: `1px solid ${themeColors.border}`, borderLeft: `1px solid ${themeColors.border}`, borderRight: `1px solid ${themeColors.border}`, borderBottom: `1px solid ${themeColors.border}`, bgcolor: themeColors.controlBg, "&:hover": { bgcolor: lockProfile ? themeColors.controlBg : themeColors.accent } }} + sx={{ width: canvasSize, height: 4, cursor: "ns-resize", borderTop: `1px solid ${themeColors.border}`, borderLeft: `1px solid ${themeColors.border}`, borderRight: `1px solid ${themeColors.border}`, borderBottom: `1px solid ${themeColors.border}`, bgcolor: themeColors.controlBg, "&:hover": { bgcolor: themeColors.accent } }} /> )} {/* DP Controls - two rows with histogram on right */} - {showControls && (!hideRoi || !hideDisplay || !hideHistogram) && ( + {showControls && ( {/* Left: two rows of controls */} {/* Row 1: Detector + slider */} - {!hideRoi && ( - - Detector: - - {(roiMode === "circle" || roiMode === "square" || roiMode === "annular") && ( - <> - { - if (lockRoi) return; - if (roiMode === "annular") { - const [inner, outer] = v as number[]; - setRoiRadiusInner(Math.min(inner, outer - 1)); - setRoiRadius(Math.max(outer, inner + 1)); - } else { - const next = Array.isArray(v) ? v[0] : v; - setRoiRadius(next); - } - }} - min={1} - max={Math.min(detRows, detCols) / 2} - size="small" - sx={{ - width: roiMode === "annular" ? 100 : 70, - mx: 1, - "& .MuiSlider-thumb": { width: 14, height: 14 } - }} - /> - - {roiMode === "annular" ? `${Math.round(roiRadiusInner)}-${Math.round(roiRadius)}px` : `${Math.round(roiRadius)}px`} - - - )} - - )} + + Detector: + + {(roiMode === "circle" || roiMode === "square" || roiMode === "annular") && ( + <> + { + if (roiMode === "annular") { + const [inner, outer] = v as number[]; + setRoiRadiusInner(Math.min(inner, outer - 1)); + setRoiRadius(Math.max(outer, inner + 1)); + } else { + const next = Array.isArray(v) ? v[0] : v; + setRoiRadius(next); + } + }} + min={1} + max={Math.min(detRows, detCols) / 2} + size="small" + sx={{ ...sliderStyles.small, width: roiMode === "annular" ? 67 : 47, mx: 1 }} + /> + + {roiMode === "annular" ? `${Math.round(roiRadiusInner)}-${Math.round(roiRadius)}px` : `${Math.round(roiRadius)}px`} + + + )} + {/* Row 2: Color + Scale + Colorbar */} - {!hideDisplay && ( - - Color: - - Scale: - - Colorbar: - { if (!lockDisplay) setShowDpColorbar(e.target.checked); }} disabled={lockDisplay} size="small" sx={switchStyles.small} /> - - )} + + Color: + + Scale: + + Colorbar: + setShowDpColorbar(e.target.checked)} size="small" sx={switchStyles.small} /> + {/* Right: Histogram spanning both rows */} - {!hideHistogram && ( - - { if (!lockHistogram) { setDpVminPct(min); setDpVmaxPct(max); } }} width={110} height={58} theme={themeInfo.theme} dataMin={dpGlobalMin} dataMax={dpGlobalMax} /> - - )} + + { setDpVminPct(min); setDpVmaxPct(max); }} width={110} height={58} theme={themeInfo.theme} dataMin={dpGlobalMin} dataMax={dpGlobalMax} /> + )} {/* SECOND COLUMN: VI Panel */} - {!hideVirtual && ( {/* VI Header */} - + {shapeRows}×{shapeCols} | {detRows}×{detCols} - {!hideFft && ( - <> - FFT: - { if (!lockFft) setShowFft(e.target.checked); }} disabled={lockFft} size="small" sx={switchStyles.small} /> - - )} - {!hideProfile && ( - <> - Profile: - { - if (lockProfile) return; - const on = e.target.checked; - setViProfileActive(on); - if (!on) { - setViProfilePoints([]); - setHoveredViProfileEndpoint(null); - setIsHoveringViProfileLine(false); - } - }} disabled={lockProfile} size="small" sx={switchStyles.small} /> - - )} - {!hideView && ( - - )} - {!hideExport && ( - - )} - {!hideExport && ( - - )} - {!hideExport && ( - setViExportAnchor(null)} anchorOrigin={{ vertical: "bottom", horizontal: "left" }} transformOrigin={{ vertical: "top", horizontal: "left" }} sx={{ zIndex: 9999 }}> - handleViExportFigure(true)} sx={{ fontSize: 12 }}>PDF + colorbar - handleViExportFigure(false)} sx={{ fontSize: 12 }}>PDF - PNG - { if (!lockExport && !lockVirtual) { setViExportAnchor(null); handleExportVI(); } }} sx={{ fontSize: 12 }}>ZIP (all panels + metadata) - - )} + FFT: + setShowFft(e.target.checked)} size="small" sx={switchStyles.small} /> + Profile: + { + const on = e.target.checked; + setViProfileActive(on); + if (!on) { + setViProfilePoints([]); + setHoveredViProfileEndpoint(null); + setIsHoveringViProfileLine(false); + } + }} size="small" sx={switchStyles.small} /> + + + + setViExportAnchor(null)} anchorOrigin={{ vertical: "bottom", horizontal: "left" }} transformOrigin={{ vertical: "top", horizontal: "left" }} sx={{ zIndex: 9999 }}> + handleViExportFigure(true)} sx={{ fontSize: 12 }}>PDF + colorbar + handleViExportFigure(false)} sx={{ fontSize: 12 }}>PDF + PNG + { setViExportAnchor(null); handleExportVI(); }} sx={{ fontSize: 12 }}>ZIP (all panels + metadata) + @@ -3957,19 +3780,17 @@ function Show4DSTEM() { ref={virtualOverlayRef} width={shapeCols} height={shapeRows} onMouseDown={handleViMouseDown} onMouseMove={handleViMouseMove} onMouseUp={handleViMouseUp} onMouseLeave={handleViMouseLeave} - onWheel={createZoomHandler(setViZoom, setViPanX, setViPanY, viZoom, viPanX, viPanY, virtualOverlayRef, lockView || lockVirtual)} + onWheel={createZoomHandler(setViZoom, setViPanX, setViPanY, viZoom, viPanX, viPanY, virtualOverlayRef)} onDoubleClick={handleViDoubleClick} style={{ position: "absolute", width: "100%", height: "100%", - cursor: (viProfileActive && lockProfile) || (!viProfileActive && (lockNavigation || lockRoi)) - ? "default" - : (draggingViProfileEndpoint !== null || isDraggingViProfileLine) - ? "grabbing" - : (viProfileActive && (hoveredViProfileEndpoint !== null || isHoveringViProfileLine)) - ? "grab" - : "crosshair", + cursor: (draggingViProfileEndpoint !== null || isDraggingViProfileLine) + ? "grabbing" + : (viProfileActive && (hoveredViProfileEndpoint !== null || isHoveringViProfileLine)) + ? "grab" + : "crosshair", }} /> @@ -3980,23 +3801,27 @@ function Show4DSTEM() { )} - {!hideView && ( - - )} + - {/* VI Stats Bar */} - {!hideStats && viStats && viStats.length === 4 && ( - + {/* VI Stats Bar — stats on left, Auto/Smooth toggles on right edge */} + {viStats && viStats.length === 4 && ( + Mean {formatStat(viStats[0])} Min {formatStat(viStats[1])} Max {formatStat(viStats[2])} Std {formatStat(viStats[3])} + + Auto: + setViAutoContrast(e.target.checked)} size="small" sx={switchStyles.small} /> + Smooth: + setViSmooth(e.target.checked)} size="small" sx={switchStyles.small} /> + )} {/* VI Profile sparkline */} - {viProfileActive && !hideProfile && ( + {viProfileActive && ( { - if (lockProfile) return; setIsResizingViProfile(true); viProfileResizeStart.current = { startY: e.clientY, startHeight: viProfileHeight }; }} - sx={{ width: viCanvasWidth, height: 4, cursor: lockProfile ? "default" : "ns-resize", borderTop: `1px solid ${themeColors.border}`, borderLeft: `1px solid ${themeColors.border}`, borderRight: `1px solid ${themeColors.border}`, borderBottom: `1px solid ${themeColors.border}`, bgcolor: themeColors.controlBg, "&:hover": { bgcolor: lockProfile ? themeColors.controlBg : themeColors.accent } }} + sx={{ width: viCanvasWidth, height: 4, cursor: "ns-resize", borderTop: `1px solid ${themeColors.border}`, borderLeft: `1px solid ${themeColors.border}`, borderRight: `1px solid ${themeColors.border}`, borderBottom: `1px solid ${themeColors.border}`, bgcolor: themeColors.controlBg, "&:hover": { bgcolor: themeColors.accent } }} /> )} {/* VI Controls - Two rows with histogram on right */} - {showControls && (!hideRoi || !hideDisplay || !hideHistogram) && ( + {showControls && ( {/* Left: Two rows of controls */} {/* Row 1: ROI selector */} - {!hideRoi && ( - - ROI: - - {viRoiMode && viRoiMode !== "off" && ( - <> - {(viRoiMode === "circle" || viRoiMode === "square") && ( - <> - { if (!lockRoi) setViRoiRadius(v as number); }} - min={1} - max={Math.min(shapeRows, shapeCols) / 2} - size="small" - sx={{ width: 80, mx: 1 }} - /> - - {Math.round(viRoiRadius || 5)}px - - - )} - {summedDpCount > 0 && ( - - {summedDpCount} pos + + ROI: + + {viRoiMode && viRoiMode !== "off" && ( + <> + {(viRoiMode === "circle" || viRoiMode === "square") && ( + <> + setViRoiRadius(v as number)} + min={1} + max={Math.min(shapeRows, shapeCols) / 2} + size="small" + sx={{ ...sliderStyles.small, width: 53, mx: 1 }} + /> + + {Math.round(viRoiRadius || 5)}px - )} - - )} - - )} + + )} + + + )} + {/* Row 2: Color + Scale */} - {!hideDisplay && ( - - Color: - - Scale: - - - )} + + Color: + + Scale: + + {/* Right: Histogram spanning both rows */} - {!hideHistogram && ( - - { if (!lockHistogram) { setViVminPct(min); setViVmaxPct(max); } }} width={110} height={58} theme={themeInfo.theme} dataMin={viDataMin} dataMax={viDataMax} /> - - )} + + { setViVminPct(min); setViVmaxPct(max); }} width={110} height={58} theme={themeInfo.theme} dataMin={viDataMin} dataMax={viDataMax} /> + )} - )} {/* THIRD COLUMN: FFT Panel (conditionally shown) */} {effectiveShowFft && ( @@ -4096,9 +3911,7 @@ function Show4DSTEM() { {roiFftActive && fftCropDims ? `ROI FFT (${fftCropDims.cropWidth}\u00D7${fftCropDims.cropHeight})` : "FFT"} - {!hideView && ( - - )} + @@ -4109,18 +3922,16 @@ function Show4DSTEM() { ref={fftOverlayRef} width={shapeCols} height={shapeRows} onMouseDown={handleFftMouseDown} onMouseMove={handleFftMouseMove} onMouseUp={handleFftMouseUp} onMouseLeave={handleFftMouseLeave} - onWheel={createZoomHandler(setFftZoom, setFftPanX, setFftPanY, fftZoom, fftPanX, fftPanY, fftOverlayRef, lockView || lockFft)} + onWheel={createZoomHandler(setFftZoom, setFftPanX, setFftPanY, fftZoom, fftPanX, fftPanY, fftOverlayRef)} onDoubleClick={handleFftDoubleClick} - style={{ position: "absolute", width: "100%", height: "100%", cursor: lockView || lockFft ? "default" : (isDraggingFFT ? "grabbing" : "grab") }} + style={{ position: "absolute", width: "100%", height: "100%", cursor: isDraggingFFT ? "grabbing" : "grab" }} /> - {!hideView && ( - - )} + {/* FFT Stats Bar */} - {!hideStats && fftStats && fftStats.length === 4 && ( - + {fftStats && fftStats.length === 4 && ( + Mean {formatStat(fftStats[0])} Min {formatStat(fftStats[1])} Max {formatStat(fftStats[2])} @@ -4151,50 +3962,46 @@ function Show4DSTEM() { )} {/* FFT Controls - Two rows with histogram on right */} - {showControls && (!hideDisplay || !hideHistogram) && ( + {showControls && ( {/* Left: Two rows of controls */} - {!hideDisplay && ( - - {/* Row 1: Scale + Clip */} - - Scale: - - Auto: - { if (!lockDisplay && !lockFft) setFftAuto(e.target.checked); }} disabled={lockDisplay || lockFft} size="small" sx={switchStyles.small} /> - {fftCropDims && ( - <> - Win: - { if (!lockDisplay && !lockFft) setFftWindow(e.target.checked); }} disabled={lockDisplay || lockFft} size="small" sx={switchStyles.small} /> - - )} - - {/* Row 2: Color */} - - Color: - - - - )} - {/* Right: Histogram spanning both rows */} - {!hideHistogram && ( - - {fftHistogramData && ( - { if (!lockHistogram && !lockFft) { setFftVminPct(min); setFftVmaxPct(max); } }} width={110} height={58} theme={themeInfo.theme} dataMin={fftDataMin} dataMax={fftDataMax} /> + + {/* Row 1: Scale + Clip */} + + Scale: + + Auto: + setFftAuto(e.target.checked)} size="small" sx={switchStyles.small} /> + {fftCropDims && ( + <> + Win: + setFftWindow(e.target.checked)} size="small" sx={switchStyles.small} /> + )} - )} + {/* Row 2: Color */} + + Color: + + + + {/* Right: Histogram spanning both rows */} + + {fftHistogramData && ( + { setFftVminPct(min); setFftVmaxPct(max); }} width={110} height={58} theme={themeInfo.theme} dataMin={fftDataMin} dataMax={fftDataMax} /> + )} + )} @@ -4204,52 +4011,52 @@ function Show4DSTEM() { {/* BOTTOM CONTROLS */} {/* Frame controls (5D time/tilt series) — matches Show3D playback */} - {showControls && nFrames > 1 && !hidePlayback && !hideFrame && (<> + {showControls && nFrames > 1 && (<> {frameDimLabel}: - { if (!lockFrame && !lockPlayback) { setFrameReverse(true); setFramePlaying(true); } }} sx={{ color: frameReverse && framePlaying ? themeColors.accent : themeColors.textMuted, p: 0.25 }}> + { setFrameReverse(true); setFramePlaying(true); }} sx={{ color: frameReverse && framePlaying ? themeColors.accent : themeColors.textMuted, p: 0.25 }}> - { if (!lockFrame && !lockPlayback) setFramePlaying(!framePlaying); }} sx={{ color: themeColors.accent, p: 0.25 }}> + setFramePlaying(!framePlaying)} sx={{ color: themeColors.accent, p: 0.25 }}> {framePlaying ? : } - { if (!lockFrame && !lockPlayback) { setFrameReverse(false); setFramePlaying(true); } }} sx={{ color: !frameReverse && framePlaying ? themeColors.accent : themeColors.textMuted, p: 0.25 }}> + { setFrameReverse(false); setFramePlaying(true); }} sx={{ color: !frameReverse && framePlaying ? themeColors.accent : themeColors.textMuted, p: 0.25 }}> - { if (!lockFrame && !lockPlayback) { setFramePlaying(false); setFrameIdx(0); } }} sx={{ color: themeColors.textMuted, p: 0.25 }}> + { setFramePlaying(false); setFrameIdx(0); }} sx={{ color: themeColors.textMuted, p: 0.25 }}> - { if (!lockFrame && !lockPlayback) { setFramePlaying(false); setFrameIdx(v as number); } }} min={0} max={Math.max(0, nFrames - 1)} size="small" sx={{ flex: 1, minWidth: 60, "& .MuiSlider-thumb": { width: 10, height: 10 } }} /> + { setFramePlaying(false); setFrameIdx(v as number); }} min={0} max={Math.max(0, nFrames - 1)} size="small" sx={{ flex: 1, minWidth: 60, "& .MuiSlider-thumb": { width: 10, height: 10 } }} /> {frameLabels && frameLabels.length > frameIdx ? frameLabels[frameIdx] : `${frameIdx + 1}/${nFrames}`} fps - { if (!lockFrame && !lockPlayback) setFrameFps(v as number); }} size="small" sx={{ ...sliderStyles.small, width: 35, flexShrink: 0 }} /> + setFrameFps(v as number)} size="small" sx={{ ...sliderStyles.small, width: 35, flexShrink: 0 }} /> {Math.round(frameFps)} Loop - { if (!lockFrame && !lockPlayback) setFrameLoop(!frameLoop); }} disabled={lockFrame || lockPlayback} sx={{ ...switchStyles.small, flexShrink: 0 }} /> + setFrameLoop(!frameLoop)} sx={{ ...switchStyles.small, flexShrink: 0 }} /> Bounce - { if (!lockFrame && !lockPlayback) setFrameBoomerang(!frameBoomerang); }} disabled={lockFrame || lockPlayback} sx={{ ...switchStyles.small, flexShrink: 0 }} /> + setFrameBoomerang(!frameBoomerang)} sx={{ ...switchStyles.small, flexShrink: 0 }} /> )} {/* Path animation slider */} - {showControls && !hidePlayback && pathLength > 0 && ( + {showControls && pathLength > 0 && ( - { if (!lockPlayback) setPathPlaying(!pathPlaying); }} sx={{ color: themeColors.accent, p: 0.25 }}> + setPathPlaying(!pathPlaying)} sx={{ color: themeColors.accent, p: 0.25 }}> {pathPlaying ? : } - { if (!lockPlayback) { setPathPlaying(false); setPathIndex(0); } }} sx={{ color: themeColors.textMuted, p: 0.25 }}> + { setPathPlaying(false); setPathIndex(0); }} sx={{ color: themeColors.textMuted, p: 0.25 }}> - { if (!lockPlayback) { setPathPlaying(false); setPathIndex(v as number); } }} min={0} max={Math.max(0, pathLength - 1)} size="small" sx={{ flex: 1, minWidth: 60, "& .MuiSlider-thumb": { width: 10, height: 10 } }} /> + { setPathPlaying(false); setPathIndex(v as number); }} min={0} max={Math.max(0, pathLength - 1)} size="small" sx={{ flex: 1, minWidth: 60, "& .MuiSlider-thumb": { width: 10, height: 10 } }} /> {pathIndex + 1}/{pathLength} Loop: - { if (!lockPlayback) { model.set("path_loop", v); model.save_changes(); } }} disabled={lockPlayback} size="small" sx={switchStyles.small} /> + { model.set("path_loop", v); model.save_changes(); }} size="small" sx={switchStyles.small} /> )} diff --git a/widget/js/show4dstem/styles.css b/widget/js/show4dstem/styles.css deleted file mode 100644 index 61876cde..00000000 --- a/widget/js/show4dstem/styles.css +++ /dev/null @@ -1,5 +0,0 @@ -/* Theme-aware styles - minimal, let JS handle most theming */ -.show4dstem-root { - border-radius: 2px; - padding: 16px; -} diff --git a/widget/js/stats.ts b/widget/js/stats.ts index b71c45aa..36ce661c 100644 --- a/widget/js/stats.ts +++ b/widget/js/stats.ts @@ -99,3 +99,23 @@ export function sliderRange( vmax: dataMin + (vmaxPct / 100) * range, }; } + +/** Compute normalized histogram bins from Float32Array. Returns array of 0-1 values. */ +export function computeHistogramFromBytes(data: Float32Array | null, numBins = 256): number[] { + if (!data || data.length === 0) return new Array(numBins).fill(0); + const bins = new Array(numBins).fill(0); + let min = Infinity, max = -Infinity; + for (let i = 0; i < data.length; i++) { + const v = data[i]; + if (isFinite(v)) { if (v < min) min = v; if (v > max) max = v; } + } + if (!isFinite(min) || !isFinite(max) || min === max) return bins; + const range = max - min; + for (let i = 0; i < data.length; i++) { + const v = data[i]; + if (isFinite(v)) bins[Math.min(numBins - 1, Math.floor(((v - min) / range) * numBins))]++; + } + const maxCount = Math.max(...bins); + if (maxCount > 0) for (let i = 0; i < numBins; i++) bins[i] /= maxCount; + return bins; +} diff --git a/widget/js/tool-parity.ts b/widget/js/tool-parity.ts deleted file mode 100644 index 8e318e6c..00000000 --- a/widget/js/tool-parity.ts +++ /dev/null @@ -1,156 +0,0 @@ -import registryJson from "../src/quantem/widget/tool_parity.json"; - -type ToolInput = string | string[] | null | undefined; - -type WidgetConfig = { - tool_groups: string[]; - aliases?: Record; -}; - -type ControlPreset = { - label: string; - show_groups: string[]; -}; - -type ToolParityRegistry = { - widgets: Record; - control_presets: Record; - viewer_widgets?: string[]; -}; - -const REGISTRY = registryJson as ToolParityRegistry; - -function getWidgetConfig(widgetName: string): WidgetConfig { - const cfg = REGISTRY.widgets[widgetName]; - if (!cfg) { - const supported = Object.keys(REGISTRY.widgets).sort().join(", "); - throw new Error(`Unknown widget '${widgetName}'. Supported widgets: ${supported}.`); - } - return cfg; -} - -function toValues(values: ToolInput): string[] { - if (values == null) return []; - if (typeof values === "string") return [values]; - return [...values]; -} - -function toCanonical(widgetName: string, value: string): string { - const cfg = getWidgetConfig(widgetName); - const aliases = cfg.aliases ?? {}; - const key = value.trim().toLowerCase(); - return aliases[key] ?? key; -} - -export function getWidgetToolGroups(widgetName: string): string[] { - return [...getWidgetConfig(widgetName).tool_groups]; -} - -export function normalizeToolGroups(widgetName: string, values: ToolInput): string[] { - const groups = getWidgetToolGroups(widgetName); - const groupSet = new Set(groups); - const out: string[] = []; - const seen = new Set(); - for (const raw of toValues(values)) { - const canonical = toCanonical(widgetName, String(raw)); - if (!canonical) continue; - if (!groupSet.has(canonical)) { - const supported = groups.map((g) => `"${g}"`).join(", "); - throw new Error(`Unknown tool group '${raw}'. Supported values: ${supported}.`); - } - if (canonical === "all") return ["all"]; - if (!seen.has(canonical)) { - seen.add(canonical); - out.push(canonical); - } - } - return out; -} - -function orderedWithoutAll(widgetName: string, values: Set): string[] { - return getWidgetToolGroups(widgetName).filter((group) => group !== "all" && values.has(group)); -} - -export function expandToolGroups(widgetName: string, values: ToolInput): string[] { - const normalized = normalizeToolGroups(widgetName, values); - if (!normalized.includes("all")) return normalized; - return getWidgetToolGroups(widgetName).filter((group) => group !== "all"); -} - -export function compactToolLabel(key: string): string { - return key - .replace(/_/g, " ") - .replace(/\b\w/g, (m) => m.toUpperCase()); -} - -export function getControlPresetIds(): string[] { - return Object.keys(REGISTRY.control_presets); -} - -export function getControlPresetLabel(presetId: string): string { - const preset = REGISTRY.control_presets[presetId]; - return preset?.label ?? presetId; -} - -export function resolvePresetHiddenTools(widgetName: string, presetId: string): string[] { - const preset = REGISTRY.control_presets[presetId]; - if (!preset) { - const supported = Object.keys(REGISTRY.control_presets).sort().join(", "); - throw new Error(`Unknown control preset '${presetId}'. Supported presets: ${supported}.`); - } - const supportedGroups = getWidgetToolGroups(widgetName).filter((group) => group !== "all"); - if (preset.show_groups.includes("*")) return []; - const show = new Set(preset.show_groups.map((g) => toCanonical(widgetName, g))); - const hidden = supportedGroups.filter((group) => !show.has(group)); - return normalizeToolGroups(widgetName, hidden); -} - -export type ToolVisibilityState = { - hideAll: boolean; - lockAll: boolean; - isHidden: (group: string) => boolean; - isLocked: (group: string) => boolean; - hiddenSet: Set; - disabledSet: Set; -}; - -export function computeToolVisibility( - widgetName: string, - disabledTools: ToolInput, - hiddenTools: ToolInput, -): ToolVisibilityState { - const hidden = normalizeToolGroups(widgetName, hiddenTools); - const disabled = normalizeToolGroups(widgetName, disabledTools); - const hiddenSet = new Set(hidden); - const disabledSet = new Set(disabled); - const hideAll = hiddenSet.has("all"); - const lockAll = hideAll || disabledSet.has("all"); - - const isHidden = (group: string): boolean => { - const canonical = toCanonical(widgetName, group); - if (canonical === "all") return hideAll; - return hideAll || hiddenSet.has(canonical); - }; - - const isLocked = (group: string): boolean => { - const canonical = toCanonical(widgetName, group); - if (canonical === "all") return lockAll; - return lockAll || isHidden(canonical) || disabledSet.has(canonical); - }; - - return { hideAll, lockAll, isHidden, isLocked, hiddenSet, disabledSet }; -} - -export function addToolGroup(widgetName: string, current: ToolInput, group: string): string[] { - const merged = new Set(expandToolGroups(widgetName, current)); - const canonical = toCanonical(widgetName, group); - if (canonical === "all") return ["all"]; - merged.add(canonical); - return orderedWithoutAll(widgetName, merged); -} - -export function removeToolGroup(widgetName: string, current: ToolInput, group: string): string[] { - const merged = new Set(expandToolGroups(widgetName, current)); - merged.delete(toCanonical(widgetName, group)); - return orderedWithoutAll(widgetName, merged); -} diff --git a/widget/package-lock.json b/widget/package-lock.json index cf4e386a..ff1510fd 100644 --- a/widget/package-lock.json +++ b/widget/package-lock.json @@ -16,13 +16,11 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@anywidget/vite": "^0.2.0", "@types/react": "^19.1.3", "@types/react-dom": "^19.1.4", - "@vitejs/plugin-react": "^4.3.0", "@webgpu/types": "^0.1.68", - "typescript": "^5.8.3", - "vite": "^5.2.0" + "esbuild": "^0.21.3", + "typescript": "^5.8.3" } }, "node_modules/@anywidget/react": { @@ -46,23 +44,13 @@ "integrity": "sha512-Qno/7V0lKHCMq3DJuSKKHMwilFKPSe8wFftL5xWmgaMCc938mNTtv+i19UrvDfpj9cQTnlPqyXy8t3JOgQ8laA==", "license": "MIT" }, - "node_modules/@anywidget/vite": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@anywidget/vite/-/vite-0.2.2.tgz", - "integrity": "sha512-6qYispivu+VAvWJbWSsZujGAQXLW5Xve/cNA/zrz+RXeg31xfJ5bH5ylqp6UC+GpFZL8azRN6HQS24Z0IgURLw==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "vite": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" - } - }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -70,55 +58,14 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/compat-data": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", - "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", - "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, "node_modules/@babel/generator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", - "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -127,23 +74,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-globals": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", @@ -154,42 +84,14 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", - "dev": true, + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" - }, - "engines": { - "node": ">=6.9.0" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", - "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -212,37 +114,13 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.28.5" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -251,38 +129,6 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/runtime": { "version": "7.29.2", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", @@ -293,31 +139,31 @@ } }, "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", - "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", "debug": "^4.3.1" }, "engines": { @@ -325,9 +171,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -356,12 +202,6 @@ "stylis": "4.2.0" } }, - "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "license": "MIT" - }, "node_modules/@emotion/cache": { "version": "11.14.0", "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", @@ -890,17 +730,6 @@ "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -1169,597 +998,101 @@ "url": "https://opencollective.com/popperjs" } }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", - "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", - "dev": true, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", "license": "MIT" }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", - "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "license": "MIT" }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz", - "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "dependencies": { + "csstype": "^3.2.2" + } }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz", - "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "peerDependencies": { + "@types/react": "^19.2.0" + } }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz", - "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "peerDependencies": { + "@types/react": "*" + } }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz", - "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", - "cpu": [ - "arm64" - ], + "node_modules/@webgpu/types": { + "version": "0.1.69", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.69.tgz", + "integrity": "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "license": "BSD-3-Clause" }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz", - "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz", - "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==", - "cpu": [ - "arm" - ], - "dev": true, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": ">=6" + } }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz", - "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==", - "cpu": [ - "arm" - ], - "dev": true, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": ">=6" + } }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", - "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz", - "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz", - "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz", - "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz", - "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz", - "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz", - "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz", - "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz", - "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz", - "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz", - "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz", - "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz", - "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz", - "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz", - "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz", - "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz", - "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/parse-json": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", - "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", - "license": "MIT" - }, - "node_modules/@types/prop-types": { - "version": "15.7.15", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", - "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "19.2.8", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.8.tgz", - "integrity": "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==", - "license": "MIT", - "dependencies": { - "csstype": "^3.2.2" - } - }, - "node_modules/@types/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "license": "MIT", - "peerDependencies": { - "@types/react": "^19.2.0" - } - }, - "node_modules/@types/react-transition-group": { - "version": "4.4.12", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", - "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*" - } - }, - "node_modules/@vitejs/plugin-react": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", - "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.28.0", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.27", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.17.0" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" - } - }, - "node_modules/@webgpu/types": { - "version": "0.1.69", - "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.69.tgz", - "integrity": "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/babel-plugin-macros": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", - "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.5", - "cosmiconfig": "^7.0.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">=10", - "npm": ">=6" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.9.14", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz", - "integrity": "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001764", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz", - "integrity": "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "license": "MIT" - }, - "node_modules/cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", "license": "MIT", "dependencies": { "@types/parse-json": "^4.0.0", @@ -1805,13 +1138,6 @@ "csstype": "^3.0.2" } }, - "node_modules/electron-to-chromium": { - "version": "1.5.267", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", - "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", - "dev": true, - "license": "ISC" - }, "node_modules/error-ex": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", @@ -1869,16 +1195,6 @@ "@esbuild/win32-x64": "0.21.5" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -1897,21 +1213,6 @@ "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", "license": "MIT" }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -1921,16 +1222,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/hasown": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", @@ -2037,19 +1328,6 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "license": "MIT" }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/jszip": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", @@ -2089,48 +1367,12 @@ "loose-envify": "cli.js" } }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", - "dev": true, - "license": "MIT" - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -2197,35 +1439,6 @@ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -2276,16 +1489,6 @@ "integrity": "sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ==", "license": "MIT" }, - "node_modules/react-refresh": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", - "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -2347,51 +1550,6 @@ "node": ">=4" } }, - "node_modules/rollup": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", - "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.55.1", - "@rollup/rollup-android-arm64": "4.55.1", - "@rollup/rollup-darwin-arm64": "4.55.1", - "@rollup/rollup-darwin-x64": "4.55.1", - "@rollup/rollup-freebsd-arm64": "4.55.1", - "@rollup/rollup-freebsd-x64": "4.55.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", - "@rollup/rollup-linux-arm-musleabihf": "4.55.1", - "@rollup/rollup-linux-arm64-gnu": "4.55.1", - "@rollup/rollup-linux-arm64-musl": "4.55.1", - "@rollup/rollup-linux-loong64-gnu": "4.55.1", - "@rollup/rollup-linux-loong64-musl": "4.55.1", - "@rollup/rollup-linux-ppc64-gnu": "4.55.1", - "@rollup/rollup-linux-ppc64-musl": "4.55.1", - "@rollup/rollup-linux-riscv64-gnu": "4.55.1", - "@rollup/rollup-linux-riscv64-musl": "4.55.1", - "@rollup/rollup-linux-s390x-gnu": "4.55.1", - "@rollup/rollup-linux-x64-gnu": "4.55.1", - "@rollup/rollup-linux-x64-musl": "4.55.1", - "@rollup/rollup-openbsd-x64": "4.55.1", - "@rollup/rollup-openharmony-arm64": "4.55.1", - "@rollup/rollup-win32-arm64-msvc": "4.55.1", - "@rollup/rollup-win32-ia32-msvc": "4.55.1", - "@rollup/rollup-win32-x64-gnu": "4.55.1", - "@rollup/rollup-win32-x64-msvc": "4.55.1", - "fsevents": "~2.3.2" - } - }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -2404,16 +1562,6 @@ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -2429,16 +1577,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -2480,110 +1618,12 @@ "node": ">=14.17" } }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, - "node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, "node_modules/yaml": { "version": "1.10.3", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", diff --git a/widget/package.json b/widget/package.json index 3d0175ae..8ceb653b 100644 --- a/widget/package.json +++ b/widget/package.json @@ -2,8 +2,8 @@ "name": "quantem-widget-frontend", "type": "module", "scripts": { - "dev": "vite build --watch", - "build": "vite build", + "dev": "npm run build -- --watch", + "build": "node scripts/build.mjs", "typecheck": "tsc --noEmit" }, "dependencies": { @@ -17,12 +17,10 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@anywidget/vite": "^0.2.0", "@types/react": "^19.1.3", "@types/react-dom": "^19.1.4", - "@vitejs/plugin-react": "^4.3.0", "@webgpu/types": "^0.1.68", - "typescript": "^5.8.3", - "vite": "^5.2.0" + "esbuild": "^0.21.3", + "typescript": "^5.8.3" } } diff --git a/widget/src/quantem/widget/__init__.py b/widget/src/quantem/widget/__init__.py index dc98e36a..96d8aebc 100644 --- a/widget/src/quantem/widget/__init__.py +++ b/widget/src/quantem/widget/__init__.py @@ -1,7 +1,12 @@ -from importlib.metadata import version +from importlib.metadata import PackageNotFoundError, version from quantem.widget.show2d import Show2D from quantem.widget.show4dstem import Show4DSTEM -__version__ = version("quantem.widget") +try: + __version__ = version("quantem.widget") +except PackageNotFoundError: + # Source-tree imports (e.g. `PYTHONPATH=src pytest`) skip pip install. + __version__ = "0.0.0+local" + __all__ = ["Show2D", "Show4DSTEM"] diff --git a/widget/src/quantem/widget/array_utils.py b/widget/src/quantem/widget/array_utils.py index a38190e6..1913a846 100644 --- a/widget/src/quantem/widget/array_utils.py +++ b/widget/src/quantem/widget/array_utils.py @@ -1,68 +1,21 @@ -""" -Array utilities for widgets. Supports NumPy + PyTorch input. -""" - -from typing import Literal +"""Array utilities for widgets. NumPy + PyTorch input.""" import numpy as np - -try: - import torch - _HAS_TORCH = True -except ImportError: - _HAS_TORCH = False - - -ArrayBackend = Literal["numpy", "torch", "unknown"] - - -def get_array_backend(data) -> ArrayBackend: - """Detect array backend. Returns 'numpy', 'torch', or 'unknown'.""" - if _HAS_TORCH and isinstance(data, torch.Tensor): - return "torch" - if isinstance(data, np.ndarray): - return "numpy" - return "unknown" +import torch def to_numpy(data, dtype: np.dtype | None = None) -> np.ndarray: - """Convert NumPy or PyTorch array to NumPy. - - Parameters - ---------- - data : np.ndarray or torch.Tensor - Input array. - dtype : np.dtype, optional - Target dtype. - - Returns - ------- - np.ndarray - - Examples - -------- - >>> import numpy as np - >>> to_numpy(np.zeros((4, 4))) - >>> import torch - >>> to_numpy(torch.zeros(4, 4)) - - Raises - ------ - TypeError - If `data` is not a NumPy array or PyTorch tensor. - """ - backend = get_array_backend(data) - if backend == "torch": + """Convert NumPy / PyTorch / Dataset to NumPy.""" + if isinstance(data, torch.Tensor): result = data.detach().cpu().numpy() - elif backend == "numpy": + elif isinstance(data, np.ndarray): result = data else: - # Try np.asarray as last-resort fallback for things like Dataset arrays + # Last-resort fallback covers Dataset.__array__, dlpack-compatible objects, etc. try: result = np.asarray(data) except Exception as e: raise TypeError( - f"to_numpy expected a NumPy array or PyTorch tensor, got {type(data).__name__}. " - f"Convert your input via np.asarray(...) or tensor.cpu().numpy() first." + f"to_numpy expected a NumPy array or PyTorch tensor, got {type(data).__name__}." ) from e if dtype is not None: result = np.asarray(result, dtype=dtype) @@ -70,10 +23,7 @@ def to_numpy(data, dtype: np.dtype | None = None) -> np.ndarray: def _resize_image(img: np.ndarray, target_h: int, target_w: int) -> np.ndarray: - """Center-pad an image to (target_h, target_w) with zeros. - - Used to align gallery images of different shapes to a common canvas. - """ + """Center-pad image to (target_h, target_w) with zeros. For gallery alignment.""" h, w = img.shape[-2:] if h == target_h and w == target_w: return img @@ -84,28 +34,14 @@ def _resize_image(img: np.ndarray, target_h: int, target_w: int) -> np.ndarray: return np.pad(img, ((pad_top, pad_bot), (pad_left, pad_right)), mode="constant", constant_values=0) -def apply_shift(img: np.ndarray, dy: float, dx: float) -> np.ndarray: - """Sub-pixel image shift via bilinear interpolation. Used for diff alignment.""" - if not _HAS_TORCH: - # Fallback: integer roll only - return np.roll(img, (int(round(dy)), int(round(dx))), axis=(-2, -1)) - t = torch.from_numpy(img).float() - if t.ndim == 2: - t = t.unsqueeze(0).unsqueeze(0) - h, w = t.shape[-2:] - y = torch.arange(h, dtype=torch.float32) - dy - x = torch.arange(w, dtype=torch.float32) - dx - yy, xx = torch.meshgrid(y, x, indexing="ij") - grid = torch.stack(((xx / (w - 1)) * 2 - 1, (yy / (h - 1)) * 2 - 1), dim=-1).unsqueeze(0) - out = torch.nn.functional.grid_sample(t, grid, mode="bilinear", padding_mode="border", align_corners=True) - return out.squeeze().numpy() - - -def bin2d(img: np.ndarray, factor: int) -> np.ndarray: - """Reduce 2D image by integer binning factor. Mean of f×f blocks.""" +def bin2d(img: np.ndarray, factor: int, mode: str = "mean") -> np.ndarray: + """Reduce 2D image by integer binning factor. mean or sum of f×f blocks.""" if factor <= 1: return img h, w = img.shape[-2:] h2, w2 = h - h % factor, w - w % factor img = img[..., :h2, :w2] - return img.reshape(*img.shape[:-2], h2 // factor, factor, w2 // factor, factor).mean(axis=(-3, -1)) + blocks = img.reshape(*img.shape[:-2], h2 // factor, factor, w2 // factor, factor) + if mode == "sum": + return blocks.sum(axis=(-3, -1)) + return blocks.mean(axis=(-3, -1)) diff --git a/widget/src/quantem/widget/show2d.py b/widget/src/quantem/widget/show2d.py index cd2dff99..f2cbed6a 100644 --- a/widget/src/quantem/widget/show2d.py +++ b/widget/src/quantem/widget/show2d.py @@ -24,12 +24,7 @@ from quantem.core.datastructures import Dataset2d, Dataset3d from quantem.widget.array_utils import to_numpy, _resize_image -from quantem.widget.json_state import resolve_widget_version, save_state_file, unwrap_state_payload -from quantem.widget.tool_parity import ( - bind_tool_runtime_api, - build_tool_groups, - normalize_tool_groups, -) +from quantem.widget.state import resolve_widget_version, save_state_file, unwrap_state_payload @@ -94,8 +89,12 @@ class Show2D(anywidget.AnyWidget): Title to display above the image(s). cmap : str, default "inferno" Colormap name ("magma", "viridis", "gray", "inferno", "plasma"). - pixel_size : float, optional - Pixel size in angstroms for scale bar display. + sampling : float or tuple of float, optional + Pixel size per axis ``(row, col)``. Scalar broadcasts to both axes. + Used for scale bar display. Defaults to ``(1, 1)``. + units : str or list of str, optional + Unit string per axis. Scalar broadcasts to both. Common: ``"A"``, + ``"nm"``, ``"pixels"``. Defaults to ``["pixels", "pixels"]``. show_fft : bool, default False Show FFT and histogram panels. show_stats : bool, default True @@ -106,7 +105,7 @@ class Show2D(anywidget.AnyWidget): Use percentile-based contrast. vmin : float, optional Absolute minimum intensity for color mapping. When both vmin and vmax - are set, all gallery images share the same intensity scale — essential + are set, all gallery images share the same intensity scale: essential for A/B visual comparison. vmax : float, optional Absolute maximum intensity for color mapping. @@ -117,24 +116,8 @@ class Show2D(anywidget.AnyWidget): ``0`` uses the frontend default: 500 px for a single image, 300 px per image in gallery mode. Pass e.g. ``size=800`` to enlarge for a presentation, or ``size=200`` to compress alongside a control panel. - This controls **display only** — the underlying image resolution is + This controls **display only**: the underlying image resolution is never resampled; zooming into a 4K image preserves every pixel. - disabled_tools : list of str, optional - Tool groups to lock while still showing controls. Supported: - ``"display"``, ``"histogram"``, ``"stats"``, ``"navigation"``, - ``"view"``, ``"export"``, ``"roi"``, ``"profile"``, ``"all"``. - disable_* : bool, optional - Convenience flags (``disable_display``, ``disable_histogram``, - ``disable_stats``, ``disable_navigation``, ``disable_view``, - ``disable_export``, ``disable_roi``, ``disable_profile``, - ``disable_all``) equivalent to adding those keys to - ``disabled_tools``. - hidden_tools : list of str, optional - Tool groups to hide from the UI. Uses the same keys as - ``disabled_tools``. - hide_* : bool, optional - Convenience flags mirroring ``disable_*`` for ``hidden_tools``. - Attributes ---------- render_total_ms : int or None @@ -153,17 +136,61 @@ class Show2D(anywidget.AnyWidget): -------- >>> import numpy as np >>> from quantem.widget import Show2D - >>> - >>> # Single image with FFT - >>> Show2D(image, title="HRTEM Image", show_fft=True, pixel_size=1.0) - >>> - >>> # Gallery of multiple images - >>> labels = ["Raw", "Filtered", "FFT"] - >>> Show2D([img1, img2, img3], labels=labels, ncols=3) + + Single 2D NumPy array: + + >>> Show2D(np.random.rand(512, 512)) + + PyTorch tensor (CPU or GPU, any dtype): + + >>> import torch + >>> Show2D(torch.rand(512, 512)) + + 3D NumPy stack ``(N, H, W)`` rendered as a gallery: + + >>> Show2D(np.random.rand(6, 256, 256), ncols=3) + + List of arrays with different shapes (center-padded to a common canvas): + + >>> Show2D([np.random.rand(256, 256), np.random.rand(300, 400)]) + + quantem ``Dataset2d``: title, sampling, units auto-extracted: + + >>> from quantem.core.datastructures import Dataset2d + >>> ds = Dataset2d.from_array(np.random.rand(512, 512)) + >>> Show2D(ds) + + quantem ``Dataset3d``: gallery view of N frames with calibration: + + >>> from quantem.core.datastructures import Dataset3d + >>> ds = Dataset3d.from_array(np.random.rand(6, 256, 256)) + >>> Show2D(ds, ncols=3) + + A/B comparison with shared contrast and linked zoom/pan: + + >>> a, b = np.random.rand(512, 512), np.random.rand(512, 512) + >>> Show2D([a, b], vmin=0, vmax=1, link_zoom=True, link_pan=True) + + Per-image absolute contrast (one ``vmin``/``vmax`` per image): + + >>> Show2D([a, b], vmin=[0.0, 0.2], vmax=[1.0, 0.8]) + + Drift comparison: diff mode adds a ``A - B`` panel alongside the originals + (gallery becomes ``[A, B, A - B]``): + + >>> Show2D([a, b], diff_mode=True, link_zoom=True, link_pan=True) + + Large image: display-only canvas size (full resolution preserved): + + >>> Show2D(np.random.rand(4096, 4096), size=800) + + Static export to PDF or PNG (vector PDF for publication figures): + + >>> w = Show2D(np.random.rand(512, 512), sampling=0.5, units="nm") + >>> w.save_image("figure.pdf", dpi=150) """ _esm = pathlib.Path(__file__).parent / "static" / "show2d.js" - _css = pathlib.Path(__file__).parent / "static" / "show2d.css" # ========================================================================= # Core State @@ -201,6 +228,7 @@ class Show2D(anywidget.AnyWidget): # Scale Bar # ========================================================================= pixel_size = traitlets.Float(0.0).tag(sync=True) + pixel_unit = traitlets.Unicode("pixels").tag(sync=True) scale_bar_visible = traitlets.Bool(True).tag(sync=True) size = traitlets.Int(0).tag(sync=True) # Canvas rendering size in CSS pixels; 0 = frontend default smooth = traitlets.Bool(False).tag(sync=True) @@ -218,8 +246,6 @@ class Show2D(anywidget.AnyWidget): # ========================================================================= show_controls = traitlets.Bool(True).tag(sync=True) show_stats = traitlets.Bool(True).tag(sync=True) - disabled_tools = traitlets.List(traitlets.Unicode()).tag(sync=True) - hidden_tools = traitlets.List(traitlets.Unicode()).tag(sync=True) stats_mean = traitlets.List(traitlets.Float()).tag(sync=True) stats_min = traitlets.List(traitlets.Float()).tag(sync=True) stats_max = traitlets.List(traitlets.Float()).tag(sync=True) @@ -253,114 +279,24 @@ class Show2D(anywidget.AnyWidget): # ========================================================================= image_rotations = traitlets.List(traitlets.Int(), []).tag(sync=True) - @classmethod - def _normalize_tool_groups(cls, tool_groups) -> list[str]: - return normalize_tool_groups("Show2D", tool_groups) - - @classmethod - def _build_disabled_tools( - cls, - disabled_tools=None, - disable_display: bool = False, - disable_histogram: bool = False, - disable_stats: bool = False, - disable_navigation: bool = False, - disable_view: bool = False, - disable_export: bool = False, - disable_roi: bool = False, - disable_profile: bool = False, - disable_all: bool = False, - ) -> list[str]: - return build_tool_groups( - "Show2D", - tool_groups=disabled_tools, - all_flag=disable_all, - flag_map={ - "display": disable_display, - "histogram": disable_histogram, - "stats": disable_stats, - "navigation": disable_navigation, - "view": disable_view, - "export": disable_export, - "roi": disable_roi, - "profile": disable_profile, - }, - ) - - @classmethod - def _build_hidden_tools( - cls, - hidden_tools=None, - hide_display: bool = False, - hide_histogram: bool = False, - hide_stats: bool = False, - hide_navigation: bool = False, - hide_view: bool = False, - hide_export: bool = False, - hide_roi: bool = False, - hide_profile: bool = False, - hide_all: bool = False, - ) -> list[str]: - return build_tool_groups( - "Show2D", - tool_groups=hidden_tools, - all_flag=hide_all, - flag_map={ - "display": hide_display, - "histogram": hide_histogram, - "stats": hide_stats, - "navigation": hide_navigation, - "view": hide_view, - "export": hide_export, - "roi": hide_roi, - "profile": hide_profile, - }, - ) - - @traitlets.validate("disabled_tools") - def _validate_disabled_tools(self, proposal): - return self._normalize_tool_groups(proposal["value"]) - - @traitlets.validate("hidden_tools") - def _validate_hidden_tools(self, proposal): - return self._normalize_tool_groups(proposal["value"]) - def __init__( self, data: np.ndarray | list[np.ndarray], labels: list[str | None] = None, title: str = "", cmap: str | Colormap = Colormap.INFERNO, - pixel_size: float = 0.0, + sampling: float | tuple[float, float] | list[float] | None = None, + units: str | list[str] | None = None, scale_bar_visible: bool = True, show_fft: bool = False, fft_window: bool = True, show_controls: bool = True, show_stats: bool = True, + verbose: bool = True, log_scale: bool = False, auto_contrast: bool = False, vmin: float | list | None = None, vmax: float | list | None = None, - disabled_tools: list[str | None] = None, - disable_display: bool = False, - disable_histogram: bool = False, - disable_stats: bool = False, - disable_navigation: bool = False, - disable_view: bool = False, - disable_export: bool = False, - disable_roi: bool = False, - disable_profile: bool = False, - disable_all: bool = False, - hidden_tools: list[str | None] = None, - hide_display: bool = False, - hide_histogram: bool = False, - hide_stats: bool = False, - hide_navigation: bool = False, - hide_view: bool = False, - hide_export: bool = False, - hide_roi: bool = False, - hide_profile: bool = False, - hide_all: bool = False, ncols: int = 3, size: int = 0, smooth: bool = False, @@ -390,70 +326,41 @@ def __init__( with self.hold_sync(): self._init_sync( data=data, labels=labels, title=title, cmap=cmap, - pixel_size=pixel_size, scale_bar_visible=scale_bar_visible, + sampling=sampling, units=units, scale_bar_visible=scale_bar_visible, show_fft=show_fft, fft_window=fft_window, show_controls=show_controls, show_stats=show_stats, log_scale=log_scale, auto_contrast=auto_contrast, vmin=vmin, vmax=vmax, - disabled_tools=disabled_tools, - disable_display=disable_display, - disable_histogram=disable_histogram, - disable_stats=disable_stats, - disable_navigation=disable_navigation, - disable_view=disable_view, - disable_export=disable_export, - disable_roi=disable_roi, - disable_profile=disable_profile, - disable_all=disable_all, - hidden_tools=hidden_tools, - hide_display=hide_display, - hide_histogram=hide_histogram, - hide_stats=hide_stats, - hide_navigation=hide_navigation, - hide_view=hide_view, - hide_export=hide_export, - hide_roi=hide_roi, - hide_profile=hide_profile, - hide_all=hide_all, ncols=ncols, size=size, smooth=smooth, zoom=zoom, zoom_row=zoom_row, zoom_col=zoom_col, link_zoom=link_zoom, link_pan=link_pan, link_contrast=link_contrast, diff_mode=diff_mode, view_box=view_box, - display_bin=display_bin, state=state, _t0=_t0) + display_bin=display_bin, verbose=verbose, state=state, _t0=_t0) - def _init_sync(self, *, data, labels, title, cmap, pixel_size, + def _init_sync(self, *, data, labels, title, cmap, sampling, units, scale_bar_visible, show_fft, fft_window, show_controls, show_stats, log_scale, auto_contrast, - vmin, vmax, disabled_tools, - disable_display, disable_histogram, disable_stats, - disable_navigation, disable_view, disable_export, - disable_roi, disable_profile, disable_all, - hidden_tools, hide_display, hide_histogram, hide_stats, - hide_navigation, hide_view, hide_export, hide_roi, - hide_profile, hide_all, + vmin, vmax, ncols, size, smooth, zoom, zoom_row, zoom_col, link_zoom, link_pan, link_contrast, diff_mode, view_box, - display_bin, state, _t0): + display_bin, verbose, state, _t0): import time as _time + self._verbose = verbose self.widget_version = resolve_widget_version() self._display_data = None # initialized after data setup self._display_bin = 1 # First-class support for quantem Dataset2d / Dataset3d: - # extract array + auto-populate title, pixel_size from sampling+units. - # (Duck-typing fallback below covers any other object exposing the same API.) + # auto-extract array + sampling + units from the dataset object. if isinstance(data, (Dataset2d, Dataset3d)) or ( hasattr(data, "array") and hasattr(data, "name") and hasattr(data, "sampling") ): if not title and data.name: title = data.name - if pixel_size == 0.0 and hasattr(data, "units"): - units = list(data.units) - sampling_val = float(data.sampling[-1]) - if units[-1] in ("nm",): - pixel_size = sampling_val * 10 # nm → Å - elif units[-1] in ("Å", "angstrom", "A"): - pixel_size = sampling_val + if sampling is None: + sampling = tuple(float(s) for s in data.sampling[-2:]) + if units is None and hasattr(data, "units"): + units = list(data.units[-2:]) data = data.array # Convert NumPy / PyTorch / list inputs to a NumPy array. @@ -481,7 +388,7 @@ def _init_sync(self, *, data, labels, title, cmap, pixel_size, self._data = np.array(data, dtype=np.float32, copy=True) else: self._data = np.asarray(data, dtype=np.float32) - # Store originals for rotation reset — views into _data (no copy). + # Store originals for rotation reset: views into _data (no copy). # Only materialized as independent copies when a rotation is applied. self._data_original = [self._data[i] for i in range(self._data.shape[0])] self._originals_are_views = True @@ -499,7 +406,20 @@ def _init_sync(self, *, data, labels, title, cmap, pixel_size, # Options self.title = title self.cmap = cmap - self.pixel_size = pixel_size + # Resolve sampling + units to scalar pixel_size + pixel_unit (column axis). + # Scalar shorthand: sampling=0.5 → (0.5, 0.5). units="nm" → ["nm", "nm"]. + if sampling is None: + self.pixel_size = 0.0 + elif isinstance(sampling, (int, float)): + self.pixel_size = float(sampling) + else: + self.pixel_size = float(sampling[-1]) + if units is None: + self.pixel_unit = "pixels" + elif isinstance(units, str): + self.pixel_unit = units + else: + self.pixel_unit = str(units[-1]) self.scale_bar_visible = scale_bar_visible self.size = size self.smooth = smooth @@ -547,30 +467,6 @@ def _expand(v): else: self.vmin = vmin self.vmax = vmax - self.disabled_tools = self._build_disabled_tools( - disabled_tools=disabled_tools, - disable_display=disable_display, - disable_histogram=disable_histogram, - disable_stats=disable_stats, - disable_navigation=disable_navigation, - disable_view=disable_view, - disable_export=disable_export, - disable_roi=disable_roi, - disable_profile=disable_profile, - disable_all=disable_all, - ) - self.hidden_tools = self._build_hidden_tools( - hidden_tools=hidden_tools, - hide_display=hide_display, - hide_histogram=hide_histogram, - hide_stats=hide_stats, - hide_navigation=hide_navigation, - hide_view=hide_view, - hide_export=hide_export, - hide_roi=hide_roi, - hide_profile=hide_profile, - hide_all=hide_all, - ) self.ncols = ncols # Auto-bin for display: keep full-res in _data, send binned to JS. @@ -601,10 +497,11 @@ def _expand(v): self._display_data = bin2d(self._data, factor=self._display_bin, mode="mean") self.height = int(self._display_data.shape[1]) self.width = int(self._display_data.shape[2]) - if pixel_size > 0: - self.pixel_size = pixel_size * self._display_bin + if self.pixel_size > 0: + self.pixel_size = self.pixel_size * self._display_bin self._display_bin_factor = self._display_bin - print(f" Display bin {self._display_bin}×: {orig_h}×{orig_w} → {self.height}×{self.width} ({self._display_data.nbytes // 1024 // 1024} MB)") + if verbose: + print(f" Display bin {self._display_bin}×: {orig_h}×{orig_w} → {self.height}×{self.width} ({self._display_data.nbytes // 1024 // 1024} MB)") else: self._display_data = self._data self._display_bin_factor = 1 @@ -629,7 +526,7 @@ def _expand(v): # Stash wall-clock start on the instance; the observer below prints the # TRUE end-to-end time after JS signals first paint. The Python-only - # __init__ number is misleading for widget UX — a widget is not "done" + # __init__ number is misleading for widget UX: a widget is not "done" # until the browser has painted its first frame. self._init_t0 = _t0 self._init_py_elapsed_ms = (_time.perf_counter() - _t0) * 1000 @@ -646,13 +543,15 @@ def _on_first_render(self, change): mem = self._data.nbytes mem_str = f"{mem / (1 << 20):.0f} MB" if mem >= 1 << 20 else f"{mem / (1 << 10):.0f} KB" # Expose as attributes so tests and notebooks can assert on them. - # These are the ground truth for "did JS actually paint" — if they're + # These are the ground truth for "did JS actually paint": if they're # None, the JS side never signaled first render. self.render_total_ms = int(total_ms) self.render_python_build_ms = int(py_ms) self.render_wire_js_ms = int(total_ms - py_ms) + if not getattr(self, "_verbose", True): + return print( - f"Show2D: {shape} {mem_str} — " + f"Show2D: {shape} {mem_str}: " f"rendered in {total_ms:.0f} ms (Python build {py_ms:.0f} ms, " f"wire+JS {total_ms - py_ms:.0f} ms)", flush=True, @@ -660,7 +559,7 @@ def _on_first_render(self, change): # Detach observer: one-shot, we only care about the first paint. try: self.unobserve(self._on_first_render, names=["_js_rendered"]) - except Exception: + except (ValueError, KeyError): pass def set_image(self, data, labels=None): @@ -706,7 +605,8 @@ def set_image(self, data, labels=None): self.height = int(self._display_data.shape[1]) self.width = int(self._display_data.shape[2]) self._display_bin_factor = self._display_bin - print(f" Display bin {self._display_bin}×: {data.shape[1]}×{data.shape[2]} → {self.height}×{self.width}") + if getattr(self, "_verbose", True): + print(f" Display bin {self._display_bin}×: {data.shape[1]}×{data.shape[2]} → {self.height}×{self.width}") else: self._display_data = self._data self.height = int(data.shape[1]) @@ -900,7 +800,6 @@ def save_image( cb.set_ticklabels(tick_labels) if scalebar and self.pixel_size > 0: - from matplotlib.patches import FancyBboxPatch # Compute a nice scale bar length target_frac = 0.2 # ~20% of image width raw_length_px = target_frac * w @@ -942,9 +841,8 @@ def state_dict(self): "show_fft": self.show_fft, "fft_window": self.fft_window, "show_controls": self.show_controls, - "disabled_tools": self.disabled_tools, - "hidden_tools": self.hidden_tools, "pixel_size": self.pixel_size, + "pixel_unit": self.pixel_unit, "scale_bar_visible": self.scale_bar_visible, "size": self.size, "smooth": self.smooth, @@ -1012,10 +910,6 @@ def summary(self): if not self.fft_window: display += " (no window)" lines.append(f"Display: {display}") - if self.disabled_tools: - lines.append(f"Locked: {', '.join(self.disabled_tools)}") - if self.hidden_tools: - lines.append(f"Hidden: {', '.join(self.hidden_tools)}") if self.roi_active and self.roi_list: lines.append(f"ROI: {len(self.roi_list)} region(s)") if self.profile_line: @@ -1310,5 +1204,3 @@ def profile_distance(self): return dist_px * self.pixel_size return dist_px - -bind_tool_runtime_api(Show2D, "Show2D") diff --git a/widget/src/quantem/widget/show4dstem.py b/widget/src/quantem/widget/show4dstem.py index c514be2f..8cd0cb58 100644 --- a/widget/src/quantem/widget/show4dstem.py +++ b/widget/src/quantem/widget/show4dstem.py @@ -1,22 +1,9 @@ """ show4dstem: Fast interactive 4D-STEM viewer widget. -Apple MPS GPU limit: PyTorch's MPS backend (Apple Silicon) has a hard limit -of ~2.1 billion elements (INT_MAX = 2^31 - 1) per tensor. Datasets exceeding -this automatically fall back to CPU, which is still fast on Apple Silicon -thanks to unified memory (CPU and GPU share the same RAM). - -CUDA GPUs do not have this limit. - -Common 4D-STEM sizes (float32): - - Scan Detector Elements Size MPS? - 128×128 128×128 268M 1.0 GB yes - 128×128 256×256 1,074M 4.0 GB yes - 256×256 128×128 1,074M 4.0 GB yes - 256×256 192×192 2,416M 9.0 GB no (auto CPU, still fast) - 256×256 256×256 4,295M 16.0 GB no (auto CPU, still fast) - 512×512 256×256 17,180M 64.0 GB no (auto CPU) +Single chunked-torch path on every device (CUDA / MPS / CPU). Reductions cast +uint16 → float32 in scan-row chunks bounded by _CHUNK_BYTE_BUDGET, so transient +memory stays the same regardless of total dataset size. To reduce data size, bin k-space at the dataset level before viewing: @@ -38,19 +25,20 @@ import torch import traitlets +# Cap transient chunk memory at ~600 MB regardless of detector size. +# A 4096 × 192² × 4 byte float32 cast = 600 MB; a 4096 × 256² × 4 byte cast +# would be 1.0 GB. _chunk_rows() picks an N-rows-per-chunk that keeps the +# transient under this cap. +_CHUNK_BYTE_BUDGET = 600 * 1024 * 1024 + from quantem.core.config import validate_device from quantem.widget.array_utils import to_numpy -from quantem.widget.json_state import ( +from quantem.widget.state import ( build_json_header, resolve_widget_version, save_state_file, unwrap_state_payload, ) -from quantem.widget.tool_parity import ( - bind_tool_runtime_api, - build_tool_groups, - normalize_tool_groups, -) def _format_memory(nbytes: int) -> str: @@ -64,7 +52,6 @@ def _format_memory(nbytes: int) -> str: # Constants # ============================================================================ DEFAULT_BF_RATIO = 0.125 # BF disk radius as fraction of detector size (1/8) -SPARSE_MASK_THRESHOLD = 0.2 # Use sparse indexing below this mask coverage MIN_LOG_VALUE = 1e-10 # Minimum value for log scale to avoid log(0) DEFAULT_VI_ROI_RATIO = 0.15 # Default VI ROI size as fraction of scan dimension @@ -84,12 +71,14 @@ class Show4DSTEM(anywidget.AnyWidget): for time-series or tilt-series data. scan_shape : tuple, optional If data is flattened (N, det_rows, det_cols), provide scan dimensions. - pixel_size : float, optional - Pixel size in Å (real-space). Used for scale bar. - Auto-extracted from Dataset4dstem if not provided. - k_pixel_size : float, optional - Detector pixel size in mrad (k-space). Used for scale bar. - Auto-extracted from Dataset4dstem if not provided. + sampling : tuple of 4 floats, optional + Pixel size per axis ``(scan_row, scan_col, k_row, k_col)``. Scalar + broadcasts to all four axes. Defaults to ``(1, 1, 1, 1)``. + Auto-extracted from ``Dataset4dstem`` if not provided. + units : list of 4 str, optional + Unit string per axis. Common: ``["A", "A", "mrad", "mrad"]``. + Defaults to ``["pixels"] * 4``. Auto-extracted from + ``Dataset4dstem`` if not provided. center : tuple[float, float], optional (center_row, center_col) of the diffraction pattern in pixels. If not provided, defaults to detector center. @@ -100,43 +89,58 @@ class Show4DSTEM(anywidget.AnyWidget): frame_dim_label : str, optional Label for the frame dimension when 5D data is provided. Defaults to "Frame". Common values: "Tilt", "Time", "Focus". - disabled_tools : list of str, optional - Tool groups to lock while still showing controls. Supported: - ``"display"``, ``"histogram"``, ``"stats"``, ``"navigation"``, - ``"playback"``, ``"view"``, ``"export"``, ``"roi"``, - ``"profile"``, ``"fft"``, ``"virtual"``, ``"frame"``, ``"all"``. - disable_* : bool, optional - Convenience flags mirroring ``disabled_tools`` for each tool group, - plus ``disable_all``. - hidden_tools : list of str, optional - Tool groups to hide from the UI. Uses the same keys as - ``disabled_tools``. - hide_* : bool, optional - Convenience flags mirroring ``disable_*`` for ``hidden_tools``. - Examples -------- - >>> # From Dataset4dstem (calibration auto-extracted) - >>> from quantem.core.io.file_readers import read_emdfile_to_4dstem - >>> dataset = read_emdfile_to_4dstem("data.h5") - >>> Show4DSTEM(dataset) - - >>> # From raw array with manual calibration >>> import numpy as np - >>> data = np.random.rand(64, 64, 128, 128) - >>> Show4DSTEM(data, pixel_size=2.39, k_pixel_size=0.46) + >>> from quantem.widget import Show4DSTEM + + 4D NumPy array ``(scan_rows, scan_cols, det_rows, det_cols)``: + + >>> Show4DSTEM(np.random.rand(64, 64, 128, 128)) + + PyTorch tensor (CPU or GPU): + + >>> import torch + >>> Show4DSTEM(torch.rand(64, 64, 128, 128)) + + With explicit calibration (real-space Å, k-space mrad): + + >>> Show4DSTEM(np.random.rand(64, 64, 128, 128), + ... sampling=(2.39, 2.39, 0.46, 0.46), + ... units=["A", "A", "mrad", "mrad"]) + + quantem ``Dataset4dstem`` — calibration + units auto-extracted: + + >>> from quantem.core.datastructures import Dataset4dstem + >>> ds = Dataset4dstem.from_array(np.random.rand(64, 64, 128, 128)) + >>> Show4DSTEM(ds) + + Flattened scan ``(N, det_rows, det_cols)`` with explicit scan shape: - >>> # With raster animation - >>> widget = Show4DSTEM(dataset) - >>> widget.raster(step=2, interval_ms=50) + >>> Show4DSTEM(np.random.rand(4096, 128, 128), scan_shape=(64, 64)) - >>> # 5D time-series or tilt-series data - >>> data_5d = np.random.rand(20, 64, 64, 128, 128) # 20 frames - >>> Show4DSTEM(data_5d, frame_dim_label="Tilt") + Custom BF disk center and radius (overrides auto-detection): + + >>> Show4DSTEM(np.random.rand(64, 64, 128, 128), + ... center=(64, 64), bf_radius=12) + + 5D time-series or tilt-series ``(n_frames, scan_r, scan_c, det_r, det_c)``: + + >>> Show4DSTEM(np.random.rand(20, 64, 64, 128, 128), frame_dim_label="Tilt") + + Raster animation (scan path through 4D dataset): + + >>> w = Show4DSTEM(np.random.rand(64, 64, 128, 128)) + >>> w.raster(step=2, interval_ms=50) + + Static export to PDF or PNG (single panel or all four): + + >>> w = Show4DSTEM(np.random.rand(64, 64, 128, 128)) + >>> w.save_image("dp.pdf", view="diffraction") + >>> w.save_image("all.pdf", view="all") """ _esm = pathlib.Path(__file__).parent / "static" / "show4dstem.js" - _css = pathlib.Path(__file__).parent / "static" / "show4dstem.css" # Position in scan space widget_version = traitlets.Unicode("unknown").tag(sync=True) @@ -188,9 +192,7 @@ class Show4DSTEM(anywidget.AnyWidget): # ========================================================================= # Virtual Image (ROI-based, updates as you drag ROI on DP) # ========================================================================= - virtual_image_bytes = traitlets.Bytes(b"").tag(sync=True) # Raw float32 - vi_data_min = traitlets.Float(0.0).tag(sync=True) # Min of current VI for normalization - vi_data_max = traitlets.Float(1.0).tag(sync=True) # Max of current VI for normalization + virtual_image_bytes = traitlets.Bytes(b"").tag(sync=True) # Raw float32 (JS computes stats + range) # ========================================================================= # VI ROI (real-space region selection for summed DP) @@ -198,18 +200,25 @@ class Show4DSTEM(anywidget.AnyWidget): vi_roi_mode = traitlets.Unicode("off").tag(sync=True) # "off", "circle", "rect" vi_roi_center_row = traitlets.Float(0.0).tag(sync=True) vi_roi_center_col = traitlets.Float(0.0).tag(sync=True) + # Compound (row, col) trait — JS sets in one call; one observer fires; bytes + # never compute against split-trait state (old col + new row, or vice versa). + vi_roi_center = traitlets.List(traitlets.Float(), default_value=[0.0, 0.0]).tag(sync=True) vi_roi_radius = traitlets.Float(5.0).tag(sync=True) vi_roi_width = traitlets.Float(10.0).tag(sync=True) vi_roi_height = traitlets.Float(10.0).tag(sync=True) - summed_dp_bytes = traitlets.Bytes(b"").tag(sync=True) # Summed DP from VI ROI - summed_dp_count = traitlets.Int(0).tag(sync=True) # Number of positions summed + # Reduction over scan positions inside vi_roi: mean is default (size-invariant DP), + # sum scales with area (quantitative counts), max picks brightest position per detector pixel. + vi_roi_reduce = traitlets.Unicode("mean").tag(sync=True) + vi_roi_dp_bytes = traitlets.Bytes(b"").tag(sync=True) # Reduced DP from VI ROI # ========================================================================= # Scale Bar # ========================================================================= - pixel_size = traitlets.Float(1.0).tag(sync=True) # Å per pixel (real-space) - k_pixel_size = traitlets.Float(1.0).tag(sync=True) # mrad per pixel (k-space) - k_calibrated = traitlets.Bool(False).tag(sync=True) # True if k-space has mrad calibration + pixel_size = traitlets.Float(1.0).tag(sync=True) # real-space pixel size (col axis) + pixel_unit = traitlets.Unicode("pixels").tag(sync=True) + k_pixel_size = traitlets.Float(1.0).tag(sync=True) # k-space pixel size (col axis) + k_pixel_unit = traitlets.Unicode("pixels").tag(sync=True) + k_calibrated = traitlets.Bool(False).tag(sync=True) # True if k-space has real units # ========================================================================= # Path Animation (programmatic crosshair control) @@ -223,14 +232,13 @@ class Show4DSTEM(anywidget.AnyWidget): # ========================================================================= # Auto-detection trigger (frontend sets to True, backend resets to False) # ========================================================================= - auto_detect_trigger = traitlets.Bool(False).tag(sync=True) # ========================================================================= # Statistics for display (mean, min, max, std) # ========================================================================= - dp_stats = traitlets.List(traitlets.Float(), default_value=[0.0, 0.0, 0.0, 0.0]).tag(sync=True) - vi_stats = traitlets.List(traitlets.Float(), default_value=[0.0, 0.0, 0.0, 0.0]).tag(sync=True) - mask_dc = traitlets.Bool(True).tag(sync=True) # Mask center pixel for DP stats + # dp_stats and vi_stats are computed JS-side from frame_bytes / virtual_image_bytes. + # Keeping them out of Python traits eliminates a 4-message comm race that produced + # mismatched bytes/min/max on rapid preset/ROI changes. # ========================================================================= # Display settings (synced for programmatic export parity) @@ -239,13 +247,9 @@ class Show4DSTEM(anywidget.AnyWidget): vi_colormap = traitlets.Unicode("inferno").tag(sync=True) fft_colormap = traitlets.Unicode("inferno").tag(sync=True) - dp_scale_mode = traitlets.Unicode("linear").tag(sync=True) # "linear" | "log" | "power" - vi_scale_mode = traitlets.Unicode("linear").tag(sync=True) # "linear" | "log" | "power" - fft_scale_mode = traitlets.Unicode("linear").tag(sync=True) # "linear" | "log" | "power" - - dp_power_exp = traitlets.Float(0.5).tag(sync=True) - vi_power_exp = traitlets.Float(0.5).tag(sync=True) - fft_power_exp = traitlets.Float(0.5).tag(sync=True) + dp_scale_mode = traitlets.Unicode("linear").tag(sync=True) # "linear" | "log" + vi_scale_mode = traitlets.Unicode("linear").tag(sync=True) # "linear" | "log" + fft_scale_mode = traitlets.Unicode("linear").tag(sync=True) # "linear" | "log" dp_vmin_pct = traitlets.Float(0.0).tag(sync=True) dp_vmax_pct = traitlets.Float(100.0).tag(sync=True) @@ -262,14 +266,19 @@ class Show4DSTEM(anywidget.AnyWidget): fft_auto = traitlets.Bool(True).tag(sync=True) show_fft = traitlets.Bool(False).tag(sync=True) + # Single-trait preset request: JS sets to "bf"/"abf"/"adf"/"haadf" → Python + # observer calls apply_preset() which batches the 5 ROI trait writes + # atomically. Avoids the JS-side ordering race where individual roi_mode/ + # radius/center traits would commit in separate comm messages. + _preset_request = traitlets.Unicode("").tag(sync=True) fft_window = traitlets.Bool(True).tag(sync=True) show_controls = traitlets.Bool(True).tag(sync=True) dp_show_colorbar = traitlets.Bool(False).tag(sync=True) - export_default_view = traitlets.Unicode("all").tag(sync=True) - export_default_format = traitlets.Unicode("png").tag(sync=True) - export_include_overlays = traitlets.Bool(True).tag(sync=True) - export_include_scalebar = traitlets.Bool(True).tag(sync=True) - export_default_dpi = traitlets.Int(300).tag(sync=True) + # VI panel auto-contrast (1st/99th percentile clip) and CSS smoothing. + # DP panel doesn't need either — Bragg spots are best read with nearest- + # neighbor + the slider's percentile range. + vi_auto_contrast = traitlets.Bool(False).tag(sync=True) + vi_smooth = traitlets.Bool(False).tag(sync=True) # ========================================================================= # Frame Animation (5D time/tilt series) @@ -294,139 +303,18 @@ class Show4DSTEM(anywidget.AnyWidget): profile_width = traitlets.Int(1).tag(sync=True) # ========================================================================= - # Tool visibility / locking - # ========================================================================= - disabled_tools = traitlets.List(traitlets.Unicode()).tag(sync=True) - hidden_tools = traitlets.List(traitlets.Unicode()).tag(sync=True) - - @classmethod - def _normalize_tool_groups(cls, tool_groups) -> list[str]: - return normalize_tool_groups("Show4DSTEM", tool_groups) - - @classmethod - def _build_disabled_tools( - cls, - disabled_tools=None, - disable_display: bool = False, - disable_histogram: bool = False, - disable_stats: bool = False, - disable_navigation: bool = False, - disable_playback: bool = False, - disable_view: bool = False, - disable_export: bool = False, - disable_roi: bool = False, - disable_profile: bool = False, - disable_fft: bool = False, - disable_virtual: bool = False, - disable_frame: bool = False, - disable_all: bool = False, - ) -> list[str]: - return build_tool_groups( - "Show4DSTEM", - tool_groups=disabled_tools, - all_flag=disable_all, - flag_map={ - "display": disable_display, - "histogram": disable_histogram, - "stats": disable_stats, - "navigation": disable_navigation, - "playback": disable_playback, - "view": disable_view, - "export": disable_export, - "roi": disable_roi, - "profile": disable_profile, - "fft": disable_fft, - "virtual": disable_virtual, - "frame": disable_frame, - }, - ) - - @classmethod - def _build_hidden_tools( - cls, - hidden_tools=None, - hide_display: bool = False, - hide_histogram: bool = False, - hide_stats: bool = False, - hide_navigation: bool = False, - hide_playback: bool = False, - hide_view: bool = False, - hide_export: bool = False, - hide_roi: bool = False, - hide_profile: bool = False, - hide_fft: bool = False, - hide_virtual: bool = False, - hide_frame: bool = False, - hide_all: bool = False, - ) -> list[str]: - return build_tool_groups( - "Show4DSTEM", - tool_groups=hidden_tools, - all_flag=hide_all, - flag_map={ - "display": hide_display, - "histogram": hide_histogram, - "stats": hide_stats, - "navigation": hide_navigation, - "playback": hide_playback, - "view": hide_view, - "export": hide_export, - "roi": hide_roi, - "profile": hide_profile, - "fft": hide_fft, - "virtual": hide_virtual, - "frame": hide_frame, - }, - ) - - @traitlets.validate("disabled_tools") - def _validate_disabled_tools(self, proposal): - return self._normalize_tool_groups(proposal["value"]) - - @traitlets.validate("hidden_tools") - def _validate_hidden_tools(self, proposal): - return self._normalize_tool_groups(proposal["value"]) - def __init__( self, data: "Dataset4dstem | np.ndarray", scan_shape: tuple[int, int] | None = None, - pixel_size: float | None = None, - k_pixel_size: float | None = None, + sampling: tuple[float, ...] | list[float] | None = None, + units: list[str] | tuple[str, ...] | None = None, center: tuple[float, float] | None = None, bf_radius: float | None = None, - precompute_virtual_images: bool = False, + precompute_virtual_images: bool = True, frame_dim_label: str | None = None, frame_labels: list[str] | None = None, title: str = "", - disabled_tools: list[str] | None = None, - disable_display: bool = False, - disable_histogram: bool = False, - disable_stats: bool = False, - disable_navigation: bool = False, - disable_playback: bool = False, - disable_view: bool = False, - disable_export: bool = False, - disable_roi: bool = False, - disable_profile: bool = False, - disable_fft: bool = False, - disable_virtual: bool = False, - disable_frame: bool = False, - disable_all: bool = False, - hidden_tools: list[str] | None = None, - hide_display: bool = False, - hide_histogram: bool = False, - hide_stats: bool = False, - hide_navigation: bool = False, - hide_playback: bool = False, - hide_view: bool = False, - hide_export: bool = False, - hide_roi: bool = False, - hide_profile: bool = False, - hide_fft: bool = False, - hide_virtual: bool = False, - hide_frame: bool = False, - hide_all: bool = False, show_fft: bool = False, fft_window: bool = True, show_controls: bool = True, @@ -445,60 +333,38 @@ def __init__( _io_labels = None - # Extract calibration from Dataset4dstem if provided - k_calibrated = False + # Auto-extract sampling + units from Dataset4dstem if available. if hasattr(data, "sampling") and hasattr(data, "array"): - # Dataset4dstem: extract calibration and array - # sampling = [scan_rows, scan_cols, det_rows, det_cols] if not title and hasattr(data, "name") and data.name: title = str(data.name) - units = getattr(data, "units", ["pixels"] * 4) - if pixel_size is None and units[0] in ("Å", "angstrom", "A", "nm"): - pixel_size = float(data.sampling[0]) - if units[0] == "nm": - pixel_size *= 10 # Convert nm to Å - if k_pixel_size is None and units[2] in ("mrad", "1/Å", "1/A"): - k_pixel_size = float(data.sampling[2]) - k_calibrated = True + if sampling is None: + sampling = tuple(float(s) for s in data.sampling) + if units is None and hasattr(data, "units"): + units = list(data.units) data = data.array + # Resolve sampling + units (4 axes for 4D-STEM): + # [scan_row, scan_col, k_row, k_col]. Scalar/None broadcast to (1, 1, 1, 1). + if sampling is None: + sampling = (1.0, 1.0, 1.0, 1.0) + elif isinstance(sampling, (int, float)): + sampling = (float(sampling),) * 4 + else: + sampling = tuple(float(s) for s in sampling) + if units is None: + units = ["pixels"] * 4 + elif isinstance(units, str): + units = [units] * 4 + else: + units = [str(u) for u in units] + self.title = title - # Store calibration values (default to 1.0 if not provided) - self.pixel_size = pixel_size if pixel_size is not None else 1.0 - self.k_pixel_size = k_pixel_size if k_pixel_size is not None else 1.0 - self.k_calibrated = k_calibrated or (k_pixel_size is not None) - self.disabled_tools = self._build_disabled_tools( - disabled_tools=disabled_tools, - disable_display=disable_display, - disable_histogram=disable_histogram, - disable_stats=disable_stats, - disable_navigation=disable_navigation, - disable_playback=disable_playback, - disable_view=disable_view, - disable_export=disable_export, - disable_roi=disable_roi, - disable_profile=disable_profile, - disable_fft=disable_fft, - disable_virtual=disable_virtual, - disable_frame=disable_frame, - disable_all=disable_all, - ) - self.hidden_tools = self._build_hidden_tools( - hidden_tools=hidden_tools, - hide_display=hide_display, - hide_histogram=hide_histogram, - hide_stats=hide_stats, - hide_navigation=hide_navigation, - hide_playback=hide_playback, - hide_view=hide_view, - hide_export=hide_export, - hide_roi=hide_roi, - hide_profile=hide_profile, - hide_fft=hide_fft, - hide_virtual=hide_virtual, - hide_frame=hide_frame, - hide_all=hide_all, - ) + self.pixel_size = sampling[1] # scan_col axis (horizontal scale bar) + self.pixel_unit = units[1] if len(units) > 1 else "pixels" + self.k_pixel_size = sampling[3] if len(sampling) > 3 else 1.0 + self.k_pixel_unit = units[3] if len(units) > 3 else "pixels" + # k-space considered calibrated when its unit is real (mrad, 1/Å, etc.). + self.k_calibrated = self.k_pixel_unit not in ("pixels", "") self.show_fft = show_fft self.fft_window = fft_window self.show_controls = show_controls @@ -508,44 +374,40 @@ def __init__( self.vi_vmax = vi_vmax # Path animation (configured via set_path() or raster()) self._path_points: list[tuple[int, int]] = [] - # Named user presets saved during this session - self._named_presets: dict[str, dict[str, Any]] = {} - # Session-scoped reproducibility log for all export calls - self._export_session_id = uuid4().hex - self._export_session_started_utc = datetime.now(timezone.utc).isoformat() - self._export_log: list[dict[str, Any]] = [] - # Sparse sampling state (for streaming/adaptive acquisition workflows) - self._sparse_samples: dict[tuple[int, int, int], np.ndarray] = {} - self._sparse_order: list[tuple[int, int, int]] = [] - # Convert to NumPy then PyTorch tensor using quantem device config - data_np = to_numpy(data) - device_str, _ = validate_device(None) # Get device from quantem config - self._device = torch.device(device_str) - # Remove saturated hot pixels in numpy (before any torch conversion) - saturated_value = 65535.0 if data_np.dtype == np.uint16 else 255.0 if data_np.dtype == np.uint8 else None - if data_np.dtype != np.float32: - _tc = time.perf_counter() - data_np = data_np.astype(np.float32) - if _verbose: - print(f" astype float32: {time.perf_counter() - _tc:.2f}s") - if saturated_value is not None: - data_np[data_np >= saturated_value] = 0 + # Suppress per-trait recompute during apply_preset batch writes + self._suppress_roi_recompute = False + # Torch tensor input keeps its device (lets user pin a specific GPU via + # `data.cuda(1)`). NumPy / Dataset input gets default-validated device. + if isinstance(data, torch.Tensor): + self._device = data.device + self._data_pre = data + data_np = None + else: + device_str, _ = validate_device(None) + self._device = torch.device(device_str) + data_np = to_numpy(data) + self._data_pre = None + self._saturation_value = ( + 65535 if data_np.dtype == np.uint16 + else 255 if data_np.dtype == np.uint8 + else None + ) # Handle dimensionality — 5D loads eagerly for instant frame switching - ndim = data_np.ndim + # Resolve shape from whichever input path we took + shape = tuple(self._data_pre.shape) if self._data_pre is not None else data_np.shape + size_elements = int(np.prod(shape)) + ndim = len(shape) _tc = time.perf_counter() if ndim == 5: - self.n_frames = data_np.shape[0] - self._scan_shape = (data_np.shape[1], data_np.shape[2]) - self._det_shape = (data_np.shape[3], data_np.shape[4]) - if data_np.size > 2**31 - 1 and device_str == "mps": - self._device = torch.device("cpu") - self._data = torch.from_numpy(data_np).to(self._device) + self.n_frames = shape[0] + self._scan_shape = (shape[1], shape[2]) + self._det_shape = (shape[3], shape[4]) elif ndim == 3: self.n_frames = 1 if scan_shape is not None: self._scan_shape = scan_shape else: - n = data_np.shape[0] + n = shape[0] side = int(n ** 0.5) if side * side != n: raise ValueError( @@ -553,24 +415,45 @@ def __init__( f"Provide scan_shape explicitly." ) self._scan_shape = (side, side) - self._det_shape = (data_np.shape[1], data_np.shape[2]) - # MPS backend can't handle tensors >INT_MAX elements; fall back to CPU - if data_np.size > 2**31 - 1 and device_str == "mps": - self._device = torch.device("cpu") - self._data = torch.from_numpy(data_np).to(self._device) + self._det_shape = (shape[1], shape[2]) elif ndim == 4: self.n_frames = 1 - self._scan_shape = (data_np.shape[0], data_np.shape[1]) - self._det_shape = (data_np.shape[2], data_np.shape[3]) - if data_np.size > 2**31 - 1 and device_str == "mps": - self._device = torch.device("cpu") - self._data = torch.from_numpy(data_np).to(self._device) + self._scan_shape = (shape[0], shape[1]) + self._det_shape = (shape[2], shape[3]) else: - raise ValueError(f"Show4DSTEM expects a 3D ((N, det_h, det_w) flat-scan), 4D ((scan_h, scan_w, det_h, det_w)), or 5D ((n_frames, scan_h, scan_w, det_h, det_w)) array. Got {ndim}D. Reshape with array.reshape((scan_h, scan_w, det_h, det_w)) or pass a Dataset4dstem.") + raise ValueError(f"Show4DSTEM expects a 3D ((N, det_h, det_w) flat-scan), 4D ((scan_h, scan_w, det_h, det_w)), or 5D ((n_frames, scan_h, scan_w, det_h, det_w)) array. Got {ndim}D.") + if self._data_pre is not None: + self._data = self._data_pre if self._data_pre.device == self._device else self._data_pre.to(self._device) + del self._data_pre + else: + self._data = torch.from_numpy(data_np).to(self._device) + # Saturation filter: zero detector pixels at full-scale (65535 / 255). + # PyTorch lacks unsigned int comparison kernels, but uint16 viewed + # as int16 has identical bytes (65535 → -1) and int16 comparison + # works on every device. Apply in scan-row chunks so the transient + # bool mask stays bounded (≤600 MB) and fits constrained-VRAM + # devices (Mac 24 GB unified, etc.). View-write keeps native dtype. + sat = getattr(self, "_saturation_value", None) + view_dtype = ( + torch.int16 if sat is not None and self._data.dtype == torch.uint16 + else torch.int8 if sat is not None and self._data.dtype == torch.uint8 + else None + ) + if view_dtype is not None: + view = self._data.view(view_dtype).reshape(-1, *self._det_shape) + rows = view.shape[0] + # Bool mask transient = positions × det_h × det_w bytes; cap at budget. + pos_per_chunk = max(1, _CHUNK_BYTE_BUDGET // max(1, self._det_shape[0] * self._det_shape[1])) + for i in range(0, rows, pos_per_chunk): + chunk = view[i:i + pos_per_chunk] + chunk.masked_fill_(chunk == -1, 0) + # Keep native dtype (uint8/uint16) to bound memory at ~ data_size. + # Reductions cast in chunks (bounded transient). if _verbose: if str(self._device) == "mps": torch.mps.synchronize() - print(f" to {self._device}: {time.perf_counter() - _tc:.2f}s ({data_np.nbytes / 1e9:.1f} GB)") + n_bytes = self._data.element_size() * self._data.numel() + print(f" to {self._device}: {time.perf_counter() - _tc:.2f}s ({n_bytes / 1e9:.1f} GB)") self.shape_rows = self._scan_shape[0] self.shape_cols = self._scan_shape[1] @@ -586,17 +469,20 @@ def __init__( self._frame_labels = resolved_labels if resolved_labels: self.frame_labels = list(resolved_labels) - # Histogram axis range — first frame is enough (JS does per-frame percentile clipping) + # Histogram axis range — first frame is enough (JS does per-frame percentile clipping). + # Cast to float for min/max reductions: PyTorch CUDA lacks integer min/max kernels, + # and the first slice is tiny (144 KB at 192×192) so the cast is free. first_frame = self._data[0] if self._data.ndim == 5 else self._data - self.dp_global_min = max(float(first_frame.min()), MIN_LOG_VALUE) - self.dp_global_max = float(first_frame.max()) + first_frame_sample = first_frame[0] if first_frame.ndim >= 3 else first_frame + if not torch.is_floating_point(first_frame_sample): + first_frame_sample = first_frame_sample.float() + self.dp_global_min = max(float(first_frame_sample.min()), MIN_LOG_VALUE) + self.dp_global_max = float(first_frame_sample.max()) # Cache coordinate tensors for mask creation (avoid repeated torch.arange) self._det_row_coords = torch.arange(self.det_rows, device=self._device, dtype=torch.float32)[:, None] self._det_col_coords = torch.arange(self.det_cols, device=self._device, dtype=torch.float32)[None, :] self._scan_row_coords = torch.arange(self.shape_rows, device=self._device, dtype=torch.float32)[:, None] self._scan_col_coords = torch.arange(self.shape_cols, device=self._device, dtype=torch.float32)[None, :] - self._sparse_mask = np.zeros((self.n_frames, self.shape_rows, self.shape_cols), dtype=bool) - self._dose_map = np.zeros((self.n_frames, self.shape_rows, self.shape_cols), dtype=np.float32) # Setup center and BF radius det_size = min(self.det_rows, self.det_cols) if center is not None and bf_radius is not None: @@ -628,6 +514,7 @@ def __init__( self._cached_bf_virtual = None self._cached_abf_virtual = None self._cached_adf_virtual = None + self._cached_haadf_virtual = None if precompute_virtual_images and self.n_frames == 1: self._precompute_common_virtual_images() @@ -662,9 +549,9 @@ def __init__( # Frame animation (5D): observe frame_idx changes from frontend self.observe(self._on_frame_idx_change, names=["frame_idx"]) + self.observe(self._on_preset_request, names=["_preset_request"]) # Auto-detect trigger: observe changes from frontend - self.observe(self._on_auto_detect_trigger, names=["auto_detect_trigger"]) # VI ROI: observe changes for summed DP computation # Initialize VI ROI center to scan center with reasonable default sizes @@ -677,8 +564,9 @@ def __init__( self.vi_roi_height = float(default_roi_size) self.observe(self._on_vi_roi_change, names=[ "vi_roi_mode", "vi_roi_center_row", "vi_roi_center_col", - "vi_roi_radius", "vi_roi_width", "vi_roi_height" + "vi_roi_radius", "vi_roi_width", "vi_roi_height", "vi_roi_reduce" ]) + self.observe(self._on_vi_roi_center_change, names=["vi_roi_center"]) if state is not None: if isinstance(state, (str, pathlib.Path)): @@ -700,16 +588,12 @@ def set_image(self, data, scan_shape=None): data = data.array data_np = to_numpy(data) saturated_value = 65535.0 if data_np.dtype == np.uint16 else 255.0 if data_np.dtype == np.uint8 else None - if data_np.dtype != np.float32: - data_np = data_np.astype(np.float32) if saturated_value is not None: data_np[data_np >= saturated_value] = 0 if data_np.ndim == 5: self.n_frames = data_np.shape[0] self._scan_shape = (data_np.shape[1], data_np.shape[2]) self._det_shape = (data_np.shape[3], data_np.shape[4]) - if data_np.size > 2**31 - 1 and str(self._device) == "mps": - self._device = torch.device("cpu") self._data = torch.from_numpy(data_np).to(self._device) elif data_np.ndim == 3: self.n_frames = 1 @@ -736,19 +620,19 @@ def set_image(self, data, scan_shape=None): self.det_rows = self._det_shape[0] self.det_cols = self._det_shape[1] first_frame = self._data[0] if self._data.ndim == 5 else self._data - self.dp_global_min = max(float(first_frame.min()), MIN_LOG_VALUE) - self.dp_global_max = float(first_frame.max()) + first_frame_sample = first_frame[0] if first_frame.ndim >= 3 else first_frame + if not torch.is_floating_point(first_frame_sample): + first_frame_sample = first_frame_sample.float() + self.dp_global_min = max(float(first_frame_sample.min()), MIN_LOG_VALUE) + self.dp_global_max = float(first_frame_sample.max()) self._det_row_coords = torch.arange(self.det_rows, device=self._device, dtype=torch.float32)[:, None] self._det_col_coords = torch.arange(self.det_cols, device=self._device, dtype=torch.float32)[None, :] self._scan_row_coords = torch.arange(self.shape_rows, device=self._device, dtype=torch.float32)[:, None] self._scan_col_coords = torch.arange(self.shape_cols, device=self._device, dtype=torch.float32)[None, :] - self._sparse_mask = np.zeros((self.n_frames, self.shape_rows, self.shape_cols), dtype=bool) - self._dose_map = np.zeros((self.n_frames, self.shape_rows, self.shape_cols), dtype=np.float32) - self._sparse_samples = {} - self._sparse_order = [] self._cached_bf_virtual = None self._cached_abf_virtual = None self._cached_adf_virtual = None + self._cached_haadf_virtual = None with self.hold_trait_notifications(): self.pos_row = min(self.pos_row, self.shape_rows - 1) self.pos_col = min(self.pos_col, self.shape_cols - 1) @@ -756,7 +640,6 @@ def set_image(self, data, scan_shape=None): self._update_frame() def __repr__(self) -> str: - k_unit = "mrad" if self.k_calibrated else "px" shape = ( f"({self.n_frames}, {self.shape_rows}, {self.shape_cols}, {self.det_rows}, {self.det_cols})" if self.n_frames > 1 @@ -766,7 +649,7 @@ def __repr__(self) -> str: title_info = f", title='{self.title}'" if self.title else "" return ( f"Show4DSTEM(shape={shape}, " - f"sampling=({self.pixel_size} Å, {self.k_pixel_size} {k_unit}), " + f"sampling=({self.pixel_size} {self.pixel_unit}, {self.k_pixel_size} {self.k_pixel_unit}), " f"pos=({self.pos_row}, {self.pos_col}){frame_info}{title_info})" ) @@ -776,7 +659,9 @@ def state_dict(self): "pos_row": self.pos_row, "pos_col": self.pos_col, "pixel_size": self.pixel_size, + "pixel_unit": self.pixel_unit, "k_pixel_size": self.k_pixel_size, + "k_pixel_unit": self.k_pixel_unit, "k_calibrated": self.k_calibrated, "center_row": self.center_row, "center_col": self.center_col, @@ -795,16 +680,13 @@ def state_dict(self): "vi_roi_radius": self.vi_roi_radius, "vi_roi_width": self.vi_roi_width, "vi_roi_height": self.vi_roi_height, - "mask_dc": self.mask_dc, + "vi_roi_reduce": self.vi_roi_reduce, "dp_colormap": self.dp_colormap, "vi_colormap": self.vi_colormap, "fft_colormap": self.fft_colormap, "dp_scale_mode": self.dp_scale_mode, "vi_scale_mode": self.vi_scale_mode, "fft_scale_mode": self.fft_scale_mode, - "dp_power_exp": self.dp_power_exp, - "vi_power_exp": self.vi_power_exp, - "fft_power_exp": self.fft_power_exp, "dp_vmin_pct": self.dp_vmin_pct, "dp_vmax_pct": self.dp_vmax_pct, "vi_vmin_pct": self.vi_vmin_pct, @@ -820,11 +702,8 @@ def state_dict(self): "fft_window": self.fft_window, "show_controls": self.show_controls, "dp_show_colorbar": self.dp_show_colorbar, - "export_default_view": self.export_default_view, - "export_default_format": self.export_default_format, - "export_include_overlays": self.export_include_overlays, - "export_include_scalebar": self.export_include_scalebar, - "export_default_dpi": self.export_default_dpi, + "vi_auto_contrast": self.vi_auto_contrast, + "vi_smooth": self.vi_smooth, "path_interval_ms": self.path_interval_ms, "path_loop": self.path_loop, "profile_line": self.profile_line, @@ -836,8 +715,6 @@ def state_dict(self): "frame_fps": self.frame_fps, "frame_reverse": self.frame_reverse, "frame_boomerang": self.frame_boomerang, - "disabled_tools": self.disabled_tools, - "hidden_tools": self.hidden_tools, } def save(self, path: str): @@ -881,16 +758,11 @@ def free(self): gc.collect() if device == "mps": try: - import torch torch.mps.empty_cache() - except Exception: + except AttributeError: pass elif device.startswith("cuda"): - try: - import torch - torch.cuda.empty_cache() - except Exception: - pass + torch.cuda.empty_cache() if nbytes > 0: print(f"freed {_format_memory(nbytes)} ({device})") @@ -912,15 +784,10 @@ def summary(self): lines.append(f"Labels: {self._frame_labels}") else: lines.append(f"Labels: {self._frame_labels[:3]} ... ({len(self._frame_labels)} total)") - lines.append(f"Scan: {self.shape_rows}×{self.shape_cols} ({self.pixel_size:.2f} Å/px)") - k_unit = "mrad" if self.k_calibrated else "px" - lines.append(f"Detector: {self.det_rows}×{self.det_cols} ({self.k_pixel_size:.4f} {k_unit}/px)") + lines.append(f"Scan: {self.shape_rows}×{self.shape_cols} ({self.pixel_size:.2f} {self.pixel_unit}/px)") + lines.append(f"Detector: {self.det_rows}×{self.det_cols} ({self.k_pixel_size:.4f} {self.k_pixel_unit}/px)") lines.append(f"Position: ({self.pos_row}, {self.pos_col})") lines.append(f"Center: ({self.center_row:.1f}, {self.center_col:.1f}) BF r={self.bf_radius:.1f} px") - display_parts = [] - if self.mask_dc: - display_parts.append("DC masked") - lines.append(f"Display: {', '.join(display_parts) if display_parts else 'default'}") if self.roi_active: lines.append(f"ROI: {self.roi_mode} at ({self.roi_center_row:.1f}, {self.roi_center_col:.1f}) r={self.roi_radius:.1f}") if self.vi_roi_mode != "off": @@ -945,10 +812,6 @@ def summary(self): if self.profile_line and len(self.profile_line) == 2: p0, p1 = self.profile_line[0], self.profile_line[1] lines.append(f"Profile: ({p0['row']:.0f}, {p0['col']:.0f}) -> ({p1['row']:.0f}, {p1['col']:.0f}) width={self.profile_width}") - if self.disabled_tools: - lines.append(f"Locked: {', '.join(self.disabled_tools)}") - if self.hidden_tools: - lines.append(f"Hidden: {', '.join(self.hidden_tools)}") print("\n".join(lines)) # ========================================================================= @@ -1124,12 +987,12 @@ def _on_path_index_change(self, change): self.pos_row = max(0, min(self.shape_rows - 1, row)) self.pos_col = max(0, min(self.shape_cols - 1, col)) - def _on_auto_detect_trigger(self, change): - """Called when auto_detect_trigger is set to True from frontend.""" - if change["new"]: - self.auto_detect_center() - # Reset trigger to allow re-triggering - self.auto_detect_trigger = False + def _on_preset_request(self, change): + """JS preset shortcut → atomic apply_preset (no per-trait race).""" + name = (change.get("new") or "").strip().lower() + if name in ("bf", "abf", "adf", "haadf"): + self.apply_preset(name) + self._preset_request = "" # consume trigger def _on_frame_idx_change(self, change=None): """Called when frame_idx changes (5D time/tilt series). @@ -1143,12 +1006,13 @@ def _on_frame_idx_change(self, change=None): self._cached_bf_virtual = None self._cached_abf_virtual = None self._cached_adf_virtual = None + self._cached_haadf_virtual = None # Recompute virtual image and displayed frame self._compute_virtual_image_from_roi() self._update_frame() - # Recompute summed DP if VI ROI is active + # Recompute reduced DP if VI ROI is active if self.vi_roi_mode != "off": - self._compute_summed_dp_from_vi_roi() + self._compute_vi_roi_dp() # ========================================================================= # Path Animation Patterns @@ -1353,28 +1217,26 @@ def auto_detect_center(self, update_roi: bool = True) -> Self: >>> widget = Show4DSTEM(data) >>> widget.auto_detect_center() # Auto-detect and apply """ - # Sum all diffraction patterns to get average (PyTorch) - if self._data.ndim == 5: - summed_dp = self._data.sum(dim=(0, 1, 2)) - elif self._data.ndim == 4: - summed_dp = self._data.sum(dim=(0, 1)) - else: - summed_dp = self._data.sum(dim=0) + # Sum diffraction patterns over scan positions to find BF disk centroid. + # Single chunked torch float path: works identically on CUDA / MPS / CPU. + # Each chunk casts uint16 → float32 transiently (~600 MB max), accumulates. + data_flat = self._data.reshape(-1, *self._det_shape) + n_pos = data_flat.shape[0] + mean_dp = torch.zeros(self._det_shape, dtype=torch.float32, device=self._device) + # Float32 cast transient = positions × det_h × det_w × 4 bytes; cap at budget. + pos_per_chunk = max(1, _CHUNK_BYTE_BUDGET // max(1, self._det_shape[0] * self._det_shape[1] * 4)) + for i in range(0, n_pos, pos_per_chunk): + mean_dp += data_flat[i:i + pos_per_chunk].sum(dim=0, dtype=torch.float32) + + threshold = mean_dp.mean() + mean_dp.std() + mask = mean_dp > threshold - # Threshold at mean + std to isolate BF disk - threshold = summed_dp.mean() + summed_dp.std() - mask = summed_dp > threshold - - # Avoid division by zero total = mask.sum() if total == 0: return self - # Calculate centroid using cached coordinate grids cx = float((self._det_col_coords * mask).sum() / total) cy = float((self._det_row_coords * mask).sum() / total) - - # Estimate radius from mask area (A = pi*r^2) radius = float(torch.sqrt(total / torch.pi)) # Apply detected values @@ -1402,17 +1264,10 @@ def _get_frame(self, row: int, col: int) -> np.ndarray: else: return data[row, col].cpu().numpy() - def _apply_scale_mode( - self, - data: np.ndarray, - mode: str, - power_exp: float = 0.5, - ) -> np.ndarray: + def _apply_scale_mode(self, data: np.ndarray, mode: str) -> np.ndarray: arr = np.asarray(data, dtype=np.float32) if mode == "log": return np.log1p(np.maximum(arr, 0.0)).astype(np.float32) - if mode == "power": - return np.power(np.maximum(arr, 0.0), float(power_exp)).astype(np.float32) return arr.astype(np.float32) def _slider_range( @@ -1458,13 +1313,13 @@ def _get_virtual_image_array(self) -> np.ndarray: return np.zeros((self.shape_rows, self.shape_cols), dtype=np.float32) return arr.reshape(self.shape_rows, self.shape_cols).copy() - def _get_summed_dp_array(self) -> np.ndarray | None: + def _get_vi_roi_dp_array(self) -> np.ndarray | None: if self.vi_roi_mode == "off": return None - self._compute_summed_dp_from_vi_roi() - if not self.summed_dp_bytes: + self._compute_vi_roi_dp() + if not self.vi_roi_dp_bytes: return None - arr = np.frombuffer(self.summed_dp_bytes, dtype=np.float32) + arr = np.frombuffer(self.vi_roi_dp_bytes, dtype=np.float32) expected = self.det_rows * self.det_cols if arr.size != expected: return None @@ -1497,24 +1352,24 @@ def _fft_enhanced_range(self, mag: np.ndarray) -> tuple[float, float]: return dmin, pmax def _render_dp_rgb(self) -> tuple[np.ndarray, dict]: - summed_dp = self._get_summed_dp_array() - if summed_dp is not None: - raw = summed_dp - source = "summed_dp" + vi_roi_arr = self._get_vi_roi_dp_array() + if vi_roi_arr is not None: + raw = vi_roi_arr + source = "vi_roi_dp" else: raw = self._get_frame(self.pos_row, self.pos_col).astype(np.float32) source = "single_frame" scale_mode = self.dp_scale_mode - scaled = self._apply_scale_mode(raw, scale_mode, self.dp_power_exp) + scaled = self._apply_scale_mode(raw, scale_mode) data_min = float(scaled.min()) if scaled.size else 0.0 data_max = float(scaled.max()) if scaled.size else 0.0 if self.dp_vmin is not None and self.dp_vmax is not None: vmin = float(self._apply_scale_mode( - np.array([max(self.dp_vmin, 0)], dtype=np.float32), scale_mode, self.dp_power_exp + np.array([max(self.dp_vmin, 0)], dtype=np.float32), scale_mode )[0]) vmax = float(self._apply_scale_mode( - np.array([max(self.dp_vmax, 0)], dtype=np.float32), scale_mode, self.dp_power_exp + np.array([max(self.dp_vmax, 0)], dtype=np.float32), scale_mode )[0]) else: vmin, vmax = self._slider_range(data_min, data_max, self.dp_vmin_pct, self.dp_vmax_pct) @@ -1532,15 +1387,15 @@ def _render_dp_rgb(self) -> tuple[np.ndarray, dict]: def _render_virtual_rgb(self) -> tuple[np.ndarray, dict]: raw = self._get_virtual_image_array() - scaled = self._apply_scale_mode(raw, self.vi_scale_mode, self.vi_power_exp) + scaled = self._apply_scale_mode(raw, self.vi_scale_mode) data_min = float(scaled.min()) if scaled.size else 0.0 data_max = float(scaled.max()) if scaled.size else 0.0 if self.vi_vmin is not None and self.vi_vmax is not None: vmin = float(self._apply_scale_mode( - np.array([max(self.vi_vmin, 0)], dtype=np.float32), self.vi_scale_mode, self.vi_power_exp + np.array([max(self.vi_vmin, 0)], dtype=np.float32), self.vi_scale_mode )[0]) vmax = float(self._apply_scale_mode( - np.array([max(self.vi_vmax, 0)], dtype=np.float32), self.vi_scale_mode, self.vi_power_exp + np.array([max(self.vi_vmax, 0)], dtype=np.float32), self.vi_scale_mode )[0]) else: vmin, vmax = self._slider_range(data_min, data_max, self.vi_vmin_pct, self.vi_vmax_pct) @@ -1559,7 +1414,7 @@ def _render_fft_rgb(self) -> tuple[np.ndarray, dict]: virtual_raw = self._get_virtual_image_array() fft = np.fft.fftshift(np.fft.fft2(virtual_raw)) mag = np.abs(fft).astype(np.float32) - scaled = self._apply_scale_mode(mag, self.fft_scale_mode, self.fft_power_exp) + scaled = self._apply_scale_mode(mag, self.fft_scale_mode) if self.fft_auto: display_min, display_max = self._fft_enhanced_range(scaled) else: @@ -1578,28 +1433,13 @@ def _render_fft_rgb(self) -> tuple[np.ndarray, dict]: } return rgb, metadata - def list_export_views(self) -> tuple[str, ...]: - return ("diffraction", "virtual", "fft", "all") - - def list_export_formats(self) -> tuple[str, ...]: - return ("png", "pdf") - - def list_figure_templates(self) -> tuple[str, ...]: - return ("dp_vi", "dp_vi_fft", "publication_dp_vi", "publication_dp_vi_fft") - - def list_presets(self) -> tuple[str, ...]: - builtin = ("bf", "abf", "adf", "haadf") - custom = tuple(sorted(self._named_presets.keys())) - return builtin + custom + _EXPORT_VIEWS = ("diffraction", "virtual", "fft", "all") + _EXPORT_FORMATS = ("png", "pdf") def _validate_export_view(self, view: str | None) -> str: - candidate = self.export_default_view if view is None else str(view) - view_key = str(candidate).strip().lower() - allowed = self.list_export_views() - if view_key not in allowed: - raise ValueError( - f"Unsupported view '{view}'. Supported: {', '.join(allowed)}" - ) + view_key = (view or "all").strip().lower() + if view_key not in self._EXPORT_VIEWS: + raise ValueError(f"Unsupported view '{view}'. Supported: {', '.join(self._EXPORT_VIEWS)}") return view_key def _validate_frame_idx(self, frame_idx: int | None) -> int: @@ -1628,21 +1468,10 @@ def _validate_position(self, position: tuple[int, int] | None) -> tuple[int, int ) return row, col - def _resolve_export_format( - self, - path: pathlib.Path, - fmt: str | None, - ) -> str: - if fmt is not None and str(fmt).strip(): - resolved = str(fmt).strip().lower() - else: - from_path = path.suffix.lstrip(".").lower() - resolved = from_path if from_path else str(self.export_default_format).strip().lower() - allowed = self.list_export_formats() - if resolved not in allowed: - raise ValueError( - f"Unsupported format '{resolved}'. Supported: {', '.join(allowed)}" - ) + def _resolve_export_format(self, path: pathlib.Path, fmt: str | None) -> str: + resolved = (fmt or path.suffix.lstrip(".") or "png").strip().lower() + if resolved not in self._EXPORT_FORMATS: + raise ValueError(f"Unsupported format '{resolved}'. Supported: {', '.join(self._EXPORT_FORMATS)}") return resolved @staticmethod @@ -1917,15 +1746,6 @@ def _vi_roi_metadata(self) -> dict[str, Any]: "height": float(self.vi_roi_height), } - def _export_settings_metadata(self) -> dict[str, Any]: - return { - "default_view": self.export_default_view, - "default_format": self.export_default_format, - "include_overlays": bool(self.export_include_overlays), - "include_scalebar": bool(self.export_include_scalebar), - "dpi": int(self.export_default_dpi), - } - def _build_image_export_metadata( self, export_path: pathlib.Path, @@ -1954,1111 +1774,129 @@ def _build_image_export_metadata( "display": render_meta, "include_overlays": bool(include_overlays), "include_scalebar": bool(include_scalebar), - "export_settings": self._export_settings_metadata(), } if extra: metadata.update(extra) return metadata - @staticmethod - def _sha256_file(path: pathlib.Path) -> str: - digest = hashlib.sha256() - with path.open("rb") as f: - while True: - chunk = f.read(1_048_576) - if not chunk: - break - digest.update(chunk) - return digest.hexdigest() - - def _build_file_record( - self, - path: pathlib.Path, - metadata_path: pathlib.Path | None = None, - index: int | None = None, - ) -> dict[str, Any]: - record: dict[str, Any] = { - "path": str(path), - "sha256": self._sha256_file(path), - "size_bytes": int(path.stat().st_size), - } - if metadata_path is not None and metadata_path.exists(): - record["metadata_path"] = str(metadata_path) - record["metadata_sha256"] = self._sha256_file(metadata_path) - record["metadata_size_bytes"] = int(metadata_path.stat().st_size) - if index is not None: - record["index"] = int(index) - return record - - def _record_export_event(self, event: dict[str, Any]) -> None: - payload = { - "session_id": self._export_session_id, - "timestamp_utc": datetime.now(timezone.utc).isoformat(), - } - payload.update(event) - self._export_log.append(payload) - - def _validate_sparse_frame_idx(self, frame_idx: int | None) -> int: - if self.n_frames <= 1: - return 0 - if frame_idx is None: - return int(self.frame_idx) - idx = int(frame_idx) - if idx < 0 or idx >= self.n_frames: - raise ValueError(f"frame_idx={idx} is out of range [0, {self.n_frames - 1}]") - return idx - - def _normalize_sparse_mask(self, mask: np.ndarray) -> np.ndarray: - arr = np.asarray(mask) - if self.n_frames <= 1: - if arr.shape == (self.shape_rows, self.shape_cols): - arr = arr[None, ...] - elif arr.shape != (1, self.shape_rows, self.shape_cols): - raise ValueError( - f"mask shape {arr.shape} does not match " - f"(scan_rows, scan_cols)=({self.shape_rows}, {self.shape_cols})" - ) - elif arr.shape != (self.n_frames, self.shape_rows, self.shape_cols): - raise ValueError( - f"mask shape {arr.shape} does not match " - f"(n_frames, scan_rows, scan_cols)=({self.n_frames}, {self.shape_rows}, {self.shape_cols})" - ) - return arr.astype(bool, copy=False) - - def _coerce_dp_array(self, dp: np.ndarray) -> np.ndarray: - arr = np.asarray(to_numpy(dp), dtype=np.float32) - if arr.shape != (self.det_rows, self.det_cols): - raise ValueError( - f"dp shape {arr.shape} does not match detector_shape " - f"({self.det_rows}, {self.det_cols})" - ) - return arr - - def _write_dp_to_data(self, frame_idx: int, row: int, col: int, dp_arr: np.ndarray) -> None: - dp_tensor = torch.from_numpy(dp_arr).to(device=self._device, dtype=torch.float32) - if self.n_frames > 1: - self._data[frame_idx, row, col] = dp_tensor - elif self._data.ndim == 4: - self._data[row, col] = dp_tensor - else: - flat_idx = row * self.shape_cols + col - self._data[flat_idx] = dp_tensor - - def _ingest_scan_point_core( - self, - row: int, - col: int, - dp: np.ndarray, - frame_idx: int, - dose: float, - refresh: bool, - ) -> None: - row_i, col_i = self._validate_position((row, col)) - frame_i = self._validate_sparse_frame_idx(frame_idx) - dp_arr = self._coerce_dp_array(dp) - dose_value = float(dose) - if not np.isfinite(dose_value) or dose_value < 0: - raise ValueError(f"dose must be finite and >= 0, got {dose}") - - key = (int(frame_i), int(row_i), int(col_i)) - if key not in self._sparse_samples: - self._sparse_order.append(key) - self._sparse_samples[key] = dp_arr.copy() - self._sparse_mask[frame_i, row_i, col_i] = True - self._dose_map[frame_i, row_i, col_i] += dose_value - - self._write_dp_to_data(frame_i, row_i, col_i, dp_arr) - self.dp_global_min = max(min(float(self.dp_global_min), float(dp_arr.min())), MIN_LOG_VALUE) - self.dp_global_max = max(float(self.dp_global_max), float(dp_arr.max())) - - if refresh: - self._compute_virtual_image_from_roi() - self._update_frame() - - def _detector_integration_kernel(self) -> tuple[np.ndarray | None, tuple[int, int] | None]: - cx, cy = float(self.roi_center_col), float(self.roi_center_row) - rr, cc = np.meshgrid( - np.arange(self.det_rows, dtype=np.float32), - np.arange(self.det_cols, dtype=np.float32), - indexing="ij", - ) - if self.roi_mode == "circle" and self.roi_radius > 0: - mask = (cc - cx) ** 2 + (rr - cy) ** 2 <= float(self.roi_radius) ** 2 - return mask.astype(np.float32, copy=False), None - if self.roi_mode == "square" and self.roi_radius > 0: - half = float(self.roi_radius) - mask = (np.abs(cc - cx) <= half) & (np.abs(rr - cy) <= half) - return mask.astype(np.float32, copy=False), None - if self.roi_mode == "annular" and self.roi_radius > 0: - outer = float(self.roi_radius) - inner = float(self.roi_radius_inner) - dist_sq = (cc - cx) ** 2 + (rr - cy) ** 2 - mask = (dist_sq >= inner**2) & (dist_sq <= outer**2) - return mask.astype(np.float32, copy=False), None - if self.roi_mode == "rect" and self.roi_width > 0 and self.roi_height > 0: - hw = float(self.roi_width) / 2.0 - hh = float(self.roi_height) / 2.0 - mask = (np.abs(cc - cx) <= hw) & (np.abs(rr - cy) <= hh) - return mask.astype(np.float32, copy=False), None - row = int(max(0, min(round(cy), self.det_rows - 1))) - col = int(max(0, min(round(cx), self.det_cols - 1))) - return None, (row, col) - - def _integrate_dp_value( - self, - dp: np.ndarray, - mask: np.ndarray | None, - point_idx: tuple[int, int] | None, - ) -> float: - arr = np.asarray(dp, dtype=np.float32) - if point_idx is not None: - row, col = point_idx - return float(arr[row, col]) - if mask is None: - return 0.0 - return float((arr * mask).sum()) - - def _virtual_image_from_frame_array(self, frame_data: np.ndarray) -> np.ndarray: - arr = np.asarray(frame_data, dtype=np.float32) - if arr.shape != (self.shape_rows, self.shape_cols, self.det_rows, self.det_cols): - raise ValueError( - f"frame_data shape {arr.shape} does not match " - f"({self.shape_rows}, {self.shape_cols}, {self.det_rows}, {self.det_cols})" - ) - mask, point_idx = self._detector_integration_kernel() - if point_idx is not None: - row, col = point_idx - return arr[:, :, row, col].astype(np.float32, copy=False) - return (arr * mask[None, None, :, :]).sum(axis=(2, 3)).astype(np.float32) - - @staticmethod - def _idw_reconstruct( - shape: tuple[int, int], - points: np.ndarray, - values: np.ndarray, - power: float = 2.0, - k_neighbors: int = 16, - ) -> np.ndarray: - if points.size == 0: - return np.zeros(shape, dtype=np.float32) - rr, cc = np.meshgrid( - np.arange(shape[0], dtype=np.float32), - np.arange(shape[1], dtype=np.float32), - indexing="ij", - ) - coords = np.stack([rr.reshape(-1), cc.reshape(-1)], axis=1) - dist_sq = ((coords[:, None, :] - points[None, :, :]) ** 2).sum(axis=2) + 1e-6 - - if k_neighbors > 0 and points.shape[0] > k_neighbors: - idx = np.argpartition(dist_sq, kth=k_neighbors - 1, axis=1)[:, :k_neighbors] - dist_sq = np.take_along_axis(dist_sq, idx, axis=1) - vals_local = values[idx] - else: - vals_local = np.broadcast_to(values[None, :], dist_sq.shape) - - weights = 1.0 / np.power(dist_sq, power / 2.0) - pred = (weights * vals_local).sum(axis=1) / np.maximum(weights.sum(axis=1), 1e-6) - return pred.reshape(shape).astype(np.float32, copy=False) - - def _resolve_reference_virtual_image( - self, - reference: str | np.ndarray, - frame_idx: int, - ) -> tuple[np.ndarray, str]: - if isinstance(reference, str): - key = reference.strip().lower() - if key != "full_raster": - raise ValueError("reference must be 'full_raster' or a NumPy array") - if self.n_frames > 1: - frame = self._data[frame_idx].detach().cpu().numpy() - elif self._data.ndim == 4: - frame = self._data.detach().cpu().numpy() - else: - frame = self._data.detach().cpu().numpy().reshape( - self.shape_rows, self.shape_cols, self.det_rows, self.det_cols - ) - return self._virtual_image_from_frame_array(frame), "full_raster" - - arr = np.asarray(to_numpy(reference), dtype=np.float32) - if arr.shape == (self.shape_rows, self.shape_cols): - return arr.astype(np.float32, copy=False), "virtual_image" - if arr.shape == (self.shape_rows, self.shape_cols, self.det_rows, self.det_cols): - return self._virtual_image_from_frame_array(arr), "frame_data" - if arr.shape == (self.n_frames, self.shape_rows, self.shape_cols, self.det_rows, self.det_cols): - return self._virtual_image_from_frame_array(arr[frame_idx]), "stack_frame_data" - raise ValueError( - "Unsupported reference shape. Expected one of: " - f"(scan_rows, scan_cols), " - f"(scan_rows, scan_cols, det_rows, det_cols), or " - f"(n_frames, scan_rows, scan_cols, det_rows, det_cols)." - ) - - def _extract_sparse_samples(self, frame_idx: int) -> tuple[np.ndarray, np.ndarray]: - mask = self._sparse_mask[frame_idx] - coords = np.argwhere(mask) - if coords.size == 0: - return ( - np.zeros((0, 2), dtype=np.float32), - np.zeros((0,), dtype=np.float32), - ) - - integ_mask, point_idx = self._detector_integration_kernel() - values = np.zeros((coords.shape[0],), dtype=np.float32) - for i, (row, col) in enumerate(coords): - key = (int(frame_idx), int(row), int(col)) - dp = self._sparse_samples.get(key) - if dp is None: - dp = self._get_frame(int(row), int(col)) - values[i] = self._integrate_dp_value(dp, integ_mask, point_idx) - points = coords.astype(np.float32, copy=False) - return points, values - - def ingest_scan_point( + def save_image( self, - row: int, - col: int, - dp: np.ndarray, - frame_idx: int = 0, - dose: float | None = None, - ) -> Self: + path: str | pathlib.Path, + view: str | None = None, + position: tuple[int, int] | None = None, + frame_idx: int | None = None, + format: str | None = None, + include_metadata: bool = True, + metadata_path: str | pathlib.Path | None = None, + include_overlays: bool | None = None, + include_scalebar: bool | None = None, + restore_state: bool = True, + dpi: int | None = None, + ) -> pathlib.Path: """ - Ingest one scanned diffraction pattern into sparse acquisition state. + Save the current visualization as PNG or PDF. Parameters ---------- - row : int - Scan-space row index. - col : int - Scan-space column index. - dp : array_like - Diffraction pattern with shape ``(det_rows, det_cols)``. - frame_idx : int, default 0 - Frame index for 5D data. - dose : float, optional - Dose contribution for this acquisition event. Defaults to ``1.0``. + path : str or pathlib.Path + Output image path. + view : str, optional + One of: "diffraction", "virtual", "fft", "all". + position : tuple[int, int], optional + Temporary scan position override as (row, col) for this export. + frame_idx : int, optional + Temporary frame index override for 5D data. + format : str, optional + "png" or "pdf". If omitted, inferred from file extension. + include_metadata : bool, default True + If True, writes JSON metadata next to the image. + metadata_path : str or pathlib.Path, optional + Override metadata JSON path. + include_overlays : bool, default True + Draw ROI/profile/crosshair overlays on exported panels. + include_scalebar : bool, default True + Draw panel scale bars on exported panels. + restore_state : bool, default True + If True, temporary position/frame overrides are reverted after export. + dpi : int, optional + Export DPI metadata. Returns ------- - Show4DSTEM - Self for method chaining. + pathlib.Path + The written image path. """ - self._ingest_scan_point_core( - row=row, - col=col, - dp=dp, - frame_idx=frame_idx, - dose=1.0 if dose is None else float(dose), - refresh=True, - ) - self._record_export_event( - { - "export_kind": "ingest_scan_point", - "frame_idx": int(self._validate_sparse_frame_idx(frame_idx)), - "row": int(row), - "col": int(col), - "dose": float(1.0 if dose is None else dose), - } - ) - return self + from PIL import Image - def ingest_scan_block( - self, - rows: list[int] | np.ndarray, - cols: list[int] | np.ndarray, - dp_block: np.ndarray, - frame_idx: int = 0, - ) -> Self: - """ - Ingest multiple scanned diffraction patterns in one call. + export_path = pathlib.Path(path) + view_key = self._validate_export_view(view) + fmt = self._resolve_export_format(export_path, format) + dpi_value = 300 if dpi is None else int(dpi) + overlays_enabled = True if include_overlays is None else bool(include_overlays) + scalebar_enabled = True if include_scalebar is None else bool(include_scalebar) + if dpi_value <= 0: + raise ValueError(f"dpi must be > 0, got {dpi_value}") - Parameters - ---------- - rows : list[int] or np.ndarray - Row indices for each pattern in ``dp_block``. - cols : list[int] or np.ndarray - Column indices for each pattern in ``dp_block``. - dp_block : np.ndarray - Diffraction stack with shape ``(n_points, det_rows, det_cols)``. - frame_idx : int, default 0 - Frame index for 5D data. + export_path.parent.mkdir(parents=True, exist_ok=True) - Returns - ------- - Show4DSTEM - Self for method chaining. - """ - rows_arr = np.asarray(rows, dtype=np.int64).reshape(-1) - cols_arr = np.asarray(cols, dtype=np.int64).reshape(-1) - if rows_arr.size != cols_arr.size: - raise ValueError("rows and cols must have the same length") - - block = np.asarray(to_numpy(dp_block), dtype=np.float32) - if block.ndim == 2: - block = block[None, ...] - if block.ndim != 3 or block.shape[1:] != (self.det_rows, self.det_cols): - raise ValueError( - f"dp_block shape must be (n_points, {self.det_rows}, {self.det_cols}), got {block.shape}" - ) - if block.shape[0] != rows_arr.size: - raise ValueError( - f"dp_block has {block.shape[0]} patterns but rows/cols specify {rows_arr.size} points" - ) + prev_row, prev_col = self.pos_row, self.pos_col + prev_frame = self.frame_idx + meta_path: pathlib.Path | None = None + export_row = int(self.pos_row) + export_col = int(self.pos_col) + export_frame = int(self.frame_idx) - frame_i = self._validate_sparse_frame_idx(frame_idx) - for idx in range(rows_arr.size): - self._ingest_scan_point_core( - row=int(rows_arr[idx]), - col=int(cols_arr[idx]), - dp=block[idx], - frame_idx=frame_i, - dose=1.0, - refresh=False, - ) + try: + if frame_idx is not None: + self.frame_idx = self._validate_frame_idx(frame_idx) + if position is not None: + row, col = self._validate_position(position) + self.pos_row = row + self.pos_col = col + export_row = int(self.pos_row) + export_col = int(self.pos_col) + export_frame = int(self.frame_idx) - self._compute_virtual_image_from_roi() - self._update_frame() - self._record_export_event( - { - "export_kind": "ingest_scan_block", - "frame_idx": int(frame_i), - "n_points": int(rows_arr.size), - } - ) - return self + if view_key == "diffraction": + image, dp_meta = self._render_panel_image( + "diffraction", overlays_enabled, scalebar_enabled + ) + render_meta = {"diffraction": dp_meta} + elif view_key == "virtual": + image, vi_meta = self._render_panel_image( + "virtual", overlays_enabled, scalebar_enabled + ) + render_meta = {"virtual": vi_meta} + elif view_key == "fft": + image, fft_meta = self._render_panel_image( + "fft", overlays_enabled, scalebar_enabled + ) + render_meta = {"fft": fft_meta} + else: + panel_images = [] + render_meta = {} + dp_img, dp_meta = self._render_panel_image( + "diffraction", overlays_enabled, scalebar_enabled + ) + vi_img, vi_meta = self._render_panel_image( + "virtual", overlays_enabled, scalebar_enabled + ) + panel_images.extend([dp_img, vi_img]) + render_meta = {"diffraction": dp_meta, "virtual": vi_meta} + if self.show_fft: + fft_img, fft_meta = self._render_panel_image( + "fft", overlays_enabled, scalebar_enabled + ) + panel_images.append(fft_img) + render_meta["fft"] = fft_meta + image = self._compose_horizontal(panel_images) - def get_sparse_state(self) -> dict[str, Any]: - """ - Return sparse acquisition state for checkpointing or replay. - - Returns - ------- - dict - Sparse state with sampling mask, sampled diffraction stack, - sampled-point coordinates, and dose map. - """ - coords = np.argwhere(self._sparse_mask) - sampled_points = [ - {"frame_idx": int(f), "row": int(r), "col": int(c)} - for (f, r, c) in coords - ] - if coords.size: - sampled_data = np.stack( - [ - self._sparse_samples.get((int(f), int(r), int(c)), self._get_frame(int(r), int(c))) - for (f, r, c) in coords - ], - axis=0, - ).astype(np.float32, copy=False) - else: - sampled_data = np.zeros((0, self.det_rows, self.det_cols), dtype=np.float32) - - mask_payload = self._sparse_mask[0].copy() if self.n_frames <= 1 else self._sparse_mask.copy() - dose_payload = self._dose_map[0].copy() if self.n_frames <= 1 else self._dose_map.copy() - return { - **build_json_header("Show4DSTEM"), - "format": "json", - "export_kind": "sparse_state_snapshot", - "frame_idx": int(self.frame_idx), - "scan_shape": {"rows": int(self.shape_rows), "cols": int(self.shape_cols)}, - "detector_shape": {"rows": int(self.det_rows), "cols": int(self.det_cols)}, - "mask": mask_payload, - "sampled_data": sampled_data, - "sampled_points": sampled_points, - "dose_map": dose_payload, - "n_sampled": int(len(sampled_points)), - "total_dose": float(self._dose_map.sum()), - } - - def set_sparse_state( - self, - mask: np.ndarray, - sampled_data: np.ndarray, - ) -> Self: - """ - Restore sparse acquisition state from mask + sampled data. - - Parameters - ---------- - mask : np.ndarray - Boolean scan mask. Shape ``(scan_rows, scan_cols)`` for 4D, - or ``(n_frames, scan_rows, scan_cols)`` for 5D. - sampled_data : np.ndarray - Either compact stack ``(n_sampled, det_rows, det_cols)`` - matching row-major ``mask`` order, or dense data aligned to mask: - ``(scan_rows, scan_cols, det_rows, det_cols)`` for 4D, - ``(n_frames, scan_rows, scan_cols, det_rows, det_cols)`` for 5D. - - Returns - ------- - Show4DSTEM - Self for method chaining. - """ - mask_3d = self._normalize_sparse_mask(mask) - coords = np.argwhere(mask_3d) - - payload = np.asarray(to_numpy(sampled_data), dtype=np.float32) - n_points = int(coords.shape[0]) - - if payload.ndim == 3: - if payload.shape[0] != n_points or payload.shape[1:] != (self.det_rows, self.det_cols): - raise ValueError( - f"Compact sampled_data must be (n_sampled, {self.det_rows}, {self.det_cols}); " - f"got {payload.shape} for n_sampled={n_points}" - ) - compact = payload - elif self.n_frames <= 1 and payload.shape == (self.shape_rows, self.shape_cols, self.det_rows, self.det_cols): - compact = np.stack( - [payload[int(r), int(c)] for (_, r, c) in coords], - axis=0, - ) if n_points else np.zeros((0, self.det_rows, self.det_cols), dtype=np.float32) - elif payload.shape == ( - self.n_frames, - self.shape_rows, - self.shape_cols, - self.det_rows, - self.det_cols, - ): - compact = np.stack( - [payload[int(f), int(r), int(c)] for (f, r, c) in coords], - axis=0, - ) if n_points else np.zeros((0, self.det_rows, self.det_cols), dtype=np.float32) - else: - raise ValueError( - "Unsupported sampled_data shape for set_sparse_state. " - "Use compact (n_sampled, det_rows, det_cols) or dense per-mask arrays." - ) - - self._sparse_samples = {} - self._sparse_order = [] - self._sparse_mask = np.zeros((self.n_frames, self.shape_rows, self.shape_cols), dtype=bool) - self._dose_map = np.zeros((self.n_frames, self.shape_rows, self.shape_cols), dtype=np.float32) - - for idx, (frame_idx, row, col) in enumerate(coords): - self._ingest_scan_point_core( - row=int(row), - col=int(col), - dp=compact[idx], - frame_idx=int(frame_idx), - dose=1.0, - refresh=False, - ) - - self._compute_virtual_image_from_roi() - self._update_frame() - self._record_export_event( - { - "export_kind": "set_sparse_state", - "n_sampled": int(n_points), - } - ) - return self - - def _resolve_proposal_count( - self, - k: int, - frame_idx: int, - budget: dict[str, Any] | None, - ) -> int: - count = int(k) - if count < 1: - raise ValueError(f"k must be >= 1, got {k}") - if budget is None: - return count - - existing_points = int(self._sparse_mask[frame_idx].sum()) - existing_dose = float(self._dose_map[frame_idx].sum()) - total_points = int(self.shape_rows * self.shape_cols) - - if "max_new_points" in budget: - count = min(count, int(budget["max_new_points"])) - if "max_total_points" in budget: - count = min(count, max(0, int(budget["max_total_points"]) - existing_points)) - if "max_total_fraction" in budget: - allowed_total = int(round(float(budget["max_total_fraction"]) * total_points)) - count = min(count, max(0, allowed_total - existing_points)) - if "max_total_dose" in budget: - dose_per_point = float(budget.get("dose_per_point", 1.0)) - if dose_per_point <= 0: - raise ValueError("budget['dose_per_point'] must be > 0") - remaining = float(budget["max_total_dose"]) - existing_dose - count = min(count, max(0, int(math.floor(remaining / dose_per_point)))) - return max(0, int(count)) - - def propose_next_points( - self, - k: int, - strategy: str = "adaptive", - budget: dict[str, Any] | None = None, - ) -> list[tuple[int, int]]: - """ - Propose next scan points from current sparse acquisition state. - - Parameters - ---------- - k : int - Maximum number of new points to propose. - strategy : str, default "adaptive" - Proposal strategy: ``"adaptive"``, ``"random"``, or ``"raster"``. - budget : dict, optional - Optional constraints and strategy parameters. Supported keys: - ``frame_idx``, ``max_new_points``, ``max_total_points``, - ``max_total_fraction``, ``max_total_dose``, ``dose_per_point``, - ``roi_mask``, ``seed``, ``min_spacing``, ``step``, - ``local_window``, ``dose_lambda``, ``weights``, ``bidirectional``. - - Returns - ------- - list[tuple[int, int]] - Proposed ``(row, col)`` scan coordinates. - """ - budget_dict = {} if budget is None else dict(budget) - strategy_key = str(strategy).strip().lower() - if strategy_key not in {"adaptive", "random", "raster"}: - raise ValueError("strategy must be one of: adaptive, random, raster") - - frame_idx = self._validate_sparse_frame_idx(budget_dict.get("frame_idx", self.frame_idx)) - n_select = self._resolve_proposal_count(int(k), frame_idx, budget_dict) - if n_select <= 0: - return [] - - sampled_mask = self._sparse_mask[frame_idx].copy() - allowed_mask = ~sampled_mask - roi_mask_raw = budget_dict.get("roi_mask", None) - if roi_mask_raw is not None: - roi_mask = np.asarray(roi_mask_raw, dtype=bool) - if roi_mask.shape != (self.shape_rows, self.shape_cols): - raise ValueError( - f"roi_mask shape {roi_mask.shape} must match " - f"scan_shape ({self.shape_rows}, {self.shape_cols})" - ) - allowed_mask &= roi_mask - - proposals: list[tuple[int, int]] = [] - if strategy_key == "adaptive": - local_window = int(budget_dict.get("local_window", 5)) - if local_window < 1: - raise ValueError("budget['local_window'] must be >= 1") - min_spacing = int(budget_dict.get("min_spacing", 2)) - if min_spacing < 0: - raise ValueError("budget['min_spacing'] must be >= 0") - dose_lambda = float(budget_dict.get("dose_lambda", 0.25)) - if not np.isfinite(dose_lambda): - raise ValueError("budget['dose_lambda'] must be finite") - - default_weights = { - "vi_gradient": 0.4, - "vi_local_std": 0.3, - "dp_variance": 0.3, - } - merged_weights = dict(default_weights) - raw_weights = budget_dict.get("weights", None) - if raw_weights is not None: - for key, value in dict(raw_weights).items(): - if key not in default_weights: - raise ValueError( - f"Unsupported adaptive weight '{key}'. " - f"Supported: {', '.join(default_weights.keys())}" - ) - merged_weights[key] = float(value) - weight_sum = sum(max(0.0, float(v)) for v in merged_weights.values()) - if weight_sum <= 0: - raise ValueError("At least one adaptive weight must be > 0") - weights = {k: max(0.0, float(v)) / weight_sum for k, v in merged_weights.items()} - - vi = self._virtual_image_for_frame(frame_idx) - grad_row, grad_col = np.gradient(vi) - vi_gradient = np.hypot(grad_row, grad_col).astype(np.float32) - mean_local = self._box_mean_map(vi, local_window) - mean_sq_local = self._box_mean_map(vi * vi, local_window) - vi_local_std = np.sqrt(np.maximum(mean_sq_local - mean_local * mean_local, 0.0)).astype(np.float32) - dp_variance = self._dp_variance_map(frame_idx=frame_idx) - - utility = ( - weights["vi_gradient"] * self._normalize_score_map(vi_gradient) - + weights["vi_local_std"] * self._normalize_score_map(vi_local_std) - + weights["dp_variance"] * self._normalize_score_map(dp_variance) - ).astype(np.float32) - - frame_dose = self._dose_map[frame_idx].astype(np.float32, copy=False) - if float(frame_dose.max()) > 0: - utility = utility - float(dose_lambda) * (frame_dose / float(frame_dose.max())) - - picks = self._select_spaced_topk( - scores=utility, - k=n_select, - min_spacing=min_spacing, - allowed_mask=allowed_mask, - excluded_mask=np.zeros_like(allowed_mask, dtype=bool), - ) - proposals = [(int(r), int(c)) for (r, c) in picks] - elif strategy_key == "random": - coords = np.argwhere(allowed_mask) - if coords.size: - seed = budget_dict.get("seed", None) - rng = np.random.default_rng(None if seed is None else int(seed)) - n_take = min(n_select, int(coords.shape[0])) - idx = rng.choice(coords.shape[0], size=n_take, replace=False) - chosen = coords[idx] - proposals = [(int(r), int(c)) for r, c in chosen] - else: - step = int(budget_dict.get("step", 1)) - if step < 1: - raise ValueError("budget['step'] must be >= 1") - bidirectional = bool(budget_dict.get("bidirectional", True)) - for row in range(0, self.shape_rows, step): - cols = list(range(0, self.shape_cols, step)) - if bidirectional and ((row // step) % 2 == 1): - cols.reverse() - for col in cols: - if allowed_mask[row, col]: - proposals.append((int(row), int(col))) - if len(proposals) >= n_select: - break - if len(proposals) >= n_select: - break - - self._record_export_event( - { - "export_kind": "propose_next_points", - "strategy": strategy_key, - "frame_idx": int(frame_idx), - "k_requested": int(k), - "k_returned": int(len(proposals)), - } - ) - return proposals - - def evaluate_against_reference( - self, - reference: str | np.ndarray = "full_raster", - metrics: list[str] | None = None, - ) -> dict[str, Any]: - """ - Evaluate sparse-sampled reconstruction against a reference image. - - Parameters - ---------- - reference : str or np.ndarray, default "full_raster" - Reference target. ``"full_raster"`` uses the current full dataset - and current ROI integration settings. Arrays are also accepted - (virtual image or full diffraction stack; see method docs). - metrics : list[str], optional - Metric names to compute. Supported: ``"rmse"``, ``"nrmse"``, - ``"mae"``, ``"psnr"``. - - Returns - ------- - dict - Evaluation summary including sampled fraction and metric values. - """ - metric_names = ( - ["rmse", "nrmse", "mae", "psnr"] - if metrics is None - else [str(name).strip().lower() for name in metrics] - ) - supported = {"rmse", "nrmse", "mae", "psnr"} - unknown = [name for name in metric_names if name not in supported] - if unknown: - raise ValueError(f"Unsupported metrics: {unknown}. Supported: {sorted(supported)}") - - frame_idx = int(self.frame_idx if self.n_frames <= 1 else self._validate_sparse_frame_idx(self.frame_idx)) - points, values = self._extract_sparse_samples(frame_idx) - if points.shape[0] == 0: - raise ValueError("No sparse samples available for evaluation. Ingest points first.") - - reference_vi, reference_kind = self._resolve_reference_virtual_image(reference, frame_idx) - reconstruction = self._idw_reconstruct( - shape=(self.shape_rows, self.shape_cols), - points=points, - values=values, - power=2.0, - k_neighbors=16, - ) - - ref = np.asarray(reference_vi, dtype=np.float32) - pred = np.asarray(reconstruction, dtype=np.float32) - diff = pred - ref - mse = float(np.mean(diff * diff)) - rmse = float(np.sqrt(mse)) - mae = float(np.mean(np.abs(diff))) - ref_range = float(ref.max() - ref.min()) + 1e-6 - nrmse = float(rmse / ref_range) - peak = float(max(float(ref.max()), 1e-6)) - psnr = 120.0 if mse <= 1e-12 else float(20.0 * np.log10(peak) - 10.0 * np.log10(mse)) - - metric_values = { - "rmse": rmse, - "nrmse": nrmse, - "mae": mae, - "psnr": psnr, - } - selected_metrics = {name: float(metric_values[name]) for name in metric_names} - - summary = { - "reference_kind": reference_kind, - "frame_idx": int(frame_idx), - "n_sampled": int(points.shape[0]), - "sampled_fraction": float(points.shape[0] / max(1, self.shape_rows * self.shape_cols)), - "metrics": selected_metrics, - "scan_shape": {"rows": int(self.shape_rows), "cols": int(self.shape_cols)}, - "detector_shape": {"rows": int(self.det_rows), "cols": int(self.det_cols)}, - } - self._record_export_event( - { - "export_kind": "evaluate_against_reference", - "reference_kind": reference_kind, - "frame_idx": int(frame_idx), - "n_sampled": int(points.shape[0]), - "sampled_fraction": float(summary["sampled_fraction"]), - "metrics": selected_metrics, - } - ) - return summary - - def export_session_bundle( - self, - path: str | pathlib.Path, - ) -> pathlib.Path: - """ - Export a reproducible session bundle for sparse/adaptive workflows. - - The bundle includes widget state, sparse-state arrays, a current view - image with metadata, and the reproducibility report. - - Parameters - ---------- - path : str or pathlib.Path - Output directory for bundle files. - - Returns - ------- - pathlib.Path - Path to the bundle manifest JSON. - """ - bundle_dir = pathlib.Path(path) - bundle_dir.mkdir(parents=True, exist_ok=True) - - state_path = bundle_dir / "widget_state.json" - self.save(state_path) - - sparse_state = self.get_sparse_state() - sparse_npz_path = bundle_dir / "sparse_state.npz" - np.savez_compressed( - sparse_npz_path, - mask=sparse_state["mask"], - sampled_data=sparse_state["sampled_data"], - dose_map=sparse_state["dose_map"], - ) - - sparse_points_path = bundle_dir / "sparse_points.json" - sparse_points_payload = { - **build_json_header("Show4DSTEM"), - "format": "json", - "export_kind": "sparse_points", - "n_sampled": int(sparse_state["n_sampled"]), - "sampled_points": sparse_state["sampled_points"], - } - sparse_points_path.write_text(json.dumps(sparse_points_payload, indent=2)) - - image_path = bundle_dir / "current_all.png" - image_written = self.save_image( - image_path, - view="all", - include_metadata=True, - include_overlays=True, - include_scalebar=True, - ) - image_meta_path = image_written.with_suffix(".json") - - report_path = self.save_reproducibility_report(bundle_dir / "reproducibility_report.json") - - manifest_path = bundle_dir / "session_bundle_manifest.json" - manifest_payload = { - **build_json_header("Show4DSTEM"), - "format": "json", - "export_kind": "session_bundle", - "bundle_path": str(bundle_dir), - "created_utc": datetime.now(timezone.utc).isoformat(), - "session_id": self._export_session_id, - "scan_shape": {"rows": int(self.shape_rows), "cols": int(self.shape_cols)}, - "detector_shape": {"rows": int(self.det_rows), "cols": int(self.det_cols)}, - "sparse_summary": { - "n_sampled": int(sparse_state["n_sampled"]), - "sampled_fraction": float( - sparse_state["n_sampled"] / max(1, self.shape_rows * self.shape_cols * self.n_frames) - ), - "total_dose": float(sparse_state["total_dose"]), - }, - "files": { - "state": str(state_path), - "sparse_npz": str(sparse_npz_path), - "sparse_points_json": str(sparse_points_path), - "image": str(image_written), - "image_metadata": str(image_meta_path), - "reproducibility_report": str(report_path), - }, - } - manifest_path.write_text(json.dumps(manifest_payload, indent=2)) - - self._record_export_event( - { - "export_kind": "session_bundle", - "n_sampled": int(sparse_state["n_sampled"]), - "outputs": [ - self._build_file_record(state_path), - self._build_file_record(sparse_npz_path), - self._build_file_record(sparse_points_path), - self._build_file_record(image_written, metadata_path=image_meta_path), - self._build_file_record(report_path), - self._build_file_record(manifest_path), - ], - } - ) - return manifest_path - - def _normalize_score_map(self, values: np.ndarray) -> np.ndarray: - arr = np.asarray(values, dtype=np.float32) - if arr.size == 0: - return np.zeros_like(arr, dtype=np.float32) - vmin = float(np.percentile(arr, 1.0)) - vmax = float(np.percentile(arr, 99.0)) - if vmax <= vmin: - return np.zeros_like(arr, dtype=np.float32) - return np.clip((arr - vmin) / (vmax - vmin), 0.0, 1.0).astype(np.float32) - - def _box_mean_map(self, values: np.ndarray, window: int) -> np.ndarray: - arr = np.asarray(values, dtype=np.float32) - win = int(window) - if win <= 1: - return arr.copy() - if win % 2 == 0: - win += 1 - pad = win // 2 - padded = np.pad(arr, ((pad, pad), (pad, pad)), mode="reflect") - integral = np.pad(padded, ((1, 0), (1, 0)), mode="constant").cumsum(axis=0).cumsum(axis=1) - sums = ( - integral[win:, win:] - - integral[:-win, win:] - - integral[win:, :-win] - + integral[:-win, :-win] - ) - return (sums / float(win * win)).astype(np.float32) - - def _dp_variance_map(self, frame_idx: int | None = None) -> np.ndarray: - if frame_idx is None or self.n_frames <= 1: - data = self._frame_data - else: - idx = self._validate_sparse_frame_idx(frame_idx) - data = self._data[idx] - if data.ndim == 4: - variance = data.var(dim=(2, 3), unbiased=False) - return variance.detach().cpu().numpy().astype(np.float32, copy=False) - variance = data.var(dim=(1, 2), unbiased=False) - return variance.detach().cpu().numpy().reshape(self.shape_rows, self.shape_cols).astype(np.float32, copy=False) - - def _build_coarse_points(self, step: int, bidirectional: bool) -> list[tuple[int, int]]: - points: list[tuple[int, int]] = [] - for r in range(0, self.shape_rows, step): - cols = list(range(0, self.shape_cols, step)) - if bidirectional and ((r // step) % 2 == 1): - cols.reverse() - for c in cols: - points.append((int(r), int(c))) - return points - - def _select_spaced_topk( - self, - scores: np.ndarray, - k: int, - min_spacing: int, - allowed_mask: np.ndarray, - excluded_mask: np.ndarray, - ) -> list[tuple[int, int]]: - work = np.asarray(scores, dtype=np.float32).copy() - work[~allowed_mask] = -np.inf - work[excluded_mask] = -np.inf - selected: list[tuple[int, int]] = [] - radius = max(0, int(min_spacing)) - - for _ in range(int(max(0, k))): - flat_idx = int(np.argmax(work)) - best_score = float(work.flat[flat_idx]) - if not np.isfinite(best_score): - break - row, col = np.unravel_index(flat_idx, work.shape) - selected.append((int(row), int(col))) - if radius == 0: - work[row, col] = -np.inf - continue - r0 = max(0, row - radius) - r1 = min(work.shape[0], row + radius + 1) - c0 = max(0, col - radius) - c1 = min(work.shape[1], col + radius + 1) - rr, cc = np.ogrid[r0:r1, c0:c1] - neighborhood = (rr - row) ** 2 + (cc - col) ** 2 <= radius ** 2 - block = work[r0:r1, c0:c1] - block[neighborhood] = -np.inf - return selected - - def _nearest_neighbor_order( - self, - points: list[tuple[int, int]], - start: tuple[int, int] | None = None, - ) -> list[tuple[int, int]]: - remaining = [tuple(map(int, pt)) for pt in points] - if not remaining: - return [] - - if start is None: - current = remaining.pop(0) - else: - sr, sc = int(start[0]), int(start[1]) - start_idx = min( - range(len(remaining)), - key=lambda i: (remaining[i][0] - sr) ** 2 + (remaining[i][1] - sc) ** 2, - ) - current = remaining.pop(start_idx) - - ordered = [current] - while remaining: - cr, cc = current - next_idx = min( - range(len(remaining)), - key=lambda i: (remaining[i][0] - cr) ** 2 + (remaining[i][1] - cc) ** 2, - ) - current = remaining.pop(next_idx) - ordered.append(current) - return ordered - - def save_image( - self, - path: str | pathlib.Path, - view: str | None = None, - position: tuple[int, int] | None = None, - frame_idx: int | None = None, - format: str | None = None, - include_metadata: bool = True, - metadata_path: str | pathlib.Path | None = None, - include_overlays: bool | None = None, - include_scalebar: bool | None = None, - restore_state: bool = True, - dpi: int | None = None, - ) -> pathlib.Path: - """ - Save the current visualization as PNG or PDF. - - Parameters - ---------- - path : str or pathlib.Path - Output image path. - view : str, optional - One of: "diffraction", "virtual", "fft", "all". - position : tuple[int, int], optional - Temporary scan position override as (row, col) for this export. - frame_idx : int, optional - Temporary frame index override for 5D data. - format : str, optional - "png" or "pdf". If omitted, inferred from file extension. - include_metadata : bool, default True - If True, writes JSON metadata next to the image. - metadata_path : str or pathlib.Path, optional - Override metadata JSON path. - include_overlays : bool, optional - Draw ROI/profile/crosshair overlays on exported panels. - Defaults to ``export_include_overlays``. - include_scalebar : bool, optional - Draw panel scale bars on exported panels. - Defaults to ``export_include_scalebar``. - restore_state : bool, default True - If True, temporary position/frame overrides are reverted after export. - dpi : int, optional - Export DPI metadata. - - Returns - ------- - pathlib.Path - The written image path. - """ - from PIL import Image - - export_path = pathlib.Path(path) - view_key = self._validate_export_view(view) - fmt = self._resolve_export_format(export_path, format) - dpi_value = int(self.export_default_dpi if dpi is None else dpi) - overlays_enabled = ( - bool(self.export_include_overlays) - if include_overlays is None - else bool(include_overlays) - ) - scalebar_enabled = ( - bool(self.export_include_scalebar) - if include_scalebar is None - else bool(include_scalebar) - ) - - if dpi_value <= 0: - raise ValueError(f"dpi must be > 0, got {dpi_value}") - - export_path.parent.mkdir(parents=True, exist_ok=True) - - prev_row, prev_col = self.pos_row, self.pos_col - prev_frame = self.frame_idx - meta_path: pathlib.Path | None = None - export_row = int(self.pos_row) - export_col = int(self.pos_col) - export_frame = int(self.frame_idx) - - try: - if frame_idx is not None: - self.frame_idx = self._validate_frame_idx(frame_idx) - if position is not None: - row, col = self._validate_position(position) - self.pos_row = row - self.pos_col = col - export_row = int(self.pos_row) - export_col = int(self.pos_col) - export_frame = int(self.frame_idx) - - if view_key == "diffraction": - image, dp_meta = self._render_panel_image( - "diffraction", overlays_enabled, scalebar_enabled - ) - render_meta = {"diffraction": dp_meta} - elif view_key == "virtual": - image, vi_meta = self._render_panel_image( - "virtual", overlays_enabled, scalebar_enabled - ) - render_meta = {"virtual": vi_meta} - elif view_key == "fft": - image, fft_meta = self._render_panel_image( - "fft", overlays_enabled, scalebar_enabled - ) - render_meta = {"fft": fft_meta} - else: - panel_images = [] - render_meta = {} - dp_img, dp_meta = self._render_panel_image( - "diffraction", overlays_enabled, scalebar_enabled - ) - vi_img, vi_meta = self._render_panel_image( - "virtual", overlays_enabled, scalebar_enabled - ) - panel_images.extend([dp_img, vi_img]) - render_meta = {"diffraction": dp_meta, "virtual": vi_meta} - if self.show_fft: - fft_img, fft_meta = self._render_panel_image( - "fft", overlays_enabled, scalebar_enabled - ) - panel_images.append(fft_img) - render_meta["fft"] = fft_meta - image = self._compose_horizontal(panel_images) - - if fmt == "pdf": - Image.init() - image = image.convert("RGB") - image.save(export_path, format="PDF", resolution=dpi_value) - else: - image.save(export_path, format="PNG", dpi=(dpi_value, dpi_value)) + if fmt == "pdf": + Image.init() + image = image.convert("RGB") + image.save(export_path, format="PDF", resolution=dpi_value) + else: + image.save(export_path, format="PNG", dpi=(dpi_value, dpi_value)) if include_metadata: meta_path = ( @@ -3083,870 +1921,72 @@ def save_image( self.pos_row = prev_row self.pos_col = prev_col - self._record_export_event( - { - "export_kind": "single_view_image", - "view": view_key, - "format": fmt, - "position": {"row": export_row, "col": export_col}, - "frame_idx": export_frame, - "include_overlays": bool(overlays_enabled), - "include_scalebar": bool(scalebar_enabled), - "dpi": int(dpi_value), - "outputs": [ - self._build_file_record(export_path, metadata_path=meta_path), - ], - } - ) return export_path - def _build_preset_payload(self) -> dict[str, Any]: - return { - "detector": { - "center_row": float(self.center_row), - "center_col": float(self.center_col), - "bf_radius": float(self.bf_radius), - "roi_active": bool(self.roi_active), - "roi_mode": self.roi_mode, - "roi_center_row": float(self.roi_center_row), - "roi_center_col": float(self.roi_center_col), - "roi_radius": float(self.roi_radius), - "roi_radius_inner": float(self.roi_radius_inner), - "roi_width": float(self.roi_width), - "roi_height": float(self.roi_height), - }, - "vi_roi": { - "mode": self.vi_roi_mode, - "center_row": float(self.vi_roi_center_row), - "center_col": float(self.vi_roi_center_col), - "radius": float(self.vi_roi_radius), - "width": float(self.vi_roi_width), - "height": float(self.vi_roi_height), - }, - "display": { - "mask_dc": bool(self.mask_dc), - "dp_colormap": self.dp_colormap, - "vi_colormap": self.vi_colormap, - "fft_colormap": self.fft_colormap, - "dp_scale_mode": self.dp_scale_mode, - "vi_scale_mode": self.vi_scale_mode, - "fft_scale_mode": self.fft_scale_mode, - "dp_power_exp": float(self.dp_power_exp), - "vi_power_exp": float(self.vi_power_exp), - "fft_power_exp": float(self.fft_power_exp), - "dp_vmin_pct": float(self.dp_vmin_pct), - "dp_vmax_pct": float(self.dp_vmax_pct), - "vi_vmin_pct": float(self.vi_vmin_pct), - "vi_vmax_pct": float(self.vi_vmax_pct), - "fft_vmin_pct": float(self.fft_vmin_pct), - "fft_vmax_pct": float(self.fft_vmax_pct), - "fft_auto": bool(self.fft_auto), - "show_fft": bool(self.show_fft), - "dp_show_colorbar": bool(self.dp_show_colorbar), - "profile_line": self.profile_line, - "profile_width": int(self.profile_width), - }, - "export": self._export_settings_metadata(), - } - - def _apply_preset_payload(self, preset: dict[str, Any]) -> None: - detector = preset.get("detector", {}) - vi_roi = preset.get("vi_roi", {}) - display = preset.get("display", {}) - export = preset.get("export", {}) - - detector_map = { - "center_row": "center_row", - "center_col": "center_col", - "bf_radius": "bf_radius", - "roi_active": "roi_active", - "roi_mode": "roi_mode", - "roi_center_row": "roi_center_row", - "roi_center_col": "roi_center_col", - "roi_radius": "roi_radius", - "roi_radius_inner": "roi_radius_inner", - "roi_width": "roi_width", - "roi_height": "roi_height", - } - for key, trait_name in detector_map.items(): - if key in detector and hasattr(self, trait_name): - setattr(self, trait_name, detector[key]) - - vi_roi_map = { - "mode": "vi_roi_mode", - "center_row": "vi_roi_center_row", - "center_col": "vi_roi_center_col", - "radius": "vi_roi_radius", - "width": "vi_roi_width", - "height": "vi_roi_height", - } - for key, trait_name in vi_roi_map.items(): - if key in vi_roi and hasattr(self, trait_name): - setattr(self, trait_name, vi_roi[key]) - - _display_keys = { - "dp_colormap", "vi_colormap", "fft_colormap", - "dp_scale_mode", "vi_scale_mode", "fft_scale_mode", - "dp_power_exp", "vi_power_exp", "fft_power_exp", - "dp_vmin_pct", "dp_vmax_pct", "vi_vmin_pct", "vi_vmax_pct", - "fft_vmin_pct", "fft_vmax_pct", "fft_auto", - "mask_dc", "dp_show_colorbar", "show_fft", "fft_window", - "show_controls", - } - for key, value in display.items(): - if key in _display_keys: - setattr(self, key, value) - - export_map = { - "default_view": "export_default_view", - "default_format": "export_default_format", - "include_overlays": "export_include_overlays", - "include_scalebar": "export_include_scalebar", - "dpi": "export_default_dpi", - } - for key, trait_name in export_map.items(): - if key in export and hasattr(self, trait_name): - setattr(self, trait_name, export[key]) - - def save_preset( - self, - name: str, - path: str | pathlib.Path | None = None, - ) -> dict[str, Any]: - preset_name = str(name).strip() - if not preset_name: - raise ValueError("Preset name must be non-empty.") - preset_key = preset_name.lower() - - payload = self._build_preset_payload() - self._named_presets[preset_key] = payload - - if path is not None: - out_path = pathlib.Path(path) - out_path.parent.mkdir(parents=True, exist_ok=True) - serialized = { - **build_json_header("Show4DSTEM"), - "format": "json", - "export_kind": "widget_preset", - "preset_name": preset_name, - "preset": payload, - } - out_path.write_text(json.dumps(serialized, indent=2)) - - return payload - - def load_preset( - self, - name: str, - path: str | pathlib.Path | None = None, - apply: bool = True, - ) -> dict[str, Any]: - preset_name = str(name).strip() - preset_key = preset_name.lower() - if path is not None: - payload = json.loads(pathlib.Path(path).read_text()) - if not isinstance(payload, dict): - raise ValueError("Preset file must contain a JSON object.") - if "preset" in payload: - preset = payload["preset"] - else: - preset = payload - if not isinstance(preset, dict): - raise ValueError("Preset payload must be a JSON object.") - if preset_name: - self._named_presets[preset_key] = preset - else: - if preset_key not in self._named_presets: - raise ValueError( - f"Preset '{preset_name}' not found. Available: {', '.join(self.list_presets())}" - ) - preset = self._named_presets[preset_key] - - if apply: - self._apply_preset_payload(preset) - return preset - def apply_preset(self, name: str) -> Self: preset_name = str(name).strip().lower() - if preset_name == "bf": - self.roi_active = True - self.roi_mode = "circle" - self.roi_center_row = float(self.center_row) - self.roi_center_col = float(self.center_col) - self.roi_radius = float(max(1.0, self.bf_radius)) - return self - if preset_name == "abf": - self.roi_active = True - self.roi_mode = "annular" - self.roi_center_row = float(self.center_row) - self.roi_center_col = float(self.center_col) - self.roi_radius_inner = float(max(0.5, self.bf_radius * 0.5)) - self.roi_radius = float(max(1.0, self.bf_radius)) - return self - if preset_name == "adf": - self.roi_active = True - self.roi_mode = "annular" - self.roi_center_row = float(self.center_row) - self.roi_center_col = float(self.center_col) - self.roi_radius_inner = float(max(1.0, self.bf_radius)) - self.roi_radius = float(max(self.roi_radius_inner + 1.0, self.bf_radius * 2.0)) - return self - if preset_name == "haadf": - self.roi_active = True - self.roi_mode = "annular" - self.roi_center_row = float(self.center_row) - self.roi_center_col = float(self.center_col) - self.roi_radius_inner = float(max(1.0, self.bf_radius * 2.0)) - self.roi_radius = float(max(self.roi_radius_inner + 1.0, self.bf_radius * 4.0)) - return self - - self.load_preset(preset_name, apply=True) - return self - - def _resolve_figure_template(self, template: str) -> tuple[str, list[str], bool]: - key = str(template).strip().lower() - mapping = { - "dp_vi": (["diffraction", "virtual"], False), - "dp_vi_fft": (["diffraction", "virtual", "fft"], False), - "publication_dp_vi": (["diffraction", "virtual"], True), - "publication_dp_vi_fft": (["diffraction", "virtual", "fft"], True), - } - if key not in mapping: - raise ValueError( - f"Unsupported template '{template}'. " - f"Supported: {', '.join(self.list_figure_templates())}" - ) - panels, publication = mapping[key] - return key, panels, publication - - def save_figure( - self, - path: str | pathlib.Path, - template: str = "dp_vi_fft", - position: tuple[int, int] | None = None, - frame_idx: int | None = None, - format: str | None = None, - include_metadata: bool = True, - metadata_path: str | pathlib.Path | None = None, - include_overlays: bool | None = None, - include_scalebar: bool | None = None, - restore_state: bool = True, - dpi: int | None = None, - title: str | None = None, - annotations: dict[str, str] | None = None, - ) -> pathlib.Path: - from PIL import Image, ImageDraw, ImageFont - - export_path = pathlib.Path(path) - template_key, panel_keys, publication_style = self._resolve_figure_template(template) - fmt = self._resolve_export_format(export_path, format) - dpi_value = int(self.export_default_dpi if dpi is None else dpi) - overlays_enabled = ( - bool(self.export_include_overlays) - if include_overlays is None - else bool(include_overlays) - ) - scalebar_enabled = ( - bool(self.export_include_scalebar) - if include_scalebar is None - else bool(include_scalebar) - ) - if dpi_value <= 0: - raise ValueError(f"dpi must be > 0, got {dpi_value}") - - export_path.parent.mkdir(parents=True, exist_ok=True) - font = ImageFont.load_default() - - prev_row, prev_col = self.pos_row, self.pos_col - prev_frame = self.frame_idx - meta_path: pathlib.Path | None = None - + # Batch all trait writes atomically. Without this, each individual + # trait change fires _on_roi_change, and intermediate states (e.g. mode + # just switched to "annular" but radius_inner still stale from the + # previous preset) compute a wrong mask -> black VI flashes before the + # final correct frame. hold_trait_notifications defers observers until + # all 5 traits have committed. + bf = self.bf_radius + center_row = float(self.center_row) + center_col = float(self.center_col) + self._suppress_roi_recompute = True try: - if frame_idx is not None: - self.frame_idx = self._validate_frame_idx(frame_idx) - if position is not None: - row, col = self._validate_position(position) - self.pos_row = row - self.pos_col = col - - panel_images: list[Any] = [] - render_meta: dict[str, Any] = {} - for panel_key in panel_keys: - panel, panel_meta = self._render_panel_image( - panel_key, - include_overlays=overlays_enabled, - include_scalebar=scalebar_enabled, - ) - panel_images.append(panel) - render_meta[panel_key] = panel_meta - - gap = 24 if publication_style else 8 - padding = 24 if publication_style else 10 - label_height = 22 if publication_style else 0 - title_text = title - if title_text is None and publication_style: - if self.n_frames > 1: - title_text = f"4D-STEM Figure ({self.frame_dim_label} {self.frame_idx})" - else: - title_text = "4D-STEM Figure" - title_height = 34 if title_text else 0 - - max_panel_height = max(panel.height for panel in panel_images) - total_width = padding * 2 + sum(panel.width for panel in panel_images) + gap * (len(panel_images) - 1) - total_height = padding * 2 + title_height + label_height + max_panel_height - - figure = Image.new("RGB", (total_width, total_height), color=(255, 255, 255)) - draw = ImageDraw.Draw(figure, mode="RGBA") - - y_title = padding - if title_text: - draw.text((padding, y_title), title_text, fill=(0, 0, 0, 255), font=font) - - y_panels = padding + title_height - if publication_style: - y_panels += label_height - - panel_names = { - "diffraction": "Diffraction", - "virtual": "Virtual", - "fft": "FFT", - } - annotation_map = annotations or {} - - x0 = padding - for idx, panel in enumerate(panel_images): - panel_key = panel_keys[idx] - if publication_style: - draw.text( - (x0, padding + title_height), - panel_names.get(panel_key, panel_key), - fill=(0, 0, 0, 255), - font=font, - ) - - figure.paste(panel, (x0, y_panels)) - - if publication_style: - draw.rectangle( - [(x0, y_panels), (x0 + panel.width - 1, y_panels + panel.height - 1)], - outline=(80, 80, 80, 255), - width=1, - ) - - if panel_key in annotation_map and str(annotation_map[panel_key]).strip(): - text = str(annotation_map[panel_key]).strip() - text_bbox = draw.textbbox((0, 0), text, font=font) - text_w = text_bbox[2] - text_bbox[0] - text_h = text_bbox[3] - text_bbox[1] - tx = x0 + 8 - ty = y_panels + 8 - draw.rectangle( - [(tx - 4, ty - 3), (tx + text_w + 4, ty + text_h + 3)], - fill=(0, 0, 0, 180), - ) - draw.text((tx, ty), text, fill=(255, 255, 255, 255), font=font) - - x0 += panel.width + gap - - if fmt == "pdf": - Image.init() - figure = figure.convert("RGB") - figure.save(export_path, format="PDF", resolution=dpi_value) + if preset_name == "bf": + with self.hold_trait_notifications(): + self.roi_active = True + self.roi_mode = "circle" + self.roi_center_row = center_row + self.roi_center_col = center_col + self.roi_radius = float(max(1.0, bf)) + elif preset_name == "abf": + with self.hold_trait_notifications(): + self.roi_active = True + self.roi_mode = "annular" + self.roi_center_row = center_row + self.roi_center_col = center_col + self.roi_radius_inner = float(max(0.5, bf * 0.5)) + self.roi_radius = float(max(1.0, bf)) + elif preset_name == "adf": + with self.hold_trait_notifications(): + self.roi_active = True + self.roi_mode = "annular" + self.roi_center_row = center_row + self.roi_center_col = center_col + self.roi_radius_inner = float(max(1.0, bf)) + self.roi_radius = float(max(bf + 1.0, bf * 2.0)) + elif preset_name == "haadf": + with self.hold_trait_notifications(): + self.roi_active = True + self.roi_mode = "annular" + self.roi_center_row = center_row + self.roi_center_col = center_col + self.roi_radius_inner = float(max(1.0, bf * 2.0)) + self.roi_radius = float(max(bf * 2.0 + 1.0, bf * 4.0)) else: - figure.save(export_path, format="PNG", dpi=(dpi_value, dpi_value)) - - if include_metadata: - meta_path = ( - pathlib.Path(metadata_path) - if metadata_path is not None - else export_path.with_suffix(".json") - ) - metadata = self._build_image_export_metadata( - export_path=export_path, - view_key="figure", - fmt=fmt, - render_meta=render_meta, - include_overlays=overlays_enabled, - include_scalebar=scalebar_enabled, - export_kind="figure_template", - extra={ - "template": template_key, - "panels": panel_keys, - "publication_style": bool(publication_style), - "title": title_text or "", - "annotations": annotation_map, - "dpi": int(dpi_value), - }, - ) - meta_path.write_text(json.dumps(metadata, indent=2)) - finally: - if restore_state: - self.frame_idx = prev_frame - self.pos_row = prev_row - self.pos_col = prev_col - - self._record_export_event( - { - "export_kind": "figure_template", - "template": template_key, - "format": fmt, - "dpi": int(dpi_value), - "include_overlays": bool(overlays_enabled), - "include_scalebar": bool(scalebar_enabled), - "outputs": [ - self._build_file_record(export_path, metadata_path=meta_path), - ], - } - ) - return export_path - - def _resolve_frame_sequence( - self, - frame_indices: list[int] | None, - frame_range: tuple[int, int] | None, - ) -> list[int]: - if frame_indices is not None and frame_range is not None: - raise ValueError("Use either frame_indices or frame_range, not both.") - - if frame_indices is not None: - if len(frame_indices) == 0: - raise ValueError("frame_indices cannot be empty.") - return [self._validate_frame_idx(idx) for idx in frame_indices] - - if frame_range is not None: - if len(frame_range) != 2: - raise ValueError("frame_range must be a (start, end) tuple.") - start, end = int(frame_range[0]), int(frame_range[1]) - if start > end: - raise ValueError("frame_range start must be <= end.") - return [self._validate_frame_idx(idx) for idx in range(start, end + 1)] - - return [int(i) for i in range(self.n_frames)] - - def _resolve_position_sequence( - self, - mode: str, - path_points: list[tuple[int, int]] | None, - raster_step: int, - raster_bidirectional: bool, - ) -> list[tuple[int, int]]: - if mode == "path": - points = self._path_points if path_points is None else path_points - if not points: - raise ValueError( - "Path mode requires points via set_path(...) or path_points=..." - ) - return [self._validate_position((int(r), int(c))) for r, c in points] - - if mode == "raster": - step = int(raster_step) - if step < 1: - raise ValueError("raster_step must be >= 1") - points: list[tuple[int, int]] = [] - for r in range(0, self.shape_rows, step): - cols = list(range(0, self.shape_cols, step)) - if raster_bidirectional and ((r // step) % 2 == 1): - cols.reverse() - for c in cols: - points.append((int(r), int(c))) - return points - - raise ValueError(f"Unsupported position sequence mode '{mode}'") - - def suggest_adaptive_path( - self, - coarse_step: int = 4, - target_fraction: float = 0.25, - min_spacing: int = 2, - include_coarse: bool = True, - coarse_bidirectional: bool = True, - local_window: int = 5, - dose_lambda: float = 0.25, - weights: dict[str, float] | None = None, - roi_mask: np.ndarray | None = None, - update_widget_path: bool = True, - interval_ms: int | None = None, - loop: bool = False, - autoplay: bool = False, - return_maps: bool = False, - ) -> dict[str, Any]: - """ - Suggest a sparse adaptive scan path using coarse-to-fine utility ranking. - - The planner computes utility from current virtual-image and diffraction - statistics, then selects spatially distributed high-utility points. - - Parameters - ---------- - coarse_step : int, default 4 - Spacing of the initial coarse grid. - target_fraction : float, default 0.25 - Target total sampled fraction of scan positions in (0, 1]. - min_spacing : int, default 2 - Minimum pixel spacing between selected dense points. - include_coarse : bool, default True - If True, include coarse-grid points in the returned path. - coarse_bidirectional : bool, default True - Use snake ordering for coarse-grid traversal. - local_window : int, default 5 - Window size for local-std utility component. - dose_lambda : float, default 0.25 - Penalty weight for re-sampling coarse points. - weights : dict[str, float], optional - Utility weights for keys: ``vi_gradient``, ``vi_local_std``, ``dp_variance``. - roi_mask : np.ndarray, optional - Optional boolean mask of shape ``scan_shape`` restricting dense picks. - update_widget_path : bool, default True - If True, calls ``set_path(...)`` with the suggested path. - interval_ms : int, optional - Path interval when ``update_widget_path=True``. - loop : bool, default False - Path looping behavior when ``update_widget_path=True``. - autoplay : bool, default False - Start playback immediately when ``update_widget_path=True``. - return_maps : bool, default False - If True, include utility component maps in the returned dict. - - Returns - ------- - dict - Planning result with coarse points, dense points, and final path. - """ - step = int(coarse_step) - if step < 1: - raise ValueError(f"coarse_step must be >= 1, got {coarse_step}") - - frac = float(target_fraction) - if frac <= 0 or frac > 1: - raise ValueError(f"target_fraction must be in (0, 1], got {target_fraction}") - - spacing = int(min_spacing) - if spacing < 0: - raise ValueError(f"min_spacing must be >= 0, got {min_spacing}") - - if local_window < 1: - raise ValueError(f"local_window must be >= 1, got {local_window}") - - if not np.isfinite(float(dose_lambda)): - raise ValueError("dose_lambda must be finite") - - default_weights = { - "vi_gradient": 0.4, - "vi_local_std": 0.3, - "dp_variance": 0.3, - } - merged_weights = dict(default_weights) - if weights is not None: - for key, value in weights.items(): - if key not in default_weights: - raise ValueError( - f"Unsupported utility weight '{key}'. " - f"Supported: {', '.join(default_weights.keys())}" - ) - merged_weights[key] = float(value) - - weight_sum = sum(max(0.0, float(v)) for v in merged_weights.values()) - if weight_sum <= 0: - raise ValueError("At least one utility weight must be > 0.") - normalized_weights = { - key: max(0.0, float(value)) / weight_sum - for key, value in merged_weights.items() - } - - n_total = int(self.shape_rows * self.shape_cols) - target_count = int(max(1, round(frac * n_total))) - - coarse_points = self._build_coarse_points(step=step, bidirectional=bool(coarse_bidirectional)) - coarse_count = len(coarse_points) if include_coarse else 0 - if include_coarse and target_count < coarse_count: - raise ValueError( - f"target_fraction={target_fraction} gives {target_count} points, " - f"but coarse grid already has {coarse_count}. " - "Increase target_fraction or coarse_step." - ) - dense_count = target_count - coarse_count if include_coarse else target_count - dense_count = max(0, int(dense_count)) - - vi = self._get_virtual_image_array().astype(np.float32, copy=False) - grad_row, grad_col = np.gradient(vi) - vi_gradient = np.hypot(grad_row, grad_col).astype(np.float32) - - mean_local = self._box_mean_map(vi, local_window) - mean_sq_local = self._box_mean_map(vi * vi, local_window) - variance_local = np.maximum(mean_sq_local - mean_local * mean_local, 0.0) - vi_local_std = np.sqrt(variance_local).astype(np.float32) - - dp_variance = self._dp_variance_map() - - grad_score = self._normalize_score_map(vi_gradient) - local_std_score = self._normalize_score_map(vi_local_std) - dp_var_score = self._normalize_score_map(dp_variance) - - utility = ( - normalized_weights["vi_gradient"] * grad_score - + normalized_weights["vi_local_std"] * local_std_score - + normalized_weights["dp_variance"] * dp_var_score - ).astype(np.float32) - - dose_penalty = np.zeros_like(utility, dtype=np.float32) - for row, col in coarse_points: - dose_penalty[int(row), int(col)] = 1.0 - utility = utility - float(dose_lambda) * dose_penalty - - allowed_mask = np.ones((self.shape_rows, self.shape_cols), dtype=bool) - if roi_mask is not None: - mask = np.asarray(roi_mask) - if mask.shape != (self.shape_rows, self.shape_cols): raise ValueError( - f"roi_mask shape {mask.shape} does not match scan_shape " - f"({self.shape_rows}, {self.shape_cols})" - ) - allowed_mask &= mask.astype(bool) - - excluded_mask = np.zeros_like(allowed_mask, dtype=bool) - for row, col in coarse_points: - excluded_mask[int(row), int(col)] = True - - dense_points = self._select_spaced_topk( - scores=utility, - k=dense_count, - min_spacing=spacing, - allowed_mask=allowed_mask, - excluded_mask=excluded_mask, - ) - - start_point = coarse_points[-1] if include_coarse and coarse_points else None - dense_path = self._nearest_neighbor_order(dense_points, start=start_point) - path_points = list(coarse_points) + dense_path if include_coarse else dense_path - - if update_widget_path and path_points: - interval_value = int(self.path_interval_ms if interval_ms is None else interval_ms) - if interval_value < 1: - raise ValueError(f"interval_ms must be >= 1, got {interval_value}") - self.set_path( - points=path_points, - interval_ms=interval_value, - loop=bool(loop), - autoplay=bool(autoplay), - ) - - result: dict[str, Any] = { - "target_fraction": float(frac), - "target_count": int(target_count), - "coarse_step": int(step), - "coarse_count": int(len(coarse_points)), - "dense_count": int(len(dense_points)), - "path_count": int(len(path_points)), - "weights": normalized_weights, - "dose_lambda": float(dose_lambda), - "coarse_points": coarse_points, - "dense_points": dense_points, - "path_points": path_points, - "selected_fraction": float(len(path_points) / max(1, n_total)), - } - if return_maps: - result["utility_map"] = utility - result["utility_components"] = { - "vi_gradient": grad_score, - "vi_local_std": local_std_score, - "dp_variance": dp_var_score, - "dose_penalty": dose_penalty, - } - - self._record_export_event( - { - "export_kind": "adaptive_path_suggestion", - "target_fraction": float(frac), - "target_count": int(target_count), - "coarse_step": int(step), - "coarse_count": int(len(coarse_points)), - "dense_count": int(len(dense_points)), - "path_count": int(len(path_points)), - "selected_fraction": float(len(path_points) / max(1, n_total)), - "weights": normalized_weights, - "dose_lambda": float(dose_lambda), - } - ) - return result - - def save_sequence( - self, - output_dir: str | pathlib.Path, - mode: str = "path", - view: str | None = None, - format: str | None = None, - include_metadata: bool = True, - include_overlays: bool | None = None, - include_scalebar: bool | None = None, - frame_idx: int | None = None, - position: tuple[int, int] | None = None, - path_points: list[tuple[int, int]] | None = None, - raster_step: int = 1, - raster_bidirectional: bool = False, - frame_indices: list[int] | None = None, - frame_range: tuple[int, int] | None = None, - filename_prefix: str | None = None, - manifest_name: str = "save_sequence_manifest.json", - restore_state: bool = True, - dpi: int | None = None, - ) -> pathlib.Path: - output_root = pathlib.Path(output_dir) - output_root.mkdir(parents=True, exist_ok=True) - mode_key = str(mode).strip().lower() - if mode_key not in {"path", "raster", "frames"}: - raise ValueError("mode must be one of: path, raster, frames") - - view_key = self._validate_export_view(view) - fmt = self._resolve_export_format(pathlib.Path(f"sequence.{self.export_default_format}"), format or self.export_default_format) - dpi_value = int(self.export_default_dpi if dpi is None else dpi) - overlays_enabled = ( - bool(self.export_include_overlays) - if include_overlays is None - else bool(include_overlays) - ) - scalebar_enabled = ( - bool(self.export_include_scalebar) - if include_scalebar is None - else bool(include_scalebar) - ) - if dpi_value <= 0: - raise ValueError(f"dpi must be > 0, got {dpi_value}") - - export_rows: list[dict[str, Any]] = [] - prefix = ( - str(filename_prefix).strip() - if filename_prefix is not None and str(filename_prefix).strip() - else f"{mode_key}_{view_key}" - ) - - prev_row, prev_col = self.pos_row, self.pos_col - prev_frame = self.frame_idx - frame_for_paths = self._validate_frame_idx(frame_idx) if frame_idx is not None else int(self.frame_idx) - - if mode_key == "frames": - row, col = self._validate_position(position) - frames = self._resolve_frame_sequence(frame_indices, frame_range) - jobs = [ - {"row": int(row), "col": int(col), "frame_idx": int(fi)} - for fi in frames - ] - else: - positions = self._resolve_position_sequence( - mode=mode_key, - path_points=path_points, - raster_step=raster_step, - raster_bidirectional=raster_bidirectional, - ) - jobs = [ - {"row": int(r), "col": int(c), "frame_idx": int(frame_for_paths)} - for r, c in positions - ] - - try: - for idx, job in enumerate(jobs): - row = int(job["row"]) - col = int(job["col"]) - fr = int(job["frame_idx"]) - basename = ( - f"{prefix}_{idx:04d}_f{fr:04d}_r{row:04d}_c{col:04d}.{fmt}" - ) - out_path = output_root / basename - out_meta = out_path.with_suffix(".json") if include_metadata else None - - self.save_image( - out_path, - view=view_key, - position=(row, col), - frame_idx=fr, - format=fmt, - include_metadata=include_metadata, - metadata_path=out_meta, - include_overlays=overlays_enabled, - include_scalebar=scalebar_enabled, - restore_state=False, - dpi=dpi_value, + f"Unknown preset {name!r}. Choices: 'bf', 'abf', 'adf', 'haadf'." ) - - record = { - "index": int(idx), - "row": row, - "col": col, - "frame_idx": fr, - } - record.update(self._build_file_record(out_path, metadata_path=out_meta, index=idx)) - export_rows.append(record) finally: - if restore_state: - self.frame_idx = prev_frame - self.pos_row = prev_row - self.pos_col = prev_col - - manifest_path = output_root / str(manifest_name) - manifest_payload = { - **build_json_header("Show4DSTEM"), - "format": "json", - "export_kind": "sequence_batch", - "mode": mode_key, - "view": view_key, - "image_format": fmt, - "output_dir": str(output_root), - "filename_prefix": prefix, - "n_exports": int(len(export_rows)), - "include_overlays": bool(overlays_enabled), - "include_scalebar": bool(scalebar_enabled), - "dpi": int(dpi_value), - "scan_shape": {"rows": int(self.shape_rows), "cols": int(self.shape_cols)}, - "detector_shape": {"rows": int(self.det_rows), "cols": int(self.det_cols)}, - "exports": export_rows, - } - manifest_path.write_text(json.dumps(manifest_payload, indent=2)) - - manifest_record = self._build_file_record(manifest_path) - self._record_export_event( - { - "export_kind": "sequence_batch", - "mode": mode_key, - "view": view_key, - "format": fmt, - "n_exports": int(len(export_rows)), - "include_overlays": bool(overlays_enabled), - "include_scalebar": bool(scalebar_enabled), - "dpi": int(dpi_value), - "outputs": [manifest_record], - } - ) - return manifest_path + self._suppress_roi_recompute = False + # Single recompute with final, consistent state. + self._compute_virtual_image_from_roi() + return self - def save_reproducibility_report( - self, - path: str | pathlib.Path, - ) -> pathlib.Path: - report_path = pathlib.Path(path) - report_path.parent.mkdir(parents=True, exist_ok=True) - payload = { - **build_json_header("Show4DSTEM"), - "format": "json", - "export_kind": "reproducibility_report", - "session_id": self._export_session_id, - "session_started_utc": self._export_session_started_utc, - "report_generated_utc": datetime.now(timezone.utc).isoformat(), - "scan_shape": {"rows": int(self.shape_rows), "cols": int(self.shape_cols)}, - "detector_shape": {"rows": int(self.det_rows), "cols": int(self.det_cols)}, - "n_exports": int(len(self._export_log)), - "exports": self._export_log, - } - report_path.write_text(json.dumps(payload, indent=2)) - return report_path def _normalize_frame(self, frame: np.ndarray) -> np.ndarray: mode = self.dp_scale_mode - scaled = self._apply_scale_mode(frame, mode, self.dp_power_exp) + scaled = self._apply_scale_mode(frame, mode) if self.dp_vmin is not None and self.dp_vmax is not None: fmin = float(self._apply_scale_mode( - np.array([max(self.dp_vmin, 0)], dtype=np.float32), mode, self.dp_power_exp + np.array([max(self.dp_vmin, 0)], dtype=np.float32), mode )[0]) fmax = float(self._apply_scale_mode( - np.array([max(self.dp_vmax, 0)], dtype=np.float32), mode, self.dp_power_exp + np.array([max(self.dp_vmax, 0)], dtype=np.float32), mode )[0]) else: fmin = float(scaled.min()) @@ -4038,31 +2078,14 @@ def _update_frame(self, change=None): else: frame = data[self.pos_row, self.pos_col] - # Compute stats from frame (optionally mask DC component) - if self.mask_dc and self.det_rows > 3 and self.det_cols > 3: - # Mask center 3x3 region for stats using detected center (not geometric center) - cr = int(round(self.center_row)) - cc = int(round(self.center_col)) - cr = max(1, min(self.det_rows - 2, cr)) - cc = max(1, min(self.det_cols - 2, cc)) - mask = torch.ones_like(frame, dtype=torch.bool) - mask[cr-1:cr+2, cc-1:cc+2] = False - masked_vals = frame[mask] - self.dp_stats = [ - float(masked_vals.mean()), - float(masked_vals.min()), - float(masked_vals.max()), - float(masked_vals.std()), - ] - else: - self.dp_stats = [ - float(frame.mean()), - float(frame.min()), - float(frame.max()), - float(frame.std()), - ] - - # Convert to numpy only for sending bytes to frontend + # Cast small frame to float32 for stats and JS transfer. Bulk data + # stays in native dtype; only this single 192×192 (~144 KB) frame + # gets promoted. + if frame.dtype != torch.float32: + frame = frame.float() + # Stats compute moved to JS (frontend has frame_bytes; computeStats() in + # js/stats.ts does mean/min/max/std on the Float32Array directly, + # avoiding 4 sync trait round-trips per scan-position click). self.frame_bytes = frame.cpu().numpy().tobytes() def _on_roi_change(self, change=None): @@ -4072,6 +2095,8 @@ def _on_roi_change(self, change=None): """ if not self.roi_active: return + if getattr(self, "_suppress_roi_recompute", False): + return self._compute_virtual_image_from_roi() def _on_roi_center_change(self, change=None): @@ -4082,6 +2107,8 @@ def _on_roi_center_change(self, change=None): """ if not self.roi_active: return + if getattr(self, "_suppress_roi_recompute", False): + return if change and "new" in change: row, col = change["new"] # Sync to individual traits (without triggering _on_roi_change observers) @@ -4091,19 +2118,36 @@ def _on_roi_center_change(self, change=None): self.observe(self._on_roi_change, names=["roi_center_col", "roi_center_row"]) self._compute_virtual_image_from_roi() + def _on_vi_roi_center_change(self, change=None): + """Apply compound (row, col) update atomically (avoids split-trait race).""" + if change and "new" in change: + row, col = change["new"] + self.unobserve(self._on_vi_roi_change, names=["vi_roi_center_row", "vi_roi_center_col"]) + self.vi_roi_center_row = float(row) + self.vi_roi_center_col = float(col) + self.observe(self._on_vi_roi_change, names=["vi_roi_center_row", "vi_roi_center_col"]) + if self.vi_roi_mode == "off": + self.vi_roi_dp_bytes = b"" + return + self._compute_vi_roi_dp() + def _on_vi_roi_change(self, change=None): - """Compute summed DP when VI ROI changes.""" + """Recompute reduced DP when VI ROI or reduction changes.""" if self.vi_roi_mode == "off": - self.summed_dp_bytes = b"" - self.summed_dp_count = 0 + self.vi_roi_dp_bytes = b"" return - self._compute_summed_dp_from_vi_roi() + self._compute_vi_roi_dp() + + def _compute_vi_roi_dp(self): + """Reduce diffraction patterns over scan positions inside VI ROI. - def _compute_summed_dp_from_vi_roi(self): - """Sum diffraction patterns from positions inside VI ROI (PyTorch).""" + Reduction selected by `vi_roi_reduce`: + - "mean": average DP (size-invariant, default for region-of-interest analysis) + - "sum": total counts (scales with ROI area; use for quantitative integration) + - "max": brightest pixel per detector position across the region + """ if self._data is None: return - # Create mask in scan space using cached coordinates if self.vi_roi_mode == "circle": mask = (self._scan_row_coords - self.vi_roi_center_row) ** 2 + (self._scan_col_coords - self.vi_roi_center_col) ** 2 <= self.vi_roi_radius ** 2 elif self.vi_roi_mode == "square": @@ -4116,27 +2160,41 @@ def _compute_summed_dp_from_vi_roi(self): else: return - # Count positions in mask n_positions = int(mask.sum()) if n_positions == 0: - self.summed_dp_bytes = b"" - self.summed_dp_count = 0 + self.vi_roi_dp_bytes = b"" return - self.summed_dp_count = n_positions - - # Compute average DP using masked sum (vectorized) + reduce = self.vi_roi_reduce data = self._frame_data - if data.ndim == 4: - # (scan_rows, scan_cols, det_rows, det_cols) - sum over masked scan positions - avg_dp = data[mask].mean(dim=0) - else: - # Flattened: (N, det_rows, det_cols) - need to convert mask indices - flat_indices = torch.nonzero(mask.flatten(), as_tuple=True)[0] - avg_dp = data[flat_indices].mean(dim=0) + # Single chunked torch path. For each scan-row chunk: cast to float32 and + # broadcast-multiply by the mask (no `chunk[row_mask]` slab, which would + # roughly duplicate the chunk in memory when the mask is dense). Sum/mean + # use einsum over scan dims; max masks zero rows then takes amax. + data_4d = data if data.ndim == 4 else data.reshape(self._scan_shape[0], self._scan_shape[1], *self._det_shape) + rows_per_chunk = self._chunk_rows() + if reduce == "sum" or reduce == "mean": + dp = torch.zeros(self._det_shape, dtype=torch.float32, device=self._device) + else: # max + dp = torch.full(self._det_shape, -float("inf"), dtype=torch.float32, device=self._device) + for i in range(0, self._scan_shape[0], rows_per_chunk): + row_mask = mask[i:i + rows_per_chunk] + if not bool(row_mask.any()): + continue + chunk = data_4d[i:i + rows_per_chunk] + if not torch.is_floating_point(chunk): + chunk = chunk.float() + row_mask_f = row_mask.float() + if reduce == "max": + # Outside-mask positions become 0; doesn't affect amax provided + # the data has any non-negative pixels (true for detector counts). + dp = torch.maximum(dp, (chunk * row_mask_f[..., None, None]).amax(dim=(0, 1))) + else: + dp += torch.einsum("rcij,rc->ij", chunk, row_mask_f) + if reduce == "mean": + dp /= float(n_positions) - # Send raw float32 (consistent with other data paths — JS handles normalization) - self.summed_dp_bytes = avg_dp.cpu().numpy().tobytes() + self.vi_roi_dp_bytes = dp.cpu().numpy().tobytes() def _create_circular_mask(self, cx: float, cy: float, radius: float): """Create circular mask (boolean tensor on device).""" @@ -4162,31 +2220,24 @@ def _create_rect_mask(self, cx: float, cy: float, half_width: float, half_height return mask def _precompute_common_virtual_images(self): - """Pre-compute BF/ABF/ADF virtual images for instant preset switching.""" + """Pre-compute BF/ABF/ADF/HAADF virtual image bytes. Annular ranges match + apply_preset() so the cache always hits on preset clicks.""" cx, cy, bf = self.center_col, self.center_row, self.bf_radius - # Cache (bytes, stats, min, max) for each preset - bf_arr = self._fast_masked_sum(self._create_circular_mask(cx, cy, bf)) - abf_arr = self._fast_masked_sum(self._create_annular_mask(cx, cy, bf * 0.5, bf)) - adf_arr = self._fast_masked_sum(self._create_annular_mask(cx, cy, bf, bf * 4.0)) - - self._cached_bf_virtual = ( - self._to_float32_bytes(bf_arr, update_vi_stats=False), - [float(bf_arr.mean()), float(bf_arr.min()), float(bf_arr.max()), float(bf_arr.std())], - float(bf_arr.min()), float(bf_arr.max()) + self._cached_bf_virtual = self._to_float32_bytes( + self._fast_masked_sum(self._create_circular_mask(cx, cy, bf)) + ) + self._cached_abf_virtual = self._to_float32_bytes( + self._fast_masked_sum(self._create_annular_mask(cx, cy, bf * 0.5, bf)) ) - self._cached_abf_virtual = ( - self._to_float32_bytes(abf_arr, update_vi_stats=False), - [float(abf_arr.mean()), float(abf_arr.min()), float(abf_arr.max()), float(abf_arr.std())], - float(abf_arr.min()), float(abf_arr.max()) + self._cached_adf_virtual = self._to_float32_bytes( + self._fast_masked_sum(self._create_annular_mask(cx, cy, bf, bf * 2.0)) ) - self._cached_adf_virtual = ( - self._to_float32_bytes(adf_arr, update_vi_stats=False), - [float(adf_arr.mean()), float(adf_arr.min()), float(adf_arr.max()), float(adf_arr.std())], - float(adf_arr.min()), float(adf_arr.max()) + self._cached_haadf_virtual = self._to_float32_bytes( + self._fast_masked_sum(self._create_annular_mask(cx, cy, bf * 2.0, bf * 4.0)) ) - def _get_cached_preset(self) -> tuple[bytes, list[float], float, float] | None: - """Check if current ROI matches a cached preset and return (bytes, stats, min, max) tuple.""" + def _get_cached_preset(self) -> bytes | None: + """Return cached preset bytes if current ROI matches BF/ABF/ADF preset shape.""" # Must be centered on detector center if abs(self.roi_center_col - self.center_col) >= 1 or abs(self.roi_center_row - self.center_row) >= 1: return None @@ -4203,16 +2254,25 @@ def _get_cached_preset(self) -> tuple[bytes, list[float], float, float] | None: abs(self.roi_radius - bf) < 1): return self._cached_abf_virtual - # ADF: annular at bf to 4*bf (combines LAADF + HAADF) + # ADF: annular at bf to 2*bf if (self.roi_mode == "annular" and abs(self.roi_radius_inner - bf) < 1 and - abs(self.roi_radius - bf * 4.0) < 1): + abs(self.roi_radius - bf * 2.0) < 1): return self._cached_adf_virtual + # HAADF: annular at 2*bf to 4*bf + if (self.roi_mode == "annular" and + abs(self.roi_radius_inner - bf * 2.0) < 1 and + abs(self.roi_radius - bf * 4.0) < 1): + return self._cached_haadf_virtual + return None def _virtual_image_for_frame(self, frame_idx: int) -> np.ndarray: - """Compute virtual image array for a specific frame without mutating traits.""" + """Compute virtual image for a specific 5D frame without mutating traits. + + Single chunked-torch path matching _fast_masked_sum. + """ data = self._data[frame_idx] if self.n_frames > 1 else self._data cx, cy = self.roi_center_col, self.roi_center_row if self.roi_mode == "circle" and self.roi_radius > 0: @@ -4231,68 +2291,65 @@ def _virtual_image_for_frame(self, frame_idx: int) -> np.ndarray: else: vi = data[:, row, col].reshape(self._scan_shape) return vi.cpu().numpy().astype(np.float32, copy=False) - mask_float = mask.float() - n_det = self._det_shape[0] * self._det_shape[1] - n_nonzero = int(mask.sum()) - coverage = n_nonzero / n_det - if coverage < SPARSE_MASK_THRESHOLD: - indices = torch.nonzero(mask_float.flatten(), as_tuple=True)[0] - n_scan = self._scan_shape[0] * self._scan_shape[1] - data_flat = data.reshape(n_scan, n_det) - result = data_flat[:, indices].sum(dim=1).reshape(self._scan_shape) - else: - if data.ndim == 3: - data_4d = data.reshape(self._scan_shape[0], self._scan_shape[1], *self._det_shape) - else: - data_4d = data - result = torch.tensordot(data_4d, mask_float, dims=([2, 3], [0, 1])) - return result.cpu().numpy().astype(np.float32, copy=False) + data_4d = data if data.ndim == 4 else data.reshape(self._scan_shape[0], self._scan_shape[1], *self._det_shape) + mask_f = mask.float() + rows_per_chunk = self._chunk_rows() + out = torch.zeros(self._scan_shape, dtype=torch.float32, device=self._device) + for i in range(0, data_4d.shape[0], rows_per_chunk): + chunk = data_4d[i:i + rows_per_chunk] + if not torch.is_floating_point(chunk): + chunk = chunk.float() + out[i:i + rows_per_chunk] = torch.tensordot(chunk, mask_f, dims=([2, 3], [0, 1])) + return out.cpu().numpy().astype(np.float32, copy=False) + + def _chunk_rows(self) -> int: + """Pick rows-per-chunk so float32 transient stays under _CHUNK_BYTE_BUDGET. + + Float32 cast of one chunk = rows × scan_cols × det_h × det_w × 4 bytes. + Selected slabs (e.g. vi_roi reduce) inherit the same per-row budget. + """ + per_row = self._scan_shape[1] * self._det_shape[0] * self._det_shape[1] * 4 + return max(1, _CHUNK_BYTE_BUDGET // max(1, per_row)) def _fast_masked_sum(self, mask: torch.Tensor) -> torch.Tensor: - """Compute masked sum using PyTorch. - - Uses sparse indexing for small masks (<20% coverage) which is faster - because it only processes non-zero pixels: - - r=10 (1%): ~0.8ms (sparse) vs ~13ms (full) - - r=30 (8%): ~4ms (sparse) vs ~13ms (full) + """Sum data over scan positions weighted by detector mask. - For large masks (≥20%), uses full tensordot which has constant ~13ms. + Chunked tensordot. Per-chunk float32 cast bounded by _CHUNK_BYTE_BUDGET. + Identical math on CUDA / MPS / CPU. """ data = self._frame_data - mask_float = mask.float() - n_det = self._det_shape[0] * self._det_shape[1] - n_nonzero = int(mask.sum()) - coverage = n_nonzero / n_det - - if coverage < SPARSE_MASK_THRESHOLD: - # Sparse: faster for small masks - indices = torch.nonzero(mask_float.flatten(), as_tuple=True)[0] - n_scan = self._scan_shape[0] * self._scan_shape[1] - data_flat = data.reshape(n_scan, n_det) - result = data_flat[:, indices].sum(dim=1).reshape(self._scan_shape) + if data.ndim == 3: + data_4d = data.reshape(self._scan_shape[0], self._scan_shape[1], *self._det_shape) else: - # Tensordot: faster for large masks - # Reshape to 4D if needed (3D flattened data) - if data.ndim == 3: - data_4d = data.reshape(self._scan_shape[0], self._scan_shape[1], *self._det_shape) - else: - data_4d = data - result = torch.tensordot(data_4d, mask_float, dims=([2, 3], [0, 1])) - - return result - - def _to_float32_bytes(self, arr: torch.Tensor, update_vi_stats: bool = True) -> bytes: - """Convert tensor to float32 bytes.""" - # Compute min/max (fast on GPU) - vmin = float(arr.min()) - vmax = float(arr.max()) + data_4d = data + # Single chunked torch path. Per scan-row chunk: cast to float32, contract + # with mask via tensordot. Transient memory bounded by chunk size. Same + # code on CUDA / MPS / CPU. Identical results regardless of device. + mask_f = mask.float() + n_rows = data_4d.shape[0] + out = torch.zeros(self._scan_shape, dtype=torch.float32, device=self._device) + # Convert positions chunk size to row chunks based on scan width. + rows_per_chunk = self._chunk_rows() + for i in range(0, n_rows, rows_per_chunk): + chunk = data_4d[i:i + rows_per_chunk] + if not torch.is_floating_point(chunk): + chunk = chunk.float() + out[i:i + rows_per_chunk] = torch.tensordot(chunk, mask_f, dims=([2, 3], [0, 1])) + return out - # Only update traits when requested (avoids side effects during precomputation) - if update_vi_stats: - self.vi_data_min = vmin - self.vi_data_max = vmax - self.vi_stats = [float(arr.mean()), vmin, vmax, float(arr.std())] + def _to_float32_bytes(self, arr: torch.Tensor) -> bytes: + """Convert tensor (any numeric dtype) to float32 bytes for JS transfer. + Cast to float32 only at the small output. Integer reductions (uint16 sums, + int64 accumulators) get promoted here so the multi-GB raw data never gets + copied to float. Stats (min/max/mean/std) are computed JS-side from the + same Float32Array — keeping them out of separate traits avoids a + comm-message ordering race where bytes from click N arrive with stats + from click N-1, producing a wrong colormap normalization (uniform white + flash on rapid preset switching). + """ + if arr.dtype != torch.float32: + arr = arr.float() return arr.cpu().numpy().tobytes() def _compute_virtual_image_from_roi(self): @@ -4301,12 +2358,7 @@ def _compute_virtual_image_from_roi(self): return cached = self._get_cached_preset() if cached is not None: - # Cached preset returns (bytes, stats, min, max) tuple - vi_bytes, vi_stats, vi_min, vi_max = cached - self.virtual_image_bytes = vi_bytes - self.vi_stats = vi_stats - self.vi_data_min = vi_min - self.vi_data_max = vi_max + self.virtual_image_bytes = cached return cx, cy = self.roi_center_col, self.roi_center_row @@ -4333,5 +2385,3 @@ def _compute_virtual_image_from_roi(self): self.virtual_image_bytes = self._to_float32_bytes(self._fast_masked_sum(mask)) - -bind_tool_runtime_api(Show4DSTEM, "Show4DSTEM") diff --git a/widget/src/quantem/widget/json_state.py b/widget/src/quantem/widget/state.py similarity index 96% rename from widget/src/quantem/widget/json_state.py rename to widget/src/quantem/widget/state.py index 4874981f..d7710287 100644 --- a/widget/src/quantem/widget/json_state.py +++ b/widget/src/quantem/widget/state.py @@ -12,8 +12,6 @@ def resolve_widget_version() -> str: return importlib.metadata.version("quantem-widget") except importlib.metadata.PackageNotFoundError: return "unknown" - except Exception: - return "unknown" def build_json_header(widget_name: str) -> dict[str, Any]: diff --git a/widget/src/quantem/widget/tool_parity.json b/widget/src/quantem/widget/tool_parity.json deleted file mode 100644 index 4271533a..00000000 --- a/widget/src/quantem/widget/tool_parity.json +++ /dev/null @@ -1,93 +0,0 @@ -{ - "widgets": { - "Show2D": { - "tool_groups": ["display", "histogram", "stats", "navigation", "view", "export", "roi", "profile", "all"], - "aliases": {} - }, - "Show3D": { - "tool_groups": ["display", "histogram", "stats", "playback", "view", "export", "roi", "profile", "all"], - "aliases": { - "navigation": "playback" - } - }, - "Show3DVolume": { - "tool_groups": ["display", "histogram", "playback", "fft", "navigation", "stats", "export", "view", "volume", "all"], - "aliases": {} - }, - "Show4D": { - "tool_groups": ["display", "roi", "histogram", "profile", "navigation", "playback", "stats", "export", "view", "fft", "all"], - "aliases": {} - }, - "Show4DSTEM": { - "tool_groups": ["display", "histogram", "stats", "navigation", "playback", "view", "export", "roi", "profile", "fft", "virtual", "frame", "all"], - "aliases": {} - }, - "ShowComplex2D": { - "tool_groups": ["display", "histogram", "fft", "roi", "stats", "export", "view", "all"], - "aliases": {} - }, - "Mark2D": { - "tool_groups": ["points", "roi", "profile", "display", "marker_style", "snap", "navigation", "view", "export", "all"], - "aliases": {} - }, - "Edit2D": { - "tool_groups": ["mode", "edit", "display", "histogram", "stats", "navigation", "export", "view", "all"], - "aliases": {} - }, - "Align2D": { - "tool_groups": ["alignment", "overlay", "display", "histogram", "stats", "export", "view", "all"], - "aliases": {} - }, - "Align2DBulk": { - "tool_groups": ["display", "histogram", "navigation", "stats", "view", "export", "all"], - "aliases": {} - }, - "Bin4D": { - "tool_groups": ["display", "binning", "mask", "preview", "stats", "export", "all"], - "aliases": {} - }, - "Browse": { - "tool_groups": ["navigation", "filter", "preview", "all"], - "aliases": {} - }, - "Bin2D": { - "tool_groups": ["display", "binning", "histogram", "stats", "navigation", "export", "all"], - "aliases": {} - }, - "Show1D": { - "tool_groups": ["display", "peaks", "stats", "export", "all"], - "aliases": {} - }, - "MetricExplorer": { - "tool_groups": ["display", "export", "all"], - "aliases": {} - }, - "ShowDiffraction": { - "tool_groups": ["display", "histogram", "stats", "navigation", "view", "export", "spots", "all"], - "aliases": {} - } - }, - "viewer_widgets": ["Show1D", "Show2D", "Show3D", "Show3DVolume", "Show4D", "Show4DSTEM", "ShowComplex2D"], - "control_presets": { - "all": { - "label": "All", - "show_groups": ["*"] - }, - "compact": { - "label": "Compact", - "show_groups": ["mode", "edit", "display", "navigation", "playback", "view", "export", "fft"] - }, - "mask_focus": { - "label": "Mask Focus", - "show_groups": ["edit", "display", "roi", "histogram", "stats", "navigation", "playback", "view", "export", "fft", "virtual", "frame"] - }, - "crop_focus": { - "label": "Crop Focus", - "show_groups": ["mode", "edit", "display", "histogram", "stats", "navigation", "view", "export"] - }, - "spectroscopy": { - "label": "Spectroscopy", - "show_groups": ["display", "peaks", "stats"] - } - } -} diff --git a/widget/src/quantem/widget/tool_parity.py b/widget/src/quantem/widget/tool_parity.py deleted file mode 100644 index d5d4f84e..00000000 --- a/widget/src/quantem/widget/tool_parity.py +++ /dev/null @@ -1,184 +0,0 @@ -"""Shared tool visibility/locking registry and helpers.""" - -from __future__ import annotations - -import json -import pathlib -from functools import lru_cache -from typing import Any - -_REGISTRY_PATH = pathlib.Path(__file__).with_name("tool_parity.json") - - -@lru_cache(maxsize=1) -def _load_registry() -> dict[str, Any]: - return json.loads(_REGISTRY_PATH.read_text()) - - -def get_widget_tool_groups(widget_name: str) -> tuple[str, ...]: - registry = _load_registry() - widgets = registry.get("widgets", {}) - if widget_name not in widgets: - supported = ", ".join(sorted(widgets)) - raise ValueError(f"Unknown widget {widget_name!r}. Supported widgets: {supported}.") - return tuple(str(v).strip().lower() for v in widgets[widget_name].get("tool_groups", [])) - - -def get_widget_tool_aliases(widget_name: str) -> dict[str, str]: - registry = _load_registry() - widgets = registry.get("widgets", {}) - if widget_name not in widgets: - supported = ", ".join(sorted(widgets)) - raise ValueError(f"Unknown widget {widget_name!r}. Supported widgets: {supported}.") - aliases = widgets[widget_name].get("aliases", {}) - return {str(k).strip().lower(): str(v).strip().lower() for k, v in aliases.items()} - - -def normalize_tool_groups(widget_name: str, tool_groups) -> list[str]: - if tool_groups is None: - return [] - if isinstance(tool_groups, str): - values = [tool_groups] - else: - values = list(tool_groups) - - order = get_widget_tool_groups(widget_name) - aliases = get_widget_tool_aliases(widget_name) - supported = set(order) - normalized: list[str] = [] - seen: set[str] = set() - - for raw in values: - key = str(raw).strip().lower() - if not key: - continue - key = aliases.get(key, key) - if key not in supported: - supported_values = ", ".join(f'"{k}"' for k in order) - raise ValueError( - f"Unknown tool group {raw!r}. Supported values: {supported_values}." - ) - if key == "all": - return ["all"] - if key not in seen: - seen.add(key) - normalized.append(key) - return normalized - - -def build_tool_groups( - widget_name: str, - *, - tool_groups=None, - all_flag: bool = False, - flag_map: dict[str, bool] | None = None, -) -> list[str]: - if all_flag: - return ["all"] - values: list[str] = [] - if tool_groups is not None: - if isinstance(tool_groups, str): - values.append(tool_groups) - else: - values.extend(tool_groups) - for key, enabled in (flag_map or {}).items(): - if enabled: - values.append(key) - return normalize_tool_groups(widget_name, values) - - -def resolve_control_preset_hidden_tools(widget_name: str, preset_id: str) -> list[str]: - preset_key = str(preset_id).strip().lower() - presets = _load_registry().get("control_presets", {}) - if preset_key not in presets: - supported = ", ".join(sorted(presets)) - raise ValueError(f"Unknown control preset {preset_id!r}. Supported presets: {supported}.") - - show_groups = [str(v).strip().lower() for v in presets[preset_key].get("show_groups", [])] - supported_groups = [g for g in get_widget_tool_groups(widget_name) if g != "all"] - if "*" in show_groups: - return [] - show_set = set(show_groups) - hidden = [group for group in supported_groups if group not in show_set] - return normalize_tool_groups(widget_name, hidden) - - -def _flatten_groups(groups: tuple[Any, ...]) -> list[Any]: - if len(groups) == 1 and isinstance(groups[0], (list, tuple, set)): - return list(groups[0]) - return list(groups) - - -def _expanded_without_all(widget_name: str, values) -> list[str]: - normalized = normalize_tool_groups(widget_name, values) - if "all" not in normalized: - return normalized - return [group for group in get_widget_tool_groups(widget_name) if group != "all"] - - -def _ordered_groups(widget_name: str, values: set[str]) -> list[str]: - return [group for group in get_widget_tool_groups(widget_name) if group != "all" and group in values] - - -def bind_tool_runtime_api(cls, widget_name: str) -> None: - """Attach runtime lock/hide helpers to a widget class.""" - - def set_disabled_tools(self, tool_groups) -> Any: - self.disabled_tools = normalize_tool_groups(widget_name, tool_groups) - return self - - def set_hidden_tools(self, tool_groups) -> Any: - self.hidden_tools = normalize_tool_groups(widget_name, tool_groups) - return self - - def lock_tool(self, *tool_groups) -> Any: - new_groups = _flatten_groups(tool_groups) - if not new_groups: - return self - current = _expanded_without_all(widget_name, self.disabled_tools) - requested = _expanded_without_all(widget_name, new_groups) - merged = set(current).union(requested) - self.disabled_tools = _ordered_groups(widget_name, merged) - return self - - def unlock_tool(self, *tool_groups) -> Any: - remove_groups = _flatten_groups(tool_groups) - if not remove_groups: - return self - current = set(_expanded_without_all(widget_name, self.disabled_tools)) - requested = set(_expanded_without_all(widget_name, remove_groups)) - current.difference_update(requested) - self.disabled_tools = _ordered_groups(widget_name, current) - return self - - def hide_tool(self, *tool_groups) -> Any: - new_groups = _flatten_groups(tool_groups) - if not new_groups: - return self - current = _expanded_without_all(widget_name, self.hidden_tools) - requested = _expanded_without_all(widget_name, new_groups) - merged = set(current).union(requested) - self.hidden_tools = _ordered_groups(widget_name, merged) - return self - - def show_tool(self, *tool_groups) -> Any: - remove_groups = _flatten_groups(tool_groups) - if not remove_groups: - return self - current = set(_expanded_without_all(widget_name, self.hidden_tools)) - requested = set(_expanded_without_all(widget_name, remove_groups)) - current.difference_update(requested) - self.hidden_tools = _ordered_groups(widget_name, current) - return self - - def apply_control_preset(self, preset: str) -> Any: - self.hidden_tools = resolve_control_preset_hidden_tools(widget_name, preset) - return self - - cls.set_disabled_tools = set_disabled_tools # type: ignore[attr-defined] - cls.set_hidden_tools = set_hidden_tools # type: ignore[attr-defined] - cls.lock_tool = lock_tool # type: ignore[attr-defined] - cls.unlock_tool = unlock_tool # type: ignore[attr-defined] - cls.hide_tool = hide_tool # type: ignore[attr-defined] - cls.show_tool = show_tool # type: ignore[attr-defined] - cls.apply_control_preset = apply_control_preset # type: ignore[attr-defined] diff --git a/widget/tests/test_fft_parity.py b/widget/tests/test_fft_parity.py new file mode 100644 index 00000000..4e38b85f --- /dev/null +++ b/widget/tests/test_fft_parity.py @@ -0,0 +1,200 @@ +"""FFT parity: JS fft1d/fft2d/fftshift line-ported to Python, validated against numpy. + +Why ports instead of running the JS directly: pytest can't drive a TypeScript +module without a Node bridge or browser harness, both of which add fragility +and slow CI. Instead we mirror js/fft.ts:14-82 line-for-line in Python below +and assert against numpy.fft. If the JS algorithm has a bug, the line-port +inherits it and this test fails — surfacing the bug at unit-test speed. + +When js/fft.ts changes, update the ports here in the same commit. The +side-by-side structure makes drift visually obvious during review. +""" +import numpy as np + + +def _next_pow2(n: int) -> int: + p = 1 + while p < n: + p <<= 1 + return p + + +def _js_fft1d(real: np.ndarray, imag: np.ndarray, inverse: bool = False) -> None: + """Line-port of js/fft.ts fft1d. In-place. Iterative radix-2 Cooley-Tukey.""" + n = real.size + if n <= 1: + return + # Bit-reversal permutation. + j = 0 + for i in range(n - 1): + if i < j: + real[i], real[j] = real[j], real[i] + imag[i], imag[j] = imag[j], imag[i] + k = n >> 1 + while k <= j: + j -= k + k >>= 1 + j += k + sign = 1 if inverse else -1 + length = 2 + while length <= n: + half = length >> 1 + angle = (sign * 2 * np.pi) / length + w_real = np.cos(angle) + w_imag = np.sin(angle) + for i in range(0, n, length): + cur_real = 1.0 + cur_imag = 0.0 + for k in range(half): + even = i + k + odd = i + k + half + t_real = cur_real * real[odd] - cur_imag * imag[odd] + t_imag = cur_real * imag[odd] + cur_imag * real[odd] + real[odd] = real[even] - t_real + imag[odd] = imag[even] - t_imag + real[even] += t_real + imag[even] += t_imag + new_real = cur_real * w_real - cur_imag * w_imag + cur_imag = cur_real * w_imag + cur_imag * w_real + cur_real = new_real + length <<= 1 + if inverse: + real /= n + imag /= n + + +def _js_fft2d(real: np.ndarray, imag: np.ndarray, width: int, height: int, inverse: bool = False) -> None: + """Line-port of js/fft.ts fft2d. In-place on (height*width) flattened arrays.""" + padded_w = _next_pow2(width) + padded_h = _next_pow2(height) + needs_padding = padded_w != width or padded_h != height + if needs_padding: + work_real = np.zeros(padded_w * padded_h, dtype=np.float64) + work_imag = np.zeros(padded_w * padded_h, dtype=np.float64) + for y in range(height): + for x in range(width): + work_real[y * padded_w + x] = real[y * width + x] + work_imag[y * padded_w + x] = imag[y * width + x] + else: + work_real = real + work_imag = imag + row_real = np.empty(padded_w, dtype=np.float64) + row_imag = np.empty(padded_w, dtype=np.float64) + for y in range(padded_h): + offset = y * padded_w + row_real[:] = work_real[offset:offset + padded_w] + row_imag[:] = work_imag[offset:offset + padded_w] + _js_fft1d(row_real, row_imag, inverse) + work_real[offset:offset + padded_w] = row_real + work_imag[offset:offset + padded_w] = row_imag + col_real = np.empty(padded_h, dtype=np.float64) + col_imag = np.empty(padded_h, dtype=np.float64) + for x in range(padded_w): + for y in range(padded_h): + col_real[y] = work_real[y * padded_w + x] + col_imag[y] = work_imag[y * padded_w + x] + _js_fft1d(col_real, col_imag, inverse) + for y in range(padded_h): + work_real[y * padded_w + x] = col_real[y] + work_imag[y * padded_w + x] = col_imag[y] + if needs_padding: + for y in range(height): + for x in range(width): + real[y * width + x] = work_real[y * padded_w + x] + imag[y * width + x] = work_imag[y * padded_w + x] + + +def _js_fftshift(data: np.ndarray, width: int, height: int) -> None: + """Line-port of js/fft.ts fftshift. In-place.""" + half_w = width >> 1 + half_h = height >> 1 + temp = np.empty(width * height, dtype=data.dtype) + for y in range(height): + for x in range(width): + temp[((y + half_h) % height) * width + ((x + half_w) % width)] = data[y * width + x] + data[:] = temp + + +# --------------------------------------------------------------------------- + +def test_fft1d_matches_numpy_pow2(): + """1D FFT on power-of-2 input matches numpy.fft.fft.""" + rng = np.random.default_rng(0) + n = 64 + x = rng.standard_normal(n) + real = x.astype(np.float64).copy() + imag = np.zeros(n, dtype=np.float64) + _js_fft1d(real, imag, inverse=False) + js = real + 1j * imag + expected = np.fft.fft(x) + np.testing.assert_allclose(js, expected, atol=1e-9) + + +def test_fft1d_inverse_roundtrip(): + """fft1d(fft1d(x), inverse=True) ≈ x.""" + rng = np.random.default_rng(1) + n = 128 + x = rng.standard_normal(n) + real = x.astype(np.float64).copy() + imag = np.zeros(n, dtype=np.float64) + _js_fft1d(real, imag, inverse=False) + _js_fft1d(real, imag, inverse=True) + np.testing.assert_allclose(real, x, atol=1e-9) + np.testing.assert_allclose(imag, np.zeros(n), atol=1e-9) + + +def test_fft2d_matches_numpy_pow2(): + """2D FFT on power-of-2 dims matches numpy.fft.fft2.""" + rng = np.random.default_rng(2) + h, w = 32, 64 + img = rng.standard_normal((h, w)) + real = img.astype(np.float64).flatten() + imag = np.zeros(h * w, dtype=np.float64) + _js_fft2d(real, imag, w, h, inverse=False) + js = (real + 1j * imag).reshape(h, w) + expected = np.fft.fft2(img) + np.testing.assert_allclose(js, expected, atol=1e-9) + + +def test_fft2d_non_pow2_zero_pads(): + """Non-power-of-2 input gets zero-padded; FFT of padded matches numpy of padded.""" + rng = np.random.default_rng(3) + h, w = 30, 50 + img = rng.standard_normal((h, w)) + real = img.astype(np.float64).flatten() + imag = np.zeros(h * w, dtype=np.float64) + _js_fft2d(real, imag, w, h, inverse=False) + # JS contract: only the (h, w) region of the result is written back to the input arrays. + js = (real + 1j * imag).reshape(h, w) + pw, ph = _next_pow2(w), _next_pow2(h) + padded = np.zeros((ph, pw)) + padded[:h, :w] = img + expected = np.fft.fft2(padded)[:h, :w] + np.testing.assert_allclose(js, expected, atol=1e-9) + + +def test_fftshift_matches_numpy(): + """fftshift matches numpy.fft.fftshift on 2D data.""" + rng = np.random.default_rng(4) + h, w = 16, 16 + img = rng.standard_normal((h, w)) + flat = img.flatten().copy() + _js_fftshift(flat, w, h) + js_shifted = flat.reshape(h, w) + expected = np.fft.fftshift(img) + np.testing.assert_array_equal(js_shifted, expected) + + +def test_fft2d_then_fftshift_matches_numpy(): + """Combined FFT + fftshift matches numpy reference.""" + rng = np.random.default_rng(5) + h, w = 32, 32 + img = rng.standard_normal((h, w)) + real = img.astype(np.float64).flatten() + imag = np.zeros(h * w, dtype=np.float64) + _js_fft2d(real, imag, w, h, inverse=False) + _js_fftshift(real, w, h) + _js_fftshift(imag, w, h) + js = (real + 1j * imag).reshape(h, w) + expected = np.fft.fftshift(np.fft.fft2(img)) + np.testing.assert_allclose(js, expected, atol=1e-9) diff --git a/widget/tests/test_state_dict.py b/widget/tests/test_state_dict.py new file mode 100644 index 00000000..9247c979 --- /dev/null +++ b/widget/tests/test_state_dict.py @@ -0,0 +1,168 @@ +"""state_dict roundtrip tests for Show2D and Show4DSTEM. + +For each widget: +1. Construct with default data. +2. Mutate every trait in state_dict() to a non-default value. +3. Get state_dict. +4. Construct a fresh widget and load_state_dict. +5. Assert every trait on the restored widget equals what we set. + +Catches silent regressions when traits are added, renamed, or dropped without +updating the state_dict roundtrip path. +""" +import json +import pathlib + +import numpy as np +import pytest + +from quantem.widget import Show2D, Show4DSTEM + + +def _flip_value(default): + """Return a value distinct from `default` for the same type.""" + if isinstance(default, bool): + return not default + if isinstance(default, int): + return int(default) + 7 + if isinstance(default, float): + return float(default) + 0.123 + if isinstance(default, str): + return default + "_x" if default else "x" + if isinstance(default, list): + return [_flip_value(default[0])] if default else [0] + return default + + +def _mutate_state(state: dict) -> dict: + """Build a new state dict with every key changed to a non-default value.""" + out = {} + for k, v in state.items(): + # Skip values our flipper can't safely tweak (None defaults, nested dicts/lists-of-dicts, bytes). + if v is None or isinstance(v, (dict, bytes)): + out[k] = v + continue + # Lists hold structured items (dicts, tuples) for ROI / profile / labels; + # mutating them generically is fragile. The roundtrip-defaults test already + # covers list trait persistence — here we only mutate scalars. + if isinstance(v, list): + out[k] = v + continue + out[k] = _flip_value(v) + return out + + +# --------------------------------------------------------------------------- +# Show4DSTEM +# --------------------------------------------------------------------------- + +@pytest.fixture +def show4dstem_widget(): + data = np.random.default_rng(0).poisson(5, (8, 8, 16, 16)).astype(np.uint16) + data[:, :, 6:10, 6:10] += 500 # synthetic BF disk + return Show4DSTEM(data, verbose=False) + + +def test_show4dstem_state_dict_keys(show4dstem_widget): + """state_dict returns a non-empty dict of public traits.""" + s = show4dstem_widget.state_dict() + assert isinstance(s, dict) + assert len(s) > 10 + # Required keys for the widget's user-facing display state + for required in ("title", "dp_colormap", "vi_colormap", "roi_mode", "vi_roi_reduce"): + assert required in s, f"state_dict missing key {required!r}" + + +def test_show4dstem_state_dict_roundtrip_defaults(show4dstem_widget): + """save → load on default widget preserves state.""" + original = show4dstem_widget.state_dict() + data = np.random.default_rng(0).poisson(5, (8, 8, 16, 16)).astype(np.uint16) + data[:, :, 6:10, 6:10] += 500 + fresh = Show4DSTEM(data, state=original, verbose=False) + restored = fresh.state_dict() + for k in original: + assert restored[k] == original[k], f"{k}: {original[k]!r} -> {restored[k]!r}" + + +def test_show4dstem_state_dict_roundtrip_mutated(show4dstem_widget): + """Mutating every trait then roundtripping preserves the mutations.""" + # Position / frame indices are clamped to valid range by trait validators + # against the data dimensions; mutating them generically is meaningless here. + skip = {"pos_row", "pos_col", "frame_idx", "path_index", "path_length", + "vi_roi_center_row", "vi_roi_center_col"} + mutated = _mutate_state(show4dstem_widget.state_dict()) + show4dstem_widget.load_state_dict(mutated) + out = show4dstem_widget.state_dict() + for k, v in mutated.items(): + if k in skip: + continue + if isinstance(v, float): + assert abs(out[k] - v) < 1e-3, f"{k}: expected {v}, got {out[k]}" + else: + assert out[k] == v, f"{k}: expected {v!r}, got {out[k]!r}" + + +def test_show4dstem_save_and_load(tmp_path, show4dstem_widget): + """save() writes a versioned envelope JSON, state= kwarg loads it.""" + show4dstem_widget.dp_colormap = "viridis" + show4dstem_widget.vi_colormap = "magma" + show4dstem_widget.show_fft = True + path = tmp_path / "show4dstem_state.json" + show4dstem_widget.save(str(path)) + + payload = json.loads(path.read_text()) + assert payload["widget_name"] == "Show4DSTEM" + assert "metadata_version" in payload + assert "state" in payload + + data = np.random.default_rng(0).poisson(5, (8, 8, 16, 16)).astype(np.uint16) + data[:, :, 6:10, 6:10] += 500 + fresh = Show4DSTEM(data, state=str(path), verbose=False) + assert fresh.dp_colormap == "viridis" + assert fresh.vi_colormap == "magma" + assert fresh.show_fft is True + + +# --------------------------------------------------------------------------- +# Show2D +# --------------------------------------------------------------------------- + +@pytest.fixture +def show2d_widget(): + return Show2D(np.random.default_rng(0).standard_normal((32, 32)).astype(np.float32), verbose=False) + + +def test_show2d_state_dict_keys(show2d_widget): + s = show2d_widget.state_dict() + assert isinstance(s, dict) + assert len(s) > 5 + for required in ("cmap", "log_scale"): + assert required in s, f"state_dict missing key {required!r}" + + +def test_show2d_state_dict_roundtrip_defaults(show2d_widget): + original = show2d_widget.state_dict() + fresh = Show2D(np.random.default_rng(0).standard_normal((32, 32)).astype(np.float32), + state=original, verbose=False) + restored = fresh.state_dict() + for k in original: + if isinstance(original[k], float): + assert abs(restored[k] - original[k]) < 1e-3, f"{k}: {original[k]} -> {restored[k]}" + else: + assert restored[k] == original[k], f"{k}: {original[k]!r} -> {restored[k]!r}" + + +def test_show2d_save_and_load(tmp_path, show2d_widget): + show2d_widget.cmap = "viridis" + show2d_widget.log_scale = True + path = tmp_path / "show2d_state.json" + show2d_widget.save(str(path)) + + payload = json.loads(path.read_text()) + assert payload["widget_name"] == "Show2D" + assert "state" in payload + + fresh = Show2D(np.random.default_rng(0).standard_normal((32, 32)).astype(np.float32), + state=str(path), verbose=False) + assert fresh.cmap == "viridis" + assert fresh.log_scale is True diff --git a/widget/vite.config.js b/widget/vite.config.js deleted file mode 100644 index 291b84aa..00000000 --- a/widget/vite.config.js +++ /dev/null @@ -1,26 +0,0 @@ -import { defineConfig } from "vite"; -import anywidget from "@anywidget/vite"; -import react from "@vitejs/plugin-react"; - -export default defineConfig({ - plugins: [anywidget(), react()], - define: { - "process.env.NODE_ENV": JSON.stringify("production"), - }, - build: { - outDir: "src/quantem/widget/static", - emptyOutDir: true, - rollupOptions: { - input: { - show2d: "js/show2d/index.tsx", - show4dstem: "js/show4dstem/index.tsx", - }, - output: { - entryFileNames: "[name].js", - assetFileNames: "[name][extname]", - format: "es", - inlineDynamicImports: false, - }, - }, - }, -}); From ff510f958ad5f3d90554ef82bad2eac87f879736 Mon Sep 17 00:00:00 2001 From: quantem-bot Date: Mon, 4 May 2026 11:09:22 +0000 Subject: [PATCH 273/335] chore: update lock file --- uv.lock | 193 ++++++++++++++++++++++++++++++++------------------------ 1 file changed, 109 insertions(+), 84 deletions(-) diff --git a/uv.lock b/uv.lock index 43a03fd0..6932e160 100644 --- a/uv.lock +++ b/uv.lock @@ -51,16 +51,16 @@ wheels = [ [[package]] name = "anywidget" -version = "0.10.0" +version = "0.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ipywidgets" }, { name = "psygnal" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7e/00/8b5d3cc6146dd091abf1495869ca447f8a801b9938dbd13b15477a51c658/anywidget-0.10.0.tar.gz", hash = "sha256:4ec7cba129613af8d210654d6297c5c3fb5d76fcd2efea829fd58050d8b28802", size = 391132, upload-time = "2026-04-07T04:27:18.795Z" } +sdist = { url = "https://files.pythonhosted.org/packages/79/31/0491d707c674b34267f55d96d6a7148e55e7b6718a271686232cf295fbe2/anywidget-0.11.0.tar.gz", hash = "sha256:6695fbef9449cf8c27f421b96c5837aa37f909ec1f60cfa33add333e1b70b169", size = 426999, upload-time = "2026-04-27T23:42:09.576Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/f1/80a173ddbb7753d3a3bf9fb7fa698280a5f418dedce2a8662e960807441b/anywidget-0.10.0-py3-none-any.whl", hash = "sha256:a001543b55be9b4e9c9783b2f1aa0d5be733b321723d9bf30975fce840730a57", size = 254744, upload-time = "2026-04-07T04:27:17.252Z" }, + { url = "https://files.pythonhosted.org/packages/8e/c2/8fec8e8e2eb920cc2280f569144080cd58622a2eda83bfa4c0c354a63264/anywidget-0.11.0-py3-none-any.whl", hash = "sha256:c574d9acc6503ad27b37a9acea48f957a8ba7c9c9876cfcb37898931c098ce9d", size = 317341, upload-time = "2026-04-27T23:42:08.356Z" }, ] [[package]] @@ -656,10 +656,10 @@ wheels = [ [[package]] name = "cuda-pathfinder" -version = "1.5.3" +version = "1.5.4" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/d6/ac63065d33dd700fee7ebd7d287332401b54e31b9346e142f871e1f0b116/cuda_pathfinder-1.5.3-py3-none-any.whl", hash = "sha256:dff021123aedbb4117cc7ec81717bbfe198fb4e8b5f1ee57e0e084fec5c8577d", size = 49991, upload-time = "2026-04-14T20:09:27.037Z" }, + { url = "https://files.pythonhosted.org/packages/11/d0/c177e29701cf1d3008d7d2b16b5fc626592ce13bd535f8795c5f57187e0e/cuda_pathfinder-1.5.4-py3-none-any.whl", hash = "sha256:9563d3175ce1828531acf4b94e1c1c7d67208c347ca002493e2654878b26f4b7", size = 51657, upload-time = "2026-04-27T22:42:07.712Z" }, ] [[package]] @@ -922,11 +922,11 @@ wheels = [ [[package]] name = "fsspec" -version = "2026.3.0" +version = "2026.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/cf/b50ddf667c15276a9ab15a70ef5f257564de271957933ffea49d2cdbcdfb/fsspec-2026.3.0.tar.gz", hash = "sha256:1ee6a0e28677557f8c2f994e3eea77db6392b4de9cd1f5d7a9e87a0ae9d01b41", size = 313547, upload-time = "2026-03-27T19:11:14.892Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/8d/1c51c094345df128ca4a990d633fe1a0ff28726c9e6b3c41ba65087bba1d/fsspec-2026.4.0.tar.gz", hash = "sha256:301d8ac70ae90ef3ad05dcf94d6c3754a097f9b5fe4667d2787aa359ec7df7e4", size = 312760, upload-time = "2026-04-29T20:42:38.635Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/1f/5f4a3cd9e4440e9d9bc78ad0a91a1c8d46b4d429d5239ebe6793c9fe5c41/fsspec-2026.3.0-py3-none-any.whl", hash = "sha256:d2ceafaad1b3457968ed14efa28798162f1638dbb5d2a6868a2db002a5ee39a4", size = 202595, upload-time = "2026-03-27T19:11:13.595Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl", hash = "sha256:11ef7bb35dab8a394fde6e608221d5cf3e8499401c249bebaeaad760a1a8dec2", size = 203402, upload-time = "2026-04-29T20:42:36.842Z" }, ] [[package]] @@ -961,49 +961,49 @@ wheels = [ [[package]] name = "greenlet" -version = "3.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/86/94/a5935717b307d7c71fe877b52b884c6af707d2d2090db118a03fbd799369/greenlet-3.4.0.tar.gz", hash = "sha256:f50a96b64dafd6169e595a5c56c9146ef80333e67d4476a65a9c55f400fc22ff", size = 195913, upload-time = "2026-04-08T17:08:00.863Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/c6/dba32cab7e3a625b011aa5647486e2d28423a48845a2998c126dd69c85e1/greenlet-3.4.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:805bebb4945094acbab757d34d6e1098be6de8966009ab9ca54f06ff492def58", size = 285504, upload-time = "2026-04-08T15:52:14.071Z" }, - { url = "https://files.pythonhosted.org/packages/54/f4/7cb5c2b1feb9a1f50e038be79980dfa969aa91979e5e3a18fdbcfad2c517/greenlet-3.4.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:439fc2f12b9b512d9dfa681c5afe5f6b3232c708d13e6f02c845e0d9f4c2d8c6", size = 605476, upload-time = "2026-04-08T16:24:37.064Z" }, - { url = "https://files.pythonhosted.org/packages/d6/af/b66ab0b2f9a4c5a867c136bf66d9599f34f21a1bcca26a2884a29c450bd9/greenlet-3.4.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a70ed1cb0295bee1df57b63bf7f46b4e56a5c93709eea769c1fec1bb23a95875", size = 618336, upload-time = "2026-04-08T16:30:56.59Z" }, - { url = "https://files.pythonhosted.org/packages/e5/5c/8c5633ece6ba611d64bf2770219a98dd439921d6424e4e8cf16b0ac74ea5/greenlet-3.4.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c660bce1940a1acae5f51f0a064f1bc785d07ea16efcb4bc708090afc4d69e83", size = 613515, upload-time = "2026-04-08T15:56:32.478Z" }, - { url = "https://files.pythonhosted.org/packages/a9/df/950d15bca0d90a0e7395eb777903060504cdb509b7b705631e8fb69ff415/greenlet-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ee407d4d1ca9dc632265aee1c8732c4a2d60adff848057cdebfe5fe94eb2c8a2", size = 1574623, upload-time = "2026-04-08T16:26:18.596Z" }, - { url = "https://files.pythonhosted.org/packages/1a/e7/0839afab829fcb7333c9ff6d80c040949510055d2d4d63251f0d1c7c804e/greenlet-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:956215d5e355fffa7c021d168728321fd4d31fd730ac609b1653b450f6a4bc71", size = 1639579, upload-time = "2026-04-08T15:57:29.231Z" }, - { url = "https://files.pythonhosted.org/packages/d9/2b/b4482401e9bcaf9f5c97f67ead38db89c19520ff6d0d6699979c6efcc200/greenlet-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:5cb614ace7c27571270354e9c9f696554d073f8aa9319079dcba466bbdead711", size = 238233, upload-time = "2026-04-08T17:02:54.286Z" }, - { url = "https://files.pythonhosted.org/packages/0c/4d/d8123a4e0bcd583d5cfc8ddae0bbe29c67aab96711be331a7cc935a35966/greenlet-3.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:04403ac74fe295a361f650818de93be11b5038a78f49ccfb64d3b1be8fbf1267", size = 235045, upload-time = "2026-04-08T17:04:05.072Z" }, - { url = "https://files.pythonhosted.org/packages/65/8b/3669ad3b3f247a791b2b4aceb3aa5a31f5f6817bf547e4e1ff712338145a/greenlet-3.4.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:1a54a921561dd9518d31d2d3db4d7f80e589083063ab4d3e2e950756ef809e1a", size = 286902, upload-time = "2026-04-08T15:52:12.138Z" }, - { url = "https://files.pythonhosted.org/packages/38/3e/3c0e19b82900873e2d8469b590a6c4b3dfd2b316d0591f1c26b38a4879a5/greenlet-3.4.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16dec271460a9a2b154e3b1c2fa1050ce6280878430320e85e08c166772e3f97", size = 606099, upload-time = "2026-04-08T16:24:38.408Z" }, - { url = "https://files.pythonhosted.org/packages/b5/33/99fef65e7754fc76a4ed14794074c38c9ed3394a5bd129d7f61b705f3168/greenlet-3.4.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:90036ce224ed6fe75508c1907a77e4540176dcf0744473627785dd519c6f9996", size = 618837, upload-time = "2026-04-08T16:30:58.298Z" }, - { url = "https://files.pythonhosted.org/packages/36/f7/229f3aed6948faa20e0616a0b8568da22e365ede6a54d7d369058b128afd/greenlet-3.4.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a1c4f6b453006efb8310affb2d132832e9bbb4fc01ce6df6b70d810d38f1f6dc", size = 615062, upload-time = "2026-04-08T15:56:33.766Z" }, - { url = "https://files.pythonhosted.org/packages/08/97/d988180011aa40135c46cd0d0cf01dd97f7162bae14139b4a3ef54889ba5/greenlet-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9b2d9a138ffa0e306d0e2b72976d2fb10b97e690d40ab36a472acaab0838e2de", size = 1573511, upload-time = "2026-04-08T16:26:20.058Z" }, - { url = "https://files.pythonhosted.org/packages/d4/0f/a5a26fe152fb3d12e6a474181f6e9848283504d0afd095f353d85726374b/greenlet-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8424683caf46eb0eb6f626cb95e008e8cc30d0cb675bdfa48200925c79b38a08", size = 1640396, upload-time = "2026-04-08T15:57:30.88Z" }, - { url = "https://files.pythonhosted.org/packages/42/cf/bb2c32d9a100e36ee9f6e38fad6b1e082b8184010cb06259b49e1266ca01/greenlet-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0a53fb071531d003b075c444014ff8f8b1a9898d36bb88abd9ac7b3524648a2", size = 238892, upload-time = "2026-04-08T17:03:10.094Z" }, - { url = "https://files.pythonhosted.org/packages/b7/47/6c41314bac56e71436ce551c7fbe3cc830ed857e6aa9708dbb9c65142eb6/greenlet-3.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:f38b81880ba28f232f1f675893a39cf7b6db25b31cc0a09bb50787ecf957e85e", size = 235599, upload-time = "2026-04-08T15:52:54.3Z" }, - { url = "https://files.pythonhosted.org/packages/7a/75/7e9cd1126a1e1f0cd67b0eda02e5221b28488d352684704a78ed505bd719/greenlet-3.4.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:43748988b097f9c6f09364f260741aa73c80747f63389824435c7a50bfdfd5c1", size = 285856, upload-time = "2026-04-08T15:52:45.82Z" }, - { url = "https://files.pythonhosted.org/packages/9d/c4/3e2df392e5cb199527c4d9dbcaa75c14edcc394b45040f0189f649631e3c/greenlet-3.4.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5566e4e2cd7a880e8c27618e3eab20f3494452d12fd5129edef7b2f7aa9a36d1", size = 610208, upload-time = "2026-04-08T16:24:39.674Z" }, - { url = "https://files.pythonhosted.org/packages/da/af/750cdfda1d1bd30a6c28080245be8d0346e669a98fdbae7f4102aa95fff3/greenlet-3.4.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1054c5a3c78e2ab599d452f23f7adafef55062a783a8e241d24f3b633ba6ff82", size = 621269, upload-time = "2026-04-08T16:30:59.767Z" }, - { url = "https://files.pythonhosted.org/packages/54/78/0cbc693622cd54ebe25207efbb3a0eb07c2639cb8594f6e3aaaa0bb077a8/greenlet-3.4.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f82cb6cddc27dd81c96b1506f4aa7def15070c3b2a67d4e46fd19016aacce6cf", size = 617549, upload-time = "2026-04-08T15:56:34.893Z" }, - { url = "https://files.pythonhosted.org/packages/ba/c0/8966767de01343c1ff47e8b855dc78e7d1a8ed2b7b9c83576a57e289f81d/greenlet-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:227a46251ecba4ff46ae742bc5ce95c91d5aceb4b02f885487aff269c127a729", size = 1575310, upload-time = "2026-04-08T16:26:21.671Z" }, - { url = "https://files.pythonhosted.org/packages/b8/38/bcdc71ba05e9a5fda87f63ffc2abcd1f15693b659346df994a48c968003d/greenlet-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5b99e87be7eba788dd5b75ba1cde5639edffdec5f91fe0d734a249535ec3408c", size = 1640435, upload-time = "2026-04-08T15:57:32.572Z" }, - { url = "https://files.pythonhosted.org/packages/a1/c2/19b664b7173b9e4ef5f77e8cef9f14c20ec7fce7920dc1ccd7afd955d093/greenlet-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:849f8bc17acd6295fcb5de8e46d55cc0e52381c56eaf50a2afd258e97bc65940", size = 238760, upload-time = "2026-04-08T17:04:03.878Z" }, - { url = "https://files.pythonhosted.org/packages/9b/96/795619651d39c7fbd809a522f881aa6f0ead504cc8201c3a5b789dfaef99/greenlet-3.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:9390ad88b652b1903814eaabd629ca184db15e0eeb6fe8a390bbf8b9106ae15a", size = 235498, upload-time = "2026-04-08T17:05:00.584Z" }, - { url = "https://files.pythonhosted.org/packages/78/02/bde66806e8f169cf90b14d02c500c44cdbe02c8e224c9c67bafd1b8cadd1/greenlet-3.4.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:10a07aca6babdd18c16a3f4f8880acfffc2b88dfe431ad6aa5f5740759d7d75e", size = 286291, upload-time = "2026-04-08T17:09:34.307Z" }, - { url = "https://files.pythonhosted.org/packages/05/1f/39da1c336a87d47c58352fb8a78541ce63d63ae57c5b9dae1fe02801bbc2/greenlet-3.4.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:076e21040b3a917d3ce4ad68fb5c3c6b32f1405616c4a57aa83120979649bd3d", size = 656749, upload-time = "2026-04-08T16:24:41.721Z" }, - { url = "https://files.pythonhosted.org/packages/d3/6c/90ee29a4ee27af7aa2e2ec408799eeb69ee3fcc5abcecac6ddd07a5cd0f2/greenlet-3.4.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e82689eea4a237e530bb5cb41b180ef81fa2160e1f89422a67be7d90da67f615", size = 669084, upload-time = "2026-04-08T16:31:01.372Z" }, - { url = "https://files.pythonhosted.org/packages/07/49/d4cad6e5381a50947bb973d2f6cf6592621451b09368b8c20d9b8af49c5b/greenlet-3.4.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4df3b0b2289ec686d3c821a5fee44259c05cfe824dd5e6e12c8e5f5df23085cf", size = 665621, upload-time = "2026-04-08T15:56:35.995Z" }, - { url = "https://files.pythonhosted.org/packages/37/31/d1edd54f424761b5d47718822f506b435b6aab2f3f93b465441143ea5119/greenlet-3.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8bff29d586ea415688f4cec96a591fcc3bf762d046a796cdadc1fdb6e7f2d5bf", size = 1622259, upload-time = "2026-04-08T16:26:23.201Z" }, - { url = "https://files.pythonhosted.org/packages/b0/c6/6d3f9cdcb21c4e12a79cb332579f1c6aa1af78eb68059c5a957c7812d95e/greenlet-3.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8a569c2fb840c53c13a2b8967c63621fafbd1a0e015b9c82f408c33d626a2fda", size = 1686916, upload-time = "2026-04-08T15:57:34.282Z" }, - { url = "https://files.pythonhosted.org/packages/63/45/c1ca4a1ad975de4727e52d3ffe641ae23e1d7a8ffaa8ff7a0477e1827b92/greenlet-3.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:207ba5b97ea8b0b60eb43ffcacf26969dd83726095161d676aac03ff913ee50d", size = 239821, upload-time = "2026-04-08T17:03:48.423Z" }, - { url = "https://files.pythonhosted.org/packages/71/c4/6f621023364d7e85a4769c014c8982f98053246d142420e0328980933ceb/greenlet-3.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:f8296d4e2b92af34ebde81085a01690f26a51eb9ac09a0fcadb331eb36dbc802", size = 236932, upload-time = "2026-04-08T17:04:33.551Z" }, - { url = "https://files.pythonhosted.org/packages/d4/8f/18d72b629783f5e8d045a76f5325c1e938e659a9e4da79c7dcd10169a48d/greenlet-3.4.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:d70012e51df2dbbccfaf63a40aaf9b40c8bed37c3e3a38751c926301ce538ece", size = 294681, upload-time = "2026-04-08T15:52:35.778Z" }, - { url = "https://files.pythonhosted.org/packages/9e/ad/5fa86ec46769c4153820d58a04062285b3b9e10ba3d461ee257b68dcbf53/greenlet-3.4.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a58bec0751f43068cd40cff31bb3ca02ad6000b3a51ca81367af4eb5abc480c8", size = 658899, upload-time = "2026-04-08T16:24:43.32Z" }, - { url = "https://files.pythonhosted.org/packages/43/f0/4e8174ca0e87ae748c409f055a1ba161038c43cc0a5a6f1433a26ac2e5bf/greenlet-3.4.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05fa0803561028f4b2e3b490ee41216a842eaee11aed004cc343a996d9523aa2", size = 665284, upload-time = "2026-04-08T16:31:02.833Z" }, - { url = "https://files.pythonhosted.org/packages/19/da/991cf7cd33662e2df92a1274b7eb4d61769294d38a1bba8a45f31364845e/greenlet-3.4.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e60d38719cb80b3ab5e85f9f1aed4960acfde09868af6762ccb27b260d68f4ed", size = 661861, upload-time = "2026-04-08T15:56:37.269Z" }, - { url = "https://files.pythonhosted.org/packages/36/c5/6c2c708e14db3d9caea4b459d8464f58c32047451142fe2cfd90e7458f41/greenlet-3.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7f50c804733b43eded05ae694691c9aa68bca7d0a867d67d4a3f514742a2d53f", size = 1622182, upload-time = "2026-04-08T16:26:24.777Z" }, - { url = "https://files.pythonhosted.org/packages/7a/4c/50c5fed19378e11a29fabab1f6be39ea95358f4a0a07e115a51ca93385d8/greenlet-3.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2d4f0635dc4aa638cda4b2f5a07ae9a2cff9280327b581a3fcb6f317b4fbc38a", size = 1685050, upload-time = "2026-04-08T15:57:36.453Z" }, - { url = "https://files.pythonhosted.org/packages/db/72/85ae954d734703ab48e622c59d4ce35d77ce840c265814af9c078cacc7aa/greenlet-3.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1a4a48f24681300c640f143ba7c404270e1ebbbcf34331d7104a4ff40f8ea705", size = 245554, upload-time = "2026-04-08T17:03:50.044Z" }, +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3c/3f/dbf99fb14bfeb88c28f16729215478c0e265cacd6dc22270c8f31bb6892f/greenlet-3.5.0.tar.gz", hash = "sha256:d419647372241bc68e957bf38d5c1f98852155e4146bd1e4121adea81f4f01e4", size = 196995, upload-time = "2026-04-27T13:37:15.544Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/0f/a91f143f356523ff682309732b175765a9bc2836fd7c081c2c67fedc1ad4/greenlet-3.5.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:8f1cc966c126639cd152fdaa52624d2655f492faa79e013fea161de3e6dda082", size = 284726, upload-time = "2026-04-27T12:20:51.402Z" }, + { url = "https://files.pythonhosted.org/packages/95/82/800646c7ffc5dbabd75ddd2f6b519bb898c0c9c969e5d0473bfe5d20bcce/greenlet-3.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:362624e6a8e5bca3b8233e45eef33903a100e9539a2b995c364d595dbc4018b3", size = 604264, upload-time = "2026-04-27T12:52:39.494Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ac/354867c0bba812fc33b15bc55aedafedd0aee3c7dd91dfca22444157dc0c/greenlet-3.5.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5ecd83806b0f4c2f53b1018e0005cd82269ea01d42befc0368730028d850ed1c", size = 616099, upload-time = "2026-04-27T12:59:39.623Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b0/815bece7399e01cadb69014219eebd0042339875c59a59b0820a46ece356/greenlet-3.5.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ff251e9a0279522e62f6176412869395a64ddf2b5c5f782ff609a8216a4e662", size = 615198, upload-time = "2026-04-27T12:25:25.928Z" }, + { url = "https://files.pythonhosted.org/packages/10/80/3b2c0a895d6698f6ddb31b07942ebfa982f3e30888bc5546a5b5990de8b2/greenlet-3.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d874e79afd41a96e11ff4c5d0bc90a80973e476fda1c2c64985667397df432b", size = 1574927, upload-time = "2026-04-27T12:53:25.81Z" }, + { url = "https://files.pythonhosted.org/packages/44/0e/f354af514a4c61454dbc68e44d47544a5a4d6317e30b77ddfa3a09f4c5f3/greenlet-3.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0ed006e4b86c59de7467eb2601cd1b77b5a7d657d1ee55e30fe30d76451edba4", size = 1642683, upload-time = "2026-04-27T12:25:23.9Z" }, + { url = "https://files.pythonhosted.org/packages/fa/6a/87f38255201e993a1915265ebb80cd7c2c78b04a45744995abbf6b259fd8/greenlet-3.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:703cb211b820dbffbbc55a16bfc6e4583a6e6e990f33a119d2cc8b83211119c8", size = 238115, upload-time = "2026-04-27T12:21:48.845Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f8/450fe3c5938fa737ea4d22699772e6e34e8e24431a47bf4e8a1ceed4a98e/greenlet-3.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:6c18dfb59c70f5a94acd271c72e90128c3c776e41e5f07767908c8c1b74ad339", size = 235017, upload-time = "2026-04-27T12:22:26.768Z" }, + { url = "https://files.pythonhosted.org/packages/ef/32/f2ce6d4cac3e55bc6173f92dbe627e782e1850f89d986c3606feb63aafa7/greenlet-3.5.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:db2910d3c809444e0a20147361f343fe2798e106af8d9d8506f5305302655a9f", size = 286228, upload-time = "2026-04-27T12:20:34.421Z" }, + { url = "https://files.pythonhosted.org/packages/b7/aa/caed9e5adf742315fc7be2a84196373aab4816e540e38ba0d76cb7584d68/greenlet-3.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ec9ea74e7268ace7f9aab1b1a4e730193fc661b39a993cd91c606c32d4a3628", size = 601775, upload-time = "2026-04-27T12:52:41.045Z" }, + { url = "https://files.pythonhosted.org/packages/c7/af/90ae08497400a941595d12774447f752d3dfe0fbb012e35b76bc5c0ff37e/greenlet-3.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54d243512da35485fc7a6bf3c178fdda6327a9d6506fcdd62b1abd1e41b2927b", size = 614436, upload-time = "2026-04-27T12:59:41.595Z" }, + { url = "https://files.pythonhosted.org/packages/2b/e0/2e13df68f367e2f9960616927d60857dd7e56aaadd59a47c644216b2f920/greenlet-3.5.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d280a7f5c331622c69f97eb167f33577ff2d1df282c41cd15907fc0a3ca198c", size = 611388, upload-time = "2026-04-27T12:25:28.008Z" }, + { url = "https://files.pythonhosted.org/packages/82/f7/393c64055132ac0d488ef6be549253b7e6274194863967ddc0bc8f5b87b8/greenlet-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1eb67d5adefb5bd2e182d42678a328979a209e4e82eb93575708185d31d1f588", size = 1570768, upload-time = "2026-04-27T12:53:28.099Z" }, + { url = "https://files.pythonhosted.org/packages/b8/4b/eaf7735253522cf56d1b74d672a58f54fc114702ceaf05def59aae72f6e1/greenlet-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2628d6c86f6cb0cb45e0c3c54058bbec559f57eaae699447748cb3928150577e", size = 1635983, upload-time = "2026-04-27T12:25:26.903Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fe/4fb3a0805bd5165da5ebf858da7cc01cce8061674106d2cf5bdab32cbfde/greenlet-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:d4d9f0624c775f2dfc56ba54d515a8c771044346852a918b405914f6b19d7fd8", size = 238840, upload-time = "2026-04-27T12:23:54.806Z" }, + { url = "https://files.pythonhosted.org/packages/cb/cb/baa584cb00532126ffe12d9787db0a60c5a4f55c27bfe2666df5d4c30a32/greenlet-3.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:83ed9f27f1680b50e89f40f6df348a290ea234b249a4003d366663a12eab94f2", size = 235615, upload-time = "2026-04-27T12:21:38.57Z" }, + { url = "https://files.pythonhosted.org/packages/0c/58/fc576f99037ce19c5aa16628e4c3226b6d1419f72a62c79f5f40576e6eb3/greenlet-3.5.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:5a5ed18de6a0f6cc7087f1563f6bd93fc7df1c19165ca01e9bde5a5dc281d106", size = 285066, upload-time = "2026-04-27T12:23:05.033Z" }, + { url = "https://files.pythonhosted.org/packages/4a/ba/b28ddbe6bfad6a8ac196ef0e8cff37bc65b79735995b9e410923fffeeb70/greenlet-3.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a717fbc46d8a354fa675f7c1e813485b6ba3885f9bef0cd56e5ba27d758ff5b", size = 604414, upload-time = "2026-04-27T12:52:42.358Z" }, + { url = "https://files.pythonhosted.org/packages/09/06/4b69f8f0b67603a8be2790e55107a190b376f2627fe0eaf5695d85ffb3cd/greenlet-3.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ddc090c5c1792b10246a78e8c2163ebbe04cf877f9d785c230a7b27b39ad038e", size = 617349, upload-time = "2026-04-27T12:59:43.32Z" }, + { url = "https://files.pythonhosted.org/packages/8a/17/a3918541fd0ddefe024a69de6d16aa7b46d36ac19562adaa63c7fa180eff/greenlet-3.5.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2094acd54b272cb6eae8c03dd87b3fa1820a4cef18d6889c378d503500a1dc13", size = 613927, upload-time = "2026-04-27T12:25:30.28Z" }, + { url = "https://files.pythonhosted.org/packages/ee/e1/bd0af6213c7dd33175d8a462d4c1fe1175124ebed4855bc1475a5b5242c2/greenlet-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5e05ba267789ea87b5a155cf0e810b1ab88bf18e9e8740813945ceb8ee4350ba", size = 1570893, upload-time = "2026-04-27T12:53:29.483Z" }, + { url = "https://files.pythonhosted.org/packages/9b/2a/0789702f864f5382cb476b93d7a9c823c10472658102ccd65f415747d2e2/greenlet-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0ecec963079cd58cbd14723582384f11f166fd58883c15dcbfb342e0bc9b5846", size = 1636060, upload-time = "2026-04-27T12:25:28.845Z" }, + { url = "https://files.pythonhosted.org/packages/b2/8f/22bf9df92bbff0eb07842b60f7e63bf7675a9742df628437a9f02d09137f/greenlet-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:728d9667d8f2f586644b748dbd9bb67e50d6a9381767d1357714ea6825bb3bf5", size = 238740, upload-time = "2026-04-27T12:24:01.341Z" }, + { url = "https://files.pythonhosted.org/packages/b6/b7/9c5c3d653bd4ff614277c049ac676422e2c557db47b4fe43e6313fc005dc/greenlet-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:47422135b1d308c14b2c6e758beedb1acd33bb91679f5670edf77bf46244722b", size = 235525, upload-time = "2026-04-27T12:23:12.308Z" }, + { url = "https://files.pythonhosted.org/packages/94/5e/a70f31e3e8d961c4ce589c15b28e4225d63704e431a23932a3808cbcc867/greenlet-3.5.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:f35807464c4c58c55f0d31dfa83c541a5615d825c2fe3d2b95360cf7c4e3c0a8", size = 285564, upload-time = "2026-04-27T12:23:08.555Z" }, + { url = "https://files.pythonhosted.org/packages/af/a6/046c0a28e21833e4086918218cfb3d8bed51c075a1b700f20b9d7861c0f4/greenlet-3.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55fa7ea52771be44af0de27d8b80c02cd18c2c3cddde6c847ecebdf72418b6a1", size = 651166, upload-time = "2026-04-27T12:52:43.644Z" }, + { url = "https://files.pythonhosted.org/packages/47/f8/4af27f71c5ff32a7fbc516adb46370d9c4ae2bc7bd3dc7d066ac542b4b15/greenlet-3.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a97e4821aa710603f94de0da25f25096454d78ffdace5dc77f3a006bc01abba3", size = 663792, upload-time = "2026-04-27T12:59:44.93Z" }, + { url = "https://files.pythonhosted.org/packages/a3/59/1bd6d7428d6ed9106efbb8c52310c60fd04f6672490f452aeaa3829aa436/greenlet-3.5.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f52a464e4ed91780bdfbbdd2b97197f3accaa629b98c200f4dffada759f3ae7", size = 660933, upload-time = "2026-04-27T12:25:33.276Z" }, + { url = "https://files.pythonhosted.org/packages/83/e4/b903e5a5fae1e8a28cdd32a0cfbfd560b668c25b692f67768822ddc5f40f/greenlet-3.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:762612baf1161ccb8437c0161c668a688223cba28e1bf038f4eb47b13e39ccdf", size = 1618401, upload-time = "2026-04-27T12:53:31.062Z" }, + { url = "https://files.pythonhosted.org/packages/0e/e3/5ec408a329acb854fb607a122e1ee5fb3ff649f9a97952948a90803c0d8e/greenlet-3.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:57a43c6079a89713522bc4bcb9f75070ecf5d3dbad7792bfe42239362cbf2a16", size = 1682038, upload-time = "2026-04-27T12:25:31.838Z" }, + { url = "https://files.pythonhosted.org/packages/91/20/6b165108058767ee643c55c5c4904d591a830ee2b3c7dbd359828fbc829f/greenlet-3.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:3bc59be3945ae9750b9e7d45067d01ae3fe90ea5f9ade99239dabdd6e28a5033", size = 239835, upload-time = "2026-04-27T12:24:54.136Z" }, + { url = "https://files.pythonhosted.org/packages/4e/62/1c498375cee177b55d980c1db319f26470e5309e54698c8f8fc06c0fd539/greenlet-3.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:a96fcee45e03fe30a62669fd16ab5c9d3c172660d3085605cb1e2d1280d3c988", size = 236862, upload-time = "2026-04-27T12:23:24.957Z" }, + { url = "https://files.pythonhosted.org/packages/78/a8/4522939255bb5409af4e87132f915446bf3622c2c292d14d3c38d128ae82/greenlet-3.5.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:a10a732421ab4fec934783ce3e54763470d0181db6e3468f9103a275c3ed1853", size = 293614, upload-time = "2026-04-27T12:24:12.874Z" }, + { url = "https://files.pythonhosted.org/packages/15/5e/8744c52e2c027b5a8772a01561934c8835f869733e101f62075c60430340/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fc391b1566f2907d17aaebe78f8855dc45675159a775fcf9e61f8ee0078e87f", size = 650723, upload-time = "2026-04-27T12:52:45.412Z" }, + { url = "https://files.pythonhosted.org/packages/00/ef/7b4c39c03cf46ceca512c5d3f914afd85aa30b2cc9a93015b0dd73e4be6c/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:680bd0e7ad5e8daa8a4aa89f68fd6adc834b8a8036dc256533f7e08f4a4b01f7", size = 656529, upload-time = "2026-04-27T12:59:46.295Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b5/c7768f352f5c010f92064d0063f987e7dc0cd290a6d92a34109015ce4aa1/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddb36c7d6c9c0a65f18c7258634e0c416c6ab59caac8c987b96f80c2ebda0112", size = 654364, upload-time = "2026-04-27T12:25:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/ef/d0/079ebe12e4b1fc758857ce5be1a5e73f06870f2101e52611d1e71925ce54/greenlet-3.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e5ddf316ced87539144621453c3aef229575825fe60c604e62bedc4003f372b2", size = 1614204, upload-time = "2026-04-27T12:53:32.618Z" }, + { url = "https://files.pythonhosted.org/packages/6d/89/6c2fb63df3596552d20e58fb4d96669243388cf680cff222758812c7bfaa/greenlet-3.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4a448128607be0de65342dc9b31be7f948ef4cc0bc8832069350abefd310a8f2", size = 1675480, upload-time = "2026-04-27T12:25:34.168Z" }, + { url = "https://files.pythonhosted.org/packages/15/32/77ee8a6c1564fc345a491a4e85b3bf360e4cf26eac98c4532d2fdb96e01f/greenlet-3.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d60097128cb0a1cab9ea541186ea13cd7b847b8449a7787c2e2350da0cb82d86", size = 245324, upload-time = "2026-04-27T12:24:40.295Z" }, ] [[package]] @@ -1197,7 +1197,7 @@ name = "importlib-metadata" version = "9.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "zipp" }, + { name = "zipp", marker = "python_full_version < '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a9/01/15bb152d77b21318514a96f43af312635eb2500c96b55398d020c93d86ea/importlib_metadata-9.0.0.tar.gz", hash = "sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc", size = 56405, upload-time = "2026-03-20T06:42:56.999Z" } wheels = [ @@ -1302,14 +1302,14 @@ wheels = [ [[package]] name = "jedi" -version = "0.19.2" +version = "0.20.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "parso" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/b7/a3635f6a2d7cf5b5dd98064fc1d5fbbafcb25477bcea204a3a92145d158b/jedi-0.20.0.tar.gz", hash = "sha256:c3f4ccbd276696f4b19c54618d4fb18f9fc24b0aef02acf704b23f487daa1011", size = 3119416, upload-time = "2026-05-01T23:38:47.814Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, + { url = "https://files.pythonhosted.org/packages/9a/93/242e2eab5fe682ffcb8b0084bde703a41d51e17ee0f3a31ff0d9d813620a/jedi-0.20.0-py2.py3-none-any.whl", hash = "sha256:7bdd9c2634f56713299976f4cbd59cb3fa92165cc5e05ea811fb253480728b67", size = 4884812, upload-time = "2026-05-01T23:38:43.919Z" }, ] [[package]] @@ -1487,7 +1487,7 @@ wheels = [ [[package]] name = "jupyterlab" -version = "4.5.6" +version = "4.5.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "async-lru" }, @@ -1504,9 +1504,9 @@ dependencies = [ { name = "tornado" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ac/d5/730628e03fff2e8a8e8ccdaedde1489ab1309f9a4fa2536248884e30b7c7/jupyterlab-4.5.6.tar.gz", hash = "sha256:642fe2cfe7f0f5922a8a558ba7a0d246c7bc133b708dfe43f7b3a826d163cf42", size = 23970670, upload-time = "2026-03-11T14:17:04.531Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/22/8440ec827762146e7cdecf04335bd348795899d29dc6ae82238707353a2c/jupyterlab-4.5.7.tar.gz", hash = "sha256:55a9822c4754da305f41e113452c68383e214dcf96de760146af89ce5d5117b0", size = 23992763, upload-time = "2026-04-29T16:43:51.328Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl", hash = "sha256:d6b3dac883aa4d9993348e0f8e95b24624f75099aed64eab6a4351a9cdd1e580", size = 12447124, upload-time = "2026-03-11T14:17:00.229Z" }, + { url = "https://files.pythonhosted.org/packages/3d/aa/537b8f7d80e799af19af35fb3ddfc970b951088a13c57dd9387dcfbb7f61/jupyterlab-4.5.7-py3-none-any.whl", hash = "sha256:fba4cb0e2c44a52859669d8c98b45de029d5e515f8407bf8534d2a8fc5f0964d", size = 12450123, upload-time = "2026-04-29T16:43:46.639Z" }, ] [[package]] @@ -1696,14 +1696,14 @@ wheels = [ [[package]] name = "mako" -version = "1.3.11" +version = "1.3.12" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/59/8a/805404d0c0b9f3d7a326475ca008db57aea9c5c9f2e1e39ed0faa335571c/mako-1.3.11.tar.gz", hash = "sha256:071eb4ab4c5010443152255d77db7faa6ce5916f35226eb02dc34479b6858069", size = 399811, upload-time = "2026-04-14T20:19:51.493Z" } +sdist = { url = "https://files.pythonhosted.org/packages/00/62/791b31e69ae182791ec67f04850f2f062716bbd205483d63a215f3e062d3/mako-1.3.12.tar.gz", hash = "sha256:9f778e93289bd410bb35daadeb4fc66d95a746f0b75777b942088b7fd7af550a", size = 400219, upload-time = "2026-04-28T19:01:08.512Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/a5/19d7aaa7e433713ffe881df33705925a196afb9532efc8475d26593921a6/mako-1.3.11-py3-none-any.whl", hash = "sha256:e372c6e333cf004aa736a15f425087ec977e1fcbd2966aae7f17c8dc1da27a77", size = 78503, upload-time = "2026-04-14T20:19:53.233Z" }, + { url = "https://files.pythonhosted.org/packages/bc/b1/a0ec7a5a9db730a08daef1fdfb8090435b82465abbf758a596f0ea88727e/mako-1.3.12-py3-none-any.whl", hash = "sha256:8f61569480282dbf557145ce441e4ba888be453c30989f879f0d652e39f53ea9", size = 78521, upload-time = "2026-04-28T19:01:10.393Z" }, ] [[package]] @@ -1867,11 +1867,11 @@ wheels = [ [[package]] name = "mistune" -version = "3.2.0" +version = "3.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/55/d01f0c4b45ade6536c51170b9043db8b2ec6ddf4a35c7ea3f5f559ac935b/mistune-3.2.0.tar.gz", hash = "sha256:708487c8a8cdd99c9d90eb3ed4c3ed961246ff78ac82f03418f5183ab70e398a", size = 95467, upload-time = "2025-12-23T11:36:34.994Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/84/620cc3f7e3adf6f5067e10f4dbae71295d8f9e16d5d3f9ef97c40f2f592c/mistune-3.2.1.tar.gz", hash = "sha256:7c8e5501d38bac1582e067e46c8343f17d57ea1aaa735823f3aba1fd59c88a28", size = 98003, upload-time = "2026-05-03T14:33:22.312Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl", hash = "sha256:febdc629a3c78616b94393c6580551e0e34cc289987ec6c35ed3f4be42d0eee1", size = 53598, upload-time = "2025-12-23T11:36:33.211Z" }, + { url = "https://files.pythonhosted.org/packages/2a/7f/a946aa4f8752b37102b41e64dca18a1976ac705c3a0d1dfe74d820a02552/mistune-3.2.1-py3-none-any.whl", hash = "sha256:78cdb0ba5e938053ccf63651b352508d2efa9411dc8810bfb05f2dc5140c0048", size = 53749, upload-time = "2026-05-03T14:33:20.551Z" }, ] [[package]] @@ -2284,11 +2284,11 @@ wheels = [ [[package]] name = "parso" -version = "0.8.6" +version = "0.8.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/81/76/a1e769043c0c0c9fe391b702539d594731a4362334cdf4dc25d0c09761e7/parso-0.8.6.tar.gz", hash = "sha256:2b9a0332696df97d454fa67b81618fd69c35a7b90327cbe6ba5c92d2c68a7bfd", size = 401621, upload-time = "2026-02-09T15:45:24.425Z" } +sdist = { url = "https://files.pythonhosted.org/packages/30/4b/90c937815137d43ce71ba043cd3566221e9df6b9c805f24b5d138c9d40a7/parso-0.8.7.tar.gz", hash = "sha256:eaaac4c9fdd5e9e8852dc778d2d7405897ec510f2a298071453e5e3a07914bb1", size = 401824, upload-time = "2026-05-01T23:13:02.138Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl", hash = "sha256:2c549f800b70a5c4952197248825584cb00f033b29c692671d3bf08bf380baff", size = 106894, upload-time = "2026-02-09T15:45:21.391Z" }, + { url = "https://files.pythonhosted.org/packages/99/5d/8268b644392ee874ee82a635cd0df1773de230bde356c38de28e298392cc/parso-0.8.7-py2.py3-none-any.whl", hash = "sha256:a8926eb2a1b915486941fdbd31e86a4baf88fe8c210f25f2f35ecec5b574ca1c", size = 107025, upload-time = "2026-05-01T23:12:58.867Z" }, ] [[package]] @@ -2829,7 +2829,8 @@ dependencies = [ { name = "torchmetrics" }, { name = "torchvision" }, { name = "tqdm" }, - { name = "zarr" }, + { name = "zarr", version = "3.1.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "zarr", version = "3.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] [package.optional-dependencies] @@ -3163,7 +3164,7 @@ dependencies = [ { name = "pillow" }, { name = "scipy" }, { name = "tifffile", version = "2026.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "tifffile", version = "2026.4.11", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "tifffile", version = "2026.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a1/b4/2528bb43c67d48053a7a649a9666432dc307d66ba02e3a6d5c40f46655df/scikit_image-0.26.0.tar.gz", hash = "sha256:f5f970ab04efad85c24714321fcc91613fcb64ef2a892a13167df2f3e59199fa", size = 22729739, upload-time = "2025-12-20T17:12:21.824Z" } wheels = [ @@ -3464,7 +3465,7 @@ wheels = [ [[package]] name = "tifffile" -version = "2026.4.11" +version = "2026.5.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", @@ -3473,9 +3474,9 @@ resolution-markers = [ dependencies = [ { name = "numpy", marker = "python_full_version >= '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d7/4a/e687f5957fead200faad58dbf9c9431a2bbb118040e96f5fb8a55f7ebc50/tifffile-2026.4.11.tar.gz", hash = "sha256:17758ff0c0d4db385792a083ad3ca51fcb0f4d942642f4d8f8bc1287fdcf17bc", size = 394956, upload-time = "2026-04-12T01:57:28.793Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/3e/695c7ab56be57814e369c1f38bc3f64b9dea0a83e867d00c0c9d613a9929/tifffile-2026.5.2.tar.gz", hash = "sha256:21b10227ede8493814a34676774797f721f487e36cb0530e7c3bd882caa87f5a", size = 429140, upload-time = "2026-05-02T20:19:31.497Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/9f/74f110b4271ded519c7add4341cbabc824de26817ff1c345b3109df9e99c/tifffile-2026.4.11-py3-none-any.whl", hash = "sha256:9b94ffeddb39e97601af646345e8808f885773de01b299e480ed6d3a41509ec9", size = 248227, upload-time = "2026-04-12T01:57:26.969Z" }, + { url = "https://files.pythonhosted.org/packages/b4/af/ce4df3ca29122d219c45d3e86e5ff9a9df03b8cf31afd76817b662c803a3/tifffile-2026.5.2-py3-none-any.whl", hash = "sha256:5129b53b826e768a5b1af26b765eeea75c2d0a227d2d12849617e0737588e105", size = 266420, upload-time = "2026-05-02T20:19:29.814Z" }, ] [[package]] @@ -3755,7 +3756,7 @@ wheels = [ [[package]] name = "virtualenv" -version = "21.2.4" +version = "21.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -3763,18 +3764,18 @@ dependencies = [ { name = "platformdirs" }, { name = "python-discovery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0c/98/3a7e644e19cb26133488caff231be390579860bbbb3da35913c49a1d0a46/virtualenv-21.2.4.tar.gz", hash = "sha256:b294ef68192638004d72524ce7ef303e9d0cf5a44c95ce2e54a7500a6381cada", size = 5850742, upload-time = "2026-04-14T22:15:31.438Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/8b/6331f7a7fe70131c301106ec1e7cf23e2501bf7d4ca3636805801ca191bb/virtualenv-21.3.0.tar.gz", hash = "sha256:733750db978ec95c2d8eb4feadaa57091002bce404cb39ba69899cf7bd28944e", size = 7614069, upload-time = "2026-04-27T17:05:58.927Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/8d/edd0bd910ff803c308ee9a6b7778621af0d10252219ad9f19ef4d4982a61/virtualenv-21.2.4-py3-none-any.whl", hash = "sha256:29d21e941795206138d0f22f4e45ff7050e5da6c6472299fb7103318763861ac", size = 5831232, upload-time = "2026-04-14T22:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/4b/eb/03bfb1299d4c4510329e470f13f9a4ce793df7fcb5a2fd3510f911066f61/virtualenv-21.3.0-py3-none-any.whl", hash = "sha256:4d28ee41f6d9ec8f1f00cd472b9ffbcedda1b3d3b9a575b5c94a2d004fd51bd7", size = 7594690, upload-time = "2026-04-27T17:05:55.468Z" }, ] [[package]] name = "wcwidth" -version = "0.6.0" +version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/35/a2/8e3becb46433538a38726c948d3399905a4c7cabd0df578ede5dc51f0ec2/wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159", size = 159684, upload-time = "2026-02-06T19:19:40.919Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/ee/afaf0f85a9a18fe47a67f1e4422ed6cf1fe642f0ae0a2f81166231303c52/wcwidth-0.7.0.tar.gz", hash = "sha256:90e3a7ea092341c44b99562e75d09e4d5160fe7a3974c6fb842a101a95e7eed0", size = 182132, upload-time = "2026-05-02T16:04:12.653Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189, upload-time = "2026-02-06T19:19:39.646Z" }, + { url = "https://files.pythonhosted.org/packages/41/52/e465037f5375f43533d1a80b6923955201596a99142ed524d77b571a1418/wcwidth-0.7.0-py3-none-any.whl", hash = "sha256:5d69154c429a82910e241c738cd0e2976fac8a2dd47a1a805f4afed1c0f136f2", size = 110825, upload-time = "2026-05-02T16:04:11.033Z" }, ] [[package]] @@ -3829,19 +3830,43 @@ wheels = [ name = "zarr" version = "3.1.6" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.12'", +] dependencies = [ - { name = "donfig" }, - { name = "google-crc32c" }, - { name = "numcodecs" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "typing-extensions" }, + { name = "donfig", marker = "python_full_version < '3.12'" }, + { name = "google-crc32c", marker = "python_full_version < '3.12'" }, + { name = "numcodecs", marker = "python_full_version < '3.12'" }, + { name = "numpy", marker = "python_full_version < '3.12'" }, + { name = "packaging", marker = "python_full_version < '3.12'" }, + { name = "typing-extensions", marker = "python_full_version < '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/31/5a/b8a0cf39a14c770c30bd1f2d120c54000c8cd9e84e8e79f38d9a7ce58071/zarr-3.1.6.tar.gz", hash = "sha256:d95e72cbea4b90e9a70679468b8266400331756232576ae2b43400ac5108d0eb", size = 386531, upload-time = "2026-03-23T17:25:18.748Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/de/7c/ba8ca8cbe9dbef8e83a95fc208fed8e6686c98b4719aaa0aa7f3d31fe390/zarr-3.1.6-py3-none-any.whl", hash = "sha256:b5a82c5079d1c3d4ee8f06746fa3b9a98a7d804300fa3f4be154362a33e1207e", size = 295655, upload-time = "2026-03-23T17:25:17.189Z" }, ] +[[package]] +name = "zarr" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version >= '3.12' and python_full_version < '3.14'", +] +dependencies = [ + { name = "donfig", marker = "python_full_version >= '3.12'" }, + { name = "google-crc32c", marker = "python_full_version >= '3.12'" }, + { name = "numcodecs", marker = "python_full_version >= '3.12'" }, + { name = "numpy", marker = "python_full_version >= '3.12'" }, + { name = "packaging", marker = "python_full_version >= '3.12'" }, + { name = "typing-extensions", marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/db/27/8f391a4304f503ab6f4df6e1724380ea2e35e78a5d1ba973ba2b1347df5b/zarr-3.2.0.tar.gz", hash = "sha256:5867fa8dd7910541075531368c8eaa6f35957ab5413c68c168830e83948665ed", size = 454948, upload-time = "2026-04-30T22:18:03.074Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/9e/2e99d08824f300046eba83b480d6be17f771f57eed80dd7c162381cbe4de/zarr-3.2.0-py3-none-any.whl", hash = "sha256:c693bd4ae24328f242e47e9e1ced221e919d9f62cad71030fd059e398320e555", size = 318784, upload-time = "2026-04-30T22:18:01.13Z" }, +] + [[package]] name = "zipp" version = "3.23.1" From e004a23da24b1db9a279ff80dc5f41f70b3a3c33 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 4 May 2026 11:40:56 -0700 Subject: [PATCH 274/335] fix: track widget/scripts/build.mjs (required for npm run build) --- .gitignore | 1 + widget/js/show2d/index.tsx | 147 ++++++++++++++++++++++++++++++------- 2 files changed, 120 insertions(+), 28 deletions(-) diff --git a/.gitignore b/.gitignore index 92d69e39..9d2da4dd 100644 --- a/.gitignore +++ b/.gitignore @@ -202,6 +202,7 @@ widget/.gitignore widget/docs/ widget/notebooks/ widget/scripts/ +!widget/scripts/build.mjs widget/tests/integration/ widget/tests/snapshots/ diff --git a/widget/js/show2d/index.tsx b/widget/js/show2d/index.tsx index 17120308..1693f177 100644 --- a/widget/js/show2d/index.tsx +++ b/widget/js/show2d/index.tsx @@ -585,6 +585,24 @@ function Show2D() { const [fftColormap, setFftColormap] = React.useState("inferno"); const [fftScaleMode, setFftScaleMode] = React.useState<"linear" | "log" | "power">("linear"); const [fftAuto, setFftAuto] = React.useState(true); + const [fftSmooth, setFftSmooth] = React.useState(true); + const [fftLinkedZoom, setFftLinkedZoom] = React.useState(false); + const [fftLinkPan, setFftLinkPan] = React.useState(false); + const [fftLinkedContrast, setFftLinkedContrast] = React.useState(true); + // Per-image FFT contrast (used when fftLinkedContrast=false) + const [fftContrastStates, setFftContrastStates] = React.useState>(new Map()); + const fftContrastFor = React.useCallback((idx: number) => { + if (fftLinkedContrast) return { vminPct: fftVminPct, vmaxPct: fftVmaxPct }; + return fftContrastStates.get(idx) || { vminPct: 0, vmaxPct: 100 }; + }, [fftLinkedContrast, fftVminPct, fftVmaxPct, fftContrastStates]); + const setFftContrastFor = React.useCallback((idx: number, val: { vminPct: number; vmaxPct: number }) => { + if (fftLinkedContrast) { + setFftVminPct(val.vminPct); + setFftVmaxPct(val.vmaxPct); + } else { + setFftContrastStates(prev => new Map(prev).set(idx, val)); + } + }, [fftLinkedContrast]); const [fftStats, setFftStats] = React.useState(null); const [fftShowColorbar, setFftShowColorbar] = React.useState(false); @@ -640,16 +658,34 @@ function Show2D() { const [linkedFftZoomState, setLinkedFftZoomState] = React.useState({ zoom: DEFAULT_FFT_ZOOM, panX: 0, panY: 0 }); const [fftPanningIdx, setFftPanningIdx] = React.useState(null); const getGalleryFftState = React.useCallback((idx: number) => { - if (linkedZoom) return linkedFftZoomState; - return galleryFftStates.get(idx) || { zoom: DEFAULT_FFT_ZOOM, panX: 0, panY: 0 }; - }, [linkedZoom, linkedFftZoomState, galleryFftStates]); + const per = galleryFftStates.get(idx) || { zoom: DEFAULT_FFT_ZOOM, panX: 0, panY: 0 }; + return { + zoom: fftLinkedZoom ? linkedFftZoomState.zoom : per.zoom, + panX: fftLinkPan ? linkedFftZoomState.panX : per.panX, + panY: fftLinkPan ? linkedFftZoomState.panY : per.panY, + }; + }, [fftLinkedZoom, fftLinkPan, linkedFftZoomState, galleryFftStates]); const setGalleryFftState = React.useCallback((idx: number, state: ZoomState) => { - if (linkedZoom) { - setLinkedFftZoomState(state); - } else { - setGalleryFftStates(prev => new Map(prev).set(idx, state)); + if (fftLinkedZoom || fftLinkPan) { + setLinkedFftZoomState(prev => ({ + zoom: fftLinkedZoom ? state.zoom : prev.zoom, + panX: fftLinkPan ? state.panX : prev.panX, + panY: fftLinkPan ? state.panY : prev.panY, + })); } - }, [linkedZoom]); + if (!fftLinkedZoom || !fftLinkPan) { + setGalleryFftStates(prev => { + const cur = prev.get(idx) || { zoom: DEFAULT_FFT_ZOOM, panX: 0, panY: 0 }; + const next = new Map(prev); + next.set(idx, { + zoom: fftLinkedZoom ? cur.zoom : state.zoom, + panX: fftLinkPan ? cur.panX : state.panX, + panY: fftLinkPan ? cur.panY : state.panY, + }); + return next; + }); + } + }, [fftLinkedZoom, fftLinkPan]); // Resizable state (gallery starts smaller) const [canvasSize, setCanvasSize] = React.useState(nImages > 1 ? GALLERY_IMAGE_TARGET : SINGLE_IMAGE_TARGET); @@ -875,12 +911,12 @@ function Show2D() { if (!off) return; const ctx = canvas.getContext("2d"); if (!ctx) return; - ctx.imageSmoothingEnabled = fftW < canvasW || fftH < canvasH; + ctx.imageSmoothingEnabled = fftSmooth; ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(off, 0, 0, fftW, fftH, 0, 0, canvasW, canvasH); })(); return () => { cancelled = true; }; - }, [effectiveShowFft, showDiffPanel, nImages, dataVersion, width, height, fftWindow, fftColormap, canvasW, canvasH]); + }, [effectiveShowFft, showDiffPanel, nImages, dataVersion, width, height, fftWindow, fftColormap, canvasW, canvasH, fftSmooth]); // Diff panels render — DYNAMIC. One per non-reference image: image[ref] − image[i]. // Computed at canvas resolution from raw float data, re-running on zoom/pan/align change. @@ -2075,7 +2111,7 @@ function Show2D() { const fftH = offscreen.height; // Use bilinear smoothing when FFT is smaller than canvas (avoids blocky upscaling) - ctx.imageSmoothingEnabled = fftW < canvasW || fftH < canvasH; + ctx.imageSmoothingEnabled = fftSmooth || (fftW < canvasW || fftH < canvasH); ctx.clearRect(0, 0, canvasW, canvasH); ctx.save(); @@ -2087,7 +2123,7 @@ function Show2D() { // Stretch cropped FFT to fill the full canvas (no layout change during drag) ctx.drawImage(offscreen, 0, 0, fftW, fftH, 0, 0, canvasW, canvasH); ctx.restore(); - }, [effectiveShowFft, isGallery, fftOffscreenVersion, canvasW, canvasH, fftZoom, fftPanX, fftPanY]); + }, [effectiveShowFft, isGallery, fftOffscreenVersion, canvasW, canvasH, fftZoom, fftPanX, fftPanY, fftSmooth]); // ------------------------------------------------------------------------- // Render FFT overlay (scale bar + colorbar + d-spacing marker) @@ -2317,7 +2353,8 @@ function Show2D() { } else { ({ min: displayMin, max: displayMax } = findDataRange(displayData)); } - const { vmin, vmax } = sliderRange(displayMin, displayMax, fftVminPct, fftVmaxPct); + const fc = fftContrastFor(idx); + const { vmin, vmax } = sliderRange(displayMin, displayMax, fc.vminPct, fc.vmaxPct); const offscreen = renderToOffscreen(displayData, fftW, fftH, lut, vmin, vmax); if (!offscreen) continue; @@ -2335,7 +2372,7 @@ function Show2D() { setFftDataRange(findDataRange(histData)); } setGalleryFftOffscreenVersion(v => v + 1); - }, [effectiveShowFft, isGallery, nImages, width, height, galleryFftMagVersion, fftColormap, fftScaleMode, fftAuto, fftVminPct, fftVmaxPct, selectedIdx]); + }, [effectiveShowFft, isGallery, nImages, width, height, galleryFftMagVersion, fftColormap, fftScaleMode, fftAuto, fftVminPct, fftVmaxPct, selectedIdx, fftLinkedContrast, fftContrastStates]); // Gallery FFT draw effect: cheap drawImage from cached offscreens (zoom/pan changes) React.useLayoutEffect(() => { @@ -2351,7 +2388,7 @@ function Show2D() { if (!ctx) continue; const { zoom, panX, panY } = getGalleryFftState(idx); - ctx.imageSmoothingEnabled = fftW < canvasW || fftH < canvasH; + ctx.imageSmoothingEnabled = fftSmooth; ctx.clearRect(0, 0, canvasW, canvasH); ctx.save(); const cx = canvasW / 2; @@ -2362,7 +2399,7 @@ function Show2D() { ctx.drawImage(offscreen, 0, 0, fftW, fftH, 0, 0, canvasW, canvasH); ctx.restore(); } - }, [effectiveShowFft, isGallery, nImages, canvasW, canvasH, width, height, galleryFftOffscreenVersion, galleryFftStates, linkedZoom, linkedFftZoomState]); + }, [effectiveShowFft, isGallery, nImages, canvasW, canvasH, width, height, galleryFftOffscreenVersion, galleryFftStates, fftLinkedZoom, linkedFftZoomState, fftSmooth]); // ------------------------------------------------------------------------- // Mouse Handlers for Zoom/Pan @@ -2526,7 +2563,7 @@ function Show2D() { // Gallery FFT zoom/pan handlers (only selected image's FFT responds) const handleGalleryFftWheel = (e: React.WheelEvent, idx: number) => { - if (isGallery && idx !== selectedIdx && !linkedZoom) return; + if (isGallery && idx !== selectedIdx && !fftLinkedZoom) return; e.preventDefault(); // Prevent page scroll when zooming FFT const zs = getGalleryFftState(idx); const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1; @@ -3636,7 +3673,7 @@ function Show2D() { { fftContainerRefs.current[i] = el; }} sx={{ mt: 0.5, position: "relative", border: `2px solid ${i === selectedIdx ? themeColors.accent : themeColors.border}`, borderRadius: 0, bgcolor: "#000", cursor: "grab" }} - onWheel={(i === selectedIdx || linkedZoom) ? (e) => handleGalleryFftWheel(e, i) : undefined} + onWheel={(i === selectedIdx || fftLinkedZoom) ? (e) => handleGalleryFftWheel(e, i) : undefined} onDoubleClick={() => setGalleryFftState(i, { zoom: DEFAULT_FFT_ZOOM, panX: 0, panY: 0 })} onMouseDown={(e) => handleGalleryFftMouseDown(e, i)} onMouseMove={(e) => handleGalleryFftMouseMove(e, i)} @@ -3747,13 +3784,10 @@ function Show2D() { FFT Scale: - setFftScaleMode(e.target.value as "linear" | "log")} size="small" sx={{ ...themedSelect, minWidth: 50, fontSize: 10 }} MenuProps={themedMenuProps}> Lin Log - Pow - Auto: - { setFftAuto(e.target.checked); }} size="small" sx={switchStyles.small} /> {roiFftActive && fftCropDims && ( <> Win: @@ -3765,11 +3799,69 @@ function Show2D() { {COLORMAP_NAMES.map((name) => ({name.charAt(0).toUpperCase() + name.slice(1)}))} + {/* FFT Row 2: Auto + Smooth + Link Zoom/Pan/Contrast (mirrors main image Row 2) */} + + Auto: + { setFftAuto(e.target.checked); }} size="small" sx={switchStyles.small} /> + Smooth: + { setFftSmooth(e.target.checked); }} size="small" sx={switchStyles.small} /> + {isGallery && ( + <> + Link: + Zoom + { setFftLinkedZoom(!fftLinkedZoom); }} size="small" sx={switchStyles.small} /> + Pan + { setFftLinkPan(!fftLinkPan); }} size="small" sx={switchStyles.small} /> + Contrast + { setFftLinkedContrast(!fftLinkedContrast); }} size="small" sx={switchStyles.small} /> + + )} + {( {fftHistogramData && ( - { setFftVminPct(min); setFftVmaxPct(max); }} width={110} height={58} theme={themeInfo.theme === "dark" ? "dark" : "light"} dataMin={fftDataRange.min} dataMax={fftDataRange.max} /> + !fftLinkedContrast && isGallery ? ( + + {Array.from({ length: nImages }).map((_, i) => { + const fc = fftContrastFor(i); + const mag = fftMagCacheGalleryRef.current[i]; + let perData: Float32Array | null = null; + if (mag) { + if (fftScaleMode === "log") perData = applyLogScale(mag); + else if (fftScaleMode === "power") { + perData = new Float32Array(mag.length); + for (let j = 0; j < mag.length; j++) perData[j] = Math.sqrt(mag[j]); + } else perData = mag; + } + const dr = perData ? findDataRange(perData) : fftDataRange; + return ( + { setFftContrastFor(i, { vminPct: min, vmaxPct: max }); }} + width={110} height={58} + theme={themeInfo.theme === "dark" ? "dark" : "light"} + dataMin={dr.min} dataMax={dr.max} + /> + ); + })} + + ) : (() => { + const fc = fftContrastFor(selectedIdx); + return ( + { setFftContrastFor(selectedIdx, { vminPct: min, vmaxPct: max }); }} + width={110} height={58} + theme={themeInfo.theme === "dark" ? "dark" : "light"} + dataMin={fftDataRange.min} dataMax={fftDataRange.max} + /> + ); + })() )} )} @@ -3798,7 +3890,7 @@ function Show2D() { {/* Controls: two rows left + histogram right, ROI below */} {showControls && ( - + {/* Top: control rows + histogram side by side */} @@ -3856,10 +3948,10 @@ function Show2D() { {/* Right: histograms. Unlinked + gallery → grid matching gallery layout (same effectiveNcols × rows). Linked or single image → one histogram. */} - {(imageHistogramData || imageHistogramBins) && ( + {(imageHistogramData || imageHistogramBins || (isGallery && !linkedContrast && rawDataRef.current)) && ( {(!linkedContrast && isGallery && rawDataRef.current) ? ( - + {Array.from({ length: nImages }).map((_, i) => { const cs = contrastStates.get(i) || { vminPct: 0, vmaxPct: 100 }; const raw = rawDataRef.current?.[i] || null; @@ -4060,10 +4152,9 @@ function Show2D() { {/* Row 1: Scale + Color + Colorbar */} Scale: - setFftScaleMode(e.target.value as "linear" | "log")} size="small" sx={{ ...themedSelect, minWidth: 50, fontSize: 10 }} MenuProps={themedMenuProps}> Lin Log - Pow Color: setFftScaleMode(e.target.value as "linear" | "log")} size="small" sx={{ ...themedSelect, minWidth: 50, fontSize: 10 }} MenuProps={themedMenuProps}> + @@ -4152,7 +4222,7 @@ function Show2D() { {/* Row 1: Scale + Color + Colorbar */} Scale: - setFftScaleMode(e.target.value as "linear" | "log")} size="small" sx={{ ...themedSelect, minWidth: 50, fontSize: 10 }} MenuProps={themedMenuProps}> Lin Log diff --git a/widget/src/quantem/widget/show2d.py b/widget/src/quantem/widget/show2d.py index f2cbed6a..edcd01db 100644 --- a/widget/src/quantem/widget/show2d.py +++ b/widget/src/quantem/widget/show2d.py @@ -303,8 +303,8 @@ def __init__( zoom: float = 1.0, zoom_row: float | None = None, zoom_col: float | None = None, - link_zoom: bool = False, - link_pan: bool = False, + link_zoom: bool | None = None, + link_pan: bool | None = None, link_contrast: bool = True, diff_mode: bool = False, view_box: tuple | list | None = None, @@ -362,6 +362,17 @@ def _init_sync(self, *, data, labels, title, cmap, sampling, units, if units is None and hasattr(data, "units"): units = list(data.units[-2:]) data = data.array + # Same auto-extract for list/tuple of Dataset2d (gallery from per-file load). + elif isinstance(data, (list, tuple)) and len(data) > 0 and ( + isinstance(data[0], (Dataset2d, Dataset3d)) or + (hasattr(data[0], "array") and hasattr(data[0], "sampling")) + ): + first = data[0] + if sampling is None: + sampling = tuple(float(s) for s in first.sampling[-2:]) + if units is None and hasattr(first, "units"): + units = list(first.units[-2:]) + data = [d.array for d in data] # Convert NumPy / PyTorch / list inputs to a NumPy array. if isinstance(data, list): @@ -434,8 +445,10 @@ def _init_sync(self, *, data, labels, title, cmap, sampling, units, self.initial_zoom = zoom self.zoom_row = zoom_row self.zoom_col = zoom_col - self.link_zoom = link_zoom - self.link_pan = link_pan + # Auto-link zoom + pan in gallery (n_images >= 2) so dragging one panel + # follows the other — typical compare/diff workflow. Single image: no-op. + self.link_zoom = (self.n_images >= 2) if link_zoom is None else link_zoom + self.link_pan = (self.n_images >= 2) if link_pan is None else link_pan self.link_contrast = link_contrast self.diff_mode = diff_mode if self.n_images >= 2 else False if show_fft and self.height * self.width > 2048 * 2048: From 88bbaecb552fbdc7babdf4170a222acfaf67d8ba Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 4 May 2026 17:18:03 -0700 Subject: [PATCH 277/335] update uv lock with all deps listed for now --- uv.lock | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/uv.lock b/uv.lock index 43a03fd0..58122fe4 100644 --- a/uv.lock +++ b/uv.lock @@ -1197,7 +1197,7 @@ name = "importlib-metadata" version = "9.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "zipp" }, + { name = "zipp", marker = "python_full_version < '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a9/01/15bb152d77b21318514a96f43af312635eb2500c96b55398d020c93d86ea/importlib_metadata-9.0.0.tar.gz", hash = "sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc", size = 56405, upload-time = "2026-03-20T06:42:56.999Z" } wheels = [ @@ -2897,10 +2897,22 @@ version = "0.0.1" source = { editable = "widget" } dependencies = [ { name = "anywidget" }, + { name = "matplotlib" }, + { name = "numpy" }, + { name = "pillow" }, + { name = "torch" }, + { name = "traitlets" }, ] [package.metadata] -requires-dist = [{ name = "anywidget", specifier = ">=0.9.0" }] +requires-dist = [ + { name = "anywidget", specifier = ">=0.9.0" }, + { name = "matplotlib", specifier = ">=3.7.0" }, + { name = "numpy", specifier = ">=2.0.0" }, + { name = "pillow", specifier = ">=10.0.0" }, + { name = "torch", specifier = ">=2.0.0" }, + { name = "traitlets", specifier = ">=5.0.0" }, +] [[package]] name = "referencing" From 8ecd3c7942b0986e12af1dc21669189847be720a Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Wed, 6 May 2026 17:25:05 -0700 Subject: [PATCH 278/335] converting ptycho models to work with new PPLR --- src/quantem/core/ml/optimizer_mixin.py | 2 +- .../diffractive_imaging/dataset_models.py | 6 ++--- .../diffractive_imaging/object_models.py | 25 ++++++++----------- .../diffractive_imaging/probe_models.py | 13 ++++------ 4 files changed, 20 insertions(+), 26 deletions(-) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index 9655eb76..55d013c9 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -648,7 +648,7 @@ def set_optimizer(self, opt_params: OptimizerType | dict | None = None) -> None: f"All parameter groups must use the same optimizer type, " f"got {type(opt_specs[0]).__name__} and {type(spec).__name__}" ) - self._optimizer = optimizer_cls(params) + self._optimizer = optimizer_cls(params) # type:ignore else: # Single-optimizer case: splat global hyperparameters optimizer_cls = self._optimizer_class_for(self._optimizer_params) diff --git a/src/quantem/diffractive_imaging/dataset_models.py b/src/quantem/diffractive_imaging/dataset_models.py index ef55c92e..585d5e4b 100644 --- a/src/quantem/diffractive_imaging/dataset_models.py +++ b/src/quantem/diffractive_imaging/dataset_models.py @@ -32,7 +32,7 @@ class PtychographyDatasetBase(AutoSerialize, OptimizerMixin, torch.nn.Module): _token = object() _patch_indices: torch.Tensor - # TODO update optimizers and such to allow for different lrs for different parameters + # TODO make this a PPLR so different lrs can be used for different parameters DEFAULT_LRS = { "descan": 1e-3, "scan_positions": 1e-3, @@ -95,7 +95,7 @@ def __init__( self._constraints = {} self._probe_energy = None - def get_optimization_parameters(self): + def get_optimization_parameters(self) -> list[dict[str, Any]]: """Get the combined descan and scan position parameters for optimization.""" params = [] if self.learn_descan: @@ -106,7 +106,7 @@ def get_optimization_parameters(self): raise RuntimeError( "No parameters to optimize for dataset: learn_descan and learn_scan_positions are both False" ) - return params + return [{"params": params}] def to(self, *args, **kwargs): """Move all relevant tensors to a different device.""" diff --git a/src/quantem/diffractive_imaging/object_models.py b/src/quantem/diffractive_imaging/object_models.py index 1c6fdab9..0860bf55 100644 --- a/src/quantem/diffractive_imaging/object_models.py +++ b/src/quantem/diffractive_imaging/object_models.py @@ -1,7 +1,7 @@ import math from abc import abstractmethod from copy import deepcopy -from typing import Callable, Literal, Self, Sequence, cast +from typing import Any, Callable, Literal, Self, Sequence, cast from warnings import warn import matplotlib.pyplot as plt @@ -194,7 +194,7 @@ def obj(self): @property @abstractmethod - def params(self): + def params(self) -> list[nn.Parameter]: raise NotImplementedError() @abstractmethod @@ -232,16 +232,13 @@ def to(self, *args, **kwargs): def name(self) -> str: raise NotImplementedError() - def get_optimization_parameters(self): + def get_optimization_parameters(self) -> list[dict[str, Any]]: """Get the parameters that should be optimized for this model.""" - try: - params = self.params - if params is None: - return [] - return params - except NotImplementedError: - # This happens when params is not implemented yet in abstract base + params = self.params + if params is None: return [] + else: + return [{"params": params}] # compatible with PPLR def _propagate_array( self, array: "torch.Tensor", propagator_array: "torch.Tensor" @@ -633,9 +630,9 @@ def num_slices(self) -> int: return self._obj.shape[0] @property - def params(self): + def params(self) -> list[nn.Parameter]: """optimization parameters""" - return self._obj + return [self._obj] @property def initial_obj(self): @@ -1025,9 +1022,9 @@ def to(self, *args, **kwargs): return self @property - def params(self): + def params(self) -> list[nn.Parameter]: """optimization parameters""" - return self.model.parameters() + return list(self.model.parameters()) def reset(self): """Reset the object model to its initial or pre-trained state""" diff --git a/src/quantem/diffractive_imaging/probe_models.py b/src/quantem/diffractive_imaging/probe_models.py index f47ea1f7..b72f1426 100644 --- a/src/quantem/diffractive_imaging/probe_models.py +++ b/src/quantem/diffractive_imaging/probe_models.py @@ -94,16 +94,13 @@ def __init__( if roi_shape is not None: self.roi_shape = roi_shape - def get_optimization_parameters(self): + def get_optimization_parameters(self) -> list[dict[str, Any]]: """Get the parameters that should be optimized for this model.""" - try: - params = self.params - if params is None: - return [] - return params - except NotImplementedError: - # This happens when params is not implemented yet in abstract base + params = self.params + if params is None: return [] + else: + return [{"params": params}] # compatible with PPLR @property def learn_probe_tilt(self) -> bool: From 0bd5ba33f1713821374e992e4afcaea35a7c915d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 6 May 2026 18:41:44 -0700 Subject: [PATCH 279/335] add hot pixel filtering in read 4dstem, default false --- src/quantem/core/io/file_readers.py | 34 ++++++++++++++++++++++++++++- tests/utils/test_filter.py | 24 ++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 tests/utils/test_filter.py diff --git a/src/quantem/core/io/file_readers.py b/src/quantem/core/io/file_readers.py index 98e9e8fa..9feac57a 100644 --- a/src/quantem/core/io/file_readers.py +++ b/src/quantem/core/io/file_readers.py @@ -15,6 +15,7 @@ def read_4dstem( file_path: str | PathLike, file_type: str | None = None, dataset_index: int | None = None, + hot_pixel_filter: bool = False, **kwargs, ) -> Dataset4dstem: """ @@ -30,6 +31,11 @@ def read_4dstem( dataset_index: int, optional Index of the dataset to load if file contains multiple datasets. If None, automatically selects the first 4D dataset found. + hot_pixel_filter: bool, optional + If True, detect and replace hot detector pixels immediately after + loading using `quantem.core.utils.filter.filter_hot_pixels` with its + default parameters. For custom thresholds, call `filter_hot_pixels` + directly on the array. **kwargs: dict Additional keyword arguments to pass to the file reader. @@ -49,6 +55,26 @@ def read_4dstem( Returns -------- Dataset4dstem + + Examples + -------- + Load an Arina 4D-STEM master file with the default hot pixel filter: + + >>> from quantem.core.io import read_4dstem + >>> ds = read_4dstem( + ... '/path/to/gold_013_master.h5', + ... file_type='arina', + ... ) + >>> ds.array.shape + (256, 256, 192, 192) + + Skip the filter to inspect the raw detector output: + + >>> ds_raw = read_4dstem( + ... '/path/to/gold_013_master.h5', + ... file_type='arina', + ... hot_pixel_filter=False, + ... ) """ if file_type is None: file_type = Path(file_path).suffix.lower().lstrip(".") @@ -104,8 +130,14 @@ def read_4dstem( else ["pixels" if ax["units"] == "1" else ax["units"] for ax in imported_axes] ) + array = imported_data["data"] + if hot_pixel_filter: + from quantem.core.utils.filter import filter_hot_pixels + + array = filter_hot_pixels(array) + dataset = Dataset4dstem.from_array( - array=imported_data["data"], + array=array, sampling=sampling, origin=origin, units=units, diff --git a/tests/utils/test_filter.py b/tests/utils/test_filter.py new file mode 100644 index 00000000..dc213bc8 --- /dev/null +++ b/tests/utils/test_filter.py @@ -0,0 +1,24 @@ +"""Tests for `filter_hot_pixels`. + +A microscopist running 4D-STEM sees most detector pixels read out at low +counts (~1-100), but a few are stuck near saturation (~60000) regardless of +incident intensity. They must be removed before virtual imaging or dp_max +analysis. +""" + +import torch + +from quantem.core.utils.filter import filter_hot_pixels + + +def test_filter_hot_pixels_replaces_stuck_detector_pixels_with_local_median(): + """Stuck pixels should drop from 60000 back into the local bulk regime (<1000).""" + ds = torch.randint(1, 101, size=(64, 64, 32, 32), dtype=torch.int32) + # Assume these 3 places have hot pixels that we later want to remove + hot_coords = [(5, 7), (18, 24), (29, 3)] + for r, c in hot_coords: + ds[:, :, r, c] = 60000 + filtered = filter_hot_pixels(ds.numpy()) + dp_max = filtered.max(axis=(0, 1)) + # no pixels should have value 101 + assert dp_max.max() < 101, f"hot pixels still present, dp_max max={dp_max.max()}" From 31a0baef7098c56def59618960bc59092f078e65 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 6 May 2026 18:59:11 -0700 Subject: [PATCH 280/335] update docstring, hot pixel being false by default --- src/quantem/core/io/file_readers.py | 8 ++++---- tests/utils/test_filter.py | 14 +++++--------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/quantem/core/io/file_readers.py b/src/quantem/core/io/file_readers.py index 9feac57a..4fe72645 100644 --- a/src/quantem/core/io/file_readers.py +++ b/src/quantem/core/io/file_readers.py @@ -58,7 +58,7 @@ def read_4dstem( Examples -------- - Load an Arina 4D-STEM master file with the default hot pixel filter: + Load a raw Arina 4D-STEM master file: >>> from quantem.core.io import read_4dstem >>> ds = read_4dstem( @@ -68,12 +68,12 @@ def read_4dstem( >>> ds.array.shape (256, 256, 192, 192) - Skip the filter to inspect the raw detector output: + Enable the hot pixel filter to repair stuck detector pixels on load: - >>> ds_raw = read_4dstem( + >>> ds = read_4dstem( ... '/path/to/gold_013_master.h5', ... file_type='arina', - ... hot_pixel_filter=False, + ... hot_pixel_filter=True, ... ) """ if file_type is None: diff --git a/tests/utils/test_filter.py b/tests/utils/test_filter.py index dc213bc8..1c43cd0c 100644 --- a/tests/utils/test_filter.py +++ b/tests/utils/test_filter.py @@ -1,18 +1,14 @@ -"""Tests for `filter_hot_pixels`. - -A microscopist running 4D-STEM sees most detector pixels read out at low -counts (~1-100), but a few are stuck near saturation (~60000) regardless of -incident intensity. They must be removed before virtual imaging or dp_max -analysis. -""" - import torch from quantem.core.utils.filter import filter_hot_pixels def test_filter_hot_pixels_replaces_stuck_detector_pixels_with_local_median(): - """Stuck pixels should drop from 60000 back into the local bulk regime (<1000).""" + """A microscopist running 4D-STEM sees most detector pixels read out at low + counts (~1-100), but a few are stuck near saturation (~60000) regardless + of incident intensity. After `filter_hot_pixels`, those stuck pixels + should drop back into the local bulk regime (<=100). + """ ds = torch.randint(1, 101, size=(64, 64, 32, 32), dtype=torch.int32) # Assume these 3 places have hot pixels that we later want to remove hot_coords = [(5, 7), (18, 24), (29, 3)] From a73dcc41fb84bc51724b3cd32f7ee257cc3a7891 Mon Sep 17 00:00:00 2001 From: henryhng Date: Thu, 7 May 2026 08:20:16 +0000 Subject: [PATCH 281/335] fix set_image --- widget/src/quantem/widget/show4dstem.py | 51 ++++++++++++++++--------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/widget/src/quantem/widget/show4dstem.py b/widget/src/quantem/widget/show4dstem.py index 8cd0cb58..01d39615 100644 --- a/widget/src/quantem/widget/show4dstem.py +++ b/widget/src/quantem/widget/show4dstem.py @@ -586,34 +586,51 @@ def set_image(self, data, scan_shape=None): """Replace the 4D-STEM data. Preserves all display and ROI settings.""" if hasattr(data, "sampling") and hasattr(data, "array"): data = data.array - data_np = to_numpy(data) - saturated_value = 65535.0 if data_np.dtype == np.uint16 else 255.0 if data_np.dtype == np.uint8 else None - if saturated_value is not None: - data_np[data_np >= saturated_value] = 0 - if data_np.ndim == 5: - self.n_frames = data_np.shape[0] - self._scan_shape = (data_np.shape[1], data_np.shape[2]) - self._det_shape = (data_np.shape[3], data_np.shape[4]) + if isinstance(data, torch.Tensor): + self._device = data.device + self._data = data if data.device == self._device else data.to(self._device) + shape = tuple(data.shape) + ndim = len(shape) + view_dtype = ( + torch.int16 if data.dtype == torch.uint16 + else torch.int8 if data.dtype == torch.uint8 + else None + ) + if view_dtype is not None: + view = self._data.view(view_dtype).reshape(-1, *shape[-2:]) + rows = view.shape[0] + pos_per_chunk = max(1, _CHUNK_BYTE_BUDGET // max(1, shape[-2] * shape[-1])) + for i in range(0, rows, pos_per_chunk): + view[i:i + pos_per_chunk].masked_fill_(view[i:i + pos_per_chunk] == -1, 0) + else: + data_np = to_numpy(data) + saturated_value = 65535.0 if data_np.dtype == np.uint16 else 255.0 if data_np.dtype == np.uint8 else None + if saturated_value is not None: + data_np[data_np >= saturated_value] = 0 + shape = data_np.shape + ndim = data_np.ndim self._data = torch.from_numpy(data_np).to(self._device) - elif data_np.ndim == 3: + if ndim == 5: + self.n_frames = shape[0] + self._scan_shape = (shape[1], shape[2]) + self._det_shape = (shape[3], shape[4]) + elif ndim == 3: self.n_frames = 1 if scan_shape is not None: self._scan_shape = scan_shape else: - n = data_np.shape[0] + n = shape[0] side = int(n ** 0.5) if side * side != n: raise ValueError(f"Cannot infer square scan_shape from N={n}. Provide scan_shape explicitly.") self._scan_shape = (side, side) - self._det_shape = (data_np.shape[1], data_np.shape[2]) - self._data = torch.from_numpy(data_np).to(self._device) - elif data_np.ndim == 4: + self._det_shape = (shape[1], shape[2]) + elif ndim == 4: self.n_frames = 1 - self._scan_shape = (data_np.shape[0], data_np.shape[1]) - self._det_shape = (data_np.shape[2], data_np.shape[3]) - self._data = torch.from_numpy(data_np).to(self._device) + self._scan_shape = (shape[0], shape[1]) + self._det_shape = (shape[2], shape[3]) else: - raise ValueError(f"Show4DSTEM expects a 3D, 4D, or 5D array. Got {data_np.ndim}D. See documentation for accepted shapes.") + raise ValueError(f"Show4DSTEM expects a 3D, 4D, or 5D array. Got {ndim}D. See documentation for accepted shapes.") self.frame_idx = 0 self.shape_rows = self._scan_shape[0] self.shape_cols = self._scan_shape[1] From f5923843ed8f6adae6748d066654b31b1d51b922 Mon Sep 17 00:00:00 2001 From: henryhng Date: Thu, 7 May 2026 08:37:06 +0000 Subject: [PATCH 282/335] fix ruff lint errors --- widget/src/quantem/widget/show2d.py | 14 +++++----- widget/src/quantem/widget/show4dstem.py | 37 ++++++++++--------------- widget/src/quantem/widget/state.py | 1 - widget/tests/test_state_dict.py | 2 -- 4 files changed, 22 insertions(+), 32 deletions(-) diff --git a/widget/src/quantem/widget/show2d.py b/widget/src/quantem/widget/show2d.py index edcd01db..793608ea 100644 --- a/widget/src/quantem/widget/show2d.py +++ b/widget/src/quantem/widget/show2d.py @@ -5,12 +5,12 @@ Unlike Show3D (interactive), Show2D focuses on static visualization. """ +import base64 +import io import json +import math import os import pathlib -import io -import base64 -import math import warnings from enum import StrEnum from typing import Self @@ -21,11 +21,10 @@ import matplotlib.pyplot as plt import numpy as np import traitlets - -from quantem.core.datastructures import Dataset2d, Dataset3d -from quantem.widget.array_utils import to_numpy, _resize_image +from quantem.widget.array_utils import _resize_image, to_numpy from quantem.widget.state import resolve_widget_version, save_state_file, unwrap_state_payload +from quantem.core.datastructures import Dataset2d, Dataset3d def _reject_unknown_kwargs(cls, kwargs: dict) -> None: @@ -467,7 +466,8 @@ def _init_sync(self, *, data, labels, title, cmap, sampling, units, if isinstance(vmin, (list, tuple)) or isinstance(vmax, (list, tuple)): n = self.n_images def _expand(v): - if v is None: return [None] * n + if v is None: + return [None] * n if isinstance(v, (list, tuple)): if len(v) != n: raise ValueError(f"vmin/vmax list has length {len(v)} but n_images is {n}. Pass a list of length {n} or a scalar to apply uniformly.") diff --git a/widget/src/quantem/widget/show4dstem.py b/widget/src/quantem/widget/show4dstem.py index 8cd0cb58..97efa8a8 100644 --- a/widget/src/quantem/widget/show4dstem.py +++ b/widget/src/quantem/widget/show4dstem.py @@ -11,27 +11,19 @@ widget = Show4DSTEM(dataset) """ -import hashlib import json import math import pathlib import time -from datetime import datetime, timezone -from typing import Any, Self -from uuid import uuid4 +from typing import TYPE_CHECKING, Any, Self + +if TYPE_CHECKING: + from quantem.core.datastructures import Dataset4dstem import anywidget import numpy as np import torch import traitlets - -# Cap transient chunk memory at ~600 MB regardless of detector size. -# A 4096 × 192² × 4 byte float32 cast = 600 MB; a 4096 × 256² × 4 byte cast -# would be 1.0 GB. _chunk_rows() picks an N-rows-per-chunk that keeps the -# transient under this cap. -_CHUNK_BYTE_BUDGET = 600 * 1024 * 1024 - -from quantem.core.config import validate_device from quantem.widget.array_utils import to_numpy from quantem.widget.state import ( build_json_header, @@ -40,11 +32,19 @@ unwrap_state_payload, ) +from quantem.core.config import validate_device + +# Cap transient chunk memory at ~600 MB regardless of detector size. +_CHUNK_BYTE_BUDGET = 600 * 1024 * 1024 + def _format_memory(nbytes: int) -> str: - if nbytes >= 1 << 30: return f"{nbytes / (1 << 30):.1f} GB" - if nbytes >= 1 << 20: return f"{nbytes / (1 << 20):.0f} MB" - if nbytes >= 1 << 10: return f"{nbytes / (1 << 10):.0f} KB" + if nbytes >= 1 << 30: + return f"{nbytes / (1 << 30):.1f} GB" + if nbytes >= 1 << 20: + return f"{nbytes / (1 << 20):.0f} MB" + if nbytes >= 1 << 10: + return f"{nbytes / (1 << 10):.0f} KB" return f"{nbytes} B" @@ -395,7 +395,6 @@ def __init__( # Handle dimensionality — 5D loads eagerly for instant frame switching # Resolve shape from whichever input path we took shape = tuple(self._data_pre.shape) if self._data_pre is not None else data_np.shape - size_elements = int(np.prod(shape)) ndim = len(shape) _tc = time.perf_counter() if ndim == 5: @@ -1842,9 +1841,6 @@ def save_image( prev_row, prev_col = self.pos_row, self.pos_col prev_frame = self.frame_idx meta_path: pathlib.Path | None = None - export_row = int(self.pos_row) - export_col = int(self.pos_col) - export_frame = int(self.frame_idx) try: if frame_idx is not None: @@ -1853,9 +1849,6 @@ def save_image( row, col = self._validate_position(position) self.pos_row = row self.pos_col = col - export_row = int(self.pos_row) - export_col = int(self.pos_col) - export_frame = int(self.frame_idx) if view_key == "diffraction": image, dp_meta = self._render_panel_image( diff --git a/widget/src/quantem/widget/state.py b/widget/src/quantem/widget/state.py index d7710287..83cb6b2f 100644 --- a/widget/src/quantem/widget/state.py +++ b/widget/src/quantem/widget/state.py @@ -3,7 +3,6 @@ import pathlib from typing import Any - JSON_METADATA_VERSION = "1.0" diff --git a/widget/tests/test_state_dict.py b/widget/tests/test_state_dict.py index 9247c979..60614941 100644 --- a/widget/tests/test_state_dict.py +++ b/widget/tests/test_state_dict.py @@ -11,11 +11,9 @@ updating the state_dict roundtrip path. """ import json -import pathlib import numpy as np import pytest - from quantem.widget import Show2D, Show4DSTEM From 5b364c65a03e47ea9eaaf45229bf1867cdf3826b Mon Sep 17 00:00:00 2001 From: henryhng Date: Sat, 9 May 2026 08:21:04 +0000 Subject: [PATCH 283/335] remove set_image() --- widget/src/quantem/widget/show4dstem.py | 74 ------------------------- 1 file changed, 74 deletions(-) diff --git a/widget/src/quantem/widget/show4dstem.py b/widget/src/quantem/widget/show4dstem.py index 01d39615..5ffca3a1 100644 --- a/widget/src/quantem/widget/show4dstem.py +++ b/widget/src/quantem/widget/show4dstem.py @@ -582,80 +582,6 @@ def __init__( shape = "x".join(str(s) for s in self._data.shape) print(f"Show4DSTEM: {shape} {self._device}, {time.perf_counter() - _t0:.2f}s total") - def set_image(self, data, scan_shape=None): - """Replace the 4D-STEM data. Preserves all display and ROI settings.""" - if hasattr(data, "sampling") and hasattr(data, "array"): - data = data.array - if isinstance(data, torch.Tensor): - self._device = data.device - self._data = data if data.device == self._device else data.to(self._device) - shape = tuple(data.shape) - ndim = len(shape) - view_dtype = ( - torch.int16 if data.dtype == torch.uint16 - else torch.int8 if data.dtype == torch.uint8 - else None - ) - if view_dtype is not None: - view = self._data.view(view_dtype).reshape(-1, *shape[-2:]) - rows = view.shape[0] - pos_per_chunk = max(1, _CHUNK_BYTE_BUDGET // max(1, shape[-2] * shape[-1])) - for i in range(0, rows, pos_per_chunk): - view[i:i + pos_per_chunk].masked_fill_(view[i:i + pos_per_chunk] == -1, 0) - else: - data_np = to_numpy(data) - saturated_value = 65535.0 if data_np.dtype == np.uint16 else 255.0 if data_np.dtype == np.uint8 else None - if saturated_value is not None: - data_np[data_np >= saturated_value] = 0 - shape = data_np.shape - ndim = data_np.ndim - self._data = torch.from_numpy(data_np).to(self._device) - if ndim == 5: - self.n_frames = shape[0] - self._scan_shape = (shape[1], shape[2]) - self._det_shape = (shape[3], shape[4]) - elif ndim == 3: - self.n_frames = 1 - if scan_shape is not None: - self._scan_shape = scan_shape - else: - n = shape[0] - side = int(n ** 0.5) - if side * side != n: - raise ValueError(f"Cannot infer square scan_shape from N={n}. Provide scan_shape explicitly.") - self._scan_shape = (side, side) - self._det_shape = (shape[1], shape[2]) - elif ndim == 4: - self.n_frames = 1 - self._scan_shape = (shape[0], shape[1]) - self._det_shape = (shape[2], shape[3]) - else: - raise ValueError(f"Show4DSTEM expects a 3D, 4D, or 5D array. Got {ndim}D. See documentation for accepted shapes.") - self.frame_idx = 0 - self.shape_rows = self._scan_shape[0] - self.shape_cols = self._scan_shape[1] - self.det_rows = self._det_shape[0] - self.det_cols = self._det_shape[1] - first_frame = self._data[0] if self._data.ndim == 5 else self._data - first_frame_sample = first_frame[0] if first_frame.ndim >= 3 else first_frame - if not torch.is_floating_point(first_frame_sample): - first_frame_sample = first_frame_sample.float() - self.dp_global_min = max(float(first_frame_sample.min()), MIN_LOG_VALUE) - self.dp_global_max = float(first_frame_sample.max()) - self._det_row_coords = torch.arange(self.det_rows, device=self._device, dtype=torch.float32)[:, None] - self._det_col_coords = torch.arange(self.det_cols, device=self._device, dtype=torch.float32)[None, :] - self._scan_row_coords = torch.arange(self.shape_rows, device=self._device, dtype=torch.float32)[:, None] - self._scan_col_coords = torch.arange(self.shape_cols, device=self._device, dtype=torch.float32)[None, :] - self._cached_bf_virtual = None - self._cached_abf_virtual = None - self._cached_adf_virtual = None - self._cached_haadf_virtual = None - with self.hold_trait_notifications(): - self.pos_row = min(self.pos_row, self.shape_rows - 1) - self.pos_col = min(self.pos_col, self.shape_cols - 1) - self._compute_virtual_image_from_roi() - self._update_frame() - def __repr__(self) -> str: shape = ( f"({self.n_frames}, {self.shape_rows}, {self.shape_cols}, {self.det_rows}, {self.det_cols})" From c57d1d028c06861296e841b0bd9761f2e3995a87 Mon Sep 17 00:00:00 2001 From: henryhng Date: Sat, 9 May 2026 08:42:02 +0000 Subject: [PATCH 284/335] remove set_image() from Show2D --- widget/src/quantem/widget/show2d.py | 60 ----------------------------- 1 file changed, 60 deletions(-) diff --git a/widget/src/quantem/widget/show2d.py b/widget/src/quantem/widget/show2d.py index edcd01db..9c074c40 100644 --- a/widget/src/quantem/widget/show2d.py +++ b/widget/src/quantem/widget/show2d.py @@ -575,66 +575,6 @@ def _on_first_render(self, change): except (ValueError, KeyError): pass - def set_image(self, data, labels=None): - """Replace the displayed image(s). Preserves all display settings.""" - if hasattr(data, "array") and hasattr(data, "name") and hasattr(data, "sampling"): - data = data.array - if isinstance(data, list): - images = [to_numpy(d) for d in data] - shapes = [img.shape for img in images] - if len(set(shapes)) > 1: - max_h = max(s[0] for s in shapes) - max_w = max(s[1] for s in shapes) - images = [_resize_image(img, max_h, max_w) for img in images] - data = np.stack(images) - else: - data = to_numpy(data) - if data.ndim == 2: - data = data[np.newaxis, ...] - if data.dtype == np.float32: - self._data = np.array(data, dtype=np.float32, copy=True) - else: - self._data = np.asarray(data, dtype=np.float32) - self._data_original = [self._data[i] for i in range(self._data.shape[0])] - self._originals_are_views = True - self.n_images = int(data.shape[0]) - - # Auto-bin for display (reuse existing _display_bin or recompute) - gpu_budget_mb = 2500 - per_image_mb = (data.shape[1] * data.shape[2] * 4 * 3) / (1024 * 1024) - total_mb = self.n_images * per_image_mb - self._display_bin = 1 - if total_mb > gpu_budget_mb: - for bf in [2, 4, 8]: - if total_mb / (bf * bf) <= gpu_budget_mb: - self._display_bin = bf - break - else: - self._display_bin = 8 - - if self._display_bin > 1: - from quantem.widget.array_utils import bin2d - self._display_data = bin2d(self._data, factor=self._display_bin, mode="mean") - self.height = int(self._display_data.shape[1]) - self.width = int(self._display_data.shape[2]) - self._display_bin_factor = self._display_bin - if getattr(self, "_verbose", True): - print(f" Display bin {self._display_bin}×: {data.shape[1]}×{data.shape[2]} → {self.height}×{self.width}") - else: - self._display_data = self._data - self.height = int(data.shape[1]) - self.width = int(data.shape[2]) - self._display_bin_factor = 1 - - self.image_rotations = [0] * self.n_images - if labels is not None: - self.labels = list(labels) - else: - self.labels = [f"Image {i+1}" for i in range(self.n_images)] - self.selected_idx = 0 - self._compute_all_stats() - self._update_all_frames() - def __repr__(self) -> str: if self.n_images > 1: shape = f"{self.n_images}×{self.height}×{self.width}" From c36be765a113ba0b6521682531fbd152ea51a736 Mon Sep 17 00:00:00 2001 From: henryhng Date: Sat, 9 May 2026 08:51:17 +0000 Subject: [PATCH 285/335] invalidate virtual image cache on calibration change, mkdir in save_state_file --- widget/src/quantem/widget/show4dstem.py | 8 ++++++++ widget/src/quantem/widget/state.py | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/widget/src/quantem/widget/show4dstem.py b/widget/src/quantem/widget/show4dstem.py index 5ffca3a1..5b25647c 100644 --- a/widget/src/quantem/widget/show4dstem.py +++ b/widget/src/quantem/widget/show4dstem.py @@ -527,6 +527,8 @@ def __init__( ]) # Observe compound roi_center for batched updates from JS self.observe(self._on_roi_center_change, names=["roi_center"]) + # Invalidate precomputed virtual image caches when calibration changes + self.observe(self._on_calibration_change, names=["center_row", "center_col", "bf_radius"]) # Initialize default ROI at BF center — batch to avoid redundant observer callbacks with self.hold_trait_notifications(): @@ -2162,6 +2164,12 @@ def _create_rect_mask(self, cx: float, cy: float, half_width: float, half_height mask = (torch.abs(self._det_col_coords - cx) <= half_width) & (torch.abs(self._det_row_coords - cy) <= half_height) return mask + def _on_calibration_change(self, change=None): + self._cached_bf_virtual = None + self._cached_abf_virtual = None + self._cached_adf_virtual = None + self._cached_haadf_virtual = None + def _precompute_common_virtual_images(self): """Pre-compute BF/ABF/ADF/HAADF virtual image bytes. Annular ranges match apply_preset() so the cache always hits on preset clicks.""" diff --git a/widget/src/quantem/widget/state.py b/widget/src/quantem/widget/state.py index d7710287..af9344b8 100644 --- a/widget/src/quantem/widget/state.py +++ b/widget/src/quantem/widget/state.py @@ -42,4 +42,6 @@ def unwrap_state_payload(payload: dict[str, Any], *, require_envelope: bool = Fa def save_state_file(path: str | pathlib.Path, widget_name: str, state: dict[str, Any]) -> None: - pathlib.Path(path).write_text(json.dumps(wrap_state_dict(widget_name, state), indent=2)) + p = pathlib.Path(path) + p.parent.mkdir(parents=True, exist_ok=True) + p.write_text(json.dumps(wrap_state_dict(widget_name, state), indent=2)) From 5a6e8a8cb85a960f7f4f481ade06349016e1c450 Mon Sep 17 00:00:00 2001 From: henryhng Date: Sat, 9 May 2026 09:19:14 +0000 Subject: [PATCH 286/335] lazy import torch in array_utils --- widget/src/quantem/widget/array_utils.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/widget/src/quantem/widget/array_utils.py b/widget/src/quantem/widget/array_utils.py index 1913a846..f4ff7592 100644 --- a/widget/src/quantem/widget/array_utils.py +++ b/widget/src/quantem/widget/array_utils.py @@ -1,11 +1,15 @@ """Array utilities for widgets. NumPy + PyTorch input.""" import numpy as np -import torch def to_numpy(data, dtype: np.dtype | None = None) -> np.ndarray: """Convert NumPy / PyTorch / Dataset to NumPy.""" - if isinstance(data, torch.Tensor): + try: + import torch + is_tensor = isinstance(data, torch.Tensor) + except ImportError: + is_tensor = False + if is_tensor: result = data.detach().cpu().numpy() elif isinstance(data, np.ndarray): result = data From 178006e55e7543878b1eebea4ef6771f7d1ba58d Mon Sep 17 00:00:00 2001 From: quantem-bot Date: Mon, 11 May 2026 12:17:04 +0000 Subject: [PATCH 287/335] chore: update lock file --- uv.lock | 240 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 120 insertions(+), 120 deletions(-) diff --git a/uv.lock b/uv.lock index 6932e160..a0488e0f 100644 --- a/uv.lock +++ b/uv.lock @@ -532,101 +532,101 @@ wheels = [ [[package]] name = "coverage" -version = "7.13.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967, upload-time = "2026-03-17T10:33:18.341Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/37/d24c8f8220ff07b839b2c043ea4903a33b0f455abe673ae3c03bbdb7f212/coverage-7.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66a80c616f80181f4d643b0f9e709d97bcea413ecd9631e1dedc7401c8e6695d", size = 219381, upload-time = "2026-03-17T10:30:14.68Z" }, - { url = "https://files.pythonhosted.org/packages/35/8b/cd129b0ca4afe886a6ce9d183c44d8301acbd4ef248622e7c49a23145605/coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:145ede53ccbafb297c1c9287f788d1bc3efd6c900da23bf6931b09eafc931587", size = 219880, upload-time = "2026-03-17T10:30:16.231Z" }, - { url = "https://files.pythonhosted.org/packages/55/2f/e0e5b237bffdb5d6c530ce87cc1d413a5b7d7dfd60fb067ad6d254c35c76/coverage-7.13.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0672854dc733c342fa3e957e0605256d2bf5934feeac328da9e0b5449634a642", size = 250303, upload-time = "2026-03-17T10:30:17.748Z" }, - { url = "https://files.pythonhosted.org/packages/92/be/b1afb692be85b947f3401375851484496134c5554e67e822c35f28bf2fbc/coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ec10e2a42b41c923c2209b846126c6582db5e43a33157e9870ba9fb70dc7854b", size = 252218, upload-time = "2026-03-17T10:30:19.804Z" }, - { url = "https://files.pythonhosted.org/packages/da/69/2f47bb6fa1b8d1e3e5d0c4be8ccb4313c63d742476a619418f85740d597b/coverage-7.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be3d4bbad9d4b037791794ddeedd7d64a56f5933a2c1373e18e9e568b9141686", size = 254326, upload-time = "2026-03-17T10:30:21.321Z" }, - { url = "https://files.pythonhosted.org/packages/d5/d0/79db81da58965bd29dabc8f4ad2a2af70611a57cba9d1ec006f072f30a54/coverage-7.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d2afbc5cc54d286bfb54541aa50b64cdb07a718227168c87b9e2fb8f25e1743", size = 256267, upload-time = "2026-03-17T10:30:23.094Z" }, - { url = "https://files.pythonhosted.org/packages/e5/32/d0d7cc8168f91ddab44c0ce4806b969df5f5fdfdbb568eaca2dbc2a04936/coverage-7.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3ad050321264c49c2fa67bb599100456fc51d004b82534f379d16445da40fb75", size = 250430, upload-time = "2026-03-17T10:30:25.311Z" }, - { url = "https://files.pythonhosted.org/packages/4d/06/a055311d891ddbe231cd69fdd20ea4be6e3603ffebddf8704b8ca8e10a3c/coverage-7.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7300c8a6d13335b29bb76d7651c66af6bd8658517c43499f110ddc6717bfc209", size = 252017, upload-time = "2026-03-17T10:30:27.284Z" }, - { url = "https://files.pythonhosted.org/packages/d6/f6/d0fd2d21e29a657b5f77a2fe7082e1568158340dceb941954f776dce1b7b/coverage-7.13.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:eb07647a5738b89baab047f14edd18ded523de60f3b30e75c2acc826f79c839a", size = 250080, upload-time = "2026-03-17T10:30:29.481Z" }, - { url = "https://files.pythonhosted.org/packages/4e/ab/0d7fb2efc2e9a5eb7ddcc6e722f834a69b454b7e6e5888c3a8567ecffb31/coverage-7.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9adb6688e3b53adffefd4a52d72cbd8b02602bfb8f74dcd862337182fd4d1a4e", size = 253843, upload-time = "2026-03-17T10:30:31.301Z" }, - { url = "https://files.pythonhosted.org/packages/ba/6f/7467b917bbf5408610178f62a49c0ed4377bb16c1657f689cc61470da8ce/coverage-7.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7c8d4bc913dd70b93488d6c496c77f3aff5ea99a07e36a18f865bca55adef8bd", size = 249802, upload-time = "2026-03-17T10:30:33.358Z" }, - { url = "https://files.pythonhosted.org/packages/75/2c/1172fb689df92135f5bfbbd69fc83017a76d24ea2e2f3a1154007e2fb9f8/coverage-7.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e3c426ffc4cd952f54ee9ffbdd10345709ecc78a3ecfd796a57236bfad0b9b8", size = 250707, upload-time = "2026-03-17T10:30:35.2Z" }, - { url = "https://files.pythonhosted.org/packages/67/21/9ac389377380a07884e3b48ba7a620fcd9dbfaf1d40565facdc6b36ec9ef/coverage-7.13.5-cp311-cp311-win32.whl", hash = "sha256:259b69bb83ad9894c4b25be2528139eecba9a82646ebdda2d9db1ba28424a6bf", size = 221880, upload-time = "2026-03-17T10:30:36.775Z" }, - { url = "https://files.pythonhosted.org/packages/af/7f/4cd8a92531253f9d7c1bbecd9fa1b472907fb54446ca768c59b531248dc5/coverage-7.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:258354455f4e86e3e9d0d17571d522e13b4e1e19bf0f8596bcf9476d61e7d8a9", size = 222816, upload-time = "2026-03-17T10:30:38.891Z" }, - { url = "https://files.pythonhosted.org/packages/12/a6/1d3f6155fb0010ca68eba7fe48ca6c9da7385058b77a95848710ecf189b1/coverage-7.13.5-cp311-cp311-win_arm64.whl", hash = "sha256:bff95879c33ec8da99fc9b6fe345ddb5be6414b41d6d1ad1c8f188d26f36e028", size = 221483, upload-time = "2026-03-17T10:30:40.463Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c3/a396306ba7db865bf96fc1fb3b7fd29bcbf3d829df642e77b13555163cd6/coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01", size = 219554, upload-time = "2026-03-17T10:30:42.208Z" }, - { url = "https://files.pythonhosted.org/packages/a6/16/a68a19e5384e93f811dccc51034b1fd0b865841c390e3c931dcc4699e035/coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422", size = 219908, upload-time = "2026-03-17T10:30:43.906Z" }, - { url = "https://files.pythonhosted.org/packages/29/72/20b917c6793af3a5ceb7fb9c50033f3ec7865f2911a1416b34a7cfa0813b/coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f", size = 251419, upload-time = "2026-03-17T10:30:45.545Z" }, - { url = "https://files.pythonhosted.org/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5", size = 254159, upload-time = "2026-03-17T10:30:47.204Z" }, - { url = "https://files.pythonhosted.org/packages/9d/00/7b0edcfe64e2ed4c0340dac14a52ad0f4c9bd0b8b5e531af7d55b703db7c/coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376", size = 255270, upload-time = "2026-03-17T10:30:48.812Z" }, - { url = "https://files.pythonhosted.org/packages/93/89/7ffc4ba0f5d0a55c1e84ea7cee39c9fc06af7b170513d83fbf3bbefce280/coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256", size = 257538, upload-time = "2026-03-17T10:30:50.77Z" }, - { url = "https://files.pythonhosted.org/packages/81/bd/73ddf85f93f7e6fa83e77ccecb6162d9415c79007b4bc124008a4995e4a7/coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c", size = 251821, upload-time = "2026-03-17T10:30:52.5Z" }, - { url = "https://files.pythonhosted.org/packages/a0/81/278aff4e8dec4926a0bcb9486320752811f543a3ce5b602cc7a29978d073/coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5", size = 253191, upload-time = "2026-03-17T10:30:54.543Z" }, - { url = "https://files.pythonhosted.org/packages/70/ee/fe1621488e2e0a58d7e94c4800f0d96f79671553488d401a612bebae324b/coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09", size = 251337, upload-time = "2026-03-17T10:30:56.663Z" }, - { url = "https://files.pythonhosted.org/packages/37/a6/f79fb37aa104b562207cc23cb5711ab6793608e246cae1e93f26b2236ed9/coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9", size = 255404, upload-time = "2026-03-17T10:30:58.427Z" }, - { url = "https://files.pythonhosted.org/packages/75/f0/ed15262a58ec81ce457ceb717b7f78752a1713556b19081b76e90896e8d4/coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf", size = 250903, upload-time = "2026-03-17T10:31:00.093Z" }, - { url = "https://files.pythonhosted.org/packages/0f/e9/9129958f20e7e9d4d56d51d42ccf708d15cac355ff4ac6e736e97a9393d2/coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c", size = 252780, upload-time = "2026-03-17T10:31:01.916Z" }, - { url = "https://files.pythonhosted.org/packages/a4/d7/0ad9b15812d81272db94379fe4c6df8fd17781cc7671fdfa30c76ba5ff7b/coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf", size = 222093, upload-time = "2026-03-17T10:31:03.642Z" }, - { url = "https://files.pythonhosted.org/packages/29/3d/821a9a5799fac2556bcf0bd37a70d1d11fa9e49784b6d22e92e8b2f85f18/coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810", size = 222900, upload-time = "2026-03-17T10:31:05.651Z" }, - { url = "https://files.pythonhosted.org/packages/d4/fa/2238c2ad08e35cf4f020ea721f717e09ec3152aea75d191a7faf3ef009a8/coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de", size = 221515, upload-time = "2026-03-17T10:31:07.293Z" }, - { url = "https://files.pythonhosted.org/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1", size = 219576, upload-time = "2026-03-17T10:31:09.045Z" }, - { url = "https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3", size = 219942, upload-time = "2026-03-17T10:31:10.708Z" }, - { url = "https://files.pythonhosted.org/packages/5f/13/93419671cee82b780bab7ea96b67c8ef448f5f295f36bf5031154ec9a790/coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26", size = 250935, upload-time = "2026-03-17T10:31:12.392Z" }, - { url = "https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3", size = 253541, upload-time = "2026-03-17T10:31:14.247Z" }, - { url = "https://files.pythonhosted.org/packages/4e/5e/3ee3b835647be646dcf3c65a7c6c18f87c27326a858f72ab22c12730773d/coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b", size = 254780, upload-time = "2026-03-17T10:31:16.193Z" }, - { url = "https://files.pythonhosted.org/packages/44/b3/cb5bd1a04cfcc49ede6cd8409d80bee17661167686741e041abc7ee1b9a9/coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a", size = 256912, upload-time = "2026-03-17T10:31:17.89Z" }, - { url = "https://files.pythonhosted.org/packages/1b/66/c1dceb7b9714473800b075f5c8a84f4588f887a90eb8645282031676e242/coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969", size = 251165, upload-time = "2026-03-17T10:31:19.605Z" }, - { url = "https://files.pythonhosted.org/packages/b7/62/5502b73b97aa2e53ea22a39cf8649ff44827bef76d90bf638777daa27a9d/coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161", size = 252908, upload-time = "2026-03-17T10:31:21.312Z" }, - { url = "https://files.pythonhosted.org/packages/7d/37/7792c2d69854397ca77a55c4646e5897c467928b0e27f2d235d83b5d08c6/coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15", size = 250873, upload-time = "2026-03-17T10:31:23.565Z" }, - { url = "https://files.pythonhosted.org/packages/a3/23/bc866fb6163be52a8a9e5d708ba0d3b1283c12158cefca0a8bbb6e247a43/coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1", size = 255030, upload-time = "2026-03-17T10:31:25.58Z" }, - { url = "https://files.pythonhosted.org/packages/7d/8b/ef67e1c222ef49860701d346b8bbb70881bef283bd5f6cbba68a39a086c7/coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6", size = 250694, upload-time = "2026-03-17T10:31:27.316Z" }, - { url = "https://files.pythonhosted.org/packages/46/0d/866d1f74f0acddbb906db212e096dee77a8e2158ca5e6bb44729f9d93298/coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17", size = 252469, upload-time = "2026-03-17T10:31:29.472Z" }, - { url = "https://files.pythonhosted.org/packages/7a/f5/be742fec31118f02ce42b21c6af187ad6a344fed546b56ca60caacc6a9a0/coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85", size = 222112, upload-time = "2026-03-17T10:31:31.526Z" }, - { url = "https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b", size = 222923, upload-time = "2026-03-17T10:31:33.633Z" }, - { url = "https://files.pythonhosted.org/packages/48/af/fea819c12a095781f6ccd504890aaddaf88b8fab263c4940e82c7b770124/coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664", size = 221540, upload-time = "2026-03-17T10:31:35.445Z" }, - { url = "https://files.pythonhosted.org/packages/23/d2/17879af479df7fbbd44bd528a31692a48f6b25055d16482fdf5cdb633805/coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d", size = 220262, upload-time = "2026-03-17T10:31:37.184Z" }, - { url = "https://files.pythonhosted.org/packages/5b/4c/d20e554f988c8f91d6a02c5118f9abbbf73a8768a3048cb4962230d5743f/coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0", size = 220617, upload-time = "2026-03-17T10:31:39.245Z" }, - { url = "https://files.pythonhosted.org/packages/29/9c/f9f5277b95184f764b24e7231e166dfdb5780a46d408a2ac665969416d61/coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806", size = 261912, upload-time = "2026-03-17T10:31:41.324Z" }, - { url = "https://files.pythonhosted.org/packages/d5/f6/7f1ab39393eeb50cfe4747ae8ef0e4fc564b989225aa1152e13a180d74f8/coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3", size = 263987, upload-time = "2026-03-17T10:31:43.724Z" }, - { url = "https://files.pythonhosted.org/packages/a0/d7/62c084fb489ed9c6fbdf57e006752e7c516ea46fd690e5ed8b8617c7d52e/coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9", size = 266416, upload-time = "2026-03-17T10:31:45.769Z" }, - { url = "https://files.pythonhosted.org/packages/a9/f6/df63d8660e1a0bff6125947afda112a0502736f470d62ca68b288ea762d8/coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd", size = 267558, upload-time = "2026-03-17T10:31:48.293Z" }, - { url = "https://files.pythonhosted.org/packages/5b/02/353ca81d36779bd108f6d384425f7139ac3c58c750dcfaafe5d0bee6436b/coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606", size = 261163, upload-time = "2026-03-17T10:31:50.125Z" }, - { url = "https://files.pythonhosted.org/packages/2c/16/2e79106d5749bcaf3aee6d309123548e3276517cd7851faa8da213bc61bf/coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e", size = 263981, upload-time = "2026-03-17T10:31:51.961Z" }, - { url = "https://files.pythonhosted.org/packages/29/c7/c29e0c59ffa6942030ae6f50b88ae49988e7e8da06de7ecdbf49c6d4feae/coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0", size = 261604, upload-time = "2026-03-17T10:31:53.872Z" }, - { url = "https://files.pythonhosted.org/packages/40/48/097cdc3db342f34006a308ab41c3a7c11c3f0d84750d340f45d88a782e00/coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87", size = 265321, upload-time = "2026-03-17T10:31:55.997Z" }, - { url = "https://files.pythonhosted.org/packages/bb/1f/4994af354689e14fd03a75f8ec85a9a68d94e0188bbdab3fc1516b55e512/coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479", size = 260502, upload-time = "2026-03-17T10:31:58.308Z" }, - { url = "https://files.pythonhosted.org/packages/22/c6/9bb9ef55903e628033560885f5c31aa227e46878118b63ab15dc7ba87797/coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2", size = 262688, upload-time = "2026-03-17T10:32:00.141Z" }, - { url = "https://files.pythonhosted.org/packages/14/4f/f5df9007e50b15e53e01edea486814783a7f019893733d9e4d6caad75557/coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a", size = 222788, upload-time = "2026-03-17T10:32:02.246Z" }, - { url = "https://files.pythonhosted.org/packages/e1/98/aa7fccaa97d0f3192bec013c4e6fd6d294a6ed44b640e6bb61f479e00ed5/coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819", size = 223851, upload-time = "2026-03-17T10:32:04.416Z" }, - { url = "https://files.pythonhosted.org/packages/3d/8b/e5c469f7352651e5f013198e9e21f97510b23de957dd06a84071683b4b60/coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911", size = 222104, upload-time = "2026-03-17T10:32:06.65Z" }, - { url = "https://files.pythonhosted.org/packages/8e/77/39703f0d1d4b478bfd30191d3c14f53caf596fac00efb3f8f6ee23646439/coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f", size = 219621, upload-time = "2026-03-17T10:32:08.589Z" }, - { url = "https://files.pythonhosted.org/packages/e2/3e/51dff36d99ae14639a133d9b164d63e628532e2974d8b1edb99dd1ebc733/coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e", size = 219953, upload-time = "2026-03-17T10:32:10.507Z" }, - { url = "https://files.pythonhosted.org/packages/6a/6c/1f1917b01eb647c2f2adc9962bd66c79eb978951cab61bdc1acab3290c07/coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a", size = 250992, upload-time = "2026-03-17T10:32:12.41Z" }, - { url = "https://files.pythonhosted.org/packages/22/e5/06b1f88f42a5a99df42ce61208bdec3bddb3d261412874280a19796fc09c/coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510", size = 253503, upload-time = "2026-03-17T10:32:14.449Z" }, - { url = "https://files.pythonhosted.org/packages/80/28/2a148a51e5907e504fa7b85490277734e6771d8844ebcc48764a15e28155/coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247", size = 254852, upload-time = "2026-03-17T10:32:16.56Z" }, - { url = "https://files.pythonhosted.org/packages/61/77/50e8d3d85cc0b7ebe09f30f151d670e302c7ff4a1bf6243f71dd8b0981fa/coverage-7.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6", size = 257161, upload-time = "2026-03-17T10:32:19.004Z" }, - { url = "https://files.pythonhosted.org/packages/3b/c4/b5fd1d4b7bf8d0e75d997afd3925c59ba629fc8616f1b3aae7605132e256/coverage-7.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0", size = 251021, upload-time = "2026-03-17T10:32:21.344Z" }, - { url = "https://files.pythonhosted.org/packages/f8/66/6ea21f910e92d69ef0b1c3346ea5922a51bad4446c9126db2ae96ee24c4c/coverage-7.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882", size = 252858, upload-time = "2026-03-17T10:32:23.506Z" }, - { url = "https://files.pythonhosted.org/packages/9e/ea/879c83cb5d61aa2a35fb80e72715e92672daef8191b84911a643f533840c/coverage-7.13.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740", size = 250823, upload-time = "2026-03-17T10:32:25.516Z" }, - { url = "https://files.pythonhosted.org/packages/8a/fb/616d95d3adb88b9803b275580bdeee8bd1b69a886d057652521f83d7322f/coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16", size = 255099, upload-time = "2026-03-17T10:32:27.944Z" }, - { url = "https://files.pythonhosted.org/packages/1c/93/25e6917c90ec1c9a56b0b26f6cad6408e5f13bb6b35d484a0d75c9cf000d/coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0", size = 250638, upload-time = "2026-03-17T10:32:29.914Z" }, - { url = "https://files.pythonhosted.org/packages/fc/7b/dc1776b0464145a929deed214aef9fb1493f159b59ff3c7eeeedf91eddd0/coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0", size = 252295, upload-time = "2026-03-17T10:32:31.981Z" }, - { url = "https://files.pythonhosted.org/packages/ea/fb/99cbbc56a26e07762a2740713f3c8f9f3f3106e3a3dd8cc4474954bccd34/coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc", size = 222360, upload-time = "2026-03-17T10:32:34.233Z" }, - { url = "https://files.pythonhosted.org/packages/8d/b7/4758d4f73fb536347cc5e4ad63662f9d60ba9118cb6785e9616b2ce5d7fa/coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633", size = 223174, upload-time = "2026-03-17T10:32:36.369Z" }, - { url = "https://files.pythonhosted.org/packages/2c/f2/24d84e1dfe70f8ac9fdf30d338239860d0d1d5da0bda528959d0ebc9da28/coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8", size = 221739, upload-time = "2026-03-17T10:32:38.736Z" }, - { url = "https://files.pythonhosted.org/packages/60/5b/4a168591057b3668c2428bff25dd3ebc21b629d666d90bcdfa0217940e84/coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b", size = 220351, upload-time = "2026-03-17T10:32:41.196Z" }, - { url = "https://files.pythonhosted.org/packages/f5/21/1fd5c4dbfe4a58b6b99649125635df46decdfd4a784c3cd6d410d303e370/coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c", size = 220612, upload-time = "2026-03-17T10:32:43.204Z" }, - { url = "https://files.pythonhosted.org/packages/d6/fe/2a924b3055a5e7e4512655a9d4609781b0d62334fa0140c3e742926834e2/coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9", size = 261985, upload-time = "2026-03-17T10:32:45.514Z" }, - { url = "https://files.pythonhosted.org/packages/d7/0d/c8928f2bd518c45990fe1a2ab8db42e914ef9b726c975facc4282578c3eb/coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29", size = 264107, upload-time = "2026-03-17T10:32:47.971Z" }, - { url = "https://files.pythonhosted.org/packages/ef/ae/4ae35bbd9a0af9d820362751f0766582833c211224b38665c0f8de3d487f/coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607", size = 266513, upload-time = "2026-03-17T10:32:50.1Z" }, - { url = "https://files.pythonhosted.org/packages/9c/20/d326174c55af36f74eac6ae781612d9492f060ce8244b570bb9d50d9d609/coverage-7.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90", size = 267650, upload-time = "2026-03-17T10:32:52.391Z" }, - { url = "https://files.pythonhosted.org/packages/7a/5e/31484d62cbd0eabd3412e30d74386ece4a0837d4f6c3040a653878bfc019/coverage-7.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3", size = 261089, upload-time = "2026-03-17T10:32:54.544Z" }, - { url = "https://files.pythonhosted.org/packages/e9/d8/49a72d6de146eebb0b7e48cc0f4bc2c0dd858e3d4790ab2b39a2872b62bd/coverage-7.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab", size = 263982, upload-time = "2026-03-17T10:32:56.803Z" }, - { url = "https://files.pythonhosted.org/packages/06/3b/0351f1bd566e6e4dd39e978efe7958bde1d32f879e85589de147654f57bb/coverage-7.13.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562", size = 261579, upload-time = "2026-03-17T10:32:59.466Z" }, - { url = "https://files.pythonhosted.org/packages/5d/ce/796a2a2f4017f554d7810f5c573449b35b1e46788424a548d4d19201b222/coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2", size = 265316, upload-time = "2026-03-17T10:33:01.847Z" }, - { url = "https://files.pythonhosted.org/packages/3d/16/d5ae91455541d1a78bc90abf495be600588aff8f6db5c8b0dae739fa39c9/coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea", size = 260427, upload-time = "2026-03-17T10:33:03.945Z" }, - { url = "https://files.pythonhosted.org/packages/48/11/07f413dba62db21fb3fad5d0de013a50e073cc4e2dc4306e770360f6dfc8/coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a", size = 262745, upload-time = "2026-03-17T10:33:06.285Z" }, - { url = "https://files.pythonhosted.org/packages/91/15/d792371332eb4663115becf4bad47e047d16234b1aff687b1b18c58d60ae/coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215", size = 223146, upload-time = "2026-03-17T10:33:08.756Z" }, - { url = "https://files.pythonhosted.org/packages/db/51/37221f59a111dca5e85be7dbf09696323b5b9f13ff65e0641d535ed06ea8/coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43", size = 224254, upload-time = "2026-03-17T10:33:11.174Z" }, - { url = "https://files.pythonhosted.org/packages/54/83/6acacc889de8987441aa7d5adfbdbf33d288dad28704a67e574f1df9bcbb/coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45", size = 222276, upload-time = "2026-03-17T10:33:13.466Z" }, - { url = "https://files.pythonhosted.org/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346, upload-time = "2026-03-17T10:33:15.691Z" }, +version = "7.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/23/7f/d0720730a397a999ffc0fd3f5bebef347338e3a47b727da66fbb228e2ff2/coverage-7.14.0.tar.gz", hash = "sha256:057a6af2f160a85384cde4ab36f0d2777bae1057bae255f95413cdd382aa5c74", size = 919489, upload-time = "2026-05-10T18:02:31.397Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/e4/649c8d4f7f1709b6dbfc474358aa1bba02f67bcd52e2fec291a5014006cd/coverage-7.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a78e2a9d9c5e3b8d4ab9b9d28c985ea66fced0a7d7c2aec1f216e03a2011480", size = 219795, upload-time = "2026-05-10T17:59:48.198Z" }, + { url = "https://files.pythonhosted.org/packages/7f/8d/46692d24b3f395d4cbf17bfcc57136b4f2f9c0c0df864b0bddfc1d71a014/coverage-7.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1816c505187592dcd1c5a5f226601a549f70365fbd00930ac88b0c225b76bb4", size = 220299, upload-time = "2026-05-10T17:59:49.683Z" }, + { url = "https://files.pythonhosted.org/packages/12/c2/a40f5cb295bbcbb697a76947a56081c494c61950366294ee426ffe261099/coverage-7.14.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d8e1762f0e9cbc26ec315471e7b47855218e833cd5a032d706fbf43845d878c7", size = 250721, upload-time = "2026-05-10T17:59:51.494Z" }, + { url = "https://files.pythonhosted.org/packages/fd/35/202235eb5c3c14c212462cd91d61b7386bf8fc44bc7a77f4742d2a69174b/coverage-7.14.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9336e23e8bb3a3925398261385e2a1533957d3e760e91070dcb0e98bfa514eed", size = 252633, upload-time = "2026-05-10T17:59:53.244Z" }, + { url = "https://files.pythonhosted.org/packages/bb/80/5f596e8995785124ee191c42535664c5e62c65995b66f4ca21e28ae04c81/coverage-7.14.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd1169b2230f9cbe9c638ba38022ed7a2b1e641cc07f7cea0365e4be2a74980", size = 254743, upload-time = "2026-05-10T17:59:55.021Z" }, + { url = "https://files.pythonhosted.org/packages/1e/6d/0d178825be2350f0adb27984d0aa7cf84bbdab201f6fb926b535d23a8f5f/coverage-7.14.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d1bb3543b58fea74d2cd1abc4054cc927e4724687cb4560cd2ed88d2c7d820c0", size = 256700, upload-time = "2026-05-10T17:59:56.511Z" }, + { url = "https://files.pythonhosted.org/packages/19/5b/9e549c2f6e9dfea472adadba06c294e64735dabc2dd19015fac082095013/coverage-7.14.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a93bac2cb577ef60074999ed56d8a1535894398e2ed920d4185c3ec0c8864742", size = 250854, upload-time = "2026-05-10T17:59:57.94Z" }, + { url = "https://files.pythonhosted.org/packages/3d/1c/b94f9f5f36396021ee2f62c5834b12e6a3d31f0bed5d6fc6d1c3caec087c/coverage-7.14.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5904abf7e18cddc463219b17552229650c6b79e061d31a1059283051169cf7d5", size = 252433, upload-time = "2026-05-10T17:59:59.688Z" }, + { url = "https://files.pythonhosted.org/packages/b5/cb/d192cd8e1345eccabc32016f2d39072ecd10cb4f4b983ed8d0ebdeaf00dc/coverage-7.14.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:741f57cddc9004a8c81b084660215f33a6b597dbe62c31386b983ee26310e327", size = 250494, upload-time = "2026-05-10T18:00:01.953Z" }, + { url = "https://files.pythonhosted.org/packages/53/c5/aac9f460a41d835dbddef1d377f105f6ac2311d0f3c1588e9f51046d8813/coverage-7.14.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:664123feb0929d7affc135717dbd70d61d98688a08ab1e5ba464739620c6252d", size = 254261, upload-time = "2026-05-10T18:00:03.779Z" }, + { url = "https://files.pythonhosted.org/packages/23/aa/7af7c0081980a9cb3d289c5a435a4b7657dcecbd128e25c580e6a50389b5/coverage-7.14.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:c83d2399a51bbec8429266905d33616f04bc5726b1138c35844d5fcd896b2e20", size = 250216, upload-time = "2026-05-10T18:00:05.262Z" }, + { url = "https://files.pythonhosted.org/packages/35/60/a4257538ce2f6b978aeb51870d6c4208c510928a03db7e0339bb625dccb7/coverage-7.14.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb2e855b87321259a037429288ae85216d191c74de3e79bf57cd2bc0761992c", size = 251125, upload-time = "2026-05-10T18:00:06.858Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ab/f91af47642ec1aa53490e835a95847168d9c77fc39aa58527604c051e145/coverage-7.14.0-cp311-cp311-win32.whl", hash = "sha256:731dc15b385ac52289743d476245b61e1a2927e803bef655b52bc3b2a75a21f3", size = 222300, upload-time = "2026-05-10T18:00:08.608Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f0/a71ddbd874431e7a7cd96071f0c331cfbbad07704833c765d24ffbab8a67/coverage-7.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:bfb0ed8ec5d25e93face268115d7964db9df8b9aae8edcde9ec6b16c726a7cc1", size = 223241, upload-time = "2026-05-10T18:00:10.746Z" }, + { url = "https://files.pythonhosted.org/packages/d8/6e/d9d312a5151a96cd110efee32efc3fc97b01ebd86203fe618ccb29cf4c92/coverage-7.14.0-cp311-cp311-win_arm64.whl", hash = "sha256:7ebb1c6df9f78046a1b1e0a89674cd4bf73b7c648914eebcf976a57fd99a5627", size = 221908, upload-time = "2026-05-10T18:00:12.242Z" }, + { url = "https://files.pythonhosted.org/packages/09/1e/2f996b2c8415cbb6f54b0f5ec1ee850c96d7911961afb4fc05f4a89d8c58/coverage-7.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7ffd19fc8aed057fd686a17a4935eef5f9859d69208f96310e893e64b9b6ccf5", size = 219967, upload-time = "2026-05-10T18:00:13.756Z" }, + { url = "https://files.pythonhosted.org/packages/34/23/35c7aea1274aef7525bdd2dc92f710bdde6d11652239d71d1ec450067939/coverage-7.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:829994cfe1aeb773ca27bf246d4badc1e764893e3bfb98fff820fcecd1ca4662", size = 220329, upload-time = "2026-05-10T18:00:15.264Z" }, + { url = "https://files.pythonhosted.org/packages/75/cf/a8f4b43a16e194b0261257ad28ded5853ec052570afef4a84e1d81189f3b/coverage-7.14.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b4f07cf7edcb7ec39431a5074d7ea83b29a9f71fcfc494f0f40af4e65180420f", size = 251839, upload-time = "2026-05-10T18:00:17.16Z" }, + { url = "https://files.pythonhosted.org/packages/69/ff/6699e7b71e60d3049eb2bdcbc95ee3f35707b2b0e48f32e9e63d3ce30c08/coverage-7.14.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ca3d9cf2c32b521bd9518385608787fa86f38daf993695307531822c3430ed67", size = 254576, upload-time = "2026-05-10T18:00:18.829Z" }, + { url = "https://files.pythonhosted.org/packages/22/ec/c936d495fcd67f48f03a9c4ad3297ff80d1f222a5df3980f15b34c186c21/coverage-7.14.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92af52828e7f29d827346b0294e5a0853fa206db77db0395b282918d41e28db9", size = 255690, upload-time = "2026-05-10T18:00:20.648Z" }, + { url = "https://files.pythonhosted.org/packages/5c/42/5af63f636cc62a4a2b1b3ba9146f6ee6f53a35a50d5cefc54d5670f60999/coverage-7.14.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7b2bb6c9d7e769360d0f20a0f219603fd64f0c8f97de17ab25853261602be0fb", size = 257949, upload-time = "2026-05-10T18:00:22.28Z" }, + { url = "https://files.pythonhosted.org/packages/26/d3/a225317bd2012132a27e1176d51660b826f99bb975876463c44ea0d7ee5a/coverage-7.14.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1c9ed6ef99f88fb8c14aa8e2bf8eb0fe55fa2edfea68f8675d78741df1a5ac0e", size = 252242, upload-time = "2026-05-10T18:00:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/f1/7f/9e65495298c3ea414742998539c37d048b5e81cc818fb1828cc6b51d10bf/coverage-7.14.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8231ade007f37959fbf58acc677f26b922c02eda6f0428ea307da0fd39681bf3", size = 253608, upload-time = "2026-05-10T18:00:25.588Z" }, + { url = "https://files.pythonhosted.org/packages/94/46/1522b524a35bdad22b2b8c4f9d32d0a104b524726ec380b2db68db1746f5/coverage-7.14.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d8b013632cc1ce1d09dbe4f32667b4d320ec2f54fc326ebeffcd0b0bcc2bb6c4", size = 251753, upload-time = "2026-05-10T18:00:27.104Z" }, + { url = "https://files.pythonhosted.org/packages/f3/e9/cdf00d38817742c541ade405e115a3f7bf36e6f2a8b99d4f209861b85a2d/coverage-7.14.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1733198802d71ec4c524f322e2867ee05c62e9e75df86bdca545407a221827d1", size = 255823, upload-time = "2026-05-10T18:00:29.038Z" }, + { url = "https://files.pythonhosted.org/packages/38/fc/5e7877cf5f902d08a17ff1c532511476d87e1bea355bd5028cb97f902e79/coverage-7.14.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:72a305291fa8ee01332f1aaf38b348ca34097f6aa0b0ef627eef2837e57bbba5", size = 251323, upload-time = "2026-05-10T18:00:30.647Z" }, + { url = "https://files.pythonhosted.org/packages/18/9d/50f05a72dff8487464fdd4178dda5daed642a060e60afb644e3d45123559/coverage-7.14.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fcaba850dd317c65423a9d63d88f9573c53b00354d6dd95724576cc98a131595", size = 253197, upload-time = "2026-05-10T18:00:32.211Z" }, + { url = "https://files.pythonhosted.org/packages/00/3f/6f61ffe6439df266c3cf60f5c99cfaa21103d0210d706a42fc6c30683ff8/coverage-7.14.0-cp312-cp312-win32.whl", hash = "sha256:5ac83957a80d0701310e96d8bec68cdcf4f90a7674b7d13f15a344315b41ab27", size = 222515, upload-time = "2026-05-10T18:00:33.717Z" }, + { url = "https://files.pythonhosted.org/packages/85/19/93853133df2cb371083285ef6a93982a0173e7a233b0f61373ba9fd30eb2/coverage-7.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:70390b0da32cb90b501953716302906e8bcce087cb283e70d8c97729f22e92b2", size = 223324, upload-time = "2026-05-10T18:00:35.172Z" }, + { url = "https://files.pythonhosted.org/packages/74/18/9f7fe62f659f24b7a82a0be56bf94c1bd0a89e0ae7ab4c668f6e82404294/coverage-7.14.0-cp312-cp312-win_arm64.whl", hash = "sha256:91b993743d959b8be85b4abf9d5478216a69329c321efe5be0433c1a841d691d", size = 221944, upload-time = "2026-05-10T18:00:37.014Z" }, + { url = "https://files.pythonhosted.org/packages/6b/76/b7c66ee3c66e1b0f9d894c8125983aa0c03fb2336f2fd16559f9c966157f/coverage-7.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f2bbb8254370eb4c628ff3d6fa8a7f74ddc40565394d4f7ab791d1fe568e37ef", size = 219990, upload-time = "2026-05-10T18:00:38.887Z" }, + { url = "https://files.pythonhosted.org/packages/b3/af/e567cbad5ba69c013a50146dfa886dc7193361fda77521f51274ff620e1b/coverage-7.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:23b81107f46d3f21d0cbce30664fcec0f5d9f585638a67081750f99738f6bf66", size = 220365, upload-time = "2026-05-10T18:00:40.864Z" }, + { url = "https://files.pythonhosted.org/packages/44/6f/9ad575d505b4d805b254febc8a5b338a2efe278f8786e56ff1cb8413f9c3/coverage-7.14.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:22a7e06a5f11a757cdfe79018e9095f9f69ae283c5cd8123774c788deec8717b", size = 251363, upload-time = "2026-05-10T18:00:42.489Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5f/b5370068b2f57787454592ed7dcd1002f0f1703b7db1fa30f6a325a4ca6e/coverage-7.14.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9d1aa57a1dc8e05bdc42e81c5d671d849577aeedf279f4c449d6d286f9ed88ca", size = 253961, upload-time = "2026-05-10T18:00:44.079Z" }, + { url = "https://files.pythonhosted.org/packages/29/1e/51adf17738976e8f2b85ddef7b7aa12a0838b056c92f175941d8862767c1/coverage-7.14.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90c1a51bcfddf645b3bb7ec333d9e94393a8e94f55642380fa8a9a5a9e636cb7", size = 255193, upload-time = "2026-05-10T18:00:45.623Z" }, + { url = "https://files.pythonhosted.org/packages/9e/7b/5bfd7ac1df3b881c2ac7a5cbc99c7609e6296c402f5ef587cd81c6f355b3/coverage-7.14.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a841fae2fadcae4f438d43b6ccc4aac2ad609f47cdb6cfdce60cbb3fe5ca7bc2", size = 257326, upload-time = "2026-05-10T18:00:47.173Z" }, + { url = "https://files.pythonhosted.org/packages/7d/38/1d37d316b174fad3843a1d76dbdfe4398771c9ecd0515935dd9ece9cd627/coverage-7.14.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c79d2319cabef1fe8e86df73371126931550804738f78ad7d31e3aad85a67367", size = 251582, upload-time = "2026-05-10T18:00:49.152Z" }, + { url = "https://files.pythonhosted.org/packages/34/46/746704f95980ba220214e1a41e18cec5aea80a898eaa53c51bf2d645ff36/coverage-7.14.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1b23b0c6f0b1db6ad769b7050c8b641c0bf215ded26c1816955b17b7f26edfa9", size = 253325, upload-time = "2026-05-10T18:00:51.252Z" }, + { url = "https://files.pythonhosted.org/packages/e1/b9/bbe87206d9687b192352f893797825b5f5b15ecd3aa9c68fbff0c074d77b/coverage-7.14.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:55d3089079ce181a4566b1065ab28d2575eb76d8ac8f81f4fcda2bf037fee087", size = 251291, upload-time = "2026-05-10T18:00:52.816Z" }, + { url = "https://files.pythonhosted.org/packages/46/57/b8cdb12ac0d73ef0243218bd5e22c9df8f92edab8018213a86aec67c5324/coverage-7.14.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:49c005cba1e2f9677fb2845dcdf9a2e72a52a17d63e8231aaaae35d9f50215ef", size = 255448, upload-time = "2026-05-10T18:00:54.548Z" }, + { url = "https://files.pythonhosted.org/packages/1f/d4/5002019538b2036ce3c84340f54d2fd5100d55b0a6b0894eee56128d03c7/coverage-7.14.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:9117377b823daa28aa8635fbb08cda1cd6be3d7143257345459559aeef852d52", size = 251110, upload-time = "2026-05-10T18:00:56.122Z" }, + { url = "https://files.pythonhosted.org/packages/37/53/20c5009477660f084e6ed60bc02a91894b8e234e617e86ecfd9aaf78e27b/coverage-7.14.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7b79d646cf46d5cf9a9f40281d4441df5849e445726e369006d2b117710b33fe", size = 252885, upload-time = "2026-05-10T18:00:57.967Z" }, + { url = "https://files.pythonhosted.org/packages/ae/ab/3cf6427ac9c1f1db747dbb1ce71dde47984876d4c2cfd018a3fef0a78d4d/coverage-7.14.0-cp313-cp313-win32.whl", hash = "sha256:fb609b3658479e33f9516d46f1a89dbb9b6c261366e3a11844a96ec487533dae", size = 222539, upload-time = "2026-05-10T18:00:59.581Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b8/9228523e80321c2cb4880d1f589bc0171f2f71432c35118ad04dc01decce/coverage-7.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:0773d8329cf32b6fd222e4b52622c61fe8d503eb966cfc8d3c3c10c96266d50e", size = 223344, upload-time = "2026-05-10T18:01:01.531Z" }, + { url = "https://files.pythonhosted.org/packages/a3/99/118daa192f95e3a6cb2740100fbf8797cda1734b4134ef0b5d501a7fa8f3/coverage-7.14.0-cp313-cp313-win_arm64.whl", hash = "sha256:b4e26a0f1b696faf283bffe5b8569e44e336c582439df5d53281ab89ee0cba96", size = 221966, upload-time = "2026-05-10T18:01:03.16Z" }, + { url = "https://files.pythonhosted.org/packages/e6/f1/a46cc0c013be170216253184a32366d7cbdb9252feaec866b05c2d12a894/coverage-7.14.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:953f521ca9445300397e65fda3dca58b2dbd68fee983777420b57ac3c77e9f90", size = 220679, upload-time = "2026-05-10T18:01:05.058Z" }, + { url = "https://files.pythonhosted.org/packages/64/8c/9c30a3d311a34177fa432995be7fbfc64477d8bac5630bd38055b1c9b424/coverage-7.14.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:98af83fd65ae24b1fdd03aaead967a9f523bcd2f1aab2d4f3ffda65bb568a6f1", size = 221033, upload-time = "2026-05-10T18:01:07.002Z" }, + { url = "https://files.pythonhosted.org/packages/9a/cd/3fb5e06c3badefd0c1b47e2044fdca67f8220a4ec2e7fcfb476aa0a67c6c/coverage-7.14.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:668b92e6958c4db7cf92e81caac328dfbbdbb215db2850ad28f0cbe1eea0bfbd", size = 262333, upload-time = "2026-05-10T18:01:08.903Z" }, + { url = "https://files.pythonhosted.org/packages/a8/e6/fbc322325c7294d3e22c1ad6b79e45d0806b25228c8e5842aed6d8169aa7/coverage-7.14.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9fbd898551762dea00d3fef2b1c4f99afd2c6a3ff952ea07d60a9bd5ed4f34bc", size = 264410, upload-time = "2026-05-10T18:01:10.531Z" }, + { url = "https://files.pythonhosted.org/packages/08/92/c497b264bec1673c47cc77e26f760fcda4654cabf1f39546d1a23a3b8c35/coverage-7.14.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68af363c07ecd8d4b7d4043d85cb376d7d227eceb54e5323ee45da73dbd3e426", size = 266836, upload-time = "2026-05-10T18:01:12.19Z" }, + { url = "https://files.pythonhosted.org/packages/78/fc/045da320987f401af5d2815d351e8aa799aec859f60e29f445e3089eeedb/coverage-7.14.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6e57054a583da8ac55edf24117ea4c9133032cfc4cf72aa2d48c1e5d4b52f899", size = 267974, upload-time = "2026-05-10T18:01:13.926Z" }, + { url = "https://files.pythonhosted.org/packages/1b/ae/227b1e379497fb7a4fc3286e620f80c8a1e7cec66d45695a01639eb1af65/coverage-7.14.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cc3499459bbcdd51a65b64c35ab7ed2764eaf3cba826e0df3f1d7fe2e102b70b", size = 261578, upload-time = "2026-05-10T18:01:15.564Z" }, + { url = "https://files.pythonhosted.org/packages/a0/f5/3570342900f2acea31d33ff1590c5d8bac1a8e1a2e1c6d34a5d5e61de681/coverage-7.14.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:45899ec2138a4346ed34d601dedf5076fb74edf2d1dd9dc76a78e82397edee90", size = 264394, upload-time = "2026-05-10T18:01:17.607Z" }, + { url = "https://files.pythonhosted.org/packages/16/29/de1bbc01c935b28f89b1dc3db85b011c055e843a8e5e3b83141c3f80af7f/coverage-7.14.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8767486808c436f05b23ab98eb963fb29185e32a9357a166971685cb3459900f", size = 262022, upload-time = "2026-05-10T18:01:19.304Z" }, + { url = "https://files.pythonhosted.org/packages/35/95/f53890b0bf2fc10ab168e05d38869215e73ca24c4cb521c3bb0eb62fe16b/coverage-7.14.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a3b5ddfd6aa7ddad53ee3edb231e88a2151507a43229b7d71b953916deca127d", size = 265732, upload-time = "2026-05-10T18:01:21.494Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ea/c919e259081dd2bdf0e43b87209709ba7ec2e4117c2a7f5185379c43463c/coverage-7.14.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:63df0fe568e698e1045792399f8ab6da3a6c2dce3182813fb92afa2641087b47", size = 260921, upload-time = "2026-05-10T18:01:23.533Z" }, + { url = "https://files.pythonhosted.org/packages/1a/2c/c2831889705a81dc5d1c6ca12e4d8e9b95dfc146d153488a6c0ea685d28e/coverage-7.14.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:827d6397dbd95144939b18f89edf31f63e1f99633e8d5f32f22ba8bdda567477", size = 263109, upload-time = "2026-05-10T18:01:25.165Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a9/2fcae5003cac3d63fe344d2166243c2756935f48420863c5272b240d550b/coverage-7.14.0-cp313-cp313t-win32.whl", hash = "sha256:7bf43e000d24012599b879791cff41589af90674722421ef11b11a5431920bab", size = 223212, upload-time = "2026-05-10T18:01:27.157Z" }, + { url = "https://files.pythonhosted.org/packages/3f/bb/18e94d7b14b9b398164197114a587a04ab7c9fdbe1d237eef57311c5e883/coverage-7.14.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3f5549365af25d770e06b1f8f5682d9a5637d06eb494db91c6fa75d3950cc917", size = 224272, upload-time = "2026-05-10T18:01:29.107Z" }, + { url = "https://files.pythonhosted.org/packages/db/56/4f14fad782b035c81c4ffd09159e7103d42bb1d93ac8496d04b90a11b7da/coverage-7.14.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6d160217ec6fe890f16ad3a9531761589443749e448f91986c972714fad361c8", size = 222530, upload-time = "2026-05-10T18:01:31.151Z" }, + { url = "https://files.pythonhosted.org/packages/1c/18/b9a6586d73992807c26f9a5f274131be3d76b56b18a82b9392e2a25d2e45/coverage-7.14.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9aed9fa983514ca032790f3fe0d1c0e42ca7e16b42432af1706b50a9a46bef5d", size = 220036, upload-time = "2026-05-10T18:01:33.057Z" }, + { url = "https://files.pythonhosted.org/packages/f3/9b/4165a1d56ddc302a0e2d518fd9d412a4fd0b57562618c78c5f21c57194f5/coverage-7.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ba3b8390db29296dbbf49e91b6fe08f990743a90c8f447ba4c2ffc29670dfa63", size = 220368, upload-time = "2026-05-10T18:01:34.705Z" }, + { url = "https://files.pythonhosted.org/packages/69/aa/c12e52a5ba148d9995229d557e3be6e554fe469addc0e9241b2f0956d8ea/coverage-7.14.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3a5d8e876dfa2f102e970b183863d6dedd023d3c0eeca1fe7a9787bc5f28b212", size = 251417, upload-time = "2026-05-10T18:01:36.949Z" }, + { url = "https://files.pythonhosted.org/packages/d7/51/ec641c26e6dca1b25a7d2035ba6ecb7c884ef1a100a9e42fbe4ce4405139/coverage-7.14.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5ebb8f4614a3787d567e610bbfdf96a4798dd69a1afb1bd8ad228d4111fe6ff3", size = 253924, upload-time = "2026-05-10T18:01:38.985Z" }, + { url = "https://files.pythonhosted.org/packages/33/c4/59c3de0bd1b538824173fd518fed51c1ce740ca5ed68e74545983f4053a9/coverage-7.14.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b9bf47223dd8db3d4c4b2e443b02bace480d428f0822c3f991600448a176c97", size = 255269, upload-time = "2026-05-10T18:01:40.957Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a9/36dfa153a62040296f6e7febfdb20a5720622f6ef5a81a41e8237b9a5344/coverage-7.14.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3485a836550b303d006d57cc06e3d5afaabc642c77050b7c985a97b13e3776b8", size = 257583, upload-time = "2026-05-10T18:01:42.607Z" }, + { url = "https://files.pythonhosted.org/packages/26/7b/cc2c048d4114d9ab1c2409e9ee365e5ae10736df6dffcfc9444effa6c708/coverage-7.14.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3e7e88110bae996d199d1693ca8ec3fd52441d426401ae963437598667b4c5eb", size = 251434, upload-time = "2026-05-10T18:01:44.537Z" }, + { url = "https://files.pythonhosted.org/packages/ee/df/6770eaa576e604575e9a78055313250faef5faa84bd6f71a39fece519c43/coverage-7.14.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15228a6800ce7bdf1b74800595e56db7138cecb338fdbf044806e10dcf182dfe", size = 253280, upload-time = "2026-05-10T18:01:46.175Z" }, + { url = "https://files.pythonhosted.org/packages/ad/9e/1c0264514a3f98259a6d64765a397b2c8373e3ba59ee722a4802d3ec0c61/coverage-7.14.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9d26ac7f5398bafc5b57421ad994e8a4749e8a7a0e62d05ec7d53014d5963bfa", size = 251241, upload-time = "2026-05-10T18:01:48.732Z" }, + { url = "https://files.pythonhosted.org/packages/64/16/4efdf3e3c4079cdbf0ece56a2fea872df9e8a3e15a13a0af4400e1075944/coverage-7.14.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2fb73254ff43c911c967a899e1359bc5049b4b115d6e8fbdde4937d0a2246cd5", size = 255516, upload-time = "2026-05-10T18:01:50.819Z" }, + { url = "https://files.pythonhosted.org/packages/93/69/b1de96346603881b3d1bc8d6447c83200e1c9700ffbaff926ba01ff5724c/coverage-7.14.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:454a380af72c6adada298ed270d38c7a391288198dbfb8467f786f588751a90c", size = 251059, upload-time = "2026-05-10T18:01:52.773Z" }, + { url = "https://files.pythonhosted.org/packages/a4/66/2881853e0363a5e0a724d1103e53650795367471b6afb234f8b49e713bc6/coverage-7.14.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:65c86fb646d2bd2972e96bd1a8b45817ed907cee68655d6295fe7ec031d04cca", size = 252716, upload-time = "2026-05-10T18:01:54.506Z" }, + { url = "https://files.pythonhosted.org/packages/55/5c/0d3305d002c41dcde873dbe456491e663dc55152ca526b630b5c47efd62f/coverage-7.14.0-cp314-cp314-win32.whl", hash = "sha256:6a6516b02a6101398e19a3f44820f69bab2590697f7def4331f668b14adaf828", size = 222788, upload-time = "2026-05-10T18:01:56.487Z" }, + { url = "https://files.pythonhosted.org/packages/f9/58/6e1b8f52fdc3184b47dc5037f5070d83a3d11042db1594b02d2a44d786c8/coverage-7.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:45e0f79d8351fa76e256716df91eab12890d32678b9590df7ae1042e4bd4cf5d", size = 223600, upload-time = "2026-05-10T18:01:58.497Z" }, + { url = "https://files.pythonhosted.org/packages/00/70/a18c408e674bc26281cadaedc7351f929bd2094e191e4b15271c30b084cc/coverage-7.14.0-cp314-cp314-win_arm64.whl", hash = "sha256:4b899594a8b2d81e5cc064a0d7f9cac2081fed91049456cae7676787e41549c9", size = 222168, upload-time = "2026-05-10T18:02:00.411Z" }, + { url = "https://files.pythonhosted.org/packages/3d/89/2681f071d238b62aff8dfc2ab44fc24cfdb38d1c01f391a80522ff5d3a16/coverage-7.14.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f580f8c80acd94ac72e863efe2cab791d8c38d153e0b463b92dfa000d5c84cd1", size = 220766, upload-time = "2026-05-10T18:02:02.313Z" }, + { url = "https://files.pythonhosted.org/packages/bd/c7/c987babafd9207ffa1995e1ef1f9b26762cf4963aa768a66b6f0501e4616/coverage-7.14.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a2bd259c442cd43c49b30fbafc51776eb19ea396faf159d26a83e6a0a5f13b0c", size = 221035, upload-time = "2026-05-10T18:02:04.017Z" }, + { url = "https://files.pythonhosted.org/packages/5a/e9/d6a5ac3b333088143d6fc877d398a9a674dc03124a2f776e131f03864823/coverage-7.14.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a706b908dfa85538863504c624b237a3cc34232bf403c057414ebfdb3b4d9f84", size = 262405, upload-time = "2026-05-10T18:02:05.915Z" }, + { url = "https://files.pythonhosted.org/packages/38/b1/e70838d29a7c08e22d44398a46db90815bbcbf28de06992bd9210d1a8d8e/coverage-7.14.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7333cd944ee4393b9b3d3c1b598c936d4fc8d70573a4c7dacfec5590dd50e436", size = 264530, upload-time = "2026-05-10T18:02:07.582Z" }, + { url = "https://files.pythonhosted.org/packages/6b/73/5c31ef97763288d03d9995152b96d5475b527c63d91c84b01caea894b83a/coverage-7.14.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f162bc9a15b82d947b02651b0c7e1609d6f7a8735ca330cfadec8481dd97d5a", size = 266932, upload-time = "2026-05-10T18:02:09.401Z" }, + { url = "https://files.pythonhosted.org/packages/e1/76/dd56d80f29c5f05b4d76f7e7c6d47cafacae017189c75c5759d24f9ff0cc/coverage-7.14.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:362cb78e01a5dc82009d88004cf60f2e6b6d6fcbfdec05b05af73b0abf40118f", size = 268062, upload-time = "2026-05-10T18:02:11.399Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c7/27ba85cd5b95614f159ff93ebff1901584a8d192e2e5e24c4943a7453f59/coverage-7.14.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:acebd068fca5512c3a6fde9c045f901613478781a73f0e82b307b214daef23fb", size = 261504, upload-time = "2026-05-10T18:02:13.257Z" }, + { url = "https://files.pythonhosted.org/packages/13/2e/e8149f60ab5d5684c6eee881bdf34b127115cddbb958b196768dd9d63473/coverage-7.14.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:29fe3da551dface75deb2ccbf87b6b66e2e7ef38f6d89050b428be94afff3490", size = 264398, upload-time = "2026-05-10T18:02:15.063Z" }, + { url = "https://files.pythonhosted.org/packages/d9/7f/1261b025285323225f4b4abffa5a643649dfd67e25ddca7ebcbdea3b7cb3/coverage-7.14.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b4cc4fce8672fffcb09b0eafc167b396b3ba53c4a7230f54b7aaffbf6c835fa9", size = 262000, upload-time = "2026-05-10T18:02:16.756Z" }, + { url = "https://files.pythonhosted.org/packages/d3/dc/829c54f60b9d08389439c00f813c752781c496fc5788c78d8006db4b4f2b/coverage-7.14.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5d4a51aad8ba8bdcd2b8bd8f03d4aca19693fa2327a3470e4718a25b03481020", size = 265732, upload-time = "2026-05-10T18:02:18.817Z" }, + { url = "https://files.pythonhosted.org/packages/ed/b0/70bd1419941652fa062689cba9c3eeafb8f5e6fbb890bce41c3bdda5dbd6/coverage-7.14.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:9f323af3e1e4f68b60b7b247e37b8515563a61375518fa59de1af48ba28a3db6", size = 260847, upload-time = "2026-05-10T18:02:20.528Z" }, + { url = "https://files.pythonhosted.org/packages/f2/73/be40b2390656c654d35ea0015ea7ba3d945769cf80790ad5e0bb2d56d2ba/coverage-7.14.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1a0abc7342ea9711c469dd8b821c6c311e6bc6aac1442e5fbd6b27fae0a8f3db", size = 263166, upload-time = "2026-05-10T18:02:22.337Z" }, + { url = "https://files.pythonhosted.org/packages/29/55/4a643f712fcf7cf2881f8ec1e0ccb7b164aff3108f69b51801246c8799f2/coverage-7.14.0-cp314-cp314t-win32.whl", hash = "sha256:a9f864ef57b7172e2db87a096642dd51e179e085ab6b2c371c29e885f65c8fb2", size = 223573, upload-time = "2026-05-10T18:02:24.11Z" }, + { url = "https://files.pythonhosted.org/packages/27/96/3acae5da0953be042c0b4dea6d6789d2f080701c77b88e44d5bd41b9219b/coverage-7.14.0-cp314-cp314t-win_amd64.whl", hash = "sha256:29943e552fdc08e082eb51400fb2f58e118a83b5542bd06531214e084399b644", size = 224680, upload-time = "2026-05-10T18:02:25.896Z" }, + { url = "https://files.pythonhosted.org/packages/93/3d/6ab5d2dd8325d838737c6f8d83d62eb6230e0d70b87b51b57bbfd08fa767/coverage-7.14.0-cp314-cp314t-win_arm64.whl", hash = "sha256:742a73ea621953b012f2c4c2219b512180dd84489acf5b1596b0aafc55b9100b", size = 222703, upload-time = "2026-05-10T18:02:27.822Z" }, + { url = "https://files.pythonhosted.org/packages/61/e8/cb8e80d6f9f55b99588625062822bf946cf03ed06315df4bd8397f5632a1/coverage-7.14.0-py3-none-any.whl", hash = "sha256:8de5b61163aee3d05c8a2beab6f47913df7981dad1baf82c414d99158c286ab1", size = 211764, upload-time = "2026-05-10T18:02:29.538Z" }, ] [package.optional-dependencies] @@ -1172,11 +1172,11 @@ wheels = [ [[package]] name = "idna" -version = "3.13" +version = "3.14" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ce/cc/762dfb036166873f0059f3b7de4565e1b5bc3d6f28a414c13da27e442f99/idna-3.13.tar.gz", hash = "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242", size = 194210, upload-time = "2026-04-22T16:42:42.314Z" } +sdist = { url = "https://files.pythonhosted.org/packages/05/b1/efac073e0c297ecf2fb33c346989a529d4e19164f1759102dee5953ee17e/idna-3.14.tar.gz", hash = "sha256:466d810d7a2cc1022bea9b037c39728d51ae7dad40d480fc9b7d7ecf98ba8ee3", size = 198272, upload-time = "2026-05-10T20:32:15.935Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3", size = 68629, upload-time = "2026-04-22T16:42:40.909Z" }, + { url = "https://files.pythonhosted.org/packages/6c/3c/3f62dee257eb3d6b2c1ef2a09d36d9793c7111156a73b5654d2c2305e5ce/idna-3.14-py3-none-any.whl", hash = "sha256:e677eaf072e290f7b725f9acf0b3a2bd55f9fd6f7c70abe5f0e34823d0accf69", size = 72184, upload-time = "2026-05-10T20:32:14.295Z" }, ] [[package]] @@ -1444,7 +1444,7 @@ wheels = [ [[package]] name = "jupyter-server" -version = "2.17.0" +version = "2.18.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1467,9 +1467,9 @@ dependencies = [ { name = "traitlets" }, { name = "websocket-client" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5b/ac/e040ec363d7b6b1f11304cc9f209dac4517ece5d5e01821366b924a64a50/jupyter_server-2.17.0.tar.gz", hash = "sha256:c38ea898566964c888b4772ae1ed58eca84592e88251d2cfc4d171f81f7e99d5", size = 731949, upload-time = "2025-08-21T14:42:54.042Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/15/1eacb0fcb79ef86e8a0a79a708e6ad7435f6f223097dd29a4ce861fabc44/jupyter_server-2.18.2.tar.gz", hash = "sha256:06b4f40d8a7a00bb39d5216859c81374a0e7cfefe6d8a5a7facc5a5c37c679a7", size = 753177, upload-time = "2026-05-06T07:04:36.274Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl", hash = "sha256:e8cb9c7db4251f51ed307e329b81b72ccf2056ff82d50524debde1ee1870e13f", size = 388221, upload-time = "2025-08-21T14:42:52.034Z" }, + { url = "https://files.pythonhosted.org/packages/e2/50/ecf4f70d65bdb7519b28a33d1b2fee8a4b4ba1ae1a92f15d97e877c5de21/jupyter_server-2.18.2-py3-none-any.whl", hash = "sha256:fa5e46539ded65791838035a2b6001f13e54d5f64b8b3752eb1e91fdd641a5b8", size = 391907, upload-time = "2026-05-06T07:04:34.014Z" }, ] [[package]] @@ -1855,14 +1855,14 @@ wheels = [ [[package]] name = "matplotlib-inline" -version = "0.2.1" +version = "0.2.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c7/74/97e72a36efd4ae2bccb3463284300f8953f199b5ffbc04cbbb0ec78f74b1/matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe", size = 8110, upload-time = "2025-10-23T09:00:22.126Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/c0/9f7c9a46090390368a4d7bcb76bb87a4a36c421e4c0792cdb53486ffac7a/matplotlib_inline-0.2.2.tar.gz", hash = "sha256:72f3fe8fce36b70d4a5b612f899090cd0401deddc4ea90e1572b9f4bfb058c79", size = 8150, upload-time = "2026-05-08T17:33:33.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" }, + { url = "https://files.pythonhosted.org/packages/41/09/5b161152e2d90f7b87f781c2e1267494aef9c32498df793f73ad0a0a494a/matplotlib_inline-0.2.2-py3-none-any.whl", hash = "sha256:3c821cf1c209f59fb2d2d64abbf5b23b67bcb2210d663f9918dd851c6da1fcf6", size = 9534, upload-time = "2026-05-08T17:33:32.055Z" }, ] [[package]] @@ -2654,15 +2654,15 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.2.2" +version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/de/ef/3bae0e537cfe91e8431efcba4434463d2c5a65f5a89edd47c6cf2f03c55f/python_discovery-1.2.2.tar.gz", hash = "sha256:876e9c57139eb757cb5878cbdd9ae5379e5d96266c99ef731119e04fffe533bb", size = 58872, upload-time = "2026-04-07T17:28:49.249Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/e0/cc5a8653e9a24f6cf84768f05064aa8ed5a83dcefd5e2a043db14a1c5f44/python_discovery-1.3.0.tar.gz", hash = "sha256:d098f1e86be5d45fe4d14bf1029294aabbd332f4321179dec85e76cddce834b0", size = 63925, upload-time = "2026-05-05T14:38:39.769Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/db/795879cc3ddfe338599bddea6388cc5100b088db0a4caf6e6c1af1c27e04/python_discovery-1.2.2-py3-none-any.whl", hash = "sha256:e1ae95d9af875e78f15e19aed0c6137ab1bb49c200f21f5061786490c9585c7a", size = 31894, upload-time = "2026-04-07T17:28:48.09Z" }, + { url = "https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl", hash = "sha256:441d9ced3dfce36e113beb35ca302c71c7ef06f3c0f9c227a0b9bb3bd49b9e9f", size = 33124, upload-time = "2026-05-05T14:38:38.539Z" }, ] [[package]] @@ -2830,7 +2830,7 @@ dependencies = [ { name = "torchvision" }, { name = "tqdm" }, { name = "zarr", version = "3.1.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "zarr", version = "3.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "zarr", version = "3.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] [package.optional-dependencies] @@ -3692,11 +3692,11 @@ wheels = [ [[package]] name = "traitlets" -version = "5.14.3" +version = "5.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/22/40f55b26baeab80c2d7b3f1db0682f8954e4617fee7d90ce634022ef05c6/traitlets-5.15.0.tar.gz", hash = "sha256:4fead733f81cf1c4c938e06f8ca4633896833c9d89eff878159457f4d4392971", size = 163197, upload-time = "2026-05-06T08:05:58.016Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, + { url = "https://files.pythonhosted.org/packages/da/98/a9937a969d018a23badfea0b381f66783649d48e0ea6c41923265c3cbeb3/traitlets-5.15.0-py3-none-any.whl", hash = "sha256:fb36a18867a6803deab09f3c5e0fa81bb7b26a5c9e82501c9933f759166eff40", size = 85877, upload-time = "2026-05-06T08:05:55.853Z" }, ] [[package]] @@ -3747,16 +3747,16 @@ wheels = [ [[package]] name = "urllib3" -version = "2.6.3" +version = "2.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, ] [[package]] name = "virtualenv" -version = "21.3.0" +version = "21.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -3764,9 +3764,9 @@ dependencies = [ { name = "platformdirs" }, { name = "python-discovery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3f/8b/6331f7a7fe70131c301106ec1e7cf23e2501bf7d4ca3636805801ca191bb/virtualenv-21.3.0.tar.gz", hash = "sha256:733750db978ec95c2d8eb4feadaa57091002bce404cb39ba69899cf7bd28944e", size = 7614069, upload-time = "2026-04-27T17:05:58.927Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/0d/915c02c94d207b85580eb09bffab54438a709e7288524094fe781da526c2/virtualenv-21.3.1.tar.gz", hash = "sha256:c2305bc1fddeec40699b8370d13f8d431b0701f00ce895061ce493aeded4426b", size = 7613791, upload-time = "2026-05-05T01:34:31.402Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/eb/03bfb1299d4c4510329e470f13f9a4ce793df7fcb5a2fd3510f911066f61/virtualenv-21.3.0-py3-none-any.whl", hash = "sha256:4d28ee41f6d9ec8f1f00cd472b9ffbcedda1b3d3b9a575b5c94a2d004fd51bd7", size = 7594690, upload-time = "2026-04-27T17:05:55.468Z" }, + { url = "https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-py3-none-any.whl", hash = "sha256:d1a71cf58f2f9228fff23a1f6ec15d39785c6b32e03658d104974247145edd35", size = 7594539, upload-time = "2026-05-05T01:34:28.98Z" }, ] [[package]] @@ -3848,7 +3848,7 @@ wheels = [ [[package]] name = "zarr" -version = "3.2.0" +version = "3.2.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", @@ -3862,9 +3862,9 @@ dependencies = [ { name = "packaging", marker = "python_full_version >= '3.12'" }, { name = "typing-extensions", marker = "python_full_version >= '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/db/27/8f391a4304f503ab6f4df6e1724380ea2e35e78a5d1ba973ba2b1347df5b/zarr-3.2.0.tar.gz", hash = "sha256:5867fa8dd7910541075531368c8eaa6f35957ab5413c68c168830e83948665ed", size = 454948, upload-time = "2026-04-30T22:18:03.074Z" } +sdist = { url = "https://files.pythonhosted.org/packages/93/8d/aeb164004f87543b06ef54f885d02c342c31ceb274e2bbec470a98927621/zarr-3.2.1.tar.gz", hash = "sha256:71565b738a0e7e8ed226f0516eba8c6bb53440ad7669a8c48ebb3534a161d035", size = 675161, upload-time = "2026-05-05T12:37:22.383Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/9e/2e99d08824f300046eba83b480d6be17f771f57eed80dd7c162381cbe4de/zarr-3.2.0-py3-none-any.whl", hash = "sha256:c693bd4ae24328f242e47e9e1ced221e919d9f62cad71030fd059e398320e555", size = 318784, upload-time = "2026-04-30T22:18:01.13Z" }, + { url = "https://files.pythonhosted.org/packages/88/0a/469e2bd01be1490336e6c8707386845655d59261543315778a3ccc7e8019/zarr-3.2.1-py3-none-any.whl", hash = "sha256:f78cdd3d9687ad0e9f9cba2c5683b64f0c52589c19f685eeabe872e93cc0d2c7", size = 319617, upload-time = "2026-05-05T12:37:20.66Z" }, ] [[package]] From 47d9c5057dbbc30b4f87def1501041ec109102a0 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 11 May 2026 11:55:34 -0700 Subject: [PATCH 288/335] address cedric's feedback on scan rotation --- widget/src/quantem/widget/show2d.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/widget/src/quantem/widget/show2d.py b/widget/src/quantem/widget/show2d.py index fbe2f8b3..e243a51d 100644 --- a/widget/src/quantem/widget/show2d.py +++ b/widget/src/quantem/widget/show2d.py @@ -835,6 +835,12 @@ def load_state_dict(self, state): setattr(self, key, val) def summary(self): + """Print a human-readable snapshot of the widget's current state. + + Reports image dimensions and pixel size, data min/max/mean, display + settings (colormap, contrast, scale, FFT), active ROIs and profile + line, per-image rotations, and the most recent render timings. + """ lines = [self.title or "Show2D", "═" * 32] if self.n_images > 1: lines.append(f"Image: {self.n_images}×{self.height}×{self.width} ({self.ncols} cols)") @@ -897,6 +903,15 @@ def _update_all_frames(self): self.frame_bytes = data.tobytes() def _apply_rotations(self): + """Re-rotate each displayed image from its original by ``image_rotations[i] * 90°``. + + This is purely a display-time reorientation of each 2D image via + ``np.rot90`` — it is NOT scan rotation (which would rotate the + scan grid in a 4D-STEM dataset). Originals are kept in + ``_data_original`` so successive rotations compose from the + unrotated source rather than accumulating interpolation error. + Mixed shapes after rotation are center-padded to a common size. + """ # Materialize originals as independent copies only when a non-zero # rotation exists (they start as views into _data to avoid 800MB copy at init) has_rotation = any( From a495a977f7bb7b1853d03393ac8c60cb1e9c6a83 Mon Sep 17 00:00:00 2001 From: quantem-bot Date: Mon, 18 May 2026 12:38:55 +0000 Subject: [PATCH 289/335] chore: update lock file --- uv.lock | 485 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 243 insertions(+), 242 deletions(-) diff --git a/uv.lock b/uv.lock index 80a97226..a01182cc 100644 --- a/uv.lock +++ b/uv.lock @@ -373,14 +373,14 @@ wheels = [ [[package]] name = "click" -version = "8.3.3" +version = "8.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bb/63/f9e1ea081ce35720d8b92acde70daaedace594dc93b693c869e0d5910718/click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2", size = 328061, upload-time = "2026-04-22T15:11:27.506Z" } +sdist = { url = "https://files.pythonhosted.org/packages/23/e4/796662cd90cf80e3a363c99db2b88e0e394b988a575f60a17e16440cd011/click-8.4.0.tar.gz", hash = "sha256:638f1338fe1235c8f4e008e4a8a254fb5c5fbdcbb40ece3c9142ebb78e792973", size = 350843, upload-time = "2026-05-17T00:47:58.425Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613", size = 110502, upload-time = "2026-04-22T15:11:25.044Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ae/8e92f8058baf87f6c7d86ee7e457668690195cc77efedb8d3797a06e3940/click-8.4.0-py3-none-any.whl", hash = "sha256:40c50b7c6c6adac2823d411041ec84f3f103f1b280d5e9ce0d7f998995832f81", size = 116147, upload-time = "2026-05-17T00:47:56.842Z" }, ] [[package]] @@ -671,9 +671,6 @@ wheels = [ ] [package.optional-dependencies] -cublas = [ - { name = "nvidia-cublas", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, -] cudart = [ { name = "nvidia-cuda-runtime", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, ] @@ -765,11 +762,11 @@ wheels = [ [[package]] name = "decorator" -version = "5.2.1" +version = "5.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } +sdist = { url = "https://files.pythonhosted.org/packages/60/8b/32f9823da46cde7df2087faa08cd98d01b908f8dcab982cdba9c84e85355/decorator-5.3.1.tar.gz", hash = "sha256:4cbcdd55a6efadb9dbea26b858f4fb3264567b52d69ca0d25b721b553f60ea82", size = 58084, upload-time = "2026-05-18T06:03:28.057Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, + { url = "https://files.pythonhosted.org/packages/05/7f/798705f5296a58ca505d600456748d1be48078eac8a7050d8a98bc9edb89/decorator-5.3.1-py3-none-any.whl", hash = "sha256:f47fe6fdbd2edd623ecfe36875d37aba411624e2670dd395dddae1358689bb3c", size = 10365, upload-time = "2026-05-18T06:03:26.517Z" }, ] [[package]] @@ -864,51 +861,51 @@ wheels = [ [[package]] name = "fonttools" -version = "4.62.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9a/08/7012b00a9a5874311b639c3920270c36ee0c445b69d9989a85e5c92ebcb0/fonttools-4.62.1.tar.gz", hash = "sha256:e54c75fd6041f1122476776880f7c3c3295ffa31962dc6ebe2543c00dca58b5d", size = 3580737, upload-time = "2026-03-13T13:54:25.52Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/39/23ff32561ec8d45a4d48578b4d241369d9270dc50926c017570e60893701/fonttools-4.62.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:40975849bac44fb0b9253d77420c6d8b523ac4dcdcefeff6e4d706838a5b80f7", size = 2871039, upload-time = "2026-03-13T13:52:33.127Z" }, - { url = "https://files.pythonhosted.org/packages/24/7f/66d3f8a9338a9b67fe6e1739f47e1cd5cee78bd3bc1206ef9b0b982289a5/fonttools-4.62.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9dde91633f77fa576879a0c76b1d89de373cae751a98ddf0109d54e173b40f14", size = 2416346, upload-time = "2026-03-13T13:52:35.676Z" }, - { url = "https://files.pythonhosted.org/packages/aa/53/5276ceba7bff95da7793a07c5284e1da901cf00341ce5e2f3273056c0cca/fonttools-4.62.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6acb4109f8bee00fec985c8c7afb02299e35e9c94b57287f3ea542f28bd0b0a7", size = 5100897, upload-time = "2026-03-13T13:52:38.102Z" }, - { url = "https://files.pythonhosted.org/packages/cc/a1/40a5c4d8e28b0851d53a8eeeb46fbd73c325a2a9a165f290a5ed90e6c597/fonttools-4.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1c5c25671ce8805e0d080e2ffdeca7f1e86778c5cbfbeae86d7f866d8830517b", size = 5071078, upload-time = "2026-03-13T13:52:41.305Z" }, - { url = "https://files.pythonhosted.org/packages/e3/be/d378fca4c65ea1956fee6d90ace6e861776809cbbc5af22388a090c3c092/fonttools-4.62.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a5d8825e1140f04e6c99bb7d37a9e31c172f3bc208afbe02175339e699c710e1", size = 5076908, upload-time = "2026-03-13T13:52:44.122Z" }, - { url = "https://files.pythonhosted.org/packages/f8/d9/ae6a1d0693a4185a84605679c8a1f719a55df87b9c6e8e817bfdd9ef5936/fonttools-4.62.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:268abb1cb221e66c014acc234e872b7870d8b5d4657a83a8f4205094c32d2416", size = 5202275, upload-time = "2026-03-13T13:52:46.591Z" }, - { url = "https://files.pythonhosted.org/packages/54/6c/af95d9c4efb15cabff22642b608342f2bd67137eea6107202d91b5b03184/fonttools-4.62.1-cp311-cp311-win32.whl", hash = "sha256:942b03094d7edbb99bdf1ae7e9090898cad7bf9030b3d21f33d7072dbcb51a53", size = 2293075, upload-time = "2026-03-13T13:52:48.711Z" }, - { url = "https://files.pythonhosted.org/packages/d3/97/bf54c5b3f2be34e1f143e6db838dfdc54f2ffa3e68c738934c82f3b2a08d/fonttools-4.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:e8514f4924375f77084e81467e63238b095abda5107620f49421c368a6017ed2", size = 2344593, upload-time = "2026-03-13T13:52:50.725Z" }, - { url = "https://files.pythonhosted.org/packages/47/d4/dbacced3953544b9a93088cc10ef2b596d348c983d5c67a404fa41ec51ba/fonttools-4.62.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:90365821debbd7db678809c7491ca4acd1e0779b9624cdc6ddaf1f31992bf974", size = 2870219, upload-time = "2026-03-13T13:52:53.664Z" }, - { url = "https://files.pythonhosted.org/packages/66/9e/a769c8e99b81e5a87ab7e5e7236684de4e96246aae17274e5347d11ebd78/fonttools-4.62.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12859ff0b47dd20f110804c3e0d0970f7b832f561630cd879969011541a464a9", size = 2414891, upload-time = "2026-03-13T13:52:56.493Z" }, - { url = "https://files.pythonhosted.org/packages/69/64/f19a9e3911968c37e1e620e14dfc5778299e1474f72f4e57c5ec771d9489/fonttools-4.62.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c125ffa00c3d9003cdaaf7f2c79e6e535628093e14b5de1dccb08859b680936", size = 5033197, upload-time = "2026-03-13T13:52:59.179Z" }, - { url = "https://files.pythonhosted.org/packages/9b/8a/99c8b3c3888c5c474c08dbfd7c8899786de9604b727fcefb055b42c84bba/fonttools-4.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:149f7d84afca659d1a97e39a4778794a2f83bf344c5ee5134e09995086cc2392", size = 4988768, upload-time = "2026-03-13T13:53:02.761Z" }, - { url = "https://files.pythonhosted.org/packages/d1/c6/0f904540d3e6ab463c1243a0d803504826a11604c72dd58c2949796a1762/fonttools-4.62.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0aa72c43a601cfa9273bb1ae0518f1acadc01ee181a6fc60cd758d7fdadffc04", size = 4971512, upload-time = "2026-03-13T13:53:05.678Z" }, - { url = "https://files.pythonhosted.org/packages/29/0b/5cbef6588dc9bd6b5c9ad6a4d5a8ca384d0cea089da31711bbeb4f9654a6/fonttools-4.62.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:19177c8d96c7c36359266e571c5173bcee9157b59cfc8cb0153c5673dc5a3a7d", size = 5122723, upload-time = "2026-03-13T13:53:08.662Z" }, - { url = "https://files.pythonhosted.org/packages/4a/47/b3a5342d381595ef439adec67848bed561ab7fdb1019fa522e82101b7d9c/fonttools-4.62.1-cp312-cp312-win32.whl", hash = "sha256:a24decd24d60744ee8b4679d38e88b8303d86772053afc29b19d23bb8207803c", size = 2281278, upload-time = "2026-03-13T13:53:10.998Z" }, - { url = "https://files.pythonhosted.org/packages/28/b1/0c2ab56a16f409c6c8a68816e6af707827ad5d629634691ff60a52879792/fonttools-4.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:9e7863e10b3de72376280b515d35b14f5eeed639d1aa7824f4cf06779ec65e42", size = 2331414, upload-time = "2026-03-13T13:53:13.992Z" }, - { url = "https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c22b1014017111c401469e3acc5433e6acf6ebcc6aa9efb538a533c800971c79", size = 2865155, upload-time = "2026-03-13T13:53:16.132Z" }, - { url = "https://files.pythonhosted.org/packages/03/c5/0e3966edd5ec668d41dfe418787726752bc07e2f5fd8c8f208615e61fa89/fonttools-4.62.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68959f5fc58ed4599b44aad161c2837477d7f35f5f79402d97439974faebfebe", size = 2412802, upload-time = "2026-03-13T13:53:18.878Z" }, - { url = "https://files.pythonhosted.org/packages/52/94/e6ac4b44026de7786fe46e3bfa0c87e51d5d70a841054065d49cd62bb909/fonttools-4.62.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef46db46c9447103b8f3ff91e8ba009d5fe181b1920a83757a5762551e32bb68", size = 5013926, upload-time = "2026-03-13T13:53:21.379Z" }, - { url = "https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6706d1cb1d5e6251a97ad3c1b9347505c5615c112e66047abbef0f8545fa30d1", size = 4964575, upload-time = "2026-03-13T13:53:23.857Z" }, - { url = "https://files.pythonhosted.org/packages/46/76/7d051671e938b1881670528fec69cc4044315edd71a229c7fd712eaa5119/fonttools-4.62.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e7abd2b1e11736f58c1de27819e1955a53267c21732e78243fa2fa2e5c1e069", size = 4953693, upload-time = "2026-03-13T13:53:26.569Z" }, - { url = "https://files.pythonhosted.org/packages/1f/ae/b41f8628ec0be3c1b934fc12b84f4576a5c646119db4d3bdd76a217c90b5/fonttools-4.62.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:403d28ce06ebfc547fbcb0cb8b7f7cc2f7a2d3e1a67ba9a34b14632df9e080f9", size = 5094920, upload-time = "2026-03-13T13:53:29.329Z" }, - { url = "https://files.pythonhosted.org/packages/f2/f6/53a1e9469331a23dcc400970a27a4caa3d9f6edbf5baab0260285238b884/fonttools-4.62.1-cp313-cp313-win32.whl", hash = "sha256:93c316e0f5301b2adbe6a5f658634307c096fd5aae60a5b3412e4f3e1728ab24", size = 2279928, upload-time = "2026-03-13T13:53:32.352Z" }, - { url = "https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl", hash = "sha256:7aa21ff53e28a9c2157acbc44e5b401149d3c9178107130e82d74ceb500e5056", size = 2330514, upload-time = "2026-03-13T13:53:34.991Z" }, - { url = "https://files.pythonhosted.org/packages/36/f0/2888cdac391807d68d90dcb16ef858ddc1b5309bfc6966195a459dd326e2/fonttools-4.62.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fa1d16210b6b10a826d71bed68dd9ec24a9e218d5a5e2797f37c573e7ec215ca", size = 2864442, upload-time = "2026-03-13T13:53:37.509Z" }, - { url = "https://files.pythonhosted.org/packages/4b/b2/e521803081f8dc35990816b82da6360fa668a21b44da4b53fc9e77efcd62/fonttools-4.62.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:aa69d10ed420d8121118e628ad47d86e4caa79ba37f968597b958f6cceab7eca", size = 2410901, upload-time = "2026-03-13T13:53:40.55Z" }, - { url = "https://files.pythonhosted.org/packages/00/a4/8c3511ff06e53110039358dbbdc1a65d72157a054638387aa2ada300a8b8/fonttools-4.62.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd13b7999d59c5eb1c2b442eb2d0c427cb517a0b7a1f5798fc5c9e003f5ff782", size = 4999608, upload-time = "2026-03-13T13:53:42.798Z" }, - { url = "https://files.pythonhosted.org/packages/28/63/cd0c3b26afe60995a5295f37c246a93d454023726c3261cfbb3559969bb9/fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8d337fdd49a79b0d51c4da87bc38169d21c3abbf0c1aa9367eff5c6656fb6dae", size = 4912726, upload-time = "2026-03-13T13:53:45.405Z" }, - { url = "https://files.pythonhosted.org/packages/70/b9/ac677cb07c24c685cf34f64e140617d58789d67a3dd524164b63648c6114/fonttools-4.62.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d241cdc4a67b5431c6d7f115fdf63335222414995e3a1df1a41e1182acd4bcc7", size = 4951422, upload-time = "2026-03-13T13:53:48.326Z" }, - { url = "https://files.pythonhosted.org/packages/e6/10/11c08419a14b85b7ca9a9faca321accccc8842dd9e0b1c8a72908de05945/fonttools-4.62.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c05557a78f8fa514da0f869556eeda40887a8abc77c76ee3f74cf241778afd5a", size = 5060979, upload-time = "2026-03-13T13:53:51.366Z" }, - { url = "https://files.pythonhosted.org/packages/4e/3c/12eea4a4cf054e7ab058ed5ceada43b46809fce2bf319017c4d63ae55bb4/fonttools-4.62.1-cp314-cp314-win32.whl", hash = "sha256:49a445d2f544ce4a69338694cad575ba97b9a75fff02720da0882d1a73f12800", size = 2283733, upload-time = "2026-03-13T13:53:53.606Z" }, - { url = "https://files.pythonhosted.org/packages/6b/67/74b070029043186b5dd13462c958cb7c7f811be0d2e634309d9a1ffb1505/fonttools-4.62.1-cp314-cp314-win_amd64.whl", hash = "sha256:1eecc128c86c552fb963fe846ca4e011b1be053728f798185a1687502f6d398e", size = 2335663, upload-time = "2026-03-13T13:53:56.23Z" }, - { url = "https://files.pythonhosted.org/packages/42/c5/4d2ed3ca6e33617fc5624467da353337f06e7f637707478903c785bd8e20/fonttools-4.62.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1596aeaddf7f78e21e68293c011316a25267b3effdaccaf4d59bc9159d681b82", size = 2947288, upload-time = "2026-03-13T13:53:59.397Z" }, - { url = "https://files.pythonhosted.org/packages/1f/e9/7ab11ddfda48ed0f89b13380e5595ba572619c27077be0b2c447a63ff351/fonttools-4.62.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8f8fca95d3bb3208f59626a4b0ea6e526ee51f5a8ad5d91821c165903e8d9260", size = 2449023, upload-time = "2026-03-13T13:54:01.642Z" }, - { url = "https://files.pythonhosted.org/packages/b2/10/a800fa090b5e8819942e54e19b55fc7c21fe14a08757c3aa3ca8db358939/fonttools-4.62.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee91628c08e76f77b533d65feb3fbe6d9dad699f95be51cf0d022db94089cdc4", size = 5137599, upload-time = "2026-03-13T13:54:04.495Z" }, - { url = "https://files.pythonhosted.org/packages/37/dc/8ccd45033fffd74deb6912fa1ca524643f584b94c87a16036855b498a1ed/fonttools-4.62.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f37df1cac61d906e7b836abe356bc2f34c99d4477467755c216b72aa3dc748b", size = 4920933, upload-time = "2026-03-13T13:54:07.557Z" }, - { url = "https://files.pythonhosted.org/packages/99/eb/e618adefb839598d25ac8136cd577925d6c513dc0d931d93b8af956210f0/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:92bb00a947e666169c99b43753c4305fc95a890a60ef3aeb2a6963e07902cc87", size = 5016232, upload-time = "2026-03-13T13:54:10.611Z" }, - { url = "https://files.pythonhosted.org/packages/d9/5f/9b5c9bfaa8ec82def8d8168c4f13615990d6ce5996fe52bd49bfb5e05134/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bdfe592802ef939a0e33106ea4a318eeb17822c7ee168c290273cbd5fabd746c", size = 5042987, upload-time = "2026-03-13T13:54:13.569Z" }, - { url = "https://files.pythonhosted.org/packages/90/aa/dfbbe24c6a6afc5c203d90cc0343e24bcbb09e76d67c4d6eef8c2558d7ba/fonttools-4.62.1-cp314-cp314t-win32.whl", hash = "sha256:b820fcb92d4655513d8402d5b219f94481c4443d825b4372c75a2072aa4b357a", size = 2348021, upload-time = "2026-03-13T13:54:16.98Z" }, - { url = "https://files.pythonhosted.org/packages/13/6f/ae9c4e4dd417948407b680855c2c7790efb52add6009aaecff1e3bc50e8e/fonttools-4.62.1-cp314-cp314t-win_amd64.whl", hash = "sha256:59b372b4f0e113d3746b88985f1c796e7bf830dd54b28374cd85c2b8acd7583e", size = 2414147, upload-time = "2026-03-13T13:54:19.416Z" }, - { url = "https://files.pythonhosted.org/packages/fd/ba/56147c165442cc5ba7e82ecf301c9a68353cede498185869e6e02b4c264f/fonttools-4.62.1-py3-none-any.whl", hash = "sha256:7487782e2113861f4ddcc07c3436450659e3caa5e470b27dc2177cade2d8e7fd", size = 1152647, upload-time = "2026-03-13T13:54:22.735Z" }, +version = "4.63.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/84/69/c97f2c18e0db87d2c7b15da1974dace76ae938f1cfa22e2727a648b7ed43/fonttools-4.63.0.tar.gz", hash = "sha256:caeb583deeb5168e694b65cda8b4ee62abedfa66cf88488734466f2366b9c4e0", size = 3597189, upload-time = "2026-05-14T12:04:30.958Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/2b/a7f1545bdf5da69c4bda0cea2a5781f0ad2a6623e0277267672db43c5fe6/fonttools-4.63.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b8ae05d9eacf6081414d759c0a352769ac28ce31280d6bb8e77b03f9e3c449f", size = 2881793, upload-time = "2026-05-14T12:02:56.645Z" }, + { url = "https://files.pythonhosted.org/packages/49/50/965308c703f085f225db2886813b27e015b8b3438c350b22dd65b52c2a2c/fonttools-4.63.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79cdc9f567aec74a72918fd060283911406750cbc9fd28c1316023deb6ce31a9", size = 2428130, upload-time = "2026-05-14T12:02:58.891Z" }, + { url = "https://files.pythonhosted.org/packages/d8/38/6937fbd7f2dc3a6b48725851bc2c15ec949b9af14d9bbcb5fe83cdf9bdf9/fonttools-4.63.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c14b4fd138c4bafcca294765c547914e1aa431ae1ca94ab99d8db08c958bd3b", size = 5111952, upload-time = "2026-05-14T12:03:01.263Z" }, + { url = "https://files.pythonhosted.org/packages/0b/43/a81f20050a3115b57d62c8e781446949512eac36690dc384ccea65ff4cc1/fonttools-4.63.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76ac49f929aecaf82d83250b8347e099d7aecba0f4726c1d9b6df3b8bb5fe18", size = 5082308, upload-time = "2026-05-14T12:03:03.211Z" }, + { url = "https://files.pythonhosted.org/packages/67/00/cdd9d4944ca6ae280d01e69cc37bde3bf663630b837a6fc6d2cd65d80e0e/fonttools-4.63.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dcf076a4474fe0d7367e5bbf5b052c7284fa1feca729c04176ce513521afd8a0", size = 5087932, upload-time = "2026-05-14T12:03:05.147Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f1/0aa0dbea778c75adbef223c42019fd47d22262b905974d62d829545d485f/fonttools-4.63.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7dd683fef0663e9f0f45cf541d788d24caa3ec9db50796b588e1757d8b3bc007", size = 5213271, upload-time = "2026-05-14T12:03:07.238Z" }, + { url = "https://files.pythonhosted.org/packages/a8/99/253e4056e1f0e67b9390125a154b73b5eb73ad521bece95c004858fdeec2/fonttools-4.63.0-cp311-cp311-win32.whl", hash = "sha256:afefc1ed0a59785a7fb06ea7e1678e849c193e1e387db783579bc7b3056fcfcb", size = 2304473, upload-time = "2026-05-14T12:03:09.271Z" }, + { url = "https://files.pythonhosted.org/packages/08/60/defa5e69641db890a63be281f41345f4c33b157824eaf0b9fad3e08b0dcb/fonttools-4.63.0-cp311-cp311-win_amd64.whl", hash = "sha256:063e08bd17bd5a90127a14123de0d6a952dbc847695fd98b63c043d58057f90c", size = 2356389, upload-time = "2026-05-14T12:03:11.53Z" }, + { url = "https://files.pythonhosted.org/packages/08/ef/b3c6b9b5be2f82416d73fe2ed2e96e2793cd80e7510bd6a17ca79cdd88ec/fonttools-4.63.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:37dd23e621e3b0aef1baa70a303b80aaf38449632cfc8fd2a55fb285bbccfc02", size = 2881131, upload-time = "2026-05-14T12:03:13.386Z" }, + { url = "https://files.pythonhosted.org/packages/44/a0/c815bea63117fa63e4e1c01f8a1110d2112fa003f838e6467094ec2432ce/fonttools-4.63.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a9faff9e0c1f76f9fd55899d2ce785832efebab37eb8ae13995853aef178bef0", size = 2426704, upload-time = "2026-05-14T12:03:15.801Z" }, + { url = "https://files.pythonhosted.org/packages/44/04/0b91d8e916e92ad1fac9e4624760baf0fd5ff2ead614c2f68fb21373f03f/fonttools-4.63.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef3048ef05dbb552b89817713d9cac912e00d0fde4a3105c00d29e52e10c89af", size = 5044298, upload-time = "2026-05-14T12:03:18.085Z" }, + { url = "https://files.pythonhosted.org/packages/77/c7/2342da9830e3e9d4870305ca5d2091d2a83284f2953079b7bdd3b5e029d8/fonttools-4.63.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58dc6bb86a78d782f00f9190ca02c119cf5bbe2807536e361e18d42019f877d8", size = 4999800, upload-time = "2026-05-14T12:03:20.161Z" }, + { url = "https://files.pythonhosted.org/packages/e6/6d/67fe16c48d7ce050979b33f47e0d28a318f02da030602e944c34f7a16ef3/fonttools-4.63.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee08ebfa58f6e1aeff5697ab9582105bb620008c1caafb681e4c557e7483027b", size = 4982666, upload-time = "2026-05-14T12:03:22.87Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/3bbab338c07c71fa56269953845e92c951a61457bbbb0f1022551ea266d9/fonttools-4.63.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:27fdc65af8da6f88b9c6121c47a464cbe359fcfff7ff6fc2d37a1f395d755b78", size = 5133598, upload-time = "2026-05-14T12:03:25.168Z" }, + { url = "https://files.pythonhosted.org/packages/62/f2/aa27c7f98db5b064883dadcc5283947e81e034de42e22a33675878d98b54/fonttools-4.63.0-cp312-cp312-win32.whl", hash = "sha256:af2fd1664d00a397d75f806985ddb36282091c2131a73a6485c23b4a34722263", size = 2292575, upload-time = "2026-05-14T12:03:27.496Z" }, + { url = "https://files.pythonhosted.org/packages/87/36/cccb9bc2a6ab63d1b2980374f0dca72ce95ae267c9b4cfe77455bb70d0d4/fonttools-4.63.0-cp312-cp312-win_amd64.whl", hash = "sha256:59ac449f8cca9b4ffa08d2e7bbadad87ce710d69d1eda5c3c1ce579baa987272", size = 2343211, upload-time = "2026-05-14T12:03:30.057Z" }, + { url = "https://files.pythonhosted.org/packages/0f/8d/d8fec3dcde2963f8c908fb315e5ff2cd0ac34f82394bbbf73a2aa5145ce3/fonttools-4.63.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd7e9857e5e63738b9d9fd707bc1f59c8b09e5177726d23664db393c59bb08bd", size = 2876062, upload-time = "2026-05-14T12:03:32.554Z" }, + { url = "https://files.pythonhosted.org/packages/ef/71/d935dc54e4ff121bfdd11e08702db63a7e6f25af21d8a3d7b7212df53641/fonttools-4.63.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c2a2a42198b696a6f48fad91709afb55176e66a5e566131219dba372fb7f8c59", size = 2424594, upload-time = "2026-05-14T12:03:34.86Z" }, + { url = "https://files.pythonhosted.org/packages/8e/40/e76320afa1df918e146155ef239b1719ee266092e96f5423bfd075affba1/fonttools-4.63.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e874792a8212b44583ea02189d9e693906b2f78b261f372f95d6c563210ac1d", size = 5024840, upload-time = "2026-05-14T12:03:36.745Z" }, + { url = "https://files.pythonhosted.org/packages/ce/36/0b805d8c485f872f65a509cbe3b58a5d0d17bee855333b54a150c79d3061/fonttools-4.63.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22135da48a348785c5e2d5d2d9d6bec5ed44adacbaeb9db12d9493bf6c6bfa68", size = 4975801, upload-time = "2026-05-14T12:03:38.833Z" }, + { url = "https://files.pythonhosted.org/packages/c8/26/2cee03d0aa083ab022da5c07aff9ed3f689da1defb81ad6917c9627896da/fonttools-4.63.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ccf41f2efdf56994d22d73bef4ced1052161958169428d06ba9724ea9e9a64be", size = 4965009, upload-time = "2026-05-14T12:03:41.494Z" }, + { url = "https://files.pythonhosted.org/packages/7e/48/cc4b66d9058c0d0982c833fad10127c4b0e9324606aafa41382295ca4102/fonttools-4.63.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9ced0bd02ac751dd6319b0da88aaef24414e3b0dbc32bb4f24944821a3741a27", size = 5105892, upload-time = "2026-05-14T12:03:43.525Z" }, + { url = "https://files.pythonhosted.org/packages/d8/1f/a98a30a814b9ddef3a2e706025f90b9e0bc94890e6cb15254bc86547d11a/fonttools-4.63.0-cp313-cp313-win32.whl", hash = "sha256:85be818f5506e8a7753153def2c9550178f0ecae6a47b5e0e8dbb23f7cc90380", size = 2291313, upload-time = "2026-05-14T12:03:45.594Z" }, + { url = "https://files.pythonhosted.org/packages/92/46/5177b01f3b4abfdd4409f31cca4ab279c9343a26efbe9ec78c97fc612e02/fonttools-4.63.0-cp313-cp313-win_amd64.whl", hash = "sha256:ba04cb5891d4c0c21b6da95eda8d7b090021508a294fff33464fc7d241e0856b", size = 2342299, upload-time = "2026-05-14T12:03:47.414Z" }, + { url = "https://files.pythonhosted.org/packages/27/d2/23d25e3f247b328be58d04a4c9f894178a0d1eda7d42867cfb388adaf416/fonttools-4.63.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fd1e3094f42d806d3d7c79162fc59e5910fcbe3a7360c385b8da969bc4493745", size = 2875338, upload-time = "2026-05-14T12:03:50.052Z" }, + { url = "https://files.pythonhosted.org/packages/cd/58/7dfa0c761cb3b2964e2a84c4dc986c926a87de0cb9fb60d5b28ded3f2914/fonttools-4.63.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6e528da43bc3791085f8cb6141b1d13e459226790240340fcbb4625649238b03", size = 2422661, upload-time = "2026-05-14T12:03:52.154Z" }, + { url = "https://files.pythonhosted.org/packages/dd/87/64cfa18a7a1621d17b7f4502b2b0ed8a135a90c3db51ea590ee99043e76b/fonttools-4.63.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b2248c5decb223562f7902ff6325077a073f608ee8e33e88ad88db734eb9f49", size = 5010526, upload-time = "2026-05-14T12:03:54.647Z" }, + { url = "https://files.pythonhosted.org/packages/36/e1/a8933a72c45a87177fbde2696e0d0755c8c9062f8c077a961c6215fa27b1/fonttools-4.63.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:308f957cdeaf8abe4e5f2f124902ef405448af92c90f80e302a3b771c2e6116b", size = 4923946, upload-time = "2026-05-14T12:03:56.984Z" }, + { url = "https://files.pythonhosted.org/packages/27/60/872e6e233b8c5e8b41413796ff18b7fe479661bd40147e071b450dfad7a1/fonttools-4.63.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bf00f21eb5fb721dbaf73d1e9da6d02a1af7768f2ebcf9798be98beab8ba90f6", size = 4962489, upload-time = "2026-05-14T12:03:59.443Z" }, + { url = "https://files.pythonhosted.org/packages/30/c4/83c24f2ec38b90cfda84bf4b1a1f49df80e84a1db4e7ac6e0d41bf23bc39/fonttools-4.63.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c1aaa4b9c75798400ac043ce04d74e7830376c85095a5a6ed7cba2f17a266bf4", size = 5071870, upload-time = "2026-05-14T12:04:02.122Z" }, + { url = "https://files.pythonhosted.org/packages/de/40/3ae22b60ff1d41ce0bd044b31238cdc72cef99f28b976f1e128ebd618c9b/fonttools-4.63.0-cp314-cp314-win32.whl", hash = "sha256:22693918177bd9ceabec4736d338045f357769416fc6b0b2508eefef75b08616", size = 2295026, upload-time = "2026-05-14T12:04:04.47Z" }, + { url = "https://files.pythonhosted.org/packages/c3/d4/98078064ccc76b45cb0f6c002452011e93c4bd26f6850344f0951cc1fe89/fonttools-4.63.0-cp314-cp314-win_amd64.whl", hash = "sha256:7d782fac32985914c351556f68ac0855391572bcd87de50e05970d3cd4c96fc5", size = 2347454, upload-time = "2026-05-14T12:04:06.752Z" }, + { url = "https://files.pythonhosted.org/packages/49/4e/652d1580c5f4e39f7d103b0c793e4773129ad633dce4addd0cf4dfebde02/fonttools-4.63.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6db5140a60a5d731d21ec076745b40a310607731b0a565b50776393188649001", size = 2958152, upload-time = "2026-05-14T12:04:08.706Z" }, + { url = "https://files.pythonhosted.org/packages/0e/55/ad864c9a9b219f552eb46b32cd7906c466e5a578ba0c3abfcc0fe7413eb6/fonttools-4.63.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d76edbff9014094dbf03bd2d074709dfa6ec7aba13d838c937a2b33d2d6a86e", size = 2460809, upload-time = "2026-05-14T12:04:10.783Z" }, + { url = "https://files.pythonhosted.org/packages/ea/2b/0aa8db70f18cf52e49b4ed5ecec68547f981160bf5ded3b5aed6faa0a6f9/fonttools-4.63.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0eac00b9118c3c2f87d272e45341871c5b3066baa3c86897fa634a7c3fb59096", size = 5148649, upload-time = "2026-05-14T12:04:12.747Z" }, + { url = "https://files.pythonhosted.org/packages/7f/63/18e4369c25043096f1048e0c9915951adc4f842bd81c6b18155824d6fa99/fonttools-4.63.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51394295f1a51de8b5f30bdb1e1b9a4231536c7064ef5c6e211eec19fa36036f", size = 4932147, upload-time = "2026-05-14T12:04:14.806Z" }, + { url = "https://files.pythonhosted.org/packages/a1/3f/67f3eac2ffd8a98446c5022f8ed3864eac878a5ff7af8df4c8286dba16cc/fonttools-4.63.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9e12f105d2b6342c559c298afb674006bb2893afc7102dcf8a1b55b0486b4e40", size = 5027237, upload-time = "2026-05-14T12:04:17.675Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ba/4e6214cb38a7b04779e97bb7636de9a5c7f20af7018d03dee0b64c08510a/fonttools-4.63.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:796f27556dbe094c4824f75ca85267e4df776c79036c8441469a4df37038c196", size = 5053933, upload-time = "2026-05-14T12:04:20.818Z" }, + { url = "https://files.pythonhosted.org/packages/34/3b/214dcc19ee31d3d38fb5ad2755c11ef0514e5dc300bbaf41c0b69f393799/fonttools-4.63.0-cp314-cp314t-win32.whl", hash = "sha256:948428a275741f0b64b113c955425a953314f4b9ab9997f73a72c83e68e569c8", size = 2359326, upload-time = "2026-05-14T12:04:24.22Z" }, + { url = "https://files.pythonhosted.org/packages/dd/1e/3ff1a9b523058c2eeb6a9d50f5574e2a738200d0d94107d5bc4105e8da3f/fonttools-4.63.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6d4741eb179121cab9eea4cb2393d24492373a260d7945006358c08cfbf45419", size = 2425829, upload-time = "2026-05-14T12:04:26.829Z" }, + { url = "https://files.pythonhosted.org/packages/2c/47/c99d5268f354002ce80f8d029cd9d7d872969da1de8b93d32de4dc56d6f4/fonttools-4.63.0-py3-none-any.whl", hash = "sha256:445af2eab030a16b9171ea8bdda7ebf7d96bda2df88ee182a464252f6e05e20d", size = 1164562, upload-time = "2026-05-14T12:04:29.092Z" }, ] [[package]] @@ -1172,11 +1169,11 @@ wheels = [ [[package]] name = "idna" -version = "3.14" +version = "3.15" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/b1/efac073e0c297ecf2fb33c346989a529d4e19164f1759102dee5953ee17e/idna-3.14.tar.gz", hash = "sha256:466d810d7a2cc1022bea9b037c39728d51ae7dad40d480fc9b7d7ecf98ba8ee3", size = 198272, upload-time = "2026-05-10T20:32:15.935Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/3c/3f62dee257eb3d6b2c1ef2a09d36d9793c7111156a73b5654d2c2305e5ce/idna-3.14-py3-none-any.whl", hash = "sha256:e677eaf072e290f7b725f9acf0b3a2bd55f9fd6f7c70abe5f0e34823d0accf69", size = 72184, upload-time = "2026-05-10T20:32:14.295Z" }, + { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" }, ] [[package]] @@ -2011,90 +2008,93 @@ wheels = [ [[package]] name = "numpy" -version = "2.4.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/9f/b8cef5bffa569759033adda9481211426f12f53299629b410340795c2514/numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0", size = 20731587, upload-time = "2026-03-29T13:22:01.298Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/c6/4218570d8c8ecc9704b5157a3348e486e84ef4be0ed3e38218ab473c83d2/numpy-2.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f983334aea213c99992053ede6168500e5f086ce74fbc4acc3f2b00f5762e9db", size = 16976799, upload-time = "2026-03-29T13:18:15.438Z" }, - { url = "https://files.pythonhosted.org/packages/dd/92/b4d922c4a5f5dab9ed44e6153908a5c665b71acf183a83b93b690996e39b/numpy-2.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72944b19f2324114e9dc86a159787333b77874143efcf89a5167ef83cfee8af0", size = 14971552, upload-time = "2026-03-29T13:18:18.606Z" }, - { url = "https://files.pythonhosted.org/packages/8a/dc/df98c095978fa6ee7b9a9387d1d58cbb3d232d0e69ad169a4ce784bde4fd/numpy-2.4.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:86b6f55f5a352b48d7fbfd2dbc3d5b780b2d79f4d3c121f33eb6efb22e9a2015", size = 5476566, upload-time = "2026-03-29T13:18:21.532Z" }, - { url = "https://files.pythonhosted.org/packages/28/34/b3fdcec6e725409223dd27356bdf5a3c2cc2282e428218ecc9cb7acc9763/numpy-2.4.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:ba1f4fc670ed79f876f70082eff4f9583c15fb9a4b89d6188412de4d18ae2f40", size = 6806482, upload-time = "2026-03-29T13:18:23.634Z" }, - { url = "https://files.pythonhosted.org/packages/68/62/63417c13aa35d57bee1337c67446761dc25ea6543130cf868eace6e8157b/numpy-2.4.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a87ec22c87be071b6bdbd27920b129b94f2fc964358ce38f3822635a3e2e03d", size = 15973376, upload-time = "2026-03-29T13:18:26.677Z" }, - { url = "https://files.pythonhosted.org/packages/cf/c5/9fcb7e0e69cef59cf10c746b84f7d58b08bc66a6b7d459783c5a4f6101a6/numpy-2.4.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df3775294accfdd75f32c74ae39fcba920c9a378a2fc18a12b6820aa8c1fb502", size = 16925137, upload-time = "2026-03-29T13:18:30.14Z" }, - { url = "https://files.pythonhosted.org/packages/7e/43/80020edacb3f84b9efdd1591120a4296462c23fd8db0dde1666f6ef66f13/numpy-2.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d4e437e295f18ec29bc79daf55e8a47a9113df44d66f702f02a293d93a2d6dd", size = 17329414, upload-time = "2026-03-29T13:18:33.733Z" }, - { url = "https://files.pythonhosted.org/packages/fd/06/af0658593b18a5f73532d377188b964f239eb0894e664a6c12f484472f97/numpy-2.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6aa3236c78803afbcb255045fbef97a9e25a1f6c9888357d205ddc42f4d6eba5", size = 18658397, upload-time = "2026-03-29T13:18:37.511Z" }, - { url = "https://files.pythonhosted.org/packages/e6/ce/13a09ed65f5d0ce5c7dd0669250374c6e379910f97af2c08c57b0608eee4/numpy-2.4.4-cp311-cp311-win32.whl", hash = "sha256:30caa73029a225b2d40d9fae193e008e24b2026b7ee1a867b7ee8d96ca1a448e", size = 6239499, upload-time = "2026-03-29T13:18:40.372Z" }, - { url = "https://files.pythonhosted.org/packages/bd/63/05d193dbb4b5eec1eca73822d80da98b511f8328ad4ae3ca4caf0f4db91d/numpy-2.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:6bbe4eb67390b0a0265a2c25458f6b90a409d5d069f1041e6aff1e27e3d9a79e", size = 12614257, upload-time = "2026-03-29T13:18:42.95Z" }, - { url = "https://files.pythonhosted.org/packages/87/c5/8168052f080c26fa984c413305012be54741c9d0d74abd7fbeeccae3889f/numpy-2.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:fcfe2045fd2e8f3cb0ce9d4ba6dba6333b8fa05bb8a4939c908cd43322d14c7e", size = 10486775, upload-time = "2026-03-29T13:18:45.835Z" }, - { url = "https://files.pythonhosted.org/packages/28/05/32396bec30fb2263770ee910142f49c1476d08e8ad41abf8403806b520ce/numpy-2.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15716cfef24d3a9762e3acdf87e27f58dc823d1348f765bbea6bef8c639bfa1b", size = 16689272, upload-time = "2026-03-29T13:18:49.223Z" }, - { url = "https://files.pythonhosted.org/packages/c5/f3/a983d28637bfcd763a9c7aafdb6d5c0ebf3d487d1e1459ffdb57e2f01117/numpy-2.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23cbfd4c17357c81021f21540da84ee282b9c8fba38a03b7b9d09ba6b951421e", size = 14699573, upload-time = "2026-03-29T13:18:52.629Z" }, - { url = "https://files.pythonhosted.org/packages/9b/fd/e5ecca1e78c05106d98028114f5c00d3eddb41207686b2b7de3e477b0e22/numpy-2.4.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b3b60bb7cba2c8c81837661c488637eee696f59a877788a396d33150c35d842", size = 5204782, upload-time = "2026-03-29T13:18:55.579Z" }, - { url = "https://files.pythonhosted.org/packages/de/2f/702a4594413c1a8632092beae8aba00f1d67947389369b3777aed783fdca/numpy-2.4.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e4a010c27ff6f210ff4c6ef34394cd61470d01014439b192ec22552ee867f2a8", size = 6552038, upload-time = "2026-03-29T13:18:57.769Z" }, - { url = "https://files.pythonhosted.org/packages/7f/37/eed308a8f56cba4d1fdf467a4fc67ef4ff4bf1c888f5fc980481890104b1/numpy-2.4.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9e75681b59ddaa5e659898085ae0eaea229d054f2ac0c7e563a62205a700121", size = 15670666, upload-time = "2026-03-29T13:19:00.341Z" }, - { url = "https://files.pythonhosted.org/packages/0a/0d/0e3ecece05b7a7e87ab9fb587855548da437a061326fff64a223b6dcb78a/numpy-2.4.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81f4a14bee47aec54f883e0cad2d73986640c1590eb9bfaaba7ad17394481e6e", size = 16645480, upload-time = "2026-03-29T13:19:03.63Z" }, - { url = "https://files.pythonhosted.org/packages/34/49/f2312c154b82a286758ee2f1743336d50651f8b5195db18cdb63675ff649/numpy-2.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62d6b0f03b694173f9fcb1fb317f7222fd0b0b103e784c6549f5e53a27718c44", size = 17020036, upload-time = "2026-03-29T13:19:07.428Z" }, - { url = "https://files.pythonhosted.org/packages/7b/e9/736d17bd77f1b0ec4f9901aaec129c00d59f5d84d5e79bba540ef12c2330/numpy-2.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbc356aae7adf9e6336d336b9c8111d390a05df88f1805573ebb0807bd06fd1d", size = 18368643, upload-time = "2026-03-29T13:19:10.775Z" }, - { url = "https://files.pythonhosted.org/packages/63/f6/d417977c5f519b17c8a5c3bc9e8304b0908b0e21136fe43bf628a1343914/numpy-2.4.4-cp312-cp312-win32.whl", hash = "sha256:0d35aea54ad1d420c812bfa0385c71cd7cc5bcf7c65fed95fc2cd02fe8c79827", size = 5961117, upload-time = "2026-03-29T13:19:13.464Z" }, - { url = "https://files.pythonhosted.org/packages/2d/5b/e1deebf88ff431b01b7406ca3583ab2bbb90972bbe1c568732e49c844f7e/numpy-2.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:b5f0362dc928a6ecd9db58868fca5e48485205e3855957bdedea308f8672ea4a", size = 12320584, upload-time = "2026-03-29T13:19:16.155Z" }, - { url = "https://files.pythonhosted.org/packages/58/89/e4e856ac82a68c3ed64486a544977d0e7bdd18b8da75b78a577ca31c4395/numpy-2.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:846300f379b5b12cc769334464656bc882e0735d27d9726568bc932fdc49d5ec", size = 10221450, upload-time = "2026-03-29T13:19:18.994Z" }, - { url = "https://files.pythonhosted.org/packages/14/1d/d0a583ce4fefcc3308806a749a536c201ed6b5ad6e1322e227ee4848979d/numpy-2.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:08f2e31ed5e6f04b118e49821397f12767934cfdd12a1ce86a058f91e004ee50", size = 16684933, upload-time = "2026-03-29T13:19:22.47Z" }, - { url = "https://files.pythonhosted.org/packages/c1/62/2b7a48fbb745d344742c0277f01286dead15f3f68e4f359fbfcf7b48f70f/numpy-2.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e823b8b6edc81e747526f70f71a9c0a07ac4e7ad13020aa736bb7c9d67196115", size = 14694532, upload-time = "2026-03-29T13:19:25.581Z" }, - { url = "https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af", size = 5199661, upload-time = "2026-03-29T13:19:28.31Z" }, - { url = "https://files.pythonhosted.org/packages/cd/da/464d551604320d1491bc345efed99b4b7034143a85787aab78d5691d5a0e/numpy-2.4.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d2a8490669bfe99a233298348acc2d824d496dee0e66e31b66a6022c2ad74a5c", size = 6547539, upload-time = "2026-03-29T13:19:30.97Z" }, - { url = "https://files.pythonhosted.org/packages/7d/90/8d23e3b0dafd024bf31bdec225b3bb5c2dbfa6912f8a53b8659f21216cbf/numpy-2.4.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45dbed2ab436a9e826e302fcdcbe9133f9b0006e5af7168afb8963a6520da103", size = 15668806, upload-time = "2026-03-29T13:19:33.887Z" }, - { url = "https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83", size = 16632682, upload-time = "2026-03-29T13:19:37.336Z" }, - { url = "https://files.pythonhosted.org/packages/34/fb/14570d65c3bde4e202a031210475ae9cde9b7686a2e7dc97ee67d2833b35/numpy-2.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:99d838547ace2c4aace6c4f76e879ddfe02bb58a80c1549928477862b7a6d6ed", size = 17019810, upload-time = "2026-03-29T13:19:40.963Z" }, - { url = "https://files.pythonhosted.org/packages/8a/77/2ba9d87081fd41f6d640c83f26fb7351e536b7ce6dd9061b6af5904e8e46/numpy-2.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0aec54fd785890ecca25a6003fd9a5aed47ad607bbac5cd64f836ad8666f4959", size = 18357394, upload-time = "2026-03-29T13:19:44.859Z" }, - { url = "https://files.pythonhosted.org/packages/a2/23/52666c9a41708b0853fa3b1a12c90da38c507a3074883823126d4e9d5b30/numpy-2.4.4-cp313-cp313-win32.whl", hash = "sha256:07077278157d02f65c43b1b26a3886bce886f95d20aabd11f87932750dfb14ed", size = 5959556, upload-time = "2026-03-29T13:19:47.661Z" }, - { url = "https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf", size = 12317311, upload-time = "2026-03-29T13:19:50.67Z" }, - { url = "https://files.pythonhosted.org/packages/ba/d8/11490cddd564eb4de97b4579ef6bfe6a736cc07e94c1598590ae25415e01/numpy-2.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:ef4059d6e5152fa1a39f888e344c73fdc926e1b2dd58c771d67b0acfbf2aa67d", size = 10222060, upload-time = "2026-03-29T13:19:54.229Z" }, - { url = "https://files.pythonhosted.org/packages/99/5d/dab4339177a905aad3e2221c915b35202f1ec30d750dd2e5e9d9a72b804b/numpy-2.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4bbc7f303d125971f60ec0aaad5e12c62d0d2c925f0ab1273debd0e4ba37aba5", size = 14822302, upload-time = "2026-03-29T13:19:57.585Z" }, - { url = "https://files.pythonhosted.org/packages/eb/e4/0564a65e7d3d97562ed6f9b0fd0fb0a6f559ee444092f105938b50043876/numpy-2.4.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:4d6d57903571f86180eb98f8f0c839fa9ebbfb031356d87f1361be91e433f5b7", size = 5327407, upload-time = "2026-03-29T13:20:00.601Z" }, - { url = "https://files.pythonhosted.org/packages/29/8d/35a3a6ce5ad371afa58b4700f1c820f8f279948cca32524e0a695b0ded83/numpy-2.4.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:4636de7fd195197b7535f231b5de9e4b36d2c440b6e566d2e4e4746e6af0ca93", size = 6647631, upload-time = "2026-03-29T13:20:02.855Z" }, - { url = "https://files.pythonhosted.org/packages/f4/da/477731acbd5a58a946c736edfdabb2ac5b34c3d08d1ba1a7b437fa0884df/numpy-2.4.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad2e2ef14e0b04e544ea2fa0a36463f847f113d314aa02e5b402fdf910ef309e", size = 15727691, upload-time = "2026-03-29T13:20:06.004Z" }, - { url = "https://files.pythonhosted.org/packages/e6/db/338535d9b152beabeb511579598418ba0212ce77cf9718edd70262cc4370/numpy-2.4.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a285b3b96f951841799528cd1f4f01cd70e7e0204b4abebac9463eecfcf2a40", size = 16681241, upload-time = "2026-03-29T13:20:09.417Z" }, - { url = "https://files.pythonhosted.org/packages/e2/a9/ad248e8f58beb7a0219b413c9c7d8151c5d285f7f946c3e26695bdbbe2df/numpy-2.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f8474c4241bc18b750be2abea9d7a9ec84f46ef861dbacf86a4f6e043401f79e", size = 17085767, upload-time = "2026-03-29T13:20:13.126Z" }, - { url = "https://files.pythonhosted.org/packages/b5/1a/3b88ccd3694681356f70da841630e4725a7264d6a885c8d442a697e1146b/numpy-2.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4e874c976154687c1f71715b034739b45c7711bec81db01914770373d125e392", size = 18403169, upload-time = "2026-03-29T13:20:17.096Z" }, - { url = "https://files.pythonhosted.org/packages/c2/c9/fcfd5d0639222c6eac7f304829b04892ef51c96a75d479214d77e3ce6e33/numpy-2.4.4-cp313-cp313t-win32.whl", hash = "sha256:9c585a1790d5436a5374bac930dad6ed244c046ed91b2b2a3634eb2971d21008", size = 6083477, upload-time = "2026-03-29T13:20:20.195Z" }, - { url = "https://files.pythonhosted.org/packages/d5/e3/3938a61d1c538aaec8ed6fd6323f57b0c2d2d2219512434c5c878db76553/numpy-2.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:93e15038125dc1e5345d9b5b68aa7f996ec33b98118d18c6ca0d0b7d6198b7e8", size = 12457487, upload-time = "2026-03-29T13:20:22.946Z" }, - { url = "https://files.pythonhosted.org/packages/97/6a/7e345032cc60501721ef94e0e30b60f6b0bd601f9174ebd36389a2b86d40/numpy-2.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:0dfd3f9d3adbe2920b68b5cd3d51444e13a10792ec7154cd0a2f6e74d4ab3233", size = 10292002, upload-time = "2026-03-29T13:20:25.909Z" }, - { url = "https://files.pythonhosted.org/packages/6e/06/c54062f85f673dd5c04cbe2f14c3acb8c8b95e3384869bb8cc9bff8cb9df/numpy-2.4.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f169b9a863d34f5d11b8698ead99febeaa17a13ca044961aa8e2662a6c7766a0", size = 16684353, upload-time = "2026-03-29T13:20:29.504Z" }, - { url = "https://files.pythonhosted.org/packages/4c/39/8a320264a84404c74cc7e79715de85d6130fa07a0898f67fb5cd5bd79908/numpy-2.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2483e4584a1cb3092da4470b38866634bafb223cbcd551ee047633fd2584599a", size = 14704914, upload-time = "2026-03-29T13:20:33.547Z" }, - { url = "https://files.pythonhosted.org/packages/91/fb/287076b2614e1d1044235f50f03748f31fa287e3dbe6abeb35cdfa351eca/numpy-2.4.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:2d19e6e2095506d1736b7d80595e0f252d76b89f5e715c35e06e937679ea7d7a", size = 5210005, upload-time = "2026-03-29T13:20:36.45Z" }, - { url = "https://files.pythonhosted.org/packages/63/eb/fcc338595309910de6ecabfcef2419a9ce24399680bfb149421fa2df1280/numpy-2.4.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6a246d5914aa1c820c9443ddcee9c02bec3e203b0c080349533fae17727dfd1b", size = 6544974, upload-time = "2026-03-29T13:20:39.014Z" }, - { url = "https://files.pythonhosted.org/packages/44/5d/e7e9044032a716cdfaa3fba27a8e874bf1c5f1912a1ddd4ed071bf8a14a6/numpy-2.4.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:989824e9faf85f96ec9c7761cd8d29c531ad857bfa1daa930cba85baaecf1a9a", size = 15684591, upload-time = "2026-03-29T13:20:42.146Z" }, - { url = "https://files.pythonhosted.org/packages/98/7c/21252050676612625449b4807d6b695b9ce8a7c9e1c197ee6216c8a65c7c/numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d", size = 16637700, upload-time = "2026-03-29T13:20:46.204Z" }, - { url = "https://files.pythonhosted.org/packages/b1/29/56d2bbef9465db24ef25393383d761a1af4f446a1df9b8cded4fe3a5a5d7/numpy-2.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e44319a2953c738205bf3354537979eaa3998ed673395b964c1176083dd46252", size = 17035781, upload-time = "2026-03-29T13:20:50.242Z" }, - { url = "https://files.pythonhosted.org/packages/e3/2b/a35a6d7589d21f44cea7d0a98de5ddcbb3d421b2622a5c96b1edf18707c3/numpy-2.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e892aff75639bbef0d2a2cfd55535510df26ff92f63c92cd84ef8d4ba5a5557f", size = 18362959, upload-time = "2026-03-29T13:20:54.019Z" }, - { url = "https://files.pythonhosted.org/packages/64/c9/d52ec581f2390e0f5f85cbfd80fb83d965fc15e9f0e1aec2195faa142cde/numpy-2.4.4-cp314-cp314-win32.whl", hash = "sha256:1378871da56ca8943c2ba674530924bb8ca40cd228358a3b5f302ad60cf875fc", size = 6008768, upload-time = "2026-03-29T13:20:56.912Z" }, - { url = "https://files.pythonhosted.org/packages/fa/22/4cc31a62a6c7b74a8730e31a4274c5dc80e005751e277a2ce38e675e4923/numpy-2.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:715d1c092715954784bc79e1174fc2a90093dc4dc84ea15eb14dad8abdcdeb74", size = 12449181, upload-time = "2026-03-29T13:20:59.548Z" }, - { url = "https://files.pythonhosted.org/packages/70/2e/14cda6f4d8e396c612d1bf97f22958e92148801d7e4f110cabebdc0eef4b/numpy-2.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:2c194dd721e54ecad9ad387c1d35e63dce5c4450c6dc7dd5611283dda239aabb", size = 10496035, upload-time = "2026-03-29T13:21:02.524Z" }, - { url = "https://files.pythonhosted.org/packages/b1/e8/8fed8c8d848d7ecea092dc3469643f9d10bc3a134a815a3b033da1d2039b/numpy-2.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2aa0613a5177c264ff5921051a5719d20095ea586ca88cc802c5c218d1c67d3e", size = 14824958, upload-time = "2026-03-29T13:21:05.671Z" }, - { url = "https://files.pythonhosted.org/packages/05/1a/d8007a5138c179c2bf33ef44503e83d70434d2642877ee8fbb230e7c0548/numpy-2.4.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:42c16925aa5a02362f986765f9ebabf20de75cdefdca827d14315c568dcab113", size = 5330020, upload-time = "2026-03-29T13:21:08.635Z" }, - { url = "https://files.pythonhosted.org/packages/99/64/ffb99ac6ae93faf117bcbd5c7ba48a7f45364a33e8e458545d3633615dda/numpy-2.4.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:874f200b2a981c647340f841730fc3a2b54c9d940566a3c4149099591e2c4c3d", size = 6650758, upload-time = "2026-03-29T13:21:10.949Z" }, - { url = "https://files.pythonhosted.org/packages/6e/6e/795cc078b78a384052e73b2f6281ff7a700e9bf53bcce2ee579d4f6dd879/numpy-2.4.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b39d38a9bd2ae1becd7eac1303d031c5c110ad31f2b319c6e7d98b135c934d", size = 15729948, upload-time = "2026-03-29T13:21:14.047Z" }, - { url = "https://files.pythonhosted.org/packages/5f/86/2acbda8cc2af5f3d7bfc791192863b9e3e19674da7b5e533fded124d1299/numpy-2.4.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b268594bccac7d7cf5844c7732e3f20c50921d94e36d7ec9b79e9857694b1b2f", size = 16679325, upload-time = "2026-03-29T13:21:17.561Z" }, - { url = "https://files.pythonhosted.org/packages/bc/59/cafd83018f4aa55e0ac6fa92aa066c0a1877b77a615ceff1711c260ffae8/numpy-2.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac6b31e35612a26483e20750126d30d0941f949426974cace8e6b5c58a3657b0", size = 17084883, upload-time = "2026-03-29T13:21:21.106Z" }, - { url = "https://files.pythonhosted.org/packages/f0/85/a42548db84e65ece46ab2caea3d3f78b416a47af387fcbb47ec28e660dc2/numpy-2.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8e3ed142f2728df44263aaf5fb1f5b0b99f4070c553a0d7f033be65338329150", size = 18403474, upload-time = "2026-03-29T13:21:24.828Z" }, - { url = "https://files.pythonhosted.org/packages/ed/ad/483d9e262f4b831000062e5d8a45e342166ec8aaa1195264982bca267e62/numpy-2.4.4-cp314-cp314t-win32.whl", hash = "sha256:dddbbd259598d7240b18c9d87c56a9d2fb3b02fe266f49a7c101532e78c1d871", size = 6155500, upload-time = "2026-03-29T13:21:28.205Z" }, - { url = "https://files.pythonhosted.org/packages/c7/03/2fc4e14c7bd4ff2964b74ba90ecb8552540b6315f201df70f137faa5c589/numpy-2.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a7164afb23be6e37ad90b2f10426149fd75aee07ca55653d2aa41e66c4ef697e", size = 12637755, upload-time = "2026-03-29T13:21:31.107Z" }, - { url = "https://files.pythonhosted.org/packages/58/78/548fb8e07b1a341746bfbecb32f2c268470f45fa028aacdbd10d9bc73aab/numpy-2.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:ba203255017337d39f89bdd58417f03c4426f12beed0440cfd933cb15f8669c7", size = 10566643, upload-time = "2026-03-29T13:21:34.339Z" }, - { url = "https://files.pythonhosted.org/packages/6b/33/8fae8f964a4f63ed528264ddf25d2b683d0b663e3cba26961eb838a7c1bd/numpy-2.4.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:58c8b5929fcb8287cbd6f0a3fae19c6e03a5c48402ae792962ac465224a629a4", size = 16854491, upload-time = "2026-03-29T13:21:38.03Z" }, - { url = "https://files.pythonhosted.org/packages/bc/d0/1aabee441380b981cf8cdda3ae7a46aa827d1b5a8cce84d14598bc94d6d9/numpy-2.4.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:eea7ac5d2dce4189771cedb559c738a71512768210dc4e4753b107a2048b3d0e", size = 14895830, upload-time = "2026-03-29T13:21:41.509Z" }, - { url = "https://files.pythonhosted.org/packages/a5/b8/aafb0d1065416894fccf4df6b49ef22b8db045187949545bced89c034b8e/numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:51fc224f7ca4d92656d5a5eb315f12eb5fe2c97a66249aa7b5f562528a3be38c", size = 5400927, upload-time = "2026-03-29T13:21:44.747Z" }, - { url = "https://files.pythonhosted.org/packages/d6/77/063baa20b08b431038c7f9ff5435540c7b7265c78cf56012a483019ca72d/numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:28a650663f7314afc3e6ec620f44f333c386aad9f6fc472030865dc0ebb26ee3", size = 6715557, upload-time = "2026-03-29T13:21:47.406Z" }, - { url = "https://files.pythonhosted.org/packages/c7/a8/379542d45a14f149444c5c4c4e7714707239ce9cc1de8c2803958889da14/numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19710a9ca9992d7174e9c52f643d4272dcd1558c5f7af7f6f8190f633bd651a7", size = 15804253, upload-time = "2026-03-29T13:21:50.753Z" }, - { url = "https://files.pythonhosted.org/packages/a2/c8/f0a45426d6d21e7ea3310a15cf90c43a14d9232c31a837702dba437f3373/numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b2aec6af35c113b05695ebb5749a787acd63cafc83086a05771d1e1cd1e555f", size = 16753552, upload-time = "2026-03-29T13:21:54.344Z" }, - { url = "https://files.pythonhosted.org/packages/04/74/f4c001f4714c3ad9ce037e18cf2b9c64871a84951eaa0baf683a9ca9301c/numpy-2.4.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f2cf083b324a467e1ab358c105f6cad5ea950f50524668a80c486ff1db24e119", size = 12509075, upload-time = "2026-03-29T13:21:57.644Z" }, +version = "2.4.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/50/8e/b8041bc719f056afd864478029d52214789341ac6583437b0ee5031e9530/numpy-2.4.5.tar.gz", hash = "sha256:ca670567a5683b7c1670ec03e0ddd5862e10934e92a70751d68d7b7b74ca7f9f", size = 20735669, upload-time = "2026-05-15T20:25:19.492Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/44/1383ee4d1e916a9e610e46c876b5c83ea023526117d23cd911983929ec34/numpy-2.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3176dc8ff71dbb593606f91a69ad0c3cd3303c7eb546af477370ab9edf760288", size = 16969261, upload-time = "2026-05-15T20:22:23.036Z" }, + { url = "https://files.pythonhosted.org/packages/3d/61/54bacfbec7550bc398e6b6d9a861db35d64f75844e1d7920f5722c3cd5e7/numpy-2.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1811150e5148f5a01a7cc282cb2f489b4a3050a773e173adb480e507bad3a3d7", size = 14964009, upload-time = "2026-05-15T20:22:25.819Z" }, + { url = "https://files.pythonhosted.org/packages/7a/55/fe86c64561761f185339c26001164a2687bd4787af681e961431abd2d534/numpy-2.4.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0d63a780070871210853ba01e90b88f9b85cf2abf63a7f143d5127189265ddf6", size = 5469106, upload-time = "2026-05-15T20:22:28.13Z" }, + { url = "https://files.pythonhosted.org/packages/2f/74/cf29b8317627f0e3aa2c9fb332d386bd734308cecd9e07da9f407d9ce0c3/numpy-2.4.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:0c6919cefafb3b76cd46a89dbb203bf1dd95529d2a6d09fef2d325d95d6a79d8", size = 6798945, upload-time = "2026-05-15T20:22:30.061Z" }, + { url = "https://files.pythonhosted.org/packages/80/a9/b61730a17fa87d5abb13ce560a1b4ce3485d37a13e03eb7b414e598e72f8/numpy-2.4.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d51efede1e58e8b11877536a5518f60e318d8ff69b89ad7b38ee5e431b24d772", size = 15967025, upload-time = "2026-05-15T20:22:32.328Z" }, + { url = "https://files.pythonhosted.org/packages/03/39/70bcd187eb4d223c21fde02c2bdfbffbffef3288cbb3947c04c74ae39a08/numpy-2.4.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:07ce7e74da92d7c71b5df157b9758bcdd53d7fea10602154de3afd2b3ddc34dd", size = 16918685, upload-time = "2026-05-15T20:22:34.759Z" }, + { url = "https://files.pythonhosted.org/packages/ab/31/400fd1315bbe228af3937cf8a74e32023df6217af36077919d00adc382e4/numpy-2.4.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d7828234a13185effb34979e146f9921f2a65dfbbe215e6dbb57d6478fc8e059", size = 17322963, upload-time = "2026-05-15T20:22:37.557Z" }, + { url = "https://files.pythonhosted.org/packages/18/6a/bbbafb657e6f6ee826b4ecdb8722a2e0aae4a981888eaf59eae6a535cc13/numpy-2.4.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f96083adc3dfc1bbf778f2c79654d88115fa07074c97cb724fe9508f12d91c55", size = 18651594, upload-time = "2026-05-15T20:22:40.449Z" }, + { url = "https://files.pythonhosted.org/packages/de/0c/857a515154a2a18b0dfae04089600d166d352d473ec17a0680d879582d06/numpy-2.4.5-cp311-cp311-win32.whl", hash = "sha256:4ed78c904a638b6e5d7cd4db90c06fca5fc6ec2f28d258305368f454a50e79cf", size = 6233849, upload-time = "2026-05-15T20:22:43.139Z" }, + { url = "https://files.pythonhosted.org/packages/f0/66/d215f3fb93541617adb5d58b3b9508e8a6413e499711e0adc0b80bcb445d/numpy-2.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:079b0fad6f2899b23c5da89792b5409d2d83fc83e8bd5c2299cc9c397a264864", size = 12608238, upload-time = "2026-05-15T20:22:45.229Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c4/611d66d3fcfa931954d37a19ce5575f3283d023e89ff0df6ad43b334ae9c/numpy-2.4.5-cp311-cp311-win_arm64.whl", hash = "sha256:d6c78e260b53affe9b395a9d54fc61f101f9521c4d9452c7e9e3718b19e2215b", size = 10479452, upload-time = "2026-05-15T20:22:47.962Z" }, + { url = "https://files.pythonhosted.org/packages/6c/18/3275231e98620002681c922e792db04d72c356e9d8073c387344fc0e4ff1/numpy-2.4.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:654fb8674b61b1c4bd568f944d13a908566fdcb0d797303521d4149d16da05ef", size = 16689166, upload-time = "2026-05-15T20:22:50.761Z" }, + { url = "https://files.pythonhosted.org/packages/db/23/000aab6a16bdec53307f0f72546b57a3ac9266a62d8c257bee97d85fd078/numpy-2.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4cd9f6fa7ce10dc4627f2bb81dd9075dab67e94632e04c2b638e12575ddaa862", size = 14699514, upload-time = "2026-05-15T20:22:53.678Z" }, + { url = "https://files.pythonhosted.org/packages/47/cc/ddaf3af9c46966fef5be879256f213d85a0c56c75d07a3b7defec7cf6b4c/numpy-2.4.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:4f5bc96d35d94e4ceab8b38a92241b4611e95dc44e63b9f1fa2a331858ee3507", size = 5204601, upload-time = "2026-05-15T20:22:56.257Z" }, + { url = "https://files.pythonhosted.org/packages/07/ea/627fadd11959b3c7759008f34c92a35af8ff942dd8284a66ced648bbe516/numpy-2.4.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:4bb33e900ee81730ad77a258965134aa8ceac805124f7e5229347beda4b8d0aa", size = 6551360, upload-time = "2026-05-15T20:22:58.334Z" }, + { url = "https://files.pythonhosted.org/packages/a1/47/0728b986b8682d742ff68c16baa5af9d185484abfc635c5cc700f44e62be/numpy-2.4.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32f8f852273ef32b291201ac2a2c97629c4a1ee8632bb670e3443eaa09fc2e72", size = 15671157, upload-time = "2026-05-15T20:23:01.081Z" }, + { url = "https://files.pythonhosted.org/packages/d1/0b/b905ae82d9419dc38123523862db64978ca2954b69609c3ae8fdaca1084c/numpy-2.4.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:685681e956fc8dcb75adc6ff26694e1dfd738b24bd8d4696c51ca0110157f912", size = 16645703, upload-time = "2026-05-15T20:23:04.358Z" }, + { url = "https://files.pythonhosted.org/packages/5f/24/e27fc3f5236b4118ed9eed67111675f5c61a07ea333acec87c869c3b359d/numpy-2.4.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6f64dd84b277a737eb59513f6b9bb6195bf41ab11941ef15b2562dbab43fa8ef", size = 17021018, upload-time = "2026-05-15T20:23:07.021Z" }, + { url = "https://files.pythonhosted.org/packages/d3/a7/9041af38d527ab80a06a93570a77e29425b41507ad41f6acf5da78cfb4a4/numpy-2.4.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b42d9496f79e3a728192f05a42d86e36163217b7cdecb3813d0028a0aa6b72d7", size = 18368768, upload-time = "2026-05-15T20:23:09.44Z" }, + { url = "https://files.pythonhosted.org/packages/49/82/326a014442f32c2663434fd424d9298791f47f8a0f17585ad60519a5606e/numpy-2.4.5-cp312-cp312-win32.whl", hash = "sha256:86d980970f5110595ca14855768073b08585fc1acc36895de303e039e7dee4a5", size = 5962819, upload-time = "2026-05-15T20:23:11.631Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/cbf5d391b0b3a5e8cad264603e2fae256b0bde8ce43566b13b78faedc659/numpy-2.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:3333dba6a4e611d666f69e177ba8fe4140366ff681a5feb2374d3fd4fff3acb6", size = 12321621, upload-time = "2026-05-15T20:23:14.305Z" }, + { url = "https://files.pythonhosted.org/packages/3c/d0/0f18909d9bc37a5f3f969fc737d2bb5df9f2ff295f71b467e6f52a0d6c4e/numpy-2.4.5-cp312-cp312-win_arm64.whl", hash = "sha256:4593d197270b894efeb538dcbe227e4bcf1c77f88c4c6bf933ead812cfaa4453", size = 10221430, upload-time = "2026-05-15T20:23:16.887Z" }, + { url = "https://files.pythonhosted.org/packages/e3/a4/fb50657c7cab297bf34edcd60a074cb0647f61771430d6363575274160fe/numpy-2.4.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1ef248460b645c102026b82337cc4e88231909c66dd77b59ec6d6cac7e44f277", size = 16684760, upload-time = "2026-05-15T20:23:19.436Z" }, + { url = "https://files.pythonhosted.org/packages/3e/43/87e731299b9408eda705b3b9cb31c7bceb9347d2af9cbb16b2b1e4b5bc0f/numpy-2.4.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4603622bdcdbf8dccb1d9d5b21d16a7aa4e473ae6c8e14048d846fd4ca2907a0", size = 14694117, upload-time = "2026-05-15T20:23:21.832Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c7/0b2bb8acea222e9dd6e582afc2bc553b89b8833cbdccc68e68f050fb31f8/numpy-2.4.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6c18d49c67689c562854b53fdc433b93e47c12952aa6fa6d59f185e1a5992419", size = 5199141, upload-time = "2026-05-15T20:23:24.066Z" }, + { url = "https://files.pythonhosted.org/packages/39/60/b6972b5d47033d90000f0097c81a98b9486589a2d7003bf725bff275cb0d/numpy-2.4.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:b1c663ddc641f4192e90511bec61a09bc231e3bbdb996cdc6edbcaa0e528d685", size = 6546954, upload-time = "2026-05-15T20:23:26.099Z" }, + { url = "https://files.pythonhosted.org/packages/c1/e9/ed667cb12c11ca0adde431f685d3a5dd78e6f78b27228c581c8415198e9e/numpy-2.4.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93793222b524f692f12b2f8752ce8b1d9d9125b2bfd5dbf0fb69c92c5e1ce86c", size = 15669430, upload-time = "2026-05-15T20:23:28.147Z" }, + { url = "https://files.pythonhosted.org/packages/44/e5/679f6ffeb01294b0008e5ada4a113cb47617bc0e1819a529fd7973c6d7f4/numpy-2.4.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1616bde34b2bcba2fa9bde06217ce00da4f3d1bdfb264d54525a99e8fe170d83", size = 16633390, upload-time = "2026-05-15T20:23:31.622Z" }, + { url = "https://files.pythonhosted.org/packages/36/46/42bfffc9a780ec902ccd7470d3219192ee82b7b442710307dd85b4d121b0/numpy-2.4.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:09d7d97da1c2c62f4818b3e150a57572ff8dcf1cf5ac501aac832ffd4ebd9566", size = 17020709, upload-time = "2026-05-15T20:23:34.08Z" }, + { url = "https://files.pythonhosted.org/packages/44/00/3e840bfee0cc6cec22209f2c97057f26eeb30de031e4933b4dfc0395416c/numpy-2.4.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d68d0b355ab2e39fe0de59001d7151dfdbbb880ef67baeed806661e03df5097", size = 18357818, upload-time = "2026-05-15T20:23:36.965Z" }, + { url = "https://files.pythonhosted.org/packages/72/cb/3447b400b9da84134575486f0f656541559b00d4b262477bce9b678bbca8/numpy-2.4.5-cp313-cp313-win32.whl", hash = "sha256:fe28b64777ddfa0eca9b5f51474034ebe3dcb8324f48f27b28f479085673ae33", size = 5961114, upload-time = "2026-05-15T20:23:39.586Z" }, + { url = "https://files.pythonhosted.org/packages/28/f9/a90d2220ffcdc0798f5d55bb5d5463cd6254ec9ef43f384dae80217d7a2f/numpy-2.4.5-cp313-cp313-win_amd64.whl", hash = "sha256:fb4a6c9c537d6ccec9cc4aeae4261bd3cc79b070c67ddc0646f5b1c07fddde42", size = 12318553, upload-time = "2026-05-15T20:23:41.436Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c9/96f531fb3234545315152d34efdf3de7daee81254448447eb619e8d16967/numpy-2.4.5-cp313-cp313-win_arm64.whl", hash = "sha256:6d7df2da2e7ea0624a43aa368104b3a3ce14aae98ad4bb2c9a93fecef76f1c97", size = 10222200, upload-time = "2026-05-15T20:23:43.681Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f4/a291caab5a3c520babf93ff77c54fd5fdb1ebbc3296cee2eb2146ce773b1/numpy-2.4.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:2a235607a18df941760a695927051af4b1cd5d3ee85840d0e2af816785771feb", size = 14821438, upload-time = "2026-05-15T20:23:45.911Z" }, + { url = "https://files.pythonhosted.org/packages/85/26/13dbb1159b864370568e7309063fd72667984df89db74e9caeb175d067c7/numpy-2.4.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:58dcf64969d870f36bc7fbd557d2617e997db7dc06261b6e3327148ea460d0a4", size = 5326663, upload-time = "2026-05-15T20:23:48.18Z" }, + { url = "https://files.pythonhosted.org/packages/7c/99/d233408072a0e019e2288e27edd23f7d572ccd4a73d1539baa3270ede85d/numpy-2.4.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:235f54b0156274d8fa3155db3ed6d2f401c7e8f3367c90db0a12f02a58fde6ed", size = 6646874, upload-time = "2026-05-15T20:23:49.856Z" }, + { url = "https://files.pythonhosted.org/packages/c5/00/eeb6f193dfe767725e952e0464f3e51f44145c5dd261cd7389aa36ac0713/numpy-2.4.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef3b5bb65437a3555c648e706475db01c645559ca80dc8b03e4f202ea757e0d6", size = 15728147, upload-time = "2026-05-15T20:23:51.655Z" }, + { url = "https://files.pythonhosted.org/packages/e5/c9/b8ed039f1fde1b13a8807c893e7e2f9432a379f4d6401edecf0028da5b2c/numpy-2.4.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7f09a7e5f017d7098c66522097c96257411c9620c0926212200d66bc8cee3976", size = 16681770, upload-time = "2026-05-15T20:23:53.933Z" }, + { url = "https://files.pythonhosted.org/packages/11/5b/0198ef6cb7016eca6d895d392106012138127fab23f46637e76d5e25c9f5/numpy-2.4.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:993a88d8fdd8554466a8765cd8bacd97ba56b70ca6b0a04bcdca77f5afed4222", size = 17086218, upload-time = "2026-05-15T20:23:56.646Z" }, + { url = "https://files.pythonhosted.org/packages/f0/fe/8821f3cfc660ae84c92ee158505941874b62c56a42e035a41425228cd8cf/numpy-2.4.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:84f58bed609b5669f5ad3d597901a4f1f86ee5b3c3708aaa55f05b4fe6e0f656", size = 18403542, upload-time = "2026-05-15T20:23:59.173Z" }, + { url = "https://files.pythonhosted.org/packages/0e/00/e64ecaf498865e7b091f57658b2c522503e5d1b70e43b807f5f8247e1d88/numpy-2.4.5-cp313-cp313t-win32.whl", hash = "sha256:7200c58f3f933ca61e66346667dcc8510bb111995e9ce15398a731e6a4afa4bb", size = 6084903, upload-time = "2026-05-15T20:24:01.506Z" }, + { url = "https://files.pythonhosted.org/packages/20/c0/354997dedaf74e8311c2cf9a6027b476fd8d424cb92189cc0ae2b25f501c/numpy-2.4.5-cp313-cp313t-win_amd64.whl", hash = "sha256:c26c71080d35db5002102f5d9ff614d45de02aa1f7802943e691e063e5ee93bc", size = 12458420, upload-time = "2026-05-15T20:24:03.735Z" }, + { url = "https://files.pythonhosted.org/packages/66/dc/917ee5ea4a31ca1a6e4c9a85386477efa318dcc60db257c5ef4adda096c1/numpy-2.4.5-cp313-cp313t-win_arm64.whl", hash = "sha256:2caa576d1707b275cba1aeb60a5c50daa6fa2a3f28ecb08123bc05fd439005db", size = 10291826, upload-time = "2026-05-15T20:24:06.535Z" }, + { url = "https://files.pythonhosted.org/packages/ca/c1/3be0bf102fc17cff5bd142e3be0bfffabec6fa46da0a462396c76b0765d0/numpy-2.4.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:889ca2c072315de638a5194a772aa1fa2df92bdd6175f6a222d4784040424b61", size = 16683455, upload-time = "2026-05-15T20:24:08.988Z" }, + { url = "https://files.pythonhosted.org/packages/e8/3e/0742d724901fa36bc54b338c6e62e463a7601180da896aa44978f0adf004/numpy-2.4.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:89e89304fb1f8c3f0ecfa4a7d48f311dd79771336a940e920159d643d1307e77", size = 14704577, upload-time = "2026-05-15T20:24:11.542Z" }, + { url = "https://files.pythonhosted.org/packages/25/1c/196c610ff4c6782d697ba780ebdc1616be143213701bf22c1a270f3bf7dd/numpy-2.4.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:144fcc5a3a17679b2b82543b4a2d8dd29937230a7af13232b5f753872feb6361", size = 5209756, upload-time = "2026-05-15T20:24:14.091Z" }, + { url = "https://files.pythonhosted.org/packages/52/c0/23fb1bc506f774e03db66219a2830e720f4d3dbcaaddf855a7ff7bb6d96f/numpy-2.4.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:398bb16772b265b9fa5c07b07072646ea97137c10ffb62a9a087b277fc825c29", size = 6543937, upload-time = "2026-05-15T20:24:16.223Z" }, + { url = "https://files.pythonhosted.org/packages/9f/49/db4662c26e68520afcc84d672a6f9f5294063dee0e57a46d61afdaa7f9ed/numpy-2.4.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb352e7b8876da1249e72254736d6c58c505fa4e58a3d7e30efca241ca9ca9ce", size = 15685292, upload-time = "2026-05-15T20:24:17.978Z" }, + { url = "https://files.pythonhosted.org/packages/43/80/1315439acedd8398319bac177d6de3d48ab39c62cc0c810f74f0a9a73996/numpy-2.4.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7341b08ff8124d7353939778e2707b8732d03c78c1c30e0815aba2dacbe1245a", size = 16638528, upload-time = "2026-05-15T20:24:20.478Z" }, + { url = "https://files.pythonhosted.org/packages/56/81/364388600932618fe735d97fdd2437cb8dd87a23377ac11d8b9d5db098b7/numpy-2.4.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:deb01226f012539f3945261ffe1c10aec081a0fa0a5c925419933c70f3ae2d23", size = 17036709, upload-time = "2026-05-15T20:24:22.949Z" }, + { url = "https://files.pythonhosted.org/packages/32/4a/a1185b18a94a6d9587e54b437e7d0ba36ecf6e614f1bea03f5249912c64e/numpy-2.4.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d888bdf7335f76878c3c7b264ac1ff089863e211ec81249f9fb5795c2183dc25", size = 18363254, upload-time = "2026-05-15T20:24:25.402Z" }, + { url = "https://files.pythonhosted.org/packages/b9/8e/95c1d2ed15ae97750ede8c8a0ac487c9c01207afff430f47078b1d9d7dc5/numpy-2.4.5-cp314-cp314-win32.whl", hash = "sha256:15f90d1256e9b2320aff24fde44815b787ab6d7c49a1a11bfd8138b321c5f080", size = 6010184, upload-time = "2026-05-15T20:24:27.852Z" }, + { url = "https://files.pythonhosted.org/packages/aa/92/d063df4d63d988b20d881856c74df76c0c1786229bb870f3a52af0981d4d/numpy-2.4.5-cp314-cp314-win_amd64.whl", hash = "sha256:4bd2cd4ef9c0afa87de73723c0a33c0edff62143e1432917458e26d3d195d87f", size = 12450344, upload-time = "2026-05-15T20:24:29.856Z" }, + { url = "https://files.pythonhosted.org/packages/3d/64/c0ae481f7c3b2f85869bcd8fc5d30aa7c96b394162eef9c9315957f115c5/numpy-2.4.5-cp314-cp314-win_arm64.whl", hash = "sha256:db304568c650e9d7039744d3575d0d287754debb2057d7c7b8cdfdc2c487a957", size = 10495674, upload-time = "2026-05-15T20:24:32.352Z" }, + { url = "https://files.pythonhosted.org/packages/57/89/c5a4c677acf17aa50ba09a15e61812f90baac42bb6ca38d112e005858351/numpy-2.4.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6de2883e0d2c63eae1bab1a84b390dca74aabb3d20ea1f5d58f360853c83abf3", size = 14824078, upload-time = "2026-05-15T20:24:34.669Z" }, + { url = "https://files.pythonhosted.org/packages/e7/52/57e7144284f6b51ba93523e495ff239260b1ecd5257e3700a436332e5688/numpy-2.4.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:06760fe73ae5005008748d182de612c733542af3cde063d532cd2127561b27be", size = 5329246, upload-time = "2026-05-15T20:24:36.957Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b3/09dbce80fd4a7db4318f2fc01eec0ae76f29306442b5a32d4b811d082cdf/numpy-2.4.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:4b51a01745cb04cc19278482207444b4d30728ce91c28d27a3bfae5fc6ff24c7", size = 6649877, upload-time = "2026-05-15T20:24:38.861Z" }, + { url = "https://files.pythonhosted.org/packages/30/c2/dbdb23e82d540b757690ef13f011c386fca6a63848eec6136baf8ce7cbed/numpy-2.4.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a05636d7937d0936f271e5ba957fa8d746b5be3c2025caa1a2508f4fe521d40", size = 15730534, upload-time = "2026-05-15T20:24:41.168Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bd/68f6e9b3c20decf40ac06708a7b506757e3a8588efed32988d1b747316be/numpy-2.4.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b86f56048ed09c3bbe48962a7dff077c2fd3274f8cf981800f3b38eac49cc3", size = 16679741, upload-time = "2026-05-15T20:24:44.874Z" }, + { url = "https://files.pythonhosted.org/packages/39/1d/0fcac0b6b4ea1b50ca8fca05a34bed5c8d56e34c1cb5ffb04cf76109ac3c/numpy-2.4.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:130d58151c4db23e9fa860b84784e219a3aa3e030acc88a493ea37006c4dfd4c", size = 17085598, upload-time = "2026-05-15T20:24:47.603Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e8/a472b2564cf6cc498ad7aa9741d9832648221b8ab8cc0dbef41faa248ede/numpy-2.4.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d475afc8cbe935ff5944f753d863bba774d7f4e1feaaa4102901e3e053ca5963", size = 18403855, upload-time = "2026-05-15T20:24:50.474Z" }, + { url = "https://files.pythonhosted.org/packages/b9/a4/da82196f8cc4bd28ecf17bd57008c84f3d4696caf06753d9bad45e4ad749/numpy-2.4.5-cp314-cp314t-win32.whl", hash = "sha256:27f4a6dc26353a860b348961b9aa9e009835688b435cfa105e873b8dc2c726f5", size = 6156900, upload-time = "2026-05-15T20:24:53.134Z" }, + { url = "https://files.pythonhosted.org/packages/98/31/860959b91a73d9a085006554fa3850da51a7ffab64599bac5097243438ab/numpy-2.4.5-cp314-cp314t-win_amd64.whl", hash = "sha256:76ac6e90f5e226011c88f9b7040a4bcae612518bc7e9adc127e697a13b28ad1a", size = 12638906, upload-time = "2026-05-15T20:24:55.009Z" }, + { url = "https://files.pythonhosted.org/packages/9e/2a/bbd3097913083ad07c0f28fc9629666221fc18923e17ce97ae22a5dccdd6/numpy-2.4.5-cp314-cp314t-win_arm64.whl", hash = "sha256:7c392e2c1bf596701d3c6832be7567eab5d5b0a13865036c33365ee097d37f8b", size = 10565875, upload-time = "2026-05-15T20:24:57.425Z" }, + { url = "https://files.pythonhosted.org/packages/fc/5d/9a644cfb841bc76b584afc3af1708b3bf6c5cb51fc84a7008246cd93b7b7/numpy-2.4.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6bf0bfc1c2e1db972e30b6cd3d4861f477f3af908b27799b239dc3cbe3eb4b95", size = 16847544, upload-time = "2026-05-15T20:24:59.746Z" }, + { url = "https://files.pythonhosted.org/packages/56/8f/4fe5e3ba76d858dae1fe79078818c0520447335be0082c0dedf82719cc08/numpy-2.4.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:73d664413fb97229149c4711ef56531a6fe8c15c1c2626b0bbe497b84c287e70", size = 14889039, upload-time = "2026-05-15T20:25:03.179Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6f/79f195abf922ecc43e7d0eb6cc969462a71b524a35bcd1fa26b4a1d7406a/numpy-2.4.5-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:b35bee5ef99e8d227a07829bee2e864fcb65f7c157646fcd8ec8b4b45dd8b88f", size = 5394106, upload-time = "2026-05-15T20:25:05.659Z" }, + { url = "https://files.pythonhosted.org/packages/58/6f/79cd6247205802bcbd10b40ea087e20ded526e10e9be224d34de832b216e/numpy-2.4.5-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:02981d0fc9f9ce147643d552966d47f329a02f7ecb3b113e84207242f20dfa83", size = 6708718, upload-time = "2026-05-15T20:25:08.071Z" }, + { url = "https://files.pythonhosted.org/packages/d7/22/5f378a9d4633c98f28c4709d4144b1a4630c5c09e109d2e781e2d26c8fe1/numpy-2.4.5-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0e63caf31a1df06338ae63d999f7a33a675ced62eea9c9b02db4b1c1f45cff38", size = 15798292, upload-time = "2026-05-15T20:25:10.689Z" }, + { url = "https://files.pythonhosted.org/packages/63/1c/cec582febef798c99888892d92dc1d28dfe29cb427c41f44d13d0dec208f/numpy-2.4.5-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d8fc52b85a7b45e474be53eddf08e006d22e381a4e41bcde8e4aa08da0e7d198", size = 16747406, upload-time = "2026-05-15T20:25:13.879Z" }, + { url = "https://files.pythonhosted.org/packages/b1/dc/d358a16a6fec86cf736b8fbe67386044b3fa2aded1a80cff90e836799301/numpy-2.4.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:40c71d50a4da1a7c317af419461052d3911a5770bfc5fd55baf52cc45e7a2c20", size = 12504085, upload-time = "2026-05-15T20:25:16.667Z" }, ] [[package]] name = "nvidia-cublas" -version = "13.1.0.3" +version = "13.1.1.3" source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cuda-nvrtc" }, +] wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/a5/fce49e2ae977e0ccc084e5adafceb4f0ac0c8333cb6863501618a7277f67/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:c86fc7f7ae36d7528288c5d88098edcb7b02c633d262e7ddbb86b0ad91be5df2", size = 542851226, upload-time = "2025-10-09T08:59:04.818Z" }, - { url = "https://files.pythonhosted.org/packages/e7/44/423ac00af4dd95a5aeb27207e2c0d9b7118702149bf4704c3ddb55bb7429/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:ee8722c1f0145ab246bccb9e452153b5e0515fd094c3678df50b2a0888b8b171", size = 423133236, upload-time = "2025-10-09T08:59:32.536Z" }, + { url = "https://files.pythonhosted.org/packages/a7/a1/0bd24ee8c8d03adac032fd2909426a00c88f8c57961b1277ded97f91119f/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b7a210458267ac818974c53038fbec2e969d5c99f305ab15c72522fa9f001dd5", size = 542848918, upload-time = "2026-04-08T18:46:22.985Z" }, + { url = "https://files.pythonhosted.org/packages/3b/cd/154ca20c38269e05eff77c1464e6c1da89f50a6390b565e9d82e06bc11e1/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:37936a16db8fe4ac1f065c2139360608a543a09275cb1a1af612e08cfa065436", size = 423138758, upload-time = "2026-04-08T18:46:58.655Z" }, ] [[package]] @@ -2126,14 +2126,14 @@ wheels = [ [[package]] name = "nvidia-cudnn-cu13" -version = "9.19.0.56" +version = "9.20.0.48" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nvidia-cublas" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/84/26025437c1e6b61a707442184fa0c03d083b661adf3a3eecfd6d21677740/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:6ed29ffaee1176c612daf442e4dd6cfeb6a0caa43ddcbeb59da94953030b1be4", size = 433781201, upload-time = "2026-02-03T20:40:53.805Z" }, - { url = "https://files.pythonhosted.org/packages/a3/22/0b4b932655d17a6da1b92fa92ab12844b053bb2ac2475e179ba6f043da1e/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:d20e1734305e9d68889a96e3f35094d733ff1f83932ebe462753973e53a572bf", size = 366066321, upload-time = "2026-02-03T20:44:52.837Z" }, + { url = "https://files.pythonhosted.org/packages/56/c5/83384d846b2fd17c44bd499b36c75a45ed4f095fbbb2252294e89cea5c5c/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:e31454ae00094b0c55319d9d15b6fa2fc50a9e1c0f5c8c80fb75258234e731e1", size = 444574296, upload-time = "2026-03-09T19:28:27.751Z" }, + { url = "https://files.pythonhosted.org/packages/6e/5e/edb9c0ae051602c3ccaffe424256463636d639e27d7f302dde9975ef9e7a/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0c45dd8eeb50b603f07995b1b300c62ffe6a1980482b82b3bcf94a4ca9d49304", size = 366173588, upload-time = "2026-03-09T19:29:34.474Z" }, ] [[package]] @@ -2194,20 +2194,20 @@ wheels = [ [[package]] name = "nvidia-cusparselt-cu13" -version = "0.8.0" +version = "0.8.1" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/10/8dcd1175260706a2fc92a16a52e306b71d4c1ea0b0cc4a9484183399818a/nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:400c6ed1cf6780fc6efedd64ec9f1345871767e6a1a0a552a1ea0578117ea77c", size = 220791277, upload-time = "2025-08-13T19:22:40.982Z" }, - { url = "https://files.pythonhosted.org/packages/fd/53/43b0d71f4e702fa9733f8b4571fdca50a8813f1e450b656c239beff12315/nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:25e30a8a7323935d4ad0340b95a0b69926eee755767e8e0b1cf8dd85b197d3fd", size = 169884119, upload-time = "2025-08-13T19:23:41.967Z" }, + { url = "https://files.pythonhosted.org/packages/46/e1/cdc1797eadf82d3a9a575a19b33fdc871a97edbec42c00b5b5e914f4aff4/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4dca476c50bf4780d46cd0bfbd82e2bc10a08e4fef7950917ce8d7578d22a23f", size = 221051344, upload-time = "2025-09-05T18:49:51.289Z" }, + { url = "https://files.pythonhosted.org/packages/34/7d/2661f2fb3ac4302f3a246f5fc030213ac60c1fe0bce84f9783dbd831dbb7/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:786ce87568c303fadb5afcc7102d454cd3040d75f6f8626f5db460d1871f4dd0", size = 170148586, upload-time = "2025-09-05T18:50:50.248Z" }, ] [[package]] name = "nvidia-nccl-cu13" -version = "2.28.9" +version = "2.29.7" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/55/1920646a2e43ffd4fc958536b276197ed740e9e0c54105b4bb3521591fc7/nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:01c873ba1626b54caa12272ed228dc5b2781545e0ae8ba3f432a8ef1c6d78643", size = 196561677, upload-time = "2025-11-18T05:49:03.45Z" }, - { url = "https://files.pythonhosted.org/packages/b0/b4/878fefaad5b2bcc6fcf8d474a25e3e3774bc5133e4b58adff4d0bca238bc/nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:e4553a30f34195f3fa1da02a6da3d6337d28f2003943aa0a3d247bbc25fefc42", size = 196493177, upload-time = "2025-11-18T05:49:17.677Z" }, + { url = "https://files.pythonhosted.org/packages/72/0d/daf50d44177ee0cbc7ff0a0c91eb5ff676c82be42f9a970bc7597f440c3a/nvidia_nccl_cu13-2.29.7-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:674a12383e3c38a1bcccae7d4f3633b37852230b6047883cb2f4c2d1b36d9bf5", size = 206014712, upload-time = "2026-03-03T05:34:20.843Z" }, + { url = "https://files.pythonhosted.org/packages/67/f4/58e4e91b6919367c7aafb8e36fce9aad1a3047e536bf7e2fd560927d3a4c/nvidia_nccl_cu13-2.29.7-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:edd81538446786ec3b73972543e53bb43bcaf0bfc8ef76cb679fcc390ffe136d", size = 205976000, upload-time = "2026-03-03T05:36:24.472Z" }, ] [[package]] @@ -2654,15 +2654,15 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.3.0" +version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/e0/cc5a8653e9a24f6cf84768f05064aa8ed5a83dcefd5e2a043db14a1c5f44/python_discovery-1.3.0.tar.gz", hash = "sha256:d098f1e86be5d45fe4d14bf1029294aabbd332f4321179dec85e76cddce834b0", size = 63925, upload-time = "2026-05-05T14:38:39.769Z" } +sdist = { url = "https://files.pythonhosted.org/packages/48/60/e88788207d81e46362cfbef0d4aaf4c0f49efc3c12d4c3fa3f542c34ebec/python_discovery-1.3.1.tar.gz", hash = "sha256:62f6db28064c9613e7ca76cb3f00c38c839a07c31c00dfe7ed0986493d2150a6", size = 68011, upload-time = "2026-05-12T20:53:36.336Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl", hash = "sha256:441d9ced3dfce36e113beb35ca302c71c7ef06f3c0f9c227a0b9bb3bd49b9e9f", size = 33124, upload-time = "2026-05-05T14:38:38.539Z" }, + { url = "https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl", hash = "sha256:ed188687ebb3b82c01a17cd5ac62fc94d9f6487a7f1a0f9dfe89753fec91039c", size = 33185, upload-time = "2026-05-12T20:53:34.969Z" }, ] [[package]] @@ -2931,7 +2931,7 @@ wheels = [ [[package]] name = "requests" -version = "2.33.1" +version = "2.34.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -2939,9 +2939,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" }, + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, ] [[package]] @@ -3140,27 +3140,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.12" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/99/43/3291f1cc9106f4c63bdce7a8d0df5047fe8422a75b091c16b5e9355e0b11/ruff-0.15.12.tar.gz", hash = "sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6", size = 4643852, upload-time = "2026-04-24T18:17:14.305Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/6e/e78ffb61d4686f3d96ba3df2c801161843746dcbcbb17a1e927d4829312b/ruff-0.15.12-py3-none-linux_armv6l.whl", hash = "sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c", size = 10640713, upload-time = "2026-04-24T18:17:22.841Z" }, - { url = "https://files.pythonhosted.org/packages/ae/08/a317bc231fb9e7b93e4ef3089501e51922ff88d6936ce5cf870c4fe55419/ruff-0.15.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3bcd123364c3770b8e1b7baaf343cc99a35f197c5c6e8af79015c666c423a6c", size = 11069267, upload-time = "2026-04-24T18:17:30.105Z" }, - { url = "https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5", size = 10397182, upload-time = "2026-04-24T18:17:07.177Z" }, - { url = "https://files.pythonhosted.org/packages/71/e0/3310fc6d1b5e1fdea22bf3b1b807c7e187b581021b0d7d4514cccdb5fb71/ruff-0.15.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84a1630093121375a3e2a95b4a6dc7b59e2b4ee76216e32d81aae550a832d002", size = 10758012, upload-time = "2026-04-24T18:16:55.759Z" }, - { url = "https://files.pythonhosted.org/packages/11/c1/a606911aee04c324ddaa883ae418f3569792fd3c4a10c50e0dd0a2311e1e/ruff-0.15.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb129f40f114f089ebe0ca56c0d251cf2061b17651d464bb6478dc01e69f11f5", size = 10447479, upload-time = "2026-04-24T18:16:51.677Z" }, - { url = "https://files.pythonhosted.org/packages/9d/68/4201e8444f0894f21ab4aeeaee68aa4f10b51613514a20d80bd628d57e88/ruff-0.15.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0c862b172d695db7598426b8af465e7e9ac00a3ea2a3630ee67eb82e366aaa6", size = 11234040, upload-time = "2026-04-24T18:17:16.529Z" }, - { url = "https://files.pythonhosted.org/packages/34/ff/8a6d6cf4ccc23fd67060874e832c18919d1557a0611ebef03fdb01fff11e/ruff-0.15.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2849ea9f3484c3aca43a82f484210370319e7170df4dfe4843395ddf6c57bc33", size = 12087377, upload-time = "2026-04-24T18:17:04.944Z" }, - { url = "https://files.pythonhosted.org/packages/85/f6/c669cf73f5152f623d34e69866a46d5e6185816b19fcd5b6dd8a2d299922/ruff-0.15.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e77c7e51c07fe396826d5969a5b846d9cd4c402535835fb6e21ce8b28fef847", size = 11367784, upload-time = "2026-04-24T18:17:25.409Z" }, - { url = "https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0", size = 11344088, upload-time = "2026-04-24T18:17:12.258Z" }, - { url = "https://files.pythonhosted.org/packages/c2/8d/49afab3645e31e12c590acb6d3b5b69d7aab5b81926dbaf7461f9441f37a/ruff-0.15.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9ba3b8f1afd7e2e43d8943e55f249e13f9682fde09711644a6e7290eb4f3e339", size = 11271770, upload-time = "2026-04-24T18:17:02.457Z" }, - { url = "https://files.pythonhosted.org/packages/46/06/33f41fe94403e2b755481cdfb9b7ef3e4e0ed031c4581124658d935d52b4/ruff-0.15.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e852ba9fdc890655e1d78f2df1499efbe0e54126bd405362154a75e2bde159c5", size = 10719355, upload-time = "2026-04-24T18:17:27.648Z" }, - { url = "https://files.pythonhosted.org/packages/0d/59/18aa4e014debbf559670e4048e39260a85c7fcee84acfd761ac01e7b8d35/ruff-0.15.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dd8aed930da53780d22fc70bdf84452c843cf64f8cb4eb38984319c24c5cd5fd", size = 10462758, upload-time = "2026-04-24T18:17:32.347Z" }, - { url = "https://files.pythonhosted.org/packages/25/e7/cc9f16fd0f3b5fddcbd7ec3d6ae30c8f3fde1047f32a4093a98d633c6570/ruff-0.15.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01da3988d225628b709493d7dc67c3b9b12c0210016b08690ef9bd27970b262b", size = 10953498, upload-time = "2026-04-24T18:17:20.674Z" }, - { url = "https://files.pythonhosted.org/packages/72/7a/a9ba7f98c7a575978698f4230c5e8cc54bbc761af34f560818f933dafa0c/ruff-0.15.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9cae0f92bd5700d1213188b31cd3bdd2b315361296d10b96b8e2337d3d11f53e", size = 11447765, upload-time = "2026-04-24T18:17:09.755Z" }, - { url = "https://files.pythonhosted.org/packages/ea/f9/0ae446942c846b8266059ad8a30702a35afae55f5cdc54c5adf8d7afdc27/ruff-0.15.12-py3-none-win32.whl", hash = "sha256:d0185894e038d7043ba8fd6aee7499ece6462dc0ea9f1e260c7451807c714c20", size = 10657277, upload-time = "2026-04-24T18:17:18.591Z" }, - { url = "https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl", hash = "sha256:c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d", size = 11837758, upload-time = "2026-04-24T18:17:00.113Z" }, - { url = "https://files.pythonhosted.org/packages/c0/98/6beb4b351e472e5f4c4613f7c35a5290b8be2497e183825310c4c3a3984b/ruff-0.15.12-py3-none-win_arm64.whl", hash = "sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f", size = 11120821, upload-time = "2026-04-24T18:16:57.979Z" }, +version = "0.15.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/21/a7d5c126d5b557715ef81098f3db2fe20f622a039ff2e626af28d674ab80/ruff-0.15.13.tar.gz", hash = "sha256:f9d89f17f7ba7fb2ed42921f0df75da797a9a5d71bc39049e2c687cf2baf44b7", size = 4678180, upload-time = "2026-05-14T13:44:37.869Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/61/11d458dc6ac22504fd8e237b29dfd40504c7fbbcc8930402cfe51a8e63ed/ruff-0.15.13-py3-none-linux_armv6l.whl", hash = "sha256:444b580fc72fd6887e650acd3e575e18cdc79dbcf42fb4030b491057921f61f8", size = 10738279, upload-time = "2026-05-14T13:44:18.7Z" }, + { url = "https://files.pythonhosted.org/packages/86/ca/caa871ee7be718c45256fada4e16a218ee3e33f0c4a46b729a60a24912e6/ruff-0.15.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6590d009e7cb7ebf36f83dbdd44a3fa48a0994ff6f1cdc1b08006abe58f98dc7", size = 11124798, upload-time = "2026-05-14T13:44:06.427Z" }, + { url = "https://files.pythonhosted.org/packages/d3/19/43f5f2e568dddde567fc41f8471f9432c09563e19d3e617a48cfa52f8f0a/ruff-0.15.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1c26d2f66163deeb6e08d8b39fbbe983ce3c71cea06a6d7591cfd1421793c629", size = 10460761, upload-time = "2026-05-14T13:44:04.375Z" }, + { url = "https://files.pythonhosted.org/packages/99/df/cf938cd6de3003178f03ad7c1ea2a6c099468c03a35037985070b37e76be/ruff-0.15.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbd6f94b434f896308e4d57fb7bfde0d02b99f7a64b3bdab0fdfa6a864203a5", size = 10804451, upload-time = "2026-05-14T13:44:25.221Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7d/5d0973129b154ded2225729169d7068f26b467760b146493fde138415f23/ruff-0.15.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf3259f3be4d181bda591da5db2571aed6853c6a048157756448020bc6c5cd22", size = 10534285, upload-time = "2026-05-14T13:44:08.888Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e3/6b999bbc66cd51e5f073842bc2a3995e99c5e0e72e16b15e7261f7abf57a/ruff-0.15.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae9c17e5eb4430c154e76abc25d79a318190f5a997f38fb6b114416c5319ffc9", size = 11312063, upload-time = "2026-05-14T13:44:11.274Z" }, + { url = "https://files.pythonhosted.org/packages/af/5a/642639e9f5db04f1e97fbd6e091c6fd20725bdf072fb114d00eefb9e6eb8/ruff-0.15.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e2e39bff6c341f4b577a21b801326fab0b11847f48fcaa83f00a113c9b3cb55", size = 12183079, upload-time = "2026-05-14T13:44:01.634Z" }, + { url = "https://files.pythonhosted.org/packages/19/4c/7585735f6b53b0f12de13618b2f7d250a844f018822efc899df2e7b8295f/ruff-0.15.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e8d9a8e08013542e94d3220bc5b62cc3e5ef87c5f74bff367d3fac14fab013e6", size = 11440833, upload-time = "2026-05-14T13:43:59.043Z" }, + { url = "https://files.pythonhosted.org/packages/e8/31/bf1a0803d077e679cfeee5f2f67290a0fa79c7385b5d9a8c17b9db2c48f0/ruff-0.15.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc411dfebe5eebe55ce041c6ae080eb7668955e866daa2fbb16692a784f1c4ca", size = 11434486, upload-time = "2026-05-14T13:44:27.761Z" }, + { url = "https://files.pythonhosted.org/packages/e1/4e/62c9b999875d4f14db80f277c030578f5e249c9852d65b7ac7ad0b43c041/ruff-0.15.13-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:768494eb08b9cee54e2fd27969966f74db5a57f6eaa7a90fcb3306af34dfc4bd", size = 11385189, upload-time = "2026-05-14T13:44:13.704Z" }, + { url = "https://files.pythonhosted.org/packages/fc/89/7e959047a104df3eb12863447c110140191fc5b6c4f379ea2e803fcdb0e4/ruff-0.15.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:fb75f9a3a7e42ffe117d734494e6c5e5cb3565d66e12612cb63d0e572a41a5b6", size = 10781380, upload-time = "2026-05-14T13:43:56.734Z" }, + { url = "https://files.pythonhosted.org/packages/ff/52/5fd18f3b88cab63e88aa11516b3b4e1e5f720e5c330f8dbe5c26210f41f8/ruff-0.15.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8cb74dd33bb2f6613faf7fc03b660053b5ac4f80e706d5788c6335e2a8048d51", size = 10540605, upload-time = "2026-05-14T13:44:20.748Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e0/9e35f338990d3e41a82875ff7053ffe97541dae81c9d02143177f381d572/ruff-0.15.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7ef823f817fcd191dc934e984be9cf4094f808effa16f2542ad8e821ba02bbf2", size = 11036554, upload-time = "2026-05-14T13:44:16.256Z" }, + { url = "https://files.pythonhosted.org/packages/c2/13/070fb048c24080fba188f66371e2a92785be257ad02242066dc7255ac6e9/ruff-0.15.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f345a13937bd7f09f6f5d19fa0721b0c103e00e7f62bc67089a8e5e037719e0b", size = 11528133, upload-time = "2026-05-14T13:44:22.808Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8c/b1e1666aef7fc6555094d73ae6cd981701781ae85b97ceefc0eebd0b4668/ruff-0.15.13-py3-none-win32.whl", hash = "sha256:4044f94208b3b05ba0fc4a4abd0558cf4d6459bd18325eead7fd8cc66f909b41", size = 10721455, upload-time = "2026-05-14T13:44:35.697Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a6/870a3e8a50590bb92be184ad928c2922f088b00d9dc5c5ec7b924ee08c22/ruff-0.15.13-py3-none-win_amd64.whl", hash = "sha256:7064884d442b7d477b4e7473d12da7f08851d2b1982763c5d3f388a19468a1a4", size = 11900409, upload-time = "2026-05-14T13:44:30.389Z" }, + { url = "https://files.pythonhosted.org/packages/9b/36/9c015cd052fca743dae8cb2aeb16b551444787467db42ceab0fc968865af/ruff-0.15.13-py3-none-win_arm64.whl", hash = "sha256:2471da9bd1068c8c064b5fd9c0c4b6dddffd6369cb1cd68b29993b1709ff1b21", size = 11179336, upload-time = "2026-05-14T13:44:33.026Z" }, ] [[package]] @@ -3176,7 +3176,7 @@ dependencies = [ { name = "pillow" }, { name = "scipy" }, { name = "tifffile", version = "2026.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "tifffile", version = "2026.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "tifffile", version = "2026.5.15", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a1/b4/2528bb43c67d48053a7a649a9666432dc307d66ba02e3a6d5c40f46655df/scikit_image-0.26.0.tar.gz", hash = "sha256:f5f970ab04efad85c24714321fcc91613fcb64ef2a892a13167df2f3e59199fa", size = 22729739, upload-time = "2025-12-20T17:12:21.824Z" } wheels = [ @@ -3477,7 +3477,7 @@ wheels = [ [[package]] name = "tifffile" -version = "2026.5.2" +version = "2026.5.15" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", @@ -3486,9 +3486,9 @@ resolution-markers = [ dependencies = [ { name = "numpy", marker = "python_full_version >= '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6c/3e/695c7ab56be57814e369c1f38bc3f64b9dea0a83e867d00c0c9d613a9929/tifffile-2026.5.2.tar.gz", hash = "sha256:21b10227ede8493814a34676774797f721f487e36cb0530e7c3bd882caa87f5a", size = 429140, upload-time = "2026-05-02T20:19:31.497Z" } +sdist = { url = "https://files.pythonhosted.org/packages/40/66/0aef917d525767a40edebe088f8ed6a4417e6eb489c58f6805bfa872636b/tifffile-2026.5.15.tar.gz", hash = "sha256:ee4f3e07ee0d8ff4745a8c735ac2b72caa3173c7d6059b00fdc3ff492a0b635b", size = 429998, upload-time = "2026-05-15T20:04:55.896Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/af/ce4df3ca29122d219c45d3e86e5ff9a9df03b8cf31afd76817b662c803a3/tifffile-2026.5.2-py3-none-any.whl", hash = "sha256:5129b53b826e768a5b1af26b765eeea75c2d0a227d2d12849617e0737588e105", size = 266420, upload-time = "2026-05-02T20:19:29.814Z" }, + { url = "https://files.pythonhosted.org/packages/1f/6e/7d8850ff112f8f80d394ca45e89b975a3a43559d47af3137b767669b3294/tifffile-2026.5.15-py3-none-any.whl", hash = "sha256:6715515a53cabc0cefc5c9f13a0ae2c250e63e2ca784ce02d0b6c333810c2a17", size = 266665, upload-time = "2026-05-15T20:04:54.227Z" }, ] [[package]] @@ -3568,15 +3568,16 @@ wheels = [ [[package]] name = "torch" -version = "2.11.0" +version = "2.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cuda-bindings", marker = "sys_platform == 'linux'" }, - { name = "cuda-toolkit", extra = ["cublas", "cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"], marker = "sys_platform == 'linux'" }, + { name = "cuda-toolkit", extra = ["cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"], marker = "sys_platform == 'linux'" }, { name = "filelock" }, { name = "fsspec" }, { name = "jinja2" }, { name = "networkx" }, + { name = "nvidia-cublas", marker = "sys_platform == 'linux'" }, { name = "nvidia-cudnn-cu13", marker = "sys_platform == 'linux'" }, { name = "nvidia-cusparselt-cu13", marker = "sys_platform == 'linux'" }, { name = "nvidia-nccl-cu13", marker = "sys_platform == 'linux'" }, @@ -3587,30 +3588,30 @@ dependencies = [ { name = "typing-extensions" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/0d/98b410492609e34a155fa8b121b55c7dca229f39636851c3a9ec20edea21/torch-2.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7b6a60d48062809f58595509c524b88e6ddec3ebe25833d6462eeab81e5f2ce4", size = 80529712, upload-time = "2026-03-23T18:12:02.608Z" }, - { url = "https://files.pythonhosted.org/packages/84/03/acea680005f098f79fd70c1d9d5ccc0cb4296ec2af539a0450108232fc0c/torch-2.11.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:d91aac77f24082809d2c5a93f52a5f085032740a1ebc9252a7b052ef5a4fddc6", size = 419718178, upload-time = "2026-03-23T18:10:46.675Z" }, - { url = "https://files.pythonhosted.org/packages/8c/8b/d7be22fbec9ffee6cff31a39f8750d4b3a65d349a286cf4aec74c2375662/torch-2.11.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:7aa2f9bbc6d4595ba72138026b2074be1233186150e9292865e04b7a63b8c67a", size = 530604548, upload-time = "2026-03-23T18:10:03.569Z" }, - { url = "https://files.pythonhosted.org/packages/d1/bd/9912d30b68845256aabbb4a40aeefeef3c3b20db5211ccda653544ada4b6/torch-2.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:73e24aaf8f36ab90d95cd1761208b2eb70841c2a9ca1a3f9061b39fc5331b708", size = 114519675, upload-time = "2026-03-23T18:11:52.995Z" }, - { url = "https://files.pythonhosted.org/packages/6f/8b/69e3008d78e5cee2b30183340cc425081b78afc5eff3d080daab0adda9aa/torch-2.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b5866312ee6e52ea625cd211dcb97d6a2cdc1131a5f15cc0d87eec948f6dd34", size = 80606338, upload-time = "2026-03-23T18:11:34.781Z" }, - { url = "https://files.pythonhosted.org/packages/13/16/42e5915ebe4868caa6bac83a8ed59db57f12e9a61b7d749d584776ed53d5/torch-2.11.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f99924682ef0aa6a4ab3b1b76f40dc6e273fca09f367d15a524266db100a723f", size = 419731115, upload-time = "2026-03-23T18:11:06.944Z" }, - { url = "https://files.pythonhosted.org/packages/1a/c9/82638ef24d7877510f83baf821f5619a61b45568ce21c0a87a91576510aa/torch-2.11.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:0f68f4ac6d95d12e896c3b7a912b5871619542ec54d3649cf48cc1edd4dd2756", size = 530712279, upload-time = "2026-03-23T18:10:31.481Z" }, - { url = "https://files.pythonhosted.org/packages/1c/ff/6756f1c7ee302f6d202120e0f4f05b432b839908f9071157302cedfc5232/torch-2.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:fbf39280699d1b869f55eac536deceaa1b60bd6788ba74f399cc67e60a5fab10", size = 114556047, upload-time = "2026-03-23T18:10:55.931Z" }, - { url = "https://files.pythonhosted.org/packages/87/89/5ea6722763acee56b045435fb84258db7375c48165ec8be7880ab2b281c5/torch-2.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e6debd97ccd3205bbb37eb806a9d8219e1139d15419982c09e23ef7d4369d18", size = 80606801, upload-time = "2026-03-23T18:10:18.649Z" }, - { url = "https://files.pythonhosted.org/packages/32/d1/8ed2173589cbfe744ed54e5a73efc107c0085ba5777ee93a5f4c1ab90553/torch-2.11.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:63a68fa59de8f87acc7e85a5478bb2dddbb3392b7593ec3e78827c793c4b73fd", size = 419732382, upload-time = "2026-03-23T18:08:30.835Z" }, - { url = "https://files.pythonhosted.org/packages/3d/e1/b73f7c575a4b8f87a5928f50a1e35416b5e27295d8be9397d5293e7e8d4c/torch-2.11.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:cc89b9b173d9adfab59fd227f0ab5e5516d9a52b658ae41d64e59d2e55a418db", size = 530711509, upload-time = "2026-03-23T18:08:47.213Z" }, - { url = "https://files.pythonhosted.org/packages/66/82/3e3fcdd388fbe54e29fd3f991f36846ff4ac90b0d0181e9c8f7236565f82/torch-2.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:4dda3b3f52d121063a731ddb835f010dc137b920d7fec2778e52f60d8e4bf0cd", size = 114555842, upload-time = "2026-03-23T18:09:52.111Z" }, - { url = "https://files.pythonhosted.org/packages/db/38/8ac78069621b8c2b4979c2f96dc8409ef5e9c4189f6aac629189a78677ca/torch-2.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8b394322f49af4362d4f80e424bcaca7efcd049619af03a4cf4501520bdf0fb4", size = 80959574, upload-time = "2026-03-23T18:10:14.214Z" }, - { url = "https://files.pythonhosted.org/packages/6d/6c/56bfb37073e7136e6dd86bfc6af7339946dd684e0ecf2155ac0eee687ae1/torch-2.11.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:2658f34ce7e2dabf4ec73b45e2ca68aedad7a5be87ea756ad656eaf32bf1e1ea", size = 419732324, upload-time = "2026-03-23T18:09:36.604Z" }, - { url = "https://files.pythonhosted.org/packages/07/f4/1b666b6d61d3394cca306ea543ed03a64aad0a201b6cd159f1d41010aeb1/torch-2.11.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:98bb213c3084cfe176302949bdc360074b18a9da7ab59ef2edc9d9f742504778", size = 530596026, upload-time = "2026-03-23T18:09:20.842Z" }, - { url = "https://files.pythonhosted.org/packages/48/6b/30d1459fa7e4b67e9e3fe1685ca1d8bb4ce7c62ef436c3a615963c6c866c/torch-2.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a97b94bbf62992949b4730c6cd2cc9aee7b335921ee8dc207d930f2ed09ae2db", size = 114793702, upload-time = "2026-03-23T18:09:47.304Z" }, - { url = "https://files.pythonhosted.org/packages/26/0d/8603382f61abd0db35841148ddc1ffd607bf3100b11c6e1dab6d2fc44e72/torch-2.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:01018087326984a33b64e04c8cb5c2795f9120e0d775ada1f6638840227b04d7", size = 80573442, upload-time = "2026-03-23T18:09:10.117Z" }, - { url = "https://files.pythonhosted.org/packages/c7/86/7cd7c66cb9cec6be330fff36db5bd0eef386d80c031b581ec81be1d4b26c/torch-2.11.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:2bb3cc54bd0dea126b0060bb1ec9de0f9c7f7342d93d436646516b0330cd5be7", size = 419749385, upload-time = "2026-03-23T18:07:33.77Z" }, - { url = "https://files.pythonhosted.org/packages/47/e8/b98ca2d39b2e0e4730c0ee52537e488e7008025bc77ca89552ff91021f7c/torch-2.11.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:4dc8b3809469b6c30b411bb8c4cad3828efd26236153d9beb6a3ec500f211a60", size = 530716756, upload-time = "2026-03-23T18:07:50.02Z" }, - { url = "https://files.pythonhosted.org/packages/78/88/d4a4cda8362f8a30d1ed428564878c3cafb0d87971fbd3947d4c84552095/torch-2.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:2b4e811728bd0cc58fb2b0948fe939a1ee2bf1422f6025be2fca4c7bd9d79718", size = 114552300, upload-time = "2026-03-23T18:09:05.617Z" }, - { url = "https://files.pythonhosted.org/packages/bf/46/4419098ed6d801750f26567b478fc185c3432e11e2cad712bc6b4c2ab0d0/torch-2.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8245477871c3700d4370352ffec94b103cfcb737229445cf9946cddb7b2ca7cd", size = 80959460, upload-time = "2026-03-23T18:09:00.818Z" }, - { url = "https://files.pythonhosted.org/packages/fd/66/54a56a4a6ceaffb567231994a9745821d3af922a854ed33b0b3a278e0a99/torch-2.11.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:ab9a8482f475f9ba20e12db84b0e55e2f58784bdca43a854a6ccd3fd4b9f75e6", size = 419735835, upload-time = "2026-03-23T18:07:18.974Z" }, - { url = "https://files.pythonhosted.org/packages/b1/e7/0b6665f533aa9e337662dc190425abc0af1fe3234088f4454c52393ded61/torch-2.11.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:563ed3d25542d7e7bbc5b235ccfacfeb97fb470c7fee257eae599adb8005c8a2", size = 530613405, upload-time = "2026-03-23T18:08:07.014Z" }, - { url = "https://files.pythonhosted.org/packages/cf/bf/c8d12a2c86dbfd7f40fb2f56fbf5a505ccf2d9ce131eb559dfc7c51e1a04/torch-2.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b2a43985ff5ef6ddd923bbcf99943e5f58059805787c5c9a2622bf05ca2965b0", size = 114792991, upload-time = "2026-03-23T18:08:19.216Z" }, + { url = "https://files.pythonhosted.org/packages/18/62/131124fb95df03811b8260d1d43dcc5ee85ea1a344b964613d7efe77fb08/torch-2.12.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:10802fd383bbfed646212e765a72c37d2185205d4f26eb197a254e8ac7ddcb25", size = 87990344, upload-time = "2026-05-13T14:55:42.154Z" }, + { url = "https://files.pythonhosted.org/packages/12/9c/dda0dbd547dc549839824135f223792fd0e725f28ed0715dda366b7acaa2/torch-2.12.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:c12592630aef72feaf18bd3f197ef587bbfa21131b31c38b23ab2e55fce92e36", size = 426362932, upload-time = "2026-05-13T14:54:15.295Z" }, + { url = "https://files.pythonhosted.org/packages/e2/d2/a7dd5a3f9bdaa7842124e8e2359202b317c48d47d2fc5816fafdf2049adb/torch-2.12.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:415c1b8d0412f67551c8e89a2daca0fb3e56694af0281ba155eaa9da481f58b4", size = 532170085, upload-time = "2026-05-13T14:55:20.788Z" }, + { url = "https://files.pythonhosted.org/packages/12/1b/a61ce2004f9ab0ea8964a6e6168133a127795667639e2ff4f8f2bdb16a65/torch-2.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:dd37188ea325042cb1f6cafa56822b11ada2520c04791a52629b0af25bdfbfd9", size = 122953128, upload-time = "2026-05-13T14:54:52.744Z" }, + { url = "https://files.pythonhosted.org/packages/ef/bb/285d643f254731294c9b595a007eac39db4600a98682d7bca688f42ca164/torch-2.12.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b41339df93d491435e790ff8bcbae1c0ce777175889bfd1281d119862793e6a2", size = 88010197, upload-time = "2026-05-13T14:55:35.414Z" }, + { url = "https://files.pythonhosted.org/packages/79/81/76debf1db1343bd929bbb5d74c89fb437c2ed88eb144712557e7bd3eea45/torch-2.12.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:8fbef9f108a863e7722a73740998967e3b074742a834fc5be3a535a2befa7057", size = 426376751, upload-time = "2026-05-13T14:55:03.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/f0/80026028b603c4650ff270fc3785bdef4bd6738765a9cc5a0f5a637d65a2/torch-2.12.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4b4f64c2c2b11f7510d93dd6412b87025ff6eddd6bb61c3b5a3d892ea20c4756", size = 532261691, upload-time = "2026-05-13T14:52:54.453Z" }, + { url = "https://files.pythonhosted.org/packages/b9/c2/64b06cbb7830fb3cd9be13e1158b31a3f36b68e6a209105ee3c9d9480be0/torch-2.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:8b958caff4a14d3a3b0b2dfc6a378f64dda9728a9dad28c08a0db9ce4dafb549", size = 122988114, upload-time = "2026-05-13T14:54:42.153Z" }, + { url = "https://files.pythonhosted.org/packages/86/ca/01896c80ba921676aa45886b2c5b8d774912de2a1f719de48169c6f755cd/torch-2.12.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:90dd587a5f61bfe1307148b581e2084fc5bc4a06e2b90a20e9a36b81087ff16b", size = 88009511, upload-time = "2026-05-13T14:54:47.411Z" }, + { url = "https://files.pythonhosted.org/packages/a5/04/52bdaf4787eab6ac7d7f5851dff934e4def0bc8ead9c8fd2b69b3e529699/torch-2.12.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:864392c73b7654f4d2b3ae712f607937d0dbb1101c4555fbb41848106b297f39", size = 426383231, upload-time = "2026-05-13T14:53:32.129Z" }, + { url = "https://files.pythonhosted.org/packages/49/8a/94bdecd13f5aaa90d45920b89789d9fe7c6f4af8c3cdd7ce01fcb59908fc/torch-2.12.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5d6b560dfa7d56291c07d615c3bb73e8d9943d9b6d87f76cd0d9d570c4797fa6", size = 532269288, upload-time = "2026-05-13T14:53:49.423Z" }, + { url = "https://files.pythonhosted.org/packages/3e/2f/bdbaaa267de519ef1b73054bf590d8c93c37a266c9a4e24a01bd38b6918f/torch-2.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:3fee918902090ade827643e758e98363278815de583c75d111fdd665ebffde9f", size = 122987706, upload-time = "2026-05-13T14:54:00.335Z" }, + { url = "https://files.pythonhosted.org/packages/9b/ad/e95e822f3538171e22640a7fbe839a1fdb666600bf6487025de2ff03b11a/torch-2.12.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:10ee1448a9f304d3b987eb4656f664ba6e4d7b410ca7a5a7c642199777a2cf88", size = 88319556, upload-time = "2026-05-13T14:54:05.574Z" }, + { url = "https://files.pythonhosted.org/packages/b7/07/055d06d985b445d67422d25b033c11cf55bbb81785d4c4e68e28bca5820e/torch-2.12.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:af68dbf403439cae9ceaeaaf92f8352b460787dcd27b92aa05c40dd4a19c0f1e", size = 426397656, upload-time = "2026-05-13T14:52:38.84Z" }, + { url = "https://files.pythonhosted.org/packages/43/94/b0b4fdc3014122e0a7302fb90086d352aa48f2576f0b252561ebb38c01a8/torch-2.12.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:a6a2eebb237d3b1d9ad3b378e86d9b9e0782afdea8b1e0eba6a13646b9b49c07", size = 532183124, upload-time = "2026-05-13T14:53:16.178Z" }, + { url = "https://files.pythonhosted.org/packages/d8/c8/052405e6ad05d3237bfe5a4df78f917773956f8e17813a2d44c059068b74/torch-2.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2140e373e9a51a3e22ef62e8d14366d0b470d18f0adf19fdc757368077133a34", size = 123232462, upload-time = "2026-05-13T14:52:27.26Z" }, + { url = "https://files.pythonhosted.org/packages/67/dc/ac069f8d6e8be701535921141055293b0d4819d3d7f224a4612cf157c7f9/torch-2.12.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:f7dfae4a519197dfa050e98d8e36378a0fb5899625a875c2b54445005a2e404e", size = 88027282, upload-time = "2026-05-13T14:53:05.258Z" }, + { url = "https://files.pythonhosted.org/packages/33/c3/1c1eb00e34555b536dddf792676026a988d710ed36981aa00499b36b0620/torch-2.12.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:891c769072637c74e9a5a77a3bc782894696d8ffec83b938df8536dee7f0ba78", size = 426386961, upload-time = "2026-05-13T14:51:28.406Z" }, + { url = "https://files.pythonhosted.org/packages/cd/d4/7e730dba0c7032a4154dc9056b76cf9625515e030e269cfbf8098fcfee7d/torch-2.12.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:e2ad3eb85d39c3cab62dfa93ed5a73516e6a53c6713cb97d004004fe089f0f1f", size = 532272265, upload-time = "2026-05-13T14:51:59.308Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b4/92c80d1bbfee1c0036c06d1d2155a3065bd2423134c83bf8a47e65cd6b9b/torch-2.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:c66696857e987efb8bc1777a37357ec4f60ab5e8af6250b83d6034437fa2d8f3", size = 122987138, upload-time = "2026-05-13T14:51:45.942Z" }, + { url = "https://files.pythonhosted.org/packages/7b/78/2e12b37ce50a19a037d7bc62d652a5a8f27385a7b05859d6bc9204f20cfe/torch-2.12.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:b4556715c8572758625d62b6e0ae3b1f76c440221913a6fb5e100f321fb4fb02", size = 88320100, upload-time = "2026-05-13T14:51:39.955Z" }, + { url = "https://files.pythonhosted.org/packages/56/5e/83c450ec7b0bb40a7b74611c1b5440f9260e33c54c90d556fd4a1f0fd955/torch-2.12.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a43ac605a5e13116c72b64c359644cce0229f213dde48d2ae0ae5eb5becf7feb", size = 426391871, upload-time = "2026-05-13T14:52:14.989Z" }, + { url = "https://files.pythonhosted.org/packages/c9/e9/1a0b575d98d0afedd8f157d23fa3d2759421483660448e60d0a4b10b6daa/torch-2.12.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6a7512adfdd7f6732e40de1c620831e3c75b39b98cef60b11d0c5f0a76473ec5", size = 532192241, upload-time = "2026-05-13T14:51:07.795Z" }, + { url = "https://files.pythonhosted.org/packages/88/21/afadd25ecd81b3cea1e11c73cf1ab41a983a50271548c3ec7ec3b9efc3e9/torch-2.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5f96b63f8287f66a005dd1b5a6abba2920f11156c5e5c4d815f3e2050fd1aa16", size = 123231092, upload-time = "2026-05-13T14:51:18.854Z" }, ] [[package]] @@ -3639,7 +3640,7 @@ wheels = [ [[package]] name = "torchvision" -version = "0.26.0" +version = "0.27.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, @@ -3647,30 +3648,30 @@ dependencies = [ { name = "torch" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/bd/d552a2521bade3295b2c6e7a4a0d1022261cab7ca7011f4e2a330dbb3caa/torchvision-0.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55bd6ad4ae77be01ba67a410b05b51f53b0d0ee45f146eb6a0dfb9007e70ab3c", size = 1863499, upload-time = "2026-03-23T18:12:58.696Z" }, - { url = "https://files.pythonhosted.org/packages/33/bf/21b899792b08cae7a298551c68398a79e333697479ed311b3b067aab4bdc/torchvision-0.26.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:1c55dc8affbcc0eb2060fbabbe996ae9e5839b24bb6419777f17848945a411b1", size = 7767527, upload-time = "2026-03-23T18:12:44.348Z" }, - { url = "https://files.pythonhosted.org/packages/9a/45/57bbf9e216850d065e66dd31a50f57424b607f1d878ab8956e56a1f4e36b/torchvision-0.26.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:fd10b5f994c210f4f6d6761cf686f82d748554adf486cb0979770c3252868c8f", size = 7519925, upload-time = "2026-03-23T18:12:53.283Z" }, - { url = "https://files.pythonhosted.org/packages/10/58/ed8f7754299f3e91d6414b6dc09f62b3fa7c6e5d63dfe48d69ab81498a37/torchvision-0.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:de6424b12887ad884f39a0ee446994ae3cd3b6a00a9cafe1bead85a031132af0", size = 3983834, upload-time = "2026-03-23T18:13:00.224Z" }, - { url = "https://files.pythonhosted.org/packages/ae/e7/56b47cc3b132aea90ccce22bcb8975dec688b002150012acc842846039d0/torchvision-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c409e1c3fdebec7a3834465086dbda8bf7680eff79abf7fd2f10c6b59520a7a4", size = 1863502, upload-time = "2026-03-23T18:12:57.326Z" }, - { url = "https://files.pythonhosted.org/packages/f4/ec/5c31c92c08b65662fe9604a4067ae8232582805949f11ddc042cebe818ed/torchvision-0.26.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:406557718e62fdf10f5706e88d8a5ec000f872da913bf629aab9297622585547", size = 7767944, upload-time = "2026-03-23T18:12:42.805Z" }, - { url = "https://files.pythonhosted.org/packages/f5/d8/cb6ccda1a1f35a6597645818641701207b3e8e13553e75fce5d86bac74b2/torchvision-0.26.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d61a5abb6b42a0c0c311996c2ac4b83a94418a97182c83b055a2a4ae985e05aa", size = 7522205, upload-time = "2026-03-23T18:12:54.654Z" }, - { url = "https://files.pythonhosted.org/packages/1c/a9/c272623a0f735c35f0f6cd6dc74784d4f970e800cf063bb76687895a2ab9/torchvision-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:7993c01648e7c61d191b018e84d38fe0825c8fcb2720cd0f37caf7ba14404aa1", size = 4255155, upload-time = "2026-03-23T18:12:32.652Z" }, - { url = "https://files.pythonhosted.org/packages/da/80/0762f77f53605d10c9477be39bb47722cc8e383bbbc2531471ce0e396c07/torchvision-0.26.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5d63dd43162691258b1b3529b9041bac7d54caa37eae0925f997108268cbf7c4", size = 1860809, upload-time = "2026-03-23T18:12:47.629Z" }, - { url = "https://files.pythonhosted.org/packages/e6/81/0b3e58d1478c660a5af4268713486b2df7203f35abd9195fea87348a5178/torchvision-0.26.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a39c7a26538c41fda453f9a9692b5ff9b35a5437db1d94f3027f6f509c160eac", size = 7727494, upload-time = "2026-03-23T18:12:46.062Z" }, - { url = "https://files.pythonhosted.org/packages/b6/dc/d9ab5d29115aa05e12e30f1397a3eeae1d88a511241dc3bce48dc4342675/torchvision-0.26.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:b7e6213620bbf97742e5f79832f9e9d769e6cf0f744c5b53dad80b76db633691", size = 7521747, upload-time = "2026-03-23T18:12:36.815Z" }, - { url = "https://files.pythonhosted.org/packages/a9/1b/f1bc86a918c5f6feab1eeff11982e2060f4704332e96185463d27855bdf5/torchvision-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:4280c35ec8cba1fcc8294fb87e136924708726864c379e4c54494797d86bc474", size = 4319880, upload-time = "2026-03-23T18:12:38.168Z" }, - { url = "https://files.pythonhosted.org/packages/66/28/b4ad0a723ed95b003454caffcc41894b34bd8379df340848cae2c33871de/torchvision-0.26.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:358fc4726d0c08615b6d83b3149854f11efb2a564ed1acb6fce882e151412d23", size = 1951973, upload-time = "2026-03-23T18:12:48.781Z" }, - { url = "https://files.pythonhosted.org/packages/71/e2/7a89096e6cf2f3336353b5338ba925e0addf9d8601920340e6bdf47e8eb3/torchvision-0.26.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:3daf9cc149cf3cdcbd4df9c59dae69ffca86c6823250442c3bbfd63fc2e26c61", size = 7728679, upload-time = "2026-03-23T18:12:26.196Z" }, - { url = "https://files.pythonhosted.org/packages/69/1d/4e1eebc17d18ce080a11dcf3df3f8f717f0efdfa00983f06e8ba79259f61/torchvision-0.26.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:82c3965eca27e86a316e31e4c3e5a16d353e0bcbe0ef8efa2e66502c54493c4b", size = 7609138, upload-time = "2026-03-23T18:12:35.327Z" }, - { url = "https://files.pythonhosted.org/packages/f3/a4/f1155e943ae5b32400d7000adc81c79bb0392b16ceb33bcf13e02e48cced/torchvision-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ebc043cc5a4f0bf22e7680806dbba37ffb19e70f6953bbb44ed1a90aeb5c9bea", size = 4248202, upload-time = "2026-03-23T18:12:41.423Z" }, - { url = "https://files.pythonhosted.org/packages/7f/c8/9bffa9c7f7bdf95b2a0a2dc535c290b9f1cc580c3fb3033ab1246ffffdeb/torchvision-0.26.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:eb61804eb9dbe88c5a2a6c4da8dec1d80d2d0a6f18c999c524e32266cb1ebcd3", size = 1860813, upload-time = "2026-03-23T18:12:39.636Z" }, - { url = "https://files.pythonhosted.org/packages/7b/ac/48f28ffd227991f2e14f4392dde7e8dc14352bb9428c1ef4a4bbf5f7ed85/torchvision-0.26.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:9a904f2131cbfadab4df828088a9f66291ad33f49ff853872aed1f86848ef776", size = 7727777, upload-time = "2026-03-23T18:12:22.549Z" }, - { url = "https://files.pythonhosted.org/packages/a4/21/a2266f7f1b0e58e624ff15fd6f01041f59182c49551ece0db9a183071329/torchvision-0.26.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:0f3e572efe62ad645017ea847e0b5e4f2f638d4e39f05bc011d1eb9ac68d4806", size = 7522174, upload-time = "2026-03-23T18:12:29.565Z" }, - { url = "https://files.pythonhosted.org/packages/fc/ba/1666f90bc0bdd77aaa11dcc42bb9f621a9c3668819c32430452e3d404730/torchvision-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:114bec0c0e98aa4ba446f63e2fe7a2cbca37b39ac933987ee4804f65de121800", size = 4348469, upload-time = "2026-03-23T18:12:24.44Z" }, - { url = "https://files.pythonhosted.org/packages/45/8f/1f0402ac55c2ae15651ff831957d083fe70b2d12282e72612a30ba601512/torchvision-0.26.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:b7d3e295624a28b3b1769228ce1345d94cf4d390dd31136766f76f2d20f718da", size = 1860826, upload-time = "2026-03-23T18:12:34.1Z" }, - { url = "https://files.pythonhosted.org/packages/d2/6a/18a582fe3c5ee26f49b5c9fb21ad8016b4d1c06d10178894a58653946fda/torchvision-0.26.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:7058c5878262937e876f20c25867b33724586aa4499e2853b2d52b99a5e51953", size = 7729089, upload-time = "2026-03-23T18:12:31.394Z" }, - { url = "https://files.pythonhosted.org/packages/c5/9b/f7e119b59499edc00c55c03adc9ec3bd96144d9b81c46852c431f9c64a9a/torchvision-0.26.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:8008474855623c6ba52876589dc52df0aa66e518c25eca841445348e5f79844c", size = 7522704, upload-time = "2026-03-23T18:12:20.301Z" }, - { url = "https://files.pythonhosted.org/packages/d0/6a/09f3844c10643f6c0de5d95abc863420cfaf194c88c7dffd0ac523e2015f/torchvision-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e9d0e022c19a78552fb055d0414d47fecb4a649309b9968573daea160ba6869c", size = 4454275, upload-time = "2026-03-23T18:12:27.487Z" }, + { url = "https://files.pythonhosted.org/packages/cf/d6/a7e71e981042d5c573e2e61891b9023b190c88adb75b18bed8594371250c/torchvision-0.27.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:df0c166b6bdf7c47f88e81e8b43bc085451d5c50d0c5d1691bc474c1227d6fed", size = 1758812, upload-time = "2026-05-13T14:57:16.662Z" }, + { url = "https://files.pythonhosted.org/packages/93/f9/f542fb7e4476603fb237ebdc64369a7d11f18eb5a129aa2559cbdb710aee/torchvision-0.27.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:9bb9251f64b854124efed95d02953a89f7e2726c3ca662d7ea0151129157297f", size = 7831148, upload-time = "2026-05-13T14:57:08.37Z" }, + { url = "https://files.pythonhosted.org/packages/f6/61/7aa7cc2c9e8750027f6fb9ae3a7393ef43860bcdfe3966e2f71fee800e31/torchvision-0.27.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:f44453f107c296d5446a79f7ac59733ad8bf5ddfa04c53805dfbae298a42a798", size = 7575519, upload-time = "2026-05-13T14:56:50.552Z" }, + { url = "https://files.pythonhosted.org/packages/19/aa/929b358b1a643849b81ec95569938044cc37dc65ab10c84eb6d82fe1bfbb/torchvision-0.27.0-cp311-cp311-win_amd64.whl", hash = "sha256:b4aacff70ea4b7377f996f9048989c850d221fef33658ddbcae42aa5bd4ca11a", size = 3749475, upload-time = "2026-05-13T14:57:11.007Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c8/5cd91932f7f3671b0743dc4ae1a4c16b1d0b45bf4087976277d325bda718/torchvision-0.27.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:1a6dd742a150645126df9e0b2e449874c1d635897c773b322c2e067e98382dfe", size = 1758824, upload-time = "2026-05-13T14:57:15.227Z" }, + { url = "https://files.pythonhosted.org/packages/d9/36/7fb7d19477b3d93283b52fea11fa8ee30ab9064a08c97b4a6b91445e26cb/torchvision-0.27.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:65772ff3ec4f4f5d680e30019835555dd239e7fefee4b0a846375fe1cb1592ef", size = 7831034, upload-time = "2026-05-13T14:57:06.483Z" }, + { url = "https://files.pythonhosted.org/packages/62/43/dfd894c3f8b01b5b33fde990f0159c1926ebc7b6e2c4193e2efb7da3c4cb/torchvision-0.27.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:7a9966a088d06b4cf6c610e03be62de469efa6f2cd2e7c7eed8e925ed6af59ac", size = 7579774, upload-time = "2026-05-13T14:56:59.337Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0c/722e989f9cf026e97ef7cb24a9bb1859e099f72d247ae35388fb89729f73/torchvision-0.27.0-cp312-cp312-win_amd64.whl", hash = "sha256:2c037709072ca9b19750c0cbe9e8bb6f91c9a1be1befa26df33e281deccbd8c7", size = 4021073, upload-time = "2026-05-13T14:57:00.848Z" }, + { url = "https://files.pythonhosted.org/packages/d8/ae/36547812e6e047c1d80bcacd1b17a340612b08a6e876e0aabf3d0b9228b0/torchvision-0.27.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:41d6dae73e1af09fa82ded597ae57f2a2314285acde54b25890a8f8e51b999d7", size = 1758826, upload-time = "2026-05-13T14:57:05.262Z" }, + { url = "https://files.pythonhosted.org/packages/ae/30/32c4ea842738728a14e3df8c576c62dedcf5ae5cb6a5c984c6429ebe7524/torchvision-0.27.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:70f071c6f74b60d5fe8851636d8d4cd5f4fa29d57fd9348a87a6f17b990b95ba", size = 7789501, upload-time = "2026-05-13T14:56:57.786Z" }, + { url = "https://files.pythonhosted.org/packages/f6/24/4d0d48684251bd0673f87d633d5d88ab00227983b00591156eed2f86c8d5/torchvision-0.27.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:aaafa6962c9d91f42503de1957d6fa349907d028c06f335bd95da7a5bc57147d", size = 7579868, upload-time = "2026-05-13T14:56:41.618Z" }, + { url = "https://files.pythonhosted.org/packages/ba/da/e6edd051d2ba25adf23b120fa97f458dff888d098c51e84724f17d2d1470/torchvision-0.27.0-cp313-cp313-win_amd64.whl", hash = "sha256:aee384a2782c89517c4ab9061d2720ba59fd2ffe5ef89d0a149cc2d43abdf521", size = 4092700, upload-time = "2026-05-13T14:57:09.729Z" }, + { url = "https://files.pythonhosted.org/packages/fa/23/95dfa40431360f42ca949bf861434bed51164adfa8fb9801e05bf3194f50/torchvision-0.27.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:c5121f1b9ab09a7f73e837871deb8321551f7eaeb19d87aa00de9191968eae44", size = 1845008, upload-time = "2026-05-13T14:57:03.768Z" }, + { url = "https://files.pythonhosted.org/packages/23/b9/9dbdf76b2b49a75ba8088df6f7c755bdb520afb6c6dbac0102b46cde5e99/torchvision-0.27.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:1c01f0d1091ae22b9dfc082b0a0fe5faaf053686a29b4fb082ba7691375c73cf", size = 7791430, upload-time = "2026-05-13T14:56:56.206Z" }, + { url = "https://files.pythonhosted.org/packages/5c/6a/e4a16cf2f3310c2ea7760dc5d9054496844391e0f4c1fae87fefac2f3d9e/torchvision-0.27.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:dadea3c5ecfd05bbb2a3312ab0374f213c58bf6459cb059122e2f4dfe13d10ed", size = 7668441, upload-time = "2026-05-13T14:57:02.127Z" }, + { url = "https://files.pythonhosted.org/packages/00/70/01b6461117a6a94b5af3f8ee166bb0f045056f3cf187750c110dabfdfffa/torchvision-0.27.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a49e55055a39a8506fe7e59850522cab004efb2c3839f6057658889c1d69c815", size = 4141602, upload-time = "2026-05-13T14:56:53.449Z" }, + { url = "https://files.pythonhosted.org/packages/92/22/c0633677b3b3f3e69554a21ac087bf705f829c40cd5e3783507b8c006681/torchvision-0.27.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:c1fac0fc2a7adf29481fc1938a0e7845c57ba1147a986784109c4d98f434ea8c", size = 1758818, upload-time = "2026-05-13T14:56:54.988Z" }, + { url = "https://files.pythonhosted.org/packages/48/e8/55f9d9667b56dae470e69e31beac9b00d458ea393feec1aae95cc4f3f1c9/torchvision-0.27.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:cbf89764fc76f3f17fbf80c12d5a89c691e91cb9d82c38412aaf0568655ffb19", size = 7789667, upload-time = "2026-05-13T14:56:48.858Z" }, + { url = "https://files.pythonhosted.org/packages/00/bc/6f8681daf3bbc4c315bb0005110f99d28e3ecd675bf9c8f2c0d393fbac7a/torchvision-0.27.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:91f61b9865423037c327eb56afa207cc72de874e458c361840db9dcf5ce0c0eb", size = 7579848, upload-time = "2026-05-13T14:56:38.209Z" }, + { url = "https://files.pythonhosted.org/packages/19/6c/8d8020e6bd1e46c53e487c9c4e9457a07f2ee28931028fb5d71e2da40adc/torchvision-0.27.0-cp314-cp314-win_amd64.whl", hash = "sha256:5bb82fc3c55daf1788621e504310b0a286f1069627a8742f692aebb075ef25a7", size = 4119284, upload-time = "2026-05-13T14:56:46.625Z" }, + { url = "https://files.pythonhosted.org/packages/8d/7e/e78c48662a8d551606efdbe11c6b9c1d6d2391b92cd0e4591b9e6a2412b8/torchvision-0.27.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:2c4099a15150143b9b034730b404a56d572efe0b79489b4c765d929cb4eac7f3", size = 1758828, upload-time = "2026-05-13T14:56:52.293Z" }, + { url = "https://files.pythonhosted.org/packages/21/dd/d03ee9f9ee7bf11a8c7c776fb8e7fd6102f59c013791a2a4e5175bd6cba7/torchvision-0.27.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:b4c6bb0a670dcba017b3643e21902c9b8a1cc1c127d602f1488fa29ec3c6e865", size = 7790618, upload-time = "2026-05-13T14:56:44.721Z" }, + { url = "https://files.pythonhosted.org/packages/39/08/4002336a74742be70728603ec1769feb2b55e0d19c532c9ec9f92008de76/torchvision-0.27.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:1c2db4bde82bc48ebff73436a6adf34d4f809448268a70d9a1285f5c8f92313d", size = 7580217, upload-time = "2026-05-13T14:56:43.274Z" }, + { url = "https://files.pythonhosted.org/packages/ed/cb/4dd4783eb3565f526ba6e64b6f6ca26c00eacc924cdfe60455db9d91b84b/torchvision-0.27.0-cp314-cp314t-win_amd64.whl", hash = "sha256:72bf547e58ddb948689734eed6f4b6a2031f979dba4fb08e3690688b392e929f", size = 4226392, upload-time = "2026-05-13T14:56:40.235Z" }, ] [[package]] @@ -3713,21 +3714,21 @@ wheels = [ [[package]] name = "triton" -version = "3.6.0" +version = "3.7.0" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/2c/96f92f3c60387e14cc45aed49487f3486f89ea27106c1b1376913c62abe4/triton-3.6.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49df5ef37379c0c2b5c0012286f80174fcf0e073e5ade1ca9a86c36814553651", size = 176081190, upload-time = "2026-01-20T16:16:00.523Z" }, - { url = "https://files.pythonhosted.org/packages/e0/12/b05ba554d2c623bffa59922b94b0775673de251f468a9609bc9e45de95e9/triton-3.6.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8e323d608e3a9bfcc2d9efcc90ceefb764a82b99dea12a86d643c72539ad5d3", size = 188214640, upload-time = "2026-01-20T16:00:35.869Z" }, - { url = "https://files.pythonhosted.org/packages/17/5d/08201db32823bdf77a0e2b9039540080b2e5c23a20706ddba942924ebcd6/triton-3.6.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:374f52c11a711fd062b4bfbb201fd9ac0a5febd28a96fb41b4a0f51dde3157f4", size = 176128243, upload-time = "2026-01-20T16:16:07.857Z" }, - { url = "https://files.pythonhosted.org/packages/ab/a8/cdf8b3e4c98132f965f88c2313a4b493266832ad47fb52f23d14d4f86bb5/triton-3.6.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74caf5e34b66d9f3a429af689c1c7128daba1d8208df60e81106b115c00d6fca", size = 188266850, upload-time = "2026-01-20T16:00:43.041Z" }, - { url = "https://files.pythonhosted.org/packages/3c/12/34d71b350e89a204c2c7777a9bba0dcf2f19a5bfdd70b57c4dbc5ffd7154/triton-3.6.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448e02fe6dc898e9e5aa89cf0ee5c371e99df5aa5e8ad976a80b93334f3494fd", size = 176133521, upload-time = "2026-01-20T16:16:13.321Z" }, - { url = "https://files.pythonhosted.org/packages/f9/0b/37d991d8c130ce81a8728ae3c25b6e60935838e9be1b58791f5997b24a54/triton-3.6.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10c7f76c6e72d2ef08df639e3d0d30729112f47a56b0c81672edc05ee5116ac9", size = 188289450, upload-time = "2026-01-20T16:00:49.136Z" }, - { url = "https://files.pythonhosted.org/packages/ce/4e/41b0c8033b503fd3cfcd12392cdd256945026a91ff02452bef40ec34bee7/triton-3.6.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1722e172d34e32abc3eb7711d0025bb69d7959ebea84e3b7f7a341cd7ed694d6", size = 176276087, upload-time = "2026-01-20T16:16:18.989Z" }, - { url = "https://files.pythonhosted.org/packages/35/f8/9c66bfc55361ec6d0e4040a0337fb5924ceb23de4648b8a81ae9d33b2b38/triton-3.6.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d002e07d7180fd65e622134fbd980c9a3d4211fb85224b56a0a0efbd422ab72f", size = 188400296, upload-time = "2026-01-20T16:00:56.042Z" }, - { url = "https://files.pythonhosted.org/packages/49/55/5ecf0dcaa0f2fbbd4420f7ef227ee3cb172e91e5fede9d0ecaddc43363b4/triton-3.6.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5523241e7d1abca00f1d240949eebdd7c673b005edbbce0aca95b8191f1d43", size = 176138577, upload-time = "2026-01-20T16:16:25.426Z" }, - { url = "https://files.pythonhosted.org/packages/df/3d/9e7eee57b37c80cec63322c0231bb6da3cfe535a91d7a4d64896fcb89357/triton-3.6.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a17a5d5985f0ac494ed8a8e54568f092f7057ef60e1b0fa09d3fd1512064e803", size = 188273063, upload-time = "2026-01-20T16:01:07.278Z" }, - { url = "https://files.pythonhosted.org/packages/48/db/56ee649cab5eaff4757541325aca81f52d02d4a7cd3506776cad2451e060/triton-3.6.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b3a97e8ed304dfa9bd23bb41ca04cdf6b2e617d5e782a8653d616037a5d537d", size = 176274804, upload-time = "2026-01-20T16:16:31.528Z" }, - { url = "https://files.pythonhosted.org/packages/f6/56/6113c23ff46c00aae423333eb58b3e60bdfe9179d542781955a5e1514cb3/triton-3.6.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46bd1c1af4b6704e554cad2eeb3b0a6513a980d470ccfa63189737340c7746a7", size = 188397994, upload-time = "2026-01-20T16:01:14.236Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c1/5d842314bb6c78442cc60437928781701c6050b8d479bc2a1aed691d37ca/triton-3.7.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a9e71fc392675fac364e0ecf4ef3f76f85b7f5433a16f4c3c5fe5f05a52c85fe", size = 188480277, upload-time = "2026-05-07T19:05:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/13/31/8315ea5f8dd18e60970b3022e3a8b93fd37e0b784fbbef86e10c8e6e5ca1/triton-3.7.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22bacffce443f54593dd20f05294d5a40622e0ea9ab632816f87154504356221", size = 201415942, upload-time = "2026-05-07T18:46:06.479Z" }, + { url = "https://files.pythonhosted.org/packages/f7/13/ec05adfcd87311d532ba61e3af143e8be59fcd26675884c4682841406a20/triton-3.7.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4bf49b00a7a377a68a6da603a876e797614e6455a80e9021669c476a953ad9a", size = 188505104, upload-time = "2026-05-07T19:05:09.843Z" }, + { url = "https://files.pythonhosted.org/packages/62/7b/468a576e35beef1426e0828e28e9ba9e65f5474d496f16ee126c15646324/triton-3.7.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f111161d49bf903c0eaedde3962353a3d841c08a836839b7cc1025b8426efcf", size = 201457567, upload-time = "2026-05-07T18:46:13.505Z" }, + { url = "https://files.pythonhosted.org/packages/01/e1/a59a583de59b8f62c495d67c80ee3ea97d09e91ac80c4c6e76456ed8d8ac/triton-3.7.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:abdf6beaa89b1bcfb9a43cd990536ce66091a997841a4814b260b7bee4c88c3c", size = 188503209, upload-time = "2026-05-07T19:05:17.935Z" }, + { url = "https://files.pythonhosted.org/packages/30/b1/b7507bb9815d403927c8dd51d4158ed2e11751a92dbc118a044f247b6848/triton-3.7.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a35d7afe3f3f058e7ec49fcce09794049e0ffc5c59019ac25ec3413741b8c4e7", size = 201453566, upload-time = "2026-05-07T18:46:20.427Z" }, + { url = "https://files.pythonhosted.org/packages/a6/8f/0bea7a6a0c989315c9135a1d7fb37e41905cfb3a17cbc1f10044ebd4cc3a/triton-3.7.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc1d61c172d257db80ddf42595131fb196ad2e9bdd751e90fe2ef13531734e8b", size = 188612899, upload-time = "2026-05-07T19:05:24.955Z" }, + { url = "https://files.pythonhosted.org/packages/e1/02/d96f57828d0912aec733b9bc7e0e7dbfd2c6f079a8fa433ac25cb93d1a30/triton-3.7.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:70fb9bbdc9f400afc54bbf6eb2670af28829a6ae3996863317964783141daf56", size = 201553816, upload-time = "2026-05-07T18:46:27.49Z" }, + { url = "https://files.pythonhosted.org/packages/40/fb/82a802dac4689f2a2fb2e69302e6a138eecc3e175bbe976ba3cfc717683a/triton-3.7.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a44a8476d0d3571eac4e4d1048e1ff75aad81a09ff4602ccfc56c6dea1672e", size = 188507879, upload-time = "2026-05-07T19:05:32.209Z" }, + { url = "https://files.pythonhosted.org/packages/8f/af/9904ec6d3c93d9b24e5ec360445bbdf758b7f00bfbeedb89cb0eb64eb8bb/triton-3.7.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b9b85e72968a9d8bba5ddb24e9b64aaabaf48affb042f2755cb7cfa92b7531ce", size = 201460637, upload-time = "2026-05-07T18:46:34.749Z" }, + { url = "https://files.pythonhosted.org/packages/a1/f9/4835a8ea746b88727d8899f4e3ccce4f9cacb38abfc3bb0a638266c53111/triton-3.7.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18a160de426fd99f92b0baf509045360afbd3bfaa0b4a5171dde800ec9f09684", size = 188608706, upload-time = "2026-05-07T19:05:39.218Z" }, + { url = "https://files.pythonhosted.org/packages/c1/68/fa86e5a39608000f645535b2c124920126327ab731f8c4fafd5b07ff8d4b/triton-3.7.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce061073102714b725f3660ec6939d94a1da7984b3aa99c921417cae273672f5", size = 201546766, upload-time = "2026-05-07T18:46:42.088Z" }, ] [[package]] @@ -3768,7 +3769,7 @@ wheels = [ [[package]] name = "virtualenv" -version = "21.3.1" +version = "21.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -3776,9 +3777,9 @@ dependencies = [ { name = "platformdirs" }, { name = "python-discovery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ec/0d/915c02c94d207b85580eb09bffab54438a709e7288524094fe781da526c2/virtualenv-21.3.1.tar.gz", hash = "sha256:c2305bc1fddeec40699b8370d13f8d431b0701f00ce895061ce493aeded4426b", size = 7613791, upload-time = "2026-05-05T01:34:31.402Z" } +sdist = { url = "https://files.pythonhosted.org/packages/15/ba/1f6e8c957e4932be060dcdc482d339c12e0216351478add3645cdaa53c05/virtualenv-21.3.3.tar.gz", hash = "sha256:f5bda277e553b1c2b3c1a8debfc30496e1288cc93ce6b7b71b3280047e317328", size = 7613784, upload-time = "2026-05-13T18:01:30.19Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-py3-none-any.whl", hash = "sha256:d1a71cf58f2f9228fff23a1f6ec15d39785c6b32e03658d104974247145edd35", size = 7594539, upload-time = "2026-05-05T01:34:28.98Z" }, + { url = "https://files.pythonhosted.org/packages/f4/34/a9dbe051de88a63eb7408ea66630bac38e72f7f6077d4be58737106860d9/virtualenv-21.3.3-py3-none-any.whl", hash = "sha256:7d5987d8369e098e41406efb780a3d4ca79280097293899e351a6407ee153ab3", size = 7594554, upload-time = "2026-05-13T18:01:27.815Z" }, ] [[package]] From adcc26dc018230d67641c0676d71b4cb877b4c2a Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Mon, 18 May 2026 11:16:02 -0700 Subject: [PATCH 290/335] Created a BaseContext class - totally optional, not sure if anyone uses this class anyways currently. Type-hinting error fixed in tomography_otp.py --- src/quantem/core/ml/constraints.py | 8 +++++++- src/quantem/tomography/tomography_context.py | 3 ++- src/quantem/tomography/tomography_opt.py | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/quantem/core/ml/constraints.py b/src/quantem/core/ml/constraints.py index f437924f..13011095 100644 --- a/src/quantem/core/ml/constraints.py +++ b/src/quantem/core/ml/constraints.py @@ -9,6 +9,12 @@ from quantem.tomography.tomography_context import ReconstructionContext +@dataclass +class BaseContext(ABC): + """ + Constraints should contain a context object that contains all necessary data for the constraints to be applied. + """ + pass @dataclass(slots=False) class Constraints(ABC): @@ -95,7 +101,7 @@ def apply_hard_constraints(self, pred: torch.Tensor) -> torch.Tensor: raise NotImplementedError @abstractmethod - def apply_soft_constraints(self, ctx: ReconstructionContext) -> torch.Tensor: + def apply_soft_constraints(self, ctx: BaseContext) -> torch.Tensor: """ Apply soft constraints to the model. """ diff --git a/src/quantem/tomography/tomography_context.py b/src/quantem/tomography/tomography_context.py index 4b67f118..2095727e 100644 --- a/src/quantem/tomography/tomography_context.py +++ b/src/quantem/tomography/tomography_context.py @@ -1,11 +1,12 @@ from dataclasses import dataclass from typing import Optional +from quantem.core.ml.constraints import BaseContext import torch @dataclass -class ReconstructionContext: +class ReconstructionContext(BaseContext): """ Handles all reconstruction parameters to be passed into object models. diff --git a/src/quantem/tomography/tomography_opt.py b/src/quantem/tomography/tomography_opt.py index c75de1e3..2c913277 100644 --- a/src/quantem/tomography/tomography_opt.py +++ b/src/quantem/tomography/tomography_opt.py @@ -27,7 +27,7 @@ def _get_default_lr(self, key: str) -> float: raise ValueError(f"Unknown optimization key: {key}") @property - def optimizer_params(self) -> dict[str, OptimizerType]: + def optimizer_params(self) -> dict[str, OptimizerType | dict[str, OptimizerType]]: return { key: params for key, params in [ From af33b962b1f8a7c47dd1ddf0702309beed185810 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Mon, 18 May 2026 11:18:42 -0700 Subject: [PATCH 291/335] Removed import in constraints.py --- src/quantem/core/ml/constraints.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/quantem/core/ml/constraints.py b/src/quantem/core/ml/constraints.py index 13011095..83270400 100644 --- a/src/quantem/core/ml/constraints.py +++ b/src/quantem/core/ml/constraints.py @@ -7,7 +7,6 @@ import torch from numpy.typing import NDArray -from quantem.tomography.tomography_context import ReconstructionContext @dataclass class BaseContext(ABC): From 03da610c5a3ce34c601058bfb243ba1d4d8ba42c Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Mon, 18 May 2026 11:21:41 -0700 Subject: [PATCH 292/335] PPLR description changed to multi-parameter optimization --- src/quantem/core/ml/models/model_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quantem/core/ml/models/model_base.py b/src/quantem/core/ml/models/model_base.py index 503f61bb..60c1c4f6 100644 --- a/src/quantem/core/ml/models/model_base.py +++ b/src/quantem/core/ml/models/model_base.py @@ -6,7 +6,7 @@ class PPLR(ABC): """ - Abstract base class for models that require multi-scale parameter optimization. + Abstract base class for models that require multi-parameter optimization. """ @abstractmethod From a62295c3b6499a95a7ea01125069c6b5710a7d1e Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Mon, 18 May 2026 11:31:16 -0700 Subject: [PATCH 293/335] SO3 Rotations paper citation in SO3params.py --- src/quantem/core/ml/models/so3params.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/quantem/core/ml/models/so3params.py b/src/quantem/core/ml/models/so3params.py index 6569ec93..fd3a0b7e 100644 --- a/src/quantem/core/ml/models/so3params.py +++ b/src/quantem/core/ml/models/so3params.py @@ -113,6 +113,8 @@ class SO3ParamR9SVD(nn.Module): SO(3) rotation bank using R9+SVD parameterization. Each rotation is stored as an unconstrained 3x3 matrix M, projected to SO(3) via SVD+(M) = U diag(1,1,det(UVt)) Vt. + + Based on Rene Geist et al., 2024: https://arxiv.org/abs/2404.11735v1 """ def __init__(self, T: int, init: str = "random"): From e222512ab6abde241ff8a189bca5966d08575df6 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Mon, 18 May 2026 11:32:14 -0700 Subject: [PATCH 294/335] Type-hinting fix in So3params --- src/quantem/core/ml/models/so3params.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/quantem/core/ml/models/so3params.py b/src/quantem/core/ml/models/so3params.py index fd3a0b7e..82a4361a 100644 --- a/src/quantem/core/ml/models/so3params.py +++ b/src/quantem/core/ml/models/so3params.py @@ -1,4 +1,5 @@ import math +from typing import Literal import torch import torch.nn as nn @@ -117,7 +118,7 @@ class SO3ParamR9SVD(nn.Module): Based on Rene Geist et al., 2024: https://arxiv.org/abs/2404.11735v1 """ - def __init__(self, T: int, init: str = "random"): + def __init__(self, T: int, init: Literal["random", "identity"] = "random"): super().__init__() if init == "random": # Initialize near identity with small noise From 91d605631c052616df5f6c1d062d22b47494f22e Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Mon, 18 May 2026 14:42:18 -0700 Subject: [PATCH 295/335] Load parameters added .to(self.device) --- src/quantem/core/utils/tomography_utils.py | 1 - src/quantem/tomography/dataset_models.py | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/quantem/core/utils/tomography_utils.py b/src/quantem/core/utils/tomography_utils.py index 9f5df102..006f8cfc 100644 --- a/src/quantem/core/utils/tomography_utils.py +++ b/src/quantem/core/utils/tomography_utils.py @@ -168,7 +168,6 @@ def fourier_binning(img, crop_size): center = np.array(img.shape) // 2 fft_img = np.fft.fftshift(np.fft.fft2(img)) - cropped_fft = fft_img[ center[0] - crop_size[0] // 2 : center[0] + crop_size[0] // 2, center[1] - crop_size[1] // 2 : center[1] + crop_size[1] // 2, diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index b50c5976..bdd2e66a 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -668,9 +668,9 @@ def load_parameters(self, path: str): Loads the learned parameters from a file. """ data = torch.load(path) - self._z1_params = nn.Parameter(data["z1"]) - self._z3_params = nn.Parameter(data["z3"]) - self._shifts_params = nn.Parameter(data["shifts"]) + self._z1_params = nn.Parameter(data["z1"]).to(self.device) + self._z3_params = nn.Parameter(data["z3"]).to(self.device) + self._shifts_params = nn.Parameter(data["shifts"]).to(self.device) if self.optimizer is not None: self.reconnect_optimizer_to_parameters() From f37ca9f06b12131dcf5c35eda29ed1fe4a35b080 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Mon, 18 May 2026 14:49:31 -0700 Subject: [PATCH 296/335] Volume added to reconstruction context --- src/quantem/tomography/tomography_context.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/quantem/tomography/tomography_context.py b/src/quantem/tomography/tomography_context.py index 2095727e..ef861651 100644 --- a/src/quantem/tomography/tomography_context.py +++ b/src/quantem/tomography/tomography_context.py @@ -16,6 +16,7 @@ class ReconstructionContext(BaseContext): - TensorDecomp reads ".coords" and ".pred" (and ".all densities") """ + volume: Optional[torch.Tensor] = None coords: Optional[torch.Tensor] = None pred: Optional[torch.Tensor] = None all_densities: Optional[torch.Tensor] = None From 8072fa6bc85b3df8871c60eadecff31af3b5c8f6 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Mon, 18 May 2026 14:52:04 -0700 Subject: [PATCH 297/335] Citations for the kplanes models --- src/quantem/core/ml/models/kplanes.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/quantem/core/ml/models/kplanes.py b/src/quantem/core/ml/models/kplanes.py index fe36dfcc..cdf55261 100644 --- a/src/quantem/core/ml/models/kplanes.py +++ b/src/quantem/core/ml/models/kplanes.py @@ -166,6 +166,9 @@ def interpolate_ms_features( class KPlanes(PPLR, TensorDecompositionModel): + """ + K-Planes model adapted from Fridovich-Keil et al., https://arxiv.org/abs/2301.10241 + """ def __init__( self, # Grid parameters @@ -354,7 +357,7 @@ def interpolate_ms_features_tilted( class KPlanesTILTED(KPlanes): """ - K-Planes with T learned SO(3) rotations (TILTED). + K-Planes with T learned SO(3) rotations (TILTED). Adapted from Yi et al., https://arxiv.org/abs/2308.15461 Inherits KPlanes for the sigma_net, density_activation, and get_params interface. Overrides: @@ -646,7 +649,7 @@ def interpolate_ms_features_cp_tilted( class CPTilted(PPLR, TensorDecompositionModel): """ CP decomposition with TILTED rotations — the true bottleneck model for - phase 1. Rank-1-per-channel feature representation. + phase 1. Rank-1-per-channel feature representation. Adapted from Yi et al., https://arxiv.org/abs/2308.15461 Shares the SO3Param and sigma_net design with KPlanesTILTED so you can lift τ directly across: cp_model.extract_tau_state() -> From eafca0c791e3fb07fe3cb003479b3bcb8ff29c74 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Mon, 18 May 2026 14:54:15 -0700 Subject: [PATCH 298/335] Explanation in object_models.py for different types of tv loss. --- src/quantem/tomography/object_models.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index a094fae8..fc2669b7 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -932,6 +932,12 @@ def apply_soft_constraints(self, ctx: ReconstructionContext) -> torch.Tensor: # TV Losses def get_tv_loss(self, ctx: ReconstructionContext) -> torch.Tensor: + """ + Gets the summed total variational loss for the tensor decomposition model. + + _get_plane_tv_loss: Total-variation across the planes. + _get_volume_tv_loss: Isotropic volume TV + """ assert ctx.coords is not None, "Coordinates must be provided for TV loss" assert ctx.pred is not None, "Prediction must be provided for TV loss" tv_loss = torch.tensor(0.0, device=ctx.pred.device) @@ -940,6 +946,9 @@ def get_tv_loss(self, ctx: ReconstructionContext) -> torch.Tensor: return tv_loss def _get_plane_tv_loss(self) -> torch.Tensor: + """ + Gets the total-variation across the planes. + """ is_tilted = self.model.tilted per_level = [] From e4c8dbe21f600554d5450b89c20000773d6e3f58 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Mon, 18 May 2026 14:58:07 -0700 Subject: [PATCH 299/335] Refactor type hinting in _unwrap function to use cast for improved type safety --- src/quantem/tomography/object_models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index fc2669b7..8fa19db7 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -1,7 +1,7 @@ from abc import abstractmethod from copy import deepcopy from dataclasses import dataclass -from typing import Any, Callable, Generator, Optional +from typing import Any, Callable, Generator, Optional, cast import numpy as np import torch @@ -200,8 +200,8 @@ def parse_dict( def _unwrap(model: nn.Module | nn.parallel.DistributedDataParallel) -> PlanarDecompositionModel: """Unwrap a DistributedDataParallel model to get the underlying module ONLY for tensor decomposition models.""" if isinstance(model, nn.parallel.DistributedDataParallel): - return model.module - return model + return cast(PlanarDecompositionModel, model.module) + return cast(PlanarDecompositionModel, model) class ObjectBase(AutoSerialize, nn.Module, RNGMixin, OptimizerMixin): From 136a65f059c5208947e92e2ffd92366a1ce68df1 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Mon, 18 May 2026 15:03:07 -0700 Subject: [PATCH 300/335] Some type-hinting fix for Contexts --- src/quantem/core/ml/constraints.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/quantem/core/ml/constraints.py b/src/quantem/core/ml/constraints.py index 83270400..fea1d309 100644 --- a/src/quantem/core/ml/constraints.py +++ b/src/quantem/core/ml/constraints.py @@ -1,12 +1,13 @@ from abc import ABC, abstractmethod from copy import deepcopy from dataclasses import dataclass -from typing import Any, Self +from typing import Any, Generic, Self, TypeVar import numpy as np import torch from numpy.typing import NDArray +T_ctx = TypeVar("T_ctx", bound=BaseContext) @dataclass class BaseContext(ABC): @@ -54,7 +55,7 @@ def __str__(self) -> str: ) -class BaseConstraints(ABC): +class BaseConstraints(ABC, Generic[T_ctx]): """ Base class for constraints. """ @@ -100,7 +101,7 @@ def apply_hard_constraints(self, pred: torch.Tensor) -> torch.Tensor: raise NotImplementedError @abstractmethod - def apply_soft_constraints(self, ctx: BaseContext) -> torch.Tensor: + def apply_soft_constraints(self, ctx: T_ctx) -> torch.Tensor: """ Apply soft constraints to the model. """ From 98ddd0d34e9ed51f63003131807f42f6843d7a16 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 18 May 2026 22:36:44 -0700 Subject: [PATCH 301/335] dataset4d, dataset4dstem hold torch array --- src/quantem/core/datastructures/dataset.py | 104 +++++++++++++----- src/quantem/core/datastructures/dataset4d.py | 33 ++---- .../core/datastructures/dataset4dstem.py | 52 ++++++++- widget/src/quantem/widget/show4dstem.py | 11 +- 4 files changed, 141 insertions(+), 59 deletions(-) diff --git a/src/quantem/core/datastructures/dataset.py b/src/quantem/core/datastructures/dataset.py index 4d2ab9e1..786b3d5d 100644 --- a/src/quantem/core/datastructures/dataset.py +++ b/src/quantem/core/datastructures/dataset.py @@ -4,6 +4,7 @@ from typing import Any, Literal, Optional, Self, Union, overload import numpy as np +import torch from numpy.typing import DTypeLike, NDArray from quantem.core.io.serialize import AutoSerialize @@ -38,24 +39,39 @@ class Dataset(AutoSerialize): def __init__( self, - array: Any, # Input can be array-like - name: str, - origin: NDArray | tuple | list | float | int, - sampling: NDArray | tuple | list | float | int, - units: list[str] | tuple | list, + array: NDArray | None = None, + tensor: torch.Tensor | None = None, + name: str = "", + origin: NDArray | tuple | list | float | int | None = None, + sampling: NDArray | tuple | list | float | int | None = None, + units: list[str] | tuple | list | None = None, signal_units: str = "arb. units", metadata: Optional[dict] = None, _token: object | None = None, ): if _token is not self._token: - raise RuntimeError("Use Dataset.from_array() to instantiate this class.") - super().__init__() - arr = ensure_valid_array(array) - if not isinstance(arr, np.ndarray): - raise TypeError( - "Dataset requires a NumPy array (CuPy is not supported on this branch)." + raise RuntimeError( + "Use Dataset.from_array() or Dataset.from_tensor() to instantiate this class." ) - self._array = arr + super().__init__() + # Dual-slot storage: exactly one of (_array, _tensor) is set. + if array is None and tensor is None: + raise ValueError("Provide either `array` (numpy) or `tensor` (torch).") + if array is not None and tensor is not None: + raise ValueError("Provide only one of `array` or `tensor`, not both.") + if array is not None: + arr = ensure_valid_array(array) + if not isinstance(arr, np.ndarray): + raise TypeError(f"Dataset.array must be numpy.ndarray, got {type(arr).__name__}.") + self._array = arr + self._tensor = None + else: + if not isinstance(tensor, torch.Tensor): + raise TypeError(f"Dataset.tensor must be torch.Tensor, got {type(tensor).__name__}.") + self._array = None + self._tensor = tensor + # Lazy cache: derived numpy from tensor, materialized only on first .array access. + self._cached_numpy: np.ndarray | None = None self.name = name self.origin = origin self.sampling = sampling @@ -123,18 +139,34 @@ def from_array( # --- Properties --- @property def array(self) -> NDArray: - """The underlying n-dimensional NumPy array data.""" - return self._array + """The data as a numpy array. + + For tensor-backed datasets, returns a CACHED read-only CPU copy derived + from ``self.tensor`` (first access pays GPU->CPU transfer, subsequent + accesses are free). Torch-aware consumers should prefer ``.tensor``. + """ + if self._array is not None: + return self._array + if self._cached_numpy is None: + self._cached_numpy = self._tensor.detach().cpu().numpy() + self._cached_numpy.flags.writeable = False + return self._cached_numpy @array.setter def array(self, value: NDArray) -> None: - arr = ensure_valid_array(value, ndim=self.ndim) # want to allow changing dtype + arr = ensure_valid_array(value, ndim=self.ndim) if not isinstance(arr, np.ndarray): - raise TypeError( - "Dataset requires a NumPy array (CuPy is not supported on this branch)." - ) + raise TypeError(f"Dataset.array must be numpy.ndarray, got {type(arr).__name__}.") self._array = arr - # self._array = ensure_valid_array(value, dtype=self.dtype, ndim=self.ndim) + + @property + def tensor(self) -> torch.Tensor: + """Torch tensor backing the data. AttributeError if numpy-backed.""" + if self._tensor is None: + raise AttributeError( + f"Dataset '{self.name}' is numpy-backed; use Dataset.from_tensor() at construction." + ) + return self._tensor @property def metadata(self) -> dict: @@ -191,26 +223,42 @@ def file_path(self, value: os.PathLike | str | None) -> None: # --- Derived Properties --- @property def shape(self) -> tuple[int, ...]: - return self.array.shape + # Direct slot access — never triggers .array derive (which would force + # a full GPU->CPU copy on tensor-backed datasets). + return tuple((self._array if self._array is not None else self._tensor).shape) @property def ndim(self) -> int: - return self.array.ndim + return (self._array if self._array is not None else self._tensor).ndim @property def dtype(self) -> DTypeLike: - return self.array.dtype + return (self._array if self._array is not None else self._tensor).dtype @property def device(self) -> str: - """ - Outputting a string is likely temporary -- once we have our use cases we can - figure out a more permanent device solution that enables easier translation between - numpy <-> torch <-> numpy, etc. + """``"cpu"`` for numpy-backed; torch device string for tensor-backed.""" + if self._tensor is not None: + return str(self._tensor.device) + return "cpu" + + def numpy(self) -> NDArray: + """Return the data as a numpy array (mirrors ``torch.Tensor.numpy()``). - For NumPy-only datasets, this is always "cpu". + Equivalent to ``self.array`` — both return numpy. For tensor-backed + datasets, first call materializes a cached read-only CPU copy. """ - return "cpu" + return self.array + + def to(self, device) -> Self: + """Move the underlying tensor to ``device``. Raises if numpy-backed.""" + if self._tensor is None: + raise AttributeError( + f"Cannot .to({device!r}) on numpy-backed Dataset '{self.name}'." + ) + self._tensor = self._tensor.to(device) + self._cached_numpy = None # invalidate stale derived numpy + return self # --- Summaries --- def __repr__(self) -> str: diff --git a/src/quantem/core/datastructures/dataset4d.py b/src/quantem/core/datastructures/dataset4d.py index 8e5bdfa0..80d219ea 100644 --- a/src/quantem/core/datastructures/dataset4d.py +++ b/src/quantem/core/datastructures/dataset4d.py @@ -1,6 +1,7 @@ from typing import Any, Self, Union import numpy as np +import torch from numpy.typing import NDArray from quantem.core.datastructures.dataset import Dataset @@ -21,36 +22,20 @@ class Dataset4d(Dataset): def __init__( self, - array: NDArray | Any, - name: str, - origin: NDArray | tuple | list | float | int, - sampling: NDArray | tuple | list | float | int, - units: list[str] | tuple | list, + array: NDArray | None = None, + tensor: torch.Tensor | None = None, + name: str = "", + origin: NDArray | tuple | list | float | int | None = None, + sampling: NDArray | tuple | list | float | int | None = None, + units: list[str] | tuple | list | None = None, signal_units: str = "arb. units", metadata: dict = {}, _token: object | None = None, ): - """Initialize a 4D dataset. - - Parameters - ---------- - array : NDArray | Any - The underlying 3D array data - name : str - A descriptive name for the dataset - origin : NDArray | tuple | list | float | int - The origin coordinates for each dimension in calibrated units - sampling : NDArray | tuple | list | float | int - The sampling rate/spacing for each dimension - units : list[str] | tuple | list - Units for each dimension - signal_units : str, optional - Units for the array values, by default "arb. units" - _token : object | None, optional - Token to prevent direct instantiation, by default None - """ + """Initialize a 4D dataset. Pass exactly one of ``array`` (numpy) or ``tensor`` (torch).""" super().__init__( array=array, + tensor=tensor, name=name, origin=origin, sampling=sampling, diff --git a/src/quantem/core/datastructures/dataset4dstem.py b/src/quantem/core/datastructures/dataset4dstem.py index 79cbc479..60890475 100644 --- a/src/quantem/core/datastructures/dataset4dstem.py +++ b/src/quantem/core/datastructures/dataset4dstem.py @@ -2,6 +2,7 @@ import matplotlib.pyplot as plt import numpy as np +import torch from matplotlib.patches import Circle, Wedge from numpy.typing import NDArray @@ -41,11 +42,12 @@ class Dataset4dstem(Dataset4d): def __init__( self, - array: NDArray | Any, - name: str, - origin: NDArray | tuple | list | float | int, - sampling: NDArray | tuple | list | float | int, - units: list[str] | tuple | list, + array: NDArray | None = None, + tensor: torch.Tensor | None = None, + name: str = "", + origin: NDArray | tuple | list | float | int | None = None, + sampling: NDArray | tuple | list | float | int | None = None, + units: list[str] | tuple | list | None = None, signal_units: str = "arb. units", metadata: dict = {}, _token: object | None = None, @@ -79,6 +81,7 @@ def __init__( super().__init__( array=array, + tensor=tensor, name=name, origin=origin, sampling=sampling, @@ -157,6 +160,45 @@ def from_array( _token=cls._token, ) + @classmethod + def from_tensor( + cls, + tensor: torch.Tensor, + name: str | None = None, + origin: NDArray | tuple | list | float | int | None = None, + sampling: NDArray | tuple | list | float | int | None = None, + units: list[str] | tuple | list | None = None, + signal_units: str = "arb. units", + metadata: dict | None = None, + ) -> Self: + """Create a Dataset4dstem from a torch tensor (any device). + + Use this when raw data is GPU-resident (CUDA pipelines, live detector + frames, GPU file readers) to skip the VRAM<->RAM round-trip. + + For cupy / jax arrays, wrap with ``torch.from_dlpack(arr)`` first. + """ + if not isinstance(tensor, torch.Tensor): + raise TypeError( + f"from_tensor requires torch.Tensor, got {type(tensor).__name__}. " + f"For cupy / jax, wrap with `torch.from_dlpack(arr)` first." + ) + if tensor.ndim != 4: + raise ValueError( + f"Dataset4dstem.from_tensor requires a 4D tensor " + f"(scan_y, scan_x, dp_y, dp_x), got shape {tuple(tensor.shape)}." + ) + return cls( + tensor=tensor, + name=name if name is not None else "4D-STEM dataset (torch)", + origin=origin if origin is not None else np.zeros(4), + sampling=sampling if sampling is not None else np.ones(4), + units=units if units is not None else ["pixels"] * 4, + signal_units=signal_units, + metadata=metadata if metadata is not None else {}, + _token=cls._token, + ) + @property def virtual_images(self) -> dict[str, Dataset2d]: """ diff --git a/widget/src/quantem/widget/show4dstem.py b/widget/src/quantem/widget/show4dstem.py index 0a6b256b..06f8b629 100644 --- a/widget/src/quantem/widget/show4dstem.py +++ b/widget/src/quantem/widget/show4dstem.py @@ -334,14 +334,21 @@ def __init__( _io_labels = None # Auto-extract sampling + units from Dataset4dstem if available. - if hasattr(data, "sampling") and hasattr(data, "array"): + # NOTE: avoid `hasattr(data, "array")` — for tensor-backed Datasets the + # `.array` getter is an expensive derive (full GPU->CPU copy). Use cheap + # `hasattr(data, "sampling")` to identify a Dataset. + if hasattr(data, "sampling"): if not title and hasattr(data, "name") and data.name: title = str(data.name) if sampling is None: sampling = tuple(float(s) for s in data.sampling) if units is None and hasattr(data, "units"): units = list(data.units) - data = data.array + # If tensor-backed (zero-copy GPU path), take .tensor. Else .array (numpy). + if getattr(data, "_tensor", None) is not None: + data = data.tensor + else: + data = data.array # Resolve sampling + units (4 axes for 4D-STEM): # [scan_row, scan_col, k_row, k_col]. Scalar/None broadcast to (1, 1, 1, 1). From d2dc32b390307f409ac8a8ba45b403a0aab58b01 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 18 May 2026 22:39:56 -0700 Subject: [PATCH 302/335] bring original docstring back --- src/quantem/core/datastructures/dataset.py | 2 +- src/quantem/core/datastructures/dataset4d.py | 22 ++++++++++++++++++- .../core/datastructures/dataset4dstem.py | 3 +++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/quantem/core/datastructures/dataset.py b/src/quantem/core/datastructures/dataset.py index 786b3d5d..5a89b8ad 100644 --- a/src/quantem/core/datastructures/dataset.py +++ b/src/quantem/core/datastructures/dataset.py @@ -139,7 +139,7 @@ def from_array( # --- Properties --- @property def array(self) -> NDArray: - """The data as a numpy array. + """The underlying n-dimensional NumPy array data. For tensor-backed datasets, returns a CACHED read-only CPU copy derived from ``self.tensor`` (first access pays GPU->CPU transfer, subsequent diff --git a/src/quantem/core/datastructures/dataset4d.py b/src/quantem/core/datastructures/dataset4d.py index 80d219ea..f5681730 100644 --- a/src/quantem/core/datastructures/dataset4d.py +++ b/src/quantem/core/datastructures/dataset4d.py @@ -32,7 +32,27 @@ def __init__( metadata: dict = {}, _token: object | None = None, ): - """Initialize a 4D dataset. Pass exactly one of ``array`` (numpy) or ``tensor`` (torch).""" + """Initialize a 4D dataset. + + Parameters + ---------- + array : NDArray | None + The underlying 4D numpy array. Provide exactly one of ``array`` or ``tensor``. + tensor : torch.Tensor | None + The underlying 4D torch tensor (any device). Provide exactly one of ``array`` or ``tensor``. + name : str + A descriptive name for the dataset + origin : NDArray | tuple | list | float | int + The origin coordinates for each dimension in calibrated units + sampling : NDArray | tuple | list | float | int + The sampling rate/spacing for each dimension + units : list[str] | tuple | list + Units for each dimension + signal_units : str, optional + Units for the array values, by default "arb. units" + _token : object | None, optional + Token to prevent direct instantiation, by default None + """ super().__init__( array=array, tensor=tensor, diff --git a/src/quantem/core/datastructures/dataset4dstem.py b/src/quantem/core/datastructures/dataset4dstem.py index 60890475..c0c331b5 100644 --- a/src/quantem/core/datastructures/dataset4dstem.py +++ b/src/quantem/core/datastructures/dataset4dstem.py @@ -58,6 +58,9 @@ def __init__( ---------- array : NDArray | Any The underlying 4D array data + tensor : torch.Tensor | None, optional + Alternative to ``array``: the underlying 4D torch tensor (any device). + Provide exactly one of ``array`` or ``tensor``. name : str A descriptive name for the dataset origin : NDArray | tuple | list | float | int From 6b7319d7996553912b7b8ce485edb16c17257183 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 18 May 2026 22:42:43 -0700 Subject: [PATCH 303/335] remove need for cached numpy array --- src/quantem/core/datastructures/dataset.py | 25 ++++++++-------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/quantem/core/datastructures/dataset.py b/src/quantem/core/datastructures/dataset.py index 5a89b8ad..3263720a 100644 --- a/src/quantem/core/datastructures/dataset.py +++ b/src/quantem/core/datastructures/dataset.py @@ -70,8 +70,6 @@ def __init__( raise TypeError(f"Dataset.tensor must be torch.Tensor, got {type(tensor).__name__}.") self._array = None self._tensor = tensor - # Lazy cache: derived numpy from tensor, materialized only on first .array access. - self._cached_numpy: np.ndarray | None = None self.name = name self.origin = origin self.sampling = sampling @@ -138,19 +136,13 @@ def from_array( # --- Properties --- @property - def array(self) -> NDArray: + def array(self) -> NDArray | None: """The underlying n-dimensional NumPy array data. - For tensor-backed datasets, returns a CACHED read-only CPU copy derived - from ``self.tensor`` (first access pays GPU->CPU transfer, subsequent - accesses are free). Torch-aware consumers should prefer ``.tensor``. + Returns ``None`` for tensor-backed datasets — use ``.tensor`` for the + torch tensor, or ``.numpy()`` to materialize a numpy copy explicitly. """ - if self._array is not None: - return self._array - if self._cached_numpy is None: - self._cached_numpy = self._tensor.detach().cpu().numpy() - self._cached_numpy.flags.writeable = False - return self._cached_numpy + return self._array @array.setter def array(self, value: NDArray) -> None: @@ -245,10 +237,12 @@ def device(self) -> str: def numpy(self) -> NDArray: """Return the data as a numpy array (mirrors ``torch.Tensor.numpy()``). - Equivalent to ``self.array`` — both return numpy. For tensor-backed - datasets, first call materializes a cached read-only CPU copy. + For numpy-backed datasets, returns ``self.array`` directly. For + tensor-backed datasets, materializes a CPU copy via ``.detach().cpu().numpy()``. """ - return self.array + if self._array is not None: + return self._array + return self._tensor.detach().cpu().numpy() def to(self, device) -> Self: """Move the underlying tensor to ``device``. Raises if numpy-backed.""" @@ -257,7 +251,6 @@ def to(self, device) -> Self: f"Cannot .to({device!r}) on numpy-backed Dataset '{self.name}'." ) self._tensor = self._tensor.to(device) - self._cached_numpy = None # invalidate stale derived numpy return self # --- Summaries --- From 7f9913f61fcc9f0bafee2f79a5366b1d1358d6f4 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 18 May 2026 22:44:38 -0700 Subject: [PATCH 304/335] further cleaup api docstring --- src/quantem/core/datastructures/dataset.py | 4 ++-- widget/src/quantem/widget/show4dstem.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/quantem/core/datastructures/dataset.py b/src/quantem/core/datastructures/dataset.py index 3263720a..a89257a3 100644 --- a/src/quantem/core/datastructures/dataset.py +++ b/src/quantem/core/datastructures/dataset.py @@ -139,7 +139,7 @@ def from_array( def array(self) -> NDArray | None: """The underlying n-dimensional NumPy array data. - Returns ``None`` for tensor-backed datasets — use ``.tensor`` for the + Returns ``None`` for tensor-backed datasets. Use ``.tensor`` for the torch tensor, or ``.numpy()`` to materialize a numpy copy explicitly. """ return self._array @@ -215,7 +215,7 @@ def file_path(self, value: os.PathLike | str | None) -> None: # --- Derived Properties --- @property def shape(self) -> tuple[int, ...]: - # Direct slot access — never triggers .array derive (which would force + # Direct slot access (never triggers .array derive, which would force # a full GPU->CPU copy on tensor-backed datasets). return tuple((self._array if self._array is not None else self._tensor).shape) diff --git a/widget/src/quantem/widget/show4dstem.py b/widget/src/quantem/widget/show4dstem.py index 06f8b629..99a5eae8 100644 --- a/widget/src/quantem/widget/show4dstem.py +++ b/widget/src/quantem/widget/show4dstem.py @@ -334,9 +334,9 @@ def __init__( _io_labels = None # Auto-extract sampling + units from Dataset4dstem if available. - # NOTE: avoid `hasattr(data, "array")` — for tensor-backed Datasets the - # `.array` getter is an expensive derive (full GPU->CPU copy). Use cheap - # `hasattr(data, "sampling")` to identify a Dataset. + # NOTE: avoid `hasattr(data, "array")` because for tensor-backed Datasets + # the `.array` getter is an expensive derive (full GPU->CPU copy). Use + # cheap `hasattr(data, "sampling")` to identify a Dataset. if hasattr(data, "sampling"): if not title and hasattr(data, "name") and data.name: title = str(data.name) From 9c93ae99d7ad483c773528c20e5c358ee9a49f55 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 18 May 2026 22:49:44 -0700 Subject: [PATCH 305/335] use _array _tensor duck typing for show4dstem --- widget/src/quantem/widget/show4dstem.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/widget/src/quantem/widget/show4dstem.py b/widget/src/quantem/widget/show4dstem.py index 99a5eae8..c3ee61a7 100644 --- a/widget/src/quantem/widget/show4dstem.py +++ b/widget/src/quantem/widget/show4dstem.py @@ -333,22 +333,18 @@ def __init__( _io_labels = None - # Auto-extract sampling + units from Dataset4dstem if available. - # NOTE: avoid `hasattr(data, "array")` because for tensor-backed Datasets - # the `.array` getter is an expensive derive (full GPU->CPU copy). Use - # cheap `hasattr(data, "sampling")` to identify a Dataset. - if hasattr(data, "sampling"): - if not title and hasattr(data, "name") and data.name: + # Extract underlying array / tensor + auto-calibrate from Dataset input + # (duck-typed via the dual-slot private attributes _tensor / _array). + tensor = getattr(data, "_tensor", None) + array = getattr(data, "_array", None) + if tensor is not None or array is not None: + if not title and getattr(data, "name", ""): title = str(data.name) if sampling is None: sampling = tuple(float(s) for s in data.sampling) - if units is None and hasattr(data, "units"): + if units is None: units = list(data.units) - # If tensor-backed (zero-copy GPU path), take .tensor. Else .array (numpy). - if getattr(data, "_tensor", None) is not None: - data = data.tensor - else: - data = data.array + data = tensor if tensor is not None else array # Resolve sampling + units (4 axes for 4D-STEM): # [scan_row, scan_col, k_row, k_col]. Scalar/None broadcast to (1, 1, 1, 1). From 40878d3d5f13f90ddabe4a4947b1b74550337085 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 18 May 2026 23:00:28 -0700 Subject: [PATCH 306/335] use row, col convention in docstring --- src/quantem/core/datastructures/dataset4dstem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quantem/core/datastructures/dataset4dstem.py b/src/quantem/core/datastructures/dataset4dstem.py index c0c331b5..48d3f737 100644 --- a/src/quantem/core/datastructures/dataset4dstem.py +++ b/src/quantem/core/datastructures/dataset4dstem.py @@ -189,7 +189,7 @@ def from_tensor( if tensor.ndim != 4: raise ValueError( f"Dataset4dstem.from_tensor requires a 4D tensor " - f"(scan_y, scan_x, dp_y, dp_x), got shape {tuple(tensor.shape)}." + f"(scan_row, scan_col, dp_row, dp_col), got shape {tuple(tensor.shape)}." ) return cls( tensor=tensor, From 21b684fa7e71f843626a27ad1602adb193494cfc Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 22 May 2026 23:29:30 -0700 Subject: [PATCH 307/335] fix: tolerate missing _tensor slot on autoserialize-loaded datasets --- src/quantem/core/datastructures/dataset.py | 35 ++++++++++++++-------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/quantem/core/datastructures/dataset.py b/src/quantem/core/datastructures/dataset.py index a89257a3..fa1fcef2 100644 --- a/src/quantem/core/datastructures/dataset.py +++ b/src/quantem/core/datastructures/dataset.py @@ -142,7 +142,7 @@ def array(self) -> NDArray | None: Returns ``None`` for tensor-backed datasets. Use ``.tensor`` for the torch tensor, or ``.numpy()`` to materialize a numpy copy explicitly. """ - return self._array + return getattr(self, "_array", None) @array.setter def array(self, value: NDArray) -> None: @@ -154,11 +154,13 @@ def array(self, value: NDArray) -> None: @property def tensor(self) -> torch.Tensor: """Torch tensor backing the data. AttributeError if numpy-backed.""" - if self._tensor is None: + # getattr handles AutoSerialize-restored instances (no __init__ run). + tensor = getattr(self, "_tensor", None) + if tensor is None: raise AttributeError( f"Dataset '{self.name}' is numpy-backed; use Dataset.from_tensor() at construction." ) - return self._tensor + return tensor @property def metadata(self) -> dict: @@ -216,22 +218,27 @@ def file_path(self, value: os.PathLike | str | None) -> None: @property def shape(self) -> tuple[int, ...]: # Direct slot access (never triggers .array derive, which would force - # a full GPU->CPU copy on tensor-backed datasets). - return tuple((self._array if self._array is not None else self._tensor).shape) + # a full GPU->CPU copy on tensor-backed datasets). getattr handles + # AutoSerialize-restored instances (no __init__ run). + array = getattr(self, "_array", None) + return tuple((array if array is not None else self._tensor).shape) @property def ndim(self) -> int: - return (self._array if self._array is not None else self._tensor).ndim + array = getattr(self, "_array", None) + return (array if array is not None else self._tensor).ndim @property def dtype(self) -> DTypeLike: - return (self._array if self._array is not None else self._tensor).dtype + array = getattr(self, "_array", None) + return (array if array is not None else self._tensor).dtype @property def device(self) -> str: """``"cpu"`` for numpy-backed; torch device string for tensor-backed.""" - if self._tensor is not None: - return str(self._tensor.device) + tensor = getattr(self, "_tensor", None) + if tensor is not None: + return str(tensor.device) return "cpu" def numpy(self) -> NDArray: @@ -240,17 +247,19 @@ def numpy(self) -> NDArray: For numpy-backed datasets, returns ``self.array`` directly. For tensor-backed datasets, materializes a CPU copy via ``.detach().cpu().numpy()``. """ - if self._array is not None: - return self._array + array = getattr(self, "_array", None) + if array is not None: + return array return self._tensor.detach().cpu().numpy() def to(self, device) -> Self: """Move the underlying tensor to ``device``. Raises if numpy-backed.""" - if self._tensor is None: + tensor = getattr(self, "_tensor", None) + if tensor is None: raise AttributeError( f"Cannot .to({device!r}) on numpy-backed Dataset '{self.name}'." ) - self._tensor = self._tensor.to(device) + self._tensor = tensor.to(device) return self # --- Summaries --- From 4be1be7efb654b8d352c92c5fd55e7b5b7c66af1 Mon Sep 17 00:00:00 2001 From: quantem-bot Date: Mon, 25 May 2026 12:36:55 +0000 Subject: [PATCH 308/335] chore: update lock file --- uv.lock | 425 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 218 insertions(+), 207 deletions(-) diff --git a/uv.lock b/uv.lock index a01182cc..716284e6 100644 --- a/uv.lock +++ b/uv.lock @@ -196,11 +196,11 @@ css = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" }, + { url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" }, ] [[package]] @@ -373,14 +373,14 @@ wheels = [ [[package]] name = "click" -version = "8.4.0" +version = "8.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/23/e4/796662cd90cf80e3a363c99db2b88e0e394b988a575f60a17e16440cd011/click-8.4.0.tar.gz", hash = "sha256:638f1338fe1235c8f4e008e4a8a254fb5c5fbdcbb40ece3c9142ebb78e792973", size = 350843, upload-time = "2026-05-17T00:47:58.425Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9b/98/518d8e5081007684232226f475082b30087d0f585e8457db087298259f49/click-8.4.1.tar.gz", hash = "sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96", size = 353007, upload-time = "2026-05-22T04:08:37.769Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/ae/8e92f8058baf87f6c7d86ee7e457668690195cc77efedb8d3797a06e3940/click-8.4.0-py3-none-any.whl", hash = "sha256:40c50b7c6c6adac2823d411041ec84f3f103f1b280d5e9ce0d7f998995832f81", size = 116147, upload-time = "2026-05-17T00:47:56.842Z" }, + { url = "https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl", hash = "sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2", size = 116639, upload-time = "2026-05-22T04:08:35.26Z" }, ] [[package]] @@ -958,49 +958,65 @@ wheels = [ [[package]] name = "greenlet" -version = "3.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3c/3f/dbf99fb14bfeb88c28f16729215478c0e265cacd6dc22270c8f31bb6892f/greenlet-3.5.0.tar.gz", hash = "sha256:d419647372241bc68e957bf38d5c1f98852155e4146bd1e4121adea81f4f01e4", size = 196995, upload-time = "2026-04-27T13:37:15.544Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/0f/a91f143f356523ff682309732b175765a9bc2836fd7c081c2c67fedc1ad4/greenlet-3.5.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:8f1cc966c126639cd152fdaa52624d2655f492faa79e013fea161de3e6dda082", size = 284726, upload-time = "2026-04-27T12:20:51.402Z" }, - { url = "https://files.pythonhosted.org/packages/95/82/800646c7ffc5dbabd75ddd2f6b519bb898c0c9c969e5d0473bfe5d20bcce/greenlet-3.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:362624e6a8e5bca3b8233e45eef33903a100e9539a2b995c364d595dbc4018b3", size = 604264, upload-time = "2026-04-27T12:52:39.494Z" }, - { url = "https://files.pythonhosted.org/packages/ca/ac/354867c0bba812fc33b15bc55aedafedd0aee3c7dd91dfca22444157dc0c/greenlet-3.5.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5ecd83806b0f4c2f53b1018e0005cd82269ea01d42befc0368730028d850ed1c", size = 616099, upload-time = "2026-04-27T12:59:39.623Z" }, - { url = "https://files.pythonhosted.org/packages/ff/b0/815bece7399e01cadb69014219eebd0042339875c59a59b0820a46ece356/greenlet-3.5.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ff251e9a0279522e62f6176412869395a64ddf2b5c5f782ff609a8216a4e662", size = 615198, upload-time = "2026-04-27T12:25:25.928Z" }, - { url = "https://files.pythonhosted.org/packages/10/80/3b2c0a895d6698f6ddb31b07942ebfa982f3e30888bc5546a5b5990de8b2/greenlet-3.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d874e79afd41a96e11ff4c5d0bc90a80973e476fda1c2c64985667397df432b", size = 1574927, upload-time = "2026-04-27T12:53:25.81Z" }, - { url = "https://files.pythonhosted.org/packages/44/0e/f354af514a4c61454dbc68e44d47544a5a4d6317e30b77ddfa3a09f4c5f3/greenlet-3.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0ed006e4b86c59de7467eb2601cd1b77b5a7d657d1ee55e30fe30d76451edba4", size = 1642683, upload-time = "2026-04-27T12:25:23.9Z" }, - { url = "https://files.pythonhosted.org/packages/fa/6a/87f38255201e993a1915265ebb80cd7c2c78b04a45744995abbf6b259fd8/greenlet-3.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:703cb211b820dbffbbc55a16bfc6e4583a6e6e990f33a119d2cc8b83211119c8", size = 238115, upload-time = "2026-04-27T12:21:48.845Z" }, - { url = "https://files.pythonhosted.org/packages/e3/f8/450fe3c5938fa737ea4d22699772e6e34e8e24431a47bf4e8a1ceed4a98e/greenlet-3.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:6c18dfb59c70f5a94acd271c72e90128c3c776e41e5f07767908c8c1b74ad339", size = 235017, upload-time = "2026-04-27T12:22:26.768Z" }, - { url = "https://files.pythonhosted.org/packages/ef/32/f2ce6d4cac3e55bc6173f92dbe627e782e1850f89d986c3606feb63aafa7/greenlet-3.5.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:db2910d3c809444e0a20147361f343fe2798e106af8d9d8506f5305302655a9f", size = 286228, upload-time = "2026-04-27T12:20:34.421Z" }, - { url = "https://files.pythonhosted.org/packages/b7/aa/caed9e5adf742315fc7be2a84196373aab4816e540e38ba0d76cb7584d68/greenlet-3.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ec9ea74e7268ace7f9aab1b1a4e730193fc661b39a993cd91c606c32d4a3628", size = 601775, upload-time = "2026-04-27T12:52:41.045Z" }, - { url = "https://files.pythonhosted.org/packages/c7/af/90ae08497400a941595d12774447f752d3dfe0fbb012e35b76bc5c0ff37e/greenlet-3.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54d243512da35485fc7a6bf3c178fdda6327a9d6506fcdd62b1abd1e41b2927b", size = 614436, upload-time = "2026-04-27T12:59:41.595Z" }, - { url = "https://files.pythonhosted.org/packages/2b/e0/2e13df68f367e2f9960616927d60857dd7e56aaadd59a47c644216b2f920/greenlet-3.5.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d280a7f5c331622c69f97eb167f33577ff2d1df282c41cd15907fc0a3ca198c", size = 611388, upload-time = "2026-04-27T12:25:28.008Z" }, - { url = "https://files.pythonhosted.org/packages/82/f7/393c64055132ac0d488ef6be549253b7e6274194863967ddc0bc8f5b87b8/greenlet-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1eb67d5adefb5bd2e182d42678a328979a209e4e82eb93575708185d31d1f588", size = 1570768, upload-time = "2026-04-27T12:53:28.099Z" }, - { url = "https://files.pythonhosted.org/packages/b8/4b/eaf7735253522cf56d1b74d672a58f54fc114702ceaf05def59aae72f6e1/greenlet-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2628d6c86f6cb0cb45e0c3c54058bbec559f57eaae699447748cb3928150577e", size = 1635983, upload-time = "2026-04-27T12:25:26.903Z" }, - { url = "https://files.pythonhosted.org/packages/4c/fe/4fb3a0805bd5165da5ebf858da7cc01cce8061674106d2cf5bdab32cbfde/greenlet-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:d4d9f0624c775f2dfc56ba54d515a8c771044346852a918b405914f6b19d7fd8", size = 238840, upload-time = "2026-04-27T12:23:54.806Z" }, - { url = "https://files.pythonhosted.org/packages/cb/cb/baa584cb00532126ffe12d9787db0a60c5a4f55c27bfe2666df5d4c30a32/greenlet-3.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:83ed9f27f1680b50e89f40f6df348a290ea234b249a4003d366663a12eab94f2", size = 235615, upload-time = "2026-04-27T12:21:38.57Z" }, - { url = "https://files.pythonhosted.org/packages/0c/58/fc576f99037ce19c5aa16628e4c3226b6d1419f72a62c79f5f40576e6eb3/greenlet-3.5.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:5a5ed18de6a0f6cc7087f1563f6bd93fc7df1c19165ca01e9bde5a5dc281d106", size = 285066, upload-time = "2026-04-27T12:23:05.033Z" }, - { url = "https://files.pythonhosted.org/packages/4a/ba/b28ddbe6bfad6a8ac196ef0e8cff37bc65b79735995b9e410923fffeeb70/greenlet-3.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a717fbc46d8a354fa675f7c1e813485b6ba3885f9bef0cd56e5ba27d758ff5b", size = 604414, upload-time = "2026-04-27T12:52:42.358Z" }, - { url = "https://files.pythonhosted.org/packages/09/06/4b69f8f0b67603a8be2790e55107a190b376f2627fe0eaf5695d85ffb3cd/greenlet-3.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ddc090c5c1792b10246a78e8c2163ebbe04cf877f9d785c230a7b27b39ad038e", size = 617349, upload-time = "2026-04-27T12:59:43.32Z" }, - { url = "https://files.pythonhosted.org/packages/8a/17/a3918541fd0ddefe024a69de6d16aa7b46d36ac19562adaa63c7fa180eff/greenlet-3.5.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2094acd54b272cb6eae8c03dd87b3fa1820a4cef18d6889c378d503500a1dc13", size = 613927, upload-time = "2026-04-27T12:25:30.28Z" }, - { url = "https://files.pythonhosted.org/packages/ee/e1/bd0af6213c7dd33175d8a462d4c1fe1175124ebed4855bc1475a5b5242c2/greenlet-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5e05ba267789ea87b5a155cf0e810b1ab88bf18e9e8740813945ceb8ee4350ba", size = 1570893, upload-time = "2026-04-27T12:53:29.483Z" }, - { url = "https://files.pythonhosted.org/packages/9b/2a/0789702f864f5382cb476b93d7a9c823c10472658102ccd65f415747d2e2/greenlet-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0ecec963079cd58cbd14723582384f11f166fd58883c15dcbfb342e0bc9b5846", size = 1636060, upload-time = "2026-04-27T12:25:28.845Z" }, - { url = "https://files.pythonhosted.org/packages/b2/8f/22bf9df92bbff0eb07842b60f7e63bf7675a9742df628437a9f02d09137f/greenlet-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:728d9667d8f2f586644b748dbd9bb67e50d6a9381767d1357714ea6825bb3bf5", size = 238740, upload-time = "2026-04-27T12:24:01.341Z" }, - { url = "https://files.pythonhosted.org/packages/b6/b7/9c5c3d653bd4ff614277c049ac676422e2c557db47b4fe43e6313fc005dc/greenlet-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:47422135b1d308c14b2c6e758beedb1acd33bb91679f5670edf77bf46244722b", size = 235525, upload-time = "2026-04-27T12:23:12.308Z" }, - { url = "https://files.pythonhosted.org/packages/94/5e/a70f31e3e8d961c4ce589c15b28e4225d63704e431a23932a3808cbcc867/greenlet-3.5.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:f35807464c4c58c55f0d31dfa83c541a5615d825c2fe3d2b95360cf7c4e3c0a8", size = 285564, upload-time = "2026-04-27T12:23:08.555Z" }, - { url = "https://files.pythonhosted.org/packages/af/a6/046c0a28e21833e4086918218cfb3d8bed51c075a1b700f20b9d7861c0f4/greenlet-3.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55fa7ea52771be44af0de27d8b80c02cd18c2c3cddde6c847ecebdf72418b6a1", size = 651166, upload-time = "2026-04-27T12:52:43.644Z" }, - { url = "https://files.pythonhosted.org/packages/47/f8/4af27f71c5ff32a7fbc516adb46370d9c4ae2bc7bd3dc7d066ac542b4b15/greenlet-3.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a97e4821aa710603f94de0da25f25096454d78ffdace5dc77f3a006bc01abba3", size = 663792, upload-time = "2026-04-27T12:59:44.93Z" }, - { url = "https://files.pythonhosted.org/packages/a3/59/1bd6d7428d6ed9106efbb8c52310c60fd04f6672490f452aeaa3829aa436/greenlet-3.5.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f52a464e4ed91780bdfbbdd2b97197f3accaa629b98c200f4dffada759f3ae7", size = 660933, upload-time = "2026-04-27T12:25:33.276Z" }, - { url = "https://files.pythonhosted.org/packages/83/e4/b903e5a5fae1e8a28cdd32a0cfbfd560b668c25b692f67768822ddc5f40f/greenlet-3.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:762612baf1161ccb8437c0161c668a688223cba28e1bf038f4eb47b13e39ccdf", size = 1618401, upload-time = "2026-04-27T12:53:31.062Z" }, - { url = "https://files.pythonhosted.org/packages/0e/e3/5ec408a329acb854fb607a122e1ee5fb3ff649f9a97952948a90803c0d8e/greenlet-3.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:57a43c6079a89713522bc4bcb9f75070ecf5d3dbad7792bfe42239362cbf2a16", size = 1682038, upload-time = "2026-04-27T12:25:31.838Z" }, - { url = "https://files.pythonhosted.org/packages/91/20/6b165108058767ee643c55c5c4904d591a830ee2b3c7dbd359828fbc829f/greenlet-3.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:3bc59be3945ae9750b9e7d45067d01ae3fe90ea5f9ade99239dabdd6e28a5033", size = 239835, upload-time = "2026-04-27T12:24:54.136Z" }, - { url = "https://files.pythonhosted.org/packages/4e/62/1c498375cee177b55d980c1db319f26470e5309e54698c8f8fc06c0fd539/greenlet-3.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:a96fcee45e03fe30a62669fd16ab5c9d3c172660d3085605cb1e2d1280d3c988", size = 236862, upload-time = "2026-04-27T12:23:24.957Z" }, - { url = "https://files.pythonhosted.org/packages/78/a8/4522939255bb5409af4e87132f915446bf3622c2c292d14d3c38d128ae82/greenlet-3.5.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:a10a732421ab4fec934783ce3e54763470d0181db6e3468f9103a275c3ed1853", size = 293614, upload-time = "2026-04-27T12:24:12.874Z" }, - { url = "https://files.pythonhosted.org/packages/15/5e/8744c52e2c027b5a8772a01561934c8835f869733e101f62075c60430340/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fc391b1566f2907d17aaebe78f8855dc45675159a775fcf9e61f8ee0078e87f", size = 650723, upload-time = "2026-04-27T12:52:45.412Z" }, - { url = "https://files.pythonhosted.org/packages/00/ef/7b4c39c03cf46ceca512c5d3f914afd85aa30b2cc9a93015b0dd73e4be6c/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:680bd0e7ad5e8daa8a4aa89f68fd6adc834b8a8036dc256533f7e08f4a4b01f7", size = 656529, upload-time = "2026-04-27T12:59:46.295Z" }, - { url = "https://files.pythonhosted.org/packages/0b/b5/c7768f352f5c010f92064d0063f987e7dc0cd290a6d92a34109015ce4aa1/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddb36c7d6c9c0a65f18c7258634e0c416c6ab59caac8c987b96f80c2ebda0112", size = 654364, upload-time = "2026-04-27T12:25:35.64Z" }, - { url = "https://files.pythonhosted.org/packages/ef/d0/079ebe12e4b1fc758857ce5be1a5e73f06870f2101e52611d1e71925ce54/greenlet-3.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e5ddf316ced87539144621453c3aef229575825fe60c604e62bedc4003f372b2", size = 1614204, upload-time = "2026-04-27T12:53:32.618Z" }, - { url = "https://files.pythonhosted.org/packages/6d/89/6c2fb63df3596552d20e58fb4d96669243388cf680cff222758812c7bfaa/greenlet-3.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4a448128607be0de65342dc9b31be7f948ef4cc0bc8832069350abefd310a8f2", size = 1675480, upload-time = "2026-04-27T12:25:34.168Z" }, - { url = "https://files.pythonhosted.org/packages/15/32/77ee8a6c1564fc345a491a4e85b3bf360e4cf26eac98c4532d2fdb96e01f/greenlet-3.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d60097128cb0a1cab9ea541186ea13cd7b847b8449a7787c2e2350da0cb82d86", size = 245324, upload-time = "2026-04-27T12:24:40.295Z" }, +version = "3.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/6e/802acd792aebb2256fbbee8cacf2727faaeb6f240ac11008f09eae4414bc/greenlet-3.5.1.tar.gz", hash = "sha256:5a56aeb7d5d9cc4b3a735efb5095bd4b4f6f0e4f93e5ca876d0e2315137b7829", size = 197356, upload-time = "2026-05-20T15:05:03.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/3c/ff890b466eaba2b0f5e6bdfff025f8c75f41b8ffdc3dbc3d24ad261e764a/greenlet-3.5.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:73f78f9b9f0a5c06e5c946ba1e8e36f5114923b6be109ee618c54f079c3ea14f", size = 284764, upload-time = "2026-05-20T13:09:10.204Z" }, + { url = "https://files.pythonhosted.org/packages/81/0e/5e5457be3d256918f6a4756f073548a3f0190836e2cc94aa6d0d617a940b/greenlet-3.5.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0cbed8bb44e23c5b199f888f4e4ce096b45ad9f25ff74a7ad0213875e936bb2", size = 603479, upload-time = "2026-05-20T14:00:04.757Z" }, + { url = "https://files.pythonhosted.org/packages/6d/e1/f89a21d58d308298e6f275f13a1b472ed96c680b601a371b08be6a725989/greenlet-3.5.1-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a203a8bd0acb0701653d3bbb26e404854a68674139ed5cbb778830f42b09bb33", size = 615495, upload-time = "2026-05-20T14:05:40.87Z" }, + { url = "https://files.pythonhosted.org/packages/75/de/af6cef182862d2ccd6975440d21c9058a77c3f9b469abf94e322dfd2e0e3/greenlet-3.5.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a271fcd66c74615cda6a964fda3f304267a12e50a084472218a39bb0376f563", size = 614754, upload-time = "2026-05-20T13:14:24.947Z" }, + { url = "https://files.pythonhosted.org/packages/1a/c6/50e520283a9f19388a7326b05f9e8637e566003475eacaadad04f558c68d/greenlet-3.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ded7b068c7c31c1a8657d4fd42d886b3e051ae29f88b80c5ff9d502257b0f071", size = 1574097, upload-time = "2026-05-20T14:02:24.003Z" }, + { url = "https://files.pythonhosted.org/packages/21/1c/13abd1f4860d987fa5e1170a01930d6e6cd40d328de487a3c9fdaff0ffd0/greenlet-3.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d0932b81d72f552ded9d810d00021b64d89f2195a91ce115b893f943b7a4ab3c", size = 1641058, upload-time = "2026-05-20T13:14:31.83Z" }, + { url = "https://files.pythonhosted.org/packages/f5/56/5f332b7705545eac2dc01b4e9254d24a793f2656d55d5cc6b94ee59d22ae/greenlet-3.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:88e300d136eac057b2397aa1cfd7328b4c87c7eb66a09c7bc6a1292234db474e", size = 238089, upload-time = "2026-05-20T13:14:03.229Z" }, + { url = "https://files.pythonhosted.org/packages/d9/a9/a3c2fa886c5b94863fb0e61b3bc14610b7aa94cf4f17f8741b11708305fc/greenlet-3.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:cc6ab7e555c8a112ad3a76e368e86e12a2754bcae1652a5602e133ec7b635523", size = 234989, upload-time = "2026-05-20T13:08:27.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/37/4549f149c9797c21b32c2683c33522af22522099de128b2406672526d005/greenlet-3.5.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:fa4f98af3a528f0c3fd592a26df7f376f93329c8f4d987f6bb979057af8bf5e2", size = 286220, upload-time = "2026-05-20T13:07:28.463Z" }, + { url = "https://files.pythonhosted.org/packages/38/ff/a4f436709716965eaab9f36ea7b906c8a927fbe32fb1372a2071d964f6b1/greenlet-3.5.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffea73584b216150eab159b6d12348fb253e68757974de1e2c40d8a318ac89ed", size = 601585, upload-time = "2026-05-20T14:00:06.141Z" }, + { url = "https://files.pythonhosted.org/packages/65/ad/54bc3fcee3ad368a61b19b67d88117f7a8c29727bf71fffdeda81fbd946e/greenlet-3.5.1-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1072b4f9edcc1e192d9283a66a3e68d6b84c561de33a83d7858beb9ba1effe10", size = 614215, upload-time = "2026-05-20T14:05:42.675Z" }, + { url = "https://files.pythonhosted.org/packages/40/69/b91cda0647df839483201545913514c2827ebea5e5ccdf931842763bc127/greenlet-3.5.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:add5217d68b31130f0beca584d7fef4878327d2e31642b66618a14eef312b63b", size = 611358, upload-time = "2026-05-20T13:14:26.37Z" }, + { url = "https://files.pythonhosted.org/packages/59/90/3cf77e080350cd02fa307bb2abf05df48f4482c240275bbd2c203ba8bb1c/greenlet-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a5ea42a752d47a145eae922b605cd1634665ac3d5ec1e72402d5048e8d60d207", size = 1570475, upload-time = "2026-05-20T14:02:25.29Z" }, + { url = "https://files.pythonhosted.org/packages/65/2c/18cece62045e74598c3c393f70dce4a63f56222015ba29a5d4eeb04f764c/greenlet-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c5551170cf4f5ff5623e9af81323751979fee2c731e2287b61f73cd27257b823", size = 1635625, upload-time = "2026-05-20T13:14:34.027Z" }, + { url = "https://files.pythonhosted.org/packages/30/f5/310d104ddf41eb5a70f4c268d22508dfb0c3c8e86fec152be34d0d2ed819/greenlet-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:3c8bb982ad117d29478ef8f5533e97df21f1e2befd17a299257b0c96d1371c0b", size = 238791, upload-time = "2026-05-20T13:10:39.018Z" }, + { url = "https://files.pythonhosted.org/packages/62/90/ceca11f504cd23a8047a3dea31919adc48df9b626dd0c13f0d858734fdfd/greenlet-3.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:80eb4b04dadc4e67df3fae179a32c4706a3f495bc7f22fc8a81115d5f5512188", size = 235580, upload-time = "2026-05-20T13:08:45.056Z" }, + { url = "https://files.pythonhosted.org/packages/27/69/7f7e5372d998b81001899b1c0823c957aa413ba0f2662e65821611cc31e4/greenlet-3.5.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:51518ff74664078fc51bffcc6fc529b0df5ae58da192691cee765d45ce944a2b", size = 285060, upload-time = "2026-05-20T13:08:51.899Z" }, + { url = "https://files.pythonhosted.org/packages/b1/bf/387f9b6b865fd2ae0d0be09e0004827295a01b71be76ed350dd1e28a91a4/greenlet-3.5.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ffdb3c0bb002c99cd8f298957e046c3dbf6006b5b7cdf11a4e19194624a0a0a", size = 604370, upload-time = "2026-05-20T14:00:07.492Z" }, + { url = "https://files.pythonhosted.org/packages/32/f5/169ce3d4e4c67291bd18f8cbe0299c9f3e45102c7f1fb3c14780c93e4532/greenlet-3.5.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7715a5a2c3378ba602c3a440558261e13a820bb53a82693aacd7b7f6d964e283", size = 616987, upload-time = "2026-05-20T14:05:44.237Z" }, + { url = "https://files.pythonhosted.org/packages/ee/e5/7f2e41d5273be07e77560d61ea4e56485b4d6c316d2a84518c62d1364061/greenlet-3.5.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc71ff466927a201b08305acac451ebe1aedfcea002f62f1f2f2ac2ac1e6a135", size = 613911, upload-time = "2026-05-20T13:14:27.539Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a4/fbdc67579b73615a1f91615e814303cc71e06128f7baaba87be79b8fb90c/greenlet-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cd443683db272ebaaca03af98c0b063ab30db70ea8a31a1559f35e3f7b744ccd", size = 1570689, upload-time = "2026-05-20T14:02:27.225Z" }, + { url = "https://files.pythonhosted.org/packages/e6/b4/77abbe35078be39718a46cd49caf16bceb35662f97a34101dca28aa98e47/greenlet-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:089fff7a6ce8d9316d1f65ebc00273a56be258c1725b32b94de90a3a979557e1", size = 1635602, upload-time = "2026-05-20T13:14:36.344Z" }, + { url = "https://files.pythonhosted.org/packages/37/f7/129f27ca700845b8ee8ca88ce7f43435a1239c2eddb7677fc938822762cf/greenlet-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:110a1ca7b49b014b097f6078272c3f4ed31af45b254de5228b79adba879f6af9", size = 238683, upload-time = "2026-05-20T13:11:50.57Z" }, + { url = "https://files.pythonhosted.org/packages/6d/5c/a485a36e87df8d8fd0632ee01511244f5156a20ed3746cc6599340326395/greenlet-3.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:f16ba1efc0715b680a18b8123d90dad887c6112ae3555b4b5c32c149540c6b4e", size = 235499, upload-time = "2026-05-20T13:12:42.028Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cb/c62454606daf5640369c94d8a9dd540599b1bfc090e2d2180cb77f4038d2/greenlet-3.5.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d8ab31c9de8651a2facdd5c5bb0011f2380dd1a7af78ce2adf4b56095294fc07", size = 285579, upload-time = "2026-05-20T13:08:56.396Z" }, + { url = "https://files.pythonhosted.org/packages/ec/71/c4270398c2eba968a6071af1dfbdcaeee6ec1c24bc8b435b8cc452700da6/greenlet-3.5.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e300185139abc337ade480c327183adf42a875ac7181bfe66d7d4efea31fbea", size = 651106, upload-time = "2026-05-20T14:00:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ab/71e34b78a44ec271fb5f550c17bc46d301ddc5953890d935f270b0dcdb5a/greenlet-3.5.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7ffdb990dcaa0234cf9845aead5df2e3c3a8b6507d409274dd87e0d5ab05ffc2", size = 663478, upload-time = "2026-05-20T14:05:45.88Z" }, + { url = "https://files.pythonhosted.org/packages/77/96/4efd6fa5c62c85426a0c19077a586258ebc3a2a146ff2493e4312a697a22/greenlet-3.5.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f82b3597e9d83b63408affed0b48fd0f54935edac4302237b9a837be0dae33c", size = 660800, upload-time = "2026-05-20T13:14:29.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/e0/6c71401a25cac7000261304e866a2f2cc04dc74810d40e2f118aa4799495/greenlet-3.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c0141e37414c10164e702b8fb1473304221ad98f71600850c6ef7ff4880feba0", size = 1617518, upload-time = "2026-05-20T14:02:28.662Z" }, + { url = "https://files.pythonhosted.org/packages/41/26/c5c06643e8c0af9e7bf18e16cb51d0ab7625155f0392e1c9015d66d556cd/greenlet-3.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:50ae25a67bea74ea41fb14b960bc532df73eb713417b2d61892dced82fe8d3bc", size = 1681593, upload-time = "2026-05-20T13:14:39.417Z" }, + { url = "https://files.pythonhosted.org/packages/8a/bd/e11a108317485075e68af9d23039619b86b28130c3b50d227d42edece64b/greenlet-3.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:8a17c42330e261299766b75ac1ea32caa437a9453c8f65d16a13140db378ecd3", size = 239800, upload-time = "2026-05-20T13:09:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/47/f8/8e8e8417b7bf28639a5a56356ef934d0375e1d0c70a57e04d7701e870ffe/greenlet-3.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:7b5f5fae05b8ac6d176a61b60c394a8cbdc2b5b91b81793066e68745cf165e54", size = 236862, upload-time = "2026-05-20T13:09:10.498Z" }, + { url = "https://files.pythonhosted.org/packages/90/12/41bf27fde4d3605d3773ae57751eda182b8be2f5398011c041173b1d9534/greenlet-3.5.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:ea8da1e900d758d078810d4255d8c6aa572181896a31ec79d779eb79c3adc9ad", size = 293637, upload-time = "2026-05-20T13:12:35.529Z" }, + { url = "https://files.pythonhosted.org/packages/44/44/ba14b23e9757707050c2f397d305bbcae62e5d7cad122f8b6baec5ae4a1f/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a19570c52a21420dcbc94e661994bc325c0b5b11304540fed514586da5dc8f2e", size = 650840, upload-time = "2026-05-20T14:00:11.079Z" }, + { url = "https://files.pythonhosted.org/packages/a8/37/5ddc2b686a6844f91abecef43411842426da2e1573f60b49ecf2547f4ae1/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3d955c89b75eeca4723d7cc14135f393cd47c32e2a6cb4a8e4c6e760a26b0986", size = 656416, upload-time = "2026-05-20T14:05:47.118Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f0/d17510297c35a2992712f0bf84de3779749999f7d3d63aa1f09db7c62dbe/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2daaaebd1a5aa88c49045b6baf9310b3263796bd88db713edf37cf53e7bb4e", size = 654397, upload-time = "2026-05-20T13:14:30.696Z" }, + { url = "https://files.pythonhosted.org/packages/37/eb/147387705bb89092645b012586e7273cb5ed3c90ef7eaf3a69173eaf0209/greenlet-3.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bfbd69cc349e43bf3a8ae1c85548ff0718efc887615c2db16c3833d7b0b072d", size = 1614469, upload-time = "2026-05-20T14:02:30.192Z" }, + { url = "https://files.pythonhosted.org/packages/a6/4e/37ee0da7732b7aa9896f17e15579a9df34b9fcb9dd494f0adfa749af6623/greenlet-3.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4378720dd888136c27215a0214d32a4d37c3852765d45bc37aad0623423cfd78", size = 1675115, upload-time = "2026-05-20T13:14:40.972Z" }, + { url = "https://files.pythonhosted.org/packages/57/f3/97dfcf4a6eb5077f8a672234216fb5923eb89f2cab7081cb10b2cf75b605/greenlet-3.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:45718441607f9325d948db98cbc691276059316d0358c188c246da4e1d4d23d2", size = 245246, upload-time = "2026-05-20T13:12:22.646Z" }, + { url = "https://files.pythonhosted.org/packages/5d/73/d7f72e34b582f694f4a9b248162db7b09cc458a259ba8f0c0bfa1a34ea7d/greenlet-3.5.1-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:2baee5ca02031757ffe8cc3d69f0cc0aec7065ce362622da74f32d3bcab1c541", size = 285575, upload-time = "2026-05-20T13:12:07.043Z" }, + { url = "https://files.pythonhosted.org/packages/df/59/fa9c6e87dc8ad27a95dabe2f29f372b733d05a8a67470f6c901ed9975655/greenlet-3.5.1-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b1ec3274918a81d3ea778b9e75b56b72b33f300edb6cf7f3a7fe1dae56683de", size = 656428, upload-time = "2026-05-20T14:00:12.556Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f9/e753408871eaa61dfe35e619cfc67512b036fde99893685d50eea9e07146/greenlet-3.5.1-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:111e2390ffffc47d5840b01711dd7fac07d4c09283d0283e7f3264b14e284c64", size = 667064, upload-time = "2026-05-20T14:05:48.662Z" }, + { url = "https://files.pythonhosted.org/packages/96/27/5565b5b40389f1c7753003a07e21892fda8660926787036d5bc0308b8113/greenlet-3.5.1-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e630136e905fe5ff43e86945ae41220b6d1470956a39220e708110ac48d01ea5", size = 665697, upload-time = "2026-05-20T13:14:32.943Z" }, + { url = "https://files.pythonhosted.org/packages/cf/82/e7de4178c0c2d1c9a5a3be3cc0b33e46a85b3ee4a77c071bf7ad8600e079/greenlet-3.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:975eac34b44a7077ca4d421348455b94f0f518246a7f14bc6d2fdcfe5b584368", size = 1621256, upload-time = "2026-05-20T14:02:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/00/10/f2dddcf7dacac17dfc68691809589adad06135eb28930429cf58a6467a2f/greenlet-3.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:9ab3c3a0b2ae6198e67c898dad5215a49f9ae0d0081b3c3ec59f333e39eeca26", size = 1685956, upload-time = "2026-05-20T13:14:42.55Z" }, + { url = "https://files.pythonhosted.org/packages/22/17/4a232b32133230ada52f70e9d7f5b65b0caef8772f01849bd8d149e7e4ca/greenlet-3.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:cbfc69be86e10dcfef5b1e6269d1d6926552aa89ee39e1de3353360c1b6989ab", size = 239802, upload-time = "2026-05-20T13:13:15.481Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ae/4e623a7e6d4d2a5f4cb8e4c82de4169fc637942caae68d6e676b8a128ac5/greenlet-3.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:92fd6d44ac5e5a887c8a5dc4a8ba0ba908527c31c12f78c6bc7dcfe8aab279f6", size = 236853, upload-time = "2026-05-20T13:15:37.301Z" }, + { url = "https://files.pythonhosted.org/packages/7a/57/816d9cff29119da3505b3d6a5e14a8af89006ac36f47f891ff293ee05af1/greenlet-3.5.1-cp315-cp315t-macosx_11_0_universal2.whl", hash = "sha256:a6fdf2433a5441ef9a95464f7c3e674775da1c8c1177fff311cee1acad4626ed", size = 293877, upload-time = "2026-05-20T13:10:19.078Z" }, + { url = "https://files.pythonhosted.org/packages/23/a1/59b0a7c7d140ff1a75626680b9a9899b79a9176cab298b394968fb023295/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7546556f0d649f99f6a361098a55f761181bb2ea12ff150bb16d26092ad88244", size = 655333, upload-time = "2026-05-20T14:00:14.758Z" }, + { url = "https://files.pythonhosted.org/packages/72/1b/5efe127597625042218939d01855109f352779050768b670b52edcc16a6c/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d5ee3ea898009fa898f85f9982255d35278c477bebe185beca249cab42d4526c", size = 659443, upload-time = "2026-05-20T14:05:50.159Z" }, + { url = "https://files.pythonhosted.org/packages/6c/6d/c404246ea4d22d097a7426d0efb5b781bd7eb67715f09e79001bd552ab18/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5c81f74d204d3edd136ebfd50dce53acbb776995d721a0fe801626cfc93b8cd", size = 658356, upload-time = "2026-05-20T13:14:35.091Z" }, + { url = "https://files.pythonhosted.org/packages/51/02/f8ee37fb6d2219329f350af241c27fcf12df57e723d11f6fc6d3bacdadaa/greenlet-3.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:2c18ef16bf6d4dd410e4dd52996888ea1497be26892fe5bbc73580aba4287b8e", size = 1619216, upload-time = "2026-05-20T14:02:33.403Z" }, + { url = "https://files.pythonhosted.org/packages/93/c5/3dc9475ace2c7a3680da12372cddd7f1ac874eb410a1ac48d3e9dab83782/greenlet-3.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:17d86354f0ae6b61bf9be5148d0dd34e06c3cb7c602c671f79f29ac3b150e659", size = 1678427, upload-time = "2026-05-20T13:14:43.71Z" }, + { url = "https://files.pythonhosted.org/packages/df/4e/750c15c317a41ffb36f0bf40b933e3d744a7dede61889f74443ea69690cf/greenlet-3.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:e7516cf6ae6b8a582c2770a0caed47b8a48373ed732c33d69a72913ae6ac923e", size = 245225, upload-time = "2026-05-20T13:13:59.366Z" }, + { url = "https://files.pythonhosted.org/packages/4f/fd/d3baea2eeb7b617efd47e87ca06e2ec2c6118d303aa9e918e0ce16eadc10/greenlet-3.5.1-cp315-cp315t-win_arm64.whl", hash = "sha256:5028648bf2253ec4745add746129d3904121fa7fe871a76bed23c5720573ce0a", size = 239590, upload-time = "2026-05-20T13:13:37.382Z" }, ] [[package]] @@ -1169,11 +1185,11 @@ wheels = [ [[package]] name = "idna" -version = "3.15" +version = "3.16" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/88/bcf9709822fe69d02c2a6a77956c98ce6ea8ca8767a9aadcedc7eb6a2390/idna-3.16.tar.gz", hash = "sha256:d7a6da03db833450fca25d2358ac9ff06cd624577a4aea3a596d5c0f77b8e03d", size = 203770, upload-time = "2026-05-22T00:16:18.781Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" }, + { url = "https://files.pythonhosted.org/packages/94/16/70255075a9859a0e3adb789b68ceb0e210dec03934245fd98d248226572f/idna-3.16-py3-none-any.whl", hash = "sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5", size = 74165, upload-time = "2026-05-22T00:16:16.698Z" }, ] [[package]] @@ -2008,81 +2024,81 @@ wheels = [ [[package]] name = "numpy" -version = "2.4.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/50/8e/b8041bc719f056afd864478029d52214789341ac6583437b0ee5031e9530/numpy-2.4.5.tar.gz", hash = "sha256:ca670567a5683b7c1670ec03e0ddd5862e10934e92a70751d68d7b7b74ca7f9f", size = 20735669, upload-time = "2026-05-15T20:25:19.492Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/44/1383ee4d1e916a9e610e46c876b5c83ea023526117d23cd911983929ec34/numpy-2.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3176dc8ff71dbb593606f91a69ad0c3cd3303c7eb546af477370ab9edf760288", size = 16969261, upload-time = "2026-05-15T20:22:23.036Z" }, - { url = "https://files.pythonhosted.org/packages/3d/61/54bacfbec7550bc398e6b6d9a861db35d64f75844e1d7920f5722c3cd5e7/numpy-2.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1811150e5148f5a01a7cc282cb2f489b4a3050a773e173adb480e507bad3a3d7", size = 14964009, upload-time = "2026-05-15T20:22:25.819Z" }, - { url = "https://files.pythonhosted.org/packages/7a/55/fe86c64561761f185339c26001164a2687bd4787af681e961431abd2d534/numpy-2.4.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0d63a780070871210853ba01e90b88f9b85cf2abf63a7f143d5127189265ddf6", size = 5469106, upload-time = "2026-05-15T20:22:28.13Z" }, - { url = "https://files.pythonhosted.org/packages/2f/74/cf29b8317627f0e3aa2c9fb332d386bd734308cecd9e07da9f407d9ce0c3/numpy-2.4.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:0c6919cefafb3b76cd46a89dbb203bf1dd95529d2a6d09fef2d325d95d6a79d8", size = 6798945, upload-time = "2026-05-15T20:22:30.061Z" }, - { url = "https://files.pythonhosted.org/packages/80/a9/b61730a17fa87d5abb13ce560a1b4ce3485d37a13e03eb7b414e598e72f8/numpy-2.4.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d51efede1e58e8b11877536a5518f60e318d8ff69b89ad7b38ee5e431b24d772", size = 15967025, upload-time = "2026-05-15T20:22:32.328Z" }, - { url = "https://files.pythonhosted.org/packages/03/39/70bcd187eb4d223c21fde02c2bdfbffbffef3288cbb3947c04c74ae39a08/numpy-2.4.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:07ce7e74da92d7c71b5df157b9758bcdd53d7fea10602154de3afd2b3ddc34dd", size = 16918685, upload-time = "2026-05-15T20:22:34.759Z" }, - { url = "https://files.pythonhosted.org/packages/ab/31/400fd1315bbe228af3937cf8a74e32023df6217af36077919d00adc382e4/numpy-2.4.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d7828234a13185effb34979e146f9921f2a65dfbbe215e6dbb57d6478fc8e059", size = 17322963, upload-time = "2026-05-15T20:22:37.557Z" }, - { url = "https://files.pythonhosted.org/packages/18/6a/bbbafb657e6f6ee826b4ecdb8722a2e0aae4a981888eaf59eae6a535cc13/numpy-2.4.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f96083adc3dfc1bbf778f2c79654d88115fa07074c97cb724fe9508f12d91c55", size = 18651594, upload-time = "2026-05-15T20:22:40.449Z" }, - { url = "https://files.pythonhosted.org/packages/de/0c/857a515154a2a18b0dfae04089600d166d352d473ec17a0680d879582d06/numpy-2.4.5-cp311-cp311-win32.whl", hash = "sha256:4ed78c904a638b6e5d7cd4db90c06fca5fc6ec2f28d258305368f454a50e79cf", size = 6233849, upload-time = "2026-05-15T20:22:43.139Z" }, - { url = "https://files.pythonhosted.org/packages/f0/66/d215f3fb93541617adb5d58b3b9508e8a6413e499711e0adc0b80bcb445d/numpy-2.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:079b0fad6f2899b23c5da89792b5409d2d83fc83e8bd5c2299cc9c397a264864", size = 12608238, upload-time = "2026-05-15T20:22:45.229Z" }, - { url = "https://files.pythonhosted.org/packages/cb/c4/611d66d3fcfa931954d37a19ce5575f3283d023e89ff0df6ad43b334ae9c/numpy-2.4.5-cp311-cp311-win_arm64.whl", hash = "sha256:d6c78e260b53affe9b395a9d54fc61f101f9521c4d9452c7e9e3718b19e2215b", size = 10479452, upload-time = "2026-05-15T20:22:47.962Z" }, - { url = "https://files.pythonhosted.org/packages/6c/18/3275231e98620002681c922e792db04d72c356e9d8073c387344fc0e4ff1/numpy-2.4.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:654fb8674b61b1c4bd568f944d13a908566fdcb0d797303521d4149d16da05ef", size = 16689166, upload-time = "2026-05-15T20:22:50.761Z" }, - { url = "https://files.pythonhosted.org/packages/db/23/000aab6a16bdec53307f0f72546b57a3ac9266a62d8c257bee97d85fd078/numpy-2.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4cd9f6fa7ce10dc4627f2bb81dd9075dab67e94632e04c2b638e12575ddaa862", size = 14699514, upload-time = "2026-05-15T20:22:53.678Z" }, - { url = "https://files.pythonhosted.org/packages/47/cc/ddaf3af9c46966fef5be879256f213d85a0c56c75d07a3b7defec7cf6b4c/numpy-2.4.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:4f5bc96d35d94e4ceab8b38a92241b4611e95dc44e63b9f1fa2a331858ee3507", size = 5204601, upload-time = "2026-05-15T20:22:56.257Z" }, - { url = "https://files.pythonhosted.org/packages/07/ea/627fadd11959b3c7759008f34c92a35af8ff942dd8284a66ced648bbe516/numpy-2.4.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:4bb33e900ee81730ad77a258965134aa8ceac805124f7e5229347beda4b8d0aa", size = 6551360, upload-time = "2026-05-15T20:22:58.334Z" }, - { url = "https://files.pythonhosted.org/packages/a1/47/0728b986b8682d742ff68c16baa5af9d185484abfc635c5cc700f44e62be/numpy-2.4.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32f8f852273ef32b291201ac2a2c97629c4a1ee8632bb670e3443eaa09fc2e72", size = 15671157, upload-time = "2026-05-15T20:23:01.081Z" }, - { url = "https://files.pythonhosted.org/packages/d1/0b/b905ae82d9419dc38123523862db64978ca2954b69609c3ae8fdaca1084c/numpy-2.4.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:685681e956fc8dcb75adc6ff26694e1dfd738b24bd8d4696c51ca0110157f912", size = 16645703, upload-time = "2026-05-15T20:23:04.358Z" }, - { url = "https://files.pythonhosted.org/packages/5f/24/e27fc3f5236b4118ed9eed67111675f5c61a07ea333acec87c869c3b359d/numpy-2.4.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6f64dd84b277a737eb59513f6b9bb6195bf41ab11941ef15b2562dbab43fa8ef", size = 17021018, upload-time = "2026-05-15T20:23:07.021Z" }, - { url = "https://files.pythonhosted.org/packages/d3/a7/9041af38d527ab80a06a93570a77e29425b41507ad41f6acf5da78cfb4a4/numpy-2.4.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b42d9496f79e3a728192f05a42d86e36163217b7cdecb3813d0028a0aa6b72d7", size = 18368768, upload-time = "2026-05-15T20:23:09.44Z" }, - { url = "https://files.pythonhosted.org/packages/49/82/326a014442f32c2663434fd424d9298791f47f8a0f17585ad60519a5606e/numpy-2.4.5-cp312-cp312-win32.whl", hash = "sha256:86d980970f5110595ca14855768073b08585fc1acc36895de303e039e7dee4a5", size = 5962819, upload-time = "2026-05-15T20:23:11.631Z" }, - { url = "https://files.pythonhosted.org/packages/3c/f0/cbf5d391b0b3a5e8cad264603e2fae256b0bde8ce43566b13b78faedc659/numpy-2.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:3333dba6a4e611d666f69e177ba8fe4140366ff681a5feb2374d3fd4fff3acb6", size = 12321621, upload-time = "2026-05-15T20:23:14.305Z" }, - { url = "https://files.pythonhosted.org/packages/3c/d0/0f18909d9bc37a5f3f969fc737d2bb5df9f2ff295f71b467e6f52a0d6c4e/numpy-2.4.5-cp312-cp312-win_arm64.whl", hash = "sha256:4593d197270b894efeb538dcbe227e4bcf1c77f88c4c6bf933ead812cfaa4453", size = 10221430, upload-time = "2026-05-15T20:23:16.887Z" }, - { url = "https://files.pythonhosted.org/packages/e3/a4/fb50657c7cab297bf34edcd60a074cb0647f61771430d6363575274160fe/numpy-2.4.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1ef248460b645c102026b82337cc4e88231909c66dd77b59ec6d6cac7e44f277", size = 16684760, upload-time = "2026-05-15T20:23:19.436Z" }, - { url = "https://files.pythonhosted.org/packages/3e/43/87e731299b9408eda705b3b9cb31c7bceb9347d2af9cbb16b2b1e4b5bc0f/numpy-2.4.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4603622bdcdbf8dccb1d9d5b21d16a7aa4e473ae6c8e14048d846fd4ca2907a0", size = 14694117, upload-time = "2026-05-15T20:23:21.832Z" }, - { url = "https://files.pythonhosted.org/packages/a9/c7/0b2bb8acea222e9dd6e582afc2bc553b89b8833cbdccc68e68f050fb31f8/numpy-2.4.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6c18d49c67689c562854b53fdc433b93e47c12952aa6fa6d59f185e1a5992419", size = 5199141, upload-time = "2026-05-15T20:23:24.066Z" }, - { url = "https://files.pythonhosted.org/packages/39/60/b6972b5d47033d90000f0097c81a98b9486589a2d7003bf725bff275cb0d/numpy-2.4.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:b1c663ddc641f4192e90511bec61a09bc231e3bbdb996cdc6edbcaa0e528d685", size = 6546954, upload-time = "2026-05-15T20:23:26.099Z" }, - { url = "https://files.pythonhosted.org/packages/c1/e9/ed667cb12c11ca0adde431f685d3a5dd78e6f78b27228c581c8415198e9e/numpy-2.4.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93793222b524f692f12b2f8752ce8b1d9d9125b2bfd5dbf0fb69c92c5e1ce86c", size = 15669430, upload-time = "2026-05-15T20:23:28.147Z" }, - { url = "https://files.pythonhosted.org/packages/44/e5/679f6ffeb01294b0008e5ada4a113cb47617bc0e1819a529fd7973c6d7f4/numpy-2.4.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1616bde34b2bcba2fa9bde06217ce00da4f3d1bdfb264d54525a99e8fe170d83", size = 16633390, upload-time = "2026-05-15T20:23:31.622Z" }, - { url = "https://files.pythonhosted.org/packages/36/46/42bfffc9a780ec902ccd7470d3219192ee82b7b442710307dd85b4d121b0/numpy-2.4.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:09d7d97da1c2c62f4818b3e150a57572ff8dcf1cf5ac501aac832ffd4ebd9566", size = 17020709, upload-time = "2026-05-15T20:23:34.08Z" }, - { url = "https://files.pythonhosted.org/packages/44/00/3e840bfee0cc6cec22209f2c97057f26eeb30de031e4933b4dfc0395416c/numpy-2.4.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d68d0b355ab2e39fe0de59001d7151dfdbbb880ef67baeed806661e03df5097", size = 18357818, upload-time = "2026-05-15T20:23:36.965Z" }, - { url = "https://files.pythonhosted.org/packages/72/cb/3447b400b9da84134575486f0f656541559b00d4b262477bce9b678bbca8/numpy-2.4.5-cp313-cp313-win32.whl", hash = "sha256:fe28b64777ddfa0eca9b5f51474034ebe3dcb8324f48f27b28f479085673ae33", size = 5961114, upload-time = "2026-05-15T20:23:39.586Z" }, - { url = "https://files.pythonhosted.org/packages/28/f9/a90d2220ffcdc0798f5d55bb5d5463cd6254ec9ef43f384dae80217d7a2f/numpy-2.4.5-cp313-cp313-win_amd64.whl", hash = "sha256:fb4a6c9c537d6ccec9cc4aeae4261bd3cc79b070c67ddc0646f5b1c07fddde42", size = 12318553, upload-time = "2026-05-15T20:23:41.436Z" }, - { url = "https://files.pythonhosted.org/packages/b8/c9/96f531fb3234545315152d34efdf3de7daee81254448447eb619e8d16967/numpy-2.4.5-cp313-cp313-win_arm64.whl", hash = "sha256:6d7df2da2e7ea0624a43aa368104b3a3ce14aae98ad4bb2c9a93fecef76f1c97", size = 10222200, upload-time = "2026-05-15T20:23:43.681Z" }, - { url = "https://files.pythonhosted.org/packages/e1/f4/a291caab5a3c520babf93ff77c54fd5fdb1ebbc3296cee2eb2146ce773b1/numpy-2.4.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:2a235607a18df941760a695927051af4b1cd5d3ee85840d0e2af816785771feb", size = 14821438, upload-time = "2026-05-15T20:23:45.911Z" }, - { url = "https://files.pythonhosted.org/packages/85/26/13dbb1159b864370568e7309063fd72667984df89db74e9caeb175d067c7/numpy-2.4.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:58dcf64969d870f36bc7fbd557d2617e997db7dc06261b6e3327148ea460d0a4", size = 5326663, upload-time = "2026-05-15T20:23:48.18Z" }, - { url = "https://files.pythonhosted.org/packages/7c/99/d233408072a0e019e2288e27edd23f7d572ccd4a73d1539baa3270ede85d/numpy-2.4.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:235f54b0156274d8fa3155db3ed6d2f401c7e8f3367c90db0a12f02a58fde6ed", size = 6646874, upload-time = "2026-05-15T20:23:49.856Z" }, - { url = "https://files.pythonhosted.org/packages/c5/00/eeb6f193dfe767725e952e0464f3e51f44145c5dd261cd7389aa36ac0713/numpy-2.4.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef3b5bb65437a3555c648e706475db01c645559ca80dc8b03e4f202ea757e0d6", size = 15728147, upload-time = "2026-05-15T20:23:51.655Z" }, - { url = "https://files.pythonhosted.org/packages/e5/c9/b8ed039f1fde1b13a8807c893e7e2f9432a379f4d6401edecf0028da5b2c/numpy-2.4.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7f09a7e5f017d7098c66522097c96257411c9620c0926212200d66bc8cee3976", size = 16681770, upload-time = "2026-05-15T20:23:53.933Z" }, - { url = "https://files.pythonhosted.org/packages/11/5b/0198ef6cb7016eca6d895d392106012138127fab23f46637e76d5e25c9f5/numpy-2.4.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:993a88d8fdd8554466a8765cd8bacd97ba56b70ca6b0a04bcdca77f5afed4222", size = 17086218, upload-time = "2026-05-15T20:23:56.646Z" }, - { url = "https://files.pythonhosted.org/packages/f0/fe/8821f3cfc660ae84c92ee158505941874b62c56a42e035a41425228cd8cf/numpy-2.4.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:84f58bed609b5669f5ad3d597901a4f1f86ee5b3c3708aaa55f05b4fe6e0f656", size = 18403542, upload-time = "2026-05-15T20:23:59.173Z" }, - { url = "https://files.pythonhosted.org/packages/0e/00/e64ecaf498865e7b091f57658b2c522503e5d1b70e43b807f5f8247e1d88/numpy-2.4.5-cp313-cp313t-win32.whl", hash = "sha256:7200c58f3f933ca61e66346667dcc8510bb111995e9ce15398a731e6a4afa4bb", size = 6084903, upload-time = "2026-05-15T20:24:01.506Z" }, - { url = "https://files.pythonhosted.org/packages/20/c0/354997dedaf74e8311c2cf9a6027b476fd8d424cb92189cc0ae2b25f501c/numpy-2.4.5-cp313-cp313t-win_amd64.whl", hash = "sha256:c26c71080d35db5002102f5d9ff614d45de02aa1f7802943e691e063e5ee93bc", size = 12458420, upload-time = "2026-05-15T20:24:03.735Z" }, - { url = "https://files.pythonhosted.org/packages/66/dc/917ee5ea4a31ca1a6e4c9a85386477efa318dcc60db257c5ef4adda096c1/numpy-2.4.5-cp313-cp313t-win_arm64.whl", hash = "sha256:2caa576d1707b275cba1aeb60a5c50daa6fa2a3f28ecb08123bc05fd439005db", size = 10291826, upload-time = "2026-05-15T20:24:06.535Z" }, - { url = "https://files.pythonhosted.org/packages/ca/c1/3be0bf102fc17cff5bd142e3be0bfffabec6fa46da0a462396c76b0765d0/numpy-2.4.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:889ca2c072315de638a5194a772aa1fa2df92bdd6175f6a222d4784040424b61", size = 16683455, upload-time = "2026-05-15T20:24:08.988Z" }, - { url = "https://files.pythonhosted.org/packages/e8/3e/0742d724901fa36bc54b338c6e62e463a7601180da896aa44978f0adf004/numpy-2.4.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:89e89304fb1f8c3f0ecfa4a7d48f311dd79771336a940e920159d643d1307e77", size = 14704577, upload-time = "2026-05-15T20:24:11.542Z" }, - { url = "https://files.pythonhosted.org/packages/25/1c/196c610ff4c6782d697ba780ebdc1616be143213701bf22c1a270f3bf7dd/numpy-2.4.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:144fcc5a3a17679b2b82543b4a2d8dd29937230a7af13232b5f753872feb6361", size = 5209756, upload-time = "2026-05-15T20:24:14.091Z" }, - { url = "https://files.pythonhosted.org/packages/52/c0/23fb1bc506f774e03db66219a2830e720f4d3dbcaaddf855a7ff7bb6d96f/numpy-2.4.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:398bb16772b265b9fa5c07b07072646ea97137c10ffb62a9a087b277fc825c29", size = 6543937, upload-time = "2026-05-15T20:24:16.223Z" }, - { url = "https://files.pythonhosted.org/packages/9f/49/db4662c26e68520afcc84d672a6f9f5294063dee0e57a46d61afdaa7f9ed/numpy-2.4.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb352e7b8876da1249e72254736d6c58c505fa4e58a3d7e30efca241ca9ca9ce", size = 15685292, upload-time = "2026-05-15T20:24:17.978Z" }, - { url = "https://files.pythonhosted.org/packages/43/80/1315439acedd8398319bac177d6de3d48ab39c62cc0c810f74f0a9a73996/numpy-2.4.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7341b08ff8124d7353939778e2707b8732d03c78c1c30e0815aba2dacbe1245a", size = 16638528, upload-time = "2026-05-15T20:24:20.478Z" }, - { url = "https://files.pythonhosted.org/packages/56/81/364388600932618fe735d97fdd2437cb8dd87a23377ac11d8b9d5db098b7/numpy-2.4.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:deb01226f012539f3945261ffe1c10aec081a0fa0a5c925419933c70f3ae2d23", size = 17036709, upload-time = "2026-05-15T20:24:22.949Z" }, - { url = "https://files.pythonhosted.org/packages/32/4a/a1185b18a94a6d9587e54b437e7d0ba36ecf6e614f1bea03f5249912c64e/numpy-2.4.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d888bdf7335f76878c3c7b264ac1ff089863e211ec81249f9fb5795c2183dc25", size = 18363254, upload-time = "2026-05-15T20:24:25.402Z" }, - { url = "https://files.pythonhosted.org/packages/b9/8e/95c1d2ed15ae97750ede8c8a0ac487c9c01207afff430f47078b1d9d7dc5/numpy-2.4.5-cp314-cp314-win32.whl", hash = "sha256:15f90d1256e9b2320aff24fde44815b787ab6d7c49a1a11bfd8138b321c5f080", size = 6010184, upload-time = "2026-05-15T20:24:27.852Z" }, - { url = "https://files.pythonhosted.org/packages/aa/92/d063df4d63d988b20d881856c74df76c0c1786229bb870f3a52af0981d4d/numpy-2.4.5-cp314-cp314-win_amd64.whl", hash = "sha256:4bd2cd4ef9c0afa87de73723c0a33c0edff62143e1432917458e26d3d195d87f", size = 12450344, upload-time = "2026-05-15T20:24:29.856Z" }, - { url = "https://files.pythonhosted.org/packages/3d/64/c0ae481f7c3b2f85869bcd8fc5d30aa7c96b394162eef9c9315957f115c5/numpy-2.4.5-cp314-cp314-win_arm64.whl", hash = "sha256:db304568c650e9d7039744d3575d0d287754debb2057d7c7b8cdfdc2c487a957", size = 10495674, upload-time = "2026-05-15T20:24:32.352Z" }, - { url = "https://files.pythonhosted.org/packages/57/89/c5a4c677acf17aa50ba09a15e61812f90baac42bb6ca38d112e005858351/numpy-2.4.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6de2883e0d2c63eae1bab1a84b390dca74aabb3d20ea1f5d58f360853c83abf3", size = 14824078, upload-time = "2026-05-15T20:24:34.669Z" }, - { url = "https://files.pythonhosted.org/packages/e7/52/57e7144284f6b51ba93523e495ff239260b1ecd5257e3700a436332e5688/numpy-2.4.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:06760fe73ae5005008748d182de612c733542af3cde063d532cd2127561b27be", size = 5329246, upload-time = "2026-05-15T20:24:36.957Z" }, - { url = "https://files.pythonhosted.org/packages/b9/b3/09dbce80fd4a7db4318f2fc01eec0ae76f29306442b5a32d4b811d082cdf/numpy-2.4.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:4b51a01745cb04cc19278482207444b4d30728ce91c28d27a3bfae5fc6ff24c7", size = 6649877, upload-time = "2026-05-15T20:24:38.861Z" }, - { url = "https://files.pythonhosted.org/packages/30/c2/dbdb23e82d540b757690ef13f011c386fca6a63848eec6136baf8ce7cbed/numpy-2.4.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a05636d7937d0936f271e5ba957fa8d746b5be3c2025caa1a2508f4fe521d40", size = 15730534, upload-time = "2026-05-15T20:24:41.168Z" }, - { url = "https://files.pythonhosted.org/packages/c4/bd/68f6e9b3c20decf40ac06708a7b506757e3a8588efed32988d1b747316be/numpy-2.4.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b86f56048ed09c3bbe48962a7dff077c2fd3274f8cf981800f3b38eac49cc3", size = 16679741, upload-time = "2026-05-15T20:24:44.874Z" }, - { url = "https://files.pythonhosted.org/packages/39/1d/0fcac0b6b4ea1b50ca8fca05a34bed5c8d56e34c1cb5ffb04cf76109ac3c/numpy-2.4.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:130d58151c4db23e9fa860b84784e219a3aa3e030acc88a493ea37006c4dfd4c", size = 17085598, upload-time = "2026-05-15T20:24:47.603Z" }, - { url = "https://files.pythonhosted.org/packages/0b/e8/a472b2564cf6cc498ad7aa9741d9832648221b8ab8cc0dbef41faa248ede/numpy-2.4.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d475afc8cbe935ff5944f753d863bba774d7f4e1feaaa4102901e3e053ca5963", size = 18403855, upload-time = "2026-05-15T20:24:50.474Z" }, - { url = "https://files.pythonhosted.org/packages/b9/a4/da82196f8cc4bd28ecf17bd57008c84f3d4696caf06753d9bad45e4ad749/numpy-2.4.5-cp314-cp314t-win32.whl", hash = "sha256:27f4a6dc26353a860b348961b9aa9e009835688b435cfa105e873b8dc2c726f5", size = 6156900, upload-time = "2026-05-15T20:24:53.134Z" }, - { url = "https://files.pythonhosted.org/packages/98/31/860959b91a73d9a085006554fa3850da51a7ffab64599bac5097243438ab/numpy-2.4.5-cp314-cp314t-win_amd64.whl", hash = "sha256:76ac6e90f5e226011c88f9b7040a4bcae612518bc7e9adc127e697a13b28ad1a", size = 12638906, upload-time = "2026-05-15T20:24:55.009Z" }, - { url = "https://files.pythonhosted.org/packages/9e/2a/bbd3097913083ad07c0f28fc9629666221fc18923e17ce97ae22a5dccdd6/numpy-2.4.5-cp314-cp314t-win_arm64.whl", hash = "sha256:7c392e2c1bf596701d3c6832be7567eab5d5b0a13865036c33365ee097d37f8b", size = 10565875, upload-time = "2026-05-15T20:24:57.425Z" }, - { url = "https://files.pythonhosted.org/packages/fc/5d/9a644cfb841bc76b584afc3af1708b3bf6c5cb51fc84a7008246cd93b7b7/numpy-2.4.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6bf0bfc1c2e1db972e30b6cd3d4861f477f3af908b27799b239dc3cbe3eb4b95", size = 16847544, upload-time = "2026-05-15T20:24:59.746Z" }, - { url = "https://files.pythonhosted.org/packages/56/8f/4fe5e3ba76d858dae1fe79078818c0520447335be0082c0dedf82719cc08/numpy-2.4.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:73d664413fb97229149c4711ef56531a6fe8c15c1c2626b0bbe497b84c287e70", size = 14889039, upload-time = "2026-05-15T20:25:03.179Z" }, - { url = "https://files.pythonhosted.org/packages/8e/6f/79f195abf922ecc43e7d0eb6cc969462a71b524a35bcd1fa26b4a1d7406a/numpy-2.4.5-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:b35bee5ef99e8d227a07829bee2e864fcb65f7c157646fcd8ec8b4b45dd8b88f", size = 5394106, upload-time = "2026-05-15T20:25:05.659Z" }, - { url = "https://files.pythonhosted.org/packages/58/6f/79cd6247205802bcbd10b40ea087e20ded526e10e9be224d34de832b216e/numpy-2.4.5-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:02981d0fc9f9ce147643d552966d47f329a02f7ecb3b113e84207242f20dfa83", size = 6708718, upload-time = "2026-05-15T20:25:08.071Z" }, - { url = "https://files.pythonhosted.org/packages/d7/22/5f378a9d4633c98f28c4709d4144b1a4630c5c09e109d2e781e2d26c8fe1/numpy-2.4.5-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0e63caf31a1df06338ae63d999f7a33a675ced62eea9c9b02db4b1c1f45cff38", size = 15798292, upload-time = "2026-05-15T20:25:10.689Z" }, - { url = "https://files.pythonhosted.org/packages/63/1c/cec582febef798c99888892d92dc1d28dfe29cb427c41f44d13d0dec208f/numpy-2.4.5-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d8fc52b85a7b45e474be53eddf08e006d22e381a4e41bcde8e4aa08da0e7d198", size = 16747406, upload-time = "2026-05-15T20:25:13.879Z" }, - { url = "https://files.pythonhosted.org/packages/b1/dc/d358a16a6fec86cf736b8fbe67386044b3fa2aded1a80cff90e836799301/numpy-2.4.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:40c71d50a4da1a7c317af419461052d3911a5770bfc5fd55baf52cc45e7a2c20", size = 12504085, upload-time = "2026-05-15T20:25:16.667Z" }, +version = "2.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0d/f5957185c0ee2f3e12f78715aa9e3b353fd83633316c8532b38faa37e3f6/numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d", size = 14964111, upload-time = "2026-05-18T23:33:17.795Z" }, + { url = "https://files.pythonhosted.org/packages/ad/40/40a40ee0ddf7ceb782c49af278894b686e586d65d8c1889c8b5da01a3d7d/numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8", size = 5469159, upload-time = "2026-05-18T23:33:20.654Z" }, + { url = "https://files.pythonhosted.org/packages/63/13/f9a8046535cb21deae82f8d03de9617e08882d274fad2539630761888228/numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538", size = 6798936, upload-time = "2026-05-18T23:33:22.987Z" }, + { url = "https://files.pythonhosted.org/packages/33/a8/6fa8c1a345a8c85dbb21932c447bee07c30a2c2a3f31e369c0a84b300147/numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47", size = 15966692, upload-time = "2026-05-18T23:33:26.62Z" }, + { url = "https://files.pythonhosted.org/packages/02/03/74fe2a4cb3817d94d86402f2506554130a2f01414e299b5a843e5a8a957f/numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93", size = 16918164, upload-time = "2026-05-18T23:33:29.955Z" }, + { url = "https://files.pythonhosted.org/packages/c5/80/3615be3313f7e7696609bc194b9f0101da809df79e859bdb84e0cd043f46/numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8", size = 17322877, upload-time = "2026-05-18T23:33:34.724Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ac/a691e0fe2675e370d0e08ff905adc49a1c8830e8cae03efe4477e92cd55d/numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6", size = 18651487, upload-time = "2026-05-18T23:33:38.217Z" }, + { url = "https://files.pythonhosted.org/packages/15/a7/9bc1cd626d7bf6869bfedf27b91b6ab5dd607758bf8e959d6fa80c6a59cb/numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8", size = 6233945, upload-time = "2026-05-18T23:33:41.331Z" }, + { url = "https://files.pythonhosted.org/packages/c5/31/7fc6239c12bce7e931463251cca4426c465e1876ba3cc785402ef4dd8f4e/numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147", size = 12608406, upload-time = "2026-05-18T23:33:44.131Z" }, + { url = "https://files.pythonhosted.org/packages/27/83/140f85a466595a16382996a1bf06b2b54bcd597488921b0c9daaeeda72af/numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577", size = 10479528, upload-time = "2026-05-18T23:33:50.725Z" }, + { url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119, upload-time = "2026-05-18T23:33:54.065Z" }, + { url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246, upload-time = "2026-05-18T23:33:57.621Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410, upload-time = "2026-05-18T23:34:00.302Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240, upload-time = "2026-05-18T23:34:02.852Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012, upload-time = "2026-05-18T23:34:05.485Z" }, + { url = "https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538, upload-time = "2026-05-18T23:34:09.265Z" }, + { url = "https://files.pythonhosted.org/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706, upload-time = "2026-05-18T23:34:13.053Z" }, + { url = "https://files.pythonhosted.org/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541, upload-time = "2026-05-18T23:34:17.024Z" }, + { url = "https://files.pythonhosted.org/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825, upload-time = "2026-05-18T23:34:20.3Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687, upload-time = "2026-05-18T23:34:23.095Z" }, + { url = "https://files.pythonhosted.org/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482, upload-time = "2026-05-18T23:34:25.876Z" }, + { url = "https://files.pythonhosted.org/packages/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648, upload-time = "2026-05-18T23:34:29.41Z" }, + { url = "https://files.pythonhosted.org/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902, upload-time = "2026-05-18T23:34:33.013Z" }, + { url = "https://files.pythonhosted.org/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992, upload-time = "2026-05-18T23:34:36.132Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944, upload-time = "2026-05-18T23:34:38.484Z" }, + { url = "https://files.pythonhosted.org/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392, upload-time = "2026-05-18T23:34:41.257Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220, upload-time = "2026-05-18T23:34:45.075Z" }, + { url = "https://files.pythonhosted.org/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800, upload-time = "2026-05-18T23:34:49.065Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600, upload-time = "2026-05-18T23:34:52.709Z" }, + { url = "https://files.pythonhosted.org/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134, upload-time = "2026-05-18T23:34:55.618Z" }, + { url = "https://files.pythonhosted.org/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598, upload-time = "2026-05-18T23:34:58.928Z" }, + { url = "https://files.pythonhosted.org/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272, upload-time = "2026-05-18T23:35:02.167Z" }, + { url = "https://files.pythonhosted.org/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197, upload-time = "2026-05-18T23:35:05.468Z" }, + { url = "https://files.pythonhosted.org/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287, upload-time = "2026-05-18T23:35:08.693Z" }, + { url = "https://files.pythonhosted.org/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763, upload-time = "2026-05-18T23:35:11.459Z" }, + { url = "https://files.pythonhosted.org/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070, upload-time = "2026-05-18T23:35:14.79Z" }, + { url = "https://files.pythonhosted.org/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752, upload-time = "2026-05-18T23:35:18.836Z" }, + { url = "https://files.pythonhosted.org/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024, upload-time = "2026-05-18T23:35:22.52Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398, upload-time = "2026-05-18T23:35:26.398Z" }, + { url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971, upload-time = "2026-05-18T23:35:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532, upload-time = "2026-05-18T23:35:32.175Z" }, + { url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881, upload-time = "2026-05-18T23:35:35.465Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" }, + { url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" }, + { url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" }, + { url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" }, + { url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" }, + { url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" }, + { url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" }, + { url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" }, + { url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" }, + { url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" }, + { url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" }, + { url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" }, + { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" }, + { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" }, + { url = "https://files.pythonhosted.org/packages/de/12/b422cc84439adc0d00de605bf4a308890ae5c26f2c71fbd73e5d08fbb0dd/numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", size = 16847511, upload-time = "2026-05-18T23:36:50.673Z" }, + { url = "https://files.pythonhosted.org/packages/44/53/f481bef68011740f8849418d82db07230e825013f31f4eef5ba5b805316a/numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7", size = 14889064, upload-time = "2026-05-18T23:36:53.879Z" }, + { url = "https://files.pythonhosted.org/packages/7f/57/42ed575c10ced8af951d426bc4e1f8aff16fd851db33f067036215a7f860/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", size = 5394157, upload-time = "2026-05-18T23:36:57.194Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ef/f66cc724fcc36c1e364c67f51ae9146090b8b584f27d58b97fdae3edd737/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c", size = 6708728, upload-time = "2026-05-18T23:36:59.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/9c/c531f2293b91265d8b48e9b329f54fdd7ffae73cb4134ea10cca4237e9cc/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0", size = 15798374, upload-time = "2026-05-18T23:37:02.674Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b0/413077f6b1153ed3cba361401c6783bbad6114804a000cc22eb71c13e190/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02", size = 16747286, upload-time = "2026-05-18T23:37:06.327Z" }, + { url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" }, ] [[package]] @@ -2475,17 +2491,17 @@ wheels = [ [[package]] name = "protobuf" -version = "7.34.1" +version = "7.35.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6b/6b/a0e95cad1ad7cc3f2c6821fcab91671bd5b78bd42afb357bb4765f29bc41/protobuf-7.34.1.tar.gz", hash = "sha256:9ce42245e704cc5027be797c1db1eb93184d44d1cdd71811fb2d9b25ad541280", size = 454708, upload-time = "2026-03-20T17:34:47.036Z" } +sdist = { url = "https://files.pythonhosted.org/packages/60/fd/5b1491d9e4b586d621c54f4c36b888714164b6875f8d6afa3f9072906a51/protobuf-7.35.0.tar.gz", hash = "sha256:a2efd84605f41e559f1881b0912b44099d0a2ac9bf46b3474823f10fb393b0e6", size = 458677, upload-time = "2026-05-19T23:02:29.197Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/11/3325d41e6ee15bf1125654301211247b042563bcc898784351252549a8ad/protobuf-7.34.1-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:d8b2cc79c4d8f62b293ad9b11ec3aebce9af481fa73e64556969f7345ebf9fc7", size = 429247, upload-time = "2026-03-20T17:34:37.024Z" }, - { url = "https://files.pythonhosted.org/packages/eb/9d/aa69df2724ff63efa6f72307b483ce0827f4347cc6d6df24b59e26659fef/protobuf-7.34.1-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:5185e0e948d07abe94bb76ec9b8416b604cfe5da6f871d67aad30cbf24c3110b", size = 325753, upload-time = "2026-03-20T17:34:38.751Z" }, - { url = "https://files.pythonhosted.org/packages/92/e8/d174c91fd48e50101943f042b09af9029064810b734e4160bbe282fa1caa/protobuf-7.34.1-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:403b093a6e28a960372b44e5eb081775c9b056e816a8029c61231743d63f881a", size = 340198, upload-time = "2026-03-20T17:34:39.871Z" }, - { url = "https://files.pythonhosted.org/packages/53/1b/3b431694a4dc6d37b9f653f0c64b0a0d9ec074ee810710c0c3da21d67ba7/protobuf-7.34.1-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:8ff40ce8cd688f7265326b38d5a1bed9bfdf5e6723d49961432f83e21d5713e4", size = 324267, upload-time = "2026-03-20T17:34:41.1Z" }, - { url = "https://files.pythonhosted.org/packages/85/29/64de04a0ac142fb685fd09999bc3d337943fb386f3a0ec57f92fd8203f97/protobuf-7.34.1-cp310-abi3-win32.whl", hash = "sha256:34b84ce27680df7cca9f231043ada0daa55d0c44a2ddfaa58ec1d0d89d8bf60a", size = 426628, upload-time = "2026-03-20T17:34:42.536Z" }, - { url = "https://files.pythonhosted.org/packages/4d/87/cb5e585192a22b8bd457df5a2c16a75ea0db9674c3a0a39fc9347d84e075/protobuf-7.34.1-cp310-abi3-win_amd64.whl", hash = "sha256:e97b55646e6ce5cbb0954a8c28cd39a5869b59090dfaa7df4598a7fba869468c", size = 437901, upload-time = "2026-03-20T17:34:44.112Z" }, - { url = "https://files.pythonhosted.org/packages/88/95/608f665226bca68b736b79e457fded9a2a38c4f4379a4a7614303d9db3bc/protobuf-7.34.1-py3-none-any.whl", hash = "sha256:bb3812cd53aefea2b028ef42bd780f5b96407247f20c6ef7c679807e9d188f11", size = 170715, upload-time = "2026-03-20T17:34:45.384Z" }, + { url = "https://files.pythonhosted.org/packages/83/ee/93d06e358a4aa32280b00e722d3ea0a1f25fc3cc5778d80581c9cca2c10e/protobuf-7.35.0-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:66be6c513931c794fa92c080ffee41671390da3d79da219cf9c0c0907f035dda", size = 433225, upload-time = "2026-05-19T23:02:19.884Z" }, + { url = "https://files.pythonhosted.org/packages/8b/39/1c76c2da93f3c507e958e0aecee2391cc44d4625de6c728bbc555195b5a8/protobuf-7.35.0-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:fcbe42a4ac09d3ec9c987ddfcd956afd0b15f1ff613bd8371bde9405ffd5c8e5", size = 328847, upload-time = "2026-05-19T23:02:22.3Z" }, + { url = "https://files.pythonhosted.org/packages/91/1a/39f7ce90a238c1a987a4d81ec26379e02ca0aff367de68e4a1fa474215b9/protobuf-7.35.0-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:4cbf5cc286130e06a6c9bbefac442431173906dfcc979712183d4adcc01b37ee", size = 344030, upload-time = "2026-05-19T23:02:23.591Z" }, + { url = "https://files.pythonhosted.org/packages/70/5b/6baf9008817964454055ff3fe65f1de0b5f1e26c80c82f7fb108b7cd4ea3/protobuf-7.35.0-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:6c0f98f10c8a05ea30f8993dfef2de093d27b490fdae78bb60c8343795d55011", size = 327130, upload-time = "2026-05-19T23:02:24.637Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e5/e46adb0badc388bfb84877a5f9f026aff63f60e611016cf64dbe77e05446/protobuf-7.35.0-cp310-abi3-win32.whl", hash = "sha256:4c4617b83ade0e279d1d2bfe04025a1adb87f9ed657de038620dc0ff959357f6", size = 428946, upload-time = "2026-05-19T23:02:25.741Z" }, + { url = "https://files.pythonhosted.org/packages/a7/ab/547fbd9e16d879dd13c167478f8ae0a83a428008ca07a5e06acdc23ad473/protobuf-7.35.0-cp310-abi3-win_amd64.whl", hash = "sha256:f05bcadf9a2a6b8dda047007075135fb7d08c73d9177aabc067e1be46881a201", size = 439996, upload-time = "2026-05-19T23:02:26.808Z" }, + { url = "https://files.pythonhosted.org/packages/b8/ef/50433d346c56657a70d27f156c7b349ac59a068b01de4eb796e747eecc43/protobuf-7.35.0-py3-none-any.whl", hash = "sha256:c13f325cf242bad135c350629eeb5d54b24228eb472fb3e2e9ebbd4c5dc20ca0", size = 171659, upload-time = "2026-05-19T23:02:27.842Z" }, ] [[package]] @@ -3140,27 +3156,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.13" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/21/a7d5c126d5b557715ef81098f3db2fe20f622a039ff2e626af28d674ab80/ruff-0.15.13.tar.gz", hash = "sha256:f9d89f17f7ba7fb2ed42921f0df75da797a9a5d71bc39049e2c687cf2baf44b7", size = 4678180, upload-time = "2026-05-14T13:44:37.869Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/61/11d458dc6ac22504fd8e237b29dfd40504c7fbbcc8930402cfe51a8e63ed/ruff-0.15.13-py3-none-linux_armv6l.whl", hash = "sha256:444b580fc72fd6887e650acd3e575e18cdc79dbcf42fb4030b491057921f61f8", size = 10738279, upload-time = "2026-05-14T13:44:18.7Z" }, - { url = "https://files.pythonhosted.org/packages/86/ca/caa871ee7be718c45256fada4e16a218ee3e33f0c4a46b729a60a24912e6/ruff-0.15.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6590d009e7cb7ebf36f83dbdd44a3fa48a0994ff6f1cdc1b08006abe58f98dc7", size = 11124798, upload-time = "2026-05-14T13:44:06.427Z" }, - { url = "https://files.pythonhosted.org/packages/d3/19/43f5f2e568dddde567fc41f8471f9432c09563e19d3e617a48cfa52f8f0a/ruff-0.15.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1c26d2f66163deeb6e08d8b39fbbe983ce3c71cea06a6d7591cfd1421793c629", size = 10460761, upload-time = "2026-05-14T13:44:04.375Z" }, - { url = "https://files.pythonhosted.org/packages/99/df/cf938cd6de3003178f03ad7c1ea2a6c099468c03a35037985070b37e76be/ruff-0.15.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbd6f94b434f896308e4d57fb7bfde0d02b99f7a64b3bdab0fdfa6a864203a5", size = 10804451, upload-time = "2026-05-14T13:44:25.221Z" }, - { url = "https://files.pythonhosted.org/packages/c7/7d/5d0973129b154ded2225729169d7068f26b467760b146493fde138415f23/ruff-0.15.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf3259f3be4d181bda591da5db2571aed6853c6a048157756448020bc6c5cd22", size = 10534285, upload-time = "2026-05-14T13:44:08.888Z" }, - { url = "https://files.pythonhosted.org/packages/1f/e3/6b999bbc66cd51e5f073842bc2a3995e99c5e0e72e16b15e7261f7abf57a/ruff-0.15.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae9c17e5eb4430c154e76abc25d79a318190f5a997f38fb6b114416c5319ffc9", size = 11312063, upload-time = "2026-05-14T13:44:11.274Z" }, - { url = "https://files.pythonhosted.org/packages/af/5a/642639e9f5db04f1e97fbd6e091c6fd20725bdf072fb114d00eefb9e6eb8/ruff-0.15.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e2e39bff6c341f4b577a21b801326fab0b11847f48fcaa83f00a113c9b3cb55", size = 12183079, upload-time = "2026-05-14T13:44:01.634Z" }, - { url = "https://files.pythonhosted.org/packages/19/4c/7585735f6b53b0f12de13618b2f7d250a844f018822efc899df2e7b8295f/ruff-0.15.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e8d9a8e08013542e94d3220bc5b62cc3e5ef87c5f74bff367d3fac14fab013e6", size = 11440833, upload-time = "2026-05-14T13:43:59.043Z" }, - { url = "https://files.pythonhosted.org/packages/e8/31/bf1a0803d077e679cfeee5f2f67290a0fa79c7385b5d9a8c17b9db2c48f0/ruff-0.15.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc411dfebe5eebe55ce041c6ae080eb7668955e866daa2fbb16692a784f1c4ca", size = 11434486, upload-time = "2026-05-14T13:44:27.761Z" }, - { url = "https://files.pythonhosted.org/packages/e1/4e/62c9b999875d4f14db80f277c030578f5e249c9852d65b7ac7ad0b43c041/ruff-0.15.13-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:768494eb08b9cee54e2fd27969966f74db5a57f6eaa7a90fcb3306af34dfc4bd", size = 11385189, upload-time = "2026-05-14T13:44:13.704Z" }, - { url = "https://files.pythonhosted.org/packages/fc/89/7e959047a104df3eb12863447c110140191fc5b6c4f379ea2e803fcdb0e4/ruff-0.15.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:fb75f9a3a7e42ffe117d734494e6c5e5cb3565d66e12612cb63d0e572a41a5b6", size = 10781380, upload-time = "2026-05-14T13:43:56.734Z" }, - { url = "https://files.pythonhosted.org/packages/ff/52/5fd18f3b88cab63e88aa11516b3b4e1e5f720e5c330f8dbe5c26210f41f8/ruff-0.15.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8cb74dd33bb2f6613faf7fc03b660053b5ac4f80e706d5788c6335e2a8048d51", size = 10540605, upload-time = "2026-05-14T13:44:20.748Z" }, - { url = "https://files.pythonhosted.org/packages/e8/e0/9e35f338990d3e41a82875ff7053ffe97541dae81c9d02143177f381d572/ruff-0.15.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7ef823f817fcd191dc934e984be9cf4094f808effa16f2542ad8e821ba02bbf2", size = 11036554, upload-time = "2026-05-14T13:44:16.256Z" }, - { url = "https://files.pythonhosted.org/packages/c2/13/070fb048c24080fba188f66371e2a92785be257ad02242066dc7255ac6e9/ruff-0.15.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f345a13937bd7f09f6f5d19fa0721b0c103e00e7f62bc67089a8e5e037719e0b", size = 11528133, upload-time = "2026-05-14T13:44:22.808Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8c/b1e1666aef7fc6555094d73ae6cd981701781ae85b97ceefc0eebd0b4668/ruff-0.15.13-py3-none-win32.whl", hash = "sha256:4044f94208b3b05ba0fc4a4abd0558cf4d6459bd18325eead7fd8cc66f909b41", size = 10721455, upload-time = "2026-05-14T13:44:35.697Z" }, - { url = "https://files.pythonhosted.org/packages/ab/a6/870a3e8a50590bb92be184ad928c2922f088b00d9dc5c5ec7b924ee08c22/ruff-0.15.13-py3-none-win_amd64.whl", hash = "sha256:7064884d442b7d477b4e7473d12da7f08851d2b1982763c5d3f388a19468a1a4", size = 11900409, upload-time = "2026-05-14T13:44:30.389Z" }, - { url = "https://files.pythonhosted.org/packages/9b/36/9c015cd052fca743dae8cb2aeb16b551444787467db42ceab0fc968865af/ruff-0.15.13-py3-none-win_arm64.whl", hash = "sha256:2471da9bd1068c8c064b5fd9c0c4b6dddffd6369cb1cd68b29993b1709ff1b21", size = 11179336, upload-time = "2026-05-14T13:44:33.026Z" }, +version = "0.15.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/8a/8bce2894573e9dae6ff4d77fe34ad727d79b9e6238ad288c5638990d90f6/ruff-0.15.14.tar.gz", hash = "sha256:48e866b165be4a9bdbf310f7d3c9a07edef2fe8cd63ffeb4e00bb590506ebf9f", size = 4700910, upload-time = "2026-05-21T14:34:55.177Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/c8/74a92c6ff9fcfb4f1f947126d3ebee8389276e161ecc85de5bda7cda51bd/ruff-0.15.14-py3-none-linux_armv6l.whl", hash = "sha256:8dd2db9416e487c8d4b01fa7056bb02c4d05969d4f8d17a08c229c2f4ff3c108", size = 10739177, upload-time = "2026-05-21T14:34:37.332Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/254a35c20acc38a7223c9d2d594af12e794432464f2cdeb52af1dc4a892d/ruff-0.15.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:be4ff55af755bd71a00ab3dc6bd7ffc467bd76e0df6881e286c2e3d23e8fb43b", size = 11144969, upload-time = "2026-05-21T14:34:43.978Z" }, + { url = "https://files.pythonhosted.org/packages/56/9e/d13e40f83b8d0a94430e6778ce1d94a43b38cf2efe63278bdd2b4c65abbf/ruff-0.15.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:48d5909d7d06276ce7dde6d32bfa4b0d4cb2651145cd8ee4b440722cbc77832f", size = 10478207, upload-time = "2026-05-21T14:34:48.378Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f1/b15a7839fa4f332f8acec78e20564f26bb2d866e3d21710b877fd0263000/ruff-0.15.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca8cbfa94c4f90984a67561978602746d4cd27103568f745fa90eee3f0d4107d", size = 10818459, upload-time = "2026-05-21T14:34:22.318Z" }, + { url = "https://files.pythonhosted.org/packages/45/33/53d651177f84f94b400a0e27f8824eeada3dddc9d5ee8aeb048f4352a520/ruff-0.15.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a6bbc0333f1ab053423bcbf6226477d266ca7cec7738c4c8e3f55647803f3c4", size = 10541800, upload-time = "2026-05-21T14:34:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a6/868f87e0bf9786ed24b5d0d0ad8676b8a94fd1912f42cddf9cfc7857818a/ruff-0.15.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a24a4f7605d7003a6674d4387651effd939dead3fddd0f36561eb77a9a2e542", size = 11342149, upload-time = "2026-05-21T14:34:46.365Z" }, + { url = "https://files.pythonhosted.org/packages/a7/8b/38cd5c19faffdcc05a408d2b78edccc69492ab9720eadb49ea15ef80d768/ruff-0.15.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:049b5326e53ed80978f2fc041a280603f69dd6b0c95464342a2bb4572d9d9e2f", size = 12212563, upload-time = "2026-05-21T14:34:28.579Z" }, + { url = "https://files.pythonhosted.org/packages/3e/4d/a3c5b874a556d5731e3e657aaf04311bb76f0a5c3ec220ed43051be6b64b/ruff-0.15.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4ed42e6696c8dfa5f06728e6441993901f548eb92d73bc472cb5a38d1395fbf", size = 11493299, upload-time = "2026-05-21T14:34:41.836Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c0/56472c251d09858a53e51efbd485b09e1995d8731668b76d52e5dd6ee0f1/ruff-0.15.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:715c543cf450c4888251f91c52f1942a800541d9bddd7ac060aa4e6b77ae7cba", size = 11455931, upload-time = "2026-05-21T14:34:57.276Z" }, + { url = "https://files.pythonhosted.org/packages/2c/4a/e2e7b4d8dbf233d4eace59c75bc3435fa6d8bd3bae82d351d4e4300c0fd1/ruff-0.15.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ebab6013ec887d439d8b7593737a0a4ffb06d45d209d4e4bf2e92813082d3f", size = 11400794, upload-time = "2026-05-21T14:34:39.773Z" }, + { url = "https://files.pythonhosted.org/packages/97/c7/83c0539fe34c3e09136204d1e75d6052492364e0b3cb05e9465423f567d7/ruff-0.15.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:49072d36abdbe97a8dd7f480afe9c675699c0c495d4c84076e2c1203c4550581", size = 10804759, upload-time = "2026-05-21T14:34:31.045Z" }, + { url = "https://files.pythonhosted.org/packages/86/a6/18f2bfc095a2ab4a78745644e428205532ce6653a5d0fa8501572891534d/ruff-0.15.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:958522aee105068640c2c2ceae08f413ae44d922f52a1374ac13d6a96032fc93", size = 10539517, upload-time = "2026-05-21T14:34:53.064Z" }, + { url = "https://files.pythonhosted.org/packages/54/3a/5a8b3b69c654d4e4bf1d246ac5b49cbcdac6eaab6905925f8915f31e3b80/ruff-0.15.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f3707da619a143a2e8830e2abab8224478d69ace2d28cb6c20543ae97c36bf61", size = 11065169, upload-time = "2026-05-21T14:34:24.484Z" }, + { url = "https://files.pythonhosted.org/packages/ed/c5/8864e4e7925b836ea354b31d57641ec03830564e281a8b6f061f8c3e0ec1/ruff-0.15.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bb01d645694e3ec0102105d07ef2d53703970407d59c04e59d3ba0b7a1d53553", size = 11560214, upload-time = "2026-05-21T14:34:50.975Z" }, + { url = "https://files.pythonhosted.org/packages/36/38/012bf76752e1f89ed50b77b99532d90f3a3e287bc7918e1fc0948ac866ac/ruff-0.15.14-py3-none-win32.whl", hash = "sha256:6d0c1ad2a0ab718d39b6d8fd2217981ce4d625cd96a720095f798fb47d8b13e6", size = 10805548, upload-time = "2026-05-21T14:34:33.453Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b7/4ea2c170f10ad760fff2a5250beb18897719dc8b52b53a24cddbb9dd3f19/ruff-0.15.14-py3-none-win_amd64.whl", hash = "sha256:802342981e056db3851a7836e5b070f8f15f67d4a685ae2a6160939d364b2902", size = 11939523, upload-time = "2026-05-21T14:34:18.077Z" }, + { url = "https://files.pythonhosted.org/packages/62/d5/bc97ff895ec35cf3925d4bd60f3b39d822f377a446906ec9bcc87405e59b/ruff-0.15.14-py3-none-win_arm64.whl", hash = "sha256:ff47b90a9ef6a40c9e2f3b479c1fb78531adf055b94c1eba0a7ba04b31951826", size = 11208607, upload-time = "2026-05-21T14:34:26.525Z" }, ] [[package]] @@ -3330,64 +3346,59 @@ wheels = [ [[package]] name = "soupsieve" -version = "2.8.3" +version = "2.8.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } +sdist = { url = "https://files.pythonhosted.org/packages/47/2c/0a5f6f8ee0d5589e48c7640213ed5175d52cf540a06725b628cc1a45d6ce/soupsieve-2.8.4.tar.gz", hash = "sha256:e121fd02e975c695e4e9e8774a5ee35d74714b59307868dcc5319ad2d9e3328e", size = 121110, upload-time = "2026-05-24T13:55:57.154Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, + { url = "https://files.pythonhosted.org/packages/5e/f5/0c41cb68dcae6b7de4fac4188a3a9589e21fb31df21ea3a2e888db95e6c9/soupsieve-2.8.4-py3-none-any.whl", hash = "sha256:e7e6b0769c8f51ed59acab6e994b00621096cfb1c640a7509295987388fbaf65", size = 37304, upload-time = "2026-05-24T13:55:55.406Z" }, ] [[package]] name = "sqlalchemy" -version = "2.0.49" +version = "2.0.50" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/09/45/461788f35e0364a8da7bda51a1fe1b09762d0c32f12f63727998d85a873b/sqlalchemy-2.0.49.tar.gz", hash = "sha256:d15950a57a210e36dd4cec1aac22787e2a4d57ba9318233e2ef8b2daf9ff2d5f", size = 9898221, upload-time = "2026-04-03T16:38:11.704Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/60/b5/e3617cc67420f8f403efebd7b043128f94775e57e5b84e7255203390ceae/sqlalchemy-2.0.49-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5070135e1b7409c4161133aa525419b0062088ed77c92b1da95366ec5cbebbe", size = 2159126, upload-time = "2026-04-03T16:50:13.242Z" }, - { url = "https://files.pythonhosted.org/packages/20/9b/91ca80403b17cd389622a642699e5f6564096b698e7cdcbcbb6409898bc4/sqlalchemy-2.0.49-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ac7a3e245fd0310fd31495eb61af772e637bdf7d88ee81e7f10a3f271bff014", size = 3315509, upload-time = "2026-04-03T16:54:49.332Z" }, - { url = "https://files.pythonhosted.org/packages/b1/61/0722511d98c54de95acb327824cb759e8653789af2b1944ab1cc69d32565/sqlalchemy-2.0.49-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d4e5a0ceba319942fa6b585cf82539288a61e314ef006c1209f734551ab9536", size = 3315014, upload-time = "2026-04-03T16:56:56.376Z" }, - { url = "https://files.pythonhosted.org/packages/46/55/d514a653ffeb4cebf4b54c47bec32ee28ad89d39fafba16eeed1d81dccd5/sqlalchemy-2.0.49-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3ddcb27fb39171de36e207600116ac9dfd4ae46f86c82a9bf3934043e80ebb88", size = 3267388, upload-time = "2026-04-03T16:54:51.272Z" }, - { url = "https://files.pythonhosted.org/packages/2f/16/0dcc56cb6d3335c1671a2258f5d2cb8267c9a2260e27fde53cbfb1b3540a/sqlalchemy-2.0.49-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:32fe6a41ad97302db2931f05bb91abbcc65b5ce4c675cd44b972428dd2947700", size = 3289602, upload-time = "2026-04-03T16:56:57.63Z" }, - { url = "https://files.pythonhosted.org/packages/51/6c/f8ab6fb04470a133cd80608db40aa292e6bae5f162c3a3d4ab19544a67af/sqlalchemy-2.0.49-cp311-cp311-win32.whl", hash = "sha256:46d51518d53edfbe0563662c96954dc8fcace9832332b914375f45a99b77cc9a", size = 2119044, upload-time = "2026-04-03T17:00:53.455Z" }, - { url = "https://files.pythonhosted.org/packages/c4/59/55a6d627d04b6ebb290693681d7683c7da001eddf90b60cfcc41ee907978/sqlalchemy-2.0.49-cp311-cp311-win_amd64.whl", hash = "sha256:951d4a210744813be63019f3df343bf233b7432aadf0db54c75802247330d3af", size = 2143642, upload-time = "2026-04-03T17:00:54.769Z" }, - { url = "https://files.pythonhosted.org/packages/49/b3/2de412451330756aaaa72d27131db6dde23995efe62c941184e15242a5fa/sqlalchemy-2.0.49-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4bbccb45260e4ff1b7db0be80a9025bb1e6698bdb808b83fff0000f7a90b2c0b", size = 2157681, upload-time = "2026-04-03T16:53:07.132Z" }, - { url = "https://files.pythonhosted.org/packages/50/84/b2a56e2105bd11ebf9f0b93abddd748e1a78d592819099359aa98134a8bf/sqlalchemy-2.0.49-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb37f15714ec2652d574f021d479e78cd4eb9d04396dca36568fdfffb3487982", size = 3338976, upload-time = "2026-04-03T17:07:40Z" }, - { url = "https://files.pythonhosted.org/packages/2c/fa/65fcae2ed62f84ab72cf89536c7c3217a156e71a2c111b1305ab6f0690e2/sqlalchemy-2.0.49-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb9ec6436a820a4c006aad1ac351f12de2f2dbdaad171692ee457a02429b672", size = 3351937, upload-time = "2026-04-03T17:12:23.374Z" }, - { url = "https://files.pythonhosted.org/packages/f8/2f/6fd118563572a7fe475925742eb6b3443b2250e346a0cc27d8d408e73773/sqlalchemy-2.0.49-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8d6efc136f44a7e8bc8088507eaabbb8c2b55b3dbb63fe102c690da0ddebe55e", size = 3281646, upload-time = "2026-04-03T17:07:41.949Z" }, - { url = "https://files.pythonhosted.org/packages/c5/d7/410f4a007c65275b9cf82354adb4bb8ba587b176d0a6ee99caa16fe638f8/sqlalchemy-2.0.49-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e06e617e3d4fd9e51d385dfe45b077a41e9d1b033a7702551e3278ac597dc750", size = 3316695, upload-time = "2026-04-03T17:12:25.642Z" }, - { url = "https://files.pythonhosted.org/packages/d9/95/81f594aa60ded13273a844539041ccf1e66c5a7bed0a8e27810a3b52d522/sqlalchemy-2.0.49-cp312-cp312-win32.whl", hash = "sha256:83101a6930332b87653886c01d1ee7e294b1fe46a07dd9a2d2b4f91bcc88eec0", size = 2117483, upload-time = "2026-04-03T17:05:40.896Z" }, - { url = "https://files.pythonhosted.org/packages/47/9e/fd90114059175cac64e4fafa9bf3ac20584384d66de40793ae2e2f26f3bb/sqlalchemy-2.0.49-cp312-cp312-win_amd64.whl", hash = "sha256:618a308215b6cececb6240b9abde545e3acdabac7ae3e1d4e666896bf5ba44b4", size = 2144494, upload-time = "2026-04-03T17:05:42.282Z" }, - { url = "https://files.pythonhosted.org/packages/ae/81/81755f50eb2478eaf2049728491d4ea4f416c1eb013338682173259efa09/sqlalchemy-2.0.49-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df2d441bacf97022e81ad047e1597552eb3f83ca8a8f1a1fdd43cd7fe3898120", size = 2154547, upload-time = "2026-04-03T16:53:08.64Z" }, - { url = "https://files.pythonhosted.org/packages/a2/bc/3494270da80811d08bcfa247404292428c4fe16294932bce5593f215cad9/sqlalchemy-2.0.49-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8e20e511dc15265fb433571391ba313e10dd8ea7e509d51686a51313b4ac01a2", size = 3280782, upload-time = "2026-04-03T17:07:43.508Z" }, - { url = "https://files.pythonhosted.org/packages/cd/f5/038741f5e747a5f6ea3e72487211579d8cbea5eb9827a9cbd61d0108c4bd/sqlalchemy-2.0.49-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47604cb2159f8bbd5a1ab48a714557156320f20871ee64d550d8bf2683d980d3", size = 3297156, upload-time = "2026-04-03T17:12:27.697Z" }, - { url = "https://files.pythonhosted.org/packages/88/50/a6af0ff9dc954b43a65ca9b5367334e45d99684c90a3d3413fc19a02d43c/sqlalchemy-2.0.49-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:22d8798819f86720bc646ab015baff5ea4c971d68121cb36e2ebc2ee43ead2b7", size = 3228832, upload-time = "2026-04-03T17:07:45.38Z" }, - { url = "https://files.pythonhosted.org/packages/bc/d1/5f6bdad8de0bf546fc74370939621396515e0cdb9067402d6ba1b8afbe9a/sqlalchemy-2.0.49-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9b1c058c171b739e7c330760044803099c7fff11511e3ab3573e5327116a9c33", size = 3267000, upload-time = "2026-04-03T17:12:29.657Z" }, - { url = "https://files.pythonhosted.org/packages/f7/30/ad62227b4a9819a5e1c6abff77c0f614fa7c9326e5a3bdbee90f7139382b/sqlalchemy-2.0.49-cp313-cp313-win32.whl", hash = "sha256:a143af2ea6672f2af3f44ed8f9cd020e9cc34c56f0e8db12019d5d9ecf41cb3b", size = 2115641, upload-time = "2026-04-03T17:05:43.989Z" }, - { url = "https://files.pythonhosted.org/packages/17/3a/7215b1b7d6d49dc9a87211be44562077f5f04f9bb5a59552c1c8e2d98173/sqlalchemy-2.0.49-cp313-cp313-win_amd64.whl", hash = "sha256:12b04d1db2663b421fe072d638a138460a51d5a862403295671c4f3987fb9148", size = 2141498, upload-time = "2026-04-03T17:05:45.7Z" }, - { url = "https://files.pythonhosted.org/packages/28/4b/52a0cb2687a9cd1648252bb257be5a1ba2c2ded20ba695c65756a55a15a4/sqlalchemy-2.0.49-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24bd94bb301ec672d8f0623eba9226cc90d775d25a0c92b5f8e4965d7f3a1518", size = 3560807, upload-time = "2026-04-03T16:58:31.666Z" }, - { url = "https://files.pythonhosted.org/packages/8c/d8/fda95459204877eed0458550d6c7c64c98cc50c2d8d618026737de9ed41a/sqlalchemy-2.0.49-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a51d3db74ba489266ef55c7a4534eb0b8db9a326553df481c11e5d7660c8364d", size = 3527481, upload-time = "2026-04-03T17:06:00.155Z" }, - { url = "https://files.pythonhosted.org/packages/ff/0a/2aac8b78ac6487240cf7afef8f203ca783e8796002dc0cf65c4ee99ff8bb/sqlalchemy-2.0.49-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:55250fe61d6ebfd6934a272ee16ef1244e0f16b7af6cd18ab5b1fc9f08631db0", size = 3468565, upload-time = "2026-04-03T16:58:33.414Z" }, - { url = "https://files.pythonhosted.org/packages/a5/3d/ce71cfa82c50a373fd2148b3c870be05027155ce791dc9a5dcf439790b8b/sqlalchemy-2.0.49-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:46796877b47034b559a593d7e4b549aba151dae73f9e78212a3478161c12ab08", size = 3477769, upload-time = "2026-04-03T17:06:02.787Z" }, - { url = "https://files.pythonhosted.org/packages/d5/e8/0a9f5c1f7c6f9ca480319bf57c2d7423f08d31445974167a27d14483c948/sqlalchemy-2.0.49-cp313-cp313t-win32.whl", hash = "sha256:9c4969a86e41454f2858256c39bdfb966a20961e9b58bf8749b65abf447e9a8d", size = 2143319, upload-time = "2026-04-03T17:02:04.328Z" }, - { url = "https://files.pythonhosted.org/packages/0e/51/fb5240729fbec73006e137c4f7a7918ffd583ab08921e6ff81a999d6517a/sqlalchemy-2.0.49-cp313-cp313t-win_amd64.whl", hash = "sha256:b9870d15ef00e4d0559ae10ee5bc71b654d1f20076dbe8bc7ed19b4c0625ceba", size = 2175104, upload-time = "2026-04-03T17:02:05.989Z" }, - { url = "https://files.pythonhosted.org/packages/55/33/bf28f618c0a9597d14e0b9ee7d1e0622faff738d44fe986ee287cdf1b8d0/sqlalchemy-2.0.49-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:233088b4b99ebcbc5258c755a097aa52fbf90727a03a5a80781c4b9c54347a2e", size = 2156356, upload-time = "2026-04-03T16:53:09.914Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a7/5f476227576cb8644650eff68cc35fa837d3802b997465c96b8340ced1e2/sqlalchemy-2.0.49-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57ca426a48eb2c682dae8204cd89ea8ab7031e2675120a47924fabc7caacbc2a", size = 3276486, upload-time = "2026-04-03T17:07:46.9Z" }, - { url = "https://files.pythonhosted.org/packages/2e/84/efc7c0bf3a1c5eef81d397f6fddac855becdbb11cb38ff957888603014a7/sqlalchemy-2.0.49-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:685e93e9c8f399b0c96a624799820176312f5ceef958c0f88215af4013d29066", size = 3281479, upload-time = "2026-04-03T17:12:32.226Z" }, - { url = "https://files.pythonhosted.org/packages/91/68/bb406fa4257099c67bd75f3f2261b129c63204b9155de0d450b37f004698/sqlalchemy-2.0.49-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e0400fa22f79acc334d9a6b185dc00a44a8e6578aa7e12d0ddcd8434152b187", size = 3226269, upload-time = "2026-04-03T17:07:48.678Z" }, - { url = "https://files.pythonhosted.org/packages/67/84/acb56c00cca9f251f437cb49e718e14f7687505749ea9255d7bd8158a6df/sqlalchemy-2.0.49-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a05977bffe9bffd2229f477fa75eabe3192b1b05f408961d1bebff8d1cd4d401", size = 3248260, upload-time = "2026-04-03T17:12:34.381Z" }, - { url = "https://files.pythonhosted.org/packages/56/19/6a20ea25606d1efd7bd1862149bb2a22d1451c3f851d23d887969201633f/sqlalchemy-2.0.49-cp314-cp314-win32.whl", hash = "sha256:0f2fa354ba106eafff2c14b0cc51f22801d1e8b2e4149342023bd6f0955de5f5", size = 2118463, upload-time = "2026-04-03T17:05:47.093Z" }, - { url = "https://files.pythonhosted.org/packages/cf/4f/8297e4ed88e80baa1f5aa3c484a0ee29ef3c69c7582f206c916973b75057/sqlalchemy-2.0.49-cp314-cp314-win_amd64.whl", hash = "sha256:77641d299179c37b89cf2343ca9972c88bb6eef0d5fc504a2f86afd15cd5adf5", size = 2144204, upload-time = "2026-04-03T17:05:48.694Z" }, - { url = "https://files.pythonhosted.org/packages/1f/33/95e7216df810c706e0cd3655a778604bbd319ed4f43333127d465a46862d/sqlalchemy-2.0.49-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c1dc3368794d522f43914e03312202523cc89692f5389c32bea0233924f8d977", size = 3565474, upload-time = "2026-04-03T16:58:35.128Z" }, - { url = "https://files.pythonhosted.org/packages/0c/a4/ed7b18d8ccf7f954a83af6bb73866f5bc6f5636f44c7731fbb741f72cc4f/sqlalchemy-2.0.49-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c821c47ecfe05cc32140dcf8dc6fd5d21971c86dbd56eabfe5ba07a64910c01", size = 3530567, upload-time = "2026-04-03T17:06:04.587Z" }, - { url = "https://files.pythonhosted.org/packages/73/a3/20faa869c7e21a827c4a2a42b41353a54b0f9f5e96df5087629c306df71e/sqlalchemy-2.0.49-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9c04bff9a5335eb95c6ecf1c117576a0aa560def274876fd156cfe5510fccc61", size = 3474282, upload-time = "2026-04-03T16:58:37.131Z" }, - { url = "https://files.pythonhosted.org/packages/b7/50/276b9a007aa0764304ad467eceb70b04822dc32092492ee5f322d559a4dc/sqlalchemy-2.0.49-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7f605a456948c35260e7b2a39f8952a26f077fd25653c37740ed186b90aaa68a", size = 3480406, upload-time = "2026-04-03T17:06:07.176Z" }, - { url = "https://files.pythonhosted.org/packages/e5/c3/c80fcdb41905a2df650c2a3e0337198b6848876e63d66fe9188ef9003d24/sqlalchemy-2.0.49-cp314-cp314t-win32.whl", hash = "sha256:6270d717b11c5476b0cbb21eedc8d4dbb7d1a956fd6c15a23e96f197a6193158", size = 2149151, upload-time = "2026-04-03T17:02:07.281Z" }, - { url = "https://files.pythonhosted.org/packages/05/52/9f1a62feab6ed368aff068524ff414f26a6daebc7361861035ae00b05530/sqlalchemy-2.0.49-cp314-cp314t-win_amd64.whl", hash = "sha256:275424295f4256fd301744b8f335cff367825d270f155d522b30c7bf49903ee7", size = 2184178, upload-time = "2026-04-03T17:02:08.623Z" }, - { url = "https://files.pythonhosted.org/packages/e5/30/8519fdde58a7bdf155b714359791ad1dc018b47d60269d5d160d311fdc36/sqlalchemy-2.0.49-py3-none-any.whl", hash = "sha256:ec44cfa7ef1a728e88ad41674de50f6db8cfdb3e2af84af86e0041aaf02d43d0", size = 1942158, upload-time = "2026-04-03T16:53:44.135Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/57/da/6fbf010c8ebb347679d0d100b22fe9ba5e13fd04046c5df7280d2f0bf706/sqlalchemy-2.0.50.tar.gz", hash = "sha256:af5607d11ef90fd6a5c0549fe0045dce1663d427426bcfb506dcb5346a85a3b9", size = 9907424, upload-time = "2026-05-24T19:20:04.018Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/5d/3172686af1770e4de2805f919a51441085f589ddadf3dd76ec582f84f497/sqlalchemy-2.0.50-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1aa6e403663a9c43c8fef7ce4bdb4cf48bcd8d352e91deda2a99f963270bd508", size = 2161366, upload-time = "2026-05-24T20:00:02.061Z" }, + { url = "https://files.pythonhosted.org/packages/0f/90/e98dedea3c3e663a17afcd003a34ba45efdac2cea3b6f2e4585e2b1e2537/sqlalchemy-2.0.50-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51b637a84f9fa35ae1f9017e786cb142974a25305085e1b378b3647a67f65ad3", size = 3318926, upload-time = "2026-05-24T20:07:42.369Z" }, + { url = "https://files.pythonhosted.org/packages/3b/4f/501308c2babb62c11753ecb4ee88ba9eef019419a4d6cbf7cb13e2bad353/sqlalchemy-2.0.50-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2dab927761d9108550f0cf8e66ff21af56f907a0ce0a689793db615e2b55f62c", size = 3319199, upload-time = "2026-05-24T20:14:28.551Z" }, + { url = "https://files.pythonhosted.org/packages/ac/39/d88996c5e03ed6248c3a788d20f0b8d8b376b9f8a495e4bab9df7c72d2f8/sqlalchemy-2.0.50-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:545eae198d37bcf837a10ede3684e2af32458d6f35c597c35c2de7502dc38fc4", size = 3270301, upload-time = "2026-05-24T20:07:44.917Z" }, + { url = "https://files.pythonhosted.org/packages/42/1b/1ae0e65161b51cc43e5ca75430ef79d80e23b5042d645586c2c342c3b92e/sqlalchemy-2.0.50-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fec460e18cdbb4c7773531122ce9a27e96c6ca17af3933941d94da475ad2c86", size = 3293465, upload-time = "2026-05-24T20:14:30.501Z" }, + { url = "https://files.pythonhosted.org/packages/83/29/17c0003f2c0dfa6d1b97672475707e3ec5980db09defd7fa20beb6833bbd/sqlalchemy-2.0.50-cp311-cp311-win32.whl", hash = "sha256:e6e814658818fd165e749e3d8490ef16cc7f379a118c37ada8b0589ffbaaac22", size = 2120694, upload-time = "2026-05-24T20:08:09.237Z" }, + { url = "https://files.pythonhosted.org/packages/c9/18/280d00654cc19d1fccf236fa5070f6dd04b84dde6f1b2e637bde0ff340a7/sqlalchemy-2.0.50-cp311-cp311-win_amd64.whl", hash = "sha256:1c5f858fe79c9f5d8fda065c06186356acb7f8df3cd52dbd5ee3f200e4b144f5", size = 2145315, upload-time = "2026-05-24T20:08:10.952Z" }, + { url = "https://files.pythonhosted.org/packages/be/b0/a9d19b43f38f878b1278bca5b00b909f7540d41494396dd2561f9ad0956d/sqlalchemy-2.0.50-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23ae23d8b9d344d30d0a92f06d45825024a5790f1c1dd4cf452636a50d3e58cb", size = 2159807, upload-time = "2026-05-24T19:27:53.086Z" }, + { url = "https://files.pythonhosted.org/packages/f5/2c/191dd58a248fd2cfd4780fa82c375c505e4ad98c8b522fa69ec492130d77/sqlalchemy-2.0.50-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47b71b933e7b4ebad407c8fdfd70d2c4f08b78b3238bb30eebdd6eb32ca51b89", size = 3343358, upload-time = "2026-05-24T20:09:29.279Z" }, + { url = "https://files.pythonhosted.org/packages/8a/2b/514fce8a7df81cf5bad7ff7865de7ac0c5776a38cc043475c4703eb7fe8b/sqlalchemy-2.0.50-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:110fdac56ace278949f00de805edacbd6141e382d992f9ba28238b3a0827a600", size = 3357994, upload-time = "2026-05-24T20:17:13.495Z" }, + { url = "https://files.pythonhosted.org/packages/35/a6/a0e283f5494f92b0d77e319ff77e437b1ffe4a051ba67c81d53234825475/sqlalchemy-2.0.50-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0f5e4ac70e9e757f6b3e87c0491ff034442ecd8dfd36d041a50564c322dafc0e", size = 3289399, upload-time = "2026-05-24T20:09:32.239Z" }, + { url = "https://files.pythonhosted.org/packages/b7/96/1b07325ba71752d6a028b77d07bed1483ad545f794e8b1dc89b3ba3b3c68/sqlalchemy-2.0.50-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:724f3dcbe53dd0151e3cb5e7ec4ba4c620bede579caacd16275dc35ce06e8615", size = 3321216, upload-time = "2026-05-24T20:17:15.581Z" }, + { url = "https://files.pythonhosted.org/packages/ed/8e/bad6ed253e8a99edfc99af02f7173ec48a1d3ed1b9b35a1b8bc1700900cc/sqlalchemy-2.0.50-cp312-cp312-win32.whl", hash = "sha256:1208050441471d003b7c8cb4054fb084f185cf35ac3f0ea270803865bca9939a", size = 2119194, upload-time = "2026-05-24T19:50:04.943Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2d/314a6690dda4b9cfc571eab1a63cf6fe6e1470aa3759ccda6aa016ee0f5a/sqlalchemy-2.0.50-cp312-cp312-win_amd64.whl", hash = "sha256:9d1af51558029a156a70986b7df88f042b3d158d7c8d8fb5072912d4b32d89c7", size = 2146186, upload-time = "2026-05-24T19:50:06.74Z" }, + { url = "https://files.pythonhosted.org/packages/0b/c4/c42356b527296e9862f67990efce31ef78b4cf69cd3f80873a528a060320/sqlalchemy-2.0.50-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:06a9210bdc5f4298cff0781087e2ff45683922252dacc452846373a58761f093", size = 2156697, upload-time = "2026-05-24T19:27:54.764Z" }, + { url = "https://files.pythonhosted.org/packages/60/a1/b1a70e3c4365ac7fe9e347f3710f19b562c866fb96d45e3c891588789a7b/sqlalchemy-2.0.50-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b53784972ade4f8174b9aa661f31a06f8a936d2cfdd602913ff3c6dd40ae873", size = 3284260, upload-time = "2026-05-24T20:09:34.195Z" }, + { url = "https://files.pythonhosted.org/packages/3f/4a/f3ac3caa19f263d57b0a47f8c91bbf56583dc2d3fc63acfbf644abb24fe0/sqlalchemy-2.0.50-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31648fa14460537e768a7303b078e4344d208e0d23e06867c1f376a227ed82db", size = 3302280, upload-time = "2026-05-24T20:17:17.825Z" }, + { url = "https://files.pythonhosted.org/packages/66/55/ccada3e3d62254587819749a0bc69f41173eb48a6e385d10e66d32a9c88e/sqlalchemy-2.0.50-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:03f4323c980ad0e918cc9e5369b015f759f4e534db5bbaf4dc36832c10d05064", size = 3231580, upload-time = "2026-05-24T20:09:36.406Z" }, + { url = "https://files.pythonhosted.org/packages/05/f6/6809349130a2de0e109e7f00fd7d431da9565b9b2868b32ee684754f672b/sqlalchemy-2.0.50-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2b9dcc43afef8ac157cd92fce96985d6b8b0cfbd3df4d666f66b4d55a75d202f", size = 3269375, upload-time = "2026-05-24T20:17:20.34Z" }, + { url = "https://files.pythonhosted.org/packages/48/84/278a811ef4e07be9c89dc5cdd7be833268509a66a68c4897cf585e67428f/sqlalchemy-2.0.50-cp313-cp313-win32.whl", hash = "sha256:60922d6599065ddca2c6f376b9aa2f41a6b85a271725e0909490bbc50b1998a5", size = 2117229, upload-time = "2026-05-24T19:50:08.215Z" }, + { url = "https://files.pythonhosted.org/packages/f6/1c/067cc6187ed32d2ec222fe6d2643acc1659a6d0659f8a7cbc5ad3ae83280/sqlalchemy-2.0.50-cp313-cp313-win_amd64.whl", hash = "sha256:287086e67275a212c4582d166a6fb03a65ccc5551d80866270ce0dd9f34eccd3", size = 2143126, upload-time = "2026-05-24T19:50:09.691Z" }, + { url = "https://files.pythonhosted.org/packages/df/32/10ac51b4be7cdecd7e93d069251c86dfbf70b7adbd7c67b48ccea6c49e1c/sqlalchemy-2.0.50-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c966932507a4d7d0a37314927dbfcd89720e3f37d2a1e3352e7ae7939fa8e8a0", size = 2158519, upload-time = "2026-05-24T19:27:56.472Z" }, + { url = "https://files.pythonhosted.org/packages/5a/76/e703d2f7681d7d66c4c891af3f07c7ccf4c76ad7f18351de035b5eda007a/sqlalchemy-2.0.50-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:faffef4bcc20a1892e65e155293d99d60855bbbc79250ab712819cfd56a8e6bb", size = 3282063, upload-time = "2026-05-24T20:09:38.57Z" }, + { url = "https://files.pythonhosted.org/packages/31/26/ef168b184a25701f9995e8fb7e503fafd7a99c1c77cda1bc1a26ea2ed486/sqlalchemy-2.0.50-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c206aec519a2e7bd08abbfb33436e325fd22c632d9c21a9047e376ce241646e", size = 3287069, upload-time = "2026-05-24T20:17:21.942Z" }, + { url = "https://files.pythonhosted.org/packages/c2/15/765acc2bc693bccc43ca4a95d5b69750da8aaf6db1b5c616536e087f8920/sqlalchemy-2.0.50-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bef4ac756363227ef6402a75fee025a4bc690f92328e825868939b3b3a446a6d", size = 3230453, upload-time = "2026-05-24T20:09:40.398Z" }, + { url = "https://files.pythonhosted.org/packages/63/61/08e03c3adbf5db0087a0b6816746fec8f3032fb2f7fc899a9bb9b2a48ce4/sqlalchemy-2.0.50-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:96fbee6b19c19cd1556c8bf9419447cf2ec149ffcab7ab64348c23e54ef8547f", size = 3252413, upload-time = "2026-05-24T20:17:24.067Z" }, + { url = "https://files.pythonhosted.org/packages/03/0c/370a1f2db38436c615e10134c8a37de3688e74084792380695f3f5083860/sqlalchemy-2.0.50-cp314-cp314-win32.whl", hash = "sha256:8f00e3eb43ba30eb1b238ee03a8a62309486d1321eda3328bb611e0340033ad8", size = 2120063, upload-time = "2026-05-24T19:50:11.08Z" }, + { url = "https://files.pythonhosted.org/packages/7f/a0/fe92bb9817863bc13ba093bda931979a26cc2ca69f8e8f26d07add3d7c6f/sqlalchemy-2.0.50-cp314-cp314-win_amd64.whl", hash = "sha256:15708c613cd5005b7dffe1f66ee6a63ee8f5e46799f71c70ebad74178c676a39", size = 2145830, upload-time = "2026-05-24T19:50:12.452Z" }, + { url = "https://files.pythonhosted.org/packages/cc/ff/e5640a98a0b2f491eb8fde10fb6c773621a2e44340de231fafcc9370f4a9/sqlalchemy-2.0.50-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3699dac4be410e97049a1658e9480da9cde956594aa0f3aebc60b88f21c5ba70", size = 2178435, upload-time = "2026-05-24T19:42:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/b7/85/337116e186f1236375b5fb70c21cfac98e8e8ab0d3a47be838dc47a59e08/sqlalchemy-2.0.50-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f96233858e3df43932ac11589e22520da6e8aeb624b03fedfeebb0e8ea213086", size = 3566059, upload-time = "2026-05-24T20:01:20.848Z" }, + { url = "https://files.pythonhosted.org/packages/96/34/bb0e190e161c3c2c24314a65add57218be14a4a9486886b7f5047c1ff7c8/sqlalchemy-2.0.50-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c4e70c46fad30c3bcc6a4708bc0130a3173e11a5b25f0ea4a9d8911b450f1f52", size = 3535366, upload-time = "2026-05-24T20:03:56.768Z" }, + { url = "https://files.pythonhosted.org/packages/df/5a/a7f759f97e4fd499c5d4e4488c760d5a7fbecf3028b465a04274fcd52384/sqlalchemy-2.0.50-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1918a3cf564d16d95bca7301005f41ab2ad50b07cd3b9da50d3ed986db148d6a", size = 3474879, upload-time = "2026-05-24T20:01:23.058Z" }, + { url = "https://files.pythonhosted.org/packages/9d/d9/2907ea38eb60687d297bf9c39e5ee58053c87b57fe8a9cae97090cecbf10/sqlalchemy-2.0.50-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b00098cdbdbd38c7be3d568b0c9c3122b8c0ec62b911b57cd5e6e0254d60a76d", size = 3486117, upload-time = "2026-05-24T20:03:59.052Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e3/5aa06f167559f8c0bdae487e297d23ba548150ab016a3418265d617a4985/sqlalchemy-2.0.50-cp314-cp314t-win32.whl", hash = "sha256:1fbd55a969d7ac44a98e3dec75016074f809fa08f871585ace58dde110d1bf3e", size = 2150823, upload-time = "2026-05-24T20:08:58.644Z" }, + { url = "https://files.pythonhosted.org/packages/65/9b/112fb8f977582d7489d036e409e3723948bcf5320b3ac465f3c481bbe8f9/sqlalchemy-2.0.50-cp314-cp314t-win_amd64.whl", hash = "sha256:c5c3cdb753a9004183e1ccb634b41611654c989e61bc68617ce878e46d6f1e51", size = 2185794, upload-time = "2026-05-24T20:09:00.319Z" }, + { url = "https://files.pythonhosted.org/packages/d0/10/f7220e9b784d295d241c86ed99aeb537f92afcd469a64861f2717e9bb077/sqlalchemy-2.0.50-py3-none-any.whl", hash = "sha256:92064363517a3ff8212b5a93b8c62876579d8dfd1ca5b561335f30152d884fa9", size = 1943861, upload-time = "2026-05-24T19:59:01.119Z" }, ] [[package]] @@ -3882,9 +3893,9 @@ wheels = [ [[package]] name = "zipp" -version = "3.23.1" +version = "4.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/21/093488dfc7cc8964ded15ab726fad40f25fd3d788fd741cc1c5a17d78ee8/zipp-3.23.1.tar.gz", hash = "sha256:32120e378d32cd9714ad503c1d024619063ec28aad2248dc6672ad13edfa5110", size = 25965, upload-time = "2026-04-13T23:21:46.6Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/d8/eab98a517c14134c0b2eb4e2387bc5f457334293ec5d2dd3857ec2966802/zipp-4.1.0.tar.gz", hash = "sha256:4cb57381f544315db7688e976e922a2b18cdb513d21cc194eb42232ba2a3e602", size = 26214, upload-time = "2026-05-18T20:08:57.967Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/08/8a/0861bec20485572fbddf3dfba2910e38fe249796cb73ecdeb74e07eeb8d3/zipp-3.23.1-py3-none-any.whl", hash = "sha256:0b3596c50a5c700c9cb40ba8d86d9f2cc4807e9bedb06bcdf7fac85633e444dc", size = 10378, upload-time = "2026-04-13T23:21:45.386Z" }, + { url = "https://files.pythonhosted.org/packages/3a/13/547360d81e6d88d58492968ffda9f9542854f11310ee556fef14260cc886/zipp-4.1.0-py3-none-any.whl", hash = "sha256:25ad4e16390cd314347dd8f1de67a2ac538ae658ed4ab9db16029c07c188e97f", size = 10238, upload-time = "2026-05-18T20:08:57.045Z" }, ] From d97d6a0e11b10328eb3f4d3063da9ea96382fbde Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 27 May 2026 18:41:11 -0700 Subject: [PATCH 309/335] add TODO for numpy/torch guarding, prevent numpy copy --- src/quantem/core/datastructures/dataset.py | 33 +++++++++++++------ .../core/datastructures/dataset4dstem.py | 2 ++ tests/datastructures/test_dataset.py | 18 ++++++++++ 3 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/quantem/core/datastructures/dataset.py b/src/quantem/core/datastructures/dataset.py index fa1fcef2..94744978 100644 --- a/src/quantem/core/datastructures/dataset.py +++ b/src/quantem/core/datastructures/dataset.py @@ -55,6 +55,7 @@ def __init__( ) super().__init__() # Dual-slot storage: exactly one of (_array, _tensor) is set. + # TODO: remove dual-init guards once torch transition is complete. if array is None and tensor is None: raise ValueError("Provide either `array` (numpy) or `tensor` (torch).") if array is not None and tensor is not None: @@ -229,37 +230,49 @@ def ndim(self) -> int: return (array if array is not None else self._tensor).ndim @property - def dtype(self) -> DTypeLike: + def dtype(self) -> DTypeLike | torch.dtype: array = getattr(self, "_array", None) return (array if array is not None else self._tensor).dtype @property def device(self) -> str: - """``"cpu"`` for numpy-backed; torch device string for tensor-backed.""" - tensor = getattr(self, "_tensor", None) - if tensor is not None: - return str(tensor.device) - return "cpu" + """Device string for the underlying storage. numpy 2.x ndarray and torch.Tensor + both expose ``.device`` (array-API convention), so this is uniform. + """ + array = getattr(self, "_array", None) + return str((array if array is not None else self._tensor).device) def numpy(self) -> NDArray: """Return the data as a numpy array (mirrors ``torch.Tensor.numpy()``). For numpy-backed datasets, returns ``self.array`` directly. For - tensor-backed datasets, materializes a CPU copy via ``.detach().cpu().numpy()``. + tensor-backed datasets, materializes a read-only CPU copy via + ``.detach().cpu().numpy()``. ``flags.writeable=False`` so accidental + in-place writes raise instead of silently being lost (the copy is not + the tensor). """ array = getattr(self, "_array", None) if array is not None: return array - return self._tensor.detach().cpu().numpy() + arr = self._tensor.detach().cpu().numpy() + arr.flags.writeable = False + return arr def to(self, device) -> Self: - """Move the underlying tensor to ``device``. Raises if numpy-backed.""" + """Move the underlying tensor to ``device``. Raises if numpy-backed. + + ``device`` is normalized via :func:`quantem.core.config.validate_device` + so values like ``"cuda"``, ``0``, ``"cuda:0"``, ``torch.device("cuda:0")`` + all resolve to the same canonical device. + """ + from quantem.core import config tensor = getattr(self, "_tensor", None) if tensor is None: raise AttributeError( f"Cannot .to({device!r}) on numpy-backed Dataset '{self.name}'." ) - self._tensor = tensor.to(device) + dev, _ = config.validate_device(device) + self._tensor = tensor.to(dev) return self # --- Summaries --- diff --git a/src/quantem/core/datastructures/dataset4dstem.py b/src/quantem/core/datastructures/dataset4dstem.py index 48d3f737..004db427 100644 --- a/src/quantem/core/datastructures/dataset4dstem.py +++ b/src/quantem/core/datastructures/dataset4dstem.py @@ -181,6 +181,8 @@ def from_tensor( For cupy / jax arrays, wrap with ``torch.from_dlpack(arr)`` first. """ + # TODO: factor type + ndim checks into `ensure_valid_tensor(value, ndim=4)` + # in validators.py, matching `ensure_valid_array` pattern. Cuts bloat. if not isinstance(tensor, torch.Tensor): raise TypeError( f"from_tensor requires torch.Tensor, got {type(tensor).__name__}. " diff --git a/tests/datastructures/test_dataset.py b/tests/datastructures/test_dataset.py index 9c83262c..201fe92b 100644 --- a/tests/datastructures/test_dataset.py +++ b/tests/datastructures/test_dataset.py @@ -434,3 +434,21 @@ def test_api_errors(self, sample_dataset_2d): # Neither specified with pytest.raises(ValueError): sample_dataset_2d.fourier_resample() + + +class TestDatasetTorch: + """Tests for torch-backed Dataset (from_tensor path).""" + + def test_numpy_copy_is_readonly(self): + """``.numpy()`` on a tensor-backed dataset returns a read-only CPU copy + so writes raise instead of silently updating only the detached copy. + """ + import torch + from quantem.core.datastructures.dataset4dstem import Dataset4dstem + ds = Dataset4dstem.from_tensor(torch.zeros(2, 2, 2, 2)) + arr = ds.numpy() + assert arr.flags.writeable is False + with pytest.raises(ValueError, match="read-only"): + arr[0, 0, 0, 0] = 99.0 + + From 990f35d27c674b51dfc3e0ce8bfacadc236efbc8 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Thu, 28 May 2026 14:09:11 -0700 Subject: [PATCH 310/335] SO3Params switched to static methods. --- src/quantem/core/ml/models/so3params.py | 145 ++++++++++++++++-------- 1 file changed, 98 insertions(+), 47 deletions(-) diff --git a/src/quantem/core/ml/models/so3params.py b/src/quantem/core/ml/models/so3params.py index 82a4361a..cd55d0ac 100644 --- a/src/quantem/core/ml/models/so3params.py +++ b/src/quantem/core/ml/models/so3params.py @@ -32,6 +32,78 @@ def __init__(self, T: int, init: str = "random"): quats = self._init_quaternions(T, init) # (T, 4) self.quats = nn.Parameter(quats) + @staticmethod + def quat_to_rotmat(q: torch.Tensor) -> torch.Tensor: + """Unit quaternion (..., 4) [x, y, z, w] -> rotation matrix (..., 3, 3). + Assumes q is already normalized.""" + x, y, z, w = q.unbind(dim=-1) + xx, yy, zz = x * x, y * y, z * z + xy, xz, yz = x * y, x * z, y * z + wx, wy, wz = w * x, w * y, w * z + R = torch.stack( + [ + 1 - 2 * (yy + zz), 2 * (xy - wz), 2 * (xz + wy), + 2 * (xy + wz), 1 - 2 * (xx + zz), 2 * (yz - wx), + 2 * (xz - wy), 2 * (yz + wx), 1 - 2 * (xx + yy), + ], + dim=-1, + ).reshape(*q.shape[:-1], 3, 3) + return R + + @staticmethod + def rotmat_to_quat(R: torch.Tensor) -> torch.Tensor: + """Rotation matrix (..., 3, 3) -> unit quaternion (..., 4) [x, y, z, w]. + + Shepperd's method: build the four candidate quaternions, each dividing + by a different diagonal combination, then per-element pick the branch + with the largest denominator so we never divide by a near-zero number. + The naive trace-only formula blows up when trace ~ -1 (180deg rotations). + """ + m00, m01, m02 = R[..., 0, 0], R[..., 0, 1], R[..., 0, 2] + m10, m11, m12 = R[..., 1, 0], R[..., 1, 1], R[..., 1, 2] + m20, m21, m22 = R[..., 2, 0], R[..., 2, 1], R[..., 2, 2] + + # 4 * (component^2) for w, x, y, z respectively; these sum to 4. + t = torch.stack( + [ + 1.0 + m00 + m11 + m22, # 4 w^2 + 1.0 + m00 - m11 - m22, # 4 x^2 + 1.0 - m00 + m11 - m22, # 4 y^2 + 1.0 - m00 - m11 + m22, # 4 z^2 + ], + dim=-1, + ) # (..., 4) + + eps = torch.finfo(R.dtype).eps + S = 2.0 * torch.sqrt(t.clamp_min(eps)) # S[k] = 4 * |component_k| + S0, S1, S2, S3 = S.unbind(-1) + + # each candidate in [x, y, z, w] order + cand_w = torch.stack([(m21 - m12) / S0, (m02 - m20) / S0, (m10 - m01) / S0, 0.25 * S0], dim=-1) + cand_x = torch.stack([0.25 * S1, (m01 + m10) / S1, (m02 + m20) / S1, (m21 - m12) / S1], dim=-1) + cand_y = torch.stack([(m01 + m10) / S2, 0.25 * S2, (m12 + m21) / S2, (m02 - m20) / S2], dim=-1) + cand_z = torch.stack([(m02 + m20) / S3, (m12 + m21) / S3, 0.25 * S3, (m10 - m01) / S3], dim=-1) + + cands = torch.stack([cand_w, cand_x, cand_y, cand_z], dim=-2) # (..., 4, 4) + idx = t.argmax(dim=-1) # (...,) + idx = idx[..., None, None].expand(*idx.shape, 1, 4) # (..., 1, 4) + q = cands.gather(-2, idx).squeeze(-2) # (..., 4) + return F.normalize(q, p=2, dim=-1) + + def as_matrix(self) -> torch.Tensor: + return self.quat_to_rotmat(self.normalized()) + + @classmethod + def from_matrix(cls, R: torch.Tensor) -> "SO3ParamQuat": + """Initialize a bank close to the given rotations R (T, 3, 3).""" + obj = cls(R.shape[0], init="identity") + with torch.no_grad(): + obj.quats.copy_(cls.rotmat_to_quat(R)) + return obj + + def extra_repr(self) -> str: + return f"T={self.quats.shape[0]}" + # ------------------------------------------------------------------ # Initialisers # ------------------------------------------------------------------ @@ -73,41 +145,6 @@ def normalized(self) -> torch.Tensor: """Returns (T, 4) unit quaternions.""" return F.normalize(self.quats, p=2, dim=-1) - def as_matrix(self) -> torch.Tensor: - """ - Converts the T stored quaternions to (T, 3, 3) rotation matrices. - - Uses the standard formula; no trig, just multiplications. - """ - q = self.normalized() # (T, 4) [x, y, z, w] - x, y, z, w = q.unbind(dim=-1) # each (T,) - - # Precompute products - xx, yy, zz = x * x, y * y, z * z - xy, xz, yz = x * y, x * z, y * z - wx, wy, wz = w * x, w * y, w * z - - # Row-major: R[i,j] - R = torch.stack( - [ - 1 - 2 * (yy + zz), - 2 * (xy - wz), - 2 * (xz + wy), - 2 * (xy + wz), - 1 - 2 * (xx + zz), - 2 * (yz - wx), - 2 * (xz - wy), - 2 * (yz + wx), - 1 - 2 * (xx + yy), - ], - dim=-1, - ).reshape(-1, 3, 3) # (T, 3, 3) - - return R - - def extra_repr(self) -> str: - return f"T={self.quats.shape[0]}" - class SO3ParamR9SVD(nn.Module): """ @@ -121,21 +158,35 @@ class SO3ParamR9SVD(nn.Module): def __init__(self, T: int, init: Literal["random", "identity"] = "random"): super().__init__() if init == "random": - # Initialize near identity with small noise - M = torch.eye(3).unsqueeze(0).repeat(T, 1, 1) - M = M + 0.1 * torch.randn(T, 3, 3) + M = torch.eye(3).unsqueeze(0).repeat(T, 1, 1) + 0.1 * torch.randn(T, 3, 3) elif init == "identity": M = torch.eye(3).unsqueeze(0).repeat(T, 1, 1) else: raise ValueError(f"Unknown init '{init}'") - self.M = nn.Parameter(M) # (T, 3, 3) + self.M = nn.Parameter(M) + + @staticmethod + def rotmat_to_r9(R: torch.Tensor) -> torch.Tensor: + """Rotation matrix (..., 3, 3) -> R9. Identity embedding: any R in SO(3) + is a fixed point of the SVD projection, so this just stores R directly.""" + return R + + @staticmethod + def r9_to_rotmat(M: torch.Tensor) -> torch.Tensor: + """R9 (..., 3, 3) -> nearest SO(3) matrix via SVD+.""" + U, _, Vh = torch.linalg.svd(M) + d = torch.det(U @ Vh) + diag = torch.ones(*M.shape[:-2], 3, device=M.device, dtype=M.dtype) + diag[..., 2] = d + return U @ (diag.unsqueeze(-1) * Vh) def as_matrix(self) -> torch.Tensor: - """Projects each M to SO(3) via SVD. Returns (T, 3, 3).""" - - U, _, Vh = torch.linalg.svd(self.M) # U: (T,3,3), Vh: (T,3,3) - # Fix reflections: det(U Vh) must be +1 - d = torch.det(U @ Vh) # (T,) - diag = torch.ones(self.M.shape[0], 3, device=self.M.device, dtype=self.M.dtype) - diag[:, 2] = d # multiply last singular vector by sign - return U @ (diag.unsqueeze(-1) * Vh) # (T, 3, 3) + return self.r9_to_rotmat(self.M) + + @classmethod + def from_matrix(cls, R: torch.Tensor) -> "SO3ParamR9SVD": + """Initialize a bank close to given rotations R (T, 3, 3).""" + obj = cls(R.shape[0], init="identity") + with torch.no_grad(): + obj.M.copy_(cls.rotmat_to_r9(R)) + return obj \ No newline at end of file From c56994a529269d7fc713ee32e0859961f9a6f642 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Thu, 28 May 2026 14:14:12 -0700 Subject: [PATCH 311/335] Fixed optimizer_params using hidden variable in set_optimizer --- src/quantem/core/ml/optimizer_mixin.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index 55d013c9..b1fee891 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -618,11 +618,11 @@ def set_optimizer(self, opt_params: OptimizerType | dict | None = None) -> None: if opt_params is not None: self.optimizer_params = opt_params - if not self._optimizer_params: + if not self.optimizer_params: self._optimizer = None return - if isinstance(self._optimizer_params, OptimizerParams.NoneOptimizer): + if isinstance(self.optimizer_params, OptimizerParams.NoneOptimizer): self.remove_optimizer() return @@ -633,11 +633,11 @@ def set_optimizer(self, opt_params: OptimizerType | dict | None = None) -> None: for p in group["params"]: p.requires_grad_(True) # Figure out which optimizer class to use - if isinstance(self._optimizer_params, dict): + if isinstance(self.optimizer_params, dict): # Per-group case: all groups must agree on the optimizer class, # and per-group hyperparameters are already baked into each dict # by get_optimization_parameters(). - opt_specs = list(self._optimizer_params.values()) + opt_specs = list(self.optimizer_params.values()) if not opt_specs: self._optimizer = None return @@ -651,8 +651,8 @@ def set_optimizer(self, opt_params: OptimizerType | dict | None = None) -> None: self._optimizer = optimizer_cls(params) # type:ignore else: # Single-optimizer case: splat global hyperparameters - optimizer_cls = self._optimizer_class_for(self._optimizer_params) - self._optimizer = optimizer_cls(params, **self._optimizer_params.params()) + optimizer_cls = self._optimizer_class_for(self.optimizer_params) + self._optimizer = optimizer_cls(params, **self.optimizer_params.params()) def _optimizer_class_for(self, opt_params) -> type[torch.optim.Optimizer]: match opt_params: From 4b8ca472a0d6fc99980c60d50739782870250e0a Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Thu, 28 May 2026 14:19:02 -0700 Subject: [PATCH 312/335] Added more description to the ReconstructionContext --- src/quantem/tomography/tomography_context.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/quantem/tomography/tomography_context.py b/src/quantem/tomography/tomography_context.py index ef861651..d322287c 100644 --- a/src/quantem/tomography/tomography_context.py +++ b/src/quantem/tomography/tomography_context.py @@ -14,6 +14,13 @@ class ReconstructionContext(BaseContext): - Pixelated reads ".volume" - INR reads ".coords" and recomputes via the model. - TensorDecomp reads ".coords" and ".pred" (and ".all densities") + + Variable descriptions: + - volume: Reconstructed object (volume). + - coords: Used for INR reconstructions to provide the coordinates to the model. + - pred: Predicted values per coordinate position from the model. + - all_densities: Integrated densities per ray from the model. + - obj: Object model (INR, TensorDecomp, etc.). """ volume: Optional[torch.Tensor] = None From 1bc17392f199257e3737ff7aa81c4a35eac8dda4 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Thu, 28 May 2026 14:28:40 -0700 Subject: [PATCH 313/335] Standardized optimizer params with new normalization to always output dict[str, OptimizerType]. --- src/quantem/core/ml/optimizer_mixin.py | 29 +++++++++++++------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index b1fee891..37264538 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -553,7 +553,7 @@ def scheduler(self) -> "torch.optim.lr_scheduler.LRScheduler | None": return self._scheduler @property - def optimizer_params(self) -> OptimizerType | dict[str, OptimizerType]: + def optimizer_params(self) -> dict[str, OptimizerType]: """Get the optimizer parameters.""" return self._optimizer_params @@ -565,20 +565,21 @@ def optimizer_params( def _normalize_optimizer_params( self, params: OptimizerType | dict[str, Any] - ) -> OptimizerType | dict[str, OptimizerType]: - """Normalize input. Subclasses can override to validate keys.""" - # dict-of-OptimizerType form (PPLR) - if isinstance(params, dict) and not self._is_single_optimizer_dict(params): - return { - k: v if isinstance(v, OptimizerType) else OptimizerParams.parse_dict(d=v) - for k, v in params.items() - } - # Single optimizer form (with dict shorthand like {"name": "adam", "lr": 1e-3}) - if isinstance(params, dict): - params = OptimizerParams.parse_dict(d=params) - if not isinstance(params, OptimizerType): + ) -> dict[str, OptimizerType]: + """Normalize input to dict[str, OptimizerType]. Subclasses can override to validate keys.""" + # Single optimizer, already an OptimizerType + if isinstance(params, OptimizerType): + return {self.DEFAULT_OPTIMIZER_KEY: params} + if not isinstance(params, dict): raise TypeError(f"optimizer_params must be OptimizerType or dict, got {type(params)}") - return params + # Single optimizer as dict shorthand, e.g. {"name": "adam", "lr": 1e-3} + if self._is_single_optimizer_dict(params): + return {self.DEFAULT_OPTIMIZER_KEY: OptimizerParams.parse_dict(d=params)} + # dict-of-OptimizerType form (PPLR) + return { + k: v if isinstance(v, OptimizerType) else OptimizerParams.parse_dict(d=v) + for k, v in params.items() + } @staticmethod def _is_single_optimizer_dict(d: dict) -> bool: From 21944c406c804d565d37c956bd7b69d8faa34dde Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Thu, 28 May 2026 14:28:40 -0700 Subject: [PATCH 314/335] Standardized optimizer params with new normalization to always output dict[str, OptimizerType]. --- src/quantem/core/ml/optimizer_mixin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index 37264538..937ef213 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -536,9 +536,9 @@ def __init__(self): """Initialize the optimizer mixin.""" self._optimizer = None self._scheduler = None - self._optimizer_params: OptimizerType | dict[str, OptimizerType] = ( - OptimizerParams.NoneOptimizer() - ) + self._optimizer_params: dict[str, OptimizerType] = { + self.DEFAULT_OPTIMIZER_KEY: OptimizerParams.NoneOptimizer() + } self._scheduler_params: SchedulerType = SchedulerParams.NoneScheduler() # Don't call super().__init__() in mixin classes to avoid MRO issues From e11e49e55c8c290d709cdd5cb717d7eddcf81f7d Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Thu, 28 May 2026 16:10:21 -0700 Subject: [PATCH 315/335] Fixed some bugs with DEFAULT_OPTIMIZER_KEY not being instantiated in optimizer_mixin.py --- src/quantem/core/ml/constraints.py | 3 ++- src/quantem/core/ml/optimizer_mixin.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/quantem/core/ml/constraints.py b/src/quantem/core/ml/constraints.py index fea1d309..590da4cb 100644 --- a/src/quantem/core/ml/constraints.py +++ b/src/quantem/core/ml/constraints.py @@ -7,7 +7,6 @@ import torch from numpy.typing import NDArray -T_ctx = TypeVar("T_ctx", bound=BaseContext) @dataclass class BaseContext(ABC): @@ -16,6 +15,8 @@ class BaseContext(ABC): """ pass +T_ctx = TypeVar("T_ctx", bound=BaseContext) + @dataclass(slots=False) class Constraints(ABC): """ diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index 937ef213..f970a859 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -531,6 +531,7 @@ class OptimizerMixin: """ DEFAULT_OPTIMIZER_TYPE = "adamw" + DEFAULT_OPTIMIZER_KEY = "default" def __init__(self): """Initialize the optimizer mixin.""" From b2e9e158d39dd7f2c38bc6fda5361b9e6d275c8a Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Thu, 28 May 2026 17:36:35 -0700 Subject: [PATCH 316/335] Working version, standardized optimizer_mixin.py --- src/quantem/core/ml/OPTIMIZER_PARAMS_FLOW.md | 221 ++++++++++++ src/quantem/core/ml/optimizer_mixin.py | 21 +- src/quantem/core/ml/optimizer_mixin_fix.html | 216 ++++++++++++ .../core/ml/optimizer_mixin_review.html | 319 ++++++++++++++++++ .../core/ml/optimizer_refactor_incident.html | 266 +++++++++++++++ 5 files changed, 1030 insertions(+), 13 deletions(-) create mode 100644 src/quantem/core/ml/OPTIMIZER_PARAMS_FLOW.md create mode 100644 src/quantem/core/ml/optimizer_mixin_fix.html create mode 100644 src/quantem/core/ml/optimizer_mixin_review.html create mode 100644 src/quantem/core/ml/optimizer_refactor_incident.html diff --git a/src/quantem/core/ml/OPTIMIZER_PARAMS_FLOW.md b/src/quantem/core/ml/OPTIMIZER_PARAMS_FLOW.md new file mode 100644 index 00000000..e1819034 --- /dev/null +++ b/src/quantem/core/ml/OPTIMIZER_PARAMS_FLOW.md @@ -0,0 +1,221 @@ +# Optimizer params: how inputs get normalized + +This document traces what happens to `optimizer_params` from the moment a user +passes them into `reconstruct(...)` to the moment a real `torch.optim.Optimizer` +is built. As of commit `1bc1739` the design invariant is: + +> **At the model level, `_optimizer_params` is *always* a `dict[str, OptimizerType]`.** + +Understanding the two normalization layers (container level → model level) is the +key to reading this code. + +--- + +## 1. The vocabulary + +| Thing | What it is | Where | +|---|---|---| +| `OptimizerType` | Union of the dataclasses `Adam`, `AdamW`, `SGD`, `NoneOptimizer` | `optimizer_mixin.py` (`OptimizerType = Adam \| AdamW \| SGD \| NoneOptimizer`) | +| `OptimizerParams.` | The individual dataclasses; each carries hyperparameters and a `.params()` method that returns them as a `dict` for torch | `optimizer_mixin.py` | +| `NoneOptimizer` | Sentinel meaning "do not optimize this thing". `.params()` returns `{}` | `optimizer_mixin.py:174` | +| `DEFAULT_OPTIMIZER_KEY` | `"default"` — the key used when a single optimizer is wrapped into a dict | `optimizer_mixin.py:534` | +| `OptimizerMixin` | Mixin inherited by each model (`obj_model`, `probe_model`, `dset`). Owns `_optimizer_params`, `set_optimizer`, etc. | `optimizer_mixin.py:527` | +| `PtychographyOpt` | The *container*. Holds the three models and exposes a combined `optimizer_params` | `ptychography_opt.py:20` | + +There are **two objects** that both have a property called `optimizer_params`, +and they mean different things: + +- **Container** (`PtychographyOpt` / tomography equivalent): a dict keyed by + *which model* — `"object"`, `"probe"`, `"dataset"`. +- **Model** (`OptimizerMixin`): a dict keyed by *parameter group* — normally just + the single key `"default"`. + +So a fully-resolved structure is **nested**: + +``` +container.optimizer_params + = {"object": {"default": Adam(lr=5e-3)}, + "probe": {"default": Adam(lr=1e-3)}, + "dataset": {"default": NoneOptimizer()}} + ^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^ + model key group key OptimizerType +``` + +--- + +## 2. Accepted input shapes + +A user can hand the container any of these: + +```python +# (a) list/tuple of model keys -> use all defaults +ptycho.optimizer_params = ["object", "probe"] + +# (b) dict, value = OptimizerType dataclass +ptycho.optimizer_params = {"object": OptimizerParams.Adam(lr=5e-3)} + +# (c) dict, value = shorthand dict (name/type + hyperparams) +ptycho.optimizer_params = {"object": {"name": "adam", "lr": 5e-3}} + +# (d) dict, value = empty dict -> use default optimizer + default lr for that key +ptycho.optimizer_params = {"object": {}} +``` + +--- + +## 3. Layer 1 — container normalization + +`PtychographyOpt.optimizer_params` **setter** (`ptychography_opt.py:56`): + +``` +list/tuple ──► {k: {} for k in list} # (a) becomes (d)-style +for each (key, value): + value is OptimizerType ───────────────► pass through unchanged + value is dict and empty ──────────────► replace(DEFAULT_OPTIMIZER_TYPE, + lr=_get_default_lr(key)) + value is dict and non-empty ──────────► inject "name" if missing, + inject "lr" if missing + else ─────────────────────────────────► TypeError + │ + └─► dispatch to the matching model: + "object" -> self.obj_model.optimizer_params = value + "probe" -> self.probe_model.optimizer_params = value + "dataset" -> self.dset.optimizer_params = value +``` + +Key points: + +- `_get_default_lr(key)` supplies a sensible LR per model + (`object` ≈ 5e-3, `probe`/`dataset` ≈ 1e-3) when the user omitted one. +- The container does **not** build torch optimizers. It just fills defaults and + forwards each value down to the relevant model's setter. +- Any model key *not* mentioned by the user keeps whatever it had — by default + `{"default": NoneOptimizer()}` (i.e. "not optimized"). + +--- + +## 4. Layer 2 — model normalization + +Each model's `optimizer_params` **setter** calls +`OptimizerMixin._normalize_optimizer_params` (`optimizer_mixin.py:567`). This is +the function that guarantees the `dict[str, OptimizerType]` invariant: + +``` +_normalize_optimizer_params(params): + + params is an OptimizerType (dataclass) + ──► {"default": params} + + params is NOT a dict + ──► TypeError + + params is a dict AND _is_single_optimizer_dict(params) # has "name" or "type" + ──► {"default": OptimizerParams.parse_dict(params)} + + otherwise (dict-of-OptimizerType, the "PPLR" form) + ──► {k: (v if v is OptimizerType else parse_dict(v)) + for k, v in params.items()} +``` + +`_is_single_optimizer_dict(d)` is simply `"type" in d or "name" in d` +(`optimizer_mixin.py:585`). + +### `parse_dict` — shorthand → dataclass + +`OptimizerParams.parse_dict` (`optimizer_mixin.py:192`) maps a shorthand dict to +the concrete dataclass: + +``` +{"name"/"type": ...} pop the name, lowercase it, then: + "adam" -> OptimizerParams.Adam(**rest) + "adamw" -> OptimizerParams.AdamW(**rest) + "sgd" -> OptimizerParams.SGD(**rest) + "none" -> OptimizerParams.NoneOptimizer() + else -> ValueError +``` + +### Idempotency note + +When the container forwards an already-resolved value like `{"default": Adam(lr=5e-3)}` +to a model setter, it has no `"name"`/`"type"` key, so it takes the *PPLR branch* +and is kept as-is (`Adam` is already an `OptimizerType`). So re-normalizing a +normalized dict is a no-op. Good. + +--- + +## 5. Building the torch optimizer + +`OptimizerMixin.set_optimizer` (`optimizer_mixin.py:614`) is where the normalized +dict becomes a real optimizer. Conceptually it should: + +1. Look at the values of the `dict[str, OptimizerType]`. +2. Drop / handle `NoneOptimizer` sentinels (→ "no optimizer for this"). +3. Confirm the remaining specs agree on an optimizer *class*. +4. Pull parameter groups from `get_optimization_parameters()` (a `list[dict]`, + each `{"params": [...]}`). +5. Construct `optimizer_cls(param_groups, **hyperparameters)`. + +`_optimizer_class_for` (`optimizer_mixin.py:659`) maps a spec dataclass to a torch +class via a `match`: + +``` +Adam() -> torch.optim.Adam +AdamW() -> torch.optim.AdamW +SGD() -> torch.optim.SGD +_ -> NotImplementedError # <-- NoneOptimizer lands here +``` + +### The reset path (where the current crash lives) + +`reconstruct(reset=True, ...)` calls `reset_recon()` **before** applying the +user's `optimizer_params` (`ptychography.py:181` vs `:185`). `reset_recon` calls +each model's `reset_optimizer()` → `set_optimizer(self._optimizer_params)`. At +that moment `_optimizer_params` is still the default `{"default": NoneOptimizer()}`. + +``` +reconstruct(reset=True, optimizer_params=opt_params) + ├─ reset_recon() # opt_params NOT applied yet + │ └─ obj_model.reset_optimizer() + │ └─ set_optimizer({"default": NoneOptimizer()}) + │ └─ _optimizer_class_for(NoneOptimizer()) -> NotImplementedError + └─ (never reached) self.optimizer_params = opt_params; set_optimizers() +``` + +--- + +## 6. Where the value flows at recon time + +Once past reset, the normal value flow is: + +``` +reconstruct(optimizer_params={"object": {"lr": 5e-3}, ...}) + │ + ├─ container.optimizer_params = {...} # Layer 1: fill defaults, dispatch + │ └─ obj_model.optimizer_params = {"name":"adamw","lr":5e-3} + │ └─ _normalize_optimizer_params -> {"default": AdamW(lr=5e-3)} # Layer 2 + │ + └─ container.set_optimizers() + for key, params in container.optimizer_params.items(): # nested dict + model.set_optimizer(params) # params = {"default": AdamW(lr=5e-3)} + └─ build torch.optim.AdamW(param_groups, lr=5e-3, ...) +``` + +`container.set_optimizers()` iterates the **container getter**, which returns the +nested `{"object": {"default": ...}, ...}` structure, and hands each inner dict to +the corresponding model's `set_optimizer`. + +--- + +## 7. Quick reference — invariants to keep in mind + +- A **model's** `_optimizer_params` is always `dict[str, OptimizerType]`, normally + one key: `"default"`. +- A **container's** `optimizer_params` is `dict[str, dict[str, OptimizerType]]`, + keyed by model name (`"object"`/`"probe"`/`"dataset"`). +- `NoneOptimizer` is a first-class `OptimizerType` meaning "skip". Anything that + consumes the dict must treat it specially, because `_optimizer_class_for` has no + case for it. +- `.params()` on a spec dataclass returns the kwargs torch needs (`lr`, etc.); + `NoneOptimizer.params()` returns `{}`. +- `get_optimization_parameters()` returns parameter *groups* (`[{"params": [...]}]`) + and currently carries **no** per-group hyperparameters. diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index f970a859..8c095d39 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -624,9 +624,6 @@ def set_optimizer(self, opt_params: OptimizerType | dict | None = None) -> None: self._optimizer = None return - if isinstance(self.optimizer_params, OptimizerParams.NoneOptimizer): - self.remove_optimizer() - return params = self.get_optimization_parameters() # always list[dict] @@ -634,14 +631,17 @@ def set_optimizer(self, opt_params: OptimizerType | dict | None = None) -> None: for group in params: for p in group["params"]: p.requires_grad_(True) - # Figure out which optimizer class to use + # Figure out which optimizer class to use if isinstance(self.optimizer_params, dict): # Per-group case: all groups must agree on the optimizer class, # and per-group hyperparameters are already baked into each dict # by get_optimization_parameters(). - opt_specs = list(self.optimizer_params.values()) + opt_specs = [ + spec for spec in self.optimizer_params.values() + if not isinstance(spec, OptimizerParams.NoneOptimizer) + ] if not opt_specs: - self._optimizer = None + self.remove_optimizer() return optimizer_cls = self._optimizer_class_for(opt_specs[0]) for spec in opt_specs[1:]: @@ -650,12 +650,7 @@ def set_optimizer(self, opt_params: OptimizerType | dict | None = None) -> None: f"All parameter groups must use the same optimizer type, " f"got {type(opt_specs[0]).__name__} and {type(spec).__name__}" ) - self._optimizer = optimizer_cls(params) # type:ignore - else: - # Single-optimizer case: splat global hyperparameters - optimizer_cls = self._optimizer_class_for(self.optimizer_params) - self._optimizer = optimizer_cls(params, **self.optimizer_params.params()) - + self._optimizer = optimizer_cls(params, **opt_specs[0].params()) def _optimizer_class_for(self, opt_params) -> type[torch.optim.Optimizer]: match opt_params: case OptimizerParams.Adam(): @@ -744,7 +739,7 @@ def get_current_lr(self) -> float: def remove_optimizer(self) -> None: """Remove the optimizer and scheduler.""" self._optimizer = None - self._optimizer_params = OptimizerParams.NoneOptimizer() + self._optimizer_params = {self.DEFAULT_OPTIMIZER_KEY: OptimizerParams.NoneOptimizer()} self._scheduler = None self._scheduler_params = SchedulerParams.NoneScheduler() diff --git a/src/quantem/core/ml/optimizer_mixin_fix.html b/src/quantem/core/ml/optimizer_mixin_fix.html new file mode 100644 index 00000000..3bb7c365 --- /dev/null +++ b/src/quantem/core/ml/optimizer_mixin_fix.html @@ -0,0 +1,216 @@ + + + + + +OptimizerMixin — minimal fixes to complete the dict[str, OptimizerType] standardization + + + +
+ +
+

Completing the dict[str, OptimizerType] standardization

+

Minimal, surgical fixes that keep your standardization goal intact and resolve the + Ptychography breakage.

+
+ FILE: src/quantem/core/ml/optimizer_mixin.py  ·  + SCOPE: consumer-side only  ·  + RISK: low, localized +
+
+ +

1. Diagnosis in one paragraph

+

Your goal — normalize everything to dict[str, OptimizerType] — is correct + and worth keeping. The standardization did not break Ptychography on its own. The breakage came + from a consumer that was never migrated to the new contract: + set_optimizer. It still branches on the old union shape (a bare + OptimizerType or a dict). After normalization always returns a dict, the two + branches that handled the bare-type cases became unreachable, and the surviving dict branch was + only a partial implementation — it never handled the NoneOptimizer sentinel and never + forwarded hyperparameters. The fix is therefore not to revert the standardization, + but to make that one dict branch self-sufficient.

+ +
+
Why your change was necessary but not sufficient
+

A return type is a contract. Narrowing it from a union to a single + shape obligates every consumer that pattern-matches on the old shape to be updated in the same + change. The producer was migrated; one consumer was not.

+
+ +

2. The two concrete symptoms

+ + + + + + + + + + +
SymptomRoot cause in set_optimizer
Crash: NotImplementedError: Unknown optimizer type: NoneOptimizer()The dict branch passes a NoneOptimizer spec to + _optimizer_class_for, which has no case for it. The old guard that caught this + (isinstance(..., NoneOptimizer)) only matches a bare sentinel and is now + unreachable.
Silent: configured lr ignoredThe dict branch builds optimizer_cls(params) with no kwargs. The old branch + that splatted **optimizer_params.params() is the now-unreachable + else.
+ +

3. Recommended minimal change set

+

Three changes, in priority order. REQUIRED alone resolves the + crash and the silent lr bug.

+ +

REQUIREDMake the dict branch self-sufficient · ~6 lines, one method

+

Filter out NoneOptimizer sentinels; if none remain, there is nothing to optimize. + Otherwise build the optimizer and forward the spec’s hyperparameters. This is the + smallest change that honors the new dict-only contract.

+
        if isinstance(self.optimizer_params, dict):
+-           opt_specs = list(self.optimizer_params.values())
+-           if not opt_specs:
+-               self._optimizer = None
+-               return
++           opt_specs = [
++               spec for spec in self.optimizer_params.values()
++               if not isinstance(spec, OptimizerParams.NoneOptimizer)
++           ]
++           if not opt_specs:
++               self.remove_optimizer()
++               return
+            optimizer_cls = self._optimizer_class_for(opt_specs[0])
+            for spec in opt_specs[1:]:
+                if type(spec) is not type(opt_specs[0]):
+                    raise ValueError(...)
+-           self._optimizer = optimizer_cls(params)  # type:ignore
++           self._optimizer = optimizer_cls(params, **opt_specs[0].params())
+
+
What this buys you
+

The default {"default": NoneOptimizer()} now filters to + an empty list → remove_optimizer() → clean no-op (this is the path + reset_recon() walks). A real {"default": Adam(lr=5e-3)} now builds + Adam(params, lr=5e-3, …) with your hyperparameters restored.

+
+
+
Note on PPLR
+

This splats opt_specs[0]’s hyperparameters across all + parameter groups. That is correct today because every model returns a single param group from + get_optimization_parameters(). True per-group LRs are a separate, larger piece of + work — see §5.

+
+ +

RECOMMENDEDKeep remove_optimizer on-contract · 1 line

+

It currently stores a bare sentinel, breaking the “always a dict” invariant. It works + only because the value gets re-normalized on the next set. Store the dict form to keep the + invariant true end-to-end.

+
    def remove_optimizer(self) -> None:
+        self._optimizer = None
+-       self._optimizer_params = OptimizerParams.NoneOptimizer()
++       self._optimizer_params = {self.DEFAULT_OPTIMIZER_KEY: OptimizerParams.NoneOptimizer()}
+        self._scheduler = None
+        self._scheduler_params = SchedulerParams.NoneScheduler()
+ +

OPTIONALDelete the now-dead branches · removes ~7 lines

+

Purely cosmetic, but it removes the misleading code that caused the confusion. With the dict + contract guaranteed, the bare-NoneOptimizer guard and the else branch can + never run. Removing them makes set_optimizer a single, honest path.

+
-       if isinstance(self.optimizer_params, OptimizerParams.NoneOptimizer):
+-           self.remove_optimizer()
+-           return
+        ...
+-       else:
+-           optimizer_cls = self._optimizer_class_for(self.optimizer_params)
+-           self._optimizer = optimizer_cls(params, **self.optimizer_params.params())
+

If you keep these, no harm — they’re dead, not wrong. Removing them is about + readability, not correctness.

+ +

4. Optional: align the container getters

+

OPTIONAL In PtychographyOpt.optimizer_params (and the + tomography equivalent) the filter if not isinstance(params, NoneOptimizer) no longer + fires, because params is now a dict. After the REQUIRED fix this is harmless — a + disabled model simply ends up with no optimizer — but if you want the getter to exclude disabled + models, check the inner specs instead.

+
-           if not isinstance(params, OptimizerParams.NoneOptimizer)
++           if any(not isinstance(s, OptimizerParams.NoneOptimizer) for s in params.values())
+ +

5. What to leave for later (don’t scope-creep)

+
    +
  • Real per-parameter LRs (PPLR). The comment claiming hyperparameters are “baked into + each dict by get_optimization_parameters()” is aspirational — the implementations + return [{"params": params}] with no lr. Either implement keyed param + groups or update the comment. Not needed to fix the break.
  • +
  • Collapsing the three “no optimizer” spellings (empty dict / NoneOptimizer / + _optimizer = None) into one. A nice tidy-up, but orthogonal to this incident.
  • +
+ +

6. Verification checklist

+ + + + + + + + + + +
StepExpected
ptycho.reconstruct(num_iters=1, reset=True, optimizer_params={"object": {"lr": 5e-3}})No NotImplementedError; runs.
Inspect ptycho.obj_model.optimizer.param_groups[0]["lr"]Equals 5e-3, not torch’s default.
A model left out of optimizer_paramshas_optimizer() is False; not stepped.
Re-run with reset=TrueDefault NoneOptimizer filters cleanly; no crash.
+ +
+
Bottom line
+

Keep your standardization. Apply the one REQUIRED change (and ideally + the one-line RECOMMENDED change). That completes the migration the type-narrowing started, with a + change set small enough to read in a single diff.

+
+ +

+ Read-only proposal · no source files modified · + companions: optimizer_mixin_review.html · optimizer_refactor_incident.html · OPTIMIZER_PARAMS_FLOW.md +

+ +
+ + diff --git a/src/quantem/core/ml/optimizer_mixin_review.html b/src/quantem/core/ml/optimizer_mixin_review.html new file mode 100644 index 00000000..1e205644 --- /dev/null +++ b/src/quantem/core/ml/optimizer_mixin_review.html @@ -0,0 +1,319 @@ + + + + + +OptimizerMixin — a friendly post-mortem & cleanup wishlist + + + +
+ +
+ Code review · ml/optimizer_mixin.py +

OptimizerMixin: a friendly post-mortem 🕵️

+

Why NoneOptimizer() blew up your ptycho run, and how to make this class + a joy to read again. No code was harmed in the making of this document.

+ +
+ +

🔪 The whodunit (in one breath)

+

+ A refactor decreed: “optimizer params shall always be a + dict[str, OptimizerType].” The normalizer obeyed. But + set_optimizer never got the memo — it still has two branches written for the + old world where a bare OptimizerType could walk in the door. Those + branches are now unreachable corpses, and every call gets funneled into the one dict branch + that doesn’t know what to do with a NoneOptimizer. +

+ +
+
💥 The crash
+

reconstruct(reset=True) calls reset_recon() + before your optimizer_params are applied. So set_optimizer + runs against the default {"default": NoneOptimizer()}, falls into the dict branch, + and hands a NoneOptimizer to _optimizer_class_for — which has no + case for it → NotImplementedError.

+
+ +
# the dead giveaway in set_optimizer (current)
+if isinstance(self.optimizer_params, OptimizerParams.NoneOptimizer):  ← never True; params is always a dict now
+    self.remove_optimizer()
+    return
+...
+if isinstance(self.optimizer_params, dict):                          # ← always taken
+    opt_specs = list(self.optimizer_params.values())             # [NoneOptimizer()]
+    optimizer_cls = self._optimizer_class_for(opt_specs[0])     ← 💥 NotImplementedError
+    ...
+    self._optimizer = optimizer_cls(params)                       ← also: no lr is ever passed!
+else:
+    # the branch that actually splatted hyperparameters... now unreachable 🪦
+    self._optimizer = optimizer_cls(params, **self.optimizer_params.params())
+ +
+
🐛 Bonus silent bug
+

Even after the crash is fixed, the dict branch builds + optimizer_cls(params) with no hyperparameters. A code comment + promises they’re “baked into each dict by get_optimization_parameters()” — but all + three implementations just return [{"params": params}] with no + lr. So your carefully chosen learning rate would silently become torch’s + default. The comment is, regrettably, fan fiction.

+
+ +

🌊 The flow, at a glance

+

Two objects both expose optimizer_params, and they mean different things. That’s + the single most confusing thing about this code, so let’s name it loudly:

+ + + + + + + + + + + + + +
LevelKeyed byExample value
Container
(PtychographyOpt)
which model
"object"/"probe"/"dataset"
{"object": {"default": Adam(lr=5e-3)}}
Model
(OptimizerMixin)
which param group
usually just "default"
{"default": Adam(lr=5e-3)}
+ +
user input ──► PtychographyOpt.optimizer_params setter   # Layer 1: fill defaults, dispatch per model
+                 │   fills "name"/"lr" defaults via _get_default_lr()
+                 └─► model.optimizer_params setter             # Layer 2: normalize to dict[str, OptimizerType]
+                       └─► _normalize_optimizer_params()
+                             OptimizerType        ──► {"default": it}
+                             {"name"/"type": ...} ──► {"default": parse_dict(it)}
+                             {k: OptimizerType}   ──► kept (PPLR form)
+                                  │
+reconstruct() ──► set_optimizers() ──► for each model: set_optimizer({"default": Adam(lr=5e-3)})
+                                                          └─► torch.optim.Adam(param_groups, lr=5e-3)
+ +

🧹 Five ways to make this a joy to read

+ +

1Make the invariant real — delete the dead branches

+

If optimizer_params is always a dict, then set_optimizer + should have one path: filter out the NoneOptimizer sentinels, agree + on a class, and splat hyperparameters. No else, no zombie isinstance.

+ +
+
+

✗ Before — 3 branches, 2 of them dead

+
if isinstance(p, NoneOptimizer):  # dead
+    remove_optimizer(); return
+...
+if isinstance(p, dict):
+    specs = list(p.values())
+    cls = _optimizer_class_for(specs[0])  # 💥
+    self._optimizer = cls(params)       # no lr
+else:                            # dead
+    cls = _optimizer_class_for(p)
+    self._optimizer = cls(params, **p.params())
+
+
+

✓ After — one honest path

+
# params is always dict[str, OptimizerType]
+active = [s for s in p.values()
+          if not isinstance(s, NoneOptimizer)]
+if not active:
+    self.remove_optimizer(); return
+
+self._assert_same_class(active)
+cls = self._optimizer_class_for(active[0])
+groups = self.get_optimization_parameters()
+self._optimizer = cls(groups, **active[0].params())  # lr restored ✓
+
+
+ +

2Give NoneOptimizer a home in the match

+

The sentinel is a first-class OptimizerType, so the place that maps specs to + torch classes should acknowledge it instead of letting it fall into case _ and + explode. A tiny is_none() helper or an explicit case makes the intent obvious.

+
match opt_params:
+    case OptimizerParams.Adam():  return torch.optim.Adam
+    case OptimizerParams.AdamW(): return torch.optim.AdamW
+    case OptimizerParams.SGD():   return torch.optim.SGD
+    case OptimizerParams.NoneOptimizer():                          # ← speak its name
+        raise ValueError("NoneOptimizer has no torch class; filter it out before calling")
+    case _: raise NotImplementedError(...)
+ +

3Keep remove_optimizer faithful to the invariant

+

Right now it resets _optimizer_params to a bare NoneOptimizer() + — violating the “always a dict” rule it works almost everywhere thanks to re-normalization, but + it’s a landmine. Store the dict form so the invariant holds end-to-end.

+
def remove_optimizer(self):
+    self._optimizer = None
+    self._optimizer_params = OptimizerParams.NoneOptimizer()           # bare → breaks invariant
+    self._optimizer_params = {self.DEFAULT_OPTIMIZER_KEY: OptimizerParams.NoneOptimizer()}
+    self._scheduler = None
+    self._scheduler_params = SchedulerParams.NoneScheduler()
+ +

4Pick one way to say “don’t optimize this”

+

Today there are three: an empty dict (if not self.optimizer_params), + the NoneOptimizer sentinel, and self._optimizer = None. Three spellings of + one idea means every reader has to hold all three in their head. Commit to the sentinel as the + single source of truth, and let has_optimizer() be the one public question.

+
+
💡 Rule of thumb
+

“No optimizer” = NoneOptimizer in the params, + None in self._optimizer. Everything else (empty dicts, None + inputs) gets normalized into that single shape at the boundary, once.

+
+ +

5Fix the container getters’ dead filters (and the lying comment)

+

Both PtychographyOpt and the tomography container filter with + if not isinstance(params, NoneOptimizer) — but params is now a + dict, so the filter never fires and disabled models sneak back into the loop. Either + filter on the inner spec, or drop the filter and lean on has_optimizer(). And while + you’re there: either make get_optimization_parameters() actually bake per-group + hyperparameters (real PPLR) or delete the comment that says it does.

+
# container getter, today — dead filter
+if not isinstance(params, OptimizerParams.NoneOptimizer)   # params is a dict → always passes
+
+# option A: check the inner specs
+if any(not isinstance(s, NoneOptimizer) for s in params.values())   # ✓
+ +
+
🎯 The big picture
+

There’s a clean seam hiding here: separate what to + optimize (parameter groups, from the model) from how (optimizer specs, from the + user). If get_optimization_parameters() returned groups keyed the same way as the + specs dict, true per-parameter learning rates would fall out naturally — and + set_optimizer would shrink to “zip specs with groups, build one optimizer.” That’s + the refactor that turns this from “works if you don’t poke it” into “obviously + correct.”

+
+ +

📋 Scorecard

+ + + + + + + + + + + + + + + + + +
TopicStateOne-line take
Normalization (Layer 1 & 2)SolidThe _normalize_optimizer_params logic is actually nice and clear.
set_optimizer branchingBrokenTwo dead branches; the live one crashes on the default state.
Hyperparameter passingBrokenlr silently dropped; comment claims otherwise.
“No optimizer” representationMuddyThree spellings of one concept.
Container gettersStaleDead isinstance filters left over from the bare-type era.
+ + + +
+ + diff --git a/src/quantem/core/ml/optimizer_refactor_incident.html b/src/quantem/core/ml/optimizer_refactor_incident.html new file mode 100644 index 00000000..89d4c762 --- /dev/null +++ b/src/quantem/core/ml/optimizer_refactor_incident.html @@ -0,0 +1,266 @@ + + + + + +The Great Type Standardization — an incident report + + + +
+ + Incident · Post-Mortem +
+

The Great Type Standardization
that quietly cut the pipes

+
How changing one return type from + OptimizerType | dict[str, OptimizerType] to just + dict[str, OptimizerType] took out Ptychography.
+
+ FILE: src/quantem/core/ml/optimizer_mixin.py  ·  + COMMIT: 1bc1739  ·  + SYMPTOM: NotImplementedError: Unknown optimizer type: NoneOptimizer() +
+
+ +
+ TL;DR — The refactor changed what the producer + (_normalize_optimizer_params) hands out, but not what the consumer + (set_optimizer) was built to receive. A return type isn’t just a label — it’s a + contract about the shape of a value, and three different places had hand-rolled + branches that pattern-matched on the old shape. When the shape changed, two branches + became unreachable and the third — never fully finished — became the only road. It couldn’t carry + a NoneOptimizer, and it dropped your learning rate on the floor. +
+ +

Act I — The good intentionOne type to rule them all

+

Someone looked at this signature and felt a totally reasonable itch:

+
+
+

Before

+
def optimizer_params(self) ->
+    OptimizerType | dict[str, OptimizerType]:
+    # could be a bare dataclass...
+    # ...or a dict of them. Ugh, which is it?
+    return self._optimizer_params
+
+
+

After — “let’s standardize”

+
def optimizer_params(self) ->
+    dict[str, OptimizerType]:
+    # always a dict now. cleaner!
+    return self._optimizer_params
+
+
+

The union type A | B is genuinely annoying: every reader, and every consumer, has + to ask “ok, but which one is it this time?” Collapsing it to a single + shape is the right instinct. The normalizer was dutifully rewritten so that everything — + a bare Adam(), a {"name": "adam"} shorthand, a list of keys — comes out + as a tidy dict[str, OptimizerType].

+ +
+
What went right
+

The normalization itself is clean and correct. If you only read + _normalize_optimizer_params, the refactor looks like an unambiguous win.

+
+ +

Act II — The unspoken contractA return type is a promise to strangers

+

Here’s the thing the type hint hides: that value doesn’t stay home. It flows downstream into + set_optimizer, which — because Python has no real runtime types — does its own + hand-rolled type dispatch with isinstance and an else:

+ +
def set_optimizer(self, opt_params):
+    ...
+    if isinstance(self.optimizer_params, OptimizerParams.NoneOptimizer):   # arm ➊
+        self.remove_optimizer(); return
+
+    if isinstance(self.optimizer_params, dict):                          # arm ➋
+        opt_specs = list(self.optimizer_params.values())
+        cls = self._optimizer_class_for(opt_specs[0])
+        self._optimizer = cls(params)
+    else:                                                                # arm ➌
+        cls = self._optimizer_class_for(self.optimizer_params)
+        self._optimizer = cls(params, **self.optimizer_params.params())
+ +

Those three arms were designed for the old contract, where + optimizer_params could be either a bare dataclass or a dict. Each arm had a + job:

+
    +
  • handle a bare NoneOptimizer sentinel → “turn the optimizer off.”
  • +
  • handle the dict / per-parameter case.
  • +
  • handle a bare optimizer dataclass → build it and splat its hyperparameters + (**params(), i.e. your lr).
  • +
+ +
Change the shape of the water, and pipes cut for the old shape stop carrying it — + even though nothing looks broken at the joint.
+ +

Act III — The reachability collapseTwo arms die, one inherits everything

+

Once the producer always returns a dict, watch what happens to the three arms:

+ + + + + + + + + + + + + + + + + + + + + + + + +
ArmMatches when…Old contractNew contractIts old job now…
bare NoneOptimizervalue is a bare sentinelreachableDEAD…nobody turns the optimizer off anymore
isinstance(dict)value is a dictreachableALWAYS…inherits ➊ and ➌’s jobs but does neither
the elsevalue is a bare dataclassreachableDEAD…the **params() splat (your lr!) is gone
+ +

So every call now lands in arm ➋. And arm ➋ was only ever a partial + implementation — it builds cls(params) and trusts that the rest will sort itself out. + But it never learned the two jobs it just inherited:

+ +
+
Failure 1 · the crash you saw
+

Arm ➋ pulls opt_specs[0] and asks + _optimizer_class_for for a torch class. When that spec is a NoneOptimizer + (the default state of every model), the match falls through to + case _NotImplementedError. Arm ➊ used to catch this; arm ➊ is now + dead.

+
+
+
Failure 2 · the silent one
+

Arm ➋ builds cls(params) with no + hyperparameters. Arm ➌ used to splat **self.optimizer_params.params() — + that’s where your lr came from. With ➌ dead, a perfectly valid Adam(lr=5e-3) + silently becomes Adam() at torch’s default LR. No error. Just wrong.

+
+ +

Act IV — Why Python smiled and said nothing

+

If types were enforced, the compiler would have screamed: “arm ➊ and arm ➌ can never + match a dict[str, OptimizerType] — delete them or fix them.” But in Python:

+
    +
  • Type hints are decorative. Changing -> dict[...] changes + documentation, not behavior. Nothing re-checked the consumers.
  • +
  • A branch that never matches doesn’t raise — it just silently never runs. + Dead code is invisible until you go looking for it.
  • +
  • The one surviving arm happened to not crash on the common happy path (pass an + Adam, get an optimizer — just at the wrong LR), so casual testing looked fine.
  • +
+ +
+
Why it detonated on reset=True specifically
+

reconstruct(reset=True, optimizer_params=…) calls + reset_recon() before it applies your params. At that instant every + model still holds its factory default — {"default": NoneOptimizer()}. So arm ➋ gets a + NoneOptimizer on the very first thing it touches (obj_model) and dies + immediately. The crash isn’t in your config; it’s baked into the default state the reset path + walks through.

+
+ +

Act V — The moralStandardizing a type is a migration, not an edit

+

“Just change the return type to dict[str, OptimizerType]felt like a + one-line cleanup. But the return type was load-bearing: three consumer branches structurally + depended on the old union. Changing the producer without migrating those consumers left the system + in a half-converted state — new shape coming out, old shape expected downstream.

+ +
+
+

What was done

+

✅ Producer (_normalize_optimizer_params) → always dict
+ ✅ Type hint updated
+ ❌ Consumer (set_optimizer) still speaks the old union
+ ❌ remove_optimizer still writes a bare sentinel

+
+
+

What “done” actually required

+

→ Collapse set_optimizer to one dict-only path
+ → Teach that path to filter NoneOptimizer (arm ➊’s job)
+ → Teach it to splat hyperparameters (arm ➌’s job)
+ → Make every producer of the value (incl. remove_optimizer) honor the new shape

+
+
+ +
The bug wasn’t the new type. The bug was believing the new type was the + whole change.
+ +
+ One sentence for the next reviewer: when you narrow a return type from a union to a single + shape, grep every consumer that does isinstance / match on it — each + arm that handled a now-impossible case is either dead code to delete or a responsibility that + just silently moved somewhere it isn’t handled. +
+ +

+ Read-only incident report · no source modified · + companions: optimizer_mixin_review.html, OPTIMIZER_PARAMS_FLOW.md +

+ +
+ + From bf19e6072e7a1ad79fa7e1db24bea44881d6f039 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Thu, 28 May 2026 17:37:15 -0700 Subject: [PATCH 317/335] Removed .md and .html files --- src/quantem/core/ml/OPTIMIZER_PARAMS_FLOW.md | 221 ------------ src/quantem/core/ml/optimizer_mixin_fix.html | 216 ------------ .../core/ml/optimizer_mixin_review.html | 319 ------------------ .../core/ml/optimizer_refactor_incident.html | 266 --------------- 4 files changed, 1022 deletions(-) delete mode 100644 src/quantem/core/ml/OPTIMIZER_PARAMS_FLOW.md delete mode 100644 src/quantem/core/ml/optimizer_mixin_fix.html delete mode 100644 src/quantem/core/ml/optimizer_mixin_review.html delete mode 100644 src/quantem/core/ml/optimizer_refactor_incident.html diff --git a/src/quantem/core/ml/OPTIMIZER_PARAMS_FLOW.md b/src/quantem/core/ml/OPTIMIZER_PARAMS_FLOW.md deleted file mode 100644 index e1819034..00000000 --- a/src/quantem/core/ml/OPTIMIZER_PARAMS_FLOW.md +++ /dev/null @@ -1,221 +0,0 @@ -# Optimizer params: how inputs get normalized - -This document traces what happens to `optimizer_params` from the moment a user -passes them into `reconstruct(...)` to the moment a real `torch.optim.Optimizer` -is built. As of commit `1bc1739` the design invariant is: - -> **At the model level, `_optimizer_params` is *always* a `dict[str, OptimizerType]`.** - -Understanding the two normalization layers (container level → model level) is the -key to reading this code. - ---- - -## 1. The vocabulary - -| Thing | What it is | Where | -|---|---|---| -| `OptimizerType` | Union of the dataclasses `Adam`, `AdamW`, `SGD`, `NoneOptimizer` | `optimizer_mixin.py` (`OptimizerType = Adam \| AdamW \| SGD \| NoneOptimizer`) | -| `OptimizerParams.` | The individual dataclasses; each carries hyperparameters and a `.params()` method that returns them as a `dict` for torch | `optimizer_mixin.py` | -| `NoneOptimizer` | Sentinel meaning "do not optimize this thing". `.params()` returns `{}` | `optimizer_mixin.py:174` | -| `DEFAULT_OPTIMIZER_KEY` | `"default"` — the key used when a single optimizer is wrapped into a dict | `optimizer_mixin.py:534` | -| `OptimizerMixin` | Mixin inherited by each model (`obj_model`, `probe_model`, `dset`). Owns `_optimizer_params`, `set_optimizer`, etc. | `optimizer_mixin.py:527` | -| `PtychographyOpt` | The *container*. Holds the three models and exposes a combined `optimizer_params` | `ptychography_opt.py:20` | - -There are **two objects** that both have a property called `optimizer_params`, -and they mean different things: - -- **Container** (`PtychographyOpt` / tomography equivalent): a dict keyed by - *which model* — `"object"`, `"probe"`, `"dataset"`. -- **Model** (`OptimizerMixin`): a dict keyed by *parameter group* — normally just - the single key `"default"`. - -So a fully-resolved structure is **nested**: - -``` -container.optimizer_params - = {"object": {"default": Adam(lr=5e-3)}, - "probe": {"default": Adam(lr=1e-3)}, - "dataset": {"default": NoneOptimizer()}} - ^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^ - model key group key OptimizerType -``` - ---- - -## 2. Accepted input shapes - -A user can hand the container any of these: - -```python -# (a) list/tuple of model keys -> use all defaults -ptycho.optimizer_params = ["object", "probe"] - -# (b) dict, value = OptimizerType dataclass -ptycho.optimizer_params = {"object": OptimizerParams.Adam(lr=5e-3)} - -# (c) dict, value = shorthand dict (name/type + hyperparams) -ptycho.optimizer_params = {"object": {"name": "adam", "lr": 5e-3}} - -# (d) dict, value = empty dict -> use default optimizer + default lr for that key -ptycho.optimizer_params = {"object": {}} -``` - ---- - -## 3. Layer 1 — container normalization - -`PtychographyOpt.optimizer_params` **setter** (`ptychography_opt.py:56`): - -``` -list/tuple ──► {k: {} for k in list} # (a) becomes (d)-style -for each (key, value): - value is OptimizerType ───────────────► pass through unchanged - value is dict and empty ──────────────► replace(DEFAULT_OPTIMIZER_TYPE, - lr=_get_default_lr(key)) - value is dict and non-empty ──────────► inject "name" if missing, - inject "lr" if missing - else ─────────────────────────────────► TypeError - │ - └─► dispatch to the matching model: - "object" -> self.obj_model.optimizer_params = value - "probe" -> self.probe_model.optimizer_params = value - "dataset" -> self.dset.optimizer_params = value -``` - -Key points: - -- `_get_default_lr(key)` supplies a sensible LR per model - (`object` ≈ 5e-3, `probe`/`dataset` ≈ 1e-3) when the user omitted one. -- The container does **not** build torch optimizers. It just fills defaults and - forwards each value down to the relevant model's setter. -- Any model key *not* mentioned by the user keeps whatever it had — by default - `{"default": NoneOptimizer()}` (i.e. "not optimized"). - ---- - -## 4. Layer 2 — model normalization - -Each model's `optimizer_params` **setter** calls -`OptimizerMixin._normalize_optimizer_params` (`optimizer_mixin.py:567`). This is -the function that guarantees the `dict[str, OptimizerType]` invariant: - -``` -_normalize_optimizer_params(params): - - params is an OptimizerType (dataclass) - ──► {"default": params} - - params is NOT a dict - ──► TypeError - - params is a dict AND _is_single_optimizer_dict(params) # has "name" or "type" - ──► {"default": OptimizerParams.parse_dict(params)} - - otherwise (dict-of-OptimizerType, the "PPLR" form) - ──► {k: (v if v is OptimizerType else parse_dict(v)) - for k, v in params.items()} -``` - -`_is_single_optimizer_dict(d)` is simply `"type" in d or "name" in d` -(`optimizer_mixin.py:585`). - -### `parse_dict` — shorthand → dataclass - -`OptimizerParams.parse_dict` (`optimizer_mixin.py:192`) maps a shorthand dict to -the concrete dataclass: - -``` -{"name"/"type": ...} pop the name, lowercase it, then: - "adam" -> OptimizerParams.Adam(**rest) - "adamw" -> OptimizerParams.AdamW(**rest) - "sgd" -> OptimizerParams.SGD(**rest) - "none" -> OptimizerParams.NoneOptimizer() - else -> ValueError -``` - -### Idempotency note - -When the container forwards an already-resolved value like `{"default": Adam(lr=5e-3)}` -to a model setter, it has no `"name"`/`"type"` key, so it takes the *PPLR branch* -and is kept as-is (`Adam` is already an `OptimizerType`). So re-normalizing a -normalized dict is a no-op. Good. - ---- - -## 5. Building the torch optimizer - -`OptimizerMixin.set_optimizer` (`optimizer_mixin.py:614`) is where the normalized -dict becomes a real optimizer. Conceptually it should: - -1. Look at the values of the `dict[str, OptimizerType]`. -2. Drop / handle `NoneOptimizer` sentinels (→ "no optimizer for this"). -3. Confirm the remaining specs agree on an optimizer *class*. -4. Pull parameter groups from `get_optimization_parameters()` (a `list[dict]`, - each `{"params": [...]}`). -5. Construct `optimizer_cls(param_groups, **hyperparameters)`. - -`_optimizer_class_for` (`optimizer_mixin.py:659`) maps a spec dataclass to a torch -class via a `match`: - -``` -Adam() -> torch.optim.Adam -AdamW() -> torch.optim.AdamW -SGD() -> torch.optim.SGD -_ -> NotImplementedError # <-- NoneOptimizer lands here -``` - -### The reset path (where the current crash lives) - -`reconstruct(reset=True, ...)` calls `reset_recon()` **before** applying the -user's `optimizer_params` (`ptychography.py:181` vs `:185`). `reset_recon` calls -each model's `reset_optimizer()` → `set_optimizer(self._optimizer_params)`. At -that moment `_optimizer_params` is still the default `{"default": NoneOptimizer()}`. - -``` -reconstruct(reset=True, optimizer_params=opt_params) - ├─ reset_recon() # opt_params NOT applied yet - │ └─ obj_model.reset_optimizer() - │ └─ set_optimizer({"default": NoneOptimizer()}) - │ └─ _optimizer_class_for(NoneOptimizer()) -> NotImplementedError - └─ (never reached) self.optimizer_params = opt_params; set_optimizers() -``` - ---- - -## 6. Where the value flows at recon time - -Once past reset, the normal value flow is: - -``` -reconstruct(optimizer_params={"object": {"lr": 5e-3}, ...}) - │ - ├─ container.optimizer_params = {...} # Layer 1: fill defaults, dispatch - │ └─ obj_model.optimizer_params = {"name":"adamw","lr":5e-3} - │ └─ _normalize_optimizer_params -> {"default": AdamW(lr=5e-3)} # Layer 2 - │ - └─ container.set_optimizers() - for key, params in container.optimizer_params.items(): # nested dict - model.set_optimizer(params) # params = {"default": AdamW(lr=5e-3)} - └─ build torch.optim.AdamW(param_groups, lr=5e-3, ...) -``` - -`container.set_optimizers()` iterates the **container getter**, which returns the -nested `{"object": {"default": ...}, ...}` structure, and hands each inner dict to -the corresponding model's `set_optimizer`. - ---- - -## 7. Quick reference — invariants to keep in mind - -- A **model's** `_optimizer_params` is always `dict[str, OptimizerType]`, normally - one key: `"default"`. -- A **container's** `optimizer_params` is `dict[str, dict[str, OptimizerType]]`, - keyed by model name (`"object"`/`"probe"`/`"dataset"`). -- `NoneOptimizer` is a first-class `OptimizerType` meaning "skip". Anything that - consumes the dict must treat it specially, because `_optimizer_class_for` has no - case for it. -- `.params()` on a spec dataclass returns the kwargs torch needs (`lr`, etc.); - `NoneOptimizer.params()` returns `{}`. -- `get_optimization_parameters()` returns parameter *groups* (`[{"params": [...]}]`) - and currently carries **no** per-group hyperparameters. diff --git a/src/quantem/core/ml/optimizer_mixin_fix.html b/src/quantem/core/ml/optimizer_mixin_fix.html deleted file mode 100644 index 3bb7c365..00000000 --- a/src/quantem/core/ml/optimizer_mixin_fix.html +++ /dev/null @@ -1,216 +0,0 @@ - - - - - -OptimizerMixin — minimal fixes to complete the dict[str, OptimizerType] standardization - - - -
- -
-

Completing the dict[str, OptimizerType] standardization

-

Minimal, surgical fixes that keep your standardization goal intact and resolve the - Ptychography breakage.

-
- FILE: src/quantem/core/ml/optimizer_mixin.py  ·  - SCOPE: consumer-side only  ·  - RISK: low, localized -
-
- -

1. Diagnosis in one paragraph

-

Your goal — normalize everything to dict[str, OptimizerType] — is correct - and worth keeping. The standardization did not break Ptychography on its own. The breakage came - from a consumer that was never migrated to the new contract: - set_optimizer. It still branches on the old union shape (a bare - OptimizerType or a dict). After normalization always returns a dict, the two - branches that handled the bare-type cases became unreachable, and the surviving dict branch was - only a partial implementation — it never handled the NoneOptimizer sentinel and never - forwarded hyperparameters. The fix is therefore not to revert the standardization, - but to make that one dict branch self-sufficient.

- -
-
Why your change was necessary but not sufficient
-

A return type is a contract. Narrowing it from a union to a single - shape obligates every consumer that pattern-matches on the old shape to be updated in the same - change. The producer was migrated; one consumer was not.

-
- -

2. The two concrete symptoms

- - - - - - - - - - -
SymptomRoot cause in set_optimizer
Crash: NotImplementedError: Unknown optimizer type: NoneOptimizer()The dict branch passes a NoneOptimizer spec to - _optimizer_class_for, which has no case for it. The old guard that caught this - (isinstance(..., NoneOptimizer)) only matches a bare sentinel and is now - unreachable.
Silent: configured lr ignoredThe dict branch builds optimizer_cls(params) with no kwargs. The old branch - that splatted **optimizer_params.params() is the now-unreachable - else.
- -

3. Recommended minimal change set

-

Three changes, in priority order. REQUIRED alone resolves the - crash and the silent lr bug.

- -

REQUIREDMake the dict branch self-sufficient · ~6 lines, one method

-

Filter out NoneOptimizer sentinels; if none remain, there is nothing to optimize. - Otherwise build the optimizer and forward the spec’s hyperparameters. This is the - smallest change that honors the new dict-only contract.

-
        if isinstance(self.optimizer_params, dict):
--           opt_specs = list(self.optimizer_params.values())
--           if not opt_specs:
--               self._optimizer = None
--               return
-+           opt_specs = [
-+               spec for spec in self.optimizer_params.values()
-+               if not isinstance(spec, OptimizerParams.NoneOptimizer)
-+           ]
-+           if not opt_specs:
-+               self.remove_optimizer()
-+               return
-            optimizer_cls = self._optimizer_class_for(opt_specs[0])
-            for spec in opt_specs[1:]:
-                if type(spec) is not type(opt_specs[0]):
-                    raise ValueError(...)
--           self._optimizer = optimizer_cls(params)  # type:ignore
-+           self._optimizer = optimizer_cls(params, **opt_specs[0].params())
-
-
What this buys you
-

The default {"default": NoneOptimizer()} now filters to - an empty list → remove_optimizer() → clean no-op (this is the path - reset_recon() walks). A real {"default": Adam(lr=5e-3)} now builds - Adam(params, lr=5e-3, …) with your hyperparameters restored.

-
-
-
Note on PPLR
-

This splats opt_specs[0]’s hyperparameters across all - parameter groups. That is correct today because every model returns a single param group from - get_optimization_parameters(). True per-group LRs are a separate, larger piece of - work — see §5.

-
- -

RECOMMENDEDKeep remove_optimizer on-contract · 1 line

-

It currently stores a bare sentinel, breaking the “always a dict” invariant. It works - only because the value gets re-normalized on the next set. Store the dict form to keep the - invariant true end-to-end.

-
    def remove_optimizer(self) -> None:
-        self._optimizer = None
--       self._optimizer_params = OptimizerParams.NoneOptimizer()
-+       self._optimizer_params = {self.DEFAULT_OPTIMIZER_KEY: OptimizerParams.NoneOptimizer()}
-        self._scheduler = None
-        self._scheduler_params = SchedulerParams.NoneScheduler()
- -

OPTIONALDelete the now-dead branches · removes ~7 lines

-

Purely cosmetic, but it removes the misleading code that caused the confusion. With the dict - contract guaranteed, the bare-NoneOptimizer guard and the else branch can - never run. Removing them makes set_optimizer a single, honest path.

-
-       if isinstance(self.optimizer_params, OptimizerParams.NoneOptimizer):
--           self.remove_optimizer()
--           return
-        ...
--       else:
--           optimizer_cls = self._optimizer_class_for(self.optimizer_params)
--           self._optimizer = optimizer_cls(params, **self.optimizer_params.params())
-

If you keep these, no harm — they’re dead, not wrong. Removing them is about - readability, not correctness.

- -

4. Optional: align the container getters

-

OPTIONAL In PtychographyOpt.optimizer_params (and the - tomography equivalent) the filter if not isinstance(params, NoneOptimizer) no longer - fires, because params is now a dict. After the REQUIRED fix this is harmless — a - disabled model simply ends up with no optimizer — but if you want the getter to exclude disabled - models, check the inner specs instead.

-
-           if not isinstance(params, OptimizerParams.NoneOptimizer)
-+           if any(not isinstance(s, OptimizerParams.NoneOptimizer) for s in params.values())
- -

5. What to leave for later (don’t scope-creep)

-
    -
  • Real per-parameter LRs (PPLR). The comment claiming hyperparameters are “baked into - each dict by get_optimization_parameters()” is aspirational — the implementations - return [{"params": params}] with no lr. Either implement keyed param - groups or update the comment. Not needed to fix the break.
  • -
  • Collapsing the three “no optimizer” spellings (empty dict / NoneOptimizer / - _optimizer = None) into one. A nice tidy-up, but orthogonal to this incident.
  • -
- -

6. Verification checklist

- - - - - - - - - - -
StepExpected
ptycho.reconstruct(num_iters=1, reset=True, optimizer_params={"object": {"lr": 5e-3}})No NotImplementedError; runs.
Inspect ptycho.obj_model.optimizer.param_groups[0]["lr"]Equals 5e-3, not torch’s default.
A model left out of optimizer_paramshas_optimizer() is False; not stepped.
Re-run with reset=TrueDefault NoneOptimizer filters cleanly; no crash.
- -
-
Bottom line
-

Keep your standardization. Apply the one REQUIRED change (and ideally - the one-line RECOMMENDED change). That completes the migration the type-narrowing started, with a - change set small enough to read in a single diff.

-
- -

- Read-only proposal · no source files modified · - companions: optimizer_mixin_review.html · optimizer_refactor_incident.html · OPTIMIZER_PARAMS_FLOW.md -

- -
- - diff --git a/src/quantem/core/ml/optimizer_mixin_review.html b/src/quantem/core/ml/optimizer_mixin_review.html deleted file mode 100644 index 1e205644..00000000 --- a/src/quantem/core/ml/optimizer_mixin_review.html +++ /dev/null @@ -1,319 +0,0 @@ - - - - - -OptimizerMixin — a friendly post-mortem & cleanup wishlist - - - -
- -
- Code review · ml/optimizer_mixin.py -

OptimizerMixin: a friendly post-mortem 🕵️

-

Why NoneOptimizer() blew up your ptycho run, and how to make this class - a joy to read again. No code was harmed in the making of this document.

- -
- -

🔪 The whodunit (in one breath)

-

- A refactor decreed: “optimizer params shall always be a - dict[str, OptimizerType].” The normalizer obeyed. But - set_optimizer never got the memo — it still has two branches written for the - old world where a bare OptimizerType could walk in the door. Those - branches are now unreachable corpses, and every call gets funneled into the one dict branch - that doesn’t know what to do with a NoneOptimizer. -

- -
-
💥 The crash
-

reconstruct(reset=True) calls reset_recon() - before your optimizer_params are applied. So set_optimizer - runs against the default {"default": NoneOptimizer()}, falls into the dict branch, - and hands a NoneOptimizer to _optimizer_class_for — which has no - case for it → NotImplementedError.

-
- -
# the dead giveaway in set_optimizer (current)
-if isinstance(self.optimizer_params, OptimizerParams.NoneOptimizer):  ← never True; params is always a dict now
-    self.remove_optimizer()
-    return
-...
-if isinstance(self.optimizer_params, dict):                          # ← always taken
-    opt_specs = list(self.optimizer_params.values())             # [NoneOptimizer()]
-    optimizer_cls = self._optimizer_class_for(opt_specs[0])     ← 💥 NotImplementedError
-    ...
-    self._optimizer = optimizer_cls(params)                       ← also: no lr is ever passed!
-else:
-    # the branch that actually splatted hyperparameters... now unreachable 🪦
-    self._optimizer = optimizer_cls(params, **self.optimizer_params.params())
- -
-
🐛 Bonus silent bug
-

Even after the crash is fixed, the dict branch builds - optimizer_cls(params) with no hyperparameters. A code comment - promises they’re “baked into each dict by get_optimization_parameters()” — but all - three implementations just return [{"params": params}] with no - lr. So your carefully chosen learning rate would silently become torch’s - default. The comment is, regrettably, fan fiction.

-
- -

🌊 The flow, at a glance

-

Two objects both expose optimizer_params, and they mean different things. That’s - the single most confusing thing about this code, so let’s name it loudly:

- - - - - - - - - - - - - -
LevelKeyed byExample value
Container
(PtychographyOpt)
which model
"object"/"probe"/"dataset"
{"object": {"default": Adam(lr=5e-3)}}
Model
(OptimizerMixin)
which param group
usually just "default"
{"default": Adam(lr=5e-3)}
- -
user input ──► PtychographyOpt.optimizer_params setter   # Layer 1: fill defaults, dispatch per model
-                 │   fills "name"/"lr" defaults via _get_default_lr()
-                 └─► model.optimizer_params setter             # Layer 2: normalize to dict[str, OptimizerType]
-                       └─► _normalize_optimizer_params()
-                             OptimizerType        ──► {"default": it}
-                             {"name"/"type": ...} ──► {"default": parse_dict(it)}
-                             {k: OptimizerType}   ──► kept (PPLR form)
-                                  │
-reconstruct() ──► set_optimizers() ──► for each model: set_optimizer({"default": Adam(lr=5e-3)})
-                                                          └─► torch.optim.Adam(param_groups, lr=5e-3)
- -

🧹 Five ways to make this a joy to read

- -

1Make the invariant real — delete the dead branches

-

If optimizer_params is always a dict, then set_optimizer - should have one path: filter out the NoneOptimizer sentinels, agree - on a class, and splat hyperparameters. No else, no zombie isinstance.

- -
-
-

✗ Before — 3 branches, 2 of them dead

-
if isinstance(p, NoneOptimizer):  # dead
-    remove_optimizer(); return
-...
-if isinstance(p, dict):
-    specs = list(p.values())
-    cls = _optimizer_class_for(specs[0])  # 💥
-    self._optimizer = cls(params)       # no lr
-else:                            # dead
-    cls = _optimizer_class_for(p)
-    self._optimizer = cls(params, **p.params())
-
-
-

✓ After — one honest path

-
# params is always dict[str, OptimizerType]
-active = [s for s in p.values()
-          if not isinstance(s, NoneOptimizer)]
-if not active:
-    self.remove_optimizer(); return
-
-self._assert_same_class(active)
-cls = self._optimizer_class_for(active[0])
-groups = self.get_optimization_parameters()
-self._optimizer = cls(groups, **active[0].params())  # lr restored ✓
-
-
- -

2Give NoneOptimizer a home in the match

-

The sentinel is a first-class OptimizerType, so the place that maps specs to - torch classes should acknowledge it instead of letting it fall into case _ and - explode. A tiny is_none() helper or an explicit case makes the intent obvious.

-
match opt_params:
-    case OptimizerParams.Adam():  return torch.optim.Adam
-    case OptimizerParams.AdamW(): return torch.optim.AdamW
-    case OptimizerParams.SGD():   return torch.optim.SGD
-    case OptimizerParams.NoneOptimizer():                          # ← speak its name
-        raise ValueError("NoneOptimizer has no torch class; filter it out before calling")
-    case _: raise NotImplementedError(...)
- -

3Keep remove_optimizer faithful to the invariant

-

Right now it resets _optimizer_params to a bare NoneOptimizer() - — violating the “always a dict” rule it works almost everywhere thanks to re-normalization, but - it’s a landmine. Store the dict form so the invariant holds end-to-end.

-
def remove_optimizer(self):
-    self._optimizer = None
-    self._optimizer_params = OptimizerParams.NoneOptimizer()           # bare → breaks invariant
-    self._optimizer_params = {self.DEFAULT_OPTIMIZER_KEY: OptimizerParams.NoneOptimizer()}
-    self._scheduler = None
-    self._scheduler_params = SchedulerParams.NoneScheduler()
- -

4Pick one way to say “don’t optimize this”

-

Today there are three: an empty dict (if not self.optimizer_params), - the NoneOptimizer sentinel, and self._optimizer = None. Three spellings of - one idea means every reader has to hold all three in their head. Commit to the sentinel as the - single source of truth, and let has_optimizer() be the one public question.

-
-
💡 Rule of thumb
-

“No optimizer” = NoneOptimizer in the params, - None in self._optimizer. Everything else (empty dicts, None - inputs) gets normalized into that single shape at the boundary, once.

-
- -

5Fix the container getters’ dead filters (and the lying comment)

-

Both PtychographyOpt and the tomography container filter with - if not isinstance(params, NoneOptimizer) — but params is now a - dict, so the filter never fires and disabled models sneak back into the loop. Either - filter on the inner spec, or drop the filter and lean on has_optimizer(). And while - you’re there: either make get_optimization_parameters() actually bake per-group - hyperparameters (real PPLR) or delete the comment that says it does.

-
# container getter, today — dead filter
-if not isinstance(params, OptimizerParams.NoneOptimizer)   # params is a dict → always passes
-
-# option A: check the inner specs
-if any(not isinstance(s, NoneOptimizer) for s in params.values())   # ✓
- -
-
🎯 The big picture
-

There’s a clean seam hiding here: separate what to - optimize (parameter groups, from the model) from how (optimizer specs, from the - user). If get_optimization_parameters() returned groups keyed the same way as the - specs dict, true per-parameter learning rates would fall out naturally — and - set_optimizer would shrink to “zip specs with groups, build one optimizer.” That’s - the refactor that turns this from “works if you don’t poke it” into “obviously - correct.”

-
- -

📋 Scorecard

- - - - - - - - - - - - - - - - - -
TopicStateOne-line take
Normalization (Layer 1 & 2)SolidThe _normalize_optimizer_params logic is actually nice and clear.
set_optimizer branchingBrokenTwo dead branches; the live one crashes on the default state.
Hyperparameter passingBrokenlr silently dropped; comment claims otherwise.
“No optimizer” representationMuddyThree spellings of one concept.
Container gettersStaleDead isinstance filters left over from the bare-type era.
- - - -
- - diff --git a/src/quantem/core/ml/optimizer_refactor_incident.html b/src/quantem/core/ml/optimizer_refactor_incident.html deleted file mode 100644 index 89d4c762..00000000 --- a/src/quantem/core/ml/optimizer_refactor_incident.html +++ /dev/null @@ -1,266 +0,0 @@ - - - - - -The Great Type Standardization — an incident report - - - -
- - Incident · Post-Mortem -
-

The Great Type Standardization
that quietly cut the pipes

-
How changing one return type from - OptimizerType | dict[str, OptimizerType] to just - dict[str, OptimizerType] took out Ptychography.
-
- FILE: src/quantem/core/ml/optimizer_mixin.py  ·  - COMMIT: 1bc1739  ·  - SYMPTOM: NotImplementedError: Unknown optimizer type: NoneOptimizer() -
-
- -
- TL;DR — The refactor changed what the producer - (_normalize_optimizer_params) hands out, but not what the consumer - (set_optimizer) was built to receive. A return type isn’t just a label — it’s a - contract about the shape of a value, and three different places had hand-rolled - branches that pattern-matched on the old shape. When the shape changed, two branches - became unreachable and the third — never fully finished — became the only road. It couldn’t carry - a NoneOptimizer, and it dropped your learning rate on the floor. -
- -

Act I — The good intentionOne type to rule them all

-

Someone looked at this signature and felt a totally reasonable itch:

-
-
-

Before

-
def optimizer_params(self) ->
-    OptimizerType | dict[str, OptimizerType]:
-    # could be a bare dataclass...
-    # ...or a dict of them. Ugh, which is it?
-    return self._optimizer_params
-
-
-

After — “let’s standardize”

-
def optimizer_params(self) ->
-    dict[str, OptimizerType]:
-    # always a dict now. cleaner!
-    return self._optimizer_params
-
-
-

The union type A | B is genuinely annoying: every reader, and every consumer, has - to ask “ok, but which one is it this time?” Collapsing it to a single - shape is the right instinct. The normalizer was dutifully rewritten so that everything — - a bare Adam(), a {"name": "adam"} shorthand, a list of keys — comes out - as a tidy dict[str, OptimizerType].

- -
-
What went right
-

The normalization itself is clean and correct. If you only read - _normalize_optimizer_params, the refactor looks like an unambiguous win.

-
- -

Act II — The unspoken contractA return type is a promise to strangers

-

Here’s the thing the type hint hides: that value doesn’t stay home. It flows downstream into - set_optimizer, which — because Python has no real runtime types — does its own - hand-rolled type dispatch with isinstance and an else:

- -
def set_optimizer(self, opt_params):
-    ...
-    if isinstance(self.optimizer_params, OptimizerParams.NoneOptimizer):   # arm ➊
-        self.remove_optimizer(); return
-
-    if isinstance(self.optimizer_params, dict):                          # arm ➋
-        opt_specs = list(self.optimizer_params.values())
-        cls = self._optimizer_class_for(opt_specs[0])
-        self._optimizer = cls(params)
-    else:                                                                # arm ➌
-        cls = self._optimizer_class_for(self.optimizer_params)
-        self._optimizer = cls(params, **self.optimizer_params.params())
- -

Those three arms were designed for the old contract, where - optimizer_params could be either a bare dataclass or a dict. Each arm had a - job:

-
    -
  • handle a bare NoneOptimizer sentinel → “turn the optimizer off.”
  • -
  • handle the dict / per-parameter case.
  • -
  • handle a bare optimizer dataclass → build it and splat its hyperparameters - (**params(), i.e. your lr).
  • -
- -
Change the shape of the water, and pipes cut for the old shape stop carrying it — - even though nothing looks broken at the joint.
- -

Act III — The reachability collapseTwo arms die, one inherits everything

-

Once the producer always returns a dict, watch what happens to the three arms:

- - - - - - - - - - - - - - - - - - - - - - - - -
ArmMatches when…Old contractNew contractIts old job now…
bare NoneOptimizervalue is a bare sentinelreachableDEAD…nobody turns the optimizer off anymore
isinstance(dict)value is a dictreachableALWAYS…inherits ➊ and ➌’s jobs but does neither
the elsevalue is a bare dataclassreachableDEAD…the **params() splat (your lr!) is gone
- -

So every call now lands in arm ➋. And arm ➋ was only ever a partial - implementation — it builds cls(params) and trusts that the rest will sort itself out. - But it never learned the two jobs it just inherited:

- -
-
Failure 1 · the crash you saw
-

Arm ➋ pulls opt_specs[0] and asks - _optimizer_class_for for a torch class. When that spec is a NoneOptimizer - (the default state of every model), the match falls through to - case _NotImplementedError. Arm ➊ used to catch this; arm ➊ is now - dead.

-
-
-
Failure 2 · the silent one
-

Arm ➋ builds cls(params) with no - hyperparameters. Arm ➌ used to splat **self.optimizer_params.params() — - that’s where your lr came from. With ➌ dead, a perfectly valid Adam(lr=5e-3) - silently becomes Adam() at torch’s default LR. No error. Just wrong.

-
- -

Act IV — Why Python smiled and said nothing

-

If types were enforced, the compiler would have screamed: “arm ➊ and arm ➌ can never - match a dict[str, OptimizerType] — delete them or fix them.” But in Python:

-
    -
  • Type hints are decorative. Changing -> dict[...] changes - documentation, not behavior. Nothing re-checked the consumers.
  • -
  • A branch that never matches doesn’t raise — it just silently never runs. - Dead code is invisible until you go looking for it.
  • -
  • The one surviving arm happened to not crash on the common happy path (pass an - Adam, get an optimizer — just at the wrong LR), so casual testing looked fine.
  • -
- -
-
Why it detonated on reset=True specifically
-

reconstruct(reset=True, optimizer_params=…) calls - reset_recon() before it applies your params. At that instant every - model still holds its factory default — {"default": NoneOptimizer()}. So arm ➋ gets a - NoneOptimizer on the very first thing it touches (obj_model) and dies - immediately. The crash isn’t in your config; it’s baked into the default state the reset path - walks through.

-
- -

Act V — The moralStandardizing a type is a migration, not an edit

-

“Just change the return type to dict[str, OptimizerType]felt like a - one-line cleanup. But the return type was load-bearing: three consumer branches structurally - depended on the old union. Changing the producer without migrating those consumers left the system - in a half-converted state — new shape coming out, old shape expected downstream.

- -
-
-

What was done

-

✅ Producer (_normalize_optimizer_params) → always dict
- ✅ Type hint updated
- ❌ Consumer (set_optimizer) still speaks the old union
- ❌ remove_optimizer still writes a bare sentinel

-
-
-

What “done” actually required

-

→ Collapse set_optimizer to one dict-only path
- → Teach that path to filter NoneOptimizer (arm ➊’s job)
- → Teach it to splat hyperparameters (arm ➌’s job)
- → Make every producer of the value (incl. remove_optimizer) honor the new shape

-
-
- -
The bug wasn’t the new type. The bug was believing the new type was the - whole change.
- -
- One sentence for the next reviewer: when you narrow a return type from a union to a single - shape, grep every consumer that does isinstance / match on it — each - arm that handled a now-impossible case is either dead code to delete or a responsibility that - just silently moved somewhere it isn’t handled. -
- -

- Read-only incident report · no source modified · - companions: optimizer_mixin_review.html, OPTIMIZER_PARAMS_FLOW.md -

- -
- - From 91642a8acf3f422424daad71ef9ba8bf0e6754b4 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Thu, 28 May 2026 18:18:06 -0700 Subject: [PATCH 318/335] Major changes per Claude's detailed report on what I actually implemented is not truly PPLR and was brittle. Will talk to @arthurmccray 5/28/2026 about this --- src/quantem/core/ml/optimizer_mixin.py | 114 +++++++++++------- .../diffractive_imaging/dataset_models.py | 42 +++++-- .../diffractive_imaging/object_models.py | 9 +- .../diffractive_imaging/probe_models.py | 9 +- src/quantem/tomography/object_models.py | 26 ++-- tests/ml/test_optimizermixin.py | 103 ++++++++++++++++ 6 files changed, 223 insertions(+), 80 deletions(-) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index 8c095d39..1575276d 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -603,62 +603,87 @@ def scheduler_params(self, params: SchedulerType | dict): @abstractmethod def get_optimization_parameters( self, - ) -> "list[dict[str, Any]]": + ) -> "dict[str, list[torch.Tensor]]": """ - Get the parameters that should be optimized for this model. - This could be replaced with just module.parameters(), but this allows for flexibility - in the future to allow for per parameter LRs. # NOTE: Cl 4/27/26 updated to iterable type-hint. + Get the parameters that should be optimized for this model, grouped by name. + + Returns a mapping ``{group_key: [tensors]}``. The group keys MUST match the keys of + ``optimizer_params`` (the common single-group case uses ``DEFAULT_OPTIMIZER_KEY``). + ``set_optimizer`` joins each group to its optimizer spec by key and bakes the per-group + hyperparameters (``spec.params()``) into the torch param group — implementations return + only the tensors, NOT pre-baked hyperparameters. Return ``{}`` when there is nothing + to optimize. """ raise NotImplementedError("Subclasses must implement get_optimization_parameters") def set_optimizer(self, opt_params: OptimizerType | dict | None = None) -> None: """ - Set the optimizer for this model. - Currently supports single LR for all parameters, TODO allow for per parameter LRs by - updating get_optimization_parameters to return a list of parameters and their LRs. + Set the optimizer for this model, supporting per-parameter-group learning rates (PPLR). + + ``optimizer_params`` is a ``dict[str, OptimizerType]`` keyed by parameter group. Each + group's spec is joined by key to the tensors returned by ``get_optimization_parameters()`` + and its hyperparameters are baked into the corresponding torch param group here. All + groups must use the same optimizer class. If every group is a ``NoneOptimizer`` (or there + are no groups), the optimizer is removed. """ if opt_params is not None: self.optimizer_params = opt_params - if not self.optimizer_params: - self._optimizer = None + # Single canonical "disable" path: drop NoneOptimizer sentinels and, if nothing is left, + # remove the optimizer. Done BEFORE get_optimization_parameters() because some models + # (e.g. the dataset model) raise / return nothing when there is nothing to optimize. + specs = { + key: spec + for key, spec in self.optimizer_params.items() + if not isinstance(spec, OptimizerParams.NoneOptimizer) + } + if not specs: + self.remove_optimizer() return + # All groups must agree on the optimizer class. + spec_list = list(specs.values()) + for spec in spec_list[1:]: + if type(spec) is not type(spec_list[0]): + raise ValueError( + f"All parameter groups must use the same optimizer type, " + f"got {type(spec_list[0]).__name__} and {type(spec).__name__}" + ) - params = self.get_optimization_parameters() # always list[dict] - - # Ensure parameters require gradients - for group in params: - for p in group["params"]: + # Join specs to param groups by key; bake each group's hyperparameters here. + groups = self.get_optimization_parameters() # dict[str, list[tensor]] + if set(groups) != set(specs): + raise ValueError( + f"optimizer_params keys {set(specs)} do not match parameter group keys " + f"{set(groups)} from {type(self).__name__}.get_optimization_parameters()" + ) + + param_groups = [] + for key, tensors in groups.items(): + for p in tensors: p.requires_grad_(True) - # Figure out which optimizer class to use - if isinstance(self.optimizer_params, dict): - # Per-group case: all groups must agree on the optimizer class, - # and per-group hyperparameters are already baked into each dict - # by get_optimization_parameters(). - opt_specs = [ - spec for spec in self.optimizer_params.values() - if not isinstance(spec, OptimizerParams.NoneOptimizer) - ] - if not opt_specs: - self.remove_optimizer() - return - optimizer_cls = self._optimizer_class_for(opt_specs[0]) - for spec in opt_specs[1:]: - if type(spec) is not type(opt_specs[0]): - raise ValueError( - f"All parameter groups must use the same optimizer type, " - f"got {type(opt_specs[0]).__name__} and {type(spec).__name__}" - ) - self._optimizer = optimizer_cls(params, **opt_specs[0].params()) - def _optimizer_class_for(self, opt_params) -> type[torch.optim.Optimizer]: + param_groups.append({"params": tensors, **specs[key].params()}) + self._optimizer = self._build_optimizer(spec_list[0], param_groups) + + def _build_optimizer(self, opt_params, param_groups) -> "torch.optim.Optimizer": + """Construct the torch optimizer for ``opt_params`` over pre-baked ``param_groups``. + + ``param_groups`` already carry their per-group hyperparameters (see ``set_optimizer``), + so each group's ``lr`` etc. overrides the optimizer-level default. ``NoneOptimizer`` must + have been filtered out by the caller. + """ match opt_params: case OptimizerParams.Adam(): - return torch.optim.Adam + return torch.optim.Adam(param_groups) case OptimizerParams.AdamW(): - return torch.optim.AdamW + return torch.optim.AdamW(param_groups) case OptimizerParams.SGD(): - return torch.optim.SGD + return torch.optim.SGD(param_groups) + case OptimizerParams.NoneOptimizer(): + raise ValueError( + "NoneOptimizer must be filtered out before _build_optimizer; " + "set_optimizer should have short-circuited to remove_optimizer()." + ) case _: raise NotImplementedError(f"Unknown optimizer type: {opt_params}") @@ -674,7 +699,10 @@ def set_scheduler( return optimizer = self._optimizer - base_LR = optimizer.param_groups[0]["lr"] + # Schedulers scale every torch param group proportionally off its own initial_lr; this + # scalar only seeds scheduler config (e.g. min_lr, cyclic bounds). Use the max group LR + # as the representative (collapses to group 0 in the single-group case). + base_LR = max(pg["lr"] for pg in optimizer.param_groups) params = self._scheduler_params.params(base_LR, num_iter=num_iter) match self.scheduler_params: case SchedulerParams.NoneScheduler(): @@ -764,8 +792,8 @@ def reconnect_optimizer_to_parameters(self) -> None: return # Ensure leaf params with grad - for group in new_groups: - for p in group["params"]: + for tensors in new_groups.values(): + for p in tensors: if not p.is_leaf: raise ValueError("Non-leaf tensor in param group; build groups from leaves") p.requires_grad_(True) @@ -776,8 +804,8 @@ def reconnect_optimizer_to_parameters(self) -> None: ] self._optimizer.param_groups.clear() - for group in new_groups: - self._optimizer.add_param_group(group) + for tensors in new_groups.values(): + self._optimizer.add_param_group({"params": tensors}) # Restore per-group hyperparameters by index for new_pg, old_pg in zip(self._optimizer.param_groups, old_hyperparams): diff --git a/src/quantem/diffractive_imaging/dataset_models.py b/src/quantem/diffractive_imaging/dataset_models.py index 585d5e4b..39895adb 100644 --- a/src/quantem/diffractive_imaging/dataset_models.py +++ b/src/quantem/diffractive_imaging/dataset_models.py @@ -1,4 +1,5 @@ from abc import abstractmethod +from dataclasses import replace from pathlib import Path from typing import Any, Literal, Self @@ -95,18 +96,39 @@ def __init__( self._constraints = {} self._probe_energy = None - def get_optimization_parameters(self) -> list[dict[str, Any]]: - """Get the combined descan and scan position parameters for optimization.""" - params = [] + def get_optimization_parameters(self) -> "dict[str, list[torch.Tensor]]": + """Descan and scan-position parameters as separate PPLR groups. + + Returns one group per *learnable* parameter set; ``{}`` when neither is learnable + (``set_optimizer`` then short-circuits to removing the optimizer). + """ + groups: dict[str, list[torch.Tensor]] = {} if self.learn_descan: - params.append(self._descan_shifts) + groups["descan"] = [self._descan_shifts] if self.learn_scan_positions: - params.append(self._scan_positions_px) - if len(params) == 0: - raise RuntimeError( - "No parameters to optimize for dataset: learn_descan and learn_scan_positions are both False" - ) - return [{"params": params}] + groups["scan_positions"] = [self._scan_positions_px] + return groups + + def _normalize_optimizer_params(self, params): + """Broadcast a single optimizer spec to the learnable descan/scan_position groups. + + A single ``OptimizerType`` / single-optimizer dict (normalized to the ``"default"`` key) + is fanned out to whichever groups are currently learnable, so the common single-LR caller + keeps working. An explicit PPLR dict (keyed by ``descan``/``scan_positions``) passes through. + """ + norm = super()._normalize_optimizer_params(params) + if set(norm) == {self.DEFAULT_OPTIMIZER_KEY}: + spec = norm[self.DEFAULT_OPTIMIZER_KEY] + learnable = [ + key + for key, on in ( + ("descan", self.learn_descan), + ("scan_positions", self.learn_scan_positions), + ) + if on + ] + return {key: replace(spec) for key in learnable} if learnable else {} + return norm def to(self, *args, **kwargs): """Move all relevant tensors to a different device.""" diff --git a/src/quantem/diffractive_imaging/object_models.py b/src/quantem/diffractive_imaging/object_models.py index 0860bf55..3b74319b 100644 --- a/src/quantem/diffractive_imaging/object_models.py +++ b/src/quantem/diffractive_imaging/object_models.py @@ -232,13 +232,12 @@ def to(self, *args, **kwargs): def name(self) -> str: raise NotImplementedError() - def get_optimization_parameters(self) -> list[dict[str, Any]]: - """Get the parameters that should be optimized for this model.""" + def get_optimization_parameters(self) -> "dict[str, list[torch.Tensor]]": + """Get the parameters that should be optimized for this model, keyed by group.""" params = self.params if params is None: - return [] - else: - return [{"params": params}] # compatible with PPLR + return {} + return {self.DEFAULT_OPTIMIZER_KEY: list(params)} def _propagate_array( self, array: "torch.Tensor", propagator_array: "torch.Tensor" diff --git a/src/quantem/diffractive_imaging/probe_models.py b/src/quantem/diffractive_imaging/probe_models.py index b72f1426..6b00793a 100644 --- a/src/quantem/diffractive_imaging/probe_models.py +++ b/src/quantem/diffractive_imaging/probe_models.py @@ -94,13 +94,12 @@ def __init__( if roi_shape is not None: self.roi_shape = roi_shape - def get_optimization_parameters(self) -> list[dict[str, Any]]: - """Get the parameters that should be optimized for this model.""" + def get_optimization_parameters(self) -> "dict[str, list[torch.Tensor]]": + """Get the parameters that should be optimized for this model, keyed by group.""" params = self.params if params is None: - return [] - else: - return [{"params": params}] # compatible with PPLR + return {} + return {self.DEFAULT_OPTIMIZER_KEY: list(params)} @property def learn_probe_tilt(self) -> bool: diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 8fa19db7..12e8a99c 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -290,14 +290,12 @@ def params(self) -> Generator[torch.nn.Parameter, None, None]: raise NotImplementedError # --- Helper Functions --- - def get_optimization_parameters(self) -> list[dict[str, Any]]: - """Default: wrap self.params in a single param group.""" - if isinstance(self._optimizer_params, dict): - # Shouldn't happen for single-group models, but be defensive - opt = next(iter(self._optimizer_params.values())) - else: - opt = self._optimizer_params - return [{"params": list(self.params), **opt.params()}] + def get_optimization_parameters(self) -> "dict[str, list[torch.Tensor]]": + """Default: a single param group keyed by DEFAULT_OPTIMIZER_KEY. + + Hyperparameters are baked by ``set_optimizer``, not here — return only the tensors. + """ + return {self.DEFAULT_OPTIMIZER_KEY: list(self.params)} @abstractmethod # Each subclass should implement this. def to(self, device: str | torch.device): @@ -1026,16 +1024,10 @@ def params(self) -> Generator[torch.nn.Parameter, None, None]: return self.model.parameters() # type: ignore[attr-defined] - def get_optimization_parameters(self) -> list[dict[str, Any]]: - """PPLR: per-key param groups.""" + def get_optimization_parameters(self) -> "dict[str, list[torch.Tensor]]": + """PPLR: per-key param groups (hyperparameters are baked by set_optimizer).""" model = _unwrap(self.model) - assert isinstance(self._optimizer_params, dict), ( - "ObjectTensorDecomp requires dict-form optimizer_params" - ) - return [ - {"params": model.get_params()[key], **self._optimizer_params[key].params()} - for key in model.param_keys - ] + return {key: list(model.get_params()[key]) for key in model.param_keys} def _normalize_optimizer_params(self, params): """ObjectTensorDecomp requires a dict matching model.param_keys.""" diff --git a/tests/ml/test_optimizermixin.py b/tests/ml/test_optimizermixin.py index d829af41..d6347ce6 100644 --- a/tests/ml/test_optimizermixin.py +++ b/tests/ml/test_optimizermixin.py @@ -348,3 +348,106 @@ def test_parse_invalid_name_type_raises(self): def test_parse_default_name_is_none(self): result = SchedulerParams.parse_dict({}) assert isinstance(result, SchedulerParams.NoneScheduler) + + +# ─── OptimizerMixin.set_optimizer / PPLR behavior ─────────────────────────── + +from quantem.core import config # noqa: E402 +from quantem.core.ml.optimizer_mixin import OptimizerMixin # noqa: E402 + +torch = pytest.importorskip("torch") if config.get("has_torch") else None +requires_torch = pytest.mark.skipif( + not config.get("has_torch"), reason="requires torch" +) + + +def _param(value=1.0): + return torch.nn.Parameter(torch.tensor([value])) + + +class _FakeModel(OptimizerMixin): + """Minimal concrete OptimizerMixin for exercising set_optimizer/reset_optimizer. + + ``groups`` is the dict[str, list[Parameter]] returned by get_optimization_parameters. + Pass ``raise_on_params=True`` to prove the disable path short-circuits before the call. + """ + + def __init__(self, groups, raise_on_params=False): + super().__init__() + self._groups = groups + self._raise_on_params = raise_on_params + + def get_optimization_parameters(self): + if self._raise_on_params: + raise AssertionError("get_optimization_parameters should not have been called") + return self._groups + + +@requires_torch +class TestSetOptimizer: + def test_single_optimizer_build(self): + model = _FakeModel({"default": [_param()]}) + model.set_optimizer(OptimizerParams.Adam(lr=1e-3)) + assert model.has_optimizer() + assert isinstance(model.optimizer, torch.optim.Adam) + assert len(model.optimizer.param_groups) == 1 + assert model.optimizer.param_groups[0]["lr"] == 1e-3 + + def test_single_optimizer_dict_shorthand(self): + model = _FakeModel({"default": [_param()]}) + model.set_optimizer({"name": "sgd", "lr": 5e-2, "momentum": 0.9}) + assert isinstance(model.optimizer, torch.optim.SGD) + assert model.optimizer.param_groups[0]["lr"] == 5e-2 + assert model.optimizer.param_groups[0]["momentum"] == 0.9 + + def test_none_optimizer_removes_without_touching_params(self): + # raise_on_params proves the disable path short-circuits before get_optimization_parameters + model = _FakeModel({"default": [_param()]}, raise_on_params=True) + model.set_optimizer(OptimizerParams.NoneOptimizer()) + assert model.optimizer is None + assert not model.has_optimizer() + assert model.optimizer_params == { + OptimizerMixin.DEFAULT_OPTIMIZER_KEY: OptimizerParams.NoneOptimizer() + } + + def test_multi_group_pplr_applies_per_group_lr(self): + model = _FakeModel({"descan": [_param()], "scan_positions": [_param(2.0)]}) + model.set_optimizer( + {"descan": OptimizerParams.SGD(lr=1e-2), "scan_positions": OptimizerParams.SGD(lr=1e-3)} + ) + groups = model.optimizer.param_groups + assert len(groups) == 2 + # key order is preserved (descan, then scan_positions) + assert groups[0]["lr"] == 1e-2 + assert groups[1]["lr"] == 1e-3 + + def test_key_mismatch_raises(self): + model = _FakeModel({"default": [_param()]}) + with pytest.raises(ValueError, match="do not match"): + model.set_optimizer( + {"descan": OptimizerParams.SGD(lr=1e-2), "scan_positions": OptimizerParams.SGD()} + ) + + def test_mixed_optimizer_classes_raises(self): + model = _FakeModel({"a": [_param()], "b": [_param()]}) + with pytest.raises(ValueError, match="same optimizer type"): + model.set_optimizer( + {"a": OptimizerParams.Adam(lr=1e-3), "b": OptimizerParams.SGD(lr=1e-3)} + ) + + def test_reset_optimizer_on_unconfigured_model_is_noop(self): + model = _FakeModel({"default": [_param()]}) + # fresh model defaults to {"default": NoneOptimizer()} + model.reset_optimizer() + assert model.optimizer is None + assert not model.has_optimizer() + + def test_set_scheduler_base_lr_uses_max_group_lr(self): + model = _FakeModel({"descan": [_param()], "scan_positions": [_param(2.0)]}) + model.set_optimizer( + {"descan": OptimizerParams.SGD(lr=1e-2), "scan_positions": OptimizerParams.SGD(lr=1e-3)} + ) + model.set_scheduler(SchedulerParams.Plateau(), num_iter=10) + assert model.scheduler is not None + # Plateau min_lr defaults to base_LR / 20, with base_LR = max group lr (1e-2) + assert model.scheduler.min_lrs[0] == pytest.approx(1e-2 / 20) From c5f8b37a8e53d0b636955a6a9b9e2d2246ae7479 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Fri, 29 May 2026 15:02:51 -0700 Subject: [PATCH 319/335] Warnings to no optimizations on PtychographyDatasetBase. --- src/quantem/core/ml/optimizer_mixin.py | 28 +++++++++---------- .../diffractive_imaging/dataset_models.py | 11 +++++++- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index 1575276d..f79d7661 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -219,7 +219,7 @@ def parse_dict(cls, d: dict): raise ValueError(f"Unknown optimizer type: {name.lower()}") -OptimizerType = ( +OptimizerParamsType = ( OptimizerParams.Adam | OptimizerParams.AdamW | OptimizerParams.SGD @@ -537,7 +537,7 @@ def __init__(self): """Initialize the optimizer mixin.""" self._optimizer = None self._scheduler = None - self._optimizer_params: dict[str, OptimizerType] = { + self._optimizer_params: dict[str, OptimizerParamsType] = { self.DEFAULT_OPTIMIZER_KEY: OptimizerParams.NoneOptimizer() } self._scheduler_params: SchedulerType = SchedulerParams.NoneScheduler() @@ -554,31 +554,31 @@ def scheduler(self) -> "torch.optim.lr_scheduler.LRScheduler | None": return self._scheduler @property - def optimizer_params(self) -> dict[str, OptimizerType]: + def optimizer_params(self) -> dict[str, OptimizerParamsType]: """Get the optimizer parameters.""" return self._optimizer_params @optimizer_params.setter def optimizer_params( - self, params: OptimizerType | dict[str, OptimizerType] | dict[str, Any] + self, params: OptimizerParamsType | dict[str, OptimizerParamsType] | dict[str, Any] ) -> None: self._optimizer_params = self._normalize_optimizer_params(params) def _normalize_optimizer_params( - self, params: OptimizerType | dict[str, Any] - ) -> dict[str, OptimizerType]: - """Normalize input to dict[str, OptimizerType]. Subclasses can override to validate keys.""" - # Single optimizer, already an OptimizerType - if isinstance(params, OptimizerType): + self, params: OptimizerParamsType | dict[str, Any] + ) -> dict[str, OptimizerParamsType]: + """Normalize input to dict[str, OptimizerParamsType]. Subclasses can override to validate keys.""" + # Single optimizer, already an OptimizerParamsType + if isinstance(params, OptimizerParamsType): return {self.DEFAULT_OPTIMIZER_KEY: params} if not isinstance(params, dict): - raise TypeError(f"optimizer_params must be OptimizerType or dict, got {type(params)}") + raise TypeError(f"optimizer_params must be OptimizerParamsType or dict, got {type(params)}") # Single optimizer as dict shorthand, e.g. {"name": "adam", "lr": 1e-3} if self._is_single_optimizer_dict(params): return {self.DEFAULT_OPTIMIZER_KEY: OptimizerParams.parse_dict(d=params)} - # dict-of-OptimizerType form (PPLR) + # dict-of-OptimizerParamsType form (PPLR) return { - k: v if isinstance(v, OptimizerType) else OptimizerParams.parse_dict(d=v) + k: v if isinstance(v, OptimizerParamsType) else OptimizerParams.parse_dict(d=v) for k, v in params.items() } @@ -616,11 +616,11 @@ def get_optimization_parameters( """ raise NotImplementedError("Subclasses must implement get_optimization_parameters") - def set_optimizer(self, opt_params: OptimizerType | dict | None = None) -> None: + def set_optimizer(self, opt_params: OptimizerParamsType | dict | None = None) -> None: """ Set the optimizer for this model, supporting per-parameter-group learning rates (PPLR). - ``optimizer_params`` is a ``dict[str, OptimizerType]`` keyed by parameter group. Each + ``optimizer_params`` is a ``dict[str, OptimizerParamsType]`` keyed by parameter group. Each group's spec is joined by key to the tensors returned by ``get_optimization_parameters()`` and its hyperparameters are baked into the corresponding torch param group here. All groups must use the same optimizer class. If every group is a ``NoneOptimizer`` (or there diff --git a/src/quantem/diffractive_imaging/dataset_models.py b/src/quantem/diffractive_imaging/dataset_models.py index 39895adb..f663f3eb 100644 --- a/src/quantem/diffractive_imaging/dataset_models.py +++ b/src/quantem/diffractive_imaging/dataset_models.py @@ -1,3 +1,4 @@ +import warnings from abc import abstractmethod from dataclasses import replace from pathlib import Path @@ -12,7 +13,7 @@ from quantem.core.datastructures.dataset3d import Dataset3d from quantem.core.datastructures.dataset4dstem import Dataset4dstem from quantem.core.io.serialize import AutoSerialize -from quantem.core.ml.optimizer_mixin import OptimizerMixin +from quantem.core.ml.optimizer_mixin import OptimizerMixin, OptimizerParams from quantem.core.utils.utils import electron_wavelength_angstrom, tqdmnd from quantem.core.utils.validators import ( validate_array, @@ -127,6 +128,14 @@ def _normalize_optimizer_params(self, params): ) if on ] + if not learnable and not isinstance(spec, OptimizerParams.NoneOptimizer): + warnings.warn( + f"{type(self).__name__}: an optimizer was requested but nothing is " + "learnable (both learn_descan and learn_scan_positions are False); " + "the optimizer will be removed. Enable learn_descan and/or " + "learn_scan_positions to optimize.", + stacklevel=2, + ) return {key: replace(spec) for key in learnable} if learnable else {} return norm From 18486ee0080eb75aae59bedd0df1027a42d01bdf Mon Sep 17 00:00:00 2001 From: quantem-bot Date: Mon, 1 Jun 2026 15:29:32 +0000 Subject: [PATCH 320/335] chore: update lock file --- uv.lock | 735 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 382 insertions(+), 353 deletions(-) diff --git a/uv.lock b/uv.lock index 716284e6..0e8424aa 100644 --- a/uv.lock +++ b/uv.lock @@ -532,101 +532,101 @@ wheels = [ [[package]] name = "coverage" -version = "7.14.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/23/7f/d0720730a397a999ffc0fd3f5bebef347338e3a47b727da66fbb228e2ff2/coverage-7.14.0.tar.gz", hash = "sha256:057a6af2f160a85384cde4ab36f0d2777bae1057bae255f95413cdd382aa5c74", size = 919489, upload-time = "2026-05-10T18:02:31.397Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/e4/649c8d4f7f1709b6dbfc474358aa1bba02f67bcd52e2fec291a5014006cd/coverage-7.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a78e2a9d9c5e3b8d4ab9b9d28c985ea66fced0a7d7c2aec1f216e03a2011480", size = 219795, upload-time = "2026-05-10T17:59:48.198Z" }, - { url = "https://files.pythonhosted.org/packages/7f/8d/46692d24b3f395d4cbf17bfcc57136b4f2f9c0c0df864b0bddfc1d71a014/coverage-7.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1816c505187592dcd1c5a5f226601a549f70365fbd00930ac88b0c225b76bb4", size = 220299, upload-time = "2026-05-10T17:59:49.683Z" }, - { url = "https://files.pythonhosted.org/packages/12/c2/a40f5cb295bbcbb697a76947a56081c494c61950366294ee426ffe261099/coverage-7.14.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d8e1762f0e9cbc26ec315471e7b47855218e833cd5a032d706fbf43845d878c7", size = 250721, upload-time = "2026-05-10T17:59:51.494Z" }, - { url = "https://files.pythonhosted.org/packages/fd/35/202235eb5c3c14c212462cd91d61b7386bf8fc44bc7a77f4742d2a69174b/coverage-7.14.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9336e23e8bb3a3925398261385e2a1533957d3e760e91070dcb0e98bfa514eed", size = 252633, upload-time = "2026-05-10T17:59:53.244Z" }, - { url = "https://files.pythonhosted.org/packages/bb/80/5f596e8995785124ee191c42535664c5e62c65995b66f4ca21e28ae04c81/coverage-7.14.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd1169b2230f9cbe9c638ba38022ed7a2b1e641cc07f7cea0365e4be2a74980", size = 254743, upload-time = "2026-05-10T17:59:55.021Z" }, - { url = "https://files.pythonhosted.org/packages/1e/6d/0d178825be2350f0adb27984d0aa7cf84bbdab201f6fb926b535d23a8f5f/coverage-7.14.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d1bb3543b58fea74d2cd1abc4054cc927e4724687cb4560cd2ed88d2c7d820c0", size = 256700, upload-time = "2026-05-10T17:59:56.511Z" }, - { url = "https://files.pythonhosted.org/packages/19/5b/9e549c2f6e9dfea472adadba06c294e64735dabc2dd19015fac082095013/coverage-7.14.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a93bac2cb577ef60074999ed56d8a1535894398e2ed920d4185c3ec0c8864742", size = 250854, upload-time = "2026-05-10T17:59:57.94Z" }, - { url = "https://files.pythonhosted.org/packages/3d/1c/b94f9f5f36396021ee2f62c5834b12e6a3d31f0bed5d6fc6d1c3caec087c/coverage-7.14.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5904abf7e18cddc463219b17552229650c6b79e061d31a1059283051169cf7d5", size = 252433, upload-time = "2026-05-10T17:59:59.688Z" }, - { url = "https://files.pythonhosted.org/packages/b5/cb/d192cd8e1345eccabc32016f2d39072ecd10cb4f4b983ed8d0ebdeaf00dc/coverage-7.14.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:741f57cddc9004a8c81b084660215f33a6b597dbe62c31386b983ee26310e327", size = 250494, upload-time = "2026-05-10T18:00:01.953Z" }, - { url = "https://files.pythonhosted.org/packages/53/c5/aac9f460a41d835dbddef1d377f105f6ac2311d0f3c1588e9f51046d8813/coverage-7.14.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:664123feb0929d7affc135717dbd70d61d98688a08ab1e5ba464739620c6252d", size = 254261, upload-time = "2026-05-10T18:00:03.779Z" }, - { url = "https://files.pythonhosted.org/packages/23/aa/7af7c0081980a9cb3d289c5a435a4b7657dcecbd128e25c580e6a50389b5/coverage-7.14.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:c83d2399a51bbec8429266905d33616f04bc5726b1138c35844d5fcd896b2e20", size = 250216, upload-time = "2026-05-10T18:00:05.262Z" }, - { url = "https://files.pythonhosted.org/packages/35/60/a4257538ce2f6b978aeb51870d6c4208c510928a03db7e0339bb625dccb7/coverage-7.14.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb2e855b87321259a037429288ae85216d191c74de3e79bf57cd2bc0761992c", size = 251125, upload-time = "2026-05-10T18:00:06.858Z" }, - { url = "https://files.pythonhosted.org/packages/a1/ab/f91af47642ec1aa53490e835a95847168d9c77fc39aa58527604c051e145/coverage-7.14.0-cp311-cp311-win32.whl", hash = "sha256:731dc15b385ac52289743d476245b61e1a2927e803bef655b52bc3b2a75a21f3", size = 222300, upload-time = "2026-05-10T18:00:08.608Z" }, - { url = "https://files.pythonhosted.org/packages/f0/f0/a71ddbd874431e7a7cd96071f0c331cfbbad07704833c765d24ffbab8a67/coverage-7.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:bfb0ed8ec5d25e93face268115d7964db9df8b9aae8edcde9ec6b16c726a7cc1", size = 223241, upload-time = "2026-05-10T18:00:10.746Z" }, - { url = "https://files.pythonhosted.org/packages/d8/6e/d9d312a5151a96cd110efee32efc3fc97b01ebd86203fe618ccb29cf4c92/coverage-7.14.0-cp311-cp311-win_arm64.whl", hash = "sha256:7ebb1c6df9f78046a1b1e0a89674cd4bf73b7c648914eebcf976a57fd99a5627", size = 221908, upload-time = "2026-05-10T18:00:12.242Z" }, - { url = "https://files.pythonhosted.org/packages/09/1e/2f996b2c8415cbb6f54b0f5ec1ee850c96d7911961afb4fc05f4a89d8c58/coverage-7.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7ffd19fc8aed057fd686a17a4935eef5f9859d69208f96310e893e64b9b6ccf5", size = 219967, upload-time = "2026-05-10T18:00:13.756Z" }, - { url = "https://files.pythonhosted.org/packages/34/23/35c7aea1274aef7525bdd2dc92f710bdde6d11652239d71d1ec450067939/coverage-7.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:829994cfe1aeb773ca27bf246d4badc1e764893e3bfb98fff820fcecd1ca4662", size = 220329, upload-time = "2026-05-10T18:00:15.264Z" }, - { url = "https://files.pythonhosted.org/packages/75/cf/a8f4b43a16e194b0261257ad28ded5853ec052570afef4a84e1d81189f3b/coverage-7.14.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b4f07cf7edcb7ec39431a5074d7ea83b29a9f71fcfc494f0f40af4e65180420f", size = 251839, upload-time = "2026-05-10T18:00:17.16Z" }, - { url = "https://files.pythonhosted.org/packages/69/ff/6699e7b71e60d3049eb2bdcbc95ee3f35707b2b0e48f32e9e63d3ce30c08/coverage-7.14.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ca3d9cf2c32b521bd9518385608787fa86f38daf993695307531822c3430ed67", size = 254576, upload-time = "2026-05-10T18:00:18.829Z" }, - { url = "https://files.pythonhosted.org/packages/22/ec/c936d495fcd67f48f03a9c4ad3297ff80d1f222a5df3980f15b34c186c21/coverage-7.14.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92af52828e7f29d827346b0294e5a0853fa206db77db0395b282918d41e28db9", size = 255690, upload-time = "2026-05-10T18:00:20.648Z" }, - { url = "https://files.pythonhosted.org/packages/5c/42/5af63f636cc62a4a2b1b3ba9146f6ee6f53a35a50d5cefc54d5670f60999/coverage-7.14.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7b2bb6c9d7e769360d0f20a0f219603fd64f0c8f97de17ab25853261602be0fb", size = 257949, upload-time = "2026-05-10T18:00:22.28Z" }, - { url = "https://files.pythonhosted.org/packages/26/d3/a225317bd2012132a27e1176d51660b826f99bb975876463c44ea0d7ee5a/coverage-7.14.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1c9ed6ef99f88fb8c14aa8e2bf8eb0fe55fa2edfea68f8675d78741df1a5ac0e", size = 252242, upload-time = "2026-05-10T18:00:24.076Z" }, - { url = "https://files.pythonhosted.org/packages/f1/7f/9e65495298c3ea414742998539c37d048b5e81cc818fb1828cc6b51d10bf/coverage-7.14.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8231ade007f37959fbf58acc677f26b922c02eda6f0428ea307da0fd39681bf3", size = 253608, upload-time = "2026-05-10T18:00:25.588Z" }, - { url = "https://files.pythonhosted.org/packages/94/46/1522b524a35bdad22b2b8c4f9d32d0a104b524726ec380b2db68db1746f5/coverage-7.14.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d8b013632cc1ce1d09dbe4f32667b4d320ec2f54fc326ebeffcd0b0bcc2bb6c4", size = 251753, upload-time = "2026-05-10T18:00:27.104Z" }, - { url = "https://files.pythonhosted.org/packages/f3/e9/cdf00d38817742c541ade405e115a3f7bf36e6f2a8b99d4f209861b85a2d/coverage-7.14.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1733198802d71ec4c524f322e2867ee05c62e9e75df86bdca545407a221827d1", size = 255823, upload-time = "2026-05-10T18:00:29.038Z" }, - { url = "https://files.pythonhosted.org/packages/38/fc/5e7877cf5f902d08a17ff1c532511476d87e1bea355bd5028cb97f902e79/coverage-7.14.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:72a305291fa8ee01332f1aaf38b348ca34097f6aa0b0ef627eef2837e57bbba5", size = 251323, upload-time = "2026-05-10T18:00:30.647Z" }, - { url = "https://files.pythonhosted.org/packages/18/9d/50f05a72dff8487464fdd4178dda5daed642a060e60afb644e3d45123559/coverage-7.14.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fcaba850dd317c65423a9d63d88f9573c53b00354d6dd95724576cc98a131595", size = 253197, upload-time = "2026-05-10T18:00:32.211Z" }, - { url = "https://files.pythonhosted.org/packages/00/3f/6f61ffe6439df266c3cf60f5c99cfaa21103d0210d706a42fc6c30683ff8/coverage-7.14.0-cp312-cp312-win32.whl", hash = "sha256:5ac83957a80d0701310e96d8bec68cdcf4f90a7674b7d13f15a344315b41ab27", size = 222515, upload-time = "2026-05-10T18:00:33.717Z" }, - { url = "https://files.pythonhosted.org/packages/85/19/93853133df2cb371083285ef6a93982a0173e7a233b0f61373ba9fd30eb2/coverage-7.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:70390b0da32cb90b501953716302906e8bcce087cb283e70d8c97729f22e92b2", size = 223324, upload-time = "2026-05-10T18:00:35.172Z" }, - { url = "https://files.pythonhosted.org/packages/74/18/9f7fe62f659f24b7a82a0be56bf94c1bd0a89e0ae7ab4c668f6e82404294/coverage-7.14.0-cp312-cp312-win_arm64.whl", hash = "sha256:91b993743d959b8be85b4abf9d5478216a69329c321efe5be0433c1a841d691d", size = 221944, upload-time = "2026-05-10T18:00:37.014Z" }, - { url = "https://files.pythonhosted.org/packages/6b/76/b7c66ee3c66e1b0f9d894c8125983aa0c03fb2336f2fd16559f9c966157f/coverage-7.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f2bbb8254370eb4c628ff3d6fa8a7f74ddc40565394d4f7ab791d1fe568e37ef", size = 219990, upload-time = "2026-05-10T18:00:38.887Z" }, - { url = "https://files.pythonhosted.org/packages/b3/af/e567cbad5ba69c013a50146dfa886dc7193361fda77521f51274ff620e1b/coverage-7.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:23b81107f46d3f21d0cbce30664fcec0f5d9f585638a67081750f99738f6bf66", size = 220365, upload-time = "2026-05-10T18:00:40.864Z" }, - { url = "https://files.pythonhosted.org/packages/44/6f/9ad575d505b4d805b254febc8a5b338a2efe278f8786e56ff1cb8413f9c3/coverage-7.14.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:22a7e06a5f11a757cdfe79018e9095f9f69ae283c5cd8123774c788deec8717b", size = 251363, upload-time = "2026-05-10T18:00:42.489Z" }, - { url = "https://files.pythonhosted.org/packages/6f/5f/b5370068b2f57787454592ed7dcd1002f0f1703b7db1fa30f6a325a4ca6e/coverage-7.14.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9d1aa57a1dc8e05bdc42e81c5d671d849577aeedf279f4c449d6d286f9ed88ca", size = 253961, upload-time = "2026-05-10T18:00:44.079Z" }, - { url = "https://files.pythonhosted.org/packages/29/1e/51adf17738976e8f2b85ddef7b7aa12a0838b056c92f175941d8862767c1/coverage-7.14.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90c1a51bcfddf645b3bb7ec333d9e94393a8e94f55642380fa8a9a5a9e636cb7", size = 255193, upload-time = "2026-05-10T18:00:45.623Z" }, - { url = "https://files.pythonhosted.org/packages/9e/7b/5bfd7ac1df3b881c2ac7a5cbc99c7609e6296c402f5ef587cd81c6f355b3/coverage-7.14.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a841fae2fadcae4f438d43b6ccc4aac2ad609f47cdb6cfdce60cbb3fe5ca7bc2", size = 257326, upload-time = "2026-05-10T18:00:47.173Z" }, - { url = "https://files.pythonhosted.org/packages/7d/38/1d37d316b174fad3843a1d76dbdfe4398771c9ecd0515935dd9ece9cd627/coverage-7.14.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c79d2319cabef1fe8e86df73371126931550804738f78ad7d31e3aad85a67367", size = 251582, upload-time = "2026-05-10T18:00:49.152Z" }, - { url = "https://files.pythonhosted.org/packages/34/46/746704f95980ba220214e1a41e18cec5aea80a898eaa53c51bf2d645ff36/coverage-7.14.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1b23b0c6f0b1db6ad769b7050c8b641c0bf215ded26c1816955b17b7f26edfa9", size = 253325, upload-time = "2026-05-10T18:00:51.252Z" }, - { url = "https://files.pythonhosted.org/packages/e1/b9/bbe87206d9687b192352f893797825b5f5b15ecd3aa9c68fbff0c074d77b/coverage-7.14.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:55d3089079ce181a4566b1065ab28d2575eb76d8ac8f81f4fcda2bf037fee087", size = 251291, upload-time = "2026-05-10T18:00:52.816Z" }, - { url = "https://files.pythonhosted.org/packages/46/57/b8cdb12ac0d73ef0243218bd5e22c9df8f92edab8018213a86aec67c5324/coverage-7.14.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:49c005cba1e2f9677fb2845dcdf9a2e72a52a17d63e8231aaaae35d9f50215ef", size = 255448, upload-time = "2026-05-10T18:00:54.548Z" }, - { url = "https://files.pythonhosted.org/packages/1f/d4/5002019538b2036ce3c84340f54d2fd5100d55b0a6b0894eee56128d03c7/coverage-7.14.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:9117377b823daa28aa8635fbb08cda1cd6be3d7143257345459559aeef852d52", size = 251110, upload-time = "2026-05-10T18:00:56.122Z" }, - { url = "https://files.pythonhosted.org/packages/37/53/20c5009477660f084e6ed60bc02a91894b8e234e617e86ecfd9aaf78e27b/coverage-7.14.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7b79d646cf46d5cf9a9f40281d4441df5849e445726e369006d2b117710b33fe", size = 252885, upload-time = "2026-05-10T18:00:57.967Z" }, - { url = "https://files.pythonhosted.org/packages/ae/ab/3cf6427ac9c1f1db747dbb1ce71dde47984876d4c2cfd018a3fef0a78d4d/coverage-7.14.0-cp313-cp313-win32.whl", hash = "sha256:fb609b3658479e33f9516d46f1a89dbb9b6c261366e3a11844a96ec487533dae", size = 222539, upload-time = "2026-05-10T18:00:59.581Z" }, - { url = "https://files.pythonhosted.org/packages/8f/b8/9228523e80321c2cb4880d1f589bc0171f2f71432c35118ad04dc01decce/coverage-7.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:0773d8329cf32b6fd222e4b52622c61fe8d503eb966cfc8d3c3c10c96266d50e", size = 223344, upload-time = "2026-05-10T18:01:01.531Z" }, - { url = "https://files.pythonhosted.org/packages/a3/99/118daa192f95e3a6cb2740100fbf8797cda1734b4134ef0b5d501a7fa8f3/coverage-7.14.0-cp313-cp313-win_arm64.whl", hash = "sha256:b4e26a0f1b696faf283bffe5b8569e44e336c582439df5d53281ab89ee0cba96", size = 221966, upload-time = "2026-05-10T18:01:03.16Z" }, - { url = "https://files.pythonhosted.org/packages/e6/f1/a46cc0c013be170216253184a32366d7cbdb9252feaec866b05c2d12a894/coverage-7.14.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:953f521ca9445300397e65fda3dca58b2dbd68fee983777420b57ac3c77e9f90", size = 220679, upload-time = "2026-05-10T18:01:05.058Z" }, - { url = "https://files.pythonhosted.org/packages/64/8c/9c30a3d311a34177fa432995be7fbfc64477d8bac5630bd38055b1c9b424/coverage-7.14.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:98af83fd65ae24b1fdd03aaead967a9f523bcd2f1aab2d4f3ffda65bb568a6f1", size = 221033, upload-time = "2026-05-10T18:01:07.002Z" }, - { url = "https://files.pythonhosted.org/packages/9a/cd/3fb5e06c3badefd0c1b47e2044fdca67f8220a4ec2e7fcfb476aa0a67c6c/coverage-7.14.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:668b92e6958c4db7cf92e81caac328dfbbdbb215db2850ad28f0cbe1eea0bfbd", size = 262333, upload-time = "2026-05-10T18:01:08.903Z" }, - { url = "https://files.pythonhosted.org/packages/a8/e6/fbc322325c7294d3e22c1ad6b79e45d0806b25228c8e5842aed6d8169aa7/coverage-7.14.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9fbd898551762dea00d3fef2b1c4f99afd2c6a3ff952ea07d60a9bd5ed4f34bc", size = 264410, upload-time = "2026-05-10T18:01:10.531Z" }, - { url = "https://files.pythonhosted.org/packages/08/92/c497b264bec1673c47cc77e26f760fcda4654cabf1f39546d1a23a3b8c35/coverage-7.14.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68af363c07ecd8d4b7d4043d85cb376d7d227eceb54e5323ee45da73dbd3e426", size = 266836, upload-time = "2026-05-10T18:01:12.19Z" }, - { url = "https://files.pythonhosted.org/packages/78/fc/045da320987f401af5d2815d351e8aa799aec859f60e29f445e3089eeedb/coverage-7.14.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6e57054a583da8ac55edf24117ea4c9133032cfc4cf72aa2d48c1e5d4b52f899", size = 267974, upload-time = "2026-05-10T18:01:13.926Z" }, - { url = "https://files.pythonhosted.org/packages/1b/ae/227b1e379497fb7a4fc3286e620f80c8a1e7cec66d45695a01639eb1af65/coverage-7.14.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cc3499459bbcdd51a65b64c35ab7ed2764eaf3cba826e0df3f1d7fe2e102b70b", size = 261578, upload-time = "2026-05-10T18:01:15.564Z" }, - { url = "https://files.pythonhosted.org/packages/a0/f5/3570342900f2acea31d33ff1590c5d8bac1a8e1a2e1c6d34a5d5e61de681/coverage-7.14.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:45899ec2138a4346ed34d601dedf5076fb74edf2d1dd9dc76a78e82397edee90", size = 264394, upload-time = "2026-05-10T18:01:17.607Z" }, - { url = "https://files.pythonhosted.org/packages/16/29/de1bbc01c935b28f89b1dc3db85b011c055e843a8e5e3b83141c3f80af7f/coverage-7.14.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8767486808c436f05b23ab98eb963fb29185e32a9357a166971685cb3459900f", size = 262022, upload-time = "2026-05-10T18:01:19.304Z" }, - { url = "https://files.pythonhosted.org/packages/35/95/f53890b0bf2fc10ab168e05d38869215e73ca24c4cb521c3bb0eb62fe16b/coverage-7.14.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a3b5ddfd6aa7ddad53ee3edb231e88a2151507a43229b7d71b953916deca127d", size = 265732, upload-time = "2026-05-10T18:01:21.494Z" }, - { url = "https://files.pythonhosted.org/packages/ed/ea/c919e259081dd2bdf0e43b87209709ba7ec2e4117c2a7f5185379c43463c/coverage-7.14.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:63df0fe568e698e1045792399f8ab6da3a6c2dce3182813fb92afa2641087b47", size = 260921, upload-time = "2026-05-10T18:01:23.533Z" }, - { url = "https://files.pythonhosted.org/packages/1a/2c/c2831889705a81dc5d1c6ca12e4d8e9b95dfc146d153488a6c0ea685d28e/coverage-7.14.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:827d6397dbd95144939b18f89edf31f63e1f99633e8d5f32f22ba8bdda567477", size = 263109, upload-time = "2026-05-10T18:01:25.165Z" }, - { url = "https://files.pythonhosted.org/packages/5a/a9/2fcae5003cac3d63fe344d2166243c2756935f48420863c5272b240d550b/coverage-7.14.0-cp313-cp313t-win32.whl", hash = "sha256:7bf43e000d24012599b879791cff41589af90674722421ef11b11a5431920bab", size = 223212, upload-time = "2026-05-10T18:01:27.157Z" }, - { url = "https://files.pythonhosted.org/packages/3f/bb/18e94d7b14b9b398164197114a587a04ab7c9fdbe1d237eef57311c5e883/coverage-7.14.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3f5549365af25d770e06b1f8f5682d9a5637d06eb494db91c6fa75d3950cc917", size = 224272, upload-time = "2026-05-10T18:01:29.107Z" }, - { url = "https://files.pythonhosted.org/packages/db/56/4f14fad782b035c81c4ffd09159e7103d42bb1d93ac8496d04b90a11b7da/coverage-7.14.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6d160217ec6fe890f16ad3a9531761589443749e448f91986c972714fad361c8", size = 222530, upload-time = "2026-05-10T18:01:31.151Z" }, - { url = "https://files.pythonhosted.org/packages/1c/18/b9a6586d73992807c26f9a5f274131be3d76b56b18a82b9392e2a25d2e45/coverage-7.14.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9aed9fa983514ca032790f3fe0d1c0e42ca7e16b42432af1706b50a9a46bef5d", size = 220036, upload-time = "2026-05-10T18:01:33.057Z" }, - { url = "https://files.pythonhosted.org/packages/f3/9b/4165a1d56ddc302a0e2d518fd9d412a4fd0b57562618c78c5f21c57194f5/coverage-7.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ba3b8390db29296dbbf49e91b6fe08f990743a90c8f447ba4c2ffc29670dfa63", size = 220368, upload-time = "2026-05-10T18:01:34.705Z" }, - { url = "https://files.pythonhosted.org/packages/69/aa/c12e52a5ba148d9995229d557e3be6e554fe469addc0e9241b2f0956d8ea/coverage-7.14.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3a5d8e876dfa2f102e970b183863d6dedd023d3c0eeca1fe7a9787bc5f28b212", size = 251417, upload-time = "2026-05-10T18:01:36.949Z" }, - { url = "https://files.pythonhosted.org/packages/d7/51/ec641c26e6dca1b25a7d2035ba6ecb7c884ef1a100a9e42fbe4ce4405139/coverage-7.14.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5ebb8f4614a3787d567e610bbfdf96a4798dd69a1afb1bd8ad228d4111fe6ff3", size = 253924, upload-time = "2026-05-10T18:01:38.985Z" }, - { url = "https://files.pythonhosted.org/packages/33/c4/59c3de0bd1b538824173fd518fed51c1ce740ca5ed68e74545983f4053a9/coverage-7.14.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b9bf47223dd8db3d4c4b2e443b02bace480d428f0822c3f991600448a176c97", size = 255269, upload-time = "2026-05-10T18:01:40.957Z" }, - { url = "https://files.pythonhosted.org/packages/7b/a9/36dfa153a62040296f6e7febfdb20a5720622f6ef5a81a41e8237b9a5344/coverage-7.14.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3485a836550b303d006d57cc06e3d5afaabc642c77050b7c985a97b13e3776b8", size = 257583, upload-time = "2026-05-10T18:01:42.607Z" }, - { url = "https://files.pythonhosted.org/packages/26/7b/cc2c048d4114d9ab1c2409e9ee365e5ae10736df6dffcfc9444effa6c708/coverage-7.14.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3e7e88110bae996d199d1693ca8ec3fd52441d426401ae963437598667b4c5eb", size = 251434, upload-time = "2026-05-10T18:01:44.537Z" }, - { url = "https://files.pythonhosted.org/packages/ee/df/6770eaa576e604575e9a78055313250faef5faa84bd6f71a39fece519c43/coverage-7.14.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15228a6800ce7bdf1b74800595e56db7138cecb338fdbf044806e10dcf182dfe", size = 253280, upload-time = "2026-05-10T18:01:46.175Z" }, - { url = "https://files.pythonhosted.org/packages/ad/9e/1c0264514a3f98259a6d64765a397b2c8373e3ba59ee722a4802d3ec0c61/coverage-7.14.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9d26ac7f5398bafc5b57421ad994e8a4749e8a7a0e62d05ec7d53014d5963bfa", size = 251241, upload-time = "2026-05-10T18:01:48.732Z" }, - { url = "https://files.pythonhosted.org/packages/64/16/4efdf3e3c4079cdbf0ece56a2fea872df9e8a3e15a13a0af4400e1075944/coverage-7.14.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2fb73254ff43c911c967a899e1359bc5049b4b115d6e8fbdde4937d0a2246cd5", size = 255516, upload-time = "2026-05-10T18:01:50.819Z" }, - { url = "https://files.pythonhosted.org/packages/93/69/b1de96346603881b3d1bc8d6447c83200e1c9700ffbaff926ba01ff5724c/coverage-7.14.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:454a380af72c6adada298ed270d38c7a391288198dbfb8467f786f588751a90c", size = 251059, upload-time = "2026-05-10T18:01:52.773Z" }, - { url = "https://files.pythonhosted.org/packages/a4/66/2881853e0363a5e0a724d1103e53650795367471b6afb234f8b49e713bc6/coverage-7.14.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:65c86fb646d2bd2972e96bd1a8b45817ed907cee68655d6295fe7ec031d04cca", size = 252716, upload-time = "2026-05-10T18:01:54.506Z" }, - { url = "https://files.pythonhosted.org/packages/55/5c/0d3305d002c41dcde873dbe456491e663dc55152ca526b630b5c47efd62f/coverage-7.14.0-cp314-cp314-win32.whl", hash = "sha256:6a6516b02a6101398e19a3f44820f69bab2590697f7def4331f668b14adaf828", size = 222788, upload-time = "2026-05-10T18:01:56.487Z" }, - { url = "https://files.pythonhosted.org/packages/f9/58/6e1b8f52fdc3184b47dc5037f5070d83a3d11042db1594b02d2a44d786c8/coverage-7.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:45e0f79d8351fa76e256716df91eab12890d32678b9590df7ae1042e4bd4cf5d", size = 223600, upload-time = "2026-05-10T18:01:58.497Z" }, - { url = "https://files.pythonhosted.org/packages/00/70/a18c408e674bc26281cadaedc7351f929bd2094e191e4b15271c30b084cc/coverage-7.14.0-cp314-cp314-win_arm64.whl", hash = "sha256:4b899594a8b2d81e5cc064a0d7f9cac2081fed91049456cae7676787e41549c9", size = 222168, upload-time = "2026-05-10T18:02:00.411Z" }, - { url = "https://files.pythonhosted.org/packages/3d/89/2681f071d238b62aff8dfc2ab44fc24cfdb38d1c01f391a80522ff5d3a16/coverage-7.14.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f580f8c80acd94ac72e863efe2cab791d8c38d153e0b463b92dfa000d5c84cd1", size = 220766, upload-time = "2026-05-10T18:02:02.313Z" }, - { url = "https://files.pythonhosted.org/packages/bd/c7/c987babafd9207ffa1995e1ef1f9b26762cf4963aa768a66b6f0501e4616/coverage-7.14.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a2bd259c442cd43c49b30fbafc51776eb19ea396faf159d26a83e6a0a5f13b0c", size = 221035, upload-time = "2026-05-10T18:02:04.017Z" }, - { url = "https://files.pythonhosted.org/packages/5a/e9/d6a5ac3b333088143d6fc877d398a9a674dc03124a2f776e131f03864823/coverage-7.14.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a706b908dfa85538863504c624b237a3cc34232bf403c057414ebfdb3b4d9f84", size = 262405, upload-time = "2026-05-10T18:02:05.915Z" }, - { url = "https://files.pythonhosted.org/packages/38/b1/e70838d29a7c08e22d44398a46db90815bbcbf28de06992bd9210d1a8d8e/coverage-7.14.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7333cd944ee4393b9b3d3c1b598c936d4fc8d70573a4c7dacfec5590dd50e436", size = 264530, upload-time = "2026-05-10T18:02:07.582Z" }, - { url = "https://files.pythonhosted.org/packages/6b/73/5c31ef97763288d03d9995152b96d5475b527c63d91c84b01caea894b83a/coverage-7.14.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f162bc9a15b82d947b02651b0c7e1609d6f7a8735ca330cfadec8481dd97d5a", size = 266932, upload-time = "2026-05-10T18:02:09.401Z" }, - { url = "https://files.pythonhosted.org/packages/e1/76/dd56d80f29c5f05b4d76f7e7c6d47cafacae017189c75c5759d24f9ff0cc/coverage-7.14.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:362cb78e01a5dc82009d88004cf60f2e6b6d6fcbfdec05b05af73b0abf40118f", size = 268062, upload-time = "2026-05-10T18:02:11.399Z" }, - { url = "https://files.pythonhosted.org/packages/6e/c7/27ba85cd5b95614f159ff93ebff1901584a8d192e2e5e24c4943a7453f59/coverage-7.14.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:acebd068fca5512c3a6fde9c045f901613478781a73f0e82b307b214daef23fb", size = 261504, upload-time = "2026-05-10T18:02:13.257Z" }, - { url = "https://files.pythonhosted.org/packages/13/2e/e8149f60ab5d5684c6eee881bdf34b127115cddbb958b196768dd9d63473/coverage-7.14.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:29fe3da551dface75deb2ccbf87b6b66e2e7ef38f6d89050b428be94afff3490", size = 264398, upload-time = "2026-05-10T18:02:15.063Z" }, - { url = "https://files.pythonhosted.org/packages/d9/7f/1261b025285323225f4b4abffa5a643649dfd67e25ddca7ebcbdea3b7cb3/coverage-7.14.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b4cc4fce8672fffcb09b0eafc167b396b3ba53c4a7230f54b7aaffbf6c835fa9", size = 262000, upload-time = "2026-05-10T18:02:16.756Z" }, - { url = "https://files.pythonhosted.org/packages/d3/dc/829c54f60b9d08389439c00f813c752781c496fc5788c78d8006db4b4f2b/coverage-7.14.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5d4a51aad8ba8bdcd2b8bd8f03d4aca19693fa2327a3470e4718a25b03481020", size = 265732, upload-time = "2026-05-10T18:02:18.817Z" }, - { url = "https://files.pythonhosted.org/packages/ed/b0/70bd1419941652fa062689cba9c3eeafb8f5e6fbb890bce41c3bdda5dbd6/coverage-7.14.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:9f323af3e1e4f68b60b7b247e37b8515563a61375518fa59de1af48ba28a3db6", size = 260847, upload-time = "2026-05-10T18:02:20.528Z" }, - { url = "https://files.pythonhosted.org/packages/f2/73/be40b2390656c654d35ea0015ea7ba3d945769cf80790ad5e0bb2d56d2ba/coverage-7.14.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1a0abc7342ea9711c469dd8b821c6c311e6bc6aac1442e5fbd6b27fae0a8f3db", size = 263166, upload-time = "2026-05-10T18:02:22.337Z" }, - { url = "https://files.pythonhosted.org/packages/29/55/4a643f712fcf7cf2881f8ec1e0ccb7b164aff3108f69b51801246c8799f2/coverage-7.14.0-cp314-cp314t-win32.whl", hash = "sha256:a9f864ef57b7172e2db87a096642dd51e179e085ab6b2c371c29e885f65c8fb2", size = 223573, upload-time = "2026-05-10T18:02:24.11Z" }, - { url = "https://files.pythonhosted.org/packages/27/96/3acae5da0953be042c0b4dea6d6789d2f080701c77b88e44d5bd41b9219b/coverage-7.14.0-cp314-cp314t-win_amd64.whl", hash = "sha256:29943e552fdc08e082eb51400fb2f58e118a83b5542bd06531214e084399b644", size = 224680, upload-time = "2026-05-10T18:02:25.896Z" }, - { url = "https://files.pythonhosted.org/packages/93/3d/6ab5d2dd8325d838737c6f8d83d62eb6230e0d70b87b51b57bbfd08fa767/coverage-7.14.0-cp314-cp314t-win_arm64.whl", hash = "sha256:742a73ea621953b012f2c4c2219b512180dd84489acf5b1596b0aafc55b9100b", size = 222703, upload-time = "2026-05-10T18:02:27.822Z" }, - { url = "https://files.pythonhosted.org/packages/61/e8/cb8e80d6f9f55b99588625062822bf946cf03ed06315df4bd8397f5632a1/coverage-7.14.0-py3-none-any.whl", hash = "sha256:8de5b61163aee3d05c8a2beab6f47913df7981dad1baf82c414d99158c286ab1", size = 211764, upload-time = "2026-05-10T18:02:29.538Z" }, +version = "7.14.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/fd/0ab2772530e946e1be1abd0bc09e647ec9b02e88f0867857601fefca8953/coverage-7.14.1.tar.gz", hash = "sha256:30c08f7d90415aa98b3c990385dea2939b0da55f38515e5b369b83655f8523be", size = 920132, upload-time = "2026-05-26T20:41:36.783Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/d7/477ad149490e6cb849f28abea1dabb9c823cea72e7500c81b4240ce619c0/coverage-7.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:478b5bcd63c2e1357c5c7e16c070690df7b07f676b1c114d7b93e533c664309f", size = 219848, upload-time = "2026-05-26T20:38:38.715Z" }, + { url = "https://files.pythonhosted.org/packages/91/82/a5eb47257c50601bb7b9a9d2857c67b7a3a85ad74180eb2c98bb1fbe0ce5/coverage-7.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a24a81f9715ee42ef59a316cc11611c98fe23920f7c81861315c9f3ff4a230f4", size = 220354, upload-time = "2026-05-26T20:38:40.232Z" }, + { url = "https://files.pythonhosted.org/packages/43/8b/78419b5391a5cb706b6544390507e469d83ffc9a8248b02c4011aceb9365/coverage-7.14.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:196a13319ad88d6d8ef5ab489ec4f44ddde2143c0c7d5b27786f6c3ffd56a7e1", size = 250771, upload-time = "2026-05-26T20:38:41.782Z" }, + { url = "https://files.pythonhosted.org/packages/77/63/e77aaacd491182210d639636b7a8bba23ffffa9b82aa3762da9431855fa9/coverage-7.14.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3d452fd08b5c72c5167c93e6867b5c08500bd40f2a21e1e854a500550b6cc36f", size = 252683, upload-time = "2026-05-26T20:38:43.305Z" }, + { url = "https://files.pythonhosted.org/packages/65/1c/a022e3cfbec2ac241640003cb3a817e161d9c7f5aa9b49173756cdc03204/coverage-7.14.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23bf7fa51ac02e07fc7c96849b82946da47ae862dc8f86d183b2a4864fc38129", size = 254791, upload-time = "2026-05-26T20:38:45.361Z" }, + { url = "https://files.pythonhosted.org/packages/61/d6/967e408aca4c1ceb88cb0cc677169110ae7f5995fb5eaf5fb1f5a1bb8f5d/coverage-7.14.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bcaa50684dcaadfa599ac48f81103c756d791cfd85c97203d2217c593d48b860", size = 256748, upload-time = "2026-05-26T20:38:46.91Z" }, + { url = "https://files.pythonhosted.org/packages/b8/be/869188f7fe28638078ec479331ace6dc5f7b40b7153eb616f47ab79404d8/coverage-7.14.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4ea1c034f95c9b056e856b794630b17f9fa3d57e4800ff1e503d3be0f9c9078c", size = 250907, upload-time = "2026-05-26T20:38:48.493Z" }, + { url = "https://files.pythonhosted.org/packages/07/aa/adb7d3b4278d690e68703abcd76ab1b948242e3668d921711551b78f9ddb/coverage-7.14.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c7e057326434e441306226fbeb5d1aaf14a2637efe97ba668306635835f32ad7", size = 252483, upload-time = "2026-05-26T20:38:50.074Z" }, + { url = "https://files.pythonhosted.org/packages/43/61/331c74103c62dcb0c4b9b3a0de9a61aca016208b0a90f109592a9f9ecc28/coverage-7.14.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:59baf88468dbc8d63b1887afd92bda52e40bb1561696e5819670601403810cec", size = 250545, upload-time = "2026-05-26T20:38:51.613Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b6/c5dae3c104d89be04828f61810e6b3473825482e4c288cc4ed04553e08ae/coverage-7.14.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d34d75f892b3ab73ba11cab5442cce7b3e168fd64162b16f0e1e0d09c508edef", size = 254310, upload-time = "2026-05-26T20:38:53.503Z" }, + { url = "https://files.pythonhosted.org/packages/ad/a1/2b9d5863e3b83c01ad8199e3c597802fbb3a9dc90b058885804c20296d31/coverage-7.14.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3a56abc20a472baf0304c455721bc601477440d28ecfde8a03dde79ede07e0df", size = 250266, upload-time = "2026-05-26T20:38:55.414Z" }, + { url = "https://files.pythonhosted.org/packages/7f/5e/0e511fbdb269359be26fe678a1c3fa1f2aa2a01573cc3f54268c8d6d4797/coverage-7.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6a3cb83d1552c0cd1b4906655b6a33fd4a8473229633a901c6b73bf86914dee9", size = 251174, upload-time = "2026-05-26T20:38:57.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/10/e55307b622b3dd9671cb321824502dc10f93e72f2802b9946159a8edadeb/coverage-7.14.1-cp311-cp311-win32.whl", hash = "sha256:10274a1fbeb8ec5d72966e17bb198a3104257aca4ac09d98667c5f8aca8c8548", size = 222354, upload-time = "2026-05-26T20:38:58.727Z" }, + { url = "https://files.pythonhosted.org/packages/71/cf/107421693cfb71e4f1ca5bf70443f64d4161878068d07a3e51c7ad21d17b/coverage-7.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:87ebdf787d4888e3f3f2d523eadc6e18c6d18c6d0eb173801a189641627fb37e", size = 223290, upload-time = "2026-05-26T20:39:00.413Z" }, + { url = "https://files.pythonhosted.org/packages/b8/1d/3e3644585eb29e9dafefb19555078529a4d7cce12bd21929664eea989277/coverage-7.14.1-cp311-cp311-win_arm64.whl", hash = "sha256:dd34767fa19848d35659ffc0a75314f58c7af3f1cd87ec521e8292a1238398a3", size = 221953, upload-time = "2026-05-26T20:39:02.159Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b7/bdbb725ba02c5b42825b200c940f38b7a54fcad24627b7192f78f8110d76/coverage-7.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a06c76364a9360e33d6d23769aefdf7f66f38e2ffb60ceb1baaa4989d83b695c", size = 220022, upload-time = "2026-05-26T20:39:03.702Z" }, + { url = "https://files.pythonhosted.org/packages/72/81/fdc0898a55c6219223291ec1a1fe89966ef212ce82276aa0899df84b5de0/coverage-7.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fad54e871165f6ec2f536063ac74c3104508a12963e64072ba44bd822de52b0c", size = 220379, upload-time = "2026-05-26T20:39:05.381Z" }, + { url = "https://files.pythonhosted.org/packages/de/72/de048c4a25e13bce59ac6a339351c10bdf2515e07459afcdaf04dc3143a2/coverage-7.14.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:84b535f00655ecafe1d929d1fb00ed5d6fa3051ea643ab2c161a3887b86f294b", size = 251888, upload-time = "2026-05-26T20:39:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/28/30/300c343f68beb9d4cbb64ec81e58c5b6b80b56927f72d2b38654ac26e013/coverage-7.14.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6b6b0853b895fe0e98cbfc580d1ec3393d9302b4b1e96a77b3f5c91fdab899e6", size = 254624, upload-time = "2026-05-26T20:39:09.037Z" }, + { url = "https://files.pythonhosted.org/packages/b1/ed/7b25642496e8170b6bac14adce00537c6e5fa2d586159401a4de3e8b49e6/coverage-7.14.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:442cc9c952b2df400cda54bb04ab87330cf2cd08a8692cbbea36773531eb6f37", size = 255739, upload-time = "2026-05-26T20:39:10.889Z" }, + { url = "https://files.pythonhosted.org/packages/7f/a2/abd210b8c4e29c24e4624916db97bb519097a91034aaeb767f937e7da794/coverage-7.14.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8270544c361ed405a27a060dbc9ed2c124b084d96dfdc2d9a2510482aef981ad", size = 257998, upload-time = "2026-05-26T20:39:12.722Z" }, + { url = "https://files.pythonhosted.org/packages/7f/24/7c50beed3792fe62f6ce0545c6686ce83379719e2c0276179333d97eae92/coverage-7.14.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:48b283b1dd6372e8de2a7a9a4c4d5dc06f4d4fd209b876f3c88a7a205a0c8f84", size = 252296, upload-time = "2026-05-26T20:39:14.259Z" }, + { url = "https://files.pythonhosted.org/packages/15/05/0f874628ebcbfc77ead559ff210281ef06a97db08481832e7dd39274a135/coverage-7.14.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5b0c99ba93a07d56f6df340bb79be53202a082b2fdb81bfe6190b741a3470d54", size = 253658, upload-time = "2026-05-26T20:39:15.923Z" }, + { url = "https://files.pythonhosted.org/packages/99/6f/ca6ad067364b337ef997802115e7ecad2abd2248b05471464b0dea02b4d4/coverage-7.14.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e471bc5769ff073b058cfadb0d736b56ce067c8560eabeb0da88462df98c23e7", size = 251803, upload-time = "2026-05-26T20:39:17.537Z" }, + { url = "https://files.pythonhosted.org/packages/c0/30/b9b4d377cd9f40baf228068f5a81faf8450c6228503011bd499708483a50/coverage-7.14.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f497a1ea81d4cd7c10ddcaa685135b9aabd291af3d55775a9ddf3cb7a364cdd9", size = 255873, upload-time = "2026-05-26T20:39:19.414Z" }, + { url = "https://files.pythonhosted.org/packages/3c/21/7c721a9e5e6bb88547d30a787aefb97512d3f54c1324c7488d9b3743f7f9/coverage-7.14.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2222be86d0b54f5dd5a38f45f17f315f737245e857bf0bdedc70734f84a13c02", size = 251372, upload-time = "2026-05-26T20:39:21.169Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f8ae5a2200130e1503cd7661a6cd3b2b7bacef98277fbf3571fb13f8b766/coverage-7.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:85e85586565842f6932abebd4c18bcb1074223dc0b3576e7d173ca710622813a", size = 253245, upload-time = "2026-05-26T20:39:23.097Z" }, + { url = "https://files.pythonhosted.org/packages/34/62/70a9024672a5f6910517d9628c52c9afbdd3cf8f46426af52bb148a56fff/coverage-7.14.1-cp312-cp312-win32.whl", hash = "sha256:4a28fd227808366b196a75476dced2eb35b351d6766ba9c858dc93319e87f4f1", size = 222567, upload-time = "2026-05-26T20:39:24.868Z" }, + { url = "https://files.pythonhosted.org/packages/f6/81/8b7cd386839b039ebe1855733b9f9449a8dec5d79564018234f185a7fa70/coverage-7.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:54acdb6674a4661768d7bf7db32dfb9f46ab1d764f8aba6df75ce1a6a088724e", size = 223372, upload-time = "2026-05-26T20:39:26.603Z" }, + { url = "https://files.pythonhosted.org/packages/ae/ba/b44d472022f620d289d95fa830143235c0c36461c6f2437ea8d51e5481ed/coverage-7.14.1-cp312-cp312-win_arm64.whl", hash = "sha256:99cd41ff91afd94896fea3bc002706b6ae4ce95727d06e4a0f39c0a8d8bd8b1a", size = 221989, upload-time = "2026-05-26T20:39:28.242Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9e/5f6d56327c62b185225d145191c607e07515294a0aa6338e58805cd4a5ac/coverage-7.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:be9f2c802dcfce3f71298303aa5dad0dce440a76c52f2f60dacd8656dab78793", size = 220044, upload-time = "2026-05-26T20:39:29.902Z" }, + { url = "https://files.pythonhosted.org/packages/75/92/e82aca356744cbbc0f77a0b623e38918c1872361963413a3bab5d0340393/coverage-7.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6223a72fd0e4c7156353ec0f08a5f93623e1d3034d0e2683b9bb8ea674131b1d", size = 220412, upload-time = "2026-05-26T20:39:31.561Z" }, + { url = "https://files.pythonhosted.org/packages/27/c9/385bde0bf7ed0f4bf3a7ee5367060a86b5d218718cfd6fb943c0f836b34f/coverage-7.14.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7279d2110a28cebc738b6459ecda2771735a4c18465fbbd36b3288fe5ed92247", size = 251412, upload-time = "2026-05-26T20:39:33.337Z" }, + { url = "https://files.pythonhosted.org/packages/51/8c/23faf6a2343a0d17f960a4bd56c43bc7eb4cf312f774dd6ceebd82c7d8fc/coverage-7.14.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9eeb3fcbc13ba40dfbdb22d01d196a28e9cef9ed4c29b60061a1e0e823a9929d", size = 254008, upload-time = "2026-05-26T20:39:35.009Z" }, + { url = "https://files.pythonhosted.org/packages/42/06/36f4aa9ca8a815e6036156e80706a67828bb97bd826948244f6996dda957/coverage-7.14.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f0cfc27c539f07cf5c0a4cfe211d0b6cae039f8f40526dbaa71944e64b50a7b", size = 255241, upload-time = "2026-05-26T20:39:36.71Z" }, + { url = "https://files.pythonhosted.org/packages/ca/79/95266316352f90f6b1c6736bb413302edfde2453fb32422d3911642691b3/coverage-7.14.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:221c70f316241a78e77e607c227cefc8808d4e08f28d99c04f35694690e940be", size = 257373, upload-time = "2026-05-26T20:39:38.412Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9c/58316d1f66c488b5fca8a0eb3e98348807813efa8a0d0833b9021be27488/coverage-7.14.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:da028256b04ec30e5e0114b6f76172938c313991f0a2d3d894271315cf5d5e43", size = 251635, upload-time = "2026-05-26T20:39:40.268Z" }, + { url = "https://files.pythonhosted.org/packages/ef/5a/ca2398a568e16fed7bb713e84ba3603a7164fb65779abe645c565ec890d5/coverage-7.14.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76a085d7005236a767e3426148b2c407e53ad61695c562f8a81da2d373324901", size = 253373, upload-time = "2026-05-26T20:39:42.145Z" }, + { url = "https://files.pythonhosted.org/packages/6e/2c/0396562c32deaebe7be51d865b3a41e9a87d7561acafe1a28f53b07e019a/coverage-7.14.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b553d04b5e778a8e56d57eb134aff42a92718ecba45e79c4764ecfa40efd92ff", size = 251341, upload-time = "2026-05-26T20:39:43.907Z" }, + { url = "https://files.pythonhosted.org/packages/fd/8f/a94f9221184c9cae1ee115820e3798e48b6b17777a9f19e46fb9a0c8dc74/coverage-7.14.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:46f714d2fb8ae2f4f29f23ada7f1e79b759fff5a70f94a1dac23af204c3ec9e4", size = 255497, upload-time = "2026-05-26T20:39:46.166Z" }, + { url = "https://files.pythonhosted.org/packages/71/69/505d70e47db1eaebcd002c39759707621ef184cd6b1ae084d9f41293f323/coverage-7.14.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:1896f5e19ff3f0431c7ce2172adc54890fd97f86b59ced8ca1649145d9ffe35d", size = 251159, upload-time = "2026-05-26T20:39:48.03Z" }, + { url = "https://files.pythonhosted.org/packages/e0/aa/58681c383aa33a9d2ed40a02d7a22fbf780d1fa4d575396365777828198c/coverage-7.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:62fd185ef9df3c33d1c8178c5af105f762afbad96038de9a4ae100aa6297ca33", size = 252934, upload-time = "2026-05-26T20:39:49.872Z" }, + { url = "https://files.pythonhosted.org/packages/eb/fd/11c928cd6bdffc7074bb5965c173d9ebf517fb00205e1da524b98d29ef92/coverage-7.14.1-cp313-cp313-win32.whl", hash = "sha256:ab4af6352741a604c431c6072fce5bee33bf0f20dc7a56618d6bf6bb89e9810c", size = 222584, upload-time = "2026-05-26T20:39:51.68Z" }, + { url = "https://files.pythonhosted.org/packages/6f/92/fb416fc26d340dcba19518c418d6048e913186e17243982c5e435e41fa7a/coverage-7.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:7af486dabe8954d03b087f0021540897afe084f04e16ff5579e08cc46f871416", size = 223394, upload-time = "2026-05-26T20:39:53.472Z" }, + { url = "https://files.pythonhosted.org/packages/73/c6/02d56e3867972f77d5036de924643f26c056e848f00452cafb4dbc3c29b4/coverage-7.14.1-cp313-cp313-win_arm64.whl", hash = "sha256:2224f89ffd0c5605ccce1ed7a584da162bc7c55f601ab1c946bc9de31a486b42", size = 222015, upload-time = "2026-05-26T20:39:55.374Z" }, + { url = "https://files.pythonhosted.org/packages/4d/9e/fcc77914050df73f7662fa1f00902774c79c075a8388ab334074574bf77e/coverage-7.14.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de286598cc65d2b489411174b1faec2f5a7775fb3201fd925db2a76b4030f37d", size = 220733, upload-time = "2026-05-26T20:39:57.189Z" }, + { url = "https://files.pythonhosted.org/packages/f7/67/2963cbdaf5cbadec44efa3a1e39eaa1f02df4079585f05387607a221e126/coverage-7.14.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:042c46ded7c288aeb07cf14a28b6c1e10b78fcba40171c3fa1e939377eeef0b5", size = 221086, upload-time = "2026-05-26T20:39:59.019Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c5/8701645574e11881f2f47d8930f98bc48b5d43b25eb5b4430dfc4a2f9f48/coverage-7.14.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f4ddbe407477f04c45115d1a4e5bc480f753553b534d338d4c3358b1cdd0ea52", size = 262381, upload-time = "2026-05-26T20:40:00.822Z" }, + { url = "https://files.pythonhosted.org/packages/7c/28/7a64d73598263e0c5abd5084211a8474488d31b3c552ff531c719dfcff62/coverage-7.14.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d13e6725992e2d2fd7d81d4f5241952d13740121dfd501da09201be39b2c003a", size = 264458, upload-time = "2026-05-26T20:40:02.506Z" }, + { url = "https://files.pythonhosted.org/packages/fa/d8/4969179db9f7eb4df218e69540adf829d1c835f59452513d065d15446802/coverage-7.14.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f747dc8edcfe740130f28f32f3995e955494285717e86ee25af51db2219df08a", size = 266884, upload-time = "2026-05-26T20:40:04.421Z" }, + { url = "https://files.pythonhosted.org/packages/a6/78/a45d5794dbc9bafd97afc96a4377c86c7820d78b6cf51b89bc1d4e919275/coverage-7.14.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ced2f09ef276fd58611a1ef502164ad266d2b75174e5a40cabbdb4033f9f6cf2", size = 268022, upload-time = "2026-05-26T20:40:06.298Z" }, + { url = "https://files.pythonhosted.org/packages/21/cb/4f5e354e9e3e67af96bd4e57113e6db6b22298c7168b13eec408a549903d/coverage-7.14.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b84800013769a78ccb9ef4659402e26d06867e337b61ec365f77ad008adea80e", size = 261631, upload-time = "2026-05-26T20:40:08.226Z" }, + { url = "https://files.pythonhosted.org/packages/ec/49/eced49af4cb996d5d8b7e94e736175c513e4facd3398507b89892b4326d8/coverage-7.14.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ea8cd6ca0ee9f616aaef3afc6882e32c2cbf18b00d96313ffd76af650574034d", size = 264443, upload-time = "2026-05-26T20:40:10.137Z" }, + { url = "https://files.pythonhosted.org/packages/f1/d8/5603a88a7c5913a6b54f6cb1a8c46f7b39cbb30f27cd3f492908da09b2d7/coverage-7.14.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:aa5e304a873fabddc11e484e9b6b738bd38bd7bed17b09aa84eecf5332e8b8bb", size = 262069, upload-time = "2026-05-26T20:40:11.999Z" }, + { url = "https://files.pythonhosted.org/packages/f0/59/2ae3cb79da554a06c8619d6c88ea19dd1e4aed4b834b6a83bb1fa243bdc5/coverage-7.14.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5a1c5215be81035e629d5bc756650634d0bf31991038db7a0eccb90f025ce16d", size = 265780, upload-time = "2026-05-26T20:40:13.858Z" }, + { url = "https://files.pythonhosted.org/packages/af/5f/b130c1dc999031f2648bd25317fbce505ad8d5562079b4ed81e736a84967/coverage-7.14.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:79058c47dae6788504b5effb319961bcd72d7240551464b91d474bc0ed186d69", size = 260970, upload-time = "2026-05-26T20:40:16.142Z" }, + { url = "https://files.pythonhosted.org/packages/87/d1/ec13ccddeb48ec963bdfa72a11224bac2584bd045ba13beca82f8113e9c7/coverage-7.14.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:370c5afae3fa0658e11694a32b24c2778f6bc2d17718121f94ee185e69f26b54", size = 263157, upload-time = "2026-05-26T20:40:18.382Z" }, + { url = "https://files.pythonhosted.org/packages/cf/c2/cd91ead503045161092d3845f7bb95ea2f25131ce96d3e314dd835d91b9c/coverage-7.14.1-cp313-cp313t-win32.whl", hash = "sha256:3758dd0a7f1fa57365ef2e781df0f0731d38b6e3772259d13dae4bd8a958d4b1", size = 223259, upload-time = "2026-05-26T20:40:20.381Z" }, + { url = "https://files.pythonhosted.org/packages/71/9f/1e28d97e6bd2c76b07f38b7c02870f1371255ff6717f54eca578fcbbdd0e/coverage-7.14.1-cp313-cp313t-win_amd64.whl", hash = "sha256:6ff665fb023a77386fe11685190cee1f60a7d635994a30d9b0a061533d470fce", size = 224320, upload-time = "2026-05-26T20:40:22.316Z" }, + { url = "https://files.pythonhosted.org/packages/a9/e0/d936e908f0e1efa55e52b91e01b52f1055cef5e1ab2718493390ed8e2fb8/coverage-7.14.1-cp313-cp313t-win_arm64.whl", hash = "sha256:17a5a241e5997621a956a7f402a7433ef4221e5152809b785bec79e2323799f1", size = 222577, upload-time = "2026-05-26T20:40:24.894Z" }, + { url = "https://files.pythonhosted.org/packages/d6/34/fc2f101b151af3799a101f0550b0454aa008afdc0add677394ec4aa8ea10/coverage-7.14.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d5ed429d0b8edaac649e889b4ffcedb6c80b06629a3f93050e3dddfb99235bee", size = 220091, upload-time = "2026-05-26T20:40:27.249Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a7/1ebae2ab5b961b5c79bb09fe7b3ac99edb190d8be4a8c510b2cf66f46468/coverage-7.14.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8011224a62280e50dab346960c03cf47aca1a1e09e608c0fb33fd6e0cc8e9500", size = 220421, upload-time = "2026-05-26T20:40:30.084Z" }, + { url = "https://files.pythonhosted.org/packages/5e/90/92aca9cf0acc95123c96cd1eb1f08917897a7f5dee01e15738922971ec31/coverage-7.14.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:12c42ec1e14f553c4f817e989365982e646e27211f10a0f717855b94a79c8906", size = 251466, upload-time = "2026-05-26T20:40:32.542Z" }, + { url = "https://files.pythonhosted.org/packages/26/2b/78048cbe3b999f6cbf9cc0d90abba6a88a3e0863a8c1c6cbc762f3f8802f/coverage-7.14.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:06144cd511cf2624873a035c5069cf297144f6e77a73ee3d7a55b605ec5efb42", size = 253973, upload-time = "2026-05-26T20:40:34.473Z" }, + { url = "https://files.pythonhosted.org/packages/8e/21/c2e33b29d1cfde484a19d437afc343c6cd30b08d78cbbf9f5aff14e57b2b/coverage-7.14.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a311d8e1da24be5c1ccf85cbfb06315dbaa1703d5a1eab3f6432c72b837917c8", size = 255318, upload-time = "2026-05-26T20:40:38.154Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ee/aad2f108d63b769121005302f16bf66db8625c88ceaba466942e09a2607e/coverage-7.14.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c79cead5b5bc584d9c71451cb984d0e3a84e0c0937379c8efcbf27c8d661b851", size = 257633, upload-time = "2026-05-26T20:40:40.164Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f8/11a2c29b4fd76d9849f81d0bb812ec0017a9396df3217214e38934a8c837/coverage-7.14.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dcbf65f1f66a26cdd88c35cf68fb4729c5d1cd2e88added72420541dfb212034", size = 251488, upload-time = "2026-05-26T20:40:42.631Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b8/9a5820de4b8ac2b71d85e3b5fb49108d7469c665f0e2ad0dd7569023e305/coverage-7.14.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fd86572566fb40189a8260446158235159bc7a82dfbc87a3b39cf4fb57fcec1c", size = 253329, upload-time = "2026-05-26T20:40:45.208Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ff/f33e4823667e27548e8fd8df44217515303f9808d0ff29817db56f87d990/coverage-7.14.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:7771b601718fdde84832c3a434ca9bbf4ae9adbc49d84198b4110700c3c77c36", size = 251291, upload-time = "2026-05-26T20:40:47.502Z" }, + { url = "https://files.pythonhosted.org/packages/68/9b/489db0ebb209054766b90a9014a45f6d26eb724c02ec21311c3733b5a644/coverage-7.14.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:39b21e212c55af06fa375e3dbf90a8a8e38792f3a910c580066d23563830ddd5", size = 255564, upload-time = "2026-05-26T20:40:49.372Z" }, + { url = "https://files.pythonhosted.org/packages/27/b5/16bc2d4c2409b23c7737edb68c83bc89e345f378050549fe1d75ac7d34d5/coverage-7.14.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f2302660e32562a532b442480121aef8aa61a5bdb20b30bf0adab29f10a5a4b4", size = 251107, upload-time = "2026-05-26T20:40:51.677Z" }, + { url = "https://files.pythonhosted.org/packages/7d/0c/2629997469a00cd069d588a41c9dc887610f2775ae89d250c4791e65272a/coverage-7.14.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:03a6f93c1ec3b7f2e77b5dbcc5573a2c21f12529a5c6bbe0f16f72303cc2fa4d", size = 252764, upload-time = "2026-05-26T20:40:54.267Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ee/f78d63c8f079e0d7211c7e2401fa17e311514534ba61bae03e4b287ce4ab/coverage-7.14.1-cp314-cp314-win32.whl", hash = "sha256:8a3ce026d73290f42f08dafecbd82c193a74df280461fbf97300fec51fd133ee", size = 222837, upload-time = "2026-05-26T20:40:56.496Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b9/be539854f93a70dfbeec69117f33ec70dc42ff0b65b5b07ab8d40d04228e/coverage-7.14.1-cp314-cp314-win_amd64.whl", hash = "sha256:114c95ef29302423b87d159075805f4ab973254a2638a5d7d046c94887cc87d7", size = 223650, upload-time = "2026-05-26T20:40:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/fe/9e/24e2842fef40f35ac82ba3a7719c8023d011bf3bf652d0675316a9d088a1/coverage-7.14.1-cp314-cp314-win_arm64.whl", hash = "sha256:a07891c3f4805442b31b71e84ba3cf29ed1aa9a428284e06deeb4b23e5b46343", size = 222218, upload-time = "2026-05-26T20:41:00.321Z" }, + { url = "https://files.pythonhosted.org/packages/0a/1d/ac0a9df5fe31c1e8bdd658074905fc12844a05c1a7e3fdb8417e97c31e23/coverage-7.14.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1101a5ebb083aecb625ebb6209d4105b58f647b093cb2dc8122d7b33f743cfe1", size = 220822, upload-time = "2026-05-26T20:41:02.281Z" }, + { url = "https://files.pythonhosted.org/packages/32/cf/f964fd9aff20323f9f1a726c97135f8a76bcd87b92dad141a456a43f3c64/coverage-7.14.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:851b9e1e4e8a4608e77c79714b2e77c0970d2ed7202a05e92ae407817481887b", size = 221084, upload-time = "2026-05-26T20:41:04.593Z" }, + { url = "https://files.pythonhosted.org/packages/d8/5e/7e5ef2aba844de2b80d678619fcf0841b42e3f37f16411226f3fe4c1016f/coverage-7.14.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d5b89cdfb2ee051b71e8c3c70bd81a9eff81100f736a269136fe1a68efe00474", size = 262454, upload-time = "2026-05-26T20:41:06.641Z" }, + { url = "https://files.pythonhosted.org/packages/64/62/75809bded87015cc4935524218a2a8ed8dd1a8498bfed30a2f4f7a4b4d34/coverage-7.14.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0177614a0370f227888b4e436a7c55686d6a9f90eb1ade2b624ba685a1686e86", size = 264578, upload-time = "2026-05-26T20:41:08.556Z" }, + { url = "https://files.pythonhosted.org/packages/f3/42/d33392dc14633525012d2d504fa1a33b05538bf535f5c1d64675e5754b78/coverage-7.14.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d69af5dea2de76fc485a83032a630523f985198b7e25be901ec60181587b01e", size = 266981, upload-time = "2026-05-26T20:41:10.824Z" }, + { url = "https://files.pythonhosted.org/packages/2a/49/0157c4428c2aca7f1e09d5565930586fd5ae36f1655f08b0daa7cf1fcae1/coverage-7.14.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:35ab22d91de736e8966b980dc355cbcdd2c6dbbcfe275f9a2991bc8a91b3df65", size = 268112, upload-time = "2026-05-26T20:41:12.966Z" }, + { url = "https://files.pythonhosted.org/packages/96/26/86b9ce71f4092b1ed325ce1421698081df1286b833400b6836912834d6e0/coverage-7.14.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:357d4e32935c36588aaba057d734fa32428c360c9fc2e4442afbf1b646beee6e", size = 261558, upload-time = "2026-05-26T20:41:15Z" }, + { url = "https://files.pythonhosted.org/packages/20/4c/c311210c5472cf5401d8422b0d7812cdd520f24417673afabda6c323faca/coverage-7.14.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:51bd64741cc6fa065abd300ede1afe5a5291ece9c31da8b24884deda48bcc3f8", size = 264447, upload-time = "2026-05-26T20:41:17.369Z" }, + { url = "https://files.pythonhosted.org/packages/fb/71/59513f8710ed3e6b0ac0a050a5b7e977bb9c9e880354863b5d00d8809256/coverage-7.14.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:9132cd363a68a4c3daa7c8704a654b1e39d3360f6f5b8ddd470608a945236c07", size = 262048, upload-time = "2026-05-26T20:41:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/84/8d/bceed32dc494f5bbf50f775cd2e78ca814953942b5ea28d3c1c3ac316f14/coverage-7.14.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:07c6290b1697b862c0478eab545eec949a0d0e4d6d03497f446d706da3b4f2de", size = 265781, upload-time = "2026-05-26T20:41:21.559Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c5/9348fe40dbfd4991aaf78df2c6c3098bfb2cc834d1fd362a64b4efef855a/coverage-7.14.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5ea0c297e27133853b4d8a3eb799bff5a2dbd9f2f41537a240d337ac9b4df890", size = 260896, upload-time = "2026-05-26T20:41:23.428Z" }, + { url = "https://files.pythonhosted.org/packages/ca/92/1ea0f03929da7cf87206b1fa24f4c8e9c158be0455481af29ec0a1f3503f/coverage-7.14.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:01b7733daad0237daa01ef80fe2dfceffc911e6a17fa7b55d14aa8214eaaaecd", size = 263214, upload-time = "2026-05-26T20:41:25.419Z" }, + { url = "https://files.pythonhosted.org/packages/f6/a9/b2493c054c0e01a643266742ab45e15744e60743f9260cd930c7142b1124/coverage-7.14.1-cp314-cp314t-win32.whl", hash = "sha256:6adc5a36984624a70bf11d7184e20fa0a49aa7c47ffab43804106a1a695ea22e", size = 223624, upload-time = "2026-05-26T20:41:27.795Z" }, + { url = "https://files.pythonhosted.org/packages/fc/bd/3e1e6a57fccd2d7c83fcdf338e93ba98eb85c6e877dd34731ac585375490/coverage-7.14.1-cp314-cp314t-win_amd64.whl", hash = "sha256:ddf799247318f34dbcd2efa8c95a8d0642674e926bb1774cf9b63dfd2a389d1c", size = 224728, upload-time = "2026-05-26T20:41:30.098Z" }, + { url = "https://files.pythonhosted.org/packages/bb/d7/31066cf1d2f0c6c797fce911bcfa01dd35642dc6da992a950256097c5860/coverage-7.14.1-cp314-cp314t-win_arm64.whl", hash = "sha256:145986fe66647eb489f18d9a997567a3fd358584c4b5a808769113abc07466af", size = 222752, upload-time = "2026-05-26T20:41:32.123Z" }, + { url = "https://files.pythonhosted.org/packages/8a/3c/1a983b9a745d7f83d53f057bcc5bf79ba6a2bbc08266b3f0c7d6fe630c9b/coverage-7.14.1-py3-none-any.whl", hash = "sha256:a252f21c27e38347e60111a3266b03827422a7d5525951aceee313aa68bab1d2", size = 211815, upload-time = "2026-05-26T20:41:34.078Z" }, ] [package.optional-dependencies] @@ -636,30 +636,30 @@ toml = [ [[package]] name = "cuda-bindings" -version = "13.2.0" +version = "13.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cuda-pathfinder" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/a9/3a8241c6e19483ac1f1dcf5c10238205dcb8a6e9d0d4d4709240dff28ff4/cuda_bindings-13.2.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:721104c603f059780d287969be3d194a18d0cc3b713ed9049065a1107706759d", size = 5730273, upload-time = "2026-03-11T00:12:37.18Z" }, - { url = "https://files.pythonhosted.org/packages/e9/94/2748597f47bb1600cd466b20cab4159f1530a3a33fe7f70fee199b3abb9e/cuda_bindings-13.2.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1eba9504ac70667dd48313395fe05157518fd6371b532790e96fbb31bbb5a5e1", size = 6313924, upload-time = "2026-03-11T00:12:39.462Z" }, - { url = "https://files.pythonhosted.org/packages/52/c8/b2589d68acf7e3d63e2be330b84bc25712e97ed799affbca7edd7eae25d6/cuda_bindings-13.2.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e865447abfb83d6a98ad5130ed3c70b1fc295ae3eeee39fd07b4ddb0671b6788", size = 5722404, upload-time = "2026-03-11T00:12:44.041Z" }, - { url = "https://files.pythonhosted.org/packages/1f/92/f899f7bbb5617bb65ec52a6eac1e9a1447a86b916c4194f8a5001b8cde0c/cuda_bindings-13.2.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46d8776a55d6d5da9dd6e9858fba2efcda2abe6743871dee47dd06eb8cb6d955", size = 6320619, upload-time = "2026-03-11T00:12:45.939Z" }, - { url = "https://files.pythonhosted.org/packages/df/93/eef988860a3ca985f82c4f3174fc0cdd94e07331ba9a92e8e064c260337f/cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6629ca2df6f795b784752409bcaedbd22a7a651b74b56a165ebc0c9dcbd504d0", size = 5614610, upload-time = "2026-03-11T00:12:50.337Z" }, - { url = "https://files.pythonhosted.org/packages/18/23/6db3aba46864aee357ab2415135b3fe3da7e9f1fa0221fa2a86a5968099c/cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7dca0da053d3b4cc4869eff49c61c03f3c5dbaa0bcd712317a358d5b8f3f385d", size = 6149914, upload-time = "2026-03-11T00:12:52.374Z" }, - { url = "https://files.pythonhosted.org/packages/c0/87/87a014f045b77c6de5c8527b0757fe644417b184e5367db977236a141602/cuda_bindings-13.2.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6464b30f46692d6c7f65d4a0e0450d81dd29de3afc1bb515653973d01c2cd6e", size = 5685673, upload-time = "2026-03-11T00:12:56.371Z" }, - { url = "https://files.pythonhosted.org/packages/ee/5e/c0fe77a73aaefd3fff25ffaccaac69c5a63eafdf8b9a4c476626ef0ac703/cuda_bindings-13.2.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4af9f3e1be603fa12d5ad6cfca7844c9d230befa9792b5abdf7dd79979c3626", size = 6191386, upload-time = "2026-03-11T00:12:58.965Z" }, - { url = "https://files.pythonhosted.org/packages/5f/58/ed2c3b39c8dd5f96aa7a4abef0d47a73932c7a988e30f5fa428f00ed0da1/cuda_bindings-13.2.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df850a1ff8ce1b3385257b08e47b70e959932f5f432d0a4e46a355962b4e4771", size = 5507469, upload-time = "2026-03-11T00:13:04.063Z" }, - { url = "https://files.pythonhosted.org/packages/1f/01/0c941b112ceeb21439b05895eace78ca1aa2eaaf695c8521a068fd9b4c00/cuda_bindings-13.2.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8a16384c6494e5485f39314b0b4afb04bee48d49edb16d5d8593fd35bbd231b", size = 6059693, upload-time = "2026-03-11T00:13:06.003Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/457ca12dad3ee9bfcc9a545cfd6b64b359ba49de40f776f6e028e678f262/cuda_bindings-13.3.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5879712accf6e14bb01aa5e67440eb84998b8d104b509cc7a6dc0b8f656a474", size = 6053539, upload-time = "2026-05-29T23:11:43.19Z" }, + { url = "https://files.pythonhosted.org/packages/95/7a/c5e3c34a409b148f5c0f5a4ea374158f95d488862c1dffedf9aa5c639df9/cuda_bindings-13.3.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04436a9364059c84b8f9636f359eccda1cf814341f5b670c71d80d2f79dbc708", size = 6674166, upload-time = "2026-05-29T23:11:45.478Z" }, + { url = "https://files.pythonhosted.org/packages/ce/67/5e7dba1ba576dd73da5dee894ca076ca5e959450dfff66d6d510a255d1f7/cuda_bindings-13.3.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7855c4868aabc0cfae28abbe83d56734bdfbd08f08fc234ac1912a12858bf49", size = 6025351, upload-time = "2026-05-29T23:11:49.685Z" }, + { url = "https://files.pythonhosted.org/packages/39/2a/6d2e9047d1fb243dbaa364b01e0297534b9ed7fd27dba1c9f361519cf69b/cuda_bindings-13.3.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e32d08f71ebcdf00f0f41eab2eb37e8da94c8ed411cc9f7f7a019ce6b34abe3a", size = 6657965, upload-time = "2026-05-29T23:11:52.227Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6e/2394f8163360f8391f8f1b7e72d300a82724edb81a7b7084c799fbd4c91f/cuda_bindings-13.3.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9efb21c1ee64981e184b9e0ba5eb3179e5ba3d4b51665a6cb52b8ef3d01a7cbf", size = 5920504, upload-time = "2026-05-29T23:11:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/34/c2/ef9b6a63f7dc432712a462c816662e662e00d38caa9b861c8c2588195d03/cuda_bindings-13.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2732904099e0a4d4db774a5fc6d91ee95fae065b4d2ecabb4968c5fe2406c9d7", size = 6476660, upload-time = "2026-05-29T23:11:59.188Z" }, + { url = "https://files.pythonhosted.org/packages/b1/81/bff68ce829999c1e4209c761bbf903b1c06ec570416ddb25020864ad5907/cuda_bindings-13.3.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ab2f74ed65bfef4163ba07a8db16f1085e0729291db12a2423aff84ee8278b8", size = 6013639, upload-time = "2026-05-29T23:12:03.509Z" }, + { url = "https://files.pythonhosted.org/packages/d4/e0/c8a1f0c8f9ffdea4f5fe6dbab89b326cef4d85caf489dad39e209da89416/cuda_bindings-13.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd4c814d311ec08c981f6dded1dbe7d4b371067ee4f6c14cccec4bde9590f80", size = 6534419, upload-time = "2026-05-29T23:12:05.633Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/83b1f563925b290f2d11a01a77a84013ba56052fe3653a5bef3ccfbb43d6/cuda_bindings-13.3.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3c772dfff49681541d59630c90f858e173ac926b9c593a2b7123f2a1043cc76", size = 5809771, upload-time = "2026-05-29T23:12:10.422Z" }, + { url = "https://files.pythonhosted.org/packages/12/20/e79b4bfe98f075195afb6343d41c498f9dbd2d161d7021d4d28bceb83581/cuda_bindings-13.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36febb7c1079d68a981dbbd8d5a67235b399802b82075c9388624719607e52b9", size = 6358584, upload-time = "2026-05-29T23:12:12.767Z" }, ] [[package]] name = "cuda-pathfinder" -version = "1.5.4" +version = "1.5.5" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/11/d0/c177e29701cf1d3008d7d2b16b5fc626592ce13bd535f8795c5f57187e0e/cuda_pathfinder-1.5.4-py3-none-any.whl", hash = "sha256:9563d3175ce1828531acf4b94e1c1c7d67208c347ca002493e2654878b26f4b7", size = 51657, upload-time = "2026-04-27T22:42:07.712Z" }, + { url = "https://files.pythonhosted.org/packages/11/c8/26f2e4aae92f11522a96043892ba39a90eac610d5242523aa863212bc1c7/cuda_pathfinder-1.5.5-py3-none-any.whl", hash = "sha256:0228c023f95d1480f143ef5c8922d27a2ab052087a942e81dc289c9eb8f91689", size = 51671, upload-time = "2026-05-27T01:21:25.413Z" }, ] [[package]] @@ -1021,53 +1021,53 @@ wheels = [ [[package]] name = "grpcio" -version = "1.80.0" +version = "1.81.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b7/48/af6173dbca4454f4637a4678b67f52ca7e0c1ed7d5894d89d434fecede05/grpcio-1.80.0.tar.gz", hash = "sha256:29aca15edd0688c22ba01d7cc01cb000d72b2033f4a3c72a81a19b56fd143257", size = 12978905, upload-time = "2026-03-30T08:49:10.502Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/db/1d56e5f5823257b291962d6c0ce106146c6447f405b60b234c4f222a7cde/grpcio-1.80.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:dfab85db094068ff42e2a3563f60ab3dddcc9d6488a35abf0132daec13209c8a", size = 6055009, upload-time = "2026-03-30T08:46:46.265Z" }, - { url = "https://files.pythonhosted.org/packages/6e/18/c83f3cad64c5ca63bca7e91e5e46b0d026afc5af9d0a9972472ceba294b3/grpcio-1.80.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:5c07e82e822e1161354e32da2662f741a4944ea955f9f580ec8fb409dd6f6060", size = 12035295, upload-time = "2026-03-30T08:46:49.099Z" }, - { url = "https://files.pythonhosted.org/packages/0f/8e/e14966b435be2dda99fbe89db9525ea436edc79780431a1c2875a3582644/grpcio-1.80.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba0915d51fd4ced2db5ff719f84e270afe0e2d4c45a7bdb1e8d036e4502928c2", size = 6610297, upload-time = "2026-03-30T08:46:52.123Z" }, - { url = "https://files.pythonhosted.org/packages/cc/26/d5eb38f42ce0e3fdc8174ea4d52036ef8d58cc4426cb800f2610f625dd75/grpcio-1.80.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3cb8130ba457d2aa09fa6b7c3ed6b6e4e6a2685fce63cb803d479576c4d80e21", size = 7300208, upload-time = "2026-03-30T08:46:54.859Z" }, - { url = "https://files.pythonhosted.org/packages/25/51/bd267c989f85a17a5b3eea65a6feb4ff672af41ca614e5a0279cc0ea381c/grpcio-1.80.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:09e5e478b3d14afd23f12e49e8b44c8684ac3c5f08561c43a5b9691c54d136ab", size = 6813442, upload-time = "2026-03-30T08:46:57.056Z" }, - { url = "https://files.pythonhosted.org/packages/9e/d9/d80eef735b19e9169e30164bbf889b46f9df9127598a83d174eb13a48b26/grpcio-1.80.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:00168469238b022500e486c1c33916acf2f2a9b2c022202cf8a1885d2e3073c1", size = 7414743, upload-time = "2026-03-30T08:46:59.682Z" }, - { url = "https://files.pythonhosted.org/packages/de/f2/567f5bd5054398ed6b0509b9a30900376dcf2786bd936812098808b49d8d/grpcio-1.80.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8502122a3cc1714038e39a0b071acb1207ca7844208d5ea0d091317555ee7106", size = 8426046, upload-time = "2026-03-30T08:47:02.474Z" }, - { url = "https://files.pythonhosted.org/packages/62/29/73ef0141b4732ff5eacd68430ff2512a65c004696997f70476a83e548e7e/grpcio-1.80.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ce1794f4ea6cc3ca29463f42d665c32ba1b964b48958a66497917fe9069f26e6", size = 7851641, upload-time = "2026-03-30T08:47:05.462Z" }, - { url = "https://files.pythonhosted.org/packages/46/69/abbfa360eb229a8623bab5f5a4f8105e445bd38ce81a89514ba55d281ad0/grpcio-1.80.0-cp311-cp311-win32.whl", hash = "sha256:51b4a7189b0bef2aa30adce3c78f09c83526cf3dddb24c6a96555e3b97340440", size = 4154368, upload-time = "2026-03-30T08:47:08.027Z" }, - { url = "https://files.pythonhosted.org/packages/6f/d4/ae92206d01183b08613e846076115f5ac5991bae358d2a749fa864da5699/grpcio-1.80.0-cp311-cp311-win_amd64.whl", hash = "sha256:02e64bb0bb2da14d947a49e6f120a75e947250aebe65f9629b62bb1f5c14e6e9", size = 4894235, upload-time = "2026-03-30T08:47:10.839Z" }, - { url = "https://files.pythonhosted.org/packages/5c/e8/a2b749265eb3415abc94f2e619bbd9e9707bebdda787e61c593004ec927a/grpcio-1.80.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:c624cc9f1008361014378c9d776de7182b11fe8b2e5a81bc69f23a295f2a1ad0", size = 6015616, upload-time = "2026-03-30T08:47:13.428Z" }, - { url = "https://files.pythonhosted.org/packages/3e/97/b1282161a15d699d1e90c360df18d19165a045ce1c343c7f313f5e8a0b77/grpcio-1.80.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:f49eddcac43c3bf350c0385366a58f36bed8cc2c0ec35ef7b74b49e56552c0c2", size = 12014204, upload-time = "2026-03-30T08:47:15.873Z" }, - { url = "https://files.pythonhosted.org/packages/6e/5e/d319c6e997b50c155ac5a8cb12f5173d5b42677510e886d250d50264949d/grpcio-1.80.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d334591df610ab94714048e0d5b4f3dd5ad1bee74dfec11eee344220077a79de", size = 6563866, upload-time = "2026-03-30T08:47:18.588Z" }, - { url = "https://files.pythonhosted.org/packages/ae/f6/fdd975a2cb4d78eb67769a7b3b3830970bfa2e919f1decf724ae4445f42c/grpcio-1.80.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0cb517eb1d0d0aaf1d87af7cc5b801d686557c1d88b2619f5e31fab3c2315921", size = 7273060, upload-time = "2026-03-30T08:47:21.113Z" }, - { url = "https://files.pythonhosted.org/packages/db/f0/a3deb5feba60d9538a962913e37bd2e69a195f1c3376a3dd44fe0427e996/grpcio-1.80.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4e78c4ac0d97dc2e569b2f4bcbbb447491167cb358d1a389fc4af71ab6f70411", size = 6782121, upload-time = "2026-03-30T08:47:23.827Z" }, - { url = "https://files.pythonhosted.org/packages/ca/84/36c6dcfddc093e108141f757c407902a05085e0c328007cb090d56646cdf/grpcio-1.80.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2ed770b4c06984f3b47eb0517b1c69ad0b84ef3f40128f51448433be904634cd", size = 7383811, upload-time = "2026-03-30T08:47:26.517Z" }, - { url = "https://files.pythonhosted.org/packages/7c/ef/f3a77e3dc5b471a0ec86c564c98d6adfa3510d38f8ee99010410858d591e/grpcio-1.80.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:256507e2f524092f1473071a05e65a5b10d84b82e3ff24c5b571513cfaa61e2f", size = 8393860, upload-time = "2026-03-30T08:47:29.439Z" }, - { url = "https://files.pythonhosted.org/packages/9b/8d/9d4d27ed7f33d109c50d6b5ce578a9914aa68edab75d65869a17e630a8d1/grpcio-1.80.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a6284a5d907c37db53350645567c522be314bac859a64a7a5ca63b77bb7958f", size = 7830132, upload-time = "2026-03-30T08:47:33.254Z" }, - { url = "https://files.pythonhosted.org/packages/14/e4/9990b41c6d7a44e1e9dee8ac11d7a9802ba1378b40d77468a7761d1ad288/grpcio-1.80.0-cp312-cp312-win32.whl", hash = "sha256:c71309cfce2f22be26aa4a847357c502db6c621f1a49825ae98aa0907595b193", size = 4140904, upload-time = "2026-03-30T08:47:35.319Z" }, - { url = "https://files.pythonhosted.org/packages/2f/2c/296f6138caca1f4b92a31ace4ae1b87dab692fc16a7a3417af3bb3c805bf/grpcio-1.80.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe648599c0e37594c4809d81a9e77bd138cc82eb8baa71b6a86af65426723ff", size = 4880944, upload-time = "2026-03-30T08:47:37.831Z" }, - { url = "https://files.pythonhosted.org/packages/2f/3a/7c3c25789e3f069e581dc342e03613c5b1cb012c4e8c7d9d5cf960a75856/grpcio-1.80.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:e9e408fc016dffd20661f0126c53d8a31c2821b5c13c5d67a0f5ed5de93319ad", size = 6017243, upload-time = "2026-03-30T08:47:40.075Z" }, - { url = "https://files.pythonhosted.org/packages/04/19/21a9806eb8240e174fd1ab0cd5b9aa948bb0e05c2f2f55f9d5d7405e6d08/grpcio-1.80.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:92d787312e613754d4d8b9ca6d3297e69994a7912a32fa38c4c4e01c272974b0", size = 12010840, upload-time = "2026-03-30T08:47:43.11Z" }, - { url = "https://files.pythonhosted.org/packages/18/3a/23347d35f76f639e807fb7a36fad3068aed100996849a33809591f26eca6/grpcio-1.80.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8ac393b58aa16991a2f1144ec578084d544038c12242da3a215966b512904d0f", size = 6567644, upload-time = "2026-03-30T08:47:46.806Z" }, - { url = "https://files.pythonhosted.org/packages/ff/40/96e07ecb604a6a67ae6ab151e3e35b132875d98bc68ec65f3e5ab3e781d7/grpcio-1.80.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:68e5851ac4b9afe07e7f84483803ad167852570d65326b34d54ca560bfa53fb6", size = 7277830, upload-time = "2026-03-30T08:47:49.643Z" }, - { url = "https://files.pythonhosted.org/packages/9b/e2/da1506ecea1f34a5e365964644b35edef53803052b763ca214ba3870c856/grpcio-1.80.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:873ff5d17d68992ef6605330127425d2fc4e77e612fa3c3e0ed4e668685e3140", size = 6783216, upload-time = "2026-03-30T08:47:52.817Z" }, - { url = "https://files.pythonhosted.org/packages/44/83/3b20ff58d0c3b7f6caaa3af9a4174d4023701df40a3f39f7f1c8e7c48f9d/grpcio-1.80.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2bea16af2750fd0a899bf1abd9022244418b55d1f37da2202249ba4ba673838d", size = 7385866, upload-time = "2026-03-30T08:47:55.687Z" }, - { url = "https://files.pythonhosted.org/packages/47/45/55c507599c5520416de5eefecc927d6a0d7af55e91cfffb2e410607e5744/grpcio-1.80.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba0db34f7e1d803a878284cd70e4c63cb6ae2510ba51937bf8f45ba997cefcf7", size = 8391602, upload-time = "2026-03-30T08:47:58.303Z" }, - { url = "https://files.pythonhosted.org/packages/10/bb/dd06f4c24c01db9cf11341b547d0a016b2c90ed7dbbb086a5710df7dd1d7/grpcio-1.80.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8eb613f02d34721f1acf3626dfdb3545bd3c8505b0e52bf8b5710a28d02e8aa7", size = 7826752, upload-time = "2026-03-30T08:48:01.311Z" }, - { url = "https://files.pythonhosted.org/packages/f9/1e/9d67992ba23371fd63d4527096eb8c6b76d74d52b500df992a3343fd7251/grpcio-1.80.0-cp313-cp313-win32.whl", hash = "sha256:93b6f823810720912fd131f561f91f5fed0fda372b6b7028a2681b8194d5d294", size = 4142310, upload-time = "2026-03-30T08:48:04.594Z" }, - { url = "https://files.pythonhosted.org/packages/cf/e6/283326a27da9e2c3038bc93eeea36fb118ce0b2d03922a9cda6688f53c5b/grpcio-1.80.0-cp313-cp313-win_amd64.whl", hash = "sha256:e172cf795a3ba5246d3529e4d34c53db70e888fa582a8ffebd2e6e48bc0cba50", size = 4882833, upload-time = "2026-03-30T08:48:07.363Z" }, - { url = "https://files.pythonhosted.org/packages/c5/6d/e65307ce20f5a09244ba9e9d8476e99fb039de7154f37fb85f26978b59c3/grpcio-1.80.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:3d4147a97c8344d065d01bbf8b6acec2cf86fb0400d40696c8bdad34a64ffc0e", size = 6017376, upload-time = "2026-03-30T08:48:10.005Z" }, - { url = "https://files.pythonhosted.org/packages/69/10/9cef5d9650c72625a699c549940f0abb3c4bfdb5ed45a5ce431f92f31806/grpcio-1.80.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d8e11f167935b3eb089ac9038e1a063e6d7dbe995c0bb4a661e614583352e76f", size = 12018133, upload-time = "2026-03-30T08:48:12.927Z" }, - { url = "https://files.pythonhosted.org/packages/04/82/983aabaad82ba26113caceeb9091706a0696b25da004fe3defb5b346e15b/grpcio-1.80.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f14b618fc30de822681ee986cfdcc2d9327229dc4c98aed16896761cacd468b9", size = 6574748, upload-time = "2026-03-30T08:48:16.386Z" }, - { url = "https://files.pythonhosted.org/packages/07/d7/031666ef155aa0bf399ed7e19439656c38bbd143779ae0861b038ce82abd/grpcio-1.80.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4ed39fbdcf9b87370f6e8df4e39ca7b38b3e5e9d1b0013c7b6be9639d6578d14", size = 7277711, upload-time = "2026-03-30T08:48:19.627Z" }, - { url = "https://files.pythonhosted.org/packages/e8/43/f437a78f7f4f1d311804189e8f11fb311a01049b2e08557c1068d470cb2e/grpcio-1.80.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2dcc70e9f0ba987526e8e8603a610fb4f460e42899e74e7a518bf3c68fe1bf05", size = 6785372, upload-time = "2026-03-30T08:48:22.373Z" }, - { url = "https://files.pythonhosted.org/packages/93/3d/f6558e9c6296cb4227faa5c43c54a34c68d32654b829f53288313d16a86e/grpcio-1.80.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:448c884b668b868562b1bda833c5fce6272d26e1926ec46747cda05741d302c1", size = 7395268, upload-time = "2026-03-30T08:48:25.638Z" }, - { url = "https://files.pythonhosted.org/packages/06/21/0fdd77e84720b08843c371a2efa6f2e19dbebf56adc72df73d891f5506f0/grpcio-1.80.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a1dc80fe55685b4a543555e6eef975303b36c8db1023b1599b094b92aa77965f", size = 8392000, upload-time = "2026-03-30T08:48:28.974Z" }, - { url = "https://files.pythonhosted.org/packages/f5/68/67f4947ed55d2e69f2cc199ab9fd85e0a0034d813bbeef84df6d2ba4d4b7/grpcio-1.80.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:31b9ac4ad1aa28ffee5503821fafd09e4da0a261ce1c1281c6c8da0423c83b6e", size = 7828477, upload-time = "2026-03-30T08:48:32.054Z" }, - { url = "https://files.pythonhosted.org/packages/44/b6/8d4096691b2e385e8271911a0de4f35f0a6c7d05aff7098e296c3de86939/grpcio-1.80.0-cp314-cp314-win32.whl", hash = "sha256:367ce30ba67d05e0592470428f0ec1c31714cab9ef19b8f2e37be1f4c7d32fae", size = 4218563, upload-time = "2026-03-30T08:48:34.538Z" }, - { url = "https://files.pythonhosted.org/packages/e5/8c/bbe6baf2557262834f2070cf668515fa308b2d38a4bbf771f8f7872a7036/grpcio-1.80.0-cp314-cp314-win_amd64.whl", hash = "sha256:3b01e1f5464c583d2f567b2e46ff0d516ef979978f72091fd81f5ab7fa6e2e7f", size = 5019457, upload-time = "2026-03-30T08:48:37.308Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/15/f3/23f47b24f8d8c2028eba501db3acfbb2f592cbb5995eaa6e363a627b74d7/grpcio-1.81.0.tar.gz", hash = "sha256:a5acd7efd3b1fe9b4eb0bcaaa1507eed68a0ad0678b654c3f7b464df9ba9dca5", size = 13032272, upload-time = "2026-06-01T05:56:22.827Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/a8/9916ab10a0201f4c7afb6918125aa2f38a7626ee18ffbc066dd9cb04a74d/grpcio-1.81.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:794e6aa648e8df47d8f908dc8c3b42347d04ec58438f1dcd4e445f09b4f6b0ce", size = 6093557, upload-time = "2026-06-01T05:54:32.64Z" }, + { url = "https://files.pythonhosted.org/packages/a7/43/99e969a048904a65df3129ee53c5f523b7c4e43127786460cac4bee82470/grpcio-1.81.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:cd78145b7f7784661c524624f3526c9c6f891b30a4b54cb93a40806d0d0d61e9", size = 12075345, upload-time = "2026-06-01T05:54:35.77Z" }, + { url = "https://files.pythonhosted.org/packages/83/70/4c3a204e190333768d4f63f4ff56bd0bf405f05b9188f3a59a8bcf161f8b/grpcio-1.81.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:638ccc1b86f7540170a169cb900799b9296a1381e47879ce60b0de9d3db73d33", size = 6640664, upload-time = "2026-06-01T05:54:38.854Z" }, + { url = "https://files.pythonhosted.org/packages/2e/a9/0fa17ac8b4e29cf59b26915be6cab8c0d4583ce24a6208a287b6e5f6d072/grpcio-1.81.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:21ec30b9ea320c8207ea7cd05873ad64aa69fdd0e81b6758b3347983ba20b50a", size = 7332542, upload-time = "2026-06-01T05:54:41.39Z" }, + { url = "https://files.pythonhosted.org/packages/f4/18/7c8e3d0dda2fb7a17076fcd6c9085209eabad3354696c64230f87b3a14eb/grpcio-1.81.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dbdb99986548a7e87f8343805ef315fd4eb50ffaabf4fb1206e42f2542bb805d", size = 6842564, upload-time = "2026-06-01T05:54:43.57Z" }, + { url = "https://files.pythonhosted.org/packages/f6/19/2f1726c2e03ad3f3fe241e6b41534532ad580d595de14a4054ad84999c80/grpcio-1.81.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c36f5d5e97944cbda2d4096b4ae262e6e68506246b61582acf1b8591607f3ccc", size = 7446236, upload-time = "2026-06-01T05:54:46.042Z" }, + { url = "https://files.pythonhosted.org/packages/a7/dc/0321f892212e2c0bfe248cea24c00d7d7111639688ec5ffd8e36b5c02fe6/grpcio-1.81.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9f355384e5543ab77a755a7085225ecc19f32b76032e851cbd8145715d79dec8", size = 8445633, upload-time = "2026-06-01T05:54:48.809Z" }, + { url = "https://files.pythonhosted.org/packages/e5/20/0e7ea7494955cf1beea3077b2fd2c04c84d4480c2ae85a1e1cfa150c62d7/grpcio-1.81.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:77eb4e9fe61486bd1198cc7236ebb0f70e66234e63c0348f40bc2553ed16a88b", size = 7873958, upload-time = "2026-06-01T05:54:52.135Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9e/6438e226046c2a0778060e2b1d791a4827277bbd9d223013c2c63ee7435e/grpcio-1.81.0-cp311-cp311-win32.whl", hash = "sha256:7915a2e63acdc05264a206e1bddfd8e1fb8a29e406c18d72d30f8c124e021374", size = 4202110, upload-time = "2026-06-01T05:54:54.134Z" }, + { url = "https://files.pythonhosted.org/packages/42/6b/d0895e93d65b186f5f1737fcc186d7faa487e2d9d934eda111a37a309869/grpcio-1.81.0-cp311-cp311-win_amd64.whl", hash = "sha256:5e925a70fe99fe5794f7beca0ea034c75f068afcc356d79047e73f99cdcca34c", size = 4940942, upload-time = "2026-06-01T05:54:56.749Z" }, + { url = "https://files.pythonhosted.org/packages/82/d5/896a3aaf07068d707d88b282a04914b872db4d32d3c7e6d88e43a3b911fa/grpcio-1.81.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:57b3b0e73a518fa286959b40c3eddd02703504ca186e8b7b2945954519bd8b2c", size = 6053538, upload-time = "2026-06-01T05:54:58.965Z" }, + { url = "https://files.pythonhosted.org/packages/68/6a/7e3eafa4727cd405ff917605ed2949e2af162f233f5cbdd773723a5fea7d/grpcio-1.81.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:8bb1789c94322a13336a2b6c58d9c14d68f8628b6e24205a799c69f5bf8516ce", size = 12053447, upload-time = "2026-06-01T05:55:01.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/79/a4302aa82428de48a922421f522b027a1a727ab4d0926368454aa953d36d/grpcio-1.81.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e4d053900a0d24b75d7521139a3872150301b3d6bde3bed5e12318fb25791e4d", size = 6595872, upload-time = "2026-06-01T05:55:04.946Z" }, + { url = "https://files.pythonhosted.org/packages/b4/1f/7ff2850eaefbecf99af3f624dbb28dd1ad6c5fd4c1d8c26909ed6482673b/grpcio-1.81.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:db217c2e52931719f9937bd12082cd4d7b495b35803d5760686975c285924bf8", size = 7303857, upload-time = "2026-06-01T05:55:07.205Z" }, + { url = "https://files.pythonhosted.org/packages/e2/98/1f3896a9baae1f2aedf4e99c55291d6fa1f30ad9603d63bc18bda967b53e/grpcio-1.81.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:19f201da7b4e5c0559198abe5a97157e726f3abe6e8f5e832d4a50740f6dcc22", size = 6809676, upload-time = "2026-06-01T05:55:09.513Z" }, + { url = "https://files.pythonhosted.org/packages/34/8b/3441983718095208c5d797fd3239882e97ea89a629f41c8df94b4eef4df9/grpcio-1.81.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:275144b0115353339dbb8a6f28a9cf8997b5bf40e37f8f66ac0b0ea57e95b43f", size = 7412654, upload-time = "2026-06-01T05:55:12.777Z" }, + { url = "https://files.pythonhosted.org/packages/3c/98/1eddf07df6e4fe85cf67502a793f7b05468b2dca3d1ef35b972cf5d54468/grpcio-1.81.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5192857589f223e5a98ff0e31f6e551b19040e647d17bfe10116c8a2ce3b8696", size = 8408026, upload-time = "2026-06-01T05:55:15.514Z" }, + { url = "https://files.pythonhosted.org/packages/5c/73/3860341e6a1f5347be6ab35c6c0e1e3a8eb59d010388207fd561dcf01a88/grpcio-1.81.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c6ff087cb1f563f47b504b4e29e684129fc5ae4863faf3ebca08a327764ee6cb", size = 7849498, upload-time = "2026-06-01T05:55:18.078Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3f/0ea06bd85c701966aa3f8f37314f2ed83520d2b7590f42d643d445d8bc8b/grpcio-1.81.0-cp312-cp312-win32.whl", hash = "sha256:98c6240f563178fc5877bd50e6ff274463e53e1472128f4110742450739659fa", size = 4184161, upload-time = "2026-06-01T05:55:20.127Z" }, + { url = "https://files.pythonhosted.org/packages/39/e3/a7c387406827a86f99ad7838b995bf9b4a182ffe2d2c439ed2873efec952/grpcio-1.81.0-cp312-cp312-win_amd64.whl", hash = "sha256:87e33b7afcfb3585121b5f007d2c52b8c534104d18f556e840d35193ca2a9141", size = 4929958, upload-time = "2026-06-01T05:55:22.736Z" }, + { url = "https://files.pythonhosted.org/packages/f3/29/779ee53c931d0fd55c1d459fde43e485172caa3ac87cbd43d003a13a0185/grpcio-1.81.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:62bbe463c9f0f2ff24e31bd25f8dd8b4bae78900e315915a3195a0ef1471a855", size = 6054973, upload-time = "2026-06-01T05:55:25.043Z" }, + { url = "https://files.pythonhosted.org/packages/9e/b6/7211807926b5a17f8d9a5d47c739a163d6812fefe3e4714e81cf92945ed7/grpcio-1.81.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:43c121e135ae44d1559b430db2b2dfad7421cbbe40e1deba506c7dc62b439719", size = 12048662, upload-time = "2026-06-01T05:55:28.453Z" }, + { url = "https://files.pythonhosted.org/packages/64/89/b1b93ef6b34bd20bbaf707fa99133bc9cc302139d5ec6f77a165c7169796/grpcio-1.81.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f345de40ef2e65f63645d53d251824e6070e07804827c5b00ec2e44555f9f901", size = 6599116, upload-time = "2026-06-01T05:55:31.185Z" }, + { url = "https://files.pythonhosted.org/packages/eb/bc/c89f9b9d1c22895715356a1e009554dae66319e97826bb4d30bcda7d29e8/grpcio-1.81.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:8c0855a350886f713b9e458e2a10d208009dcaa849f574e39cd6067db1fe1279", size = 7307591, upload-time = "2026-06-01T05:55:33.463Z" }, + { url = "https://files.pythonhosted.org/packages/65/4a/1df2a4cb4a1386e066ab7e4175e34bb884b35ccb60d3621c09c84af6aabb/grpcio-1.81.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a524cd530900bd24511fcb7f2ed144da4ea37711c4b094475d0bceca7a93a170", size = 6811797, upload-time = "2026-06-01T05:55:36.731Z" }, + { url = "https://files.pythonhosted.org/packages/8d/dc/fa189d20601a1be25b08850cfb733879bbb1047b62a8feec3a60e3e1a87b/grpcio-1.81.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e7746ba3e6efc9e2b748eff59470a2b8684d5a9ec607c6580bcaa5be175820bc", size = 7415131, upload-time = "2026-06-01T05:55:39.451Z" }, + { url = "https://files.pythonhosted.org/packages/ad/a3/5625c48cb48d23c6631b3e5294f88e4c751f22a52591ae78859fab96dca1/grpcio-1.81.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:aaaa4f7f2057d795952e4eacf3f342be8b5b156992f6ac85023c8b98794ebd47", size = 8408398, upload-time = "2026-06-01T05:55:42.219Z" }, + { url = "https://files.pythonhosted.org/packages/75/34/0f8202c6809a46c2b4d69125ef3667c40b1c211f8e19930e5fa1f1197039/grpcio-1.81.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0fba53cb96004b2b7fb758b46b2288cb49d0b658316a4e73f3ef67230616ee65", size = 7844481, upload-time = "2026-06-01T05:55:44.849Z" }, + { url = "https://files.pythonhosted.org/packages/c0/95/c3366b5b5edf4c4adc90f2e29ca16e57965a8e56dc8d2ee89565ba1905bb/grpcio-1.81.0-cp313-cp313-win32.whl", hash = "sha256:c197e2ef75a442528072b29e9755da299110e8610e8bcbb59a6b4cf55384f005", size = 4182777, upload-time = "2026-06-01T05:55:47.459Z" }, + { url = "https://files.pythonhosted.org/packages/a9/a7/932f2f748511a32e641a2aba0d30dded3ed6e8bc330e0924e4d5d86853e6/grpcio-1.81.0-cp313-cp313-win_amd64.whl", hash = "sha256:194eddfacc84d80f50512e9fd4ee851d5f2499f18f299c95aa8fb4748f0537e0", size = 4928085, upload-time = "2026-06-01T05:55:50.158Z" }, + { url = "https://files.pythonhosted.org/packages/c5/1d/28b231333857deb840bc3d182ae087510170ea6d68f21393aeb0fe499530/grpcio-1.81.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:a9351055f52660b58f3d4890ea66188b5134399f82b11aa0c55bd4b99eff5390", size = 6055712, upload-time = "2026-06-01T05:55:52.889Z" }, + { url = "https://files.pythonhosted.org/packages/e8/b8/999c14f9dff0fc47549d2e827cba1343ddc18e1d1bf0d06d2cf628eecbd9/grpcio-1.81.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:300f3337b6425fd16ead9a4f9b2ac25801acb64aa5bc0b99eb69901645b2b1d2", size = 12057189, upload-time = "2026-06-01T05:55:55.952Z" }, + { url = "https://files.pythonhosted.org/packages/1e/3d/1fbde079572562af65351151d840525a13879eb7b481d35b55cd64c6127a/grpcio-1.81.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:97bbd623f7ded558fd4f7cb5a4f600c4d4de65c5dd364c83a5b14b2a10a2d3b5", size = 6608136, upload-time = "2026-06-01T05:55:59.069Z" }, + { url = "https://files.pythonhosted.org/packages/32/89/1f17cb6882abfd8e5a303a25d5d1665abef5a8c499a96198c65a651d1b85/grpcio-1.81.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ff83d889e3ebf6341c8c7864ad8031591ad5ca61599072fc511644d1eb962d2b", size = 7307045, upload-time = "2026-06-01T05:56:02.376Z" }, + { url = "https://files.pythonhosted.org/packages/48/5a/f98e91b2e755652e637ea2144318b0229b290062199f761b445fe1fa6015/grpcio-1.81.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c4fe218c5a35e1d87a5a26544237f1fa41dfd9cbd3c856b0810a30061f8b0aaf", size = 6812794, upload-time = "2026-06-01T05:56:05.777Z" }, + { url = "https://files.pythonhosted.org/packages/0a/0c/77892d715ac41e7ec0ace2a50080ffb64e189188056f607a66fe0014d1ee/grpcio-1.81.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b8b025b6af43ee0ad4a70307025d77bcab5adde7c4597786010d802c203e9fc5", size = 7422767, upload-time = "2026-06-01T05:56:08.524Z" }, + { url = "https://files.pythonhosted.org/packages/3f/b8/aa04590c6564714d94954515f15a236e59d4b9b3ad01e615f1b706d7792d/grpcio-1.81.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:3d4e0ce5a40a998cf608c8ba60ecfe18fdf364a9aa193ae4ac3faeecd0e86757", size = 8408551, upload-time = "2026-06-01T05:56:11.283Z" }, + { url = "https://files.pythonhosted.org/packages/43/3d/4f4a3450a1973568910c6909cb74abbf2126f68aefae5976962f9f7ad50d/grpcio-1.81.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:aa948712c8e5fa40ec250870bda14bc7578e1bb832a8912d9d2a0f720518edbe", size = 7846468, upload-time = "2026-06-01T05:56:14.536Z" }, + { url = "https://files.pythonhosted.org/packages/88/f4/5827fd248221ad3b44161c23ce9b5f4ee405b04fc6da5fd402a9aa87a84a/grpcio-1.81.0-cp314-cp314-win32.whl", hash = "sha256:fbbe81314a9d92156abce8b62c09364eb8bafc0ca2a19919a45ec64b5c6cb664", size = 4264427, upload-time = "2026-06-01T05:56:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e8/127dc2b246096ad50ef7c8d9b7b31d757787aeb796368bcdd4454e4204c4/grpcio-1.81.0-cp314-cp314-win_amd64.whl", hash = "sha256:b93cee313cae4e113fbb3a0ce1ea5633db6f63cfde2b2dc1d817429026b2a50b", size = 5070848, upload-time = "2026-06-01T05:56:19.735Z" }, ] [[package]] @@ -1185,11 +1185,11 @@ wheels = [ [[package]] name = "idna" -version = "3.16" +version = "3.17" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1a/88/bcf9709822fe69d02c2a6a77956c98ce6ea8ca8767a9aadcedc7eb6a2390/idna-3.16.tar.gz", hash = "sha256:d7a6da03db833450fca25d2358ac9ff06cd624577a4aea3a596d5c0f77b8e03d", size = 203770, upload-time = "2026-05-22T00:16:18.781Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/28/99c51f664567218d824af024c0251650fb27e4ca066df188dab0769c5b91/idna-3.17.tar.gz", hash = "sha256:5eb0cb53bc467c12eadcf6de83163ad8527cec9416f44b9b61b19caedad2b87f", size = 196048, upload-time = "2026-05-28T14:32:38.55Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/16/70255075a9859a0e3adb789b68ceb0e210dec03934245fd98d248226572f/idna-3.16-py3-none-any.whl", hash = "sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5", size = 74165, upload-time = "2026-05-22T00:16:16.698Z" }, + { url = "https://files.pythonhosted.org/packages/de/a7/f76514cc40ad6234098ecdebda08732d75964776c51a42845b7da10649e2/idna-3.17-py3-none-any.whl", hash = "sha256:466e48829084efe2548012b855df21540b96f2e20e51bd124c851536556a592c", size = 65316, upload-time = "2026-05-28T14:32:37.035Z" }, ] [[package]] @@ -1252,7 +1252,7 @@ wheels = [ [[package]] name = "ipython" -version = "9.13.0" +version = "9.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -1262,15 +1262,15 @@ dependencies = [ { name = "matplotlib-inline" }, { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, { name = "prompt-toolkit" }, - { name = "psutil" }, + { name = "psutil", marker = "sys_platform != 'emscripten'" }, { name = "pygments" }, { name = "stack-data" }, { name = "traitlets" }, { name = "typing-extensions", marker = "python_full_version < '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cd/c4/87cda5842cf5c31837c06ddb588e11c3c35d8ece89b7a0108c06b8c9b00a/ipython-9.13.0.tar.gz", hash = "sha256:7e834b6afc99f020e3f05966ced34792f40267d64cb1ea9043886dab0dde5967", size = 4430549, upload-time = "2026-04-24T12:24:55.221Z" } +sdist = { url = "https://files.pythonhosted.org/packages/21/c2/c0064cf15d026501a1ef70e42efd9c3f818663089399aacc5e37a82901c1/ipython-9.14.0.tar.gz", hash = "sha256:6f27ff0f1d9ea050e0551f71568bc4b34d8aba579e8f111c5b4175f44ac6b4aa", size = 4432601, upload-time = "2026-05-29T15:13:24.611Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/86/3060e8029b7cc505cce9a0137431dda81d0a3fde93a8f0f50ee0bf37a795/ipython-9.13.0-py3-none-any.whl", hash = "sha256:57f9d4639e20818d328d287c7b549af3d05f12486ea8f2e7f73e52a36ec4d201", size = 627274, upload-time = "2026-04-24T12:24:53.038Z" }, + { url = "https://files.pythonhosted.org/packages/14/a3/9e59340f02c1dc8f8c0a05b09244712b8609eb5439f9996e887e2b82f452/ipython-9.14.0-py3-none-any.whl", hash = "sha256:8fd984a3372c14b12790b084ba6b5cff5678c0cb063244a0034f06a51f20d6c2", size = 627457, upload-time = "2026-05-29T15:13:22.942Z" }, ] [[package]] @@ -1457,7 +1457,7 @@ wheels = [ [[package]] name = "jupyter-server" -version = "2.18.2" +version = "2.19.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1480,9 +1480,9 @@ dependencies = [ { name = "traitlets" }, { name = "websocket-client" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ca/15/1eacb0fcb79ef86e8a0a79a708e6ad7435f6f223097dd29a4ce861fabc44/jupyter_server-2.18.2.tar.gz", hash = "sha256:06b4f40d8a7a00bb39d5216859c81374a0e7cfefe6d8a5a7facc5a5c37c679a7", size = 753177, upload-time = "2026-05-06T07:04:36.274Z" } +sdist = { url = "https://files.pythonhosted.org/packages/76/a0/eb3c511f54df7b54ca5fc7bff3f4d2277d69052d6a7f521643dfed5279d6/jupyter_server-2.19.0.tar.gz", hash = "sha256:1731236bc32b680223e1ceb9d68209a845203475012ef68773a81434b46a31a7", size = 754561, upload-time = "2026-05-29T11:21:26.057Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/50/ecf4f70d65bdb7519b28a33d1b2fee8a4b4ba1ae1a92f15d97e877c5de21/jupyter_server-2.18.2-py3-none-any.whl", hash = "sha256:fa5e46539ded65791838035a2b6001f13e54d5f64b8b3752eb1e91fdd641a5b8", size = 391907, upload-time = "2026-05-06T07:04:34.014Z" }, + { url = "https://files.pythonhosted.org/packages/c1/78/d2881e68894cecdcd05912a9c585cfb776ef1fb38b62c8dba98f12ab3adc/jupyter_server-2.19.0-py3-none-any.whl", hash = "sha256:cb76591b76d7093584c2ad2ae72ac3d58614a4b597507a1bb04e1f9f683cf9ea", size = 392244, upload-time = "2026-05-29T11:21:23.871Z" }, ] [[package]] @@ -2255,7 +2255,7 @@ wheels = [ [[package]] name = "optuna" -version = "4.8.0" +version = "4.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alembic" }, @@ -2266,9 +2266,9 @@ dependencies = [ { name = "sqlalchemy" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bf/9b/62f120fb2ecbc4338bee70c5a3671c8e561714f3aa1a046b897ff142050e/optuna-4.8.0.tar.gz", hash = "sha256:6f7043e9f8ecb5e607af86a7eb00fb5ec2be26c3b08c201209a73d36aff37a38", size = 482603, upload-time = "2026-03-16T04:59:58.659Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/aa/05f5e3f662cc96a4c478fc3446b8ed6359825a2b504ecb614a9ac84e4a4d/optuna-4.9.0.tar.gz", hash = "sha256:b322e5cbdf1655fb84c37646c4a7a1f391de1b47806bbe222e015825d0a82b87", size = 485834, upload-time = "2026-06-01T06:23:30.424Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/24/7c731839566d30dc70556d9824ef17692d896c15e3df627bce8c16f753e1/optuna-4.8.0-py3-none-any.whl", hash = "sha256:c57a7682679c36bfc9bca0da430698179e513874074b71bebedb0334964ab930", size = 419456, upload-time = "2026-03-16T04:59:56.977Z" }, + { url = "https://files.pythonhosted.org/packages/ab/f3/e5fcd5d9b15771ed6dc10e3a7eeddc672e418f4f4c4653d216cc1d857e2d/optuna-4.9.0-py3-none-any.whl", hash = "sha256:f52f3be6148654850c92a5860d398fd88ec6b2c84ab68d9c3d07dcff02e7afee", size = 425553, upload-time = "2026-06-01T06:23:28.804Z" }, ] [[package]] @@ -2436,11 +2436,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.9.6" +version = "4.10.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400, upload-time = "2026-04-09T00:04:10.812Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/47/e4501f49c178ae1d9f4a75073fda4204f52647993f075a9db4d14930e0c5/platformdirs-4.10.0.tar.gz", hash = "sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7", size = 31224, upload-time = "2026-05-28T03:32:53.587Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348, upload-time = "2026-04-09T00:04:09.463Z" }, + { url = "https://files.pythonhosted.org/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a", size = 22743, upload-time = "2026-05-28T03:32:52.175Z" }, ] [[package]] @@ -2670,15 +2670,15 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.3.1" +version = "1.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/48/60/e88788207d81e46362cfbef0d4aaf4c0f49efc3c12d4c3fa3f542c34ebec/python_discovery-1.3.1.tar.gz", hash = "sha256:62f6db28064c9613e7ca76cb3f00c38c839a07c31c00dfe7ed0986493d2150a6", size = 68011, upload-time = "2026-05-12T20:53:36.336Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/12/38c1a0b1e64806780c9563e3fc9f6e472251839662587cfbe9bfaf2ae10a/python_discovery-1.4.0.tar.gz", hash = "sha256:eb8bc7daad3c226c147e45bb4e970a1feb1bf4048ee178e6db59e197b8010ce3", size = 68455, upload-time = "2026-05-28T01:15:37.639Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl", hash = "sha256:ed188687ebb3b82c01a17cd5ac62fc94d9f6487a7f1a0f9dfe89753fec91039c", size = 33185, upload-time = "2026-05-12T20:53:34.969Z" }, + { url = "https://files.pythonhosted.org/packages/c8/8d/3d316429f65029532bb1e28ff77b797d86b5ac3915bb44ca4e19aa283d43/python_discovery-1.4.0-py3-none-any.whl", hash = "sha256:26ed78d703e234879a66244c7d4114563fb13ec5cd30a2d1357e5fb4850782da", size = 33217, upload-time = "2026-05-28T01:15:36.573Z" }, ] [[package]] @@ -2995,7 +2995,7 @@ wheels = [ [[package]] name = "rosettasciio" -version = "0.13.0" +version = "0.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "dask", extra = ["array"] }, @@ -3005,178 +3005,207 @@ dependencies = [ { name = "python-dateutil" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f8/08/435d64b1605fb871b445259f6f275b946960e1a918afc854c847f452147c/rosettasciio-0.13.0.tar.gz", hash = "sha256:59bf2834c4ecb84132b1f89380bc95375d170b48cf59a5608e3bed684caf52c9", size = 1189632, upload-time = "2026-04-09T09:39:02.511Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/69/6d/fafb202c842ff399f445ec15af58befbc04d8d18aa6698d83f2d810ee5f9/rosettasciio-0.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bdbdea62745fb3d6854d8995bd156987d1e2b48a5e2296c6b1c7edd913ceb096", size = 822742, upload-time = "2026-04-09T09:37:56.153Z" }, - { url = "https://files.pythonhosted.org/packages/50/60/5ad9b2576427833f8f37d31583d49b49109a4ac5b84697de72dfc42d64f0/rosettasciio-0.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4cc73fce7e09a75dca154197e2694a2685bfffa82862b99ac7ea2de6454303f1", size = 821901, upload-time = "2026-04-09T09:37:57.83Z" }, - { url = "https://files.pythonhosted.org/packages/00/63/ecd34cb552824c4cd38805be8e6dab8a689833dea865a467aeeb4f281601/rosettasciio-0.13.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0bdc2c22ed1f422851b3b84b27beb93e0fe610642f254486c3b9c8a3f721981", size = 1277605, upload-time = "2026-04-09T09:37:59.365Z" }, - { url = "https://files.pythonhosted.org/packages/62/a9/6dbe0468db382c39f9293a69268df9d554e2f3fd5fdf9cddab8c89cbb028/rosettasciio-0.13.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5140c6ff68443c97c487454da23c5f1ac9f753d534aaf119f9cdadc03899ee6b", size = 1281953, upload-time = "2026-04-09T09:38:01.862Z" }, - { url = "https://files.pythonhosted.org/packages/c6/dc/8a1024dca145dc8ed2891ae98232b536d1abebd959e5f7f334d7355c28e1/rosettasciio-0.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:56b9ddaaa9d570c9d4fe43bd5a353cc4b4d609fc9970550e1f186465b35d8cd7", size = 811825, upload-time = "2026-04-09T09:38:03.907Z" }, - { url = "https://files.pythonhosted.org/packages/e0/6f/73d874f47b40cf1fe3b97d01f321f9dca4e1546551ff6fa22e8bfb707a97/rosettasciio-0.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:61878596a661e0b3004537be40779c2a9298678469c355dfe121f2e3688d8949", size = 800557, upload-time = "2026-04-09T09:38:05.404Z" }, - { url = "https://files.pythonhosted.org/packages/d0/43/8c3b5ffc09d54d94177aedea457fe571feb97c1631dbcc643a31ef4cc5b1/rosettasciio-0.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aaa1dbeeaa6798fd3b29fe0727160a37c3cc257234126db0d3c8016f8991f255", size = 823809, upload-time = "2026-04-09T09:38:07.017Z" }, - { url = "https://files.pythonhosted.org/packages/11/2f/c90520fe1a70585330950ab2a1cd4251a2ad76901e7c0d5ec74227fea9bf/rosettasciio-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27ad8e41a3c68df9404e2bd0d87deada52068a50d60649bd2d57b852b9fefb99", size = 822143, upload-time = "2026-04-09T09:38:08.642Z" }, - { url = "https://files.pythonhosted.org/packages/ff/05/7d5e18d60d5e50733ece82461d45a0f74517e1d75dfc8bc7e11140903d33/rosettasciio-0.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf6618fcdbccf61a773e1f86509e82f6ec5476bd38343856dcff707312ce4073", size = 1279557, upload-time = "2026-04-09T09:38:10.533Z" }, - { url = "https://files.pythonhosted.org/packages/1a/e2/7051df866c689552770b46fc35b0757199d16e8a3108927f5510c4db5224/rosettasciio-0.13.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:addc3f82fb4ea03c208c151d06eb65dba7e5fb0bea0244c5c054dbc5510ad962", size = 1284323, upload-time = "2026-04-09T09:38:12.365Z" }, - { url = "https://files.pythonhosted.org/packages/97/8f/450fb8bed3cbf05f875852ca243489e43fa0ed844438e700d9d5cb26e74b/rosettasciio-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:5157fd2468f9b77fd36948e7a54013d8608df30c243d6833ea48fa42f8f8a044", size = 812447, upload-time = "2026-04-09T09:38:14.357Z" }, - { url = "https://files.pythonhosted.org/packages/c0/cf/2d5686cf88a7d65760ecfa4583d4e7e26154932c7f60f405ac350ef7a898/rosettasciio-0.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:b0cfe472b561b74e678d71e05982979dc705dfda76e812278de283be6b295b8d", size = 800290, upload-time = "2026-04-09T09:38:16.311Z" }, - { url = "https://files.pythonhosted.org/packages/cb/b0/446ecd78b50d268df112c393b47f8e764415f84097a778faa0e4730a6569/rosettasciio-0.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:337e63409040541196a530ed9a4a7cfb80ffdd5333183097c47a6774597e5e29", size = 823025, upload-time = "2026-04-09T09:38:17.798Z" }, - { url = "https://files.pythonhosted.org/packages/60/df/e7e7f9a6410543403a2675c13c68f8d4efa825dfce9308484fdb44333941/rosettasciio-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3d94b6a3cac5f99a055a71dca7a21fd16447376a990621788c8c87370a35fa4f", size = 821326, upload-time = "2026-04-09T09:38:20.027Z" }, - { url = "https://files.pythonhosted.org/packages/34/1a/db727b64bd30669951cd779b8ac98cc7ec0590c2d433bba451280b8c9ff5/rosettasciio-0.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d817ca99b43df9d478b1da6c007b345fc4f65ab80bb502486190e88df74d6123", size = 1272603, upload-time = "2026-04-09T09:38:21.879Z" }, - { url = "https://files.pythonhosted.org/packages/bf/f6/16f10a129e2675ba680daa88dd5f38a512ada1351b64488d49f03544cfb3/rosettasciio-0.13.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:252d19fd5eff9cf9524fe1c2df4cf618f5e742c26ecd376adacb567df49527b9", size = 1283074, upload-time = "2026-04-09T09:38:23.819Z" }, - { url = "https://files.pythonhosted.org/packages/58/06/59425c136ff864bb4cff7d888f185c42579e2b76cd778bf0733629111fc8/rosettasciio-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:6e61bbc1babab958234c455e2ae2aad3155ef1d897cebcc76c55ceb2d89f8fe2", size = 812215, upload-time = "2026-04-09T09:38:26.04Z" }, - { url = "https://files.pythonhosted.org/packages/f4/8a/c1d39f91cdea09984ce37c546b1f28ad3a077426766d7c85d49ac3eb2b7d/rosettasciio-0.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:5f3557ea5d116e87ad6d0b09b192c7ebe4d2d87e1fedd2f56eab701d8f8846d0", size = 800093, upload-time = "2026-04-09T09:38:27.696Z" }, - { url = "https://files.pythonhosted.org/packages/8b/2a/2d01c6ab98dc522eb41bbfcd04afaef6a7793f5a59d6160acba8ebd0f033/rosettasciio-0.13.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1fa9bcc7112b9b28f4b95e9e92f887cf0e499db927577273b3487bd5765e5bd8", size = 827252, upload-time = "2026-04-09T09:38:29.369Z" }, - { url = "https://files.pythonhosted.org/packages/30/2c/42d58c33f69237787db336eaaa6cb067acef63665dde8d13df8fe082fdb2/rosettasciio-0.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0176857cf0e38b0fb6d71b143e207581447995fb41933b3c80eec1315b65549e", size = 827226, upload-time = "2026-04-09T09:38:31.536Z" }, - { url = "https://files.pythonhosted.org/packages/5d/29/a91d1055ff02c806df84c4f95f4f712a739369bada65eda849d9cc8cbae4/rosettasciio-0.13.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c2c36529cf8707aaa68876ce96110707a9341f2e3284fde6273c6cf5a88bfe7", size = 1287317, upload-time = "2026-04-09T09:38:33.001Z" }, - { url = "https://files.pythonhosted.org/packages/58/d3/dea667d2742e4ba9c78ae50c08cdd839550697b9c7676660d53d633cac31/rosettasciio-0.13.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ee2e1d51bfd010340f0bc55781d833f556ffd2bfd7d8890a33c1bd4173c5de3", size = 1274142, upload-time = "2026-04-09T09:38:34.643Z" }, - { url = "https://files.pythonhosted.org/packages/77/37/b4e71488292bb8eac88e22e4c59378181e511b26e26bb3dc4a53e3eee51e/rosettasciio-0.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:74cb1ce27667011611bf5f1b96c460985cd28b0dac9dbf7723259f5a0205f63f", size = 822578, upload-time = "2026-04-09T09:38:36.241Z" }, - { url = "https://files.pythonhosted.org/packages/2f/6a/3e074d0c9b75f6971bcd6266b08e739c1fe3709e9ce08fb9e960ea64cb8d/rosettasciio-0.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:727cc1f1bd37a139098184a426517dd60664b4a6e5b836e4e254de91a5d6e357", size = 804707, upload-time = "2026-04-09T09:38:37.98Z" }, - { url = "https://files.pythonhosted.org/packages/3c/72/00d7aa20493963e3337276fe97927b38f58117b4e488236c6b76332df199/rosettasciio-0.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:79332f1165b4f2029a7c03a339f467302e4d71f3aee2c699a49ab6236dd2aeec", size = 823255, upload-time = "2026-04-09T09:38:39.778Z" }, - { url = "https://files.pythonhosted.org/packages/4b/ea/6230f0f1afd3f73af32a229b2907a86d184ad584e1f75c16b234d7e48ec5/rosettasciio-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0be2e98e862cd1d725b105d2b20849e4608497ef77152ff5994cdbb28f27e0df", size = 822154, upload-time = "2026-04-09T09:38:41.409Z" }, - { url = "https://files.pythonhosted.org/packages/01/1a/5af45536c4e527cef10085135efd553e79d822d4bad5304109b31a00583e/rosettasciio-0.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:059ee09c3d335e590470820ce16c4f779822b91908f3506456cb825849714ddd", size = 1271542, upload-time = "2026-04-09T09:38:43.243Z" }, - { url = "https://files.pythonhosted.org/packages/cb/55/95bc7d475a861fe4ed17c10b1cbf62ccf00073e149983091cdc046d94ed2/rosettasciio-0.13.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8fd84038d0744065d9dc0be37050679f7f2ee59286c05e6841d9983b54544a6", size = 1278938, upload-time = "2026-04-09T09:38:45.044Z" }, - { url = "https://files.pythonhosted.org/packages/9e/ce/59addee6caea52623690f1e2d56b52c60b12c9eb4d993cb86da88b187398/rosettasciio-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:9567d347f368109b43301a3a31c2f243d9cb081315e361b72ec0d90065e37d04", size = 811208, upload-time = "2026-04-09T09:38:46.954Z" }, - { url = "https://files.pythonhosted.org/packages/b0/87/faf3209b600c579b3e38187b7a9d9263462a97e0858e99258f20ea6c40e3/rosettasciio-0.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:b6bbec93b2947e2035aca559284441be3f3dde5aa755b318a6413eeb21ab7c69", size = 799008, upload-time = "2026-04-09T09:38:48.629Z" }, - { url = "https://files.pythonhosted.org/packages/9b/9b/007063117e177147705b3cefb618689d7d4b5e243acd8e39e7e48a35fb69/rosettasciio-0.13.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:34dd225f0e11635ca09ccebb0ac3d8d38b71483491247d1f7789a9501288d659", size = 827711, upload-time = "2026-04-09T09:38:50.625Z" }, - { url = "https://files.pythonhosted.org/packages/97/64/42082e253b0ae2acf7aa7a67bd4547c6d918c248d24e715cbd63c4cadd4d/rosettasciio-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:dbaed91a90c118e74b2729a0738d2d5170d4d2f5763d84b06e8993f82fa5644d", size = 827561, upload-time = "2026-04-09T09:38:52.143Z" }, - { url = "https://files.pythonhosted.org/packages/91/ac/95837b692eafdb4ec67464cc13780da01b358ab8b293f327e6d59bc59f27/rosettasciio-0.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6d249fbc568dd4077df94fc98d512ea8bdabb7d87f73724bf6bf75c9a5649dc", size = 1287593, upload-time = "2026-04-09T09:38:53.66Z" }, - { url = "https://files.pythonhosted.org/packages/2c/cc/69c859bcedad6564a46456058ba952c54b76040240e556c9930adf292534/rosettasciio-0.13.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3fb7ff9b9c55aefb2596b900e7b009a49b36c506e473130dd7304834463e0f73", size = 1274514, upload-time = "2026-04-09T09:38:55.152Z" }, - { url = "https://files.pythonhosted.org/packages/54/2b/5888169b6dc708f68af424120f27f8e28b0d628d6d1526b3e9232b599785/rosettasciio-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:ad1111fa3a582b88cd7401dda5f66bf6f7c6f3f0a7ab3a881338062ff0d8fb80", size = 823903, upload-time = "2026-04-09T09:38:57.121Z" }, - { url = "https://files.pythonhosted.org/packages/9e/eb/df4ed7ef0a525611b3b42947879a59cb26f2dac1f5359bd448c545913207/rosettasciio-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f15d405a134a8e6e50eaf4b69ac7b69d227d014b839c15832b068914fe8a6ddf", size = 803657, upload-time = "2026-04-09T09:38:58.93Z" }, - { url = "https://files.pythonhosted.org/packages/23/94/83d57a093891773b2c28d16098bda58e10c43e1425583e40873baa0c09c2/rosettasciio-0.13.0-py3-none-any.whl", hash = "sha256:bc3e8b0dab257e5ff6f9f1f4b4207ee67f6d2c55adf5f7112f92dec2ab50ef59", size = 573680, upload-time = "2026-04-09T09:39:00.338Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/57/ca/dc7c3871b47e1bd89b7a11a34b7b68219504a03047788e52cff1ce823755/rosettasciio-0.14.0.tar.gz", hash = "sha256:4b21582f0d832f086e10236b1798b1c17e327dc6d610e845a8eddd4df817504f", size = 1245393, upload-time = "2026-05-27T15:29:54.331Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/bf/0ecc513c23c2a61f1497c9801cc4144e40a996099d36b71407673ad1a975/rosettasciio-0.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:73c7bb64e6a456fd058807c9a698d50b6fa757985f0e505087b641b10dd50904", size = 876315, upload-time = "2026-05-27T15:28:58.173Z" }, + { url = "https://files.pythonhosted.org/packages/5d/36/07adfd0a1c36d249f6294538b030bd1c0da29486e6a73d35e1adbf689144/rosettasciio-0.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8f5ac58e7ff8cf2113eb878a50c7ceeb12748433ecb6287b1045e0f97aa7916", size = 875404, upload-time = "2026-05-27T15:28:59.682Z" }, + { url = "https://files.pythonhosted.org/packages/49/fa/7df79374170885b2a542192a18f22d92a6a1a941dbc62dd4f5195ad26e57/rosettasciio-0.14.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a844a914f11a72d06426862721227f54587c3a72f5a6dc1477646fbbd41109c", size = 1330312, upload-time = "2026-05-27T15:29:01.093Z" }, + { url = "https://files.pythonhosted.org/packages/61/d0/55cc1aa3e3bc621a638bd456282b4eee88be66eec969d480dd59acdfc85b/rosettasciio-0.14.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d66924bc504eb0af348ecf4223cff8052f45cb1f73bc47775d8b040f5c4d1d6f", size = 1334306, upload-time = "2026-05-27T15:29:02.543Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ca/603088354e78a828a67920c0d321e6ca979be73aca0719f7af8ffe35d7d9/rosettasciio-0.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:32ba0bd12da1a53345bf59853b8325dd1065038876f36466e0e71921042cfaf0", size = 864309, upload-time = "2026-05-27T15:29:03.912Z" }, + { url = "https://files.pythonhosted.org/packages/23/a1/9395d12ba2fca40290d9461512ca19c8d91c735aa3de6172855c1891920a/rosettasciio-0.14.0-cp311-cp311-win_arm64.whl", hash = "sha256:43939dfa6dc92f7d39e55b11001285b62dfe136c03f90977aab1ed44f79b243a", size = 854003, upload-time = "2026-05-27T15:29:05.254Z" }, + { url = "https://files.pythonhosted.org/packages/9d/03/7df5ab31e0a8ab429d2debd5ea9246d4ebad4062fd9ca50b8f97f95a826d/rosettasciio-0.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:559269d18a179db96b28bda98b9458dc865ef6d82b0d59f0e4c277ceb275b8cb", size = 877346, upload-time = "2026-05-27T15:29:06.964Z" }, + { url = "https://files.pythonhosted.org/packages/9d/34/d3f6595107d730d52ecf0b5fe0f3f106718c82fe3b3f92a94d175d95d67a/rosettasciio-0.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:654336c91b75a0efa9a6a9112397a131771b7ee70e18c297394f200042b3a2fa", size = 875628, upload-time = "2026-05-27T15:29:08.7Z" }, + { url = "https://files.pythonhosted.org/packages/65/ae/23894df831445e95a6dc9d915d500ff755e8c6241c32b62321d154b45398/rosettasciio-0.14.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed65d0c844a5e3623b88bb1ae035908e256113f0ec43f9b0acefc1489b7a6430", size = 1334614, upload-time = "2026-05-27T15:29:10.04Z" }, + { url = "https://files.pythonhosted.org/packages/2e/50/108c50cd05f2ad3c3a105d7d1cafb596951859ebf596ca8f4132fa135dfc/rosettasciio-0.14.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c931d1a06f45f297c0a4de69cf4f477e65566adca63e9dd5e1976332140b06a7", size = 1340152, upload-time = "2026-05-27T15:29:11.586Z" }, + { url = "https://files.pythonhosted.org/packages/d4/43/04a92bdb09d4b12b8769a438bf048cc5220a6a49e33e81a4ff98064ac990/rosettasciio-0.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:27b7018bacbecbcaa8426650fa4fe22db24842f45ea9d3d5b9b4744218f2c628", size = 864789, upload-time = "2026-05-27T15:29:13.104Z" }, + { url = "https://files.pythonhosted.org/packages/ea/0e/c51fed20238121afeb6b4bf5d9e78a474ec644906bdaf7330a2a667502b5/rosettasciio-0.14.0-cp312-cp312-win_arm64.whl", hash = "sha256:708530b949084aca56a22ef80066ccfea3a242a83cc804ad2c6a0e8b7f52f248", size = 853639, upload-time = "2026-05-27T15:29:14.645Z" }, + { url = "https://files.pythonhosted.org/packages/c2/52/f2576629d0c194374fd8806232249c76808b0c9a64b4fd718648236a9ce2/rosettasciio-0.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3d3d0b93d4df943085e59109d9c2f7905eedd0ae35ef7914f6c1e6a8441de141", size = 876444, upload-time = "2026-05-27T15:29:15.973Z" }, + { url = "https://files.pythonhosted.org/packages/93/67/ec4d3991463f16ddae84bb4fa2b345567f1898fad9a73bbe9159deb07920/rosettasciio-0.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f70d1beaa0c552438b569b4ca383737155f96d4be501429d95dd258aff8b7272", size = 874764, upload-time = "2026-05-27T15:29:17.485Z" }, + { url = "https://files.pythonhosted.org/packages/1a/52/f8b16002613afe7fbcd51980d84172713aca218b1a1a1a7e1f6190c9c8b4/rosettasciio-0.14.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a96615c707782391bc082fe7c3de54b67e560af347274a4ce9881671547eb88", size = 1326408, upload-time = "2026-05-27T15:29:19.028Z" }, + { url = "https://files.pythonhosted.org/packages/48/91/25609a0eeb730ff2451b6f8466b8df194ac15003c03396714c626fa2d7cd/rosettasciio-0.14.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0cea0b240c8c87fae9137ae65496ec2e5208392f1f1058f3953b4f8a5a26d6e3", size = 1335956, upload-time = "2026-05-27T15:29:20.592Z" }, + { url = "https://files.pythonhosted.org/packages/72/67/f3a4bccf16b4a2813d1dcf7f4c5ff2ba6ccba31033cb73355d44b36ce437/rosettasciio-0.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:ad675d0f7dfe31d7864cd7fa4372076771110c82da0eb5b5471c26ccfe086995", size = 864658, upload-time = "2026-05-27T15:29:22.159Z" }, + { url = "https://files.pythonhosted.org/packages/28/01/77624b05b41991af136ad87fb9ef6a7f4f417a66202fd8b6f1366ed38a75/rosettasciio-0.14.0-cp313-cp313-win_arm64.whl", hash = "sha256:bf1c02213933cc82ae685d3444dc595e7c6ba6110ed0894963cc200a9bb567d2", size = 853524, upload-time = "2026-05-27T15:29:23.607Z" }, + { url = "https://files.pythonhosted.org/packages/a4/f6/153d463328eb1566b25283f519756720274dcf18269b1f9d93038158743e/rosettasciio-0.14.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e1bdd98eb232a39807c5d8d1a4fb69f81f2616662c22fe27ba43c972f5ecc648", size = 880788, upload-time = "2026-05-27T15:29:25.064Z" }, + { url = "https://files.pythonhosted.org/packages/7e/42/277d00ad1144d51938a6abe46d58416e7bd366ef0a12b3bda411ea0754d2/rosettasciio-0.14.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d6bf22187819fdf825bd80bc7a3ad8ad924c371e324c8d0ba60be7e0c2d61c58", size = 880606, upload-time = "2026-05-27T15:29:26.611Z" }, + { url = "https://files.pythonhosted.org/packages/91/24/b777a9e76cd1dce493e447b1fe96d7edefed0444b806bf1e61192111e2fc/rosettasciio-0.14.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db18d7832309bc6556319aaf28faa4992db0362ffd11851be07f70ff239e8afa", size = 1340574, upload-time = "2026-05-27T15:29:28.42Z" }, + { url = "https://files.pythonhosted.org/packages/6c/56/8c0d56499fa6b98603dd34c24f91a5994f4313809dbaabc213937dffbb62/rosettasciio-0.14.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c2ce75139da7dc89f5b2bf04226975beafac76d59a758780e9363173042ea9a", size = 1327203, upload-time = "2026-05-27T15:29:30.268Z" }, + { url = "https://files.pythonhosted.org/packages/59/0a/ebfde6c65a0c311ce191a1be71177568fc7dcdf567e8ea4e154b454f8620/rosettasciio-0.14.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c88f8923dacc23ad85c679109cdb3e879b749de77c9ef8575ca9fbab70708aa5", size = 869838, upload-time = "2026-05-27T15:29:31.662Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ed/9bfbf1e151b2956e055dcd1f83277da555387afa1fd17c85ace4d3b0ac5e/rosettasciio-0.14.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2b98254384dc1b55d8377a048f6fe6609d42b538c30428f55d7d97c183b19d1d", size = 858169, upload-time = "2026-05-27T15:29:33.529Z" }, + { url = "https://files.pythonhosted.org/packages/66/75/9dbc1e8bda660a6010091e0e898b326642559efdba580cdf49f2e236c1a1/rosettasciio-0.14.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:8f5d568b1a759084ba72550d4d5278c8464055d4c10d4e21d57dba81acc5a9fb", size = 876737, upload-time = "2026-05-27T15:29:34.957Z" }, + { url = "https://files.pythonhosted.org/packages/3a/32/74c8c91d22cc11594f42222e0936ced5c042ece324ae73663e04921d3366/rosettasciio-0.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e14ed16e32e74767c4d2233722ebc1a726b13ba2afb77c22353df37d7706b7bf", size = 875572, upload-time = "2026-05-27T15:29:36.404Z" }, + { url = "https://files.pythonhosted.org/packages/d7/05/cd32f2d5589a41bcf6de8e48ac1fda403913df6ade7e56252cdc1de7242e/rosettasciio-0.14.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:165bd0c416700de21d7b1da1f29f3e6d4aaac8c25003d1569ce4f6401111d4b4", size = 1324655, upload-time = "2026-05-27T15:29:37.852Z" }, + { url = "https://files.pythonhosted.org/packages/b3/99/6e3d0bd825939e22a98f3f8e41b9cb7fe1ba16f7861d1b379a571b0b5d27/rosettasciio-0.14.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:107942ffa5302e2a3812571d3fead609949a28a20d25f53151f2baaed1c7f790", size = 1331210, upload-time = "2026-05-27T15:29:39.286Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e8/5004fbffc23a6d565628481e111b69335f928365c57d485807c99eaeaeef/rosettasciio-0.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:f2b8fd2f0423e0be811c662dbfb20d48b2c8479d8249ce04062d039b27b4040c", size = 863712, upload-time = "2026-05-27T15:29:40.891Z" }, + { url = "https://files.pythonhosted.org/packages/40/b2/8c16af430876a637e6e18c9317468cba7a69e47d712dd10a1c69d785b02d/rosettasciio-0.14.0-cp314-cp314-win_arm64.whl", hash = "sha256:569b581cc909d0a755947c180388348ab1e00c5ab068fbdc289dc2a2e74864d8", size = 852806, upload-time = "2026-05-27T15:29:42.288Z" }, + { url = "https://files.pythonhosted.org/packages/9e/48/af1981ca42b19c99a5bb03226bc03faf48137c5fc6393626e216d1f96f94/rosettasciio-0.14.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ebb19e47a261d1d16cdfaf2104dd86af6d891fad82e6ee66eda76dab12fa4de8", size = 881288, upload-time = "2026-05-27T15:29:43.624Z" }, + { url = "https://files.pythonhosted.org/packages/86/e1/e965ac6158117841b814bb6d086bf601cc0d7fb385c7844f52b885ce533d/rosettasciio-0.14.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d61dede6dc3f8365797afc2a9eb05eac8fe08a9ea4e995203c95dcf2b85c5104", size = 880931, upload-time = "2026-05-27T15:29:45.093Z" }, + { url = "https://files.pythonhosted.org/packages/9c/4e/b5f24a072e5aae2b00facf4bab0870f6dc02b9b34c928f39bec5b7be4ace/rosettasciio-0.14.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0dedf178bd0a072e0c4bf46d9a55048c69664ec651a8739994f75070fe188d88", size = 1340469, upload-time = "2026-05-27T15:29:46.502Z" }, + { url = "https://files.pythonhosted.org/packages/83/da/10a17fed897fc9de139078b05439961750ae4fd47e5a309df454b472cca8/rosettasciio-0.14.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a72fd270b4d17a0a0a20188a3354770113debea3bb56bd976e3c0ca52d05c7e", size = 1327680, upload-time = "2026-05-27T15:29:48.048Z" }, + { url = "https://files.pythonhosted.org/packages/14/f3/3a5e2c3fa67fbf2219a914c6342a91f2f7ba19554ffe2b654fd44e13685c/rosettasciio-0.14.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4a17300595ae1a83331ce4e015478341eac99cf8544f36c88f7c6de6e2662541", size = 869656, upload-time = "2026-05-27T15:29:49.836Z" }, + { url = "https://files.pythonhosted.org/packages/55/3c/5f189a330ba363f04ee73e25b37f8e08ae1266ad72e6c93d65166a7c7c6e/rosettasciio-0.14.0-cp314-cp314t-win_arm64.whl", hash = "sha256:66ca5c8d08cd3caa5397b134f83803915807d8e799ad3ac2fda5b9607342371d", size = 857389, upload-time = "2026-05-27T15:29:51.353Z" }, + { url = "https://files.pythonhosted.org/packages/d2/a3/5cf5424b16d68e8db9139d613570ef01825995d260343fdec704d3f646ba/rosettasciio-0.14.0-py3-none-any.whl", hash = "sha256:f64e045ced6827ba75e6b8b85c54812c0346eda6b76f08acded3d0f6e7fb5a27", size = 628709, upload-time = "2026-05-27T15:29:52.825Z" }, ] [[package]] name = "rpds-py" -version = "0.30.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157, upload-time = "2025-11-30T20:21:53.789Z" }, - { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676, upload-time = "2025-11-30T20:21:55.475Z" }, - { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938, upload-time = "2025-11-30T20:21:57.079Z" }, - { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932, upload-time = "2025-11-30T20:21:58.47Z" }, - { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830, upload-time = "2025-11-30T20:21:59.699Z" }, - { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033, upload-time = "2025-11-30T20:22:00.991Z" }, - { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828, upload-time = "2025-11-30T20:22:02.723Z" }, - { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683, upload-time = "2025-11-30T20:22:04.367Z" }, - { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583, upload-time = "2025-11-30T20:22:05.814Z" }, - { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496, upload-time = "2025-11-30T20:22:07.713Z" }, - { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669, upload-time = "2025-11-30T20:22:09.312Z" }, - { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011, upload-time = "2025-11-30T20:22:11.309Z" }, - { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406, upload-time = "2025-11-30T20:22:13.101Z" }, - { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024, upload-time = "2025-11-30T20:22:14.853Z" }, - { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069, upload-time = "2025-11-30T20:22:16.577Z" }, - { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, - { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, - { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, - { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, - { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, - { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, - { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, - { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, - { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, - { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, - { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, - { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, - { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, - { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, - { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, - { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, - { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, - { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, - { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, - { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, - { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, - { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, - { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, - { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, - { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, - { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, - { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, - { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, - { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, - { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, - { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, - { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, - { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, - { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, - { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, - { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, - { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, - { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, - { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, - { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, - { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, - { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, - { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, - { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, - { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, - { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, - { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, - { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, - { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, - { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, - { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, - { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, - { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, - { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, - { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, - { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, - { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, - { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, - { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, - { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, - { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, - { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, - { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, - { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, - { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, - { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, - { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, - { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, - { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, - { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, - { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, - { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, - { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292, upload-time = "2025-11-30T20:24:16.537Z" }, - { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128, upload-time = "2025-11-30T20:24:18.086Z" }, - { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542, upload-time = "2025-11-30T20:24:20.092Z" }, - { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004, upload-time = "2025-11-30T20:24:22.231Z" }, - { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063, upload-time = "2025-11-30T20:24:24.302Z" }, - { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099, upload-time = "2025-11-30T20:24:25.916Z" }, - { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177, upload-time = "2025-11-30T20:24:27.834Z" }, - { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015, upload-time = "2025-11-30T20:24:29.457Z" }, - { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736, upload-time = "2025-11-30T20:24:31.22Z" }, - { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981, upload-time = "2025-11-30T20:24:32.934Z" }, - { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782, upload-time = "2025-11-30T20:24:35.169Z" }, - { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" }, +version = "2026.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/43/25a8dcd3feedd735039a8f0b5b7e3b118232b5eae288c4fd9ab200d41094/rpds_py-2026.5.1.tar.gz", hash = "sha256:07b24fea40541e28570e5b795a4a38fbdcd12550c06bd0748005ecc8116ca256", size = 64459, upload-time = "2026-05-28T12:02:13.232Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/a0/acf8b6fc20bfdcd3a45bd3f57680fb198e157b7e997b9123b10763798bd2/rpds_py-2026.5.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3397a5ed7174dc2786bb214030232fc36fe8e5584fec43a9952cc542b1a12036", size = 355609, upload-time = "2026-05-28T11:58:50.78Z" }, + { url = "https://files.pythonhosted.org/packages/b6/95/f8203fd997484b1690a6869cd0e503b6c3c6be55b0ecc36d1a491fe742f0/rpds_py-2026.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:99ab6ba7bfa2cb0f96a04e3652355bf04e3f51aceb1e943b8541dab7ba4828cc", size = 348460, upload-time = "2026-05-28T11:58:52.374Z" }, + { url = "https://files.pythonhosted.org/packages/33/8c/b47326ad2f0be545a5e5c1a55937a12afaea7d392ba2837bb9680f57e6c9/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0efbe45632665e53e3db8fe1e5692db58fc5cb9bab4459d570b83efefe11164", size = 381031, upload-time = "2026-05-28T11:58:53.775Z" }, + { url = "https://files.pythonhosted.org/packages/22/0b/e83bbd97ffac6f6389b605cd4e1c8ac5761dc7e977769c9255d8c5adb7bd/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:01d17b29c0c23d82b1f4751147ec49cf451f1fc2554eb9ef5f957e55d2656ead", size = 387121, upload-time = "2026-05-28T11:58:55.243Z" }, + { url = "https://files.pythonhosted.org/packages/fd/0e/d285d1bc8864245919c61e1ca82263e4a66d337759c3a4cef72766ff9afc/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7559f72b94ae52659086c595dfa017cde03155f7832071d30959049052cb3ece", size = 501026, upload-time = "2026-05-28T11:58:56.788Z" }, + { url = "https://files.pythonhosted.org/packages/86/06/ccb2109a1e543437b5e43816f2b43b9554cc6783145528a4e3711e05c011/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e25b7088f9ccbfc0dfcaa52bf969300ca229e10ecf758974ebcbb080a4b37bb", size = 391865, upload-time = "2026-05-28T11:58:58.298Z" }, + { url = "https://files.pythonhosted.org/packages/3d/33/237173db1cfef10105b3839a24de00eb8d2a523711add4632447cdf0aedd/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613fc4ee9eaef26dc5840666214dd6fbcebcf32f46e76f4abc473059f4e13dda", size = 378012, upload-time = "2026-05-28T11:58:59.589Z" }, + { url = "https://files.pythonhosted.org/packages/97/64/1eae54e34d5161f9969295e80bd6b62a55f2b6ac5f2a5b60d02c2140e758/rpds_py-2026.5.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:85264a90ff4c05c1568dd65f5921c837614b67c60358fb4c17df3b7f2e90690a", size = 391111, upload-time = "2026-05-28T11:59:01.104Z" }, + { url = "https://files.pythonhosted.org/packages/d8/34/5bb334a5a0f65d77869217c4654f34c78a7d11b93938a3c076a2edeafc52/rpds_py-2026.5.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe71bca7d547acb17027c7fd1624ff8aae623499c498d3e7011182c4de5c25e0", size = 409225, upload-time = "2026-05-28T11:59:02.433Z" }, + { url = "https://files.pythonhosted.org/packages/16/0f/007ec21283b5b040b4ec3bd95e0402591e22bfa7d5c93dfe01c465c2d2d7/rpds_py-2026.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05fa4f41f37ec97c9c260441a940450a192f78d774d2b097eee1379f1e1246a", size = 556487, upload-time = "2026-05-28T11:59:04.012Z" }, + { url = "https://files.pythonhosted.org/packages/ff/10/5437c94508169b6b22d8418fef7a66e9ffb5f3b9e9c94460f2eedafe06ff/rpds_py-2026.5.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df1d2a1996755b24b9ecee92cb4d36c28f86f464a6a173349c26bab41e94b8c2", size = 620798, upload-time = "2026-05-28T11:59:05.485Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d5/9937dce4d6bda74157b954e7d1460db05a22f5929dccfeeba1ed27a93df0/rpds_py-2026.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8895840ac4809e5f60c88fd07617cd71326e73d6e5a8aa783c5c0f7c24985de2", size = 584053, upload-time = "2026-05-28T11:59:06.837Z" }, + { url = "https://files.pythonhosted.org/packages/6c/31/750617dd0ae1752471bf43f9e41d263398fae7cde7849d23b8574a70e617/rpds_py-2026.5.1-cp311-cp311-win32.whl", hash = "sha256:3684a59b158a7683aaeb8e25352e9a9dd2122cec78f2d8530266e4f91b4c7b3f", size = 214390, upload-time = "2026-05-28T11:59:08.402Z" }, + { url = "https://files.pythonhosted.org/packages/3c/bb/3dcab0e1d9516303f2eb672a5d6f62eca5a69e2886301e9c8c54b520c39b/rpds_py-2026.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:7bd530e6a530bb3ea892f194fafa455f3516ac25ecf7143fd33c09be62b0470a", size = 231097, upload-time = "2026-05-28T11:59:09.786Z" }, + { url = "https://files.pythonhosted.org/packages/49/d6/c6bbf5cb1cf12b9732df8074b57f6ef8341ba884c95d40632ae8bddb44e4/rpds_py-2026.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:0a5ae4dbe43c1076983b72616496919872ae7bbe7a1e21cc48336bc3154d130b", size = 226361, upload-time = "2026-05-28T11:59:11.079Z" }, + { url = "https://files.pythonhosted.org/packages/d4/e7/a78582dc57caa592dcc7d4fb69b61390561e908eb3d2f5df5928a8e354c0/rpds_py-2026.5.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3abe24a66e57adcfa645d718063a5fa5103ecc71ddbf26d78af8f9368018ff1d", size = 353040, upload-time = "2026-05-28T11:59:12.531Z" }, + { url = "https://files.pythonhosted.org/packages/a3/43/35e3f136343aef451e545ce8c38d36c2f93c0ed88703db8b64ba2b205c68/rpds_py-2026.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58b1d94308ddf0b1982f61f2eb54bf92997c9ece8a8093ef014250f4a517906c", size = 345775, upload-time = "2026-05-28T11:59:13.827Z" }, + { url = "https://files.pythonhosted.org/packages/20/e1/0f2160c5982d3157734d5cb3ed63d8b2d583a73c9864f77b666449f32cf8/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa92420128dadce7f54bd73ba1825a273e9268fe9e35dbf7e6362890efa4e08", size = 376329, upload-time = "2026-05-28T11:59:15.271Z" }, + { url = "https://files.pythonhosted.org/packages/d0/11/ee0ba42aff83bf4effdbc576673c6be64c5e173978c3f6d537e94482f77d/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ca653c6546386227cd9800d1bef6a348099acf8db4250341da6d90f663d6dfcb", size = 383539, upload-time = "2026-05-28T11:59:16.665Z" }, + { url = "https://files.pythonhosted.org/packages/11/df/d94aa6a499d4ac40afe2d7620f2c597fd3c0f182e854ad7cf3f596a81cb6/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66c93681c4729e4e3ecba31b8179fae083ff3118841672835140338b4b9867c1", size = 494674, upload-time = "2026-05-28T11:59:17.991Z" }, + { url = "https://files.pythonhosted.org/packages/1f/75/33d30f43bb2f458de11979486a591b1bf6e5651765ed1704c6197c2dc773/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40ff257542e04796880e011e15cd4dc21c2599975df2aaa8f2c8495ca574e1a5", size = 389268, upload-time = "2026-05-28T11:59:19.434Z" }, + { url = "https://files.pythonhosted.org/packages/f4/1e/2c9096fc19d5fd084b0184ca2b651e659aa0a37e6fdbecf6ece47f147fe1/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6825cc329b290e93c5f6a9be2393118a763f6ccf6abd83704e0c102ca583644", size = 376280, upload-time = "2026-05-28T11:59:21Z" }, + { url = "https://files.pythonhosted.org/packages/b9/e5/61ec9f8be8211ea7f48448195549e4aaf02004083475493b0e137702ecb2/rpds_py-2026.5.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:de42116e69cb53b911cc34aee5ab98f36c597b822545045d49e938818b99e5e4", size = 387233, upload-time = "2026-05-28T11:59:22.454Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ca/bcec1005c4f4a234f92a29078631fee49206c7265ccae966f18fd332e80e/rpds_py-2026.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0f920015df2a504bebaba6d4c31ccf3fcf942f92655c086da30b671aad19aa6", size = 405009, upload-time = "2026-05-28T11:59:23.845Z" }, + { url = "https://files.pythonhosted.org/packages/72/e6/4d5718c5cf26c522dc7c9999e238da1e77380b81d0c5d1df11e271ddfeb1/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0408a24e44feb919423dc6d9da677cb5cddb894d2ca9e763967d156d9c60fab4", size = 553113, upload-time = "2026-05-28T11:59:25.184Z" }, + { url = "https://files.pythonhosted.org/packages/d4/25/2ee807bdb3e1f0b7eddf7782acd5665a8b5205a331a7d7244a52c4812fd9/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cea68bcd53467561ae2f96a6bdad1544299ba97b5b0ddcd5ac3d376e5c781c24", size = 618838, upload-time = "2026-05-28T11:59:26.749Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c1/7d4c26f167f8c41501cc073d30ee22082b16ce358cf5b00ec97cbc7804ea/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4be8b1d2a705cc37d08256004e1d07de143fa0075c8e85a3df020b776f62b732", size = 582436, upload-time = "2026-05-28T11:59:28.11Z" }, + { url = "https://files.pythonhosted.org/packages/04/1d/9d12b0a337bab46f4769f8857f4007e3b2d639e14f9a44a0efe157696e64/rpds_py-2026.5.1-cp312-cp312-win32.whl", hash = "sha256:6736718bd4fc49cbcb538ba30516fdbef161522acefb739657d48b97bd864fed", size = 212734, upload-time = "2026-05-28T11:59:29.689Z" }, + { url = "https://files.pythonhosted.org/packages/c5/93/e4116f2de7f56bc7406a76033dc501811ddeb22b7f056b92d632871ebb0c/rpds_py-2026.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:0a7d1eec967df0e9b22614a5e177622e0c89611d03727fa0cb48e45028907870", size = 229045, upload-time = "2026-05-28T11:59:31.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/53/6c3419d85eb2ec5938a37627c585b42d76a63bb731d6e42ed4b079ebf486/rpds_py-2026.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:1841d067089e117142d79b98aa0df2f08b52f2ecc1819dd2700636c0db74a473", size = 223967, upload-time = "2026-05-28T11:59:32.318Z" }, + { url = "https://files.pythonhosted.org/packages/6c/32/14c961ad295f490eb0849ada8b79683e93a59b9de3afdd983eaf55fa6867/rpds_py-2026.5.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:efef4ac29c6ff495531eb17ee705b62841ecaa291b7c7077e848ea03e237164d", size = 352787, upload-time = "2026-05-28T11:59:33.655Z" }, + { url = "https://files.pythonhosted.org/packages/ca/bb/d1b85117967c11191441a7274ae616c65d93901d082c588f89a50a8da5ae/rpds_py-2026.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c39f5b67a8a2e67179ada2a954227d670fe65fa9098457f698f56ddf248709b3", size = 345179, upload-time = "2026-05-28T11:59:35Z" }, + { url = "https://files.pythonhosted.org/packages/7c/46/d84105f062e626a1b233f863907288a4708c2d833b8b4c6fb2764bc080c0/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5c30f3f04eef4fbd362226a6f31d7c8895ca4fbb6e0b790f6890a98d8da8559", size = 376173, upload-time = "2026-05-28T11:59:36.43Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ae/469d7959ce5b1201e1de135dc735b86db3b35dd0d1734f6a44246d5f061c/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:277f6c82f0580848796c7ecc8a7173aa3bfb928e4ff831261c2f60a81dc270db", size = 383162, upload-time = "2026-05-28T11:59:37.995Z" }, + { url = "https://files.pythonhosted.org/packages/dc/a2/57853d31a1116a561aa072794602ad3f6341e18d70a8523f1bd5b9fc1e5a/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63c2c4c213f1a4e3f3de28ecab029dbdee976324e729c0d7a55211be72576b02", size = 495093, upload-time = "2026-05-28T11:59:39.453Z" }, + { url = "https://files.pythonhosted.org/packages/99/63/3a8eabcad9314b7daf5c65f451d2c33d989235cd8a5762186cf2c3f5a4f8/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3350ec808fb538fe71a1f94dfaa0e29c598dfad805ce49f0caec5ae3183c652b", size = 389829, upload-time = "2026-05-28T11:59:40.896Z" }, + { url = "https://files.pythonhosted.org/packages/4b/25/05678d97fc25e2622df14dc530fb82023174ecfff6733991ed0d78f167bd/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1b964e3ab599e718dc46c018d104b1ebc007cbc6567d827c94a687fca56d77e", size = 374786, upload-time = "2026-05-28T11:59:42.626Z" }, + { url = "https://files.pythonhosted.org/packages/88/d1/8c90b6431e80a3b91b284a5c7c8c0c4f9c006444d90477a740d6e0f9c694/rpds_py-2026.5.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:19cb09fab7b7fc96b2a6e28f2e34b72a3705ff27b37edb77455316e5d3f3dc9b", size = 386920, upload-time = "2026-05-28T11:59:44.124Z" }, + { url = "https://files.pythonhosted.org/packages/ff/99/4638f672ab356682d633ee0da9255f5b67ce6efd0b85eb94ad3e255e65a5/rpds_py-2026.5.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:abe76bcdba31e576cb83eeb8797aa0d882b738fef6dc65d0601fc753806a5b46", size = 405059, upload-time = "2026-05-28T11:59:47.177Z" }, + { url = "https://files.pythonhosted.org/packages/66/3f/3546524b6eb4cc2e1f363a3d638fa52f6c24faae3500c25fb488b02f1740/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bff7073db3899158fff55ebf57b113a67030af26f80a18978f9f0aa60250ddf", size = 553030, upload-time = "2026-05-28T11:59:48.603Z" }, + { url = "https://files.pythonhosted.org/packages/c6/c3/7b3388c796fcf471bd17194242d4dc1a7608567c0fa422bcc1c5e79f9c1e/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8ba264fa49be666cd9cc56bf34ec7002fb3d27a4aee5bcb4d43d0d18feb1bb6f", size = 618975, upload-time = "2026-05-28T11:59:50.314Z" }, + { url = "https://files.pythonhosted.org/packages/61/1e/a3cb07f2795075d1d88efddae2f541359fde5f08c81ee114c29c2949c90a/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4860b603ddda0475a8885499b3729e90229d480105b42651962a5397d995fa89", size = 581178, upload-time = "2026-05-28T11:59:51.673Z" }, + { url = "https://files.pythonhosted.org/packages/a1/74/e758c03a5ef46f04c37f2651a2893db846d569ba8a7bca469d4b58939bcd/rpds_py-2026.5.1-cp313-cp313-win32.whl", hash = "sha256:7944270ae71383f6e2657dd7d5ce4eeb4ac2d0059a6738f0510583d462ab4842", size = 212481, upload-time = "2026-05-28T11:59:53.148Z" }, + { url = "https://files.pythonhosted.org/packages/70/ec/a2aca432db9c7359b40fa393eeeaa0d166c2f70175be956e75fa24197c44/rpds_py-2026.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:88647f43a73c4e01be19b04ceef0c8d3a1958153604d13c773becd8016f2a0cf", size = 228519, upload-time = "2026-05-28T11:59:54.505Z" }, + { url = "https://files.pythonhosted.org/packages/29/60/a73bfdd45b096574556acf303bbd9fa9eed36ca8a818b514e2a5d5fe2b9d/rpds_py-2026.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:453895624ecf7db7063b1004e44037522bbaef9ff6a945e59bc71662d7a03abd", size = 223446, upload-time = "2026-05-28T11:59:56.081Z" }, + { url = "https://files.pythonhosted.org/packages/18/e2/408105fd611823f00882aea810f3989a30d26b1bab8b6beb20f98c724e0e/rpds_py-2026.5.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:b4e4bc98639ec915f512fde3aa7a95e0041d95d9c3cc86eea841fa63cb1e8600", size = 355287, upload-time = "2026-05-28T11:59:57.448Z" }, + { url = "https://files.pythonhosted.org/packages/8d/58/5c4a43436843c90d0f6d19f82c200c80e3843ca9fa07b237623327f6d384/rpds_py-2026.5.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cacedb7a6e167680acba45ad5716e89067d225dc80da0d7040cae8c81d4572fa", size = 347033, upload-time = "2026-05-28T11:59:58.881Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c2/1a71acdacaf4e259b10278fb87b039ded3cf80041bcd89dd8a3ea702ded6/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68700371c5d7ae1412862ddfa719090925c93ecf351c566d66f09d04b136ea00", size = 376891, upload-time = "2026-05-28T12:00:00.516Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c8/535f3d9b65addd8e28aa87b83c6e526799c3717a88273db8ea795beeef7a/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:296c799becfa849c779c8725494fe9ed94959ed886787df4364b058465bad7f0", size = 385646, upload-time = "2026-05-28T12:00:02.394Z" }, + { url = "https://files.pythonhosted.org/packages/1c/91/dc033f313345c354ade914dbe73cdb90b615a4409ea02430d5356794f3d8/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3858b908218ee108d0bbfb2095ccc237648053c9bf98affad7cb079acaf1d97", size = 498830, upload-time = "2026-05-28T12:00:04.189Z" }, + { url = "https://files.pythonhosted.org/packages/27/fc/90fcbea459dbb8ddc18a2e0fd1de9412b48bc84ffff2db771cf714bacfd6/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4fb8d2e7cb2f850b169806d61d1b991738acec96500a75c30f49caf064ce7cef", size = 392830, upload-time = "2026-05-28T12:00:05.797Z" }, + { url = "https://files.pythonhosted.org/packages/b2/1d/46cd11a228c9750684a798d98f878be6f614aa762438da7378f035e79e35/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27b74c10ed6a8f190f4287f53bcfea348b92a84a9c9f70d30183d1e6172d580d", size = 379613, upload-time = "2026-05-28T12:00:07.433Z" }, + { url = "https://files.pythonhosted.org/packages/24/4a/d9b0c6af3a1de03eb93741bbe8be2bdce84d8fda8224f3005451d86df389/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:b9a6528956191c48c52294a592dbd4a8386d7048bdb25c0efcb6b966466c6d83", size = 388183, upload-time = "2026-05-28T12:00:09.227Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b4/db7aaabdda6d020afc87d981bcc2f57a434c7dec60ecfc2ab3dd50b20351/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:af03e34e860047bc7a352b842856fcf78798fbb81132cc98bd2f907ab4eb9cd2", size = 408578, upload-time = "2026-05-28T12:00:10.779Z" }, + { url = "https://files.pythonhosted.org/packages/08/d6/070f6a41cbb343e2ac4171859bf3f3623e0ab002f72619d6d505313ec2de/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fea6e836d10abbe191d557d33bd58bd5987725fe63aa1eefe557d230209855bd", size = 553573, upload-time = "2026-05-28T12:00:12.443Z" }, + { url = "https://files.pythonhosted.org/packages/75/ab/1a71ea3589c4345dac0a0518f0e6a031cb42689277851b683c46d27463a5/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:fc0c0f878ea770a0a8a462456c5ad36fc9fe6358e6b76fdadc7f17575e0b8bf1", size = 620861, upload-time = "2026-05-28T12:00:14.09Z" }, + { url = "https://files.pythonhosted.org/packages/8a/22/9bf80a56069c0c443fcfefac639a86a744550a2898817a6dfd3e26654924/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e0b360f316d966b048b085857630b3cc51f3db2f07b06f440eac8f695374d1e3", size = 585633, upload-time = "2026-05-28T12:00:15.66Z" }, + { url = "https://files.pythonhosted.org/packages/da/68/3b2c0a75c9e04125696f84ebdbbf304acf5a40b58ba4481cdb98a922c3ba/rpds_py-2026.5.1-cp313-cp313t-win32.whl", hash = "sha256:a2999883eedf72fdfb7520b92c7d4ec2572a71ff40239377aa604cc529eecafc", size = 210074, upload-time = "2026-05-28T12:00:17.291Z" }, + { url = "https://files.pythonhosted.org/packages/e7/8b/609157d5a25d37d4f29f92840ba531f416907c34ae5c5739dd21fc2bef98/rpds_py-2026.5.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e07be2a9d7122bd6e82dea89814ef8dc893feb1aae97fec1630f3263bbb30e55", size = 228635, upload-time = "2026-05-28T12:00:18.73Z" }, + { url = "https://files.pythonhosted.org/packages/d4/6f/19c1918a4b590d8de87e712e4abe4b3875771eff60216fb6153cf6665c68/rpds_py-2026.5.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:1f2c391c3059798093b65df23aca2cac150460ae9c630d99dec83d703d9485b9", size = 349756, upload-time = "2026-05-28T12:00:20.217Z" }, + { url = "https://files.pythonhosted.org/packages/e5/60/a06fe7da34eca79dacbf958a2ba0c6eea85bc2b29de20080bf40f72f66fa/rpds_py-2026.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:413b424f7c4ee65ab5e5be91f5731be0f8b41a1ee2b12dfe810d716312e95a78", size = 343831, upload-time = "2026-05-28T12:00:21.711Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ec/b2333b97b90e2a6ef6ca8ad386ee284968e74bcfe113b3f1a8d9036429a9/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c595a1d9255dce0599e13130d1440ab2506654f2b50294226ee06402f8fef63", size = 375127, upload-time = "2026-05-28T12:00:23.326Z" }, + { url = "https://files.pythonhosted.org/packages/14/7f/e00aae54067f2b488c4637961d5f58204d470795fc791085fa3f15060d2e/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1c27c5f6102eac8c03e7595a00827a53b271ba40a53b59ff8709170e0855ea4a", size = 379034, upload-time = "2026-05-28T12:00:24.89Z" }, + { url = "https://files.pythonhosted.org/packages/be/cc/423999bbb8ae8dc93c77fc1d5e984ade5eb89d237d3bb884ccfa72ae2890/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c7fcf61d44cacecaf3aea542b0e053db77972a4573e7ceda16fb2b399161195", size = 490823, upload-time = "2026-05-28T12:00:26.676Z" }, + { url = "https://files.pythonhosted.org/packages/0f/aa/c671bf660f12e68d3c52ff86c7066ed1372df5a0f4f2ff584e419b8207e7/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c817a189d4ee14290420e5ff051e4dd6baa13f3edf84685071dee07a6d538ee", size = 388144, upload-time = "2026-05-28T12:00:28.577Z" }, + { url = "https://files.pythonhosted.org/packages/19/c8/d63bb75b68afe77b229e3021c6031bcaf01da5db5b0e69d0d10f9ba679a7/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21846aac0ed2e0589f38c12dc44e77bb64e494b771eadbcf169cba00566ba7ba", size = 371959, upload-time = "2026-05-28T12:00:30.304Z" }, + { url = "https://files.pythonhosted.org/packages/82/35/c51122014d8274ff37dc606d60049c3db7d83da02b5b282511e5a906a9a6/rpds_py-2026.5.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b317c87a13f769a4e787819bd508aaa5d69aa09b0880de9af6d3a8a54571cdec", size = 383558, upload-time = "2026-05-28T12:00:31.764Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f9/2790cb99c136a5363acdeacf5c27c56f3de0d4118a1f48fca83404c99c89/rpds_py-2026.5.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ce87129d9f2c14fa6c4a8601fb80eb4488c80d38a20cd13758ef11123e14995d", size = 402789, upload-time = "2026-05-28T12:00:33.247Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1b/e4fb584f8c75d35c38150ff6a332cda949e6f97acba1f4fd123b14ab56fe/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9cdddb6c1207d284d94fd1530adf57fbd797fe7c4b8704ba85f49414f2557e7d", size = 551405, upload-time = "2026-05-28T12:00:34.819Z" }, + { url = "https://files.pythonhosted.org/packages/d8/f7/a6731b4216cb3793ea1af5391da240f5683dacc0d13e034fe5fc3503f240/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:4e237e139f94d3c036fd28eb9f564c99055476ff4ff05cd42be55ce349b5aa02", size = 616975, upload-time = "2026-05-28T12:00:36.268Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/2e051a81d95d8e63f4b35a1c463a87e8766bc3d083c067c5dfb6bf220747/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ed0954b524873214369184a9c82b0eaa45a3fbb9a798cd95b17e0d98499e7ea0", size = 578701, upload-time = "2026-05-28T12:00:37.82Z" }, + { url = "https://files.pythonhosted.org/packages/65/56/b5f6fdb2083e32bca8a8993d89e70db114b4756c9e2c38421328126689d2/rpds_py-2026.5.1-cp314-cp314-win32.whl", hash = "sha256:2d88621d6a7d4dfa633d21abe90f280bb205274e16b1d1e61c6ad4640b2453b7", size = 209806, upload-time = "2026-05-28T12:00:39.492Z" }, + { url = "https://files.pythonhosted.org/packages/fb/80/65a5aa96c155e611d1ed844e4e1f57f3e36b021f396d9f8585d756e6b90d/rpds_py-2026.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:cef8ac28d26f4dda3533060c20fbf80a325458fa9fd23ea72a73cdfa8e978838", size = 225985, upload-time = "2026-05-28T12:00:40.94Z" }, + { url = "https://files.pythonhosted.org/packages/27/7c/ad185212e87b05f196daef92bc5f3caf07298eb47c295b5585c3dd3093ac/rpds_py-2026.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:eaaea962c68cdc68d4a533ba985ab8e9484277910bbfaa2ab3ef7732667bfed8", size = 221219, upload-time = "2026-05-28T12:00:43.15Z" }, + { url = "https://files.pythonhosted.org/packages/23/58/e14ae18759020334646b031e708ab4158d653a938822bfb7b95ef2e93aa3/rpds_py-2026.5.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:21942f52dbbd5f8758bf021213d28bd45c39e873e65e2407faf5f1846f5761ad", size = 352148, upload-time = "2026-05-28T12:00:44.638Z" }, + { url = "https://files.pythonhosted.org/packages/31/9b/5f4a1e2f960bca3ac5d052b139dd31eed97b259f9d909173821760d542e8/rpds_py-2026.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f414556f6e3958300ff941e40c9f97e3dc9774ddd1b3434c475d73dd354bbed3", size = 345196, upload-time = "2026-05-28T12:00:46.14Z" }, + { url = "https://files.pythonhosted.org/packages/1a/71/1d9574d6a2fa20ab60eaa55c7467f5aa20cbc770f341a05f09c0876f59e2/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef1013a8625c74043210190b246f5b1551e09757c1f356c6e4160ef96c5bc081", size = 374981, upload-time = "2026-05-28T12:00:47.531Z" }, + { url = "https://files.pythonhosted.org/packages/0c/9a/37e99f4915a80aa71670263c1267f7ae0af95f53a3f61e6c3bdc016d4515/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cc68e231a77a5f0d774ae278a1f8e55c0456501820847c1e4efb3829f3441df6", size = 379961, upload-time = "2026-05-28T12:00:49.216Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ff/6e73f74b89d2e0715e0fc86b7dde893f9a61ae2f9b256ff3bdfe41ac4e94/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9baffb505aff33acc69b422a19f77806680f3c8632227d79f48de8a810d1c2c5", size = 495965, upload-time = "2026-05-28T12:00:51.111Z" }, + { url = "https://files.pythonhosted.org/packages/ea/e0/425faba25f59d74d4638b267f7c7a80e8649d2ef4db10a19b0c4a71e6e6f/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8d2f912928d426e8cfa396f7f3f8d29a59e6689c86dcca3c420730c1096322b", size = 389526, upload-time = "2026-05-28T12:00:52.77Z" }, + { url = "https://files.pythonhosted.org/packages/c6/76/7a41960e3fddae47fab43a28684d5da981401dffd88253de0944148654cb/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90f628283be835db980c941767d41c9a27b5239e54ba0a9c1335247e82406964", size = 376190, upload-time = "2026-05-28T12:00:54.215Z" }, + { url = "https://files.pythonhosted.org/packages/27/60/5f38dc70824fc6951b51d35377e577a3a3a4c81a6769cc5a2de25ebe0ad1/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:1ebb2f0ab7e16132995a72de805170e0203df0c3dd22e1ef1cd1fdd90bd7a131", size = 383921, upload-time = "2026-05-28T12:00:55.673Z" }, + { url = "https://files.pythonhosted.org/packages/60/1a/d60a38caa1505f4b9483c3fbbde12c94e1079154f4f401a6da96f7e77621/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f3df3d16ded76f1f8c9cdebd0e1ea55fdf4c23b812de189814da7cf229c22a81", size = 404766, upload-time = "2026-05-28T12:00:57.518Z" }, + { url = "https://files.pythonhosted.org/packages/87/ff/602fd3f174d6425f0bce05ad0dfbec0e96b38d0f7d08a79af5aa20083885/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9af8905b8f854990e40d5206aa5ac58d9b0fe0b7f351ff2bb086c20f6c8c6a47", size = 551343, upload-time = "2026-05-28T12:00:58.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c1/1be13327acdbead3eca1fde03b6a34dbb011f1e864e217f0d32cc1779a7f/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:036a36a87fb1cd3b214d11c4b3c4f7d2ddad933625dca1c900b56a057c07740a", size = 618502, upload-time = "2026-05-28T12:01:00.656Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d7/afb49b49d7f2be8b7ba1a9f0977fa5168003437b93086726f066544e8351/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:62ae3853454fe9ef283a03c96c2d835d39e84b14643a9d62c82ef0fb87d702ca", size = 581916, upload-time = "2026-05-28T12:01:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/25/d1/dbef8c1f8a10f07beb62b5f054e20099fd9924b3ec001b8f0b6ac7813a85/rpds_py-2026.5.1-cp314-cp314t-win32.whl", hash = "sha256:6c3d771a46ec18b12af06ce36243a9a80b07a5d0515236332d90863ca8bb326a", size = 207855, upload-time = "2026-05-28T12:01:03.821Z" }, + { url = "https://files.pythonhosted.org/packages/2a/72/bfa4e61ab8e7dc1c8adf397e05e6cbdd4239357bd72b248d3de662f23915/rpds_py-2026.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c93c629be4636cf54337bd5f06c104d55e42ced54d681f6fe21ae510a65116f6", size = 225422, upload-time = "2026-05-28T12:01:05.194Z" }, + { url = "https://files.pythonhosted.org/packages/27/3a/7b5da92b640f67b6717ccafc83cdd06bfa7ff2395c3685c68922bb54d703/rpds_py-2026.5.1-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:3574b55c604b8f75dacb007136508bbc0db406e626301778096a133327e7f2fb", size = 349576, upload-time = "2026-05-28T12:01:06.722Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8a/2aafd7ad355a1bd48ca76e2262b74b15e6432b5a1efe150efd4d779cd55d/rpds_py-2026.5.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:94068eb3ae6d43f5a786b7db96a406a34e6d5c24489feef32fd6e8946ea7b291", size = 343640, upload-time = "2026-05-28T12:01:08.441Z" }, + { url = "https://files.pythonhosted.org/packages/f7/7d/6c9523c1abbe840a1b7fba3c516d48e1d3487cc80fea4366c4071cf56784/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a5b10e8ce894825f380a8f1b6444cf73c294dfea62afbb2d13e3a9e630cec1", size = 375322, upload-time = "2026-05-28T12:01:09.934Z" }, + { url = "https://files.pythonhosted.org/packages/5a/5d/0b7b03fb1dc509321f01de3149784ab773e34c8573022029af8076afcb9c/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fc09f82e63d4bcd58149572f857a431bae851dc747e313c3b5bdf7abb907fda8", size = 379066, upload-time = "2026-05-28T12:01:11.48Z" }, + { url = "https://files.pythonhosted.org/packages/d7/e2/8ef6012999ebf1cb1c22f876d9ce5e63d960fd4631d2af3202d3f480aa25/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e10464d17df3b582745c25cec695cb9558bca2cb6ddb631aee1787fc72c767b2", size = 494586, upload-time = "2026-05-28T12:01:13.051Z" }, + { url = "https://files.pythonhosted.org/packages/80/af/1eeb029bec67582c226b7809172207cd005073af4ebd906e65ff494f4983/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ba05adbf15d994c38ec0b7ab32e858e5110c21e9009a00a86545fd220f84e038", size = 388415, upload-time = "2026-05-28T12:01:14.631Z" }, + { url = "https://files.pythonhosted.org/packages/18/23/ffbe10711c4d766c1cab0557d6906c074f795814863c67b351355d29354a/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77c004fdc7b891967106f78ddfd7b076bfe6813c6139c6fff6aed3bcaa960b26", size = 372427, upload-time = "2026-05-28T12:01:16.153Z" }, + { url = "https://files.pythonhosted.org/packages/bd/3a/30ba4a6ad457e5b070c18d742a33fb77d8d922b565cc881f8a5313d63bfe/rpds_py-2026.5.1-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:83bcf894486c9d78dd290d3c0124ff6dd8875d3025e2090a8ec49fcc37c55fdd", size = 383615, upload-time = "2026-05-28T12:01:17.809Z" }, + { url = "https://files.pythonhosted.org/packages/d3/69/62e242b53ce39c0814bd24e1a6e6eba6c92be716277745f317f9540a2e7b/rpds_py-2026.5.1-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3df104083952a0e0c6f10de33e440eabe98fb6317d23e1a58c68f6df08d01b9", size = 402786, upload-time = "2026-05-28T12:01:19.419Z" }, + { url = "https://files.pythonhosted.org/packages/38/c1/a770b9c186928a1ed0f7e6d7ae50e7f3950ed23e3f9e366dbc8e38cb55de/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:980450826cf22e133c57e0835070bdd0dd3f73b9b708c3ce223def2cb9469e14", size = 551583, upload-time = "2026-05-28T12:01:21.013Z" }, + { url = "https://files.pythonhosted.org/packages/21/7c/68e8579b95375b70d2a963103c42e705856cdb98569258bd807f4423891c/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:205dde846f24332ab0c1188699a043b8d165b79bb84529ce272c45048ff6be01", size = 616941, upload-time = "2026-05-28T12:01:22.548Z" }, + { url = "https://files.pythonhosted.org/packages/70/a1/a6135aed5730ff03ab957182259987ac11e55fb392a28dc6f0592048a280/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:3966b82dd563176396df030f3dd52a6e54cb69b718e95e78bd555ed3d1e0185d", size = 578349, upload-time = "2026-05-28T12:01:24.118Z" }, + { url = "https://files.pythonhosted.org/packages/09/6e/f24201a76a84e6c49d0bdfdfcb735210e21701e9b21c5bfc0ba497dd62f6/rpds_py-2026.5.1-cp315-cp315-win32.whl", hash = "sha256:7818f8d0a415be74d2be3590b0a1c1f463a642f4d0217e7d10602dceef5b79aa", size = 209922, upload-time = "2026-05-28T12:01:25.522Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e4/966bc240bb0485fc265278f6de44d05834bf0b3618886e0b22e33d54c49a/rpds_py-2026.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:b3cc20c0d800af78fd0fac68086e28c1856cec51ea528bb81ea851aa40d39325", size = 226003, upload-time = "2026-05-28T12:01:27.062Z" }, + { url = "https://files.pythonhosted.org/packages/5c/5c/a15a59269cd5e74472734516c73795c15eccfc841b3d4b0228c3f53f19d0/rpds_py-2026.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:3609e9939a8a76cd904cf98a3f1f13b5dc7e150adeaee89e0ea09652ea213e16", size = 221245, upload-time = "2026-05-28T12:01:28.51Z" }, + { url = "https://files.pythonhosted.org/packages/e0/22/135ce03804e179a71ceb13be095deda4a279bc88f7a6b8fa161c5ad44e12/rpds_py-2026.5.1-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:5d333a7127d4b307601ac37792bee01bb95c867cbfacf21b6375b804d6bbd723", size = 352015, upload-time = "2026-05-28T12:01:30.214Z" }, + { url = "https://files.pythonhosted.org/packages/3b/5f/f1f6d2652eb9d848f6eb369d8db83a2da6249bb49ad2c2a48f45d54538d3/rpds_py-2026.5.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:b5f077b44a4f7808520f66dae234988d867deb9aed9be5da057ce9ba831b2a41", size = 345016, upload-time = "2026-05-28T12:01:31.656Z" }, + { url = "https://files.pythonhosted.org/packages/88/66/b74182775691ea2290c99e52ac8d5db844e56fbec90ce421f107658c8314/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d8f9b7b78c9538fc9e04e82ec0e888ff0c3cffcfad152c77e57cd09351a98a", size = 374775, upload-time = "2026-05-28T12:01:33.136Z" }, + { url = "https://files.pythonhosted.org/packages/ff/8f/15e5a61d9f0a43902d36561d4f07cae6ae9f4716be825159fd72717f33af/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e3a8ae58895ac107ed934a6bf51e5846f95c53b9b940c2c6d310838fd5846358", size = 380270, upload-time = "2026-05-28T12:01:34.574Z" }, + { url = "https://files.pythonhosted.org/packages/02/c3/f859b12763a80540cdf2af0f15b19904cf756a71d7bdd3f82ff3e5b1bbf9/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0957cf3c2b8632ec7aaebffebea8005b353cc2a237b6e2ae3c2cac0820704cfb", size = 495285, upload-time = "2026-05-28T12:01:36.127Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c7/ff27c2ac8411d30b03b1829fd88cae8dad1a4d0da48dd25e57c4038042e6/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c396c1304de421050b3681ea70f371874b54d41b0151e96109758144c231e30b", size = 389581, upload-time = "2026-05-28T12:01:37.635Z" }, + { url = "https://files.pythonhosted.org/packages/6e/67/fe92ee32a6cc05c77228a2f8b1762e7124f386ec20ff83d0757b762d58d0/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad1bff7f666b9598e573815affd666aac6a13a585dde336f843e33350c7fadc", size = 376041, upload-time = "2026-05-28T12:01:39.307Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/b4d6685c27aba55bd82f25b278be8237038117d05f9659a6213ad3408130/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:656a042550878f12d45752452d47094b7cfe5ad1e9d7b87b5a22ad3ae5ff8015", size = 383946, upload-time = "2026-05-28T12:01:41.043Z" }, + { url = "https://files.pythonhosted.org/packages/bd/79/2c1d832a53c8e0f8e98fc970ec257b950fecd4f62be2ab7182b500a0cbc8/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c4bd4f70294737b5206a3e8e30ccadbf8a60301831c8ea23eec5dbeea1ecfa", size = 405526, upload-time = "2026-05-28T12:01:43.032Z" }, + { url = "https://files.pythonhosted.org/packages/78/c4/c98117b03c6a8581ab2c2dfccfe9a5ad82bd8128a3c28b46a6ad2d97c393/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:43bca78665423cabae77146f2fe7ce55272b6c8d55d82cca83effd42c7e13972", size = 551165, upload-time = "2026-05-28T12:01:44.648Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c1/bc479ca069200af730881b1bd525e3114b2b391a351509fcb1b772f28086/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:42d0f20e85e549c870749d0e247f0c10d318a45b7e9676d575d2dcb04a1b2e66", size = 618778, upload-time = "2026-05-28T12:01:46.337Z" }, + { url = "https://files.pythonhosted.org/packages/77/65/38ab2f90df44c2febfb63cc10ced40763d9b4bc94d173e734528663fe7f5/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:b1be5c35683684d5331b93600c210e8367c254683d8a6df6bd21bd2da3a334fb", size = 581839, upload-time = "2026-05-28T12:01:48.109Z" }, + { url = "https://files.pythonhosted.org/packages/15/2d/ce1f605fe036aadd460e5822e578c6c7ec3a860936cca37d6e0f299daa77/rpds_py-2026.5.1-cp315-cp315t-win32.whl", hash = "sha256:75808f6c38ce7749bb68cc2770161aae5045e6c6f6781a9782e74b93304399df", size = 207866, upload-time = "2026-05-28T12:01:49.648Z" }, + { url = "https://files.pythonhosted.org/packages/79/cb/966040123eb102371559746908ef2c9471f4d43e17ec9a645a2258dab64b/rpds_py-2026.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:90bd6630002a1c7f09e7843dd79f0d24f3d2897cc25a753480917865d14f15b3", size = 225441, upload-time = "2026-05-28T12:01:51.408Z" }, + { url = "https://files.pythonhosted.org/packages/42/56/3fe0fb34820ff667be791b3a3c22b85e8bcba54e9c832f47438c191fa7be/rpds_py-2026.5.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:edf2765d84e42447f112ad877af8fe1db0089aaec5b28e88d6eab45e7fe99cea", size = 357151, upload-time = "2026-05-28T12:01:53.43Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f2/3eb9ccdb9f143b8c9b003978898cb497f942a324c077401e6b8834238e63/rpds_py-2026.5.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ad3773236e95f7f33991eb125224b7da66f206504d032a253a02da7e134519fb", size = 350195, upload-time = "2026-05-28T12:01:54.901Z" }, + { url = "https://files.pythonhosted.org/packages/a7/24/dbda232bc4f3ed732120692ab0d2c8402cb020516556d8bee622dcef2413/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a04df86b3f0fade39ec8fd0e0aab089b1da9fbd2b48df778a57ef96f5e7d38df", size = 381850, upload-time = "2026-05-28T12:01:56.601Z" }, + { url = "https://files.pythonhosted.org/packages/40/30/32e769839a358f78810c234f160f2cc21d1e4e47e1c0e0e0d535be5a0219/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6142dbd80c4df62a5d899f0d616d417f84e0bc8d32526c8e5589019d75d028a7", size = 387899, upload-time = "2026-05-28T12:01:58.212Z" }, + { url = "https://files.pythonhosted.org/packages/ab/86/ec84d243aadb3b34b71dd26a010d0930b2d284ff5fc9a69fec53810ee6fd/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0b35217adefe87f2fe4db7e9766cabe84744bfe9616d9667be18988928c7f2dc", size = 501618, upload-time = "2026-05-28T12:01:59.888Z" }, + { url = "https://files.pythonhosted.org/packages/74/25/b60e52686bbff777a64f9e4f4d3dd57980dc846913777177a2c92e4937aa/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b95d5e11fc712b752081183a55a244c03cd00570489edd7014d8899f8ceb8162", size = 394003, upload-time = "2026-05-28T12:02:01.482Z" }, + { url = "https://files.pythonhosted.org/packages/9b/c7/b3a6a588cc2219510ef3f42e207483a93950bedd1e3a0fd4015c95cff9e5/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141c9498daf2ace9eda35d2b0e376f9ea8b058d84f2aef4f96fccfd449a2f251", size = 379778, upload-time = "2026-05-28T12:02:03.197Z" }, + { url = "https://files.pythonhosted.org/packages/31/00/c7dba3fc8a3da8cb3f6db1eb3386be4d79c2e97c6890d20eb9ac66ae8c43/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:6f249f8b860a200ad35193af961183ebe9132710484e6f6ce0cf89fd83c63a9a", size = 392359, upload-time = "2026-05-28T12:02:04.817Z" }, + { url = "https://files.pythonhosted.org/packages/93/dd/472ba494c70753f93745992c99855bee0636daf74e6984e5e003f150316f/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e4abbf391a70be864920858bf360f4fb380577c9a0f732438a1996726e2c195b", size = 412820, upload-time = "2026-05-28T12:02:06.401Z" }, + { url = "https://files.pythonhosted.org/packages/1d/6f/93831a3bfe789542ed0c1d0d74b78b440f055d6dc3ea4640eba2d95e6e23/rpds_py-2026.5.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:c74005a7bb87752acf351c93897ec63ad77a07a0da7ecad9c050e32e7286ba34", size = 557243, upload-time = "2026-05-28T12:02:08.013Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ff/0b3d604614ffc77522c6b288fdbce68957eb583da1002aa65ba38ac0ee40/rpds_py-2026.5.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:8213afbe8a3a906fb9acb2014423fe3359ee783d0bf90995f70623a3217bfa6c", size = 623541, upload-time = "2026-05-28T12:02:09.661Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ea/e7b0251441da9adfeaebcf29601d10f2a1455fcf0772fae9e7e19032bd96/rpds_py-2026.5.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:8c43a8a973270fd173bf48cdf80bbe66312421cba68d40845034f174f2389049", size = 586326, upload-time = "2026-05-28T12:02:11.47Z" }, ] [[package]] name = "ruff" -version = "0.15.14" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dc/8a/8bce2894573e9dae6ff4d77fe34ad727d79b9e6238ad288c5638990d90f6/ruff-0.15.14.tar.gz", hash = "sha256:48e866b165be4a9bdbf310f7d3c9a07edef2fe8cd63ffeb4e00bb590506ebf9f", size = 4700910, upload-time = "2026-05-21T14:34:55.177Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/c8/74a92c6ff9fcfb4f1f947126d3ebee8389276e161ecc85de5bda7cda51bd/ruff-0.15.14-py3-none-linux_armv6l.whl", hash = "sha256:8dd2db9416e487c8d4b01fa7056bb02c4d05969d4f8d17a08c229c2f4ff3c108", size = 10739177, upload-time = "2026-05-21T14:34:37.332Z" }, - { url = "https://files.pythonhosted.org/packages/45/91/254a35c20acc38a7223c9d2d594af12e794432464f2cdeb52af1dc4a892d/ruff-0.15.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:be4ff55af755bd71a00ab3dc6bd7ffc467bd76e0df6881e286c2e3d23e8fb43b", size = 11144969, upload-time = "2026-05-21T14:34:43.978Z" }, - { url = "https://files.pythonhosted.org/packages/56/9e/d13e40f83b8d0a94430e6778ce1d94a43b38cf2efe63278bdd2b4c65abbf/ruff-0.15.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:48d5909d7d06276ce7dde6d32bfa4b0d4cb2651145cd8ee4b440722cbc77832f", size = 10478207, upload-time = "2026-05-21T14:34:48.378Z" }, - { url = "https://files.pythonhosted.org/packages/8d/f1/b15a7839fa4f332f8acec78e20564f26bb2d866e3d21710b877fd0263000/ruff-0.15.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca8cbfa94c4f90984a67561978602746d4cd27103568f745fa90eee3f0d4107d", size = 10818459, upload-time = "2026-05-21T14:34:22.318Z" }, - { url = "https://files.pythonhosted.org/packages/45/33/53d651177f84f94b400a0e27f8824eeada3dddc9d5ee8aeb048f4352a520/ruff-0.15.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a6bbc0333f1ab053423bcbf6226477d266ca7cec7738c4c8e3f55647803f3c4", size = 10541800, upload-time = "2026-05-21T14:34:20.209Z" }, - { url = "https://files.pythonhosted.org/packages/b8/a6/868f87e0bf9786ed24b5d0d0ad8676b8a94fd1912f42cddf9cfc7857818a/ruff-0.15.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a24a4f7605d7003a6674d4387651effd939dead3fddd0f36561eb77a9a2e542", size = 11342149, upload-time = "2026-05-21T14:34:46.365Z" }, - { url = "https://files.pythonhosted.org/packages/a7/8b/38cd5c19faffdcc05a408d2b78edccc69492ab9720eadb49ea15ef80d768/ruff-0.15.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:049b5326e53ed80978f2fc041a280603f69dd6b0c95464342a2bb4572d9d9e2f", size = 12212563, upload-time = "2026-05-21T14:34:28.579Z" }, - { url = "https://files.pythonhosted.org/packages/3e/4d/a3c5b874a556d5731e3e657aaf04311bb76f0a5c3ec220ed43051be6b64b/ruff-0.15.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4ed42e6696c8dfa5f06728e6441993901f548eb92d73bc472cb5a38d1395fbf", size = 11493299, upload-time = "2026-05-21T14:34:41.836Z" }, - { url = "https://files.pythonhosted.org/packages/1e/c0/56472c251d09858a53e51efbd485b09e1995d8731668b76d52e5dd6ee0f1/ruff-0.15.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:715c543cf450c4888251f91c52f1942a800541d9bddd7ac060aa4e6b77ae7cba", size = 11455931, upload-time = "2026-05-21T14:34:57.276Z" }, - { url = "https://files.pythonhosted.org/packages/2c/4a/e2e7b4d8dbf233d4eace59c75bc3435fa6d8bd3bae82d351d4e4300c0fd1/ruff-0.15.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ebab6013ec887d439d8b7593737a0a4ffb06d45d209d4e4bf2e92813082d3f", size = 11400794, upload-time = "2026-05-21T14:34:39.773Z" }, - { url = "https://files.pythonhosted.org/packages/97/c7/83c0539fe34c3e09136204d1e75d6052492364e0b3cb05e9465423f567d7/ruff-0.15.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:49072d36abdbe97a8dd7f480afe9c675699c0c495d4c84076e2c1203c4550581", size = 10804759, upload-time = "2026-05-21T14:34:31.045Z" }, - { url = "https://files.pythonhosted.org/packages/86/a6/18f2bfc095a2ab4a78745644e428205532ce6653a5d0fa8501572891534d/ruff-0.15.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:958522aee105068640c2c2ceae08f413ae44d922f52a1374ac13d6a96032fc93", size = 10539517, upload-time = "2026-05-21T14:34:53.064Z" }, - { url = "https://files.pythonhosted.org/packages/54/3a/5a8b3b69c654d4e4bf1d246ac5b49cbcdac6eaab6905925f8915f31e3b80/ruff-0.15.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f3707da619a143a2e8830e2abab8224478d69ace2d28cb6c20543ae97c36bf61", size = 11065169, upload-time = "2026-05-21T14:34:24.484Z" }, - { url = "https://files.pythonhosted.org/packages/ed/c5/8864e4e7925b836ea354b31d57641ec03830564e281a8b6f061f8c3e0ec1/ruff-0.15.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bb01d645694e3ec0102105d07ef2d53703970407d59c04e59d3ba0b7a1d53553", size = 11560214, upload-time = "2026-05-21T14:34:50.975Z" }, - { url = "https://files.pythonhosted.org/packages/36/38/012bf76752e1f89ed50b77b99532d90f3a3e287bc7918e1fc0948ac866ac/ruff-0.15.14-py3-none-win32.whl", hash = "sha256:6d0c1ad2a0ab718d39b6d8fd2217981ce4d625cd96a720095f798fb47d8b13e6", size = 10805548, upload-time = "2026-05-21T14:34:33.453Z" }, - { url = "https://files.pythonhosted.org/packages/d1/b7/4ea2c170f10ad760fff2a5250beb18897719dc8b52b53a24cddbb9dd3f19/ruff-0.15.14-py3-none-win_amd64.whl", hash = "sha256:802342981e056db3851a7836e5b070f8f15f67d4a685ae2a6160939d364b2902", size = 11939523, upload-time = "2026-05-21T14:34:18.077Z" }, - { url = "https://files.pythonhosted.org/packages/62/d5/bc97ff895ec35cf3925d4bd60f3b39d822f377a446906ec9bcc87405e59b/ruff-0.15.14-py3-none-win_arm64.whl", hash = "sha256:ff47b90a9ef6a40c9e2f3b479c1fb78531adf055b94c1eba0a7ba04b31951826", size = 11208607, upload-time = "2026-05-21T14:34:26.525Z" }, +version = "0.15.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/84/6f/a76f7d96e5c962f5b69cee865e49c15c1116897c01990faa8a57edb62e7f/ruff-0.15.15.tar.gz", hash = "sha256:b8dff018130b46d8e5bf0f926ef6b60cf871d6d5ae45fc9334e09632daa741d6", size = 4706985, upload-time = "2026-05-28T14:16:57.784Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/9d/3a45c05b8ab04b4705989de70a79008e27c8003296a0feaee9edc18dd7e9/ruff-0.15.15-py3-none-linux_armv6l.whl", hash = "sha256:cf93e5388f412e1b108b1f8b34a6e036b70fe8aff89393befad96fe48670311b", size = 10710652, upload-time = "2026-05-28T14:16:06.701Z" }, + { url = "https://files.pythonhosted.org/packages/05/66/da974431624bf3b49f6ee1f9543c02d929ff1cba78b0d5a79c38cf21f744/ruff-0.15.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac5a646d1f6a7dadd5d50842dae2c1f9862ac887ef5d1b1375e02def791fde6e", size = 11096615, upload-time = "2026-05-28T14:16:23.313Z" }, + { url = "https://files.pythonhosted.org/packages/8c/09/7443452e5d290230a712103f2fdceeef7184f3ec99a2bd01c8be78aaceb5/ruff-0.15.15-py3-none-macosx_11_0_arm64.whl", hash = "sha256:77d955a431430c66f72dd94e379ad38a16daea3d25094872ac4edf9e797be530", size = 10436683, upload-time = "2026-05-28T14:16:40.974Z" }, + { url = "https://files.pythonhosted.org/packages/53/01/d330c26a57fa4f3943a14424904027428315b700fe4d14a84bb123a649e5/ruff-0.15.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7614ee79c69788cf6cedd568069ade9cecc22a1ad20494efe8d0c9ebb4b622d4", size = 10769064, upload-time = "2026-05-28T14:16:28.905Z" }, + { url = "https://files.pythonhosted.org/packages/1d/85/cc8770f8bdff541b1da8392d1634141fe4a0e3f4ee596605959b7906c27f/ruff-0.15.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3cdb1679e06a1f6b47bc384714ae96f6e2fb65ca441eb78c43d2ca554176ce1f", size = 10511987, upload-time = "2026-05-28T14:16:43.732Z" }, + { url = "https://files.pythonhosted.org/packages/7c/29/8c190c1472b63013583ba391f3342036e02010544c1270455ed8e519bdf3/ruff-0.15.15-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2728b93d7b23a603ea2c0ac6eb73d760bd38ec9de35f35fb41e18f7a3fee7622", size = 11275100, upload-time = "2026-05-28T14:16:55.244Z" }, + { url = "https://files.pythonhosted.org/packages/9f/6b/7e145ce2cc8e63d6834eca03d83a0e18d121def5c69f91b4cf4011ed4879/ruff-0.15.15-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be582fcc0db438902c7792b08d6ddf6c9b9e21addaa10092c2c741cfb09e5a45", size = 12176903, upload-time = "2026-05-28T14:16:14.368Z" }, + { url = "https://files.pythonhosted.org/packages/80/a3/d5974637f68e451f7fadf015cf3101d1cd7d8ba5027cffe0b9e3826ebe6b/ruff-0.15.15-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7aa77465b8ecaf1a27bea098d696f7fed5e1eccbd10b321b682d6de586ae5627", size = 11404550, upload-time = "2026-05-28T14:16:20.138Z" }, + { url = "https://files.pythonhosted.org/packages/fe/1c/e6e5e568f22be4fb05d6244234aba384c06b451252453b821e1a529263cf/ruff-0.15.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48decfa11d740de4889de623be1463308346312f2409a56e24aa280c86162dc4", size = 11382027, upload-time = "2026-05-28T14:16:46.615Z" }, + { url = "https://files.pythonhosted.org/packages/1d/01/170921b49fcd2e8858825593f91cf7146c3e40a5c3e6df763e4bb0484dde/ruff-0.15.15-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a5015088452ca0081387063649ec67f06d3d1d6b8b936a1f836b5e9657ecd48c", size = 11366041, upload-time = "2026-05-28T14:16:26.247Z" }, + { url = "https://files.pythonhosted.org/packages/87/54/a7bad711d7de93254e15e06a4c375b89a03d18de45d3e5dcc86a4472fb1a/ruff-0.15.15-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f5294aab6356c81600fcdea3a62bb1b924dfd5e91767c12318d3f68f86af57cd", size = 10741795, upload-time = "2026-05-28T14:16:17.11Z" }, + { url = "https://files.pythonhosted.org/packages/c9/31/38c075963668f8b41c6914ee0f6f318727fbe30ab9145cb29e6df464c5fa/ruff-0.15.15-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:db5bd4d802415cca656dc1616070b725952d6ae95eb5d4831e49fbd94a38f75f", size = 10511117, upload-time = "2026-05-28T14:16:31.767Z" }, + { url = "https://files.pythonhosted.org/packages/9d/96/6ff689e1f7e375d1d97075eca022f74c2bab59554a432fe4d2e6f091986a/ruff-0.15.15-py3-none-musllinux_1_2_i686.whl", hash = "sha256:587a6278ed42059191c1a466e490bd7930fb50bd2e255398bc29616c895a61cb", size = 10994867, upload-time = "2026-05-28T14:16:35.149Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c2/5dce0ab9f92a8d534fa62b9bf9caca3eddb8c1a81b616f5e195ada4f0d6e/ruff-0.15.15-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:df0c1c084f5f4be9812f61518a45c440d3c30d69ce4bf6c5270e66d38338f02a", size = 11482101, upload-time = "2026-05-28T14:16:49.598Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c0/1003b60edd697c649faf61f1a34094b1abb38fb3d1181e3f895781250a08/ruff-0.15.15-py3-none-win32.whl", hash = "sha256:29428ea79694afbe756d45fd59b36f22b6b020dc0443cf7de0173046236964b9", size = 10716774, upload-time = "2026-05-28T14:16:52.337Z" }, + { url = "https://files.pythonhosted.org/packages/02/a8/1269eddd6945a06c23f055ef7848886e37cf9d6a8bebb386a3115f01470c/ruff-0.15.15-py3-none-win_amd64.whl", hash = "sha256:8df0323902e15e24bc4bf246da830573d3cf3352bd0b9a164eab335d111ff4a4", size = 11868463, upload-time = "2026-05-28T14:16:11.333Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b2/920464c907b191e37469d477a1aa8bc048b8f36c4c1610dfa4ab87b39e18/ruff-0.15.15-py3-none-win_arm64.whl", hash = "sha256:3c8ceca6792f38196b8f589bc92eccd03eef286602da92e5dc05cc42ef6441b7", size = 11138498, upload-time = "2026-05-28T14:16:38.425Z" }, ] [[package]] @@ -3192,7 +3221,7 @@ dependencies = [ { name = "pillow" }, { name = "scipy" }, { name = "tifffile", version = "2026.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "tifffile", version = "2026.5.15", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "tifffile", version = "2026.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a1/b4/2528bb43c67d48053a7a649a9666432dc307d66ba02e3a6d5c40f46655df/scikit_image-0.26.0.tar.gz", hash = "sha256:f5f970ab04efad85c24714321fcc91613fcb64ef2a892a13167df2f3e59199fa", size = 22729739, upload-time = "2025-12-20T17:12:21.824Z" } wheels = [ @@ -3488,7 +3517,7 @@ wheels = [ [[package]] name = "tifffile" -version = "2026.5.15" +version = "2026.6.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", @@ -3497,9 +3526,9 @@ resolution-markers = [ dependencies = [ { name = "numpy", marker = "python_full_version >= '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/40/66/0aef917d525767a40edebe088f8ed6a4417e6eb489c58f6805bfa872636b/tifffile-2026.5.15.tar.gz", hash = "sha256:ee4f3e07ee0d8ff4745a8c735ac2b72caa3173c7d6059b00fdc3ff492a0b635b", size = 429998, upload-time = "2026-05-15T20:04:55.896Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/38/5e2ecef5af2f4fd4a89bb8d6240de9458bab4d51a4cbd97aeb3a0cd618e2/tifffile-2026.6.1.tar.gz", hash = "sha256:626c892c0e899d959b9438e7c0e1491dc154a7fead1f1f37a991724a50eceba9", size = 429694, upload-time = "2026-05-31T23:57:12.165Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/6e/7d8850ff112f8f80d394ca45e89b975a3a43559d47af3137b767669b3294/tifffile-2026.5.15-py3-none-any.whl", hash = "sha256:6715515a53cabc0cefc5c9f13a0ae2c250e63e2ca784ce02d0b6c333810c2a17", size = 266665, upload-time = "2026-05-15T20:04:54.227Z" }, + { url = "https://files.pythonhosted.org/packages/81/59/208f71d70ddc6184f79b8c6d87d46eb7d7b12c19186a817dec9c9c3f3693/tifffile-2026.6.1-py3-none-any.whl", hash = "sha256:0d7382d2769b855b81ce358528e2b40c16d48aa39031746efa81215205332a8d", size = 267108, upload-time = "2026-05-31T23:57:10.597Z" }, ] [[package]] @@ -3687,19 +3716,19 @@ wheels = [ [[package]] name = "tornado" -version = "6.5.5" +version = "6.5.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/f1/3173dfa4a18db4a9b03e5d55325559dab51ee653763bb8745a75af491286/tornado-6.5.5.tar.gz", hash = "sha256:192b8f3ea91bd7f1f50c06955416ed76c6b72f96779b962f07f911b91e8d30e9", size = 516006, upload-time = "2026-03-10T21:31:02.067Z" } +sdist = { url = "https://files.pythonhosted.org/packages/50/57/6d7303a77ae439d9189108f76c0c4fd89ee5e2cc8387bffb55232565c4ed/tornado-6.5.6.tar.gz", hash = "sha256:9a365179fe8ff6b8766f602c0f67c185d778193e9bdd828b19f0b6ed7764177d", size = 518139, upload-time = "2026-05-27T15:35:54.646Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/59/8c/77f5097695f4dd8255ecbd08b2a1ed8ba8b953d337804dd7080f199e12bf/tornado-6.5.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:487dc9cc380e29f58c7ab88f9e27cdeef04b2140862e5076a66fb6bb68bb1bfa", size = 445983, upload-time = "2026-03-10T21:30:44.28Z" }, - { url = "https://files.pythonhosted.org/packages/ab/5e/7625b76cd10f98f1516c36ce0346de62061156352353ef2da44e5c21523c/tornado-6.5.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:65a7f1d46d4bb41df1ac99f5fcb685fb25c7e61613742d5108b010975a9a6521", size = 444246, upload-time = "2026-03-10T21:30:46.571Z" }, - { url = "https://files.pythonhosted.org/packages/b2/04/7b5705d5b3c0fab088f434f9c83edac1573830ca49ccf29fb83bf7178eec/tornado-6.5.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e74c92e8e65086b338fd56333fb9a68b9f6f2fe7ad532645a290a464bcf46be5", size = 447229, upload-time = "2026-03-10T21:30:48.273Z" }, - { url = "https://files.pythonhosted.org/packages/34/01/74e034a30ef59afb4097ef8659515e96a39d910b712a89af76f5e4e1f93c/tornado-6.5.5-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:435319e9e340276428bbdb4e7fa732c2d399386d1de5686cb331ec8eee754f07", size = 448192, upload-time = "2026-03-10T21:30:51.22Z" }, - { url = "https://files.pythonhosted.org/packages/be/00/fe9e02c5a96429fce1a1d15a517f5d8444f9c412e0bb9eadfbe3b0fc55bf/tornado-6.5.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3f54aa540bdbfee7b9eb268ead60e7d199de5021facd276819c193c0fb28ea4e", size = 448039, upload-time = "2026-03-10T21:30:53.52Z" }, - { url = "https://files.pythonhosted.org/packages/82/9e/656ee4cec0398b1d18d0f1eb6372c41c6b889722641d84948351ae19556d/tornado-6.5.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:36abed1754faeb80fbd6e64db2758091e1320f6bba74a4cf8c09cd18ccce8aca", size = 447445, upload-time = "2026-03-10T21:30:55.541Z" }, - { url = "https://files.pythonhosted.org/packages/5a/76/4921c00511f88af86a33de770d64141170f1cfd9c00311aea689949e274e/tornado-6.5.5-cp39-abi3-win32.whl", hash = "sha256:dd3eafaaeec1c7f2f8fdcd5f964e8907ad788fe8a5a32c4426fbbdda621223b7", size = 448582, upload-time = "2026-03-10T21:30:57.142Z" }, - { url = "https://files.pythonhosted.org/packages/2c/23/f6c6112a04d28eed765e374435fb1a9198f73e1ec4b4024184f21faeb1ad/tornado-6.5.5-cp39-abi3-win_amd64.whl", hash = "sha256:6443a794ba961a9f619b1ae926a2e900ac20c34483eea67be4ed8f1e58d3ef7b", size = 448990, upload-time = "2026-03-10T21:30:58.857Z" }, - { url = "https://files.pythonhosted.org/packages/b7/c8/876602cbc96469911f0939f703453c1157b0c826ecb05bdd32e023397d4e/tornado-6.5.5-cp39-abi3-win_arm64.whl", hash = "sha256:2c9a876e094109333f888539ddb2de4361743e5d21eece20688e3e351e4990a6", size = 448016, upload-time = "2026-03-10T21:31:00.43Z" }, + { url = "https://files.pythonhosted.org/packages/1b/0d/b4f481e18c5a51864e6d12b9a05ecf72919696680b747c958c3fc1f4fbae/tornado-6.5.6-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:65fcfaafb079435c2c19dc9e07c0f1cf0fa9051759ed0a7d0a3ba7ea7f64919c", size = 447737, upload-time = "2026-05-27T15:35:38.122Z" }, + { url = "https://files.pythonhosted.org/packages/9e/9c/5430c39fcab1144d35860f457b15e9c08b4bc7ac86764354204e983d6183/tornado-6.5.6-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:38bc01b4acacded2de63ae78023548e41ebe6fbed3ec05a796d7ae3ad893887e", size = 445899, upload-time = "2026-05-27T15:35:40.519Z" }, + { url = "https://files.pythonhosted.org/packages/8b/79/fa7e14a2f939c807a8d30619b4eb604eab219601b78792516ebe22d40cf9/tornado-6.5.6-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b942e6a137fda31ff54bf8e6e2c8d1c37f1f50583f3ed53fb840b53b9601d104", size = 448964, upload-time = "2026-05-27T15:35:42.106Z" }, + { url = "https://files.pythonhosted.org/packages/a7/71/bd67d5f5199f937dafe03a49a37989f60f600ff6fef34c79412a829d97bd/tornado-6.5.6-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8666946e70171b8c3f1fc9b7876fac492e84822c4c7f3746f4e8f8bc9ac92a79", size = 449935, upload-time = "2026-05-27T15:35:43.906Z" }, + { url = "https://files.pythonhosted.org/packages/cc/a4/c24388c9cf5b3c3a513b56a158af9f23092c9a2810d789e294310797df21/tornado-6.5.6-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1c34cfab7ad6d104f052f55de06d39bbafc5885cfeb4da688803308dbcfa90b7", size = 449767, upload-time = "2026-05-27T15:35:45.793Z" }, + { url = "https://files.pythonhosted.org/packages/a5/eb/6a07ad550c3f7b37244bd0becdf293ec3d3e961783d8b720a97df50de1b2/tornado-6.5.6-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:385f35e4e22fb52551dfcda4cdc8c30c61c2c001aef5ddad99cdfe116952efd3", size = 449174, upload-time = "2026-05-27T15:35:47.485Z" }, + { url = "https://files.pythonhosted.org/packages/bb/84/3469e098dccdb6763130e06aacd786bb4363fca7b590a55c101ddf34ed30/tornado-6.5.6-cp39-abi3-win32.whl", hash = "sha256:db475f1b67b2809b10bb16264829087724ca8d24fe4ed47f7b8675cae453ef86", size = 450230, upload-time = "2026-05-27T15:35:49.322Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3c/273a04e0b9dd9016f1685cca0c1c8795a71ac88a34a8c889a0b443483226/tornado-6.5.6-cp39-abi3-win_amd64.whl", hash = "sha256:6739bf1e8eb09230f1280ddbd3236f0309db70f2c551a8dbc40f62babdf82f79", size = 450667, upload-time = "2026-05-27T15:35:51.194Z" }, + { url = "https://files.pythonhosted.org/packages/02/98/0cffe22a224f60c5fb1e3aa0b76f9da2e1ca78b0e9545e3d077c68ce60a7/tornado-6.5.6-cp39-abi3-win_arm64.whl", hash = "sha256:2543597b24a695d72338a9a77818362d72387c03ae173f1f169eadc5c91466ac", size = 449690, upload-time = "2026-05-27T15:35:52.902Z" }, ] [[package]] @@ -3780,7 +3809,7 @@ wheels = [ [[package]] name = "virtualenv" -version = "21.3.3" +version = "21.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -3788,9 +3817,9 @@ dependencies = [ { name = "platformdirs" }, { name = "python-discovery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/15/ba/1f6e8c957e4932be060dcdc482d339c12e0216351478add3645cdaa53c05/virtualenv-21.3.3.tar.gz", hash = "sha256:f5bda277e553b1c2b3c1a8debfc30496e1288cc93ce6b7b71b3280047e317328", size = 7613784, upload-time = "2026-05-13T18:01:30.19Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/0d/4e93c8e6d1001a75763f87d8f5ecda8ebc7f4aa2153dddfaf4ae8892821a/virtualenv-21.4.2.tar.gz", hash = "sha256:38e6ee0a555615c0ea9da2ac7e9998fe8dc3b911dd33ad8eaad2020957653b0c", size = 7613326, upload-time = "2026-05-31T17:01:22.827Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/34/a9dbe051de88a63eb7408ea66630bac38e72f7f6077d4be58737106860d9/virtualenv-21.3.3-py3-none-any.whl", hash = "sha256:7d5987d8369e098e41406efb780a3d4ca79280097293899e351a6407ee153ab3", size = 7594554, upload-time = "2026-05-13T18:01:27.815Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c4/557dc082be035381b85fdb2b74e21d3d21b57750b74f2b47a32f3a639ff9/virtualenv-21.4.2-py3-none-any.whl", hash = "sha256:854210ca524a1a4d0d744734f4acbc721c3ffe163b85bbf5d56d14d5ae2f0fae", size = 7594079, upload-time = "2026-05-31T17:01:20.735Z" }, ] [[package]] From 15f22b993fee04610540b06dc32de688f118a6a8 Mon Sep 17 00:00:00 2001 From: arthurmccray Date: Mon, 1 Jun 2026 12:00:41 -0700 Subject: [PATCH 321/335] fixing OptimizerParamsType refactor, and changing SchedulerParamsType to match --- src/quantem/core/ml/optimizer_mixin.py | 14 ++++++------- .../diffractive_imaging/dataset_models.py | 2 +- .../diffractive_imaging/object_models.py | 12 +++++++---- .../diffractive_imaging/probe_models.py | 10 +++++++--- .../diffractive_imaging/ptychography_opt.py | 20 +++++++++---------- src/quantem/tomography/object_models.py | 2 +- src/quantem/tomography/tomography_opt.py | 18 ++++++++++------- 7 files changed, 45 insertions(+), 33 deletions(-) diff --git a/src/quantem/core/ml/optimizer_mixin.py b/src/quantem/core/ml/optimizer_mixin.py index f79d7661..ece31dff 100644 --- a/src/quantem/core/ml/optimizer_mixin.py +++ b/src/quantem/core/ml/optimizer_mixin.py @@ -514,7 +514,7 @@ def parse_dict(cls, d: dict): raise ValueError(f"Unknown scheduler type: {name}") -SchedulerType = ( +SchedulerParamsType = ( SchedulerParams.Plateau | SchedulerParams.Exponential | SchedulerParams.Cyclic @@ -540,7 +540,7 @@ def __init__(self): self._optimizer_params: dict[str, OptimizerParamsType] = { self.DEFAULT_OPTIMIZER_KEY: OptimizerParams.NoneOptimizer() } - self._scheduler_params: SchedulerType = SchedulerParams.NoneScheduler() + self._scheduler_params: SchedulerParamsType = SchedulerParams.NoneScheduler() # Don't call super().__init__() in mixin classes to avoid MRO issues @property @@ -587,17 +587,17 @@ def _is_single_optimizer_dict(d: dict) -> bool: return "type" in d or "name" in d @property - def scheduler_params(self) -> SchedulerType: + def scheduler_params(self) -> SchedulerParamsType: """Get the scheduler parameters.""" return self._scheduler_params @scheduler_params.setter - def scheduler_params(self, params: SchedulerType | dict): + def scheduler_params(self, params: SchedulerParamsType | dict): """Set the scheduler parameters.""" if isinstance(params, dict): params = SchedulerParams.parse_dict(d=params) - if not isinstance(params, SchedulerType): - raise TypeError(f"scheduler parameters must be a SchedulerType, got {type(params)}") + if not isinstance(params, SchedulerParamsType): + raise TypeError(f"scheduler parameters must be a SchedulerParamsType, got {type(params)}") self._scheduler_params = params @abstractmethod @@ -688,7 +688,7 @@ def _build_optimizer(self, opt_params, param_groups) -> "torch.optim.Optimizer": raise NotImplementedError(f"Unknown optimizer type: {opt_params}") def set_scheduler( - self, scheduler_params: SchedulerType | dict | None = None, num_iter: int | None = None + self, scheduler_params: SchedulerParamsType | dict | None = None, num_iter: int | None = None ) -> None: """Set the scheduler for this model.""" if scheduler_params is not None: diff --git a/src/quantem/diffractive_imaging/dataset_models.py b/src/quantem/diffractive_imaging/dataset_models.py index f663f3eb..751c9245 100644 --- a/src/quantem/diffractive_imaging/dataset_models.py +++ b/src/quantem/diffractive_imaging/dataset_models.py @@ -113,7 +113,7 @@ def get_optimization_parameters(self) -> "dict[str, list[torch.Tensor]]": def _normalize_optimizer_params(self, params): """Broadcast a single optimizer spec to the learnable descan/scan_position groups. - A single ``OptimizerType`` / single-optimizer dict (normalized to the ``"default"`` key) + A single ``OptimizerParamsType`` / single-optimizer dict (normalized to the ``"default"`` key) is fanned out to whichever groups are currently learnable, so the common single-LR caller keeps working. An explicit PPLR dict (keyed by ``descan``/``scan_positions``) passes through. """ diff --git a/src/quantem/diffractive_imaging/object_models.py b/src/quantem/diffractive_imaging/object_models.py index 3b74319b..d311d8dd 100644 --- a/src/quantem/diffractive_imaging/object_models.py +++ b/src/quantem/diffractive_imaging/object_models.py @@ -1,7 +1,7 @@ import math from abc import abstractmethod from copy import deepcopy -from typing import Any, Callable, Literal, Self, Sequence, cast +from typing import Callable, Literal, Self, Sequence, cast from warnings import warn import matplotlib.pyplot as plt @@ -14,7 +14,11 @@ from quantem.core.io.serialize import AutoSerialize from quantem.core.ml.blocks import reset_weights from quantem.core.ml.loss_functions import get_loss_module -from quantem.core.ml.optimizer_mixin import OptimizerMixin, OptimizerType, SchedulerType +from quantem.core.ml.optimizer_mixin import ( + OptimizerMixin, + OptimizerParamsType, + SchedulerParamsType, +) from quantem.core.utils.rng import RNGMixin from quantem.core.utils.validators import ( validate_arr_gt, @@ -1046,8 +1050,8 @@ def pretrain( pretrain_target: torch.Tensor | None = None, reset: bool = False, num_iters: int = 100, - optimizer_params: dict | OptimizerType | None = None, - scheduler_params: dict | SchedulerType | None = None, + optimizer_params: dict | OptimizerParamsType | None = None, + scheduler_params: dict | SchedulerParamsType | None = None, loss_fn: Callable | str = "l2", apply_constraints: bool = False, show: bool = True, diff --git a/src/quantem/diffractive_imaging/probe_models.py b/src/quantem/diffractive_imaging/probe_models.py index 6b00793a..af638b85 100644 --- a/src/quantem/diffractive_imaging/probe_models.py +++ b/src/quantem/diffractive_imaging/probe_models.py @@ -15,7 +15,11 @@ from quantem.core.io.serialize import AutoSerialize from quantem.core.ml.blocks import reset_weights from quantem.core.ml.loss_functions import get_loss_module -from quantem.core.ml.optimizer_mixin import OptimizerMixin, OptimizerType, SchedulerType +from quantem.core.ml.optimizer_mixin import ( + OptimizerMixin, + OptimizerParamsType, + SchedulerParamsType, +) from quantem.core.utils.rng import RNGMixin from quantem.core.utils.utils import electron_wavelength_angstrom, to_numpy from quantem.core.utils.validators import ( @@ -1355,8 +1359,8 @@ def pretrain( pretrain_target: torch.Tensor | None = None, reset: bool = False, num_iters: int = 100, - optimizer_params: dict | OptimizerType | None = None, - scheduler_params: dict | SchedulerType | None = None, + optimizer_params: dict | OptimizerParamsType | None = None, + scheduler_params: dict | SchedulerParamsType | None = None, loss_fn: Callable | str = "l2", apply_constraints: bool = False, show: bool = True, diff --git a/src/quantem/diffractive_imaging/ptychography_opt.py b/src/quantem/diffractive_imaging/ptychography_opt.py index c18150e0..ff1b3a2c 100644 --- a/src/quantem/diffractive_imaging/ptychography_opt.py +++ b/src/quantem/diffractive_imaging/ptychography_opt.py @@ -4,9 +4,9 @@ from quantem.core import config from quantem.core.ml.optimizer_mixin import ( OptimizerParams, - OptimizerType, + OptimizerParamsType, SchedulerParams, - SchedulerType, + SchedulerParamsType, ) from quantem.diffractive_imaging.ptychography_base import PtychographyBase @@ -23,7 +23,7 @@ class PtychographyOpt(PtychographyBase): """ OPTIMIZABLE_VALS = ["object", "probe", "dataset"] - DEFAULT_OPTIMIZER_TYPE: OptimizerType = OptimizerParams.Adam() + DEFAULT_OPTIMIZER_TYPE: OptimizerParamsType = OptimizerParams.Adam() def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -42,7 +42,7 @@ def _get_default_lr(self, key: str) -> float: # region --- explicit properties and setters --- @property - def optimizer_params(self) -> dict[str, OptimizerType]: + def optimizer_params(self) -> dict[str, OptimizerParamsType | dict[str, OptimizerParamsType]]: return { key: params for key, params in [ @@ -56,7 +56,7 @@ def optimizer_params(self) -> dict[str, OptimizerType]: @optimizer_params.setter def optimizer_params(self, d: dict) -> None: """ - Takes a dictionary mapping optimizable keys to either an ``OptimizerType`` + Takes a dictionary mapping optimizable keys to either an ``OptimizerParamsType`` dataclass or a plain dict (with optional ``"name"``/``"type"`` and ``"lr"`` keys). Missing ``"name"`` / ``"lr"`` are filled from ``DEFAULT_OPTIMIZER_TYPE`` and ``_get_default_lr`` respectively. @@ -71,7 +71,7 @@ def optimizer_params(self, d: dict) -> None: d = {k: {} for k in d} for k, v in d.items(): - if isinstance(v, OptimizerType): + if isinstance(v, OptimizerParamsType): pass # already a dataclass, pass through elif isinstance(v, dict): if not v: @@ -82,7 +82,7 @@ def optimizer_params(self, d: dict) -> None: if "lr" not in v: v["lr"] = self._get_default_lr(k) else: - raise TypeError(f"Expected OptimizerType or dict for key '{k}', got {type(v)}") + raise TypeError(f"Expected OptimizerParamsType or dict for key '{k}', got {type(v)}") if k == "object": self.obj_model.optimizer_params = v @@ -131,7 +131,7 @@ def remove_optimizer(self, key: str) -> None: self.dset.remove_optimizer() @property - def scheduler_params(self) -> dict[str, SchedulerType]: + def scheduler_params(self) -> dict[str, SchedulerParamsType]: """Returns the parameters used to set the schedulers.""" return { "object": self.obj_model.scheduler_params, @@ -142,7 +142,7 @@ def scheduler_params(self) -> dict[str, SchedulerType]: @scheduler_params.setter def scheduler_params(self, d: dict) -> None: """ - Takes a dictionary mapping optimizable keys to either a ``SchedulerType`` + Takes a dictionary mapping optimizable keys to either a ``SchedulerParamsType`` dataclass or a plain dict. Keys not present in ``d`` are set to ``SchedulerParams.NoneScheduler()`` (disables scheduling for that model). @@ -178,7 +178,7 @@ def schedulers(self) -> dict[str, "torch.optim.lr_scheduler._LRScheduler"]: schedulers["dataset"] = self.dset.scheduler return schedulers - def set_schedulers(self, params: dict[str, SchedulerType], num_iter: int | None = None): + def set_schedulers(self, params: dict[str, SchedulerParamsType], num_iter: int | None = None): """Set schedulers for each model.""" for key, scheduler_params in params.items(): if key not in self.OPTIMIZABLE_VALS: diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index 12e8a99c..c6b85b64 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -1033,7 +1033,7 @@ def _normalize_optimizer_params(self, params): """ObjectTensorDecomp requires a dict matching model.param_keys.""" if not isinstance(params, dict) or self._is_single_optimizer_dict(params): raise TypeError( - f"ObjectTensorDecomp requires dict[str, OptimizerType] keyed by " + f"ObjectTensorDecomp requires dict[str, OptimizerParamsType] keyed by " f"param_keys; got {type(params)}" ) model = _unwrap(self.model) diff --git a/src/quantem/tomography/tomography_opt.py b/src/quantem/tomography/tomography_opt.py index 2c913277..cf990379 100644 --- a/src/quantem/tomography/tomography_opt.py +++ b/src/quantem/tomography/tomography_opt.py @@ -2,7 +2,11 @@ import torch -from quantem.core.ml.optimizer_mixin import OptimizerType, SchedulerType +from quantem.core.ml.optimizer_mixin import ( + OptimizerParams, + OptimizerParamsType, + SchedulerParamsType, +) from quantem.tomography.tomography_base import TomographyBase @@ -12,7 +16,7 @@ class TomographyOpt(TomographyBase): """ OPTIMIZABLE_VALS = ["object", "pose"] - DEFAULT_OPTIMIZER_TYPE = "adam" + DEFAULT_OPTIMIZER_TYPE: OptimizerParamsType = OptimizerParams.Adam() def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -27,7 +31,7 @@ def _get_default_lr(self, key: str) -> float: raise ValueError(f"Unknown optimization key: {key}") @property - def optimizer_params(self) -> dict[str, OptimizerType | dict[str, OptimizerType]]: + def optimizer_params(self) -> dict[str, OptimizerParamsType | dict[str, OptimizerParamsType]]: return { key: params for key, params in [ @@ -38,7 +42,7 @@ def optimizer_params(self) -> dict[str, OptimizerType | dict[str, OptimizerType] } @optimizer_params.setter - def optimizer_params(self, d: dict[str, OptimizerType] | dict[str, dict]): + def optimizer_params(self, d: dict[str, OptimizerParamsType] | dict[str, dict]): """Set the optimizer parameters.""" if isinstance(d, (tuple, list)): d = {k: {} for k in d} @@ -52,7 +56,7 @@ def optimizer_params(self, d: dict[str, OptimizerType] | dict[str, dict]): if k not in targets: raise ValueError(f"Unknown optimization key: {k}") - # if not isinstance(v, OptimizerType): + # if not isinstance(v, OptimizerParamsType): # v = OptimizerParams.parse_dict(v) targets[k].optimizer_params = v @@ -100,7 +104,7 @@ def remove_optimizer(self, key: str): raise ValueError(f"Unknown optimization key: {key}") @property - def scheduler_params(self) -> dict[str, SchedulerType]: + def scheduler_params(self) -> dict[str, SchedulerParamsType]: """Returns the parameters used to set the schedulers.""" return { "object": self.obj_model.scheduler_params, @@ -136,7 +140,7 @@ def schedulers(self) -> dict[str, torch.optim.lr_scheduler._LRScheduler]: return schedulers def set_schedulers( - self, params: Mapping[str, SchedulerType | dict], num_iter: int | None = None + self, params: Mapping[str, SchedulerParamsType | dict], num_iter: int | None = None ): for key, scheduler_params in params.items(): if key == "object": From 14a146349e8fe23af1479f1be919a1b6d66a3138 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Mon, 1 Jun 2026 13:43:55 -0700 Subject: [PATCH 322/335] Tomography tests, and bug-fix on get_optimization_parameters in TomographyDatasetBase. --- src/quantem/tomography/dataset_models.py | 15 +- tests/tomography/conftest.py | 72 ++++++++ tests/tomography/test_dataset_models.py | 106 ++++++++++++ tests/tomography/test_object_models.py | 159 ++++++++++++++++++ tests/tomography/test_radon.py | 111 ++++++++++++ .../test_tomography_conventional.py | 72 ++++++++ tests/tomography/test_tomography_inr.py | 96 +++++++++++ tests/tomography/test_tomography_opt.py | 143 ++++++++++++++++ tests/tomography/test_utils.py | 78 +++++++++ 9 files changed, 843 insertions(+), 9 deletions(-) create mode 100644 tests/tomography/conftest.py create mode 100644 tests/tomography/test_dataset_models.py create mode 100644 tests/tomography/test_object_models.py create mode 100644 tests/tomography/test_radon.py create mode 100644 tests/tomography/test_tomography_conventional.py create mode 100644 tests/tomography/test_tomography_inr.py create mode 100644 tests/tomography/test_tomography_opt.py create mode 100644 tests/tomography/test_utils.py diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index bdd2e66a..4838395d 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -232,16 +232,13 @@ def from_data( # --- Optimization Parameters --- - def get_optimization_parameters(self) -> list[dict[str, Any]]: - """ - Get the parameters that should be optimized for this model, - wrapped in a single param group. + def get_optimization_parameters(self) -> dict[str, list[torch.Tensor]]: + """Single param group keyed by DEFAULT_OPTIMIZER_KEY. + + Hyperparameters are baked by ``set_optimizer``, not here — return only the tensors, + matching the ``dict[str, list[tensor]]`` contract the object models use. """ - if isinstance(self._optimizer_params, dict): - opt = next(iter(self._optimizer_params.values())) - else: - opt = self._optimizer_params - return [{"params": list(self.parameters()), **opt.params()}] + return {self.DEFAULT_OPTIMIZER_KEY: list(self.parameters())} # --- Forward pass --- @abstractmethod diff --git a/tests/tomography/conftest.py b/tests/tomography/conftest.py new file mode 100644 index 00000000..06957095 --- /dev/null +++ b/tests/tomography/conftest.py @@ -0,0 +1,72 @@ +"""Shared fixtures and markers for the tomography test suite. + +The suite is split into three tiers (see the plan / individual modules): + +* CPU, always-on -- radon, utils, object/dataset models, optimizer-param wiring. +* CPU, slow -- conventional SIRT/FBP reconstruction (``--runslow``). +* GPU, slow -- INR / KPlanes reconstruction (``requires_gpu`` + ``--runslow``). + +``torch`` and ``scikit-image`` are core dependencies of quantem, so they are always +importable; ``requires_torch`` is kept only for parity with the existing test style. The +meaningful gate is ``requires_gpu`` (CI runs CPU-only) combined with ``@pytest.mark.slow``. +""" + +import numpy as np +import pytest +import torch + +from quantem.core import config +from quantem.tomography.utils import rot_ZXZ + +# --- Markers --------------------------------------------------------------- +requires_torch = pytest.mark.skipif(not config.get("has_torch"), reason="requires torch") +requires_gpu = pytest.mark.skipif(not torch.cuda.is_available(), reason="requires a CUDA device") + + +@pytest.fixture +def torch_device() -> str: + """Device for torch-only tests. + + ``cuda:0`` when available, else ``cpu``. Note: object models that go through + ``setup_distributed`` must be built on ``cuda`` when CUDA is present (the CPU path is + only valid when no CUDA device exists), so torch-only construction tests follow this + fixture rather than hard-coding ``"cpu"``. + """ + return "cuda:0" if torch.cuda.is_available() else "cpu" + + +# --- Synthetic data -------------------------------------------------------- +def make_tilt_series(volume: torch.Tensor, angles: np.ndarray) -> np.ndarray: + """Project a volume into a tilt series with quantem's own forward model. + + Mirrors ``tomography_00_generate_phantom``: rotate by ``rot_ZXZ`` (Euler ZXZ, tilt on + the X axis) then sum along the beam axis. Using this rather than ``radon_torch`` keeps + the synthetic data consistent with the geometry the reconstructors assume. + """ + projections = [] + vol = volume.unsqueeze(0) # (1, Z, Y, X) + for angle in angles: + rotated = rot_ZXZ(vol, 0.0, float(angle), 0.0, device="cpu") + projections.append(rotated[0].sum(0)) + return torch.stack(projections).numpy().astype(np.float32) + + +@pytest.fixture(scope="module") +def phantom_volume() -> np.ndarray: + """Small deterministic (32, 32, 32) phantom with a couple of solid blocks.""" + vol = np.zeros((32, 32, 32), dtype=np.float32) + vol[8:24, 10:20, 12:22] = 1.0 + vol[18:26, 6:12, 16:24] = 0.6 + return vol + + +@pytest.fixture(scope="module") +def tilt_angles() -> np.ndarray: + """Nine tilt angles spanning -70..70 degrees.""" + return np.linspace(-70, 70, 9).astype(np.float32) + + +@pytest.fixture(scope="module") +def tilt_series(phantom_volume: np.ndarray, tilt_angles: np.ndarray) -> np.ndarray: + """Synthetic tilt series, shape (n_angles, 32, 32).""" + return make_tilt_series(torch.from_numpy(phantom_volume), tilt_angles) diff --git a/tests/tomography/test_dataset_models.py b/tests/tomography/test_dataset_models.py new file mode 100644 index 00000000..78ded427 --- /dev/null +++ b/tests/tomography/test_dataset_models.py @@ -0,0 +1,106 @@ +"""Tests for ``quantem.tomography.dataset_models``. + +Covers constraint parsing, the pixelated dataset (validation, normalisation, tilt-angle +convention, pose-parameter materialisation) and the INR / pretrain datasets. +""" + +import numpy as np +import pytest +import torch + +from quantem.tomography.dataset_models import ( + DatasetConstraintParams, + DatasetValue, + TomographyINRDataset, + TomographyINRPretrainDataset, + TomographyPixDataset, +) + +from .conftest import requires_torch + + +class TestDatasetConstraintParse: + def test_parse_base_by_name(self): + c = DatasetConstraintParams.parse_dict({"name": "base_tomography_dataset", "tv_zs": 0.1}) + assert isinstance(c, DatasetConstraintParams.BaseTomographyDatasetConstraints) + assert c.tv_zs == 0.1 + + def test_parse_base_by_type_key(self): + c = DatasetConstraintParams.parse_dict( + {"type": "base_tomography_dataset", "tv_shifts": 0.2} + ) + assert c.tv_shifts == 0.2 + + def test_unknown_name_raises(self): + with pytest.raises(ValueError): + DatasetConstraintParams.parse_dict({"name": "nope"}) + + +def _stack(nang=5, n=12, seed=0): + rng = np.random.default_rng(seed) + return (rng.random((nang, n, n)) * 10).astype(np.float32) + + +class TestTomographyPixDataset: + def test_wrong_projection_axis_raises(self): + # projections must live on axis 0 (i.e. fewer than the image dims). + bad = np.zeros((20, 5, 5), dtype=np.float32) + with pytest.raises(ValueError): + TomographyPixDataset.from_data(bad, np.linspace(-60, 60, 20).astype(np.float32)) + + def test_tilt_angles_are_negated(self): + angles = np.linspace(-40, 60, 5).astype(np.float32) + d = TomographyPixDataset.from_data(_stack(), angles) + np.testing.assert_allclose(d.tilt_angles.numpy(), -angles, atol=1e-5) + + def test_normalised_by_95th_quantile(self): + d = TomographyPixDataset.from_data(_stack(), np.linspace(-60, 60, 5).astype(np.float32)) + q95 = torch.quantile(d.tilt_stack, 0.95) + assert torch.isclose(q95, torch.tensor(1.0), atol=1e-4) + + def test_reference_idx_and_learnable_tilts(self): + # negated angles -> [40, 15, -10, -35, -60]; smallest |angle| is index 2. + angles = np.linspace(-40, 60, 5).astype(np.float32) + d = TomographyPixDataset.from_data(_stack(), angles) + assert d.reference_tilt_idx == 2 + assert d.learnable_tilts == 4 + + def test_forward_returns_dataset_value(self): + angles = np.linspace(-40, 60, 5).astype(np.float32) + d = TomographyPixDataset.from_data(_stack(nang=5, n=12), angles) + out = d.forward(0) + assert isinstance(out, DatasetValue) + assert out.target.shape == (12, 12) + assert out.tilt_angle == pytest.approx(float(-angles[0])) + + def test_to_materialises_pose_parameters(self): + d = TomographyPixDataset.from_data(_stack(), np.linspace(-60, 60, 5).astype(np.float32)) + d.to("cpu") + assert isinstance(d.z1_params, torch.nn.Parameter) + assert d.shifts_params.shape == (d.learnable_tilts, 2) + + +@requires_torch +class TestTomographyINRDataset: + def test_len_is_projections_times_pixels(self): + d = TomographyINRDataset.from_data( + _stack(nang=5, n=12), np.linspace(-60, 60, 5, dtype="f4") + ) + assert len(d) == 5 * 12 * 12 + + def test_getitem_keys(self): + d = TomographyINRDataset.from_data( + _stack(nang=5, n=12), np.linspace(-60, 60, 5, dtype="f4") + ) + item = d[0] + assert {"phi", "pixel_i", "pixel_j", "projection_idx", "target_value"} <= set(item.keys()) + + +class TestTomographyINRPretrainDataset: + def test_len_and_getitem(self): + vol = torch.rand(1, 8, 8, 8) + ds = TomographyINRPretrainDataset(pretrain_target=vol) + assert len(ds) == 8**3 + item = ds[0] + assert set(item.keys()) == {"coords", "target"} + assert item["coords"].shape == (3,) diff --git a/tests/tomography/test_object_models.py b/tests/tomography/test_object_models.py new file mode 100644 index 00000000..f9f1922c --- /dev/null +++ b/tests/tomography/test_object_models.py @@ -0,0 +1,159 @@ +"""Tests for ``quantem.tomography.object_models``. + +The constraint-parsing and ``ObjectPixelated`` tests are pure CPU. The INR / tensor-decomp +construction tests are ``requires_torch`` and follow the ``torch_device`` fixture (they must +be built on CUDA when CUDA is present; see conftest). +""" + +import numpy as np +import pytest +import torch + +from quantem.tomography.object_models import ( + ObjConstraintParams, + ObjectBase, + ObjectINR, + ObjectPixelated, + ObjectTensorDecomp, +) +from quantem.tomography.tomography_context import ReconstructionContext + +from .conftest import requires_torch + + +class TestObjConstraintParse: + def test_parse_pixelated_by_name(self): + c = ObjConstraintParams.parse_dict({"name": "obj_pixelated", "tv_vol": 0.01}) + assert isinstance(c, ObjConstraintParams.ObjPixelatedConstraints) + assert c.tv_vol == 0.01 + + def test_parse_inr_by_type_key(self): + c = ObjConstraintParams.parse_dict({"type": "obj_inr", "sparsity": 0.05}) + assert isinstance(c, ObjConstraintParams.ObjINRConstraints) + assert c.sparsity == 0.05 + + def test_parse_tensor_decomp(self): + c = ObjConstraintParams.parse_dict({"name": "obj_tensor_decomp", "tv_plane": 0.1}) + assert isinstance(c, ObjConstraintParams.ObjTensorDecompConstraints) + assert c.tv_plane == 0.1 + + def test_missing_name_raises(self): + with pytest.raises(ValueError): + ObjConstraintParams.parse_dict({"tv_vol": 0.1}) + + def test_unknown_name_raises(self): + with pytest.raises(ValueError): + ObjConstraintParams.parse_dict({"name": "obj_nope"}) + + def test_constraint_key_partitions(self): + c = ObjConstraintParams.ObjPixelatedConstraints() + assert "positivity" in c.hard_constraint_keys + assert "tv_vol" in c.soft_constraint_keys + + +class TestObjectPixelatedConstruction: + def test_from_uniform_is_zeros(self): + obj = ObjectPixelated.from_uniform(shape=(8, 8, 8), device="cpu") + assert obj.shape == (8, 8, 8) + assert torch.allclose(obj.obj, torch.zeros(8, 8, 8)) + assert obj.obj_type == "pixelated" + + def test_from_array_numpy(self): + arr = np.random.default_rng(0).random((6, 6, 6)).astype(np.float32) + obj = ObjectPixelated.from_array(arr, device="cpu") + assert obj.shape == (6, 6, 6) + assert torch.allclose(obj.obj, torch.from_numpy(arr)) + assert obj.dtype == torch.float32 + + def test_from_array_torch_is_copied(self): + t = torch.ones(4, 4, 4) + obj = ObjectPixelated.from_array(t, device="cpu") + t += 5.0 + assert torch.allclose(obj.obj, torch.ones(4, 4, 4)) # original copy untouched + + def test_obj_view_shape(self): + obj = ObjectPixelated.from_uniform(shape=(5, 6, 7), device="cpu") + assert obj.obj_view.shape == (1, 5, 6, 7) + + def test_forward_returns_obj(self): + obj = ObjectPixelated.from_array(torch.full((4, 4, 4), 2.0), device="cpu") + assert torch.allclose(obj.forward(), obj.obj) + + +class TestObjectPixelatedConstraints: + def test_positivity_clamps_negatives(self): + obj = ObjectPixelated.from_array(torch.full((4, 4, 4), -1.0), device="cpu") + obj.constraints.positivity = True + assert torch.all(obj.obj >= 0.0) + + def test_positivity_off_keeps_negatives(self): + obj = ObjectPixelated.from_array(torch.full((4, 4, 4), -1.0), device="cpu") + obj.constraints.positivity = False + assert torch.all(obj.obj < 0.0) + + def test_shrinkage_subtracts_then_floors(self): + obj = ObjectPixelated.from_array(torch.full((4, 4, 4), 1.0), device="cpu") + obj.constraints.positivity = False + obj.constraints.shrinkage = 0.25 + assert torch.allclose(obj.obj, torch.full((4, 4, 4), 0.75)) + + def test_tv_loss_scales_with_weight(self): + ctx = ReconstructionContext(obj=torch.rand(1, 1, 8, 8, 8)) + obj = ObjectPixelated.from_uniform(shape=(8, 8, 8), device="cpu") + obj.constraints.tv_vol = 1.0 + loss1 = obj.get_tv_loss(ctx) + obj.constraints.tv_vol = 2.0 + loss2 = obj.get_tv_loss(ctx) + assert torch.isclose(loss2, 2.0 * loss1) + + def test_soft_constraint_zero_when_tv_off(self): + ctx = ReconstructionContext(obj=torch.rand(1, 1, 8, 8, 8)) + obj = ObjectPixelated.from_uniform(shape=(8, 8, 8), device="cpu") + obj.constraints.tv_vol = 0.0 + assert float(obj.apply_soft_constraints(ctx).detach()) == 0.0 + + +class TestFactoryGuard: + def test_objectbase_requires_token(self): + with pytest.raises(RuntimeError): + ObjectBase(shape=(4, 4, 4)) + + +@requires_torch +class TestObjectINR: + def test_from_model_builds(self, torch_device): + from quantem.core.ml.inr import HSiren + + model = HSiren(in_features=3, out_features=1, hidden_layers=1, hidden_features=8) + obj = ObjectINR.from_model(model, shape=(16, 16, 16), device=torch_device) + assert obj.shape == (16, 16, 16) + assert obj.model is not None + + def test_optimization_parameters_single_group(self, torch_device): + from quantem.core.ml.inr import HSiren + + model = HSiren(in_features=3, out_features=1, hidden_layers=1, hidden_features=8) + obj = ObjectINR.from_model(model, shape=(16, 16, 16), device=torch_device) + groups = obj.get_optimization_parameters() + assert list(groups.keys()) == ["default"] + assert len(groups["default"]) > 0 + + +@requires_torch +class TestObjectTensorDecomp: + def _model(self, n=16): + from quantem.core.ml.models.kplanes import KPlanesTILTED + + return KPlanesTILTED( + M_features=2, resolution=(n, n, n), multiscale_res_multipliers=[1], T=2 + ) + + def test_pplr_optimization_parameter_keys(self, torch_device): + obj = ObjectTensorDecomp.from_model(self._model(), shape=(16, 16, 16), device=torch_device) + keys = set(obj.get_optimization_parameters().keys()) + assert keys == {"grids", "sigma_net", "so3"} + + def test_pretrain_not_implemented(self, torch_device): + obj = ObjectTensorDecomp.from_model(self._model(), shape=(16, 16, 16), device=torch_device) + with pytest.raises(NotImplementedError): + obj.pretrain() diff --git a/tests/tomography/test_radon.py b/tests/tomography/test_radon.py new file mode 100644 index 00000000..4f61837a --- /dev/null +++ b/tests/tomography/test_radon.py @@ -0,0 +1,111 @@ +"""Tests for the pure-torch Radon transform (``quantem.tomography.radon.radon``). + +All CPU, deterministic. Cross-checked against scikit-image where a ground truth helps. +""" + +import numpy as np +import pytest +import torch + +from quantem.tomography.radon.radon import ( + get_fourier_filter_torch, + iradon_torch, + radon_torch, +) + + +def _disk(n: int, cy: int, cx: int, r: int) -> torch.Tensor: + yy, xx = np.mgrid[0:n, 0:n] + return torch.from_numpy((((yy - cy) ** 2 + (xx - cx) ** 2) < r**2).astype(np.float32)) + + +class TestRadonShapes: + def test_2d_input_returns_angles_by_pixels(self): + img = _disk(64, 32, 32, 10) + theta = torch.linspace(0, 180, 30) + sino = radon_torch(img, theta=theta) + assert sino.shape == (30, 64) + + def test_batched_input_returns_batch_angles_pixels(self): + imgs = torch.stack([_disk(48, 24, 20, 8), _disk(48, 24, 28, 8)]) + theta = torch.linspace(0, 180, 20) + sino = radon_torch(imgs, theta=theta) + assert sino.shape == (2, 20, 48) + + def test_default_theta_is_180_angles(self): + sino = radon_torch(_disk(32, 16, 16, 6)) + assert sino.shape == (180, 32) + + def test_iradon_shapes(self): + sino = radon_torch(_disk(40, 20, 20, 8), theta=torch.linspace(0, 180, 25)) + rec = iradon_torch(sino, theta=torch.linspace(0, 180, 25)) + assert rec.shape == (40, 40) + + def test_iradon_output_size_override(self): + sino = radon_torch(_disk(40, 20, 20, 8), theta=torch.linspace(0, 180, 25)) + rec = iradon_torch(sino, theta=torch.linspace(0, 180, 25), output_size=32) + assert rec.shape == (32, 32) + + +class TestFourierFilter: + def test_even_size_ok(self): + f = get_fourier_filter_torch(64, "ramp") + assert f.shape == (1, 64) + + def test_odd_size_raises(self): + with pytest.raises(ValueError): + get_fourier_filter_torch(63, "ramp") + + def test_unknown_filter_raises(self): + with pytest.raises(ValueError): + get_fourier_filter_torch(64, "not-a-filter") + + def test_none_filter_is_all_ones(self): + f = get_fourier_filter_torch(64, None) + assert torch.allclose(f, torch.ones_like(f)) + + @pytest.mark.parametrize("name", ["ramp", "shepp-logan", "cosine", "hamming", "hann"]) + def test_named_filters_run(self, name): + f = get_fourier_filter_torch(64, name) + assert f.shape == (1, 64) + assert torch.isfinite(f).all() + + +class TestRadonBehaviour: + def test_circular_mask_zeros_corners(self): + """The forward transform masks to the inscribed circle, so corner mass is dropped.""" + img = torch.ones(32, 32) + full = img.sum() + sino = radon_torch(img, theta=torch.tensor([0.0])) + # A single 0-degree projection sums columns; total equals the masked mass < full. + assert sino.sum() < full + + def test_iradon_circle_zeros_outside(self): + sino = radon_torch(_disk(48, 24, 24, 10), theta=torch.linspace(0, 180, 30)) + rec = iradon_torch(sino, theta=torch.linspace(0, 180, 30), circle=True) + n = rec.shape[0] + yy, xx = np.mgrid[0:n, 0:n] + outside = ((yy - n // 2) ** 2 + (xx - n // 2) ** 2) > (n // 2) ** 2 + assert torch.allclose(rec[outside], torch.zeros(int(outside.sum()))) + + def test_roundtrip_recovers_structure(self): + disk = _disk(64, 32, 24, 9) + theta = torch.linspace(0, 180, 60) + rec = iradon_torch(radon_torch(disk, theta=theta), theta=theta, filter_name="ramp") + corr = np.corrcoef(disk.numpy().ravel(), rec.numpy().ravel())[0, 1] + assert corr > 0.9 + + +class TestRadonVsSkimage: + """Loose cross-check against scikit-image's reference implementation.""" + + def test_forward_matches_skimage(self): + sk = pytest.importorskip("skimage.transform") + n = 64 + disk = _disk(n, n // 2, n // 2, 12) + theta = np.linspace(0.0, 180.0, 45, endpoint=False).astype(np.float32) + ours = radon_torch(disk, theta=torch.from_numpy(theta)).numpy() # (A, N) + ref = sk.radon(disk.numpy(), theta=theta, circle=True).T # skimage: (N, A) -> (A, N) + # Different interpolation conventions; require strong agreement, not equality. + corr = np.corrcoef(ours.ravel(), ref.ravel())[0, 1] + assert corr > 0.95 diff --git a/tests/tomography/test_tomography_conventional.py b/tests/tomography/test_tomography_conventional.py new file mode 100644 index 00000000..a7cfb5e0 --- /dev/null +++ b/tests/tomography/test_tomography_conventional.py @@ -0,0 +1,72 @@ +"""End-to-end conventional (SIRT / FBP) reconstruction. + +CPU, deterministic, but marked ``slow`` because it runs a (tiny) iterative reconstruction. +Reconstructions are capped at a few iterations: the suite checks behaviour and wiring (loss +decreases, output stays physical) rather than convergence quality, so spatial agreement with +the phantom is only a loose lower bound. +""" + +import numpy as np +import pytest + +from quantem.tomography.dataset_models import TomographyPixDataset +from quantem.tomography.object_models import ObjConstraintParams, ObjectPixelated +from quantem.tomography.tomography import TomographyConventional + +pytestmark = pytest.mark.slow + + +def _build(tilt_series, tilt_angles, n): + dset = TomographyPixDataset.from_data( + tilt_series, tilt_angles, learn_shift=False, learn_tilt_axis=False + ) + obj = ObjectPixelated.from_uniform(shape=(n, n, n), device="cpu") + return TomographyConventional.from_models( + dset=dset, obj_model=obj, device="cpu", verbose=False + ) + + +class TestSIRT: + def test_loss_decreases_and_output_physical(self, phantom_volume, tilt_series, tilt_angles): + n = phantom_volume.shape[0] + tomo = _build(tilt_series, tilt_angles, n) + tomo.reconstruct( + num_iter=4, + mode="sirt", + obj_constraints=ObjConstraintParams.ObjPixelatedConstraints(positivity=True), + ) + losses = tomo.epoch_losses + assert tomo.num_epochs == 4 + assert losses[-1] < losses[0] + rec = tomo.obj_model.obj.detach().cpu().numpy() + assert np.isfinite(rec).all() + assert rec.min() >= 0.0 # positivity + + def test_recon_correlates_with_phantom(self, phantom_volume, tilt_series, tilt_angles): + n = phantom_volume.shape[0] + tomo = _build(tilt_series, tilt_angles, n) + tomo.reconstruct( + num_iter=4, + mode="sirt", + obj_constraints=ObjConstraintParams.ObjPixelatedConstraints(positivity=True), + ) + rec = tomo.obj_model.obj.detach().cpu().numpy() + corr = np.corrcoef(rec.ravel(), phantom_volume.ravel())[0, 1] + assert corr > 0.15 # loose: only a handful of iterations + + def test_obj_constraints_accepts_dict(self, phantom_volume, tilt_series, tilt_angles): + n = phantom_volume.shape[0] + tomo = _build(tilt_series, tilt_angles, n) + tomo.reconstruct(num_iter=2, mode="sirt", obj_constraints={"name": "obj_pixelated"}) + assert tomo.num_epochs == 2 + + +class TestFBP: + def test_fbp_runs_single_epoch(self, phantom_volume, tilt_series, tilt_angles): + n = phantom_volume.shape[0] + tomo = _build(tilt_series, tilt_angles, n) + tomo.reconstruct(num_iter=5, mode="fbp") + # FBP breaks after the first epoch regardless of num_iter. + assert tomo.num_epochs == 1 + rec = tomo.obj_model.obj.detach().cpu().numpy() + assert np.isfinite(rec).all() diff --git a/tests/tomography/test_tomography_inr.py b/tests/tomography/test_tomography_inr.py new file mode 100644 index 00000000..fbd5f1a1 --- /dev/null +++ b/tests/tomography/test_tomography_inr.py @@ -0,0 +1,96 @@ +"""End-to-end INR / KPlanes (tensor-decomposition) reconstruction. + +These exercise the full learned-reconstruction path (model + pose optimisation, autocast, +spawned dataloader workers), so they are gated behind ``requires_gpu`` and ``slow`` and only +run locally with ``--runslow``. Reconstructions are capped at 4 iterations; the assertion is +loss-decreases plus finite output, not convergence quality. + +The ``num_workers=2`` is required, not incidental: ``setup_dataloader`` hard-codes +``multiprocessing_context="spawn"``, which is invalid with ``num_workers=0``. +""" + +import numpy as np +import pytest +import torch + +from quantem.tomography.dataset_models import TomographyINRDataset +from quantem.tomography.object_models import ObjectTensorDecomp +from quantem.tomography.tomography import Tomography +from quantem.tomography.tomography_lite import TomographyLiteConv, TomographyLiteINR + +from .conftest import make_tilt_series, requires_gpu + +pytestmark = [requires_gpu, pytest.mark.slow] + +DEVICE = "cuda:0" + + +@pytest.fixture(scope="module") +def small_phantom(): + vol = torch.zeros(1, 24, 24, 24) + vol[0, 6:18, 6:14, 8:16] = 1.0 + angles = np.linspace(-60, 60, 7).astype(np.float32) + series = make_tilt_series(vol[0], angles) + return series, angles + + +class TestLiteINR: + def test_reconstruct_reduces_loss(self, small_phantom): + series, angles = small_phantom + tomo = TomographyLiteINR.from_dataset( + tilt_series=series, tilt_angles=angles, device=DEVICE + ) + tomo.reconstruct(num_iter=4, num_workers=2, batch_size=256, learn_pose=True) + losses = tomo.epoch_losses + assert len(losses) == 4 + assert losses[-1] < losses[0] + view = tomo.obj_model.obj_view + assert view.shape == (1, 24, 24, 24) + assert np.isfinite(view).all() + + +class TestLiteConv: + def test_smoke(self, small_phantom): + series, angles = small_phantom + tomo = TomographyLiteConv.from_dataset( + tilt_series=series, tilt_angles=angles, device=DEVICE + ) + tomo.reconstruct(num_iter=3, mode="sirt") + assert tomo.num_epochs == 3 + assert np.isfinite(tomo.obj_model.obj.detach().cpu().numpy()).all() + + +class TestKPlanes: + def test_pplr_reconstruct_reduces_loss(self, small_phantom): + from quantem.core.ml.models.kplanes import KPlanesTILTED + from quantem.core.ml.optimizer_mixin import OptimizerParams, SchedulerParams + + series, angles = small_phantom + n = series.shape[1] + model = KPlanesTILTED( + M_features=2, resolution=(n, n, n), multiscale_res_multipliers=[1], T=2 + ) + obj = ObjectTensorDecomp.from_model(model, shape=(n, n, n), device=DEVICE) + dset = TomographyINRDataset.from_data(series, angles) + tomo = Tomography.from_models(dset=dset, obj_model=obj, device=DEVICE, verbose=False) + tomo.reconstruct( + optimizer_params={ + "object": { + "grids": OptimizerParams.Adam(lr=1e-2), + "sigma_net": OptimizerParams.Adam(lr=1e-3), + "so3": OptimizerParams.Adam(lr=1e-2), + }, + "pose": OptimizerParams.Adam(lr=1e-2), + }, + scheduler_params={ + "object": SchedulerParams.CosineAnnealing(T_max=4), + "pose": SchedulerParams.CosineAnnealing(T_max=4), + }, + num_iter=4, + batch_size=256, + num_samples_per_ray=20, + num_workers=2, + ) + losses = tomo.epoch_losses + assert len(losses) == 4 + assert losses[-1] < losses[0] diff --git a/tests/tomography/test_tomography_opt.py b/tests/tomography/test_tomography_opt.py new file mode 100644 index 00000000..4ec56a13 --- /dev/null +++ b/tests/tomography/test_tomography_opt.py @@ -0,0 +1,143 @@ +"""Tests for the tomography optimizer / scheduler wiring (``TomographyOpt``). + +This is the surface the PPLR ``OptimizerParamsType`` / ``SchedulerParamsType`` refactor +touched. In particular, ``test_set_optimizers_builds_object_and_pose`` and the PPLR test +regression-guard the pose-optimizer path: ``TomographyDatasetBase.get_optimization_parameters`` +must return a ``dict[str, list[tensor]]`` (it previously returned a ``list`` and crashed +``set_optimizer`` with ``TypeError: unhashable type: 'dict'``). + +Construction only -- no forward passes -- so these run on CPU under CI. +""" + +import numpy as np +import pytest + +from quantem.core.ml.optimizer_mixin import OptimizerParams, SchedulerParams +from quantem.tomography.dataset_models import TomographyINRDataset +from quantem.tomography.object_models import ObjectINR, ObjectTensorDecomp +from quantem.tomography.tomography import Tomography + +from .conftest import requires_torch + + +def _tilts(nang=5, n=12): + rng = np.random.default_rng(0) + angles = np.linspace(-60, 60, nang).astype(np.float32) + stack = rng.random((nang, n, n)).astype(np.float32) + return stack, angles + + +def _inr_tomography(device): + from quantem.core.ml.inr import HSiren + + model = HSiren(in_features=3, out_features=1, hidden_layers=1, hidden_features=8) + obj = ObjectINR.from_model(model, shape=(16, 16, 16), device=device) + stack, angles = _tilts() + dset = TomographyINRDataset.from_data(stack, angles) + return Tomography.from_models(dset=dset, obj_model=obj, device=device, verbose=False) + + +def _td_tomography(device): + from quantem.core.ml.models.kplanes import KPlanesTILTED + + model = KPlanesTILTED( + M_features=2, resolution=(16, 16, 16), multiscale_res_multipliers=[1], T=2 + ) + obj = ObjectTensorDecomp.from_model(model, shape=(16, 16, 16), device=device) + stack, angles = _tilts() + dset = TomographyINRDataset.from_data(stack, angles) + return Tomography.from_models(dset=dset, obj_model=obj, device=device, verbose=False) + + +@requires_torch +class TestOptimizerParams: + def test_setter_getter_roundtrip(self, torch_device): + tomo = _inr_tomography(torch_device) + tomo.optimizer_params = { + "object": OptimizerParams.Adam(lr=1e-3), + "pose": OptimizerParams.Adam(lr=1e-2), + } + assert set(tomo.optimizer_params.keys()) == {"object", "pose"} + + def test_unknown_key_raises(self, torch_device): + tomo = _inr_tomography(torch_device) + with pytest.raises(ValueError): + tomo.optimizer_params = {"banana": OptimizerParams.Adam(lr=1e-3)} + + def test_set_optimizers_builds_object_and_pose(self, torch_device): + """Regression guard: the pose path must not raise (see module docstring).""" + tomo = _inr_tomography(torch_device) + tomo.optimizer_params = { + "object": OptimizerParams.Adam(lr=1e-3), + "pose": OptimizerParams.Adam(lr=1e-2), + } + tomo.set_optimizers() + assert set(tomo.optimizers.keys()) == {"object", "pose"} + + def test_current_lrs(self, torch_device): + tomo = _inr_tomography(torch_device) + tomo.optimizer_params = { + "object": OptimizerParams.Adam(lr=1e-3), + "pose": OptimizerParams.Adam(lr=1e-2), + } + tomo.set_optimizers() + lrs = tomo.get_current_lrs() + assert set(lrs.keys()) == {"object", "pose"} + assert lrs["object"] == pytest.approx(1e-3) + assert lrs["pose"] == pytest.approx(1e-2) + + def test_remove_optimizer(self, torch_device): + tomo = _inr_tomography(torch_device) + tomo.optimizer_params = { + "object": OptimizerParams.Adam(lr=1e-3), + "pose": OptimizerParams.Adam(lr=1e-2), + } + tomo.set_optimizers() + tomo.remove_optimizer("object") + assert "object" not in tomo.optimizers + assert "pose" in tomo.optimizers + + def test_pplr_object_groups(self, torch_device): + """Per-parameter LR: object optimizer carries one torch param group per key.""" + tomo = _td_tomography(torch_device) + tomo.optimizer_params = { + "object": { + "grids": OptimizerParams.Adam(lr=1e-2), + "sigma_net": OptimizerParams.Adam(lr=1e-3), + "so3": OptimizerParams.Adam(lr=1e-2), + }, + "pose": OptimizerParams.Adam(lr=1e-2), + } + tomo.set_optimizers() + assert len(tomo.optimizers["object"].param_groups) == 3 + assert "pose" in tomo.optimizers + + +@requires_torch +class TestSchedulerParams: + def test_scheduler_setter_getter(self, torch_device): + tomo = _inr_tomography(torch_device) + tomo.scheduler_params = { + "object": SchedulerParams.CosineAnnealing(T_max=10), + "pose": SchedulerParams.CosineAnnealing(T_max=10), + } + assert set(tomo.scheduler_params.keys()) == {"object", "pose"} + + def test_set_schedulers_builds(self, torch_device): + tomo = _inr_tomography(torch_device) + tomo.optimizer_params = { + "object": OptimizerParams.Adam(lr=1e-3), + "pose": OptimizerParams.Adam(lr=1e-2), + } + tomo.set_optimizers() + tomo.scheduler_params = { + "object": SchedulerParams.CosineAnnealing(T_max=10), + "pose": SchedulerParams.CosineAnnealing(T_max=10), + } + tomo.set_schedulers(tomo.scheduler_params, num_iter=10) + assert set(tomo.schedulers.keys()) == {"object", "pose"} + + def test_bad_scheduler_type_raises(self, torch_device): + tomo = _inr_tomography(torch_device) + with pytest.raises(TypeError): + tomo.obj_model.scheduler_params = 123 diff --git a/tests/tomography/test_utils.py b/tests/tomography/test_utils.py new file mode 100644 index 00000000..1fa6a47c --- /dev/null +++ b/tests/tomography/test_utils.py @@ -0,0 +1,78 @@ +"""Tests for ``quantem.tomography.utils``: 1D total-variation loss and the +differentiable ZXZ rotation operators. All CPU.""" + +import pytest +import torch + +from quantem.tomography.utils import ( + differentiable_rotz_vectorized, + rot_ZXZ, + tv_loss_1d, +) + + +class TestTVLoss1D: + def test_constant_input_is_zero(self): + assert tv_loss_1d(torch.ones(10)) == 0.0 + + def test_known_value_mean(self): + # diffs are [1, 1, 1], abs-mean = 1.0 + x = torch.tensor([0.0, 1.0, 2.0, 3.0]) + assert torch.isclose(tv_loss_1d(x, reduction="mean"), torch.tensor(1.0)) + + def test_known_value_sum(self): + x = torch.tensor([0.0, 1.0, 2.0, 3.0]) + assert torch.isclose(tv_loss_1d(x, reduction="sum"), torch.tensor(3.0)) + + def test_reduction_none_shape(self): + x = torch.zeros(2, 5) + out = tv_loss_1d(x, reduction="none") + assert out.shape == (2, 4) + + def test_bad_reduction_raises(self): + with pytest.raises(ValueError): + tv_loss_1d(torch.zeros(4), reduction="median") + + +def _block_volume(n: int = 16) -> torch.Tensor: + """(1, n, n, n) volume with an off-centre block so rotations are detectable.""" + vol = torch.zeros(1, n, n, n) + vol[0, 4:12, 4:10, 5:11] = 1.0 + return vol + + +class TestRotations: + def test_zero_rotation_is_identity(self): + vol = _block_volume() + out = rot_ZXZ(vol, 0.0, 0.0, 0.0, device="cpu") + assert torch.max(torch.abs(out - vol)) < 1e-4 + + def test_rotation_preserves_mass(self): + vol = _block_volume() + rotated = rot_ZXZ(vol, 0.0, 30.0, 0.0, device="cpu") + rel_err = abs(float(rotated.sum()) - float(vol.sum())) / float(vol.sum()) + assert rel_err < 0.02 + + def test_accepts_python_float_and_tensor_angle(self): + vol = _block_volume() + out_float = rot_ZXZ(vol, 0.0, 25.0, 0.0, device="cpu") + out_tensor = rot_ZXZ( + vol, + torch.tensor(0.0), + torch.tensor(25.0), + torch.tensor(0.0), + device="cpu", + ) + assert torch.allclose(out_float, out_tensor, atol=1e-5) + + def test_rotation_changes_volume(self): + vol = _block_volume() + rotated = rot_ZXZ(vol, 0.0, 90.0, 0.0, device="cpu") + assert torch.max(torch.abs(rotated - vol)) > 0.1 + + def test_gradient_flows_through_rotation(self): + vol = _block_volume().requires_grad_(True) + out = differentiable_rotz_vectorized(vol, torch.tensor(20.0)) + out.sum().backward() + assert vol.grad is not None + assert torch.isfinite(vol.grad).all() From 8260c5b0a984562336f0665a898c00eae0296bf7 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Mon, 1 Jun 2026 14:43:09 -0700 Subject: [PATCH 323/335] Fix latent bugs in tomography optimizer wiring, constraints, and radon step_optimizers and zero_grad_all looped over optimizer_params and stepped both the object and pose optimizers on every pass, so with pose optimization enabled each optimizer took two Adam steps per batch. Gate by key, matching step_schedulers. Also: - pass num_iter to set_schedulers on the reset_dset path so cosine/linear/ exponential schedulers get a valid T_max - scheduler_params setter no longer mutates the caller's dict - drop the non-existent tv_plane key from ObjINRConstraints.soft_constraint_keys (it crashed Constraints.__str__) - ObjectPixelated.get_tv_loss now takes TV over the trailing spatial dims, so it handles a 3D volume, obj_view's [1, D, H, W], and a multimodal [C, D, H, W] - remove an unreachable duplicate branch in TomographyINRDataset.forward - align iradon_torch's default theta with radon_torch / scikit-image (endpoint excluded) Add regression tests covering each fix. --- src/quantem/tomography/dataset_models.py | 2 - src/quantem/tomography/object_models.py | 16 ++++--- src/quantem/tomography/radon/radon.py | 3 +- src/quantem/tomography/tomography.py | 2 +- src/quantem/tomography/tomography_opt.py | 19 ++++---- tests/tomography/test_dataset_models.py | 29 +++++++++++++ tests/tomography/test_object_models.py | 40 ++++++++++++++++- tests/tomography/test_radon.py | 11 +++++ tests/tomography/test_tomography_opt.py | 55 ++++++++++++++++++++++++ 9 files changed, 156 insertions(+), 21 deletions(-) diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index 4838395d..2255636c 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -486,8 +486,6 @@ def forward(self, dummy_input: Any = None): return shifts, torch.zeros_like(z1), torch.zeros_like(z3) elif self.learn_tilt_axis: return torch.zeros_like(shifts), z1, z3 - elif self.learn_shift and self.learn_tilt_axis: - return shifts, z1, z3 else: return torch.zeros_like(shifts), torch.zeros_like(z1), torch.zeros_like(z3) diff --git a/src/quantem/tomography/object_models.py b/src/quantem/tomography/object_models.py index c6b85b64..817e7ed5 100644 --- a/src/quantem/tomography/object_models.py +++ b/src/quantem/tomography/object_models.py @@ -101,7 +101,7 @@ class ObjINRConstraints(Constraints): sparsity: float = 0.0 _name: str = "obj_inr" - soft_constraint_keys = ["tv_vol", "tv_plane", "sparsity"] + soft_constraint_keys = ["tv_vol", "sparsity"] hard_constraint_keys = ["positivity", "shrinkage"] @dataclass @@ -436,12 +436,16 @@ def forward(self, coords=None) -> torch.Tensor: # --- Defining the TV loss --- def get_tv_loss(self, ctx: ReconstructionContext) -> torch.Tensor: assert ctx.obj is not None, "ObjectPixelated requires ctx.obj to be set" - tv_d = torch.pow(ctx.obj[:, :, 1:, :, :] - ctx.obj[:, :, :-1, :, :], 2).sum() - tv_h = torch.pow(ctx.obj[:, :, :, 1:, :] - ctx.obj[:, :, :, :-1, :], 2).sum() - tv_w = torch.pow(ctx.obj[:, :, :, :, 1:] - ctx.obj[:, :, :, :, :-1], 2).sum() + # TV over the three trailing spatial dims, leaving any leading channel/batch axes + # intact. Works for a 3-D volume, obj_view's [1, D, H, W], and a multimodal + # [C, D, H, W] (channels = elemental compositions), matching the INR / tensor-decomp + # convention where the object carries a leading channel dimension. + tv_d = torch.pow(ctx.obj[..., 1:, :, :] - ctx.obj[..., :-1, :, :], 2).sum() + tv_h = torch.pow(ctx.obj[..., :, 1:, :] - ctx.obj[..., :, :-1, :], 2).sum() + tv_w = torch.pow(ctx.obj[..., :, :, 1:] - ctx.obj[..., :, :, :-1], 2).sum() tv_loss = tv_d + tv_h + tv_w - return tv_loss * self.constraints.tv_vol / (torch.prod(torch.tensor(ctx.obj.shape))) + return tv_loss * self.constraints.tv_vol / ctx.obj.numel() # --- Helper Functions --- def to(self, device: str | torch.device): @@ -934,7 +938,7 @@ def get_tv_loss(self, ctx: ReconstructionContext) -> torch.Tensor: Gets the summed total variational loss for the tensor decomposition model. _get_plane_tv_loss: Total-variation across the planes. - _get_volume_tv_loss: Isotropic volume TV + _get_volume_tv_loss: Isotropic volume TV """ assert ctx.coords is not None, "Coordinates must be provided for TV loss" assert ctx.pred is not None, "Prediction must be provided for TV loss" diff --git a/src/quantem/tomography/radon/radon.py b/src/quantem/tomography/radon/radon.py index 8510e570..ca133db3 100644 --- a/src/quantem/tomography/radon/radon.py +++ b/src/quantem/tomography/radon/radon.py @@ -91,7 +91,8 @@ def iradon_torch( sinograms = sinograms.unsqueeze(0) B, A, N = sinograms.shape device = device or sinograms.device - theta = theta if theta is not None else torch.linspace(0, 180, steps=A, device=device) + # Match radon_torch / scikit-image: A angles evenly spanning [0, 180) (endpoint excluded). + theta = theta if theta is not None else torch.linspace(0, 180, steps=A + 1, device=device)[:-1] if output_size is None: output_size = N if circle else int((N**2 / 2) ** 0.5) diff --git a/src/quantem/tomography/tomography.py b/src/quantem/tomography/tomography.py index 8976272e..f86095ad 100644 --- a/src/quantem/tomography/tomography.py +++ b/src/quantem/tomography/tomography.py @@ -143,7 +143,7 @@ def reconstruct( self.set_optimizers() if scheduler_params is not None: self.scheduler_params = scheduler_params - self.set_schedulers(self.scheduler_params) + self.set_schedulers(self.scheduler_params, num_iter=num_iter) self.dataloader, self.sampler, self.val_dataloader, self.val_sampler = ( self.setup_dataloader( diff --git a/src/quantem/tomography/tomography_opt.py b/src/quantem/tomography/tomography_opt.py index cf990379..7f98fabd 100644 --- a/src/quantem/tomography/tomography_opt.py +++ b/src/quantem/tomography/tomography_opt.py @@ -114,7 +114,8 @@ def scheduler_params(self) -> dict[str, SchedulerParamsType]: @scheduler_params.setter def scheduler_params(self, d: dict): """Set the scheduler parameters.""" - self._scheduler_params = d.copy() if d else {} + d = dict(d) if d else {} + self._scheduler_params = d.copy() for key in self.OPTIMIZABLE_VALS: if key not in d: @@ -152,21 +153,21 @@ def set_schedulers( def step_optimizers(self): for key in self.optimizer_params.keys(): - if self.obj_model.has_optimizer(): - self.obj_model.step_optimizer() - if self.dset.has_optimizer(): - self.dset.step_optimizer() if key not in self.OPTIMIZABLE_VALS: raise ValueError(f"Unknown optimization key: {key}") + if key == "object" and self.obj_model.has_optimizer(): + self.obj_model.step_optimizer() + elif key == "pose" and self.dset.has_optimizer(): + self.dset.step_optimizer() def zero_grad_all(self): for key in self.optimizer_params.keys(): - if self.obj_model.has_optimizer(): - self.obj_model.zero_optimizer_grad() - if self.dset.has_optimizer(): - self.dset.zero_optimizer_grad() if key not in self.OPTIMIZABLE_VALS: raise ValueError(f"Unknown optimization key: {key}") + if key == "object" and self.obj_model.has_optimizer(): + self.obj_model.zero_optimizer_grad() + elif key == "pose" and self.dset.has_optimizer(): + self.dset.zero_optimizer_grad() def step_schedulers(self, loss: float | None = None): for key in self.scheduler_params.keys(): diff --git a/tests/tomography/test_dataset_models.py b/tests/tomography/test_dataset_models.py index 78ded427..9ebd99dc 100644 --- a/tests/tomography/test_dataset_models.py +++ b/tests/tomography/test_dataset_models.py @@ -95,6 +95,35 @@ def test_getitem_keys(self): item = d[0] assert {"phi", "pixel_i", "pixel_j", "projection_idx", "target_value"} <= set(item.keys()) + @pytest.mark.parametrize( + "learn_shift,learn_tilt_axis", + [(True, True), (True, False), (False, True), (False, False)], + ) + def test_forward_gates_shift_and_tilt(self, learn_shift, learn_tilt_axis): + """``forward`` zeros the disabled component and passes the enabled one through. + + Guards the gating after removing the unreachable duplicate branch: shifts are + controlled by ``learn_shift``; the z1/z3 Euler angles by ``learn_tilt_axis``. + """ + d = TomographyINRDataset.from_data( + _stack(nang=5, n=12), + np.linspace(-60, 60, 5, dtype="f4"), + learn_shift=learn_shift, + learn_tilt_axis=learn_tilt_axis, + ) + d.to("cpu") + # Make every pose parameter non-zero so the gating is observable by value. + for p in (d.z1_params, d.z3_params, d.shifts_params): + p.data.fill_(1.0) + d._z1_ref = torch.ones_like(d._z1_ref) + d._z3_ref = torch.ones_like(d._z3_ref) + d._shifts_ref = torch.ones_like(d._shifts_ref) + + shifts, z1, z3 = d.forward(None) + assert bool(shifts.any()) == learn_shift + assert bool(z1.any()) == learn_tilt_axis + assert bool(z3.any()) == learn_tilt_axis + class TestTomographyINRPretrainDataset: def test_len_and_getitem(self): diff --git a/tests/tomography/test_object_models.py b/tests/tomography/test_object_models.py index f9f1922c..b200d2b9 100644 --- a/tests/tomography/test_object_models.py +++ b/tests/tomography/test_object_models.py @@ -50,6 +50,22 @@ def test_constraint_key_partitions(self): assert "positivity" in c.hard_constraint_keys assert "tv_vol" in c.soft_constraint_keys + def test_constraint_keys_are_real_fields(self): + """Regression: every soft/hard key must be an attribute, so __str__ never raises. + + ``ObjINRConstraints`` previously listed ``tv_plane`` (a field it does not have), which + made ``str(constraints)`` blow up with AttributeError via ``Constraints.__str__``. + """ + for cls in ( + ObjConstraintParams.ObjPixelatedConstraints, + ObjConstraintParams.ObjINRConstraints, + ObjConstraintParams.ObjTensorDecompConstraints, + ): + c = cls() + for key in c.soft_constraint_keys + c.hard_constraint_keys: + assert hasattr(c, key), f"{cls.__name__} lists missing key {key!r}" + assert isinstance(str(c), str) # must not raise + class TestObjectPixelatedConstruction: def test_from_uniform_is_zeros(self): @@ -98,7 +114,8 @@ def test_shrinkage_subtracts_then_floors(self): assert torch.allclose(obj.obj, torch.full((4, 4, 4), 0.75)) def test_tv_loss_scales_with_weight(self): - ctx = ReconstructionContext(obj=torch.rand(1, 1, 8, 8, 8)) + # ctx.obj is the 3-D pixelated volume (D, H, W), matching ObjectPixelated._obj. + ctx = ReconstructionContext(obj=torch.rand(8, 8, 8)) obj = ObjectPixelated.from_uniform(shape=(8, 8, 8), device="cpu") obj.constraints.tv_vol = 1.0 loss1 = obj.get_tv_loss(ctx) @@ -106,8 +123,27 @@ def test_tv_loss_scales_with_weight(self): loss2 = obj.get_tv_loss(ctx) assert torch.isclose(loss2, 2.0 * loss1) + @pytest.mark.parametrize( + "shape", + [ + (8, 8, 8), # bare 3-D volume + (1, 8, 8, 8), # obj_view layout [C=1, D, H, W] + (3, 8, 8, 8), # multimodal [C, D, H, W] (e.g. 3 elemental channels) + ], + ) + def test_tv_loss_rank_agnostic_finite_and_positive(self, shape): + """Regression: get_tv_loss takes TV over the trailing spatial dims for any leading + channel/batch axes -- not the old 5-D-only indexing. Supports multimodal [C, ...].""" + ctx = ReconstructionContext(obj=torch.rand(*shape)) + obj = ObjectPixelated.from_uniform(shape=(8, 8, 8), device="cpu") + obj.constraints.tv_vol = 1.0 + loss = obj.get_tv_loss(ctx) + assert loss.ndim == 0 + assert torch.isfinite(loss) + assert loss > 0.0 # random volume has non-zero total variation + def test_soft_constraint_zero_when_tv_off(self): - ctx = ReconstructionContext(obj=torch.rand(1, 1, 8, 8, 8)) + ctx = ReconstructionContext(obj=torch.rand(8, 8, 8)) obj = ObjectPixelated.from_uniform(shape=(8, 8, 8), device="cpu") obj.constraints.tv_vol = 0.0 assert float(obj.apply_soft_constraints(ctx).detach()) == 0.0 diff --git a/tests/tomography/test_radon.py b/tests/tomography/test_radon.py index 4f61837a..41516f03 100644 --- a/tests/tomography/test_radon.py +++ b/tests/tomography/test_radon.py @@ -95,6 +95,17 @@ def test_roundtrip_recovers_structure(self): corr = np.corrcoef(disk.numpy().ravel(), rec.numpy().ravel())[0, 1] assert corr > 0.9 + def test_default_theta_roundtrip_is_consistent(self): + """radon and iradon must share an angle convention when ``theta`` is defaulted. + + iradon's default previously included the 180-degree endpoint while radon's did not, + so a default-theta round-trip sampled mismatched angles. + """ + disk = _disk(64, 32, 28, 10) + rec = iradon_torch(radon_torch(disk), filter_name="ramp") # both default theta + corr = np.corrcoef(disk.numpy().ravel(), rec.numpy().ravel())[0, 1] + assert corr > 0.9 + class TestRadonVsSkimage: """Loose cross-check against scikit-image's reference implementation.""" diff --git a/tests/tomography/test_tomography_opt.py b/tests/tomography/test_tomography_opt.py index 4ec56a13..6c597c90 100644 --- a/tests/tomography/test_tomography_opt.py +++ b/tests/tomography/test_tomography_opt.py @@ -112,6 +112,54 @@ def test_pplr_object_groups(self, torch_device): assert len(tomo.optimizers["object"].param_groups) == 3 assert "pose" in tomo.optimizers + def test_step_optimizers_steps_each_once(self, torch_device, monkeypatch): + """Regression: with object+pose, each optimizer must step exactly once per call. + + ``step_optimizers`` previously looped over both keys and stepped *both* optimizers on + every pass, so each took two Adam steps per batch. + """ + tomo = _inr_tomography(torch_device) + tomo.optimizer_params = { + "object": OptimizerParams.Adam(lr=1e-3), + "pose": OptimizerParams.Adam(lr=1e-2), + } + tomo.set_optimizers() + assert set(tomo.optimizers.keys()) == {"object", "pose"} # both optimizers live + + counts = {"object": 0, "pose": 0} + monkeypatch.setattr( + tomo.obj_model, + "step_optimizer", + lambda: counts.__setitem__("object", counts["object"] + 1), + ) + monkeypatch.setattr( + tomo.dset, "step_optimizer", lambda: counts.__setitem__("pose", counts["pose"] + 1) + ) + tomo.step_optimizers() + assert counts == {"object": 1, "pose": 1} + + def test_zero_grad_all_zeros_each_once(self, torch_device, monkeypatch): + """Companion to the step regression: zero_grad_all must touch each optimizer once.""" + tomo = _inr_tomography(torch_device) + tomo.optimizer_params = { + "object": OptimizerParams.Adam(lr=1e-3), + "pose": OptimizerParams.Adam(lr=1e-2), + } + tomo.set_optimizers() + counts = {"object": 0, "pose": 0} + monkeypatch.setattr( + tomo.obj_model, + "zero_optimizer_grad", + lambda: counts.__setitem__("object", counts["object"] + 1), + ) + monkeypatch.setattr( + tomo.dset, + "zero_optimizer_grad", + lambda: counts.__setitem__("pose", counts["pose"] + 1), + ) + tomo.zero_grad_all() + assert counts == {"object": 1, "pose": 1} + @requires_torch class TestSchedulerParams: @@ -141,3 +189,10 @@ def test_bad_scheduler_type_raises(self, torch_device): tomo = _inr_tomography(torch_device) with pytest.raises(TypeError): tomo.obj_model.scheduler_params = 123 + + def test_setter_does_not_mutate_caller_dict(self, torch_device): + """Regression: the setter must not inject missing keys into the caller's dict.""" + tomo = _inr_tomography(torch_device) + d = {"object": SchedulerParams.CosineAnnealing(T_max=10)} + tomo.scheduler_params = d + assert set(d.keys()) == {"object"} # "pose" must not have been added to the input From 077348176f833f0953dfd68459b64842b3964aba Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Tue, 2 Jun 2026 13:54:39 -0700 Subject: [PATCH 324/335] Tomography Slow/Fast Tests --- src/quantem/core/ml/ddp.py | 8 +- tests/tomography/test_dataset_models.py | 59 ++++++ tests/tomography/test_logger_tomography.py | 103 ++++++++++ tests/tomography/test_object_models.py | 79 ++++++++ tests/tomography/test_tomography.py | 177 ++++++++++++++++++ tests/tomography/test_tomography_cpu_recon.py | 44 +++++ tests/tomography/test_tomography_opt.py | 22 +++ 7 files changed, 490 insertions(+), 2 deletions(-) create mode 100644 tests/tomography/test_logger_tomography.py create mode 100644 tests/tomography/test_tomography.py create mode 100644 tests/tomography/test_tomography_cpu_recon.py diff --git a/src/quantem/core/ml/ddp.py b/src/quantem/core/ml/ddp.py index dedd90dd..18b7ccd7 100644 --- a/src/quantem/core/ml/ddp.py +++ b/src/quantem/core/ml/ddp.py @@ -61,6 +61,10 @@ def setup_dataloader( ): pin_mem = self.device.type == "cuda" persist = num_workers > 0 + # ``multiprocessing_context`` is only valid for multi-process loading; passing it with + # num_workers=0 raises ValueError (and num_workers=0 keeps the dataset in-process, which + # is what CPU / coverage runs use). + mp_ctx = "spawn" if num_workers > 0 else None if val_fraction > 0.0: train_dataset, val_dataset = random_split(dataset, [1 - val_fraction, val_fraction]) # type: ignore[reportArgumentType] --> dataset inherits from torch Dataset so this is fine. @@ -102,7 +106,7 @@ def setup_dataloader( pin_memory=pin_mem, drop_last=True, persistent_workers=persist, - multiprocessing_context="spawn", + multiprocessing_context=mp_ctx, worker_init_fn=worker_init_fn, ) @@ -116,7 +120,7 @@ def setup_dataloader( pin_memory=pin_mem, drop_last=False, persistent_workers=persist, - multiprocessing_context="spawn", + multiprocessing_context=mp_ctx, worker_init_fn=worker_init_fn, ) val_dataloader = val_dataloader diff --git a/tests/tomography/test_dataset_models.py b/tests/tomography/test_dataset_models.py index 9ebd99dc..04abd3b5 100644 --- a/tests/tomography/test_dataset_models.py +++ b/tests/tomography/test_dataset_models.py @@ -133,3 +133,62 @@ def test_len_and_getitem(self): item = ds[0] assert set(item.keys()) == {"coords", "target"} assert item["coords"].shape == (3,) + + +@requires_torch +class TestINRRayMath: + """The static ray helpers are pure tensor math (CPU), exercised here without a recon.""" + + def test_create_batch_rays_shape_and_endpoints(self): + N, S = 8, 5 + rays = TomographyINRDataset.create_batch_rays( + torch.tensor([0, N - 1]), torch.tensor([0, N - 1]), N=N, num_samples_per_ray=S + ) + assert rays.shape == (2, S, 3) + # pixel 0 maps to -1, pixel N-1 maps to +1 on both x (j) and y (i). + assert torch.allclose(rays[0, :, 0], torch.full((S,), -1.0)) + assert torch.allclose(rays[1, :, 0], torch.full((S,), 1.0)) + # z spans the full -1..1 sampling range. + assert torch.isclose(rays[0, 0, 2], torch.tensor(-1.0)) + assert torch.isclose(rays[0, -1, 2], torch.tensor(1.0)) + + def test_transform_batch_rays_identity_at_zero_pose(self): + rays = torch.rand(4, 6, 3) + zero = torch.zeros(4) + out = TomographyINRDataset.transform_batch_rays( + rays, z1=zero, x=zero, z3=zero, shifts=torch.zeros(4, 2), N=8, sampling_rate=1.0 + ) + # No rotation and no shift -> rays pass through unchanged. + assert torch.allclose(out, rays, atol=1e-5) + + def test_integrate_rays_sums_with_step_size(self): + B, S = 3, 5 + out = TomographyINRDataset.integrate_rays( + torch.ones(B, S), num_samples_per_ray=S, target_values_len=B + ) + step = 2.0 / (S - 1) + assert out.shape == (B,) + assert torch.allclose(out, torch.full((B,), S * step)) + + def test_getitem_index_mapping(self): + # n=4 -> H*W=16 per projection. idx=21 -> projection 1, remaining 5 -> i=1, j=1. + stack = _stack(nang=3, n=4) + d = TomographyINRDataset.from_data(stack, np.linspace(-60, 60, 3, dtype="f4")) + item = d[21] + assert int(item["projection_idx"]) == 1 + assert int(item["pixel_i"]) == 1 + assert int(item["pixel_j"]) == 1 + assert torch.isclose(item["target_value"], d.tilt_stack[1, 1, 1]) + + def test_save_load_parameters_roundtrip(self, tmp_path): + angles = np.linspace(-60, 60, 5, dtype="f4") + d = TomographyINRDataset.from_data(_stack(), angles) + d.to("cpu") + d.z1_params.data.fill_(0.37) + path = str(tmp_path / "params.pt") + d.save_parameters(path) + + d2 = TomographyINRDataset.from_data(_stack(), angles) + d2.to("cpu") + d2.load_parameters(path) + assert torch.allclose(d2.z1_params.detach(), d.z1_params.detach()) diff --git a/tests/tomography/test_logger_tomography.py b/tests/tomography/test_logger_tomography.py new file mode 100644 index 00000000..88d6bb26 --- /dev/null +++ b/tests/tomography/test_logger_tomography.py @@ -0,0 +1,103 @@ +"""Tests for ``quantem.tomography.logger_tomography``. + +``LoggerTomography`` is a thin tensorboard wrapper that the reconstruction loop only drives +when a ``log_dir`` is passed, so the end-to-end recon tests never exercise it. These CPU, +always-on tests construct a logger against a ``tmp_path`` and drive each method with small +stubs that expose only the attributes the logger reads, asserting the calls run and write +event files. Matplotlib backend is ``Agg`` (set in the root conftest), so figure logging is +headless. +""" + +from types import SimpleNamespace + +import numpy as np +import torch + +from quantem.tomography.logger_tomography import LoggerTomography + + +def _make_logger(tmp_path) -> LoggerTomography: + return LoggerTomography( + log_dir=str(tmp_path), + run_prefix="test_tomo", + run_suffix="", + log_images_every=1, + ) + + +def test_init_creates_log_dir(tmp_path): + logger = _make_logger(tmp_path) + try: + assert logger.log_dir.exists() + assert logger.log_dir.name.startswith("test_tomo_") + finally: + logger.close() + + +def test_log_epoch_writes_events(tmp_path): + logger = _make_logger(tmp_path) + try: + logger.log_epoch(epoch=0, loss=1.0, tilt_series_loss=0.8, soft_loss=0.2) + logger.flush() + events = list(logger.log_dir.glob("events.out.tfevents.*")) + assert events, "log_epoch should have written a tensorboard event file" + finally: + logger.close() + + +def test_log_iter_unpacks_learning_rates(tmp_path): + logger = _make_logger(tmp_path) + obj_model = SimpleNamespace(_soft_constraint_losses=[0.3]) + try: + logger.log_iter( + object_model=obj_model, + iter=2, + consistency_loss=0.5, + total_loss=0.7, + learning_rates={"object": 1e-3, "pose": 1e-2}, + num_samples_per_ray=16, + val_loss=0.4, + ) + logger.flush() + assert list(logger.log_dir.glob("events.out.tfevents.*")) + finally: + logger.close() + + +def test_log_iter_without_val_loss(tmp_path): + logger = _make_logger(tmp_path) + obj_model = SimpleNamespace(_soft_constraint_losses=[0.1]) + try: + # val_loss defaults to None -> the val branch must be skipped without error. + logger.log_iter( + object_model=obj_model, + iter=0, + consistency_loss=0.5, + total_loss=0.6, + learning_rates={}, + num_samples_per_ray=8, + ) + logger.flush() + finally: + logger.close() + + +def test_log_iter_images(tmp_path): + logger = _make_logger(tmp_path) + n_tilts = 5 + dataset_model = SimpleNamespace( + z1_params=torch.linspace(-1.0, 1.0, n_tilts), + z3_params=torch.linspace(1.0, -1.0, n_tilts), + shifts_params=torch.zeros(n_tilts, 2), + ) + pred_volume = np.random.default_rng(0).random((2, 6, 6, 6)).astype(np.float32) + try: + logger.log_iter_images( + pred_volume=pred_volume, + dataset_model=dataset_model, + iter=1, + ) + logger.flush() + assert list(logger.log_dir.glob("events.out.tfevents.*")) + finally: + logger.close() diff --git a/tests/tomography/test_object_models.py b/tests/tomography/test_object_models.py index b200d2b9..c4216827 100644 --- a/tests/tomography/test_object_models.py +++ b/tests/tomography/test_object_models.py @@ -193,3 +193,82 @@ def test_pretrain_not_implemented(self, torch_device): obj = ObjectTensorDecomp.from_model(self._model(), shape=(16, 16, 16), device=torch_device) with pytest.raises(NotImplementedError): obj.pretrain() + + +@requires_torch +class TestObjectINRBehaviour: + def _obj(self, device, n=16): + from quantem.core.ml.inr import HSiren + + model = HSiren(in_features=3, out_features=1, hidden_layers=1, hidden_features=8) + return ObjectINR.from_model(model, shape=(n, n, n), device=device) + + def test_forward_masks_out_of_range(self, torch_device): + obj = self._obj(torch_device) + coords = torch.tensor([[0.0, 0.0, 0.0], [5.0, 0.0, 0.0]], device=torch_device) + out = obj.forward(coords) + assert out.shape[0] == 2 + assert float(out[1].detach()) == 0.0 # x=5 is outside [-1, 1] -> masked to zero + + def test_apply_hard_constraints_positivity(self, torch_device): + obj = self._obj(torch_device) + obj.constraints.positivity = True + pred = torch.tensor([-1.0, 0.5, 2.0], device=torch_device) + assert torch.all(obj.apply_hard_constraints(pred) >= 0.0) + + def test_get_tv_loss_scalar(self, torch_device): + obj = self._obj(torch_device) + obj.constraints.tv_vol = 1.0 + coords = torch.rand(64, 3, device=torch_device) * 2 - 1 + ctx = ReconstructionContext(coords=coords, pred=torch.rand(64, device=torch_device)) + loss = obj.get_tv_loss(ctx) + assert loss.ndim == 0 + assert torch.isfinite(loss) + + +@requires_torch +class TestObjectTensorDecompTV: + def _obj(self, device, n=16): + from quantem.core.ml.models.kplanes import KPlanesTILTED + + model = KPlanesTILTED( + M_features=2, resolution=(n, n, n), multiscale_res_multipliers=[1], T=2 + ) + return ObjectTensorDecomp.from_model(model, shape=(n, n, n), device=device) + + def test_apply_hard_constraints_positivity(self, torch_device): + obj = self._obj(torch_device) + obj.constraints.positivity = True + pred = torch.tensor([-2.0, 0.0, 3.0], device=torch_device) + assert torch.all(obj.apply_hard_constraints(pred) >= 0.0) + + def test_plane_tv_loss_nonneg_scalar(self, torch_device): + obj = self._obj(torch_device) + obj.constraints.tv_plane = 0.1 + loss = obj._get_plane_tv_loss() + assert loss.ndim == 0 + assert float(loss.detach()) >= 0.0 + + def test_volume_tv_loss_scalar(self, torch_device): + obj = self._obj(torch_device) + obj.constraints.tv_vol = 0.1 + coords = torch.rand(64, 3, device=torch_device) * 2 - 1 + loss = obj.get_volume_tv_loss(coords) + assert loss.ndim == 0 + assert torch.isfinite(loss) + + def test_normalize_optimizer_params_rejects_non_dict(self, torch_device): + from quantem.core.ml.optimizer_mixin import OptimizerParams + + obj = self._obj(torch_device) + with pytest.raises(TypeError): + obj._normalize_optimizer_params([OptimizerParams.Adam()]) + + def test_normalize_optimizer_params_rejects_wrong_keys(self, torch_device): + from quantem.core.ml.optimizer_mixin import OptimizerParams + + obj = self._obj(torch_device) + with pytest.raises(ValueError): + obj._normalize_optimizer_params( + {"grids": OptimizerParams.Adam(), "wrong": OptimizerParams.Adam()} + ) diff --git a/tests/tomography/test_tomography.py b/tests/tomography/test_tomography.py new file mode 100644 index 00000000..da71f9c9 --- /dev/null +++ b/tests/tomography/test_tomography.py @@ -0,0 +1,177 @@ +"""Tests for the ``Tomography`` / ``TomographyConventional`` orchestrators and the shared +``TomographyBase`` plumbing, without running a reconstruction. + +The ``TomographyConventional`` path uses ``ObjectPixelated`` (no DDP setup), so it builds on +CPU and is always-on -- this exercises the bulk of ``tomography_base.py`` (factory, property +setters/validation, loss accessors). The ``Tomography`` (INR) factory and ``save_volume`` go +through ``setup_distributed`` and so follow the ``torch_device`` fixture under +``requires_torch`` (build on CUDA when present; see conftest). +""" + +import numpy as np +import pytest +import torch + +from quantem.tomography.dataset_models import TomographyINRDataset, TomographyPixDataset +from quantem.tomography.object_models import ObjConstraintParams, ObjectINR, ObjectPixelated +from quantem.tomography.tomography import Tomography, TomographyConventional +from quantem.tomography.tomography_lite import TomographyLiteINR + +from .conftest import requires_torch + + +def _stack(nang=5, n=12, seed=0): + rng = np.random.default_rng(seed) + return (rng.random((nang, n, n)) * 10).astype(np.float32) + + +def _conventional(n=12): + angles = np.linspace(-60, 60, 5).astype(np.float32) + dset = TomographyPixDataset.from_data(_stack(nang=5, n=n), angles) + obj = ObjectPixelated.from_uniform(shape=(n, n, n), device="cpu") + return TomographyConventional.from_models( + dset=dset, obj_model=obj, device="cpu", verbose=False + ) + + +class TestConventionalFactory: + def test_from_models_builds(self): + tomo = _conventional() + assert isinstance(tomo, TomographyConventional) + assert isinstance(tomo.obj_model, ObjectPixelated) + assert isinstance(tomo.dset, TomographyPixDataset) + assert tomo.num_epochs == 0 + + def test_direct_init_requires_token(self): + with pytest.raises(RuntimeError): + TomographyConventional( + dset=TomographyPixDataset.from_data( + _stack(), np.linspace(-60, 60, 5).astype(np.float32) + ), + obj_model=ObjectPixelated.from_uniform(shape=(12, 12, 12), device="cpu"), + ) + + +class TestBaseProperties: + def test_constraints_setter_dict_and_object(self): + tomo = _conventional() + tomo.constraints = {"name": "obj_pixelated", "tv_vol": 0.02} + assert isinstance(tomo.constraints, ObjConstraintParams.ObjPixelatedConstraints) + assert tomo.constraints.tv_vol == 0.02 + obj_c = ObjConstraintParams.ObjPixelatedConstraints(positivity=True) + tomo.constraints = obj_c + assert tomo.constraints is obj_c + + def test_constraints_setter_none_is_noop(self): + tomo = _conventional() + before = tomo.constraints + tomo.constraints = None + assert tomo.constraints is before + + def test_constraints_setter_invalid_raises(self): + tomo = _conventional() + with pytest.raises(ValueError): + tomo.constraints = 1.0 + + def test_logger_setter_rejects_wrong_type(self): + tomo = _conventional() + with pytest.raises(TypeError): + tomo.logger = "not a logger" + + def test_dset_setter_rejects_wrong_type(self): + tomo = _conventional() + with pytest.raises(TypeError): + tomo.dset = object() + + def test_loss_accessors_start_empty(self): + tomo = _conventional() + assert tomo.epoch_losses.shape == (0,) + assert tomo.consistency_losses.shape == (0,) + assert tomo.learning_rates == {} + + def test_append_learning_rates_accumulates(self): + tomo = _conventional() + tomo.append_learning_rates({"object": 1e-3, "pose": 1e-2}) + tomo.append_learning_rates({"object": 5e-4, "pose": 5e-3}) + assert tomo.learning_rates["object"] == [1e-3, 5e-4] + assert tomo.learning_rates["pose"] == [1e-2, 5e-3] + + def test_to_updates_device(self): + tomo = _conventional() + tomo.to("cpu") + assert torch.device(tomo.device) == torch.device("cpu") + + def test_plot_losses_runs(self): + tomo = _conventional() + tomo._epoch_losses.extend([1.0, 0.5, 0.25]) + tomo.plot_losses() # Agg backend; plt.show() is a no-op + + +@requires_torch +class TestInrFactory: + def _inr_tomo(self, device, n=16): + from quantem.core.ml.inr import HSiren + + model = HSiren(in_features=3, out_features=1, hidden_layers=1, hidden_features=8) + obj = ObjectINR.from_model(model, shape=(n, n, n), device=device) + dset = TomographyINRDataset.from_data( + _stack(nang=5, n=n), np.linspace(-60, 60, 5).astype(np.float32) + ) + return Tomography.from_models(dset=dset, obj_model=obj, device=device, verbose=False) + + def test_from_models_builds(self, torch_device): + tomo = self._inr_tomo(torch_device) + assert isinstance(tomo, Tomography) + assert isinstance(tomo.obj_model, ObjectINR) + + def test_plot_losses_runs(self, torch_device): + tomo = self._inr_tomo(torch_device) + tomo._epoch_losses.extend([1.0, 0.5]) + tomo._lrs["object"] = [1e-3, 5e-4] + tomo.plot_losses() + + def test_save_volume_overwrite_guard(self, torch_device, tmp_path): + tomo = self._inr_tomo(torch_device) + path = str(tmp_path / "vol.npz") + tomo.save_volume(path) + assert (tmp_path / "vol.npz").exists() + with pytest.raises(FileExistsError): + tomo.save_volume(path) + tomo.save_volume(path, overwrite=True) # must not raise + with np.load(path) as data: + assert "volume" in data + + +@requires_torch +class TestLiteINRReconstructBranch: + """``TomographyLiteINR.reconstruct`` bundles optimizer/scheduler params only on the first + epoch and passes ``None`` afterwards. Stub out the heavy ``Tomography.reconstruct`` to + assert the branch without running a reconstruction.""" + + def _lite(self, device, n=12): + return TomographyLiteINR.from_dataset( + tilt_series=_stack(nang=5, n=n), + tilt_angles=np.linspace(-60, 60, 5).astype(np.float32), + device=device, + ) + + def test_param_bundling_first_then_subsequent(self, torch_device, monkeypatch): + tomo = self._lite(torch_device) + captured = {} + + def fake_reconstruct(self, **kwargs): + captured.clear() + captured.update(kwargs) + self._epoch_losses.append(1.0) # mark an epoch as having run + + monkeypatch.setattr(Tomography, "reconstruct", fake_reconstruct) + + # First call (num_epochs == 0): object + pose params are assembled. + tomo.reconstruct(num_iter=1, num_workers=0, learn_pose=True) + assert set(captured["optimizer_params"].keys()) == {"object", "pose"} + assert set(captured["scheduler_params"].keys()) == {"object", "pose"} + + # Second call (num_epochs > 0): params are passed through as None. + tomo.reconstruct(num_iter=1, num_workers=0) + assert captured["optimizer_params"] is None + assert captured["scheduler_params"] is None diff --git a/tests/tomography/test_tomography_cpu_recon.py b/tests/tomography/test_tomography_cpu_recon.py new file mode 100644 index 00000000..0ebf7752 --- /dev/null +++ b/tests/tomography/test_tomography_cpu_recon.py @@ -0,0 +1,44 @@ +"""CPU INR reconstruction smoke test. + +The GPU INR tests (``test_tomography_inr.py``) are gated behind ``requires_gpu``, so on +CPU-only CI the entire learned-reconstruction loop in ``Tomography.reconstruct`` is never +exercised. This runs a tiny INR reconstruction on CPU with ``num_workers=0`` -- valid since +``DDPMixin.setup_dataloader`` now only sets ``multiprocessing_context`` when +``num_workers > 0`` -- so that path is covered in CI. + +It is skipped when CUDA is present: object models that go through ``setup_distributed`` must +be built on CUDA when a CUDA device exists (the CPU path is only valid with no CUDA device), +and on GPU machines the ``requires_gpu`` tests already cover this path. Marked ``slow`` so it +runs under ``--runslow``. +""" + +import numpy as np +import pytest +import torch + +from quantem.tomography.tomography_lite import TomographyLiteINR + +pytestmark = [ + pytest.mark.slow, + pytest.mark.skipif( + torch.cuda.is_available(), + reason="CPU INR path is only valid without CUDA; GPU machines use the requires_gpu tests", + ), +] + + +def test_cpu_inr_reconstruct_reduces_loss(): + rng = np.random.default_rng(0) + n = 12 + series = (rng.random((5, n, n)) * 10).astype(np.float32) + angles = np.linspace(-60, 60, 5).astype(np.float32) + + tomo = TomographyLiteINR.from_dataset(tilt_series=series, tilt_angles=angles, device="cpu") + tomo.reconstruct(num_iter=2, num_workers=0, batch_size=64, learn_pose=True) + + losses = tomo.epoch_losses + assert len(losses) == 2 + assert losses[-1] < losses[0] + view = tomo.obj_model.obj_view + assert view.shape == (1, n, n, n) + assert np.isfinite(view).all() diff --git a/tests/tomography/test_tomography_opt.py b/tests/tomography/test_tomography_opt.py index 6c597c90..bae00ad9 100644 --- a/tests/tomography/test_tomography_opt.py +++ b/tests/tomography/test_tomography_opt.py @@ -161,6 +161,28 @@ def test_zero_grad_all_zeros_each_once(self, torch_device, monkeypatch): assert counts == {"object": 1, "pose": 1} +@requires_torch +class TestOptHelpers: + def test_get_default_lr_object_and_pose(self, torch_device): + tomo = _inr_tomography(torch_device) + assert isinstance(tomo._get_default_lr("object"), float) + assert isinstance(tomo._get_default_lr("pose"), float) + + def test_get_default_lr_unknown_raises(self, torch_device): + tomo = _inr_tomography(torch_device) + with pytest.raises(ValueError): + tomo._get_default_lr("banana") + + def test_remove_optimizer_unknown_raises(self, torch_device): + tomo = _inr_tomography(torch_device) + with pytest.raises(ValueError): + tomo.remove_optimizer("banana") + + def test_current_lrs_zero_without_optimizers(self, torch_device): + tomo = _inr_tomography(torch_device) + assert tomo.get_current_lrs() == {"object": 0.0, "pose": 0.0} + + @requires_torch class TestSchedulerParams: def test_scheduler_setter_getter(self, torch_device): From aac7239153132fb8c58eabfa36e60fe823345048 Mon Sep 17 00:00:00 2001 From: quantem-bot Date: Mon, 8 Jun 2026 13:11:18 +0000 Subject: [PATCH 325/335] chore: update lock file --- uv.lock | 163 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 82 insertions(+), 81 deletions(-) diff --git a/uv.lock b/uv.lock index 0e8424aa..34073ea1 100644 --- a/uv.lock +++ b/uv.lock @@ -166,27 +166,27 @@ wheels = [ [[package]] name = "beautifulsoup4" -version = "4.14.3" +version = "4.15.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "soupsieve" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/65/318323f98dbee45d42dff61d8f047181bc6f2268a9068cfad035a46be5af/beautifulsoup4-4.15.0.tar.gz", hash = "sha256:288e3ca7d54b06f2ac191970bc275c1939cb46d450b255bf6718b04aa37ab4f7", size = 632571, upload-time = "2026-06-07T16:44:20.453Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, + { url = "https://files.pythonhosted.org/packages/88/c6/92fcd42f1ba33e1184263f25bfabf3d27c383410470f169e4b8163bf9c17/beautifulsoup4-4.15.0-py3-none-any.whl", hash = "sha256:d6f88de62e1d4e38ecb1077eb9724cd0eff29d2a08ca16a401e9b9e93f117cf9", size = 109924, upload-time = "2026-06-07T16:44:21.566Z" }, ] [[package]] name = "bleach" -version = "6.3.0" +version = "6.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "webencodings" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/07/18/3c8523962314be6bf4c8989c79ad9531c825210dd13a8669f6b84336e8bd/bleach-6.3.0.tar.gz", hash = "sha256:6f3b91b1c0a02bb9a78b5a454c92506aa0fdf197e1d5e114d2e00c6f64306d22", size = 203533, upload-time = "2025-10-27T17:57:39.211Z" } +sdist = { url = "https://files.pythonhosted.org/packages/48/3c/e12ac860709702bd5ebeb9b56a4fe334f1001246ee1b8f2b7ee28912df7d/bleach-6.4.0.tar.gz", hash = "sha256:4202482733d85cedd04e59fcb2f89f4e4c7c385a78d3c3c23c30446843a37452", size = 204857, upload-time = "2026-06-05T13:01:13.734Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl", hash = "sha256:fe10ec77c93ddf3d13a73b035abaac7a9f5e436513864ccdad516693213c65d6", size = 164437, upload-time = "2025-10-27T17:57:37.538Z" }, + { url = "https://files.pythonhosted.org/packages/58/9d/40b6267367182187139a4000b82a3b287d84d745bccd808e75d916920e9d/bleach-6.4.0-py3-none-any.whl", hash = "sha256:4b6b6a54fff2e69a3dde9d21cc6301220bee3c3cb792187d11403fd795031081", size = 165109, upload-time = "2026-06-05T13:01:12.504Z" }, ] [package.optional-dependencies] @@ -737,27 +737,27 @@ array = [ [[package]] name = "debugpy" -version = "1.8.20" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/b7/cd8080344452e4874aae67c40d8940e2b4d47b01601a8fd9f44786c757c7/debugpy-1.8.20.tar.gz", hash = "sha256:55bc8701714969f1ab89a6d5f2f3d40c36f91b2cbe2f65d98bf8196f6a6a2c33", size = 1645207, upload-time = "2026-01-29T23:03:28.199Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/56/c3baf5cbe4dd77427fd9aef99fcdade259ad128feeb8a786c246adb838e5/debugpy-1.8.20-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:eada6042ad88fa1571b74bd5402ee8b86eded7a8f7b827849761700aff171f1b", size = 2208318, upload-time = "2026-01-29T23:03:36.481Z" }, - { url = "https://files.pythonhosted.org/packages/9a/7d/4fa79a57a8e69fe0d9763e98d1110320f9ecd7f1f362572e3aafd7417c9d/debugpy-1.8.20-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:7de0b7dfeedc504421032afba845ae2a7bcc32ddfb07dae2c3ca5442f821c344", size = 3171493, upload-time = "2026-01-29T23:03:37.775Z" }, - { url = "https://files.pythonhosted.org/packages/7d/f2/1e8f8affe51e12a26f3a8a8a4277d6e60aa89d0a66512f63b1e799d424a4/debugpy-1.8.20-cp311-cp311-win32.whl", hash = "sha256:773e839380cf459caf73cc533ea45ec2737a5cc184cf1b3b796cd4fd98504fec", size = 5209240, upload-time = "2026-01-29T23:03:39.109Z" }, - { url = "https://files.pythonhosted.org/packages/d5/92/1cb532e88560cbee973396254b21bece8c5d7c2ece958a67afa08c9f10dc/debugpy-1.8.20-cp311-cp311-win_amd64.whl", hash = "sha256:1f7650546e0eded1902d0f6af28f787fa1f1dbdbc97ddabaf1cd963a405930cb", size = 5233481, upload-time = "2026-01-29T23:03:40.659Z" }, - { url = "https://files.pythonhosted.org/packages/14/57/7f34f4736bfb6e00f2e4c96351b07805d83c9a7b33d28580ae01374430f7/debugpy-1.8.20-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:4ae3135e2089905a916909ef31922b2d733d756f66d87345b3e5e52b7a55f13d", size = 2550686, upload-time = "2026-01-29T23:03:42.023Z" }, - { url = "https://files.pythonhosted.org/packages/ab/78/b193a3975ca34458f6f0e24aaf5c3e3da72f5401f6054c0dfd004b41726f/debugpy-1.8.20-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:88f47850a4284b88bd2bfee1f26132147d5d504e4e86c22485dfa44b97e19b4b", size = 4310588, upload-time = "2026-01-29T23:03:43.314Z" }, - { url = "https://files.pythonhosted.org/packages/c1/55/f14deb95eaf4f30f07ef4b90a8590fc05d9e04df85ee379712f6fb6736d7/debugpy-1.8.20-cp312-cp312-win32.whl", hash = "sha256:4057ac68f892064e5f98209ab582abfee3b543fb55d2e87610ddc133a954d390", size = 5331372, upload-time = "2026-01-29T23:03:45.526Z" }, - { url = "https://files.pythonhosted.org/packages/a1/39/2bef246368bd42f9bd7cba99844542b74b84dacbdbea0833e610f384fee8/debugpy-1.8.20-cp312-cp312-win_amd64.whl", hash = "sha256:a1a8f851e7cf171330679ef6997e9c579ef6dd33c9098458bd9986a0f4ca52e3", size = 5372835, upload-time = "2026-01-29T23:03:47.245Z" }, - { url = "https://files.pythonhosted.org/packages/15/e2/fc500524cc6f104a9d049abc85a0a8b3f0d14c0a39b9c140511c61e5b40b/debugpy-1.8.20-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:5dff4bb27027821fdfcc9e8f87309a28988231165147c31730128b1c983e282a", size = 2539560, upload-time = "2026-01-29T23:03:48.738Z" }, - { url = "https://files.pythonhosted.org/packages/90/83/fb33dcea789ed6018f8da20c5a9bc9d82adc65c0c990faed43f7c955da46/debugpy-1.8.20-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:84562982dd7cf5ebebfdea667ca20a064e096099997b175fe204e86817f64eaf", size = 4293272, upload-time = "2026-01-29T23:03:50.169Z" }, - { url = "https://files.pythonhosted.org/packages/a6/25/b1e4a01bfb824d79a6af24b99ef291e24189080c93576dfd9b1a2815cd0f/debugpy-1.8.20-cp313-cp313-win32.whl", hash = "sha256:da11dea6447b2cadbf8ce2bec59ecea87cc18d2c574980f643f2d2dfe4862393", size = 5331208, upload-time = "2026-01-29T23:03:51.547Z" }, - { url = "https://files.pythonhosted.org/packages/13/f7/a0b368ce54ffff9e9028c098bd2d28cfc5b54f9f6c186929083d4c60ba58/debugpy-1.8.20-cp313-cp313-win_amd64.whl", hash = "sha256:eb506e45943cab2efb7c6eafdd65b842f3ae779f020c82221f55aca9de135ed7", size = 5372930, upload-time = "2026-01-29T23:03:53.585Z" }, - { url = "https://files.pythonhosted.org/packages/33/2e/f6cb9a8a13f5058f0a20fe09711a7b726232cd5a78c6a7c05b2ec726cff9/debugpy-1.8.20-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:9c74df62fc064cd5e5eaca1353a3ef5a5d50da5eb8058fcef63106f7bebe6173", size = 2538066, upload-time = "2026-01-29T23:03:54.999Z" }, - { url = "https://files.pythonhosted.org/packages/c5/56/6ddca50b53624e1ca3ce1d1e49ff22db46c47ea5fb4c0cc5c9b90a616364/debugpy-1.8.20-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:077a7447589ee9bc1ff0cdf443566d0ecf540ac8aa7333b775ebcb8ce9f4ecad", size = 4269425, upload-time = "2026-01-29T23:03:56.518Z" }, - { url = "https://files.pythonhosted.org/packages/c5/d9/d64199c14a0d4c476df46c82470a3ce45c8d183a6796cfb5e66533b3663c/debugpy-1.8.20-cp314-cp314-win32.whl", hash = "sha256:352036a99dd35053b37b7803f748efc456076f929c6a895556932eaf2d23b07f", size = 5331407, upload-time = "2026-01-29T23:03:58.481Z" }, - { url = "https://files.pythonhosted.org/packages/e0/d9/1f07395b54413432624d61524dfd98c1a7c7827d2abfdb8829ac92638205/debugpy-1.8.20-cp314-cp314-win_amd64.whl", hash = "sha256:a98eec61135465b062846112e5ecf2eebb855305acc1dfbae43b72903b8ab5be", size = 5372521, upload-time = "2026-01-29T23:03:59.864Z" }, - { url = "https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl", hash = "sha256:5be9bed9ae3be00665a06acaa48f8329d2b9632f15fd09f6a9a8c8d9907e54d7", size = 5337658, upload-time = "2026-01-29T23:04:17.404Z" }, +version = "1.8.21" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/aa/12037145b7a56eaa5b29b41872f7a21b538e807e13f32c4d3c46e59be084/debugpy-1.8.21.tar.gz", hash = "sha256:a3c53278e84c94e11bd87c53970ec391d1a67396c8b22609fcac576520e611a6", size = 1697577, upload-time = "2026-06-01T19:30:35.156Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/fb/cbf306d6e07a313a91e7171a98669054502840931432c227cfd505ee367f/debugpy-1.8.21-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:da456226c7b4c69e35dbe35dcee6623d912000a77816db7856a41af1c72a0264", size = 2203120, upload-time = "2026-06-01T19:30:43.964Z" }, + { url = "https://files.pythonhosted.org/packages/aa/57/aa739bd4ad2cbf96aeb1b20b56918ddd5ae4c28b68709bfcd327f02123ee/debugpy-1.8.21-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:f68b891688e61bdc08b8d364d919ff0051e0b94657b39dcd027bc3173edb7cdc", size = 3059958, upload-time = "2026-06-01T19:30:45.622Z" }, + { url = "https://files.pythonhosted.org/packages/a8/31/453d2c9a23d133fe2c8ec7ca1d816ded52a913487fe3ffef7c01b4b706af/debugpy-1.8.21-cp311-cp311-win32.whl", hash = "sha256:f843a8b08c2edeaf9b1582eed4f25441af21a297c22ff16bf76a662557aa9c9e", size = 5236515, upload-time = "2026-06-01T19:30:47.461Z" }, + { url = "https://files.pythonhosted.org/packages/60/94/6660de2f2d7bf388f229335ba4637646eebabdbf38564cb439a95a9193c9/debugpy-1.8.21-cp311-cp311-win_amd64.whl", hash = "sha256:84c564d8cc701d41843b29a92814c1f1bef6798724ca9d675c284ad9f6a547d7", size = 5256138, upload-time = "2026-06-01T19:30:49.113Z" }, + { url = "https://files.pythonhosted.org/packages/a2/df/bf625547431a9cadc9f4cbfeda38866e2b17f6aed147b625377e87834449/debugpy-1.8.21-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:9f96713896f39c3dff0ee841f47320c3f2983d33c341e009361bb0ebc79adc4e", size = 2483609, upload-time = "2026-06-01T19:30:50.794Z" }, + { url = "https://files.pythonhosted.org/packages/bf/09/59324b903599031ff9faaec1758292409f6561a0ec2492fe4b703327705a/debugpy-1.8.21-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:c193d474f0a211191f2b4449d2d06157c689013035bd952f3b617e0ef422b176", size = 3968900, upload-time = "2026-06-01T19:30:52.341Z" }, + { url = "https://files.pythonhosted.org/packages/14/cd/27f65b805d7fe005c44e1a36b9183ecdfbcdbf9d3e721a5115d461ecc7ee/debugpy-1.8.21-cp312-cp312-win32.whl", hash = "sha256:4743373c1cac7f9e74a1b9915bf1dbe0e900eca657ffb170ae07ac8363205ae9", size = 5336340, upload-time = "2026-06-01T19:30:54.047Z" }, + { url = "https://files.pythonhosted.org/packages/77/1d/c84e30c0c674184948b66f076ab271c01d940618a2824c23cd035a27bc20/debugpy-1.8.21-cp312-cp312-win_amd64.whl", hash = "sha256:bd7ba9dd3daa7c2f942c6ca8d4695a16bf9ac16b63615261c7982bc74f7ed20c", size = 5374751, upload-time = "2026-06-01T19:30:55.891Z" }, + { url = "https://files.pythonhosted.org/packages/77/6b/d817e1f8cc77aa055d37fba092e0febfdff40fe652d8d53d4cd7a86ad98d/debugpy-1.8.21-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:13678151fc401e2d68c9880b91e28714f797d40422994572b24560ef80910a88", size = 2477398, upload-time = "2026-06-01T19:30:57.644Z" }, + { url = "https://files.pythonhosted.org/packages/48/57/412421516afc3055fa577516f00beec3d663f9b0ab330639547ae6c57720/debugpy-1.8.21-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:ecbd158386c31ffe71d46f72d44d56e66331ab9b16cad649156d514368f23ab2", size = 3962096, upload-time = "2026-06-01T19:30:59.235Z" }, + { url = "https://files.pythonhosted.org/packages/c1/62/2c616337cf6ba7b07ebbc97f02c6c945a8e2f76b365e33ee809c32ee36d1/debugpy-1.8.21-cp313-cp313-win32.whl", hash = "sha256:2c2ae706dec41d99a9ca1f7ebc987a83e65578363be6f6b3ac9067504917fae1", size = 5336288, upload-time = "2026-06-01T19:31:00.79Z" }, + { url = "https://files.pythonhosted.org/packages/f8/99/9175103392f84c4b1bf7622888cdc68da07f0ff7d9e581266428f6776033/debugpy-1.8.21-cp313-cp313-win_amd64.whl", hash = "sha256:aa648733047443eb1d07682c4ef287d36a54507b643ffdf38b09a3ef002c72a0", size = 5376567, upload-time = "2026-06-01T19:31:02.56Z" }, + { url = "https://files.pythonhosted.org/packages/ce/3d/f4bbb323a548bfab2af3d6b4ffd9bf22636e55956a1285d317a1de643aad/debugpy-1.8.21-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:9bb2a685287a2ac9b181cde89edcec64845cb51de7faaa75badb9a698bc24782", size = 2477209, upload-time = "2026-06-01T19:31:04.157Z" }, + { url = "https://files.pythonhosted.org/packages/8c/2d/6e7ec524984a1702777868de49a4c53202bddac2a432a76a093469587750/debugpy-1.8.21-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:3d6922439bf33fd38a3e2c447869ebc7b97da5cd3d329ff1ef9bc06c4903437e", size = 3927115, upload-time = "2026-06-01T19:31:05.863Z" }, + { url = "https://files.pythonhosted.org/packages/97/47/d1aa6d64005a98a9144647d99306b419396f9ad7bf1d73c119e17a81fb4d/debugpy-1.8.21-cp314-cp314-win32.whl", hash = "sha256:15d4963bd5ffa48f0da0947fd06757fa7621945048a14ad7705431566d3c0e7c", size = 5336724, upload-time = "2026-06-01T19:31:07.711Z" }, + { url = "https://files.pythonhosted.org/packages/5f/67/b905b90d163af11878c1af8abafa4a25206335e112e284e413454543a6da/debugpy-1.8.21-cp314-cp314-win_amd64.whl", hash = "sha256:fe0744a12353406de0ae8ccff0d0a4a666f00801a3db8fd04e7a5f761cd520e8", size = 5373803, upload-time = "2026-06-01T19:31:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/95/51/67e7cf11a53e40694f720457d5b3a1cdaaa3d5a9a633e482f225456b93ff/debugpy-1.8.21-py2.py3-none-any.whl", hash = "sha256:b1e37d333663c8851516a47364ef473da127f9caebe4417e6df6f5825a7e9a92", size = 5352888, upload-time = "2026-06-01T19:31:25.186Z" }, ] [[package]] @@ -789,11 +789,11 @@ wheels = [ [[package]] name = "distlib" -version = "0.4.0" +version = "0.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +sdist = { url = "https://files.pythonhosted.org/packages/86/b2/d6fc3f2347f43dada79e5ff118493e8109c98400a0e29a1d5264a3aa479b/distlib-0.4.1.tar.gz", hash = "sha256:c3804d0d2d4b5fcd44036eb860cb6660485fcdf5c2aba53dc324d805837ea65b", size = 610526, upload-time = "2026-06-02T11:17:40.691Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, + { url = "https://files.pythonhosted.org/packages/25/18/3497c4fa83a76dcb154923fd2075522e8dd6995ecee4093c00ae18160046/distlib-0.4.1-py2.py3-none-any.whl", hash = "sha256:9c2c552c68cbadc619f2d0ed3a69e27c351a3f4c9baa9ffb7df9e9cdc3d19a97", size = 469216, upload-time = "2026-06-02T11:17:38.779Z" }, ] [[package]] @@ -828,11 +828,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.29.0" +version = "3.29.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b5/fe/997687a931ab51049acce6fa1f23e8f01216374ea81374ddee763c493db5/filelock-3.29.0.tar.gz", hash = "sha256:69974355e960702e789734cb4871f884ea6fe50bd8404051a3530bc07809cf90", size = 57571, upload-time = "2026-04-19T15:39:10.068Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/f9/f38573ed5844586db374d085911740a501ccfa373b455fc9413f09f85237/filelock-3.29.1.tar.gz", hash = "sha256:d97e6b1b9757569626c58caa07dc4beb1613f4a2938b1e8cc81afca398906c9e", size = 59335, upload-time = "2026-06-03T15:19:04.053Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258", size = 39812, upload-time = "2026-04-19T15:39:08.752Z" }, + { url = "https://files.pythonhosted.org/packages/4c/a0/614c5fe402fd88951df45f4dda2fa3b4e17a99ecd92340771929169b3b95/filelock-3.29.1-py3-none-any.whl", hash = "sha256:85199dfd706869641b72b2e8955d5416a4b2b7dc4b0e8e6d97b4cc1299a6983b", size = 40750, upload-time = "2026-06-03T15:19:02.959Z" }, ] [[package]] @@ -1185,11 +1185,11 @@ wheels = [ [[package]] name = "idna" -version = "3.17" +version = "3.18" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b9/28/99c51f664567218d824af024c0251650fb27e4ca066df188dab0769c5b91/idna-3.17.tar.gz", hash = "sha256:5eb0cb53bc467c12eadcf6de83163ad8527cec9416f44b9b61b19caedad2b87f", size = 196048, upload-time = "2026-05-28T14:32:38.55Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/a7/f76514cc40ad6234098ecdebda08732d75964776c51a42845b7da10649e2/idna-3.17-py3-none-any.whl", hash = "sha256:466e48829084efe2548012b855df21540b96f2e20e51bd124c851536556a592c", size = 65316, upload-time = "2026-05-28T14:32:37.035Z" }, + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, ] [[package]] @@ -1252,7 +1252,7 @@ wheels = [ [[package]] name = "ipython" -version = "9.14.0" +version = "9.14.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -1268,9 +1268,9 @@ dependencies = [ { name = "traitlets" }, { name = "typing-extensions", marker = "python_full_version < '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/21/c2/c0064cf15d026501a1ef70e42efd9c3f818663089399aacc5e37a82901c1/ipython-9.14.0.tar.gz", hash = "sha256:6f27ff0f1d9ea050e0551f71568bc4b34d8aba579e8f111c5b4175f44ac6b4aa", size = 4432601, upload-time = "2026-05-29T15:13:24.611Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/23/3a27530575643c8bb7bfc757a28e2e7ef80092afbf59a2bc5716320b6602/ipython-9.14.1.tar.gz", hash = "sha256:f913bf74df06d458e46ced84ca506c23797590d594b236fe60b14df213291e7b", size = 4433457, upload-time = "2026-06-05T08:12:34.921Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/a3/9e59340f02c1dc8f8c0a05b09244712b8609eb5439f9996e887e2b82f452/ipython-9.14.0-py3-none-any.whl", hash = "sha256:8fd984a3372c14b12790b084ba6b5cff5678c0cb063244a0034f06a51f20d6c2", size = 627457, upload-time = "2026-05-29T15:13:22.942Z" }, + { url = "https://files.pythonhosted.org/packages/9d/22/58818a63eaf8982b67632b1bc20585c811611b15a8da19d6012323dc76a5/ipython-9.14.1-py3-none-any.whl", hash = "sha256:5d4a9ecaa3b10e6e5f269dd0948bdb58ca9cb851899cd23e07c320d3eb11613c", size = 627770, upload-time = "2026-06-05T08:12:33.045Z" }, ] [[package]] @@ -1397,7 +1397,7 @@ wheels = [ [[package]] name = "jupyter-client" -version = "8.8.0" +version = "8.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jupyter-core" }, @@ -1405,10 +1405,11 @@ dependencies = [ { name = "pyzmq" }, { name = "tornado" }, { name = "traitlets" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/05/e4/ba649102a3bc3fbca54e7239fb924fd434c766f855693d86de0b1f2bec81/jupyter_client-8.8.0.tar.gz", hash = "sha256:d556811419a4f2d96c869af34e854e3f059b7cc2d6d01a9cd9c85c267691be3e", size = 348020, upload-time = "2026-01-08T13:55:47.938Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/a2/9ef7832e6c7d619a35bf6732d199a850ef3b6db4af1cd783a71f81eaeab0/jupyter_client-8.9.0.tar.gz", hash = "sha256:23c0c182e1901ffdab96b5a02cb7bc6f0b04524fd7fc43688a14c4ff2308fb77", size = 358714, upload-time = "2026-06-05T12:17:44.014Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl", hash = "sha256:f93a5b99c5e23a507b773d3a1136bd6e16c67883ccdbd9a829b0bbdb98cd7d7a", size = 107371, upload-time = "2026-01-08T13:55:45.562Z" }, + { url = "https://files.pythonhosted.org/packages/ef/ce/93ccaca54d41327491b1f6d7341d0eef49f71e8929f875b53c45446335ca/jupyter_client-8.9.0-py3-none-any.whl", hash = "sha256:a0efc16adcec2bb6669d2cf91e3ba5337b338bd1ecd0d9c70940752fcb1144b2", size = 109723, upload-time = "2026-06-05T12:17:42.135Z" }, ] [[package]] @@ -1500,7 +1501,7 @@ wheels = [ [[package]] name = "jupyterlab" -version = "4.5.7" +version = "4.5.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "async-lru" }, @@ -1517,9 +1518,9 @@ dependencies = [ { name = "tornado" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2b/22/8440ec827762146e7cdecf04335bd348795899d29dc6ae82238707353a2c/jupyterlab-4.5.7.tar.gz", hash = "sha256:55a9822c4754da305f41e113452c68383e214dcf96de760146af89ce5d5117b0", size = 23992763, upload-time = "2026-04-29T16:43:51.328Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0e/74/089613e6099e851a6130816f2df592c839d8565f8746a701edada05a33e4/jupyterlab-4.5.8.tar.gz", hash = "sha256:af54d7242cc689a1e6c3ad213cc9b6d9781787d9ec67c52ec9a8f4707088cadd", size = 23994076, upload-time = "2026-06-04T12:32:12.906Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/aa/537b8f7d80e799af19af35fb3ddfc970b951088a13c57dd9387dcfbb7f61/jupyterlab-4.5.7-py3-none-any.whl", hash = "sha256:fba4cb0e2c44a52859669d8c98b45de029d5e515f8407bf8534d2a8fc5f0964d", size = 12450123, upload-time = "2026-04-29T16:43:46.639Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d1/56a400100559cbf154a23cd29989261941ae5c9f743898fc10e8a5508b7c/jupyterlab-4.5.8-py3-none-any.whl", hash = "sha256:7d514c856d0d607601ec7692374da4f26e2aaf3b6e7cd363136b422a50588d6c", size = 12449443, upload-time = "2026-06-04T12:32:08.442Z" }, ] [[package]] @@ -1898,7 +1899,7 @@ wheels = [ [[package]] name = "nbclient" -version = "0.10.4" +version = "0.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jupyter-client" }, @@ -1906,9 +1907,9 @@ dependencies = [ { name = "nbformat" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/56/91/1c1d5a4b9a9ebba2b4e32b8c852c2975c872aec1fe42ab5e516b2cecd193/nbclient-0.10.4.tar.gz", hash = "sha256:1e54091b16e6da39e297b0ece3e10f6f29f4ac4e8ee515d29f8a7099bd6553c9", size = 62554, upload-time = "2025-12-23T07:45:46.369Z" } +sdist = { url = "https://files.pythonhosted.org/packages/28/a5/b3bae4b590c0cbcada2c63a34f7580024e834a8ba213e949a2f906705787/nbclient-0.11.0.tar.gz", hash = "sha256:04a134a5b087f2c5887f228aca155db50169b8cd9334dee6942c8e927e56081a", size = 62535, upload-time = "2026-06-05T07:52:41.746Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl", hash = "sha256:9162df5a7373d70d606527300a95a975a47c137776cd942e52d9c7e29ff83440", size = 25465, upload-time = "2025-12-23T07:45:44.51Z" }, + { url = "https://files.pythonhosted.org/packages/36/c9/94d73e5a01c5b926c3fa2496e97d7a8dc28ed5a77c0b2ed712f1a62e6694/nbclient-0.11.0-py3-none-any.whl", hash = "sha256:ef7fa0d59d6e1d41103933d8a445a18d5de860ca6b613b87b8574accdb3c2895", size = 25288, upload-time = "2026-06-05T07:52:40.115Z" }, ] [[package]] @@ -3185,27 +3186,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.15" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/84/6f/a76f7d96e5c962f5b69cee865e49c15c1116897c01990faa8a57edb62e7f/ruff-0.15.15.tar.gz", hash = "sha256:b8dff018130b46d8e5bf0f926ef6b60cf871d6d5ae45fc9334e09632daa741d6", size = 4706985, upload-time = "2026-05-28T14:16:57.784Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/9d/3a45c05b8ab04b4705989de70a79008e27c8003296a0feaee9edc18dd7e9/ruff-0.15.15-py3-none-linux_armv6l.whl", hash = "sha256:cf93e5388f412e1b108b1f8b34a6e036b70fe8aff89393befad96fe48670311b", size = 10710652, upload-time = "2026-05-28T14:16:06.701Z" }, - { url = "https://files.pythonhosted.org/packages/05/66/da974431624bf3b49f6ee1f9543c02d929ff1cba78b0d5a79c38cf21f744/ruff-0.15.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac5a646d1f6a7dadd5d50842dae2c1f9862ac887ef5d1b1375e02def791fde6e", size = 11096615, upload-time = "2026-05-28T14:16:23.313Z" }, - { url = "https://files.pythonhosted.org/packages/8c/09/7443452e5d290230a712103f2fdceeef7184f3ec99a2bd01c8be78aaceb5/ruff-0.15.15-py3-none-macosx_11_0_arm64.whl", hash = "sha256:77d955a431430c66f72dd94e379ad38a16daea3d25094872ac4edf9e797be530", size = 10436683, upload-time = "2026-05-28T14:16:40.974Z" }, - { url = "https://files.pythonhosted.org/packages/53/01/d330c26a57fa4f3943a14424904027428315b700fe4d14a84bb123a649e5/ruff-0.15.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7614ee79c69788cf6cedd568069ade9cecc22a1ad20494efe8d0c9ebb4b622d4", size = 10769064, upload-time = "2026-05-28T14:16:28.905Z" }, - { url = "https://files.pythonhosted.org/packages/1d/85/cc8770f8bdff541b1da8392d1634141fe4a0e3f4ee596605959b7906c27f/ruff-0.15.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3cdb1679e06a1f6b47bc384714ae96f6e2fb65ca441eb78c43d2ca554176ce1f", size = 10511987, upload-time = "2026-05-28T14:16:43.732Z" }, - { url = "https://files.pythonhosted.org/packages/7c/29/8c190c1472b63013583ba391f3342036e02010544c1270455ed8e519bdf3/ruff-0.15.15-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2728b93d7b23a603ea2c0ac6eb73d760bd38ec9de35f35fb41e18f7a3fee7622", size = 11275100, upload-time = "2026-05-28T14:16:55.244Z" }, - { url = "https://files.pythonhosted.org/packages/9f/6b/7e145ce2cc8e63d6834eca03d83a0e18d121def5c69f91b4cf4011ed4879/ruff-0.15.15-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be582fcc0db438902c7792b08d6ddf6c9b9e21addaa10092c2c741cfb09e5a45", size = 12176903, upload-time = "2026-05-28T14:16:14.368Z" }, - { url = "https://files.pythonhosted.org/packages/80/a3/d5974637f68e451f7fadf015cf3101d1cd7d8ba5027cffe0b9e3826ebe6b/ruff-0.15.15-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7aa77465b8ecaf1a27bea098d696f7fed5e1eccbd10b321b682d6de586ae5627", size = 11404550, upload-time = "2026-05-28T14:16:20.138Z" }, - { url = "https://files.pythonhosted.org/packages/fe/1c/e6e5e568f22be4fb05d6244234aba384c06b451252453b821e1a529263cf/ruff-0.15.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48decfa11d740de4889de623be1463308346312f2409a56e24aa280c86162dc4", size = 11382027, upload-time = "2026-05-28T14:16:46.615Z" }, - { url = "https://files.pythonhosted.org/packages/1d/01/170921b49fcd2e8858825593f91cf7146c3e40a5c3e6df763e4bb0484dde/ruff-0.15.15-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a5015088452ca0081387063649ec67f06d3d1d6b8b936a1f836b5e9657ecd48c", size = 11366041, upload-time = "2026-05-28T14:16:26.247Z" }, - { url = "https://files.pythonhosted.org/packages/87/54/a7bad711d7de93254e15e06a4c375b89a03d18de45d3e5dcc86a4472fb1a/ruff-0.15.15-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f5294aab6356c81600fcdea3a62bb1b924dfd5e91767c12318d3f68f86af57cd", size = 10741795, upload-time = "2026-05-28T14:16:17.11Z" }, - { url = "https://files.pythonhosted.org/packages/c9/31/38c075963668f8b41c6914ee0f6f318727fbe30ab9145cb29e6df464c5fa/ruff-0.15.15-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:db5bd4d802415cca656dc1616070b725952d6ae95eb5d4831e49fbd94a38f75f", size = 10511117, upload-time = "2026-05-28T14:16:31.767Z" }, - { url = "https://files.pythonhosted.org/packages/9d/96/6ff689e1f7e375d1d97075eca022f74c2bab59554a432fe4d2e6f091986a/ruff-0.15.15-py3-none-musllinux_1_2_i686.whl", hash = "sha256:587a6278ed42059191c1a466e490bd7930fb50bd2e255398bc29616c895a61cb", size = 10994867, upload-time = "2026-05-28T14:16:35.149Z" }, - { url = "https://files.pythonhosted.org/packages/c3/c2/5dce0ab9f92a8d534fa62b9bf9caca3eddb8c1a81b616f5e195ada4f0d6e/ruff-0.15.15-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:df0c1c084f5f4be9812f61518a45c440d3c30d69ce4bf6c5270e66d38338f02a", size = 11482101, upload-time = "2026-05-28T14:16:49.598Z" }, - { url = "https://files.pythonhosted.org/packages/b1/c0/1003b60edd697c649faf61f1a34094b1abb38fb3d1181e3f895781250a08/ruff-0.15.15-py3-none-win32.whl", hash = "sha256:29428ea79694afbe756d45fd59b36f22b6b020dc0443cf7de0173046236964b9", size = 10716774, upload-time = "2026-05-28T14:16:52.337Z" }, - { url = "https://files.pythonhosted.org/packages/02/a8/1269eddd6945a06c23f055ef7848886e37cf9d6a8bebb386a3115f01470c/ruff-0.15.15-py3-none-win_amd64.whl", hash = "sha256:8df0323902e15e24bc4bf246da830573d3cf3352bd0b9a164eab335d111ff4a4", size = 11868463, upload-time = "2026-05-28T14:16:11.333Z" }, - { url = "https://files.pythonhosted.org/packages/4e/b2/920464c907b191e37469d477a1aa8bc048b8f36c4c1610dfa4ab87b39e18/ruff-0.15.15-py3-none-win_arm64.whl", hash = "sha256:3c8ceca6792f38196b8f589bc92eccd03eef286602da92e5dc05cc42ef6441b7", size = 11138498, upload-time = "2026-05-28T14:16:38.425Z" }, +version = "0.15.16" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/bd/5f7ec371001337d8fa61701c186ff8b613ecac1651848c5950f4c4d5f2e9/ruff-0.15.16.tar.gz", hash = "sha256:d05e78d38c78caf020b03789e25106c93017db5a0cb6e2819885018c61343b78", size = 4714267, upload-time = "2026-06-04T16:33:09.974Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/42/53ef1c3953f157956db9bf7861e3bc50b9b887ce93300aa48cdba8336fe6/ruff-0.15.16-py3-none-linux_armv6l.whl", hash = "sha256:6ac3c0b3969cc6cf6b158c4e2f8f682acb58e7d700d8a44b65ecdc72d66ab0b2", size = 10709025, upload-time = "2026-06-04T16:32:51.935Z" }, + { url = "https://files.pythonhosted.org/packages/93/9a/a79159346f19134a956607754e57d8d128f7a4c00f4ad2f7514d224c172c/ruff-0.15.16-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:197c207ed75ffba54a0dec23db4aa939a27a3053073e085e0042433cbdc58e4a", size = 11063550, upload-time = "2026-06-04T16:32:42.24Z" }, + { url = "https://files.pythonhosted.org/packages/bc/72/3ce2ac000a5299ec238e01f51397b3b653c93b077d9b1bfe8715bb895f20/ruff-0.15.16-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3a39fec45ab316cc23e7558f23fea4a70403ddb5648ea9a4a3854a16973d0071", size = 10421345, upload-time = "2026-06-04T16:32:37.251Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c2/cc7fad3ec9169373f5b6a18f1917b91080feec40c3f9658334a1d28e2f03/ruff-0.15.16-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba93191d79003116b95128c9d306e045200fdbd0bccb782b110f3cd1d4abc5cf", size = 10757217, upload-time = "2026-06-04T16:32:54.722Z" }, + { url = "https://files.pythonhosted.org/packages/69/d2/3474009eaa0a65b31fa7152a2fad5e2f050c640ceb1e6b02ee6922e94c82/ruff-0.15.16-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c6ee4b90520630120ef032aa5cc10db483852dff950e78b1d717e2993a61ac8d", size = 10507035, upload-time = "2026-06-04T16:33:05.343Z" }, + { url = "https://files.pythonhosted.org/packages/ca/81/b7ae6ccbd11f0c8dc3d5d67fc4be9b57ff57ca86ba56152021378e1277f2/ruff-0.15.16-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e4215bc938bc3c8215c1472c1aa437e310fee20cd427335fec9d7e609563628", size = 11255291, upload-time = "2026-06-04T16:32:49.49Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e1/46e526f1a7cc90857ce6ddf25fbb77eb6568651ac38d71b033af07076dd5/ruff-0.15.16-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c8d26be963b090f10e29abc8b3e74a2a321f6fa34e02424e30b5af89350ecbb", size = 12124922, upload-time = "2026-06-04T16:33:07.821Z" }, + { url = "https://files.pythonhosted.org/packages/1a/da/5c791b088b596b24d0deb967fa28ae02ad751a140c0b9ea81c5ab915d6c0/ruff-0.15.16-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f198cf4123602a2280ed46c307bcbafe41758d6fee5b456b6b6058ca1514b3b4", size = 11332186, upload-time = "2026-06-04T16:33:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/72/11/5da87abe20047c8962361473923ebb2f62b595250126aadfad8c20649c1e/ruff-0.15.16-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb27515fa6240fb586ae82b901a59e67d24acff86f2190b433dc542fe0435aeb", size = 11373541, upload-time = "2026-06-04T16:32:47.007Z" }, + { url = "https://files.pythonhosted.org/packages/fe/2a/8554754c23a854ae3fd6b507e36ad61ddb121e298c6d5d617dec94ed0f14/ruff-0.15.16-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a267c46ba1593fc26b8eecbea050b39d40c0b6bb7781ee11c90a02cd10032951", size = 11353014, upload-time = "2026-06-04T16:32:34.795Z" }, + { url = "https://files.pythonhosted.org/packages/62/25/62ea41529ec89f742ea3fed9cb1059c72877ec7cf9b9e99ac9cf3294d1d9/ruff-0.15.16-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:528c68f39a91498a8d50e91ff5985df3d105782bab49cc378e73ac26bff083e8", size = 10737467, upload-time = "2026-06-04T16:32:26.348Z" }, + { url = "https://files.pythonhosted.org/packages/90/17/334d3ad9de4d40f9dd58fdd09e35ce64553bb501e2f19a839e2fb6be14fc/ruff-0.15.16-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7ed55c58950df60589a9a7a5d2f8fa5f54ebd287163be805adfe6ee95a9de123", size = 10521910, upload-time = "2026-06-04T16:32:32.54Z" }, + { url = "https://files.pythonhosted.org/packages/4d/bd/3ac7c6ae77a885c1004b3dda2446ea401768d24f851c14b4ad4b24f6639c/ruff-0.15.16-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d482feaf51512b50f9790ceb417a56a61dd1e9d9bf967662b9ed27c01b34f53a", size = 10979190, upload-time = "2026-06-04T16:32:57.492Z" }, + { url = "https://files.pythonhosted.org/packages/33/d7/609546e6a413c3f216fbf2a50c928f97c80939154f6a0503114094a86191/ruff-0.15.16-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1e15bc8c94513dae2a40cc9ef07c94fdd4ecc9e29dabebeebe170f952322c9e3", size = 11477014, upload-time = "2026-06-04T16:32:44.687Z" }, + { url = "https://files.pythonhosted.org/packages/74/0d/f2cd247ad32633a5c36e97141a2c21b11c6279f7957bc2ff360b1e08fddd/ruff-0.15.16-py3-none-win32.whl", hash = "sha256:580378f7bd4aa25f72e74aa54948a9622f142b1e509521dd10902e886681cc1e", size = 10735541, upload-time = "2026-06-04T16:32:30.145Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9e/02e845ef151b1dee585e55c4739f8e1734ae1d9f1221dff65761c162208b/ruff-0.15.16-py3-none-win_amd64.whl", hash = "sha256:408256017284eddf98fff77b29aa4fb30f586042d535b2d9befc6512f400aaec", size = 11843403, upload-time = "2026-06-04T16:32:39.76Z" }, + { url = "https://files.pythonhosted.org/packages/15/19/016553f86f207450aebebc2b2b5088d086b901cc8186c02ac4284db3bd88/ruff-0.15.16-py3-none-win_arm64.whl", hash = "sha256:8cd61783afb39638a7133ef0d2dfb1e91277593962f81b5a8423eb0b888a6121", size = 11134555, upload-time = "2026-06-04T16:33:00.136Z" }, ] [[package]] @@ -3533,14 +3534,14 @@ wheels = [ [[package]] name = "tinycss2" -version = "1.4.0" +version = "1.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "webencodings" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085, upload-time = "2024-10-24T14:58:29.895Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/ae/2ca4913e5c0f09781d75482874c3a95db9105462a92ddd303c7d285d3df2/tinycss2-1.5.1.tar.gz", hash = "sha256:d339d2b616ba90ccce58da8495a78f46e55d4d25f9fd71dfd526f07e7d53f957", size = 88195, upload-time = "2025-11-23T10:29:10.082Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610, upload-time = "2024-10-24T14:58:28.029Z" }, + { url = "https://files.pythonhosted.org/packages/60/45/c7b5c3168458db837e8ceab06dc77824e18202679d0463f0e8f002143a97/tinycss2-1.5.1-py3-none-any.whl", hash = "sha256:3415ba0f5839c062696996998176c4a3751d18b7edaaeeb658c9ce21ec150661", size = 28404, upload-time = "2025-11-23T10:29:08.676Z" }, ] [[package]] @@ -3733,23 +3734,23 @@ wheels = [ [[package]] name = "tqdm" -version = "4.67.3" +version = "4.68.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/b3/36c8ecf72e8925200671613332db156d84b99b3aee742a41c1938ebb0808/tqdm-4.68.1.tar.gz", hash = "sha256:fc163d96b287bd031e1aa24421ce4411b25559bd0a1be4fe649bdaa4d2c02bf5", size = 171236, upload-time = "2026-06-05T17:23:15.267Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, + { url = "https://files.pythonhosted.org/packages/47/aa/218a0eb34de1f753c83e4d0d1c8e7c4cef27f20dcb8342e024f63a80dc86/tqdm-4.68.1-py3-none-any.whl", hash = "sha256:fea4a90e4023f764914569f7802a297277c5ab1a66be5144143e142e1a4031d8", size = 78354, upload-time = "2026-06-05T17:23:13.654Z" }, ] [[package]] name = "traitlets" -version = "5.15.0" +version = "5.15.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1b/22/40f55b26baeab80c2d7b3f1db0682f8954e4617fee7d90ce634022ef05c6/traitlets-5.15.0.tar.gz", hash = "sha256:4fead733f81cf1c4c938e06f8ca4633896833c9d89eff878159457f4d4392971", size = 163197, upload-time = "2026-05-06T08:05:58.016Z" } +sdist = { url = "https://files.pythonhosted.org/packages/57/a9/a2584b8313b89f94869ddb3c4074617a691de1812a614d2d50e32ca5a7a6/traitlets-5.15.1.tar.gz", hash = "sha256:7b1c07854fe25acb39e009bae49f11b79ff6cbb2f27999104e9110e7a6b53722", size = 163344, upload-time = "2026-06-03T12:26:06.181Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/98/a9937a969d018a23badfea0b381f66783649d48e0ea6c41923265c3cbeb3/traitlets-5.15.0-py3-none-any.whl", hash = "sha256:fb36a18867a6803deab09f3c5e0fa81bb7b26a5c9e82501c9933f759166eff40", size = 85877, upload-time = "2026-05-06T08:05:55.853Z" }, + { url = "https://files.pythonhosted.org/packages/96/8d/1080ee4c231f361b6ce4470d556c8c435b67c7e0753aaa641497ee92f88b/traitlets-5.15.1-py3-none-any.whl", hash = "sha256:770a53705f84b81ac107e83a1b3328ff2dae16094d8fc3cfc004e4b22dfd8e92", size = 85858, upload-time = "2026-06-03T12:26:04.395Z" }, ] [[package]] @@ -3824,11 +3825,11 @@ wheels = [ [[package]] name = "wcwidth" -version = "0.7.0" +version = "0.8.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2c/ee/afaf0f85a9a18fe47a67f1e4422ed6cf1fe642f0ae0a2f81166231303c52/wcwidth-0.7.0.tar.gz", hash = "sha256:90e3a7ea092341c44b99562e75d09e4d5160fe7a3974c6fb842a101a95e7eed0", size = 182132, upload-time = "2026-05-02T16:04:12.653Z" } +sdist = { url = "https://files.pythonhosted.org/packages/49/b4/51fe890511f0f242d07cb1ebe6a5b6db417262b9d2568b460347c57d95cc/wcwidth-0.8.1.tar.gz", hash = "sha256:faf5b4a5366a72dc49cad48cdf21f52bdf63bdda995178e483ba247ff79089b9", size = 1466072, upload-time = "2026-06-08T05:57:23.146Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/41/52/e465037f5375f43533d1a80b6923955201596a99142ed524d77b571a1418/wcwidth-0.7.0-py3-none-any.whl", hash = "sha256:5d69154c429a82910e241c738cd0e2976fac8a2dd47a1a805f4afed1c0f136f2", size = 110825, upload-time = "2026-05-02T16:04:11.033Z" }, + { url = "https://files.pythonhosted.org/packages/bd/6e/95b0e537de1f4d4301f76f944642c6da50d1511cc7b3d64dc418a66c7509/wcwidth-0.8.1-py3-none-any.whl", hash = "sha256:f453740b1e4a4f3291faa37944c555d71056c4da08d59809b307ef4feba695c8", size = 323092, upload-time = "2026-06-08T05:57:21.413Z" }, ] [[package]] From 868fad342bad46a7778794bd52d58e4752b48e1b Mon Sep 17 00:00:00 2001 From: quantem-bot Date: Mon, 15 Jun 2026 15:20:32 +0000 Subject: [PATCH 326/335] chore: update lock file --- uv.lock | 356 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 178 insertions(+), 178 deletions(-) diff --git a/uv.lock b/uv.lock index 34073ea1..3bec33e5 100644 --- a/uv.lock +++ b/uv.lock @@ -713,7 +713,7 @@ wheels = [ [[package]] name = "dask" -version = "2026.3.0" +version = "2026.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -725,9 +725,9 @@ dependencies = [ { name = "pyyaml" }, { name = "toolz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d7/2a/5d8cc1579590af86576dde890254440e478c7174b93a02095ecfc2e6ba38/dask-2026.3.0.tar.gz", hash = "sha256:f7d96c8274e8a900d217c1ff6ea8d1bbf0b4c2c21e74a409644498d925eb8f85", size = 11000710, upload-time = "2026-03-18T07:10:14.945Z" } +sdist = { url = "https://files.pythonhosted.org/packages/60/ca/58434f10ebb45d2ddc6edd6e2988abcd38da1ab1897a5f6f402711a594e9/dask-2026.6.0.tar.gz", hash = "sha256:ae3436bd31ebce2be75edf952bd1fc687a1f11ec03fe8b1bec2903d222344a45", size = 11544529, upload-time = "2026-06-11T17:48:43.316Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl", hash = "sha256:be614b9242b0b38288060fb2d7696125946469c98a1c30e174883fd199e0428d", size = 1485630, upload-time = "2026-03-18T07:10:12.832Z" }, + { url = "https://files.pythonhosted.org/packages/02/17/b82b537a30be67ba178fc0b0fbda8fcbaeda188fb039d6ad8a84e89353a2/dask-2026.6.0-py3-none-any.whl", hash = "sha256:1539859071065dca379ca592ff76e911cd7965dc466da0040354ab466179189b", size = 1488995, upload-time = "2026-06-11T17:48:41.008Z" }, ] [package.optional-dependencies] @@ -789,11 +789,11 @@ wheels = [ [[package]] name = "distlib" -version = "0.4.1" +version = "0.4.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/86/b2/d6fc3f2347f43dada79e5ff118493e8109c98400a0e29a1d5264a3aa479b/distlib-0.4.1.tar.gz", hash = "sha256:c3804d0d2d4b5fcd44036eb860cb6660485fcdf5c2aba53dc324d805837ea65b", size = 610526, upload-time = "2026-06-02T11:17:40.691Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/02/bd72be9134d25ed783ecbbc38a539ffaefbf90c78418c7fb7229600dbac7/distlib-0.4.3.tar.gz", hash = "sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed", size = 615141, upload-time = "2026-06-12T08:04:52.847Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/18/3497c4fa83a76dcb154923fd2075522e8dd6995ecee4093c00ae18160046/distlib-0.4.1-py2.py3-none-any.whl", hash = "sha256:9c2c552c68cbadc619f2d0ed3a69e27c351a3f4c9baa9ffb7df9e9cdc3d19a97", size = 469216, upload-time = "2026-06-02T11:17:38.779Z" }, + { url = "https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl", hash = "sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b", size = 470628, upload-time = "2026-06-12T08:04:50.506Z" }, ] [[package]] @@ -828,11 +828,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.29.1" +version = "3.29.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1f/f9/f38573ed5844586db374d085911740a501ccfa373b455fc9413f09f85237/filelock-3.29.1.tar.gz", hash = "sha256:d97e6b1b9757569626c58caa07dc4beb1613f4a2938b1e8cc81afca398906c9e", size = 59335, upload-time = "2026-06-03T15:19:04.053Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/dc/be6cbe99670cd6e4ad387123647cb08e0c32975e223f82551e914c5568a6/filelock-3.29.4.tar.gz", hash = "sha256:10cdb3656fc44541cdf30652a93fb10ec6b05325620eb316bd26893e4201538a", size = 63028, upload-time = "2026-06-13T16:12:00.744Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/a0/614c5fe402fd88951df45f4dda2fa3b4e17a99ecd92340771929169b3b95/filelock-3.29.1-py3-none-any.whl", hash = "sha256:85199dfd706869641b72b2e8955d5416a4b2b7dc4b0e8e6d97b4cc1299a6983b", size = 40750, upload-time = "2026-06-03T15:19:02.959Z" }, + { url = "https://files.pythonhosted.org/packages/13/37/a065dc3bd6e49423a6532c642ca7378d3f467b1ef44c2800c937af7f9739/filelock-3.29.4-py3-none-any.whl", hash = "sha256:dac1648087d5115554850d113e7dd8c83ab2d38e3435dde2d4f163847e57b767", size = 42757, upload-time = "2026-06-13T16:11:59.582Z" }, ] [[package]] @@ -1021,53 +1021,53 @@ wheels = [ [[package]] name = "grpcio" -version = "1.81.0" +version = "1.81.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/15/f3/23f47b24f8d8c2028eba501db3acfbb2f592cbb5995eaa6e363a627b74d7/grpcio-1.81.0.tar.gz", hash = "sha256:a5acd7efd3b1fe9b4eb0bcaaa1507eed68a0ad0678b654c3f7b464df9ba9dca5", size = 13032272, upload-time = "2026-06-01T05:56:22.827Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/45/a8/9916ab10a0201f4c7afb6918125aa2f38a7626ee18ffbc066dd9cb04a74d/grpcio-1.81.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:794e6aa648e8df47d8f908dc8c3b42347d04ec58438f1dcd4e445f09b4f6b0ce", size = 6093557, upload-time = "2026-06-01T05:54:32.64Z" }, - { url = "https://files.pythonhosted.org/packages/a7/43/99e969a048904a65df3129ee53c5f523b7c4e43127786460cac4bee82470/grpcio-1.81.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:cd78145b7f7784661c524624f3526c9c6f891b30a4b54cb93a40806d0d0d61e9", size = 12075345, upload-time = "2026-06-01T05:54:35.77Z" }, - { url = "https://files.pythonhosted.org/packages/83/70/4c3a204e190333768d4f63f4ff56bd0bf405f05b9188f3a59a8bcf161f8b/grpcio-1.81.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:638ccc1b86f7540170a169cb900799b9296a1381e47879ce60b0de9d3db73d33", size = 6640664, upload-time = "2026-06-01T05:54:38.854Z" }, - { url = "https://files.pythonhosted.org/packages/2e/a9/0fa17ac8b4e29cf59b26915be6cab8c0d4583ce24a6208a287b6e5f6d072/grpcio-1.81.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:21ec30b9ea320c8207ea7cd05873ad64aa69fdd0e81b6758b3347983ba20b50a", size = 7332542, upload-time = "2026-06-01T05:54:41.39Z" }, - { url = "https://files.pythonhosted.org/packages/f4/18/7c8e3d0dda2fb7a17076fcd6c9085209eabad3354696c64230f87b3a14eb/grpcio-1.81.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dbdb99986548a7e87f8343805ef315fd4eb50ffaabf4fb1206e42f2542bb805d", size = 6842564, upload-time = "2026-06-01T05:54:43.57Z" }, - { url = "https://files.pythonhosted.org/packages/f6/19/2f1726c2e03ad3f3fe241e6b41534532ad580d595de14a4054ad84999c80/grpcio-1.81.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c36f5d5e97944cbda2d4096b4ae262e6e68506246b61582acf1b8591607f3ccc", size = 7446236, upload-time = "2026-06-01T05:54:46.042Z" }, - { url = "https://files.pythonhosted.org/packages/a7/dc/0321f892212e2c0bfe248cea24c00d7d7111639688ec5ffd8e36b5c02fe6/grpcio-1.81.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9f355384e5543ab77a755a7085225ecc19f32b76032e851cbd8145715d79dec8", size = 8445633, upload-time = "2026-06-01T05:54:48.809Z" }, - { url = "https://files.pythonhosted.org/packages/e5/20/0e7ea7494955cf1beea3077b2fd2c04c84d4480c2ae85a1e1cfa150c62d7/grpcio-1.81.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:77eb4e9fe61486bd1198cc7236ebb0f70e66234e63c0348f40bc2553ed16a88b", size = 7873958, upload-time = "2026-06-01T05:54:52.135Z" }, - { url = "https://files.pythonhosted.org/packages/d3/9e/6438e226046c2a0778060e2b1d791a4827277bbd9d223013c2c63ee7435e/grpcio-1.81.0-cp311-cp311-win32.whl", hash = "sha256:7915a2e63acdc05264a206e1bddfd8e1fb8a29e406c18d72d30f8c124e021374", size = 4202110, upload-time = "2026-06-01T05:54:54.134Z" }, - { url = "https://files.pythonhosted.org/packages/42/6b/d0895e93d65b186f5f1737fcc186d7faa487e2d9d934eda111a37a309869/grpcio-1.81.0-cp311-cp311-win_amd64.whl", hash = "sha256:5e925a70fe99fe5794f7beca0ea034c75f068afcc356d79047e73f99cdcca34c", size = 4940942, upload-time = "2026-06-01T05:54:56.749Z" }, - { url = "https://files.pythonhosted.org/packages/82/d5/896a3aaf07068d707d88b282a04914b872db4d32d3c7e6d88e43a3b911fa/grpcio-1.81.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:57b3b0e73a518fa286959b40c3eddd02703504ca186e8b7b2945954519bd8b2c", size = 6053538, upload-time = "2026-06-01T05:54:58.965Z" }, - { url = "https://files.pythonhosted.org/packages/68/6a/7e3eafa4727cd405ff917605ed2949e2af162f233f5cbdd773723a5fea7d/grpcio-1.81.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:8bb1789c94322a13336a2b6c58d9c14d68f8628b6e24205a799c69f5bf8516ce", size = 12053447, upload-time = "2026-06-01T05:55:01.862Z" }, - { url = "https://files.pythonhosted.org/packages/16/79/a4302aa82428de48a922421f522b027a1a727ab4d0926368454aa953d36d/grpcio-1.81.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e4d053900a0d24b75d7521139a3872150301b3d6bde3bed5e12318fb25791e4d", size = 6595872, upload-time = "2026-06-01T05:55:04.946Z" }, - { url = "https://files.pythonhosted.org/packages/b4/1f/7ff2850eaefbecf99af3f624dbb28dd1ad6c5fd4c1d8c26909ed6482673b/grpcio-1.81.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:db217c2e52931719f9937bd12082cd4d7b495b35803d5760686975c285924bf8", size = 7303857, upload-time = "2026-06-01T05:55:07.205Z" }, - { url = "https://files.pythonhosted.org/packages/e2/98/1f3896a9baae1f2aedf4e99c55291d6fa1f30ad9603d63bc18bda967b53e/grpcio-1.81.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:19f201da7b4e5c0559198abe5a97157e726f3abe6e8f5e832d4a50740f6dcc22", size = 6809676, upload-time = "2026-06-01T05:55:09.513Z" }, - { url = "https://files.pythonhosted.org/packages/34/8b/3441983718095208c5d797fd3239882e97ea89a629f41c8df94b4eef4df9/grpcio-1.81.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:275144b0115353339dbb8a6f28a9cf8997b5bf40e37f8f66ac0b0ea57e95b43f", size = 7412654, upload-time = "2026-06-01T05:55:12.777Z" }, - { url = "https://files.pythonhosted.org/packages/3c/98/1eddf07df6e4fe85cf67502a793f7b05468b2dca3d1ef35b972cf5d54468/grpcio-1.81.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5192857589f223e5a98ff0e31f6e551b19040e647d17bfe10116c8a2ce3b8696", size = 8408026, upload-time = "2026-06-01T05:55:15.514Z" }, - { url = "https://files.pythonhosted.org/packages/5c/73/3860341e6a1f5347be6ab35c6c0e1e3a8eb59d010388207fd561dcf01a88/grpcio-1.81.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c6ff087cb1f563f47b504b4e29e684129fc5ae4863faf3ebca08a327764ee6cb", size = 7849498, upload-time = "2026-06-01T05:55:18.078Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3f/0ea06bd85c701966aa3f8f37314f2ed83520d2b7590f42d643d445d8bc8b/grpcio-1.81.0-cp312-cp312-win32.whl", hash = "sha256:98c6240f563178fc5877bd50e6ff274463e53e1472128f4110742450739659fa", size = 4184161, upload-time = "2026-06-01T05:55:20.127Z" }, - { url = "https://files.pythonhosted.org/packages/39/e3/a7c387406827a86f99ad7838b995bf9b4a182ffe2d2c439ed2873efec952/grpcio-1.81.0-cp312-cp312-win_amd64.whl", hash = "sha256:87e33b7afcfb3585121b5f007d2c52b8c534104d18f556e840d35193ca2a9141", size = 4929958, upload-time = "2026-06-01T05:55:22.736Z" }, - { url = "https://files.pythonhosted.org/packages/f3/29/779ee53c931d0fd55c1d459fde43e485172caa3ac87cbd43d003a13a0185/grpcio-1.81.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:62bbe463c9f0f2ff24e31bd25f8dd8b4bae78900e315915a3195a0ef1471a855", size = 6054973, upload-time = "2026-06-01T05:55:25.043Z" }, - { url = "https://files.pythonhosted.org/packages/9e/b6/7211807926b5a17f8d9a5d47c739a163d6812fefe3e4714e81cf92945ed7/grpcio-1.81.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:43c121e135ae44d1559b430db2b2dfad7421cbbe40e1deba506c7dc62b439719", size = 12048662, upload-time = "2026-06-01T05:55:28.453Z" }, - { url = "https://files.pythonhosted.org/packages/64/89/b1b93ef6b34bd20bbaf707fa99133bc9cc302139d5ec6f77a165c7169796/grpcio-1.81.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f345de40ef2e65f63645d53d251824e6070e07804827c5b00ec2e44555f9f901", size = 6599116, upload-time = "2026-06-01T05:55:31.185Z" }, - { url = "https://files.pythonhosted.org/packages/eb/bc/c89f9b9d1c22895715356a1e009554dae66319e97826bb4d30bcda7d29e8/grpcio-1.81.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:8c0855a350886f713b9e458e2a10d208009dcaa849f574e39cd6067db1fe1279", size = 7307591, upload-time = "2026-06-01T05:55:33.463Z" }, - { url = "https://files.pythonhosted.org/packages/65/4a/1df2a4cb4a1386e066ab7e4175e34bb884b35ccb60d3621c09c84af6aabb/grpcio-1.81.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a524cd530900bd24511fcb7f2ed144da4ea37711c4b094475d0bceca7a93a170", size = 6811797, upload-time = "2026-06-01T05:55:36.731Z" }, - { url = "https://files.pythonhosted.org/packages/8d/dc/fa189d20601a1be25b08850cfb733879bbb1047b62a8feec3a60e3e1a87b/grpcio-1.81.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e7746ba3e6efc9e2b748eff59470a2b8684d5a9ec607c6580bcaa5be175820bc", size = 7415131, upload-time = "2026-06-01T05:55:39.451Z" }, - { url = "https://files.pythonhosted.org/packages/ad/a3/5625c48cb48d23c6631b3e5294f88e4c751f22a52591ae78859fab96dca1/grpcio-1.81.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:aaaa4f7f2057d795952e4eacf3f342be8b5b156992f6ac85023c8b98794ebd47", size = 8408398, upload-time = "2026-06-01T05:55:42.219Z" }, - { url = "https://files.pythonhosted.org/packages/75/34/0f8202c6809a46c2b4d69125ef3667c40b1c211f8e19930e5fa1f1197039/grpcio-1.81.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0fba53cb96004b2b7fb758b46b2288cb49d0b658316a4e73f3ef67230616ee65", size = 7844481, upload-time = "2026-06-01T05:55:44.849Z" }, - { url = "https://files.pythonhosted.org/packages/c0/95/c3366b5b5edf4c4adc90f2e29ca16e57965a8e56dc8d2ee89565ba1905bb/grpcio-1.81.0-cp313-cp313-win32.whl", hash = "sha256:c197e2ef75a442528072b29e9755da299110e8610e8bcbb59a6b4cf55384f005", size = 4182777, upload-time = "2026-06-01T05:55:47.459Z" }, - { url = "https://files.pythonhosted.org/packages/a9/a7/932f2f748511a32e641a2aba0d30dded3ed6e8bc330e0924e4d5d86853e6/grpcio-1.81.0-cp313-cp313-win_amd64.whl", hash = "sha256:194eddfacc84d80f50512e9fd4ee851d5f2499f18f299c95aa8fb4748f0537e0", size = 4928085, upload-time = "2026-06-01T05:55:50.158Z" }, - { url = "https://files.pythonhosted.org/packages/c5/1d/28b231333857deb840bc3d182ae087510170ea6d68f21393aeb0fe499530/grpcio-1.81.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:a9351055f52660b58f3d4890ea66188b5134399f82b11aa0c55bd4b99eff5390", size = 6055712, upload-time = "2026-06-01T05:55:52.889Z" }, - { url = "https://files.pythonhosted.org/packages/e8/b8/999c14f9dff0fc47549d2e827cba1343ddc18e1d1bf0d06d2cf628eecbd9/grpcio-1.81.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:300f3337b6425fd16ead9a4f9b2ac25801acb64aa5bc0b99eb69901645b2b1d2", size = 12057189, upload-time = "2026-06-01T05:55:55.952Z" }, - { url = "https://files.pythonhosted.org/packages/1e/3d/1fbde079572562af65351151d840525a13879eb7b481d35b55cd64c6127a/grpcio-1.81.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:97bbd623f7ded558fd4f7cb5a4f600c4d4de65c5dd364c83a5b14b2a10a2d3b5", size = 6608136, upload-time = "2026-06-01T05:55:59.069Z" }, - { url = "https://files.pythonhosted.org/packages/32/89/1f17cb6882abfd8e5a303a25d5d1665abef5a8c499a96198c65a651d1b85/grpcio-1.81.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ff83d889e3ebf6341c8c7864ad8031591ad5ca61599072fc511644d1eb962d2b", size = 7307045, upload-time = "2026-06-01T05:56:02.376Z" }, - { url = "https://files.pythonhosted.org/packages/48/5a/f98e91b2e755652e637ea2144318b0229b290062199f761b445fe1fa6015/grpcio-1.81.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c4fe218c5a35e1d87a5a26544237f1fa41dfd9cbd3c856b0810a30061f8b0aaf", size = 6812794, upload-time = "2026-06-01T05:56:05.777Z" }, - { url = "https://files.pythonhosted.org/packages/0a/0c/77892d715ac41e7ec0ace2a50080ffb64e189188056f607a66fe0014d1ee/grpcio-1.81.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b8b025b6af43ee0ad4a70307025d77bcab5adde7c4597786010d802c203e9fc5", size = 7422767, upload-time = "2026-06-01T05:56:08.524Z" }, - { url = "https://files.pythonhosted.org/packages/3f/b8/aa04590c6564714d94954515f15a236e59d4b9b3ad01e615f1b706d7792d/grpcio-1.81.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:3d4e0ce5a40a998cf608c8ba60ecfe18fdf364a9aa193ae4ac3faeecd0e86757", size = 8408551, upload-time = "2026-06-01T05:56:11.283Z" }, - { url = "https://files.pythonhosted.org/packages/43/3d/4f4a3450a1973568910c6909cb74abbf2126f68aefae5976962f9f7ad50d/grpcio-1.81.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:aa948712c8e5fa40ec250870bda14bc7578e1bb832a8912d9d2a0f720518edbe", size = 7846468, upload-time = "2026-06-01T05:56:14.536Z" }, - { url = "https://files.pythonhosted.org/packages/88/f4/5827fd248221ad3b44161c23ce9b5f4ee405b04fc6da5fd402a9aa87a84a/grpcio-1.81.0-cp314-cp314-win32.whl", hash = "sha256:fbbe81314a9d92156abce8b62c09364eb8bafc0ca2a19919a45ec64b5c6cb664", size = 4264427, upload-time = "2026-06-01T05:56:17.192Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e8/127dc2b246096ad50ef7c8d9b7b31d757787aeb796368bcdd4454e4204c4/grpcio-1.81.0-cp314-cp314-win_amd64.whl", hash = "sha256:b93cee313cae4e113fbb3a0ce1ea5633db6f63cfde2b2dc1d817429026b2a50b", size = 5070848, upload-time = "2026-06-01T05:56:19.735Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/b0/b5/1ff353970a87eda4c98251e34d2dfd214abd4982dc89119c9252a2a482d2/grpcio-1.81.1.tar.gz", hash = "sha256:6fa10a767143a5e82e8eaab53918af0cd8909a57a27f8cb2288b80a613ac671b", size = 13026582, upload-time = "2026-06-11T12:46:51.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/ea/1c2fa386b718ff493225e61cfc052ef400b4d6ffc54cbe261026432624b5/grpcio-1.81.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:d71d30f2d92f67d944631c523713934fee37292469e182ebcd2c1dd8a64ce53f", size = 6093112, upload-time = "2026-06-11T12:44:52.131Z" }, + { url = "https://files.pythonhosted.org/packages/2b/18/acf45fa8bd1bc5d7b0c2fd3dc4c209379fbd5bb396b440b68a83342226b7/grpcio-1.81.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b137f4bf3ada9dc44d411478decc6ff09a79ed30b306cd2abaa98408c3588137", size = 12074277, upload-time = "2026-06-11T12:44:55.354Z" }, + { url = "https://files.pythonhosted.org/packages/48/d7/ee86a60699b7db039f772a2c4a7e4facc7138984ff42c0130933a0063884/grpcio-1.81.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a3acb384427816dd5d470f47e62137b87f74da694faa8a50147012cf40df276a", size = 6640348, upload-time = "2026-06-11T12:44:59.223Z" }, + { url = "https://files.pythonhosted.org/packages/26/ee/d2de5e47378ffc207d476c230fea3be4d2601edbce9995f4fe45535d4896/grpcio-1.81.1-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f9a0ebbe45c29b5e5866593c12b78bd9035f0f0f0d4bc8361680cd580d99db49", size = 7331842, upload-time = "2026-06-11T12:45:02.001Z" }, + { url = "https://files.pythonhosted.org/packages/23/d6/abeda5c2b896a0b341584fe5ac411bbf72e197a9a374c355fb90965e08d2/grpcio-1.81.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a37165cc80b1a368384b383e63a4c38116a10467ae44c904d2d7468c4470ec2", size = 6842229, upload-time = "2026-06-11T12:45:04.76Z" }, + { url = "https://files.pythonhosted.org/packages/10/1c/1f0da7d590b4aeee006826ba568d0e419ca14b23e18f901a3da3e9fba613/grpcio-1.81.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6282caffb41ec326d4cb67ca9cf53b739d1b2f975a2acb498c7418e9f7d9a416", size = 7446096, upload-time = "2026-06-11T12:45:07.499Z" }, + { url = "https://files.pythonhosted.org/packages/6a/81/5c505d508f7c887aa7982d21443a4126597c80d34b0bcf40f9cec576d7f3/grpcio-1.81.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a35009284d0d3d5c2c9601c164a911b8b4331608d98a9a66d47d97bb2f522b70", size = 8445238, upload-time = "2026-06-11T12:45:10.243Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b2/524847365122ee509ca17bcc4e092198b700e94af7bfd5bb5e6dd9f3ee66/grpcio-1.81.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1b22c80559854b789a01fd89e8929b3798a156c0829b5282a8939f33ad4115ad", size = 7873989, upload-time = "2026-06-11T12:45:13.102Z" }, + { url = "https://files.pythonhosted.org/packages/18/fa/07c037c50b006909d1d13a5848774f8aa7b242f70dc03a035c64eea0e6db/grpcio-1.81.1-cp311-cp311-win32.whl", hash = "sha256:428bec0161b48d8cf583c068591bc0016d0d9cfff52462b72b3884861ea768c5", size = 4202223, upload-time = "2026-06-11T12:45:16.166Z" }, + { url = "https://files.pythonhosted.org/packages/41/ed/6bff15376920942fac6b95b9802752b837437172c9e8fc2d3170546b89cc/grpcio-1.81.1-cp311-cp311-win_amd64.whl", hash = "sha256:30e825f6848d9f18bba350ed6c75c1b02a0b5184474a31db9a32b1fa66fd8c79", size = 4941303, upload-time = "2026-06-11T12:45:18.724Z" }, + { url = "https://files.pythonhosted.org/packages/85/07/9a979c81738863a738dc23d65177056e71fbb2db817740ed870b33434e7a/grpcio-1.81.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:8b39472beafc0bdcafc4c8c73ad082ebfdb449d566897a61e7acb4fa88089115", size = 6053264, upload-time = "2026-06-11T12:45:21.017Z" }, + { url = "https://files.pythonhosted.org/packages/75/95/539706ca0d3bd40dbad583dc56fd883da941f37556b629132da5762781b9/grpcio-1.81.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:12b7524c88d4026d3dcb7b0ebe16b6714f3b4af402ddd0f0639ab064a00c87c3", size = 12052560, upload-time = "2026-06-11T12:45:23.652Z" }, + { url = "https://files.pythonhosted.org/packages/e0/44/f257b7e0bd69c93b06c6cb8ac8d1b901ccb42bedabd83c1a4c77a71f8810/grpcio-1.81.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1e123f9b37edb8375fd74130d1f69c944bbf0a7b06761ae7211154b8759e94d2", size = 6595983, upload-time = "2026-06-11T12:45:26.963Z" }, + { url = "https://files.pythonhosted.org/packages/b9/f3/19782aa04c960968bef8c5539329d8e3bbc3364e2e46d19eb5e5cc5e43b7/grpcio-1.81.1-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:2c2e2ae6867c2966b8daccc836d54a13218e0007e9a490aeb81dd05be64d22d7", size = 7303455, upload-time = "2026-06-11T12:45:29.707Z" }, + { url = "https://files.pythonhosted.org/packages/eb/8c/dea020b6d91508cd84463917a63149ec196ee7db505d032ae43fcb3303b9/grpcio-1.81.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:766bc7c9a9c340342f4c864ccbda8e78111e4751f13b895812b9c148fb79e9d0", size = 6809167, upload-time = "2026-06-11T12:45:32.52Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c7/3030dd940408083bd32cd95d634777a71605ade4887154d93e8a89244946/grpcio-1.81.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b259a04a737cb3496be0901328eb8b7552ed8df4865d8c8f1cf1bffcfc0776a3", size = 7412536, upload-time = "2026-06-11T12:45:35.403Z" }, + { url = "https://files.pythonhosted.org/packages/e0/dd/1172a9e42b168edcafefad6115346ef619a3fc02158bb170e66ced24bcdd/grpcio-1.81.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:85b10a45b8993d195c4f3ff57025b8d1e11834909ee475c403bfa60cb4caefaf", size = 8408276, upload-time = "2026-06-11T12:45:37.78Z" }, + { url = "https://files.pythonhosted.org/packages/25/7a/71437c7f3596e5246155c515852795a85a1a8d228190212432b13b97a95d/grpcio-1.81.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8ea1936c26b99999b27479853039a7f34713f56c49375ad52b38535ec93a796c", size = 7849660, upload-time = "2026-06-11T12:45:40.627Z" }, + { url = "https://files.pythonhosted.org/packages/65/40/7debc0da45d2efebafb82da75644be347497fe4ee250514b8cd3b86ae8bf/grpcio-1.81.1-cp312-cp312-win32.whl", hash = "sha256:a185a04039df6cae8648bc8ab6d6fde7bf94f7188ecf7828e76ac52eef1e41d6", size = 4185819, upload-time = "2026-06-11T12:45:43.027Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b9/8fe3ba5ed462067774ebc1f9c7f26aa7ebcc280ddd476be107153de1339e/grpcio-1.81.1-cp312-cp312-win_amd64.whl", hash = "sha256:3ad74f8bb1a18963914c5452d289422830b39459e8776ebbcd207be1fbfb1d94", size = 4930461, upload-time = "2026-06-11T12:45:45.775Z" }, + { url = "https://files.pythonhosted.org/packages/7a/42/dcc2e4b600538ef18327c0839d56b7d3c3812337c5d710df5877dbb39b1e/grpcio-1.81.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:b10e1ff4756ed27d5a29d7fc79cfce7ef1ff56ad20025b89bac7cf79e09abbbe", size = 6054466, upload-time = "2026-06-11T12:45:48.43Z" }, + { url = "https://files.pythonhosted.org/packages/7b/4a/a36e03210183a8a7d4c80c3936acee679f4bd77d5861f369db47b2cc5f05/grpcio-1.81.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:819edbdcb42ab8598b494bcf0222684bbb7a3c772bd1b1f0be7e029a6063c28e", size = 12048795, upload-time = "2026-06-11T12:45:54.011Z" }, + { url = "https://files.pythonhosted.org/packages/b0/d5/d68e30b29098f63beab6fe501100fe82674ff142b32c672532da86a99b3a/grpcio-1.81.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c5bf2dc311127d91230cc79b92188c082634a06cf66c5234db49a43b910183b0", size = 6599094, upload-time = "2026-06-11T12:45:57.799Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b3/e837954d279754f638a11cca5dcf6b24a005efb398984cefaf7735945a54/grpcio-1.81.1-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:e8ca6a1fcdb2943c9cbc1804a1baf3acb6071d72a471591678ded84218006e14", size = 7307182, upload-time = "2026-06-11T12:46:00.568Z" }, + { url = "https://files.pythonhosted.org/packages/0d/1e/b47957057e729adc6cdf519a47f8be2562b7140e280f1418443eb4022192/grpcio-1.81.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e64dd101d380a115cc5a0c7856788adb535f1a4e21fc543775602f8be95180ae", size = 6810962, upload-time = "2026-06-11T12:46:03.312Z" }, + { url = "https://files.pythonhosted.org/packages/40/26/569868e364e05b19ec8f969da53d230bcd89c962cd198f7c29943155c4d3/grpcio-1.81.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:98a07f9bf591e3a8919797bee1c53f026ba4acd587e5a4404c8e57c9ec36b2a5", size = 7415698, upload-time = "2026-06-11T12:46:06.005Z" }, + { url = "https://files.pythonhosted.org/packages/36/0c/5440a0582cb5653fc42a6e262eeb22700943313f8076f9dc927491b20a59/grpcio-1.81.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c261d74b1a945cf895a9d6eccd1685a8e837531beaab782da4d630a8d12deffb", size = 8407779, upload-time = "2026-06-11T12:46:08.84Z" }, + { url = "https://files.pythonhosted.org/packages/ff/aa/66fe9f39871d766987d869a03ee0842a026f499c7b1e62decb9e78a8088e/grpcio-1.81.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:58ad1131c300d3c9b933802b3cc4dc69d380822935ba50b28703156ea826fbf7", size = 7844521, upload-time = "2026-06-11T12:46:12.171Z" }, + { url = "https://files.pythonhosted.org/packages/f0/9e/69bb7194861bcd28fb3193261d4f9c3831b4446993f002cf59068943e7ab/grpcio-1.81.1-cp313-cp313-win32.whl", hash = "sha256:78e29211f26da2fdd0e9c6d2b79f489476140cf7029b6a64808ade7ca4156a42", size = 4182786, upload-time = "2026-06-11T12:46:15.192Z" }, + { url = "https://files.pythonhosted.org/packages/0d/20/3da8bb0d637feccdc3e1e419bb511ce93651ce7d54164f95de22cc0b8b34/grpcio-1.81.1-cp313-cp313-win_amd64.whl", hash = "sha256:edb59506291b647a30884b1d51a599d605f40b20af4a7dc3d33786a47a31de60", size = 4928648, upload-time = "2026-06-11T12:46:17.823Z" }, + { url = "https://files.pythonhosted.org/packages/b6/58/19414622b1bf6981bc9c05a365bd548e71876c89000083b3af489251e9c0/grpcio-1.81.1-cp314-cp314-linux_armv7l.whl", hash = "sha256:506f48f2f9c29b143fca3dad7b0d518c188b6c9648c75a2ae6e2d9f2c13a060b", size = 6055336, upload-time = "2026-06-11T12:46:20.557Z" }, + { url = "https://files.pythonhosted.org/packages/32/f1/2ec88adb92b0eba970dd0e0e7dd086341daa3c75eba4f735f9e44bf684b0/grpcio-1.81.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d865db4a6318e1c1bea83292e0ed231090538fc4ca45425b0f0480eb338bbc6e", size = 12056279, upload-time = "2026-06-11T12:46:24.255Z" }, + { url = "https://files.pythonhosted.org/packages/41/36/e8c5f8c6ec71de73733695ebc809e98b178b534ec6d8eaa31a7ebab4ad4c/grpcio-1.81.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2aa72e3ce1770317ef534f63d397b55e130725f5149bd36077c3b539019db27", size = 6608225, upload-time = "2026-06-11T12:46:27.601Z" }, + { url = "https://files.pythonhosted.org/packages/30/22/96fc577a845ab093326d9ab1adb874bd4936c8cf98ac8ed2f3db13a0a2fb/grpcio-1.81.1-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0490c30c261eded63f3f354979f9dc4502a9fb944cccb60cd9dc85f5a7349854", size = 7306576, upload-time = "2026-06-11T12:46:30.514Z" }, + { url = "https://files.pythonhosted.org/packages/76/7b/61dab5d5969f28d97fb1009cead1df0a5cd987d3315e1b37f18a4449f8bc/grpcio-1.81.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:410482da976329fe5f4067270401b12cf2bd552ff8020f054ecfaddb5475f9d6", size = 6812165, upload-time = "2026-06-11T12:46:33.699Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/6e501929d4f5f96462fd82fd9f0f06e5f9612207582b862868d68757b27d/grpcio-1.81.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e3657301562ac3cb8018d30d0d3ebfa39932239f7b5703422057ef14b69949f5", size = 7422962, upload-time = "2026-06-11T12:46:36.511Z" }, + { url = "https://files.pythonhosted.org/packages/2a/7e/f2157589e66daa78ebb3165942d05a08bdea93b9d11c2bc1e172aef89685/grpcio-1.81.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:24c8e57504c8f45b237e40b99262d181071e5099a07053695b75d97bb53053a0", size = 8408176, upload-time = "2026-06-11T12:46:39.803Z" }, + { url = "https://files.pythonhosted.org/packages/da/df/c6717fef716e00d235ffb96123baf6dce76d6004f6233fa767c502861460/grpcio-1.81.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b427c19380991a4eaab2f6144b64b99b412043314c6bf4ab544f97bb31ee4190", size = 7846681, upload-time = "2026-06-11T12:46:43.013Z" }, + { url = "https://files.pythonhosted.org/packages/36/84/3502e9f210a6a5c4438c8aca3f88edd2e04f6a27f3d41b26cf0a0024b096/grpcio-1.81.1-cp314-cp314-win32.whl", hash = "sha256:61233fe8951e5c85dff81c2458b6528624760166946b5b47ea150a589168411f", size = 4264615, upload-time = "2026-06-11T12:46:45.741Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b0/4af731ff7492c68a96e4c71bfd0f4590acde92b31c6fe4894e6465c10ff6/grpcio-1.81.1-cp314-cp314-win_amd64.whl", hash = "sha256:3768a5ff1b2125e6f552e561b6b2dca0e64982d8949689b4df145cf8b98d7821", size = 5070275, upload-time = "2026-06-11T12:46:48.486Z" }, ] [[package]] @@ -1228,7 +1228,7 @@ wheels = [ [[package]] name = "ipykernel" -version = "7.2.0" +version = "7.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "appnope", marker = "sys_platform == 'darwin'" }, @@ -1238,16 +1238,16 @@ dependencies = [ { name = "jupyter-client" }, { name = "jupyter-core" }, { name = "matplotlib-inline" }, - { name = "nest-asyncio" }, + { name = "nest-asyncio2" }, { name = "packaging" }, { name = "psutil" }, { name = "pyzmq" }, { name = "tornado" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ca/8d/b68b728e2d06b9e0051019640a40a9eb7a88fcd82c2e1b5ce70bef5ff044/ipykernel-7.2.0.tar.gz", hash = "sha256:18ed160b6dee2cbb16e5f3575858bc19d8f1fe6046a9a680c708494ce31d909e", size = 176046, upload-time = "2026-02-06T16:43:27.403Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/c4/e4a38f579de4225a561305666f7541cdabb30075def2aa1ac17bd73c1fb5/ipykernel-7.3.0.tar.gz", hash = "sha256:9acaaaf97d16355166e4085afe9d225bfbdf2b7ef520f9df3be8f2b248275e09", size = 184899, upload-time = "2026-06-10T08:41:25.481Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl", hash = "sha256:3bbd4420d2b3cc105cbdf3756bfc04500b1e52f090a90716851f3916c62e1661", size = 118788, upload-time = "2026-02-06T16:43:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/3d/02/77b271f5dc58bfbc0b577c877b2365d1ffea2afe66a80c13f2312820348c/ipykernel-7.3.0-py3-none-any.whl", hash = "sha256:897eb64da762549ef610698fca5e9675195ec6ac8ec7f19d81ce1ca20c876057", size = 120583, upload-time = "2026-06-10T08:41:23.648Z" }, ] [[package]] @@ -1397,7 +1397,7 @@ wheels = [ [[package]] name = "jupyter-client" -version = "8.9.0" +version = "8.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jupyter-core" }, @@ -1407,9 +1407,9 @@ dependencies = [ { name = "traitlets" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9c/a2/9ef7832e6c7d619a35bf6732d199a850ef3b6db4af1cd783a71f81eaeab0/jupyter_client-8.9.0.tar.gz", hash = "sha256:23c0c182e1901ffdab96b5a02cb7bc6f0b04524fd7fc43688a14c4ff2308fb77", size = 358714, upload-time = "2026-06-05T12:17:44.014Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/dc/5512503b088997c2250b8bf18258fba9d9ce5ead641183700960d3c9d342/jupyter_client-8.9.1.tar.gz", hash = "sha256:a58f730dd9e728ba16ba1d62ebccf7ffe1ebbdbce4e95cfae941b7321ae1f4fa", size = 359256, upload-time = "2026-06-09T13:15:01.033Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/ce/93ccaca54d41327491b1f6d7341d0eef49f71e8929f875b53c45446335ca/jupyter_client-8.9.0-py3-none-any.whl", hash = "sha256:a0efc16adcec2bb6669d2cf91e3ba5337b338bd1ecd0d9c70940752fcb1144b2", size = 109723, upload-time = "2026-06-05T12:17:42.135Z" }, + { url = "https://files.pythonhosted.org/packages/3f/6f/56d39bf385c5c27988aebaf0c18a2a17e960575740100973511018bd904e/jupyter_client-8.9.1-py3-none-any.whl", hash = "sha256:0b7a295bc46e8751e9adae84781f726c851c1d911bd793edc4a3bde942e3da81", size = 109828, upload-time = "2026-06-09T13:14:58.835Z" }, ] [[package]] @@ -1805,7 +1805,7 @@ wheels = [ [[package]] name = "matplotlib" -version = "3.10.9" +version = "3.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "contourpy" }, @@ -1818,53 +1818,53 @@ dependencies = [ { name = "pyparsing" }, { name = "python-dateutil" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/63/1b/4be5be87d43d327a0cf4de1a56e86f7f84c89312452406cf122efe2839e6/matplotlib-3.10.9.tar.gz", hash = "sha256:fd66508e8c6877d98e586654b608a0456db8d7e8a546eb1e2600efd957302358", size = 34811233, upload-time = "2026-04-24T00:14:13.539Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/8c/290f021104741fea63769c31494f5324c0cd249bf536a65a4350767b1f22/matplotlib-3.10.9-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:68cfdcede415f7c8f5577b03303dd94526cdb6d11036cecdc205e08733b2d2bb", size = 8306860, upload-time = "2026-04-24T00:12:01.207Z" }, - { url = "https://files.pythonhosted.org/packages/51/18/325cd32ece1120d1da51cc4e4294c6580190699490183fc2fe8cb6d61ec5/matplotlib-3.10.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfca0129678bd56379db26c52b5d77ed7de314c047492fbdc763aa7501710cfb", size = 8199254, upload-time = "2026-04-24T00:12:04.239Z" }, - { url = "https://files.pythonhosted.org/packages/79/db/e28c1b83e3680740aa78925f5fb2ae4d16207207419ad75ea9fe604f8676/matplotlib-3.10.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e436d155fa8a3399dc62683f8f5d0e2e50d25d0144a73edd73f82eec8f4abfb", size = 8777092, upload-time = "2026-04-24T00:12:06.793Z" }, - { url = "https://files.pythonhosted.org/packages/55/fa/3ce7adfe9ba101748f465211660d9c6374c876b671bdb8c2bb6d347e8b94/matplotlib-3.10.9-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56fc0bd271b00025c6edfdc7c2dcd247372c8e1544971d62e1dc7c17367e8bf9", size = 9595691, upload-time = "2026-04-24T00:12:09.706Z" }, - { url = "https://files.pythonhosted.org/packages/36/c4/6960a76686ed668f2c60f84e9799ba4c0d56abdb36b1577b60c1d061d1ec/matplotlib-3.10.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5a6104ed666402ba5106d7f36e0e0cdca4e8d7fa4d39708ca88019e2835a2eb", size = 9659771, upload-time = "2026-04-24T00:12:12.766Z" }, - { url = "https://files.pythonhosted.org/packages/7e/0d/271aace3342157c64700c9ff4c59c7b392f3dbab393692e8db6fbe7ab96c/matplotlib-3.10.9-cp311-cp311-win_amd64.whl", hash = "sha256:d730e984eddf56974c3e72b6129c7ca462ac38dc624338f4b0b23eb23ecba00f", size = 8205112, upload-time = "2026-04-24T00:12:15.773Z" }, - { url = "https://files.pythonhosted.org/packages/e2/ee/cb57ad4754f3e7b9174ce6ce66d9205fb827067e48a9f58ac09d7e7d6b77/matplotlib-3.10.9-cp311-cp311-win_arm64.whl", hash = "sha256:51bf0ddbdc598e060d46c16b5590708f81a1624cefbaaf62f6a81bf9285b8c80", size = 8132310, upload-time = "2026-04-24T00:12:18.645Z" }, - { url = "https://files.pythonhosted.org/packages/35/c6/5581e26c72233ebb2a2a6fed2d24fb7c66b4700120b813f51b0555acf0b6/matplotlib-3.10.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f0c3c28d9fbcc1fe7a03be236d73430cf6409c41fb2383a7ac52fe932b072cb1", size = 8319908, upload-time = "2026-04-24T00:12:21.323Z" }, - { url = "https://files.pythonhosted.org/packages/b7/18/4880dd762e40cd360c1bf06e890c5a97b997e91cb324602b1a19950ad5ce/matplotlib-3.10.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cb28c2bd769aa3e98322c6ab09854cbcc52ab69d2759d681bba3e327b2b320", size = 8216016, upload-time = "2026-04-24T00:12:23.4Z" }, - { url = "https://files.pythonhosted.org/packages/32/91/d024616abdba99e83120e07a20658976f6a343646710760c4a51df126029/matplotlib-3.10.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ae20801130378b82d647ff5047c07316295b68dc054ca6b3c13519d0ea624285", size = 8789336, upload-time = "2026-04-24T00:12:26.096Z" }, - { url = "https://files.pythonhosted.org/packages/5c/04/030a2f61ef2158f5e4c259487a92ac877732499fb33d871585d89e03c42d/matplotlib-3.10.9-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c63ebcd8b4b169eb2f5c200552ae6b8be8999a005b6b507ed76fb8d7d674fe2", size = 9604602, upload-time = "2026-04-24T00:12:29.052Z" }, - { url = "https://files.pythonhosted.org/packages/fc/c2/541e4d09d87bb6b5830fc28b4c887a9a8cf4e1c6cee698a8c05552ae2003/matplotlib-3.10.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d75d11c949914165976c621b2324f9ef162af7ebf4b057ddf95dd1dba7e5edcf", size = 9670966, upload-time = "2026-04-24T00:12:32.131Z" }, - { url = "https://files.pythonhosted.org/packages/04/a1/4571fc46e7702de8d0c2dc54ad1b2f8e29328dea3ee90831181f7353d93c/matplotlib-3.10.9-cp312-cp312-win_amd64.whl", hash = "sha256:d091f9d758b34aaaaa6331d13574bf01891d903b3dec59bfff458ef7551de5d6", size = 8217462, upload-time = "2026-04-24T00:12:35.226Z" }, - { url = "https://files.pythonhosted.org/packages/4b/d0/2269edb12aa30c13c8bcc9382892e39943ce1d28aab4ec296e0381798e81/matplotlib-3.10.9-cp312-cp312-win_arm64.whl", hash = "sha256:10cc5ce06d10231c36f40e875f3c7e8050362a4ee8f0ee5d29a6b3277d57bb42", size = 8136688, upload-time = "2026-04-24T00:12:37.442Z" }, - { url = "https://files.pythonhosted.org/packages/aa/d3/8d4f6afbecb49fc04e060a57c0fce39ea51cc163a6bd87303ccd698e4fa6/matplotlib-3.10.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b580440f1ff81a0e34122051a3dfabb7e4b7f9e380629929bde0eff9af72165f", size = 8320331, upload-time = "2026-04-24T00:12:39.688Z" }, - { url = "https://files.pythonhosted.org/packages/63/d9/9e14bc7564bf92d5ffa801ae5fac819ce74b925dfb55e3ebde61a3bbad3e/matplotlib-3.10.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b1b745c489cd1a77a0dc1120a05dc87af9798faebc913601feb8c73d89bf2d1e", size = 8216461, upload-time = "2026-04-24T00:12:42.494Z" }, - { url = "https://files.pythonhosted.org/packages/8a/17/4402d0d14ccf1dfc70932600b68097fbbf9c898a4871d2cbbe79c7801a32/matplotlib-3.10.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8f3bcac1ca5ed000a6f4337d47ba67dfddf37ed6a46c15fd7f014997f7bf865f", size = 8790091, upload-time = "2026-04-24T00:12:44.789Z" }, - { url = "https://files.pythonhosted.org/packages/3e/0b/322aeec06dd9b91411f92028b37d447342770a24392aa4813e317064dad5/matplotlib-3.10.9-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a8d66a55def891c33147ba3ba9bfcabf0b526a43764c818acbb4525e5ed0838", size = 9605027, upload-time = "2026-04-24T00:12:47.583Z" }, - { url = "https://files.pythonhosted.org/packages/74/88/5f13482f55e7b00bcfc09838b093c2456e1379978d2a146844aae05350ad/matplotlib-3.10.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d843374407c4017a6403b59c6c81606773d136f3259d5b6da3131bc814542cc2", size = 9671269, upload-time = "2026-04-24T00:12:50.878Z" }, - { url = "https://files.pythonhosted.org/packages/c5/e0/0840fd2f93da988ec660b8ad1984abe9f25d2aed22a5e394ff1c68c88307/matplotlib-3.10.9-cp313-cp313-win_amd64.whl", hash = "sha256:f4399f64b3e94cd500195490972ae1ee81170df1636fa15364d157d5bdd7b921", size = 8217588, upload-time = "2026-04-24T00:12:53.784Z" }, - { url = "https://files.pythonhosted.org/packages/47/b9/d706d06dd605c49b9f83a2aed8c13e3e5db70697d7a80b7e3d7915de6b17/matplotlib-3.10.9-cp313-cp313-win_arm64.whl", hash = "sha256:ba7b3b8ef09eab7df0e86e9ae086faa433efbfbdb46afcb3aa16aabf779469a8", size = 8136913, upload-time = "2026-04-24T00:12:56.501Z" }, - { url = "https://files.pythonhosted.org/packages/9b/45/6e32d96978264c8ca8c4b1010adb955a1a49cfaf314e212bbc8908f04a61/matplotlib-3.10.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:09218df8a93712bd6ea133e83a153c755448cf7868316c531cffcc43f69d1cc9", size = 8368019, upload-time = "2026-04-24T00:12:58.896Z" }, - { url = "https://files.pythonhosted.org/packages/86/0a/c8e3d3bba245f0f7fc424937f8ff7ef77291a36af3edb97ccd78aa93d84f/matplotlib-3.10.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:82368699727bfb7b0182e1aa13082e3c08e092fa1a25d3e1fd92405bff96f6d4", size = 8264645, upload-time = "2026-04-24T00:13:01.406Z" }, - { url = "https://files.pythonhosted.org/packages/3d/aa/5bf5a14fe4fed73a4209a155606f8096ff797aad89c6c35179026571133e/matplotlib-3.10.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3225f4e1edcb8c86c884ddf79ebe20ecd0a67d30188f279897554ccd8fded4dc", size = 8802194, upload-time = "2026-04-24T00:13:03.702Z" }, - { url = "https://files.pythonhosted.org/packages/dd/5e/b4be852d6bba6fd15893fadf91ff26ae49cb91aac789e95dde9d342e664f/matplotlib-3.10.9-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de2445a0c6690d21b7eb6ce071cebad6d40a2e9bdf10d039074a96ba19797b99", size = 9622684, upload-time = "2026-04-24T00:13:06.647Z" }, - { url = "https://files.pythonhosted.org/packages/4c/3d/ed428c971139112ef730f62770654d609467346d09d4b62617e1afd68a5a/matplotlib-3.10.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b2b9516251cb89ff618d757daec0e2ed1bf21248013844a853d87ef85ab3081d", size = 9680790, upload-time = "2026-04-24T00:13:10.009Z" }, - { url = "https://files.pythonhosted.org/packages/e7/09/052e884aaf2b985c63cb79f715f1d5b6a3eaa7de78f6a52b9dbc077d5b53/matplotlib-3.10.9-cp313-cp313t-win_amd64.whl", hash = "sha256:e9fae004b941b23ff2edcf1567a857ed77bafc8086ffa258190462328434faf8", size = 8287571, upload-time = "2026-04-24T00:13:13.087Z" }, - { url = "https://files.pythonhosted.org/packages/f4/38/ae27288e788c35a4250491422f3db7750366fc8c97d6f36fbdecfc1f5518/matplotlib-3.10.9-cp313-cp313t-win_arm64.whl", hash = "sha256:6b63d9c7c769b88ab81e10dc86e4e0607cf56817b9f9e6cf24b2a5f1693b8e38", size = 8188292, upload-time = "2026-04-24T00:13:15.546Z" }, - { url = "https://files.pythonhosted.org/packages/d6/e6/3bd8afd04949f02eabc1c17115ea5255e19cacd4d06fc5abdde4eeb0052c/matplotlib-3.10.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:172db52c9e683f5d12eaf57f0f54834190e12581fe1cc2a19595a8f5acb4e77d", size = 8321276, upload-time = "2026-04-24T00:13:18.318Z" }, - { url = "https://files.pythonhosted.org/packages/41/86/86231232fff41c9f8e4a1a7d7a597d349a02527109c3af7d618366122139/matplotlib-3.10.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97e35e8d39ccc85859095e01a53847432ba9a53ddf7986f7a54a11b73d0e143f", size = 8218218, upload-time = "2026-04-24T00:13:20.974Z" }, - { url = "https://files.pythonhosted.org/packages/85/8f/becc9722cafc64f5d2eb0b7c1bf5f585271c618a45dbd8fabeb021f898b6/matplotlib-3.10.9-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aba1615dabe83188e19d4f75a253c6a08423e04c1425e64039f800050a69de6b", size = 9608145, upload-time = "2026-04-24T00:13:23.228Z" }, - { url = "https://files.pythonhosted.org/packages/32/5d/f7e914f7d9325abff4057cee62c0fa70263683189f774473cbfb534cd13b/matplotlib-3.10.9-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34cf8167e023ad956c15f36302911d5406bd99a9862c1a8499ea6f7c0e015dc2", size = 9885085, upload-time = "2026-04-24T00:13:25.849Z" }, - { url = "https://files.pythonhosted.org/packages/a5/fd/fa69f2221534e80cc5772ac2b7d222011a2acafc2ec7216d5dd174c864ae/matplotlib-3.10.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:59476c6d29d612b8e9bb6ce8c5b631be6ba8f9e3a2421f22a02b192c7dd28716", size = 9672358, upload-time = "2026-04-24T00:13:28.906Z" }, - { url = "https://files.pythonhosted.org/packages/ab/1a/5a4f747a8b271cbb024946d2dd3c913ab5032ba430626f8c3528ada96b4b/matplotlib-3.10.9-cp314-cp314-win_amd64.whl", hash = "sha256:336b9acc64d309063126edcdaca00db9373af3c476bb94388fe9c5a53ad13e6f", size = 8349970, upload-time = "2026-04-24T00:13:31.904Z" }, - { url = "https://files.pythonhosted.org/packages/64/dc/95d60ecaefe30680a154b52ea96ab4b0dab547f1fd6aa12f5fb655e89cae/matplotlib-3.10.9-cp314-cp314-win_arm64.whl", hash = "sha256:2dc9477819ffd78ad12a20df1d9d6a6bd4fec6aaa9072681465fddca052f1456", size = 8272785, upload-time = "2026-04-24T00:13:34.511Z" }, - { url = "https://files.pythonhosted.org/packages/70/a0/005d68bc8b8418300ce6591f18586910a8526806e2ab663933d9f20a41e9/matplotlib-3.10.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:da4e09638420548f31c354032a6250e473c68e5a4e96899b4844cf39ddea23fe", size = 8367999, upload-time = "2026-04-24T00:13:36.962Z" }, - { url = "https://files.pythonhosted.org/packages/22/05/1236cc9290be70b2498af20ca348add76e3fffe7f67b477db5133a84f3ea/matplotlib-3.10.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:345f6f68ecc8da0ca56fad2ea08fde1a115eda530079eca185d50a7bc3e146c6", size = 8264543, upload-time = "2026-04-24T00:13:39.851Z" }, - { url = "https://files.pythonhosted.org/packages/cd/c2/071f5a5ff6c5bd63aaaf2f45c811d9bf2ced94bde188d9e1a519e21d0cba/matplotlib-3.10.9-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4edcfbd8565339aa62f1cd4012f7180926fdbe71850f7b0d3c379c175cd6b66c", size = 9622800, upload-time = "2026-04-24T00:13:42.296Z" }, - { url = "https://files.pythonhosted.org/packages/95/57/da7d1f10a85624b9e7db68e069dd94e58dc41dbf9463c5921632ecbe3661/matplotlib-3.10.9-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6be157fe17fc37cb95ac1d7374cf717ce9259616edec911a78d9d26dae8522d4", size = 9888561, upload-time = "2026-04-24T00:13:45.026Z" }, - { url = "https://files.pythonhosted.org/packages/67/b2/ef8d6bb59b0edb6c16c968b70f548aa13b54348972def5aa6ac85df67145/matplotlib-3.10.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4e42042d54db34fda4e95a7bd3e5789c2a995d2dad3eb8850232ee534092fbbf", size = 9680884, upload-time = "2026-04-24T00:13:48.066Z" }, - { url = "https://files.pythonhosted.org/packages/61/1c/d21bfeb9931881ebe96bcfcff27c7ae4b160ae0ec291a714c42641a56d75/matplotlib-3.10.9-cp314-cp314t-win_amd64.whl", hash = "sha256:c27df8b3848f32a83d1767566595e43cfaa4460380974da06f4279a7ec143c39", size = 8432333, upload-time = "2026-04-24T00:13:51.008Z" }, - { url = "https://files.pythonhosted.org/packages/78/23/92493c3e6e1b635ccfff146f7b99e674808787915420373ac399283764c2/matplotlib-3.10.9-cp314-cp314t-win_arm64.whl", hash = "sha256:a49f1eadc84ca85fd72fa4e89e70e61bf86452df6f971af04b12c60761a0772c", size = 8324785, upload-time = "2026-04-24T00:13:53.633Z" }, - { url = "https://files.pythonhosted.org/packages/63/e2/9f66ca6a651a52abfe0d4964ce01439ed34f3f1e119de10ff3a07f403043/matplotlib-3.10.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:42fb814efabe95c06c1994d8ab5a8385f43a249e23badd3ba931d4308e5bca20", size = 8304420, upload-time = "2026-04-24T00:14:04.57Z" }, - { url = "https://files.pythonhosted.org/packages/e8/e8/467c03568218792906aa87b5e7bb379b605e056ed0c74fe00c051786d925/matplotlib-3.10.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f76e640a5268850bfda54b5131b1b1941cc685e42c5fa98ed9f2d64038308cba", size = 8197981, upload-time = "2026-04-24T00:14:07.233Z" }, - { url = "https://files.pythonhosted.org/packages/6f/87/afead29192170917537934c6aff4b008c805fff7b1ccea0c79120d96beda/matplotlib-3.10.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3fc0364dfbe1d07f6d15c5ebd0c5bf89e126916e5a8667dd4a7a6e84c36653d4", size = 8774002, upload-time = "2026-04-24T00:14:09.816Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/1f/24/080c99d223d158d3a8902769269ab6da5b50f7a0e6e072513907e02b7a6c/matplotlib-3.11.0.tar.gz", hash = "sha256:68c0c7be01b30dcca3638934f7f591df73401235cbdbf0d1ab1c71e7db7f8b57", size = 33251176, upload-time = "2026-06-12T02:29:15.508Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/a2/78f662f1b18968531f67d3fcde1b7ea8496920bacd4f16ddb5b79d112e46/matplotlib-3.11.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f857524b442f0f36e641868ce2171aafa88cb0bc0644f4e1d8a5df9b32649fef", size = 9436261, upload-time = "2026-06-12T02:27:34.161Z" }, + { url = "https://files.pythonhosted.org/packages/5e/92/044f1de43901310202f4c79acf4f141be53b2ca8d8380e2fcefb3d523a75/matplotlib-3.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:57baa92fdc82948ed716eae6d2579d4d6f40965cd8d2f416755b4a72580a3233", size = 9264669, upload-time = "2026-06-12T02:27:37.413Z" }, + { url = "https://files.pythonhosted.org/packages/53/f4/f0b4f9ba7ec14a7af8151f3ad71ecfe3561e6ba38cfab1db3681ba4ca112/matplotlib-3.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:630eee0e67d35cce2019a0e670719f4816e3b86aff0fa72729f6c69786fceb45", size = 10021076, upload-time = "2026-06-12T02:27:39.926Z" }, + { url = "https://files.pythonhosted.org/packages/d7/33/4d679c6dcd594a156542080ac907ddccf7b09ca11655c4b28eca8e9ee5da/matplotlib-3.11.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5106c444d0bf966eee2853548c03772af4ab7199118e086c62fbac8ccb07c055", size = 10828999, upload-time = "2026-06-12T02:27:42.433Z" }, + { url = "https://files.pythonhosted.org/packages/07/74/0a3683802037d8cd013144d77c247219b47f2aabace6fdde74faa12bacf7/matplotlib-3.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4d7aea652b58e686444079be3376ef546bffa1eee9b9bb9c472b9fcf6cf410d3", size = 10913103, upload-time = "2026-06-12T02:27:44.827Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9f/970fcbf381e82ec66fdf5da8ea76e2e9240f61a24011ce9fd1d42c37ac2d/matplotlib-3.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:70a5b3e9a5dab708c0f039709ae7c68d5b4d254e291ef76492cdba230c8bb5e4", size = 9310945, upload-time = "2026-06-12T02:27:46.867Z" }, + { url = "https://files.pythonhosted.org/packages/14/4e/6e7cfed23611265ded53806852343b5c59339e506e84c474a9b5afc3b249/matplotlib-3.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:3d68266213e73823ac3be90615bab0cf31f88851e114cdb1dd25dacf3b01e1a7", size = 8999304, upload-time = "2026-06-12T02:27:48.798Z" }, + { url = "https://files.pythonhosted.org/packages/da/17/f5276b496c61477a6c4fc5e7401f4bfe1c2e5ef7c6cd67896f2ade3809cb/matplotlib-3.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:06b5872e9cf11adc8f589ded3ce11bc3e1061ad498259664fabc1f6615beb918", size = 9449976, upload-time = "2026-06-12T02:27:50.989Z" }, + { url = "https://files.pythonhosted.org/packages/82/34/bdd77418adb2178a1d59f044bd67bfebb115896e91b840b8a197eb3f4f4e/matplotlib-3.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0515d495124be3124340e59f164d901ed4484e2246a5b74cfa483cac3b80bd97", size = 9279307, upload-time = "2026-06-12T02:27:53.247Z" }, + { url = "https://files.pythonhosted.org/packages/94/95/7f522393c88313336b20d70fc849555757b2e5febc22b83b3a3f0fd4bce9/matplotlib-3.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be5f93a1d21981bfb802ded0d77a0caa92d4342a47d45754fac77e314a506344", size = 10031353, upload-time = "2026-06-12T02:27:55.215Z" }, + { url = "https://files.pythonhosted.org/packages/87/ce/8f25a0e3186aefd61913e7467d1b999465bcd0d0c03ac695c1b26ca559b7/matplotlib-3.11.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41635d7909d19e52e924a521dde6d8f670b0f53ab1d0e8c331fa831554f681d1", size = 10839232, upload-time = "2026-06-12T02:27:57.746Z" }, + { url = "https://files.pythonhosted.org/packages/85/c2/db15da2bbdf9e3ca66df7db8e2c33a1dfed67be24a24d2c878efaaff01d6/matplotlib-3.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:94f5000f67ca9faa300863ea17f8bce9175cb67b88bec4bc7780502d53dd7c9e", size = 10923899, upload-time = "2026-06-12T02:28:00.223Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2f/a58a4443a4d052a4ea77557478336aefc26c7981f6408d37adba763aa758/matplotlib-3.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:ac6f1ef39f3d0f9e2463303013094992cdbe0f85f43bc54155bc472b2042768e", size = 9329528, upload-time = "2026-06-12T02:28:02.27Z" }, + { url = "https://files.pythonhosted.org/packages/61/0f/4b669589d47733b97ab9df4b58d6fc1e68acb5ea42a928dc7cbdd6bf5871/matplotlib-3.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:9dd11fb612ce7bc60b1de5b4fc87ff959d22317b5de42aabf392f66f97af22eb", size = 9003413, upload-time = "2026-06-12T02:28:04.49Z" }, + { url = "https://files.pythonhosted.org/packages/55/41/aa47f156b061d14c98b906f76c428507397708ec63ff94f410ae1752b426/matplotlib-3.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ce3b839b34ae1f430b4616893a2945a2999debaa7e94e7e29a2a8bbf286f7b5", size = 9450532, upload-time = "2026-06-12T02:28:06.769Z" }, + { url = "https://files.pythonhosted.org/packages/8c/4f/5a9eb0375e81413953febf8af7b012a6b6357f53438a15c4f5ad86c6bbb5/matplotlib-3.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:373db8f91214e8ccaf35ac833cc1dd59dd961e148bbd55dd027141591dde1313", size = 9279760, upload-time = "2026-06-12T02:28:09.152Z" }, + { url = "https://files.pythonhosted.org/packages/a4/c0/1117d53077e3ac3152503a84e9cf7a5c239576805ee71276e80c2aaa7471/matplotlib-3.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be152b7570324dc8d01574cc9474dd2d803237acf528bcbb5b211fa347461a09", size = 10031623, upload-time = "2026-06-12T02:28:11.26Z" }, + { url = "https://files.pythonhosted.org/packages/92/7e/e937138daffad65b71bf831a377809dcbc830fb4f31a31e067dc1faa2575/matplotlib-3.11.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:126f256df600652d7e4b394cf3164ff75210a00038f287c95a012a6f58d0e83f", size = 10839372, upload-time = "2026-06-12T02:28:14.102Z" }, + { url = "https://files.pythonhosted.org/packages/1d/c2/438ecc197ffb8023b6b9922915542f2172f5fd45b76703b0b4fc47322243/matplotlib-3.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:03acfeddf87b0dddb11b081ef7740ad445a3ca8bcb6b8e3011b08f2cf802b75c", size = 10924099, upload-time = "2026-06-12T02:28:16.383Z" }, + { url = "https://files.pythonhosted.org/packages/40/2e/395883da416f378b3ed2c9f3e843ac477eae1ce731b671b79adaa6f0bacd/matplotlib-3.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:ab3722f04f3ff34c23b5012c5873d2894174e06c3822fcdac3610965a5ac7d06", size = 9329727, upload-time = "2026-06-12T02:28:18.581Z" }, + { url = "https://files.pythonhosted.org/packages/61/82/2c388956abf8bf392dfb5b8917c502f1082df6a941b781ab8c8e5ba2474b/matplotlib-3.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:c945824670fb8915b4ac879e5e61f3c58e0913022f70a0de4c082b17372f8771", size = 9003506, upload-time = "2026-06-12T02:28:20.474Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c1/34454baa44da7975ada82e9aea37105ec47059514dc967d3be14426ba8dc/matplotlib-3.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3489c3dc487669b4a980bc3068f87856de7a1564248d3f6c629efb2a58b03f24", size = 9499838, upload-time = "2026-06-12T02:28:22.713Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c3/98fe79a398cf232219f090163a7fa7e6766e9f2e0ad26df54d6f8934d8ee/matplotlib-3.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6a98f5476ce784a50ce09998f4ae1e6a9f25043cef8a480c98949902eda74620", size = 9332298, upload-time = "2026-06-12T02:28:24.796Z" }, + { url = "https://files.pythonhosted.org/packages/95/e4/b4b7c33151e74e5c802f3cde1ba807ebfc38401e329b44e215a5888dd76d/matplotlib-3.11.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:565af866fd63e4bd3f987d580afe27c44c2552a3b3305f4ecbb85133601ea6f3", size = 10045491, upload-time = "2026-06-12T02:28:27.141Z" }, + { url = "https://files.pythonhosted.org/packages/71/28/394548efd68354110c1a1be11fe6b6e559e06d1a23da35908a0e316c55a9/matplotlib-3.11.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e6b3e64dea5062c570f04358e2711859f3531b459f29516274fbad889079e4f3", size = 10857059, upload-time = "2026-06-12T02:28:29.222Z" }, + { url = "https://files.pythonhosted.org/packages/c8/44/e7922e6e2a4d63bdfbc9dc4a53e3850ab438d46cf42e6779bb15ec92c948/matplotlib-3.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:942b37c5db1899610bd1543ce8e13e4ecff9a4633e7f63bb6aa9205d2644ebd1", size = 10939576, upload-time = "2026-06-12T02:28:31.66Z" }, + { url = "https://files.pythonhosted.org/packages/3d/be/b1ca96003a441d619b727fee21d671fdff7a5ce2f1bb797b2521aa2f679a/matplotlib-3.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c08e649a6313e1291e713623b97a38e5bb4aa580b2a100a94a3309bc6b9c8eb3", size = 9379519, upload-time = "2026-06-12T02:28:33.888Z" }, + { url = "https://files.pythonhosted.org/packages/e3/72/4bf3b91821c34596dd6a7bdac5836d94f744144c8208939ef49d8ec43f7e/matplotlib-3.11.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2746cd2c113742ff6ce37a864c5ac5fd7aa644568f445e66166e457ac78e40e0", size = 9055456, upload-time = "2026-06-12T02:28:35.878Z" }, + { url = "https://files.pythonhosted.org/packages/57/52/a94102ac99eb78e2fe9b826674f9ef9ee23327110ea6ab4776c1b4eb6209/matplotlib-3.11.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3338e3e3de128cf50d0d2fb92a122815daf9c755bd882a474343c05f8fd7ec79", size = 9452137, upload-time = "2026-06-12T02:28:37.93Z" }, + { url = "https://files.pythonhosted.org/packages/7c/03/b8cdb625a21f710dfa11bbca1f48fb4057d2c0286975f8b415bf80942c99/matplotlib-3.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:25c2e5455efd8d99f41fb79871a31feb7d301569642e332ec58d72cfe9282bc3", size = 9281514, upload-time = "2026-06-12T02:28:40.028Z" }, + { url = "https://files.pythonhosted.org/packages/b7/2d/4e1240ea82ee197dfb3851e71f71c87eeeb975f1753b56a0588e4e80739a/matplotlib-3.11.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9695457a467ff86d23f35037a43deb6f1134dd6d3e2ac8ce1e2087cff09ffb9", size = 10843005, upload-time = "2026-06-12T02:28:42.39Z" }, + { url = "https://files.pythonhosted.org/packages/29/dc/6377ecfaa5fef79430f74a1a16638b4e2aa30d4692bae2c19f9d76fe3b01/matplotlib-3.11.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:19c16c61dea63b3582918503e6b294193961261d9daa806d4ae2151f1ad05430", size = 11127459, upload-time = "2026-06-12T02:28:44.483Z" }, + { url = "https://files.pythonhosted.org/packages/6f/41/795c405aa7560443a3b01309424cde4a1113b85c90b8a63417444a749617/matplotlib-3.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2d72ea8b7924f3cb955e61518d21e43b3df1e6c8a793b480a0c1214f185d30ba", size = 10925160, upload-time = "2026-06-12T02:28:46.564Z" }, + { url = "https://files.pythonhosted.org/packages/1a/f7/3a9e6389a7cfaeff76c56e40c2dabcb13110e21e82f837228c834ebe748c/matplotlib-3.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:1c02da0a629dfa9debf52725ea06866b74c1fb70a895bae05e4493d34074f9f2", size = 9485186, upload-time = "2026-06-12T02:28:49.344Z" }, + { url = "https://files.pythonhosted.org/packages/8b/c0/396478ee7cf2091d182db8b4a8695f6a37f1ddb978989cf9dbb84cd5c123/matplotlib-3.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:aa55d73b3117d4b07f959cd9eb6f69b375d8df3414139c479388e551aa5d999d", size = 9160349, upload-time = "2026-06-12T02:28:51.382Z" }, + { url = "https://files.pythonhosted.org/packages/c5/6f/1c3bd51bb2b34eaacdcf3c3d859dbb357f952fc8020c617dc118ad7c9e38/matplotlib-3.11.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a9d8c6e7cd2f0ddf11d8d92e520dd1d9d2abb0cf6ac8831e338666c81e905847", size = 9500921, upload-time = "2026-06-12T02:28:53.443Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/4d861d0121840cb1a3fd4a10deb211efd6fccd481ed23e553f31f4f4da4a/matplotlib-3.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:be050fcf32f729eda99f7f75a80bf67612ce16ab9ac1c23a387dcaede95cb70e", size = 9332190, upload-time = "2026-06-12T02:28:55.623Z" }, + { url = "https://files.pythonhosted.org/packages/4b/cb/22f6bc35711a0b5639a784e74e653e77c86210bd4304449dd399a482f74e/matplotlib-3.11.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfabef0230d0697aa0d717385194dd41162e00207a68bf4abf94c2bf4c27dca0", size = 10854181, upload-time = "2026-06-12T02:28:57.856Z" }, + { url = "https://files.pythonhosted.org/packages/3f/7e/9a9eaca731a2939589da520f0ebe8fd8753d0f51fca98c7d20af6dbe261a/matplotlib-3.11.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1644db30e759199443493ac5e5caec24fdb775a8f6123021f85ba47c4133c3cb", size = 11137715, upload-time = "2026-06-12T02:29:00.555Z" }, + { url = "https://files.pythonhosted.org/packages/ef/f9/9b030b6088354acb0296871bb624b25befc1c42509d3c6cd17420c83a5b8/matplotlib-3.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:15b0d160079cb10699a0e98b5989c70677b2df7cacdc62af67c30f2facec46d9", size = 10939427, upload-time = "2026-06-12T02:29:02.527Z" }, + { url = "https://files.pythonhosted.org/packages/59/94/6b273eaee4ee250863567d100865da61a5c1527fa67f527b7ed22e0dd29c/matplotlib-3.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:446307e6b04b57b1f1239e228a1ec2af0d589a1008cebc3dfa3f5441d095cfb6", size = 9535809, upload-time = "2026-06-12T02:29:04.994Z" }, + { url = "https://files.pythonhosted.org/packages/60/95/1d36bddf2b7e2692c1540e78a6e5bc88bc1496b137e3e35a611f91b65ac3/matplotlib-3.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:652fb5696271d4c50f196d22a5ff4f8e4444c74f847423570d7dc0aa2bbd0159", size = 9209226, upload-time = "2026-06-12T02:29:07.033Z" }, + { url = "https://files.pythonhosted.org/packages/0f/c2/f5da6cd37ed6871f5c9b3c0507ddb69f14d6c36fac4541e4e0c60cb8cdfc/matplotlib-3.11.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:81ae77077a1e16d37a5b61096ccb07c8d90a99b518fa8256b8f21578932f2f62", size = 9434094, upload-time = "2026-06-12T02:29:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/f8/07/56f66906e0f87a0c6d0d0acbd34dbc9432b1931d8f26ef618bd6f92932a9/matplotlib-3.11.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ddef37840695f5eef65f9f070fe2d2f510f584c2156203f9f622a5b0584efffd", size = 9262183, upload-time = "2026-06-12T02:29:11.283Z" }, + { url = "https://files.pythonhosted.org/packages/0c/d8/c4ecab06b7ea36a570c4f3bd2d48d1799fd5d9174470e45c2194199431e7/matplotlib-3.11.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cf662e5ac5707658cb931e19972c4bd99f7b4f8b7bf79d3c821d239fa6b71e64", size = 10015653, upload-time = "2026-06-12T02:29:13.251Z" }, ] [[package]] @@ -1953,12 +1953,12 @@ wheels = [ ] [[package]] -name = "nest-asyncio" -version = "1.6.0" +name = "nest-asyncio2" +version = "1.7.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b4/73/731debf26e27e0a0323d7bda270dc2f634b398e38f040a09da1f4351d0aa/nest_asyncio2-1.7.2.tar.gz", hash = "sha256:1921d70b92cc4612c374928d081552efb59b83d91b2b789d935c665fa01729a8", size = 14743, upload-time = "2026-02-13T00:34:04.386Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, + { url = "https://files.pythonhosted.org/packages/c5/3c/3179b85b0e1c3659f0369940200cd6d0fa900e6cefcc7ea0bc6dd0e29ffb/nest_asyncio2-1.7.2-py3-none-any.whl", hash = "sha256:f5dfa702f3f81f6a03857e9a19e2ba578c0946a4ad417b4c50a24d7ba641fe01", size = 7843, upload-time = "2026-02-13T00:34:02.691Z" }, ] [[package]] @@ -2492,17 +2492,17 @@ wheels = [ [[package]] name = "protobuf" -version = "7.35.0" +version = "7.35.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/60/fd/5b1491d9e4b586d621c54f4c36b888714164b6875f8d6afa3f9072906a51/protobuf-7.35.0.tar.gz", hash = "sha256:a2efd84605f41e559f1881b0912b44099d0a2ac9bf46b3474823f10fb393b0e6", size = 458677, upload-time = "2026-05-19T23:02:29.197Z" } +sdist = { url = "https://files.pythonhosted.org/packages/da/01/9ef0afd7999eb9badb3a768b4aedd78c86d4c65cfaf1958ab276199e76b4/protobuf-7.35.1.tar.gz", hash = "sha256:ce115a26fe0c39a2c29973d914d327e516a6455464489fe3cd1e51a1b354f81a", size = 458717, upload-time = "2026-06-11T21:55:40.257Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/ee/93d06e358a4aa32280b00e722d3ea0a1f25fc3cc5778d80581c9cca2c10e/protobuf-7.35.0-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:66be6c513931c794fa92c080ffee41671390da3d79da219cf9c0c0907f035dda", size = 433225, upload-time = "2026-05-19T23:02:19.884Z" }, - { url = "https://files.pythonhosted.org/packages/8b/39/1c76c2da93f3c507e958e0aecee2391cc44d4625de6c728bbc555195b5a8/protobuf-7.35.0-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:fcbe42a4ac09d3ec9c987ddfcd956afd0b15f1ff613bd8371bde9405ffd5c8e5", size = 328847, upload-time = "2026-05-19T23:02:22.3Z" }, - { url = "https://files.pythonhosted.org/packages/91/1a/39f7ce90a238c1a987a4d81ec26379e02ca0aff367de68e4a1fa474215b9/protobuf-7.35.0-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:4cbf5cc286130e06a6c9bbefac442431173906dfcc979712183d4adcc01b37ee", size = 344030, upload-time = "2026-05-19T23:02:23.591Z" }, - { url = "https://files.pythonhosted.org/packages/70/5b/6baf9008817964454055ff3fe65f1de0b5f1e26c80c82f7fb108b7cd4ea3/protobuf-7.35.0-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:6c0f98f10c8a05ea30f8993dfef2de093d27b490fdae78bb60c8343795d55011", size = 327130, upload-time = "2026-05-19T23:02:24.637Z" }, - { url = "https://files.pythonhosted.org/packages/8e/e5/e46adb0badc388bfb84877a5f9f026aff63f60e611016cf64dbe77e05446/protobuf-7.35.0-cp310-abi3-win32.whl", hash = "sha256:4c4617b83ade0e279d1d2bfe04025a1adb87f9ed657de038620dc0ff959357f6", size = 428946, upload-time = "2026-05-19T23:02:25.741Z" }, - { url = "https://files.pythonhosted.org/packages/a7/ab/547fbd9e16d879dd13c167478f8ae0a83a428008ca07a5e06acdc23ad473/protobuf-7.35.0-cp310-abi3-win_amd64.whl", hash = "sha256:f05bcadf9a2a6b8dda047007075135fb7d08c73d9177aabc067e1be46881a201", size = 439996, upload-time = "2026-05-19T23:02:26.808Z" }, - { url = "https://files.pythonhosted.org/packages/b8/ef/50433d346c56657a70d27f156c7b349ac59a068b01de4eb796e747eecc43/protobuf-7.35.0-py3-none-any.whl", hash = "sha256:c13f325cf242bad135c350629eeb5d54b24228eb472fb3e2e9ebbd4c5dc20ca0", size = 171659, upload-time = "2026-05-19T23:02:27.842Z" }, + { url = "https://files.pythonhosted.org/packages/10/03/8aeeb7458d22546bf64b5250ca1daeb5ff757d900e8e4a7476c6f0db843e/protobuf-7.35.1-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:24f857477359a85c0c235261b8ba905fd51b2562f4a64ca1df5473f29850cbf6", size = 433226, upload-time = "2026-06-11T21:55:31.719Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/dfb89eb0e652a1ff073c39a59fb5e3a83cfe9b57a2c83fa6d78270101767/protobuf-7.35.1-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:11d6b0ec246892d85215b0a13ca6e0233cf5284b68f0ac02646427f4ff88a799", size = 328847, upload-time = "2026-06-11T21:55:34.035Z" }, + { url = "https://files.pythonhosted.org/packages/0f/58/dc12f2cd484951524af6e3382c785869b9b3fb5e52ee95ae23add53ee8f9/protobuf-7.35.1-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:b73f9489a4b8b1c9cb1f8ed951c736392592edb24b9d6819f36d2e10b171d5b4", size = 344030, upload-time = "2026-06-11T21:55:34.941Z" }, + { url = "https://files.pythonhosted.org/packages/e4/be/5b3cfe508bfab6761414ff944e3366eb13be4fd71efcd69450f89ba39f43/protobuf-7.35.1-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:74758715c53d7158fb76caf4f0cfdacc5329a4b1bb994f865d6cf302d413a1c4", size = 327130, upload-time = "2026-06-11T21:55:35.921Z" }, + { url = "https://files.pythonhosted.org/packages/d8/bc/6d6c7ba8709c85f8f2c390b2b118d6fb08a783676a572271851bf45a7d22/protobuf-7.35.1-cp310-abi3-win32.whl", hash = "sha256:353652e4efd0bca5b5fc2656abf8307ef351f0cf938c9eba09f0e09c20a25c30", size = 428945, upload-time = "2026-06-11T21:55:37.034Z" }, + { url = "https://files.pythonhosted.org/packages/0a/19/8d0cb6f20a1ef7b18f1c8986ad5783f22f84cce39c6ce9a6e645ea55192e/protobuf-7.35.1-cp310-abi3-win_amd64.whl", hash = "sha256:230a75ddfc2de4806e56696ce9640c1cdfdb6543b7cfce98d42a4c0a0e7bdb87", size = 439996, upload-time = "2026-06-11T21:55:38.123Z" }, + { url = "https://files.pythonhosted.org/packages/19/c7/5f7c636ec43e0c545e28d1f1db71990108306f7bdcb89f069ba97e428e7f/protobuf-7.35.1-py3-none-any.whl", hash = "sha256:4bc97768d8fe4ad6743c8a19403e314511ed9f6d13205b687e52421c023ac1b9", size = 171659, upload-time = "2026-06-11T21:55:39.155Z" }, ] [[package]] @@ -2609,7 +2609,7 @@ wheels = [ [[package]] name = "pytest" -version = "9.0.3" +version = "9.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -2618,9 +2618,9 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +sdist = { url = "https://files.pythonhosted.org/packages/84/0e/b5858858d74958632c49b72cb25a3976ff9f632397626715be71c89d3971/pytest-9.1.0.tar.gz", hash = "sha256:41dd9148c08072446394cefd3d79701701335a9f4cae69ba92e39f6c7f5c061c", size = 1634181, upload-time = "2026-06-13T18:52:45.983Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, + { url = "https://files.pythonhosted.org/packages/8b/5a/ba30a81239b909821b3153e303e7def45178bf353da4f72380e6c5e8793b/pytest-9.1.0-py3-none-any.whl", hash = "sha256:8ebb0e7888bdf2bdfc602ec51f8f62d50200af37356c74e503c79a94f5c81f32", size = 386453, upload-time = "2026-06-13T18:52:44.045Z" }, ] [[package]] @@ -2671,15 +2671,15 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.4.0" +version = "1.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a6/12/38c1a0b1e64806780c9563e3fc9f6e472251839662587cfbe9bfaf2ae10a/python_discovery-1.4.0.tar.gz", hash = "sha256:eb8bc7daad3c226c147e45bb4e970a1feb1bf4048ee178e6db59e197b8010ce3", size = 68455, upload-time = "2026-05-28T01:15:37.639Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/1a/cbbaf13b730abb0a16b964d984e19f2fe520c21a4dc664051359a3f5a9e7/python_discovery-1.4.2.tar.gz", hash = "sha256:8f3746c4b4968d22afbb97d36e1a0e5b66e6c0f297290f2e95f05b9b8bf18690", size = 70277, upload-time = "2026-06-11T16:10:42.383Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/8d/3d316429f65029532bb1e28ff77b797d86b5ac3915bb44ca4e19aa283d43/python_discovery-1.4.0-py3-none-any.whl", hash = "sha256:26ed78d703e234879a66244c7d4114563fb13ec5cd30a2d1357e5fb4850782da", size = 33217, upload-time = "2026-05-28T01:15:36.573Z" }, + { url = "https://files.pythonhosted.org/packages/1a/82/a70006589557f267f15bd384c0642ad49f0d97b690c3a05b166b9dcbad3b/python_discovery-1.4.2-py3-none-any.whl", hash = "sha256:475803f53b7b2ed6e490e27373f9d8340f7d2eebf9acdaf645d7d714c97bb500", size = 33886, upload-time = "2026-06-11T16:10:41.192Z" }, ] [[package]] @@ -2693,22 +2693,22 @@ wheels = [ [[package]] name = "pywinpty" -version = "3.0.3" +version = "3.0.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f7/54/37c7370ba91f579235049dc26cd2c5e657d2a943e01820844ffc81f32176/pywinpty-3.0.3.tar.gz", hash = "sha256:523441dc34d231fb361b4b00f8c99d3f16de02f5005fd544a0183112bcc22412", size = 31309, upload-time = "2026-02-04T21:51:09.524Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/ef/2d27f30c59a67be7025b2d7858c8c2d282b74d66544b2384730b82de74fd/pywinpty-3.0.5.tar.gz", hash = "sha256:61db0db063de9865adbea66db294628f8577f608d9764a4c7d3384eeacc4e81b", size = 16223484, upload-time = "2026-06-11T00:11:58.93Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/c3/3e75075c7f71735f22b66fab0481f2c98e3a4d58cba55cb50ba29114bcf6/pywinpty-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:dff25a9a6435f527d7c65608a7e62783fc12076e7d44487a4911ee91be5a8ac8", size = 2114430, upload-time = "2026-02-04T21:54:19.485Z" }, - { url = "https://files.pythonhosted.org/packages/8d/1e/8a54166a8c5e4f5cb516514bdf4090be4d51a71e8d9f6d98c0aa00fe45d4/pywinpty-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:fbc1e230e5b193eef4431cba3f39996a288f9958f9c9f092c8a961d930ee8f68", size = 236191, upload-time = "2026-02-04T21:50:36.239Z" }, - { url = "https://files.pythonhosted.org/packages/7c/d4/aeb5e1784d2c5bff6e189138a9ca91a090117459cea0c30378e1f2db3d54/pywinpty-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:c9081df0e49ffa86d15db4a6ba61530630e48707f987df42c9d3313537e81fc0", size = 2113098, upload-time = "2026-02-04T21:54:37.711Z" }, - { url = "https://files.pythonhosted.org/packages/b9/53/7278223c493ccfe4883239cf06c823c56460a8010e0fc778eef67858dc14/pywinpty-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:15e79d870e18b678fb8a5a6105fd38496b55697c66e6fc0378236026bc4d59e9", size = 234901, upload-time = "2026-02-04T21:53:31.35Z" }, - { url = "https://files.pythonhosted.org/packages/e5/cb/58d6ed3fd429c96a90ef01ac9a617af10a6d41469219c25e7dc162abbb71/pywinpty-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9c91dbb026050c77bdcef964e63a4f10f01a639113c4d3658332614544c467ab", size = 2112686, upload-time = "2026-02-04T21:52:03.035Z" }, - { url = "https://files.pythonhosted.org/packages/fd/50/724ed5c38c504d4e58a88a072776a1e880d970789deaeb2b9f7bd9a5141a/pywinpty-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:fe1f7911805127c94cf51f89ab14096c6f91ffdcacf993d2da6082b2142a2523", size = 234591, upload-time = "2026-02-04T21:52:29.821Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ad/90a110538696b12b39fd8758a06d70ded899308198ad2305ac68e361126e/pywinpty-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:3f07a6cf1c1d470d284e614733c3d0f726d2c85e78508ea10a403140c3c0c18a", size = 2112360, upload-time = "2026-02-04T21:55:33.397Z" }, - { url = "https://files.pythonhosted.org/packages/44/0f/7ffa221757a220402bc79fda44044c3f2cc57338d878ab7d622add6f4581/pywinpty-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:15c7c0b6f8e9d87aabbaff76468dabf6e6121332c40fc1d83548d02a9d6a3759", size = 233107, upload-time = "2026-02-04T21:51:45.455Z" }, - { url = "https://files.pythonhosted.org/packages/28/88/2ff917caff61e55f38bcdb27de06ee30597881b2cae44fbba7627be015c4/pywinpty-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:d4b6b7b0fe0cdcd02e956bd57cfe9f4e5a06514eecf3b5ae174da4f951b58be9", size = 2113282, upload-time = "2026-02-04T21:52:08.188Z" }, - { url = "https://files.pythonhosted.org/packages/63/32/40a775343ace542cc43ece3f1d1fce454021521ecac41c4c4573081c2336/pywinpty-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:34789d685fc0d547ce0c8a65e5a70e56f77d732fa6e03c8f74fefb8cbb252019", size = 234207, upload-time = "2026-02-04T21:51:58.687Z" }, - { url = "https://files.pythonhosted.org/packages/8d/54/5d5e52f4cb75028104ca6faf36c10f9692389b1986d34471663b4ebebd6d/pywinpty-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:0c37e224a47a971d1a6e08649a1714dac4f63c11920780977829ed5c8cadead1", size = 2112910, upload-time = "2026-02-04T21:52:30.976Z" }, - { url = "https://files.pythonhosted.org/packages/0a/44/dcd184824e21d4620b06c7db9fbb15c3ad0a0f1fa2e6de79969fb82647ec/pywinpty-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:c4e9c3dff7d86ba81937438d5819f19f385a39d8f592d4e8af67148ceb4f6ab5", size = 233425, upload-time = "2026-02-04T21:51:56.754Z" }, + { url = "https://files.pythonhosted.org/packages/b0/5c/31feb3dd82d1b33ae0bd09ca601edb993d9da1b7f0226b3336d4b4c39e1e/pywinpty-3.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:af7a8720c78776ddd6259b71dd567944f766a6cd67f8d2887fbc4973967bacda", size = 2092466, upload-time = "2026-06-10T23:44:24.453Z" }, + { url = "https://files.pythonhosted.org/packages/ee/fe/fe23e2229ffec0c10190cef5964f5c9b2dba179d23b69ae537b7ea90bcab/pywinpty-3.0.5-cp311-cp311-win_arm64.whl", hash = "sha256:c2406f54f699eab75953fb75ce805f2ae55a33a957cd070890abd454fb4b7680", size = 818395, upload-time = "2026-06-10T23:41:56.93Z" }, + { url = "https://files.pythonhosted.org/packages/45/34/942cc95ca4e26489875aa8a95192766247a687379ec29543eebe73ec945f/pywinpty-3.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:d62946adf14b15b54c0b8d785f93fe18b04da23f4ad59e2e8c4612646e9abd23", size = 2090915, upload-time = "2026-06-10T23:43:14.98Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/5b9053004844139ea8bd86209c57ade12b134b2782f383a095784c8531ec/pywinpty-3.0.5-cp312-cp312-win_arm64.whl", hash = "sha256:e9391c05fbfa7a992a97e831fc6849887b4014a614192e3d984a7ca59592b376", size = 815934, upload-time = "2026-06-10T23:41:42.384Z" }, + { url = "https://files.pythonhosted.org/packages/b9/f4/2a464b9893cceb3b3f416356e94fdc3e1bca9476993927e4e6d99fe95382/pywinpty-3.0.5-cp313-cp313-win_amd64.whl", hash = "sha256:48db1b0ad9d0a1b81dcaaa7163a99a7808deaceb0c1b2344716dc1fc090c3c4c", size = 2090471, upload-time = "2026-06-10T23:42:11.071Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2c/a138491a0afbdb50eb79395577bd326d4b0fbde7209417d1a8087ff2493a/pywinpty-3.0.5-cp313-cp313-win_arm64.whl", hash = "sha256:2c6008fb2d3774b48693b2fcb7f2cc317ade9dc581289a964ffeeaf81307c9b5", size = 815518, upload-time = "2026-06-10T23:42:02.363Z" }, + { url = "https://files.pythonhosted.org/packages/6f/15/54400049a380582acd1282665c70fcf11e0bd3713679aca78e24c3aae738/pywinpty-3.0.5-cp313-cp313t-win_amd64.whl", hash = "sha256:22ce1b780d89821cc52daf6eac0708af22d93d000ce9c7c07e37489db8594598", size = 2089920, upload-time = "2026-06-10T23:44:13.395Z" }, + { url = "https://files.pythonhosted.org/packages/94/0c/6f24f3c0799f502259b24bdf841a99ad2b0d59df5c2525b4e2a286d14be2/pywinpty-3.0.5-cp313-cp313t-win_arm64.whl", hash = "sha256:9c2919a81bc5cfb09b86fc5a002112b2de95ca4304a07413cbeeb746a1307a5c", size = 814520, upload-time = "2026-06-10T23:43:28.588Z" }, + { url = "https://files.pythonhosted.org/packages/e9/23/f3cd1b1e5fc56517f54452c49f92049e7dd9ffc8a63de22a495581f50d04/pywinpty-3.0.5-cp314-cp314-win_amd64.whl", hash = "sha256:03bb3c16d691d9242267201830bcd0e64a9b663170e9042bc84b210da9de15ac", size = 2090663, upload-time = "2026-06-10T23:43:59.845Z" }, + { url = "https://files.pythonhosted.org/packages/9d/dd/96d6cbfc6d9ddab5c1c2f92c26545ae8997446a2ba7ee2024cd43c81f49b/pywinpty-3.0.5-cp314-cp314-win_arm64.whl", hash = "sha256:89c5c6ef08997a3b4b277b214a35fe15cab4dd6d119f0140aa71df5b1168fdbc", size = 815700, upload-time = "2026-06-10T23:40:50.001Z" }, + { url = "https://files.pythonhosted.org/packages/30/36/d98087bce0acaa4cce7f196103cfa7be3f63ce65f52473bb3e38784ae5d9/pywinpty-3.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:7b566165e0c5fdd6abe167a5ac8b954be6a843eb55a85946576d6bc1dea03d6d", size = 2090093, upload-time = "2026-06-10T23:40:58.933Z" }, + { url = "https://files.pythonhosted.org/packages/57/fd/fe2b0db922ba052ce3976a08f3fc05d0c05047c8b4ebb6102e832b8ef563/pywinpty-3.0.5-cp314-cp314t-win_arm64.whl", hash = "sha256:24366280a8aa677323da87bec729cb3ea3b35367386cece0978bdc6e4695c690", size = 814517, upload-time = "2026-06-10T23:42:34.946Z" }, ] [[package]] @@ -3186,27 +3186,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.16" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/bd/5f7ec371001337d8fa61701c186ff8b613ecac1651848c5950f4c4d5f2e9/ruff-0.15.16.tar.gz", hash = "sha256:d05e78d38c78caf020b03789e25106c93017db5a0cb6e2819885018c61343b78", size = 4714267, upload-time = "2026-06-04T16:33:09.974Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/42/53ef1c3953f157956db9bf7861e3bc50b9b887ce93300aa48cdba8336fe6/ruff-0.15.16-py3-none-linux_armv6l.whl", hash = "sha256:6ac3c0b3969cc6cf6b158c4e2f8f682acb58e7d700d8a44b65ecdc72d66ab0b2", size = 10709025, upload-time = "2026-06-04T16:32:51.935Z" }, - { url = "https://files.pythonhosted.org/packages/93/9a/a79159346f19134a956607754e57d8d128f7a4c00f4ad2f7514d224c172c/ruff-0.15.16-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:197c207ed75ffba54a0dec23db4aa939a27a3053073e085e0042433cbdc58e4a", size = 11063550, upload-time = "2026-06-04T16:32:42.24Z" }, - { url = "https://files.pythonhosted.org/packages/bc/72/3ce2ac000a5299ec238e01f51397b3b653c93b077d9b1bfe8715bb895f20/ruff-0.15.16-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3a39fec45ab316cc23e7558f23fea4a70403ddb5648ea9a4a3854a16973d0071", size = 10421345, upload-time = "2026-06-04T16:32:37.251Z" }, - { url = "https://files.pythonhosted.org/packages/b0/c2/cc7fad3ec9169373f5b6a18f1917b91080feec40c3f9658334a1d28e2f03/ruff-0.15.16-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba93191d79003116b95128c9d306e045200fdbd0bccb782b110f3cd1d4abc5cf", size = 10757217, upload-time = "2026-06-04T16:32:54.722Z" }, - { url = "https://files.pythonhosted.org/packages/69/d2/3474009eaa0a65b31fa7152a2fad5e2f050c640ceb1e6b02ee6922e94c82/ruff-0.15.16-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c6ee4b90520630120ef032aa5cc10db483852dff950e78b1d717e2993a61ac8d", size = 10507035, upload-time = "2026-06-04T16:33:05.343Z" }, - { url = "https://files.pythonhosted.org/packages/ca/81/b7ae6ccbd11f0c8dc3d5d67fc4be9b57ff57ca86ba56152021378e1277f2/ruff-0.15.16-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e4215bc938bc3c8215c1472c1aa437e310fee20cd427335fec9d7e609563628", size = 11255291, upload-time = "2026-06-04T16:32:49.49Z" }, - { url = "https://files.pythonhosted.org/packages/d9/e1/46e526f1a7cc90857ce6ddf25fbb77eb6568651ac38d71b033af07076dd5/ruff-0.15.16-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c8d26be963b090f10e29abc8b3e74a2a321f6fa34e02424e30b5af89350ecbb", size = 12124922, upload-time = "2026-06-04T16:33:07.821Z" }, - { url = "https://files.pythonhosted.org/packages/1a/da/5c791b088b596b24d0deb967fa28ae02ad751a140c0b9ea81c5ab915d6c0/ruff-0.15.16-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f198cf4123602a2280ed46c307bcbafe41758d6fee5b456b6b6058ca1514b3b4", size = 11332186, upload-time = "2026-06-04T16:33:02.971Z" }, - { url = "https://files.pythonhosted.org/packages/72/11/5da87abe20047c8962361473923ebb2f62b595250126aadfad8c20649c1e/ruff-0.15.16-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb27515fa6240fb586ae82b901a59e67d24acff86f2190b433dc542fe0435aeb", size = 11373541, upload-time = "2026-06-04T16:32:47.007Z" }, - { url = "https://files.pythonhosted.org/packages/fe/2a/8554754c23a854ae3fd6b507e36ad61ddb121e298c6d5d617dec94ed0f14/ruff-0.15.16-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a267c46ba1593fc26b8eecbea050b39d40c0b6bb7781ee11c90a02cd10032951", size = 11353014, upload-time = "2026-06-04T16:32:34.795Z" }, - { url = "https://files.pythonhosted.org/packages/62/25/62ea41529ec89f742ea3fed9cb1059c72877ec7cf9b9e99ac9cf3294d1d9/ruff-0.15.16-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:528c68f39a91498a8d50e91ff5985df3d105782bab49cc378e73ac26bff083e8", size = 10737467, upload-time = "2026-06-04T16:32:26.348Z" }, - { url = "https://files.pythonhosted.org/packages/90/17/334d3ad9de4d40f9dd58fdd09e35ce64553bb501e2f19a839e2fb6be14fc/ruff-0.15.16-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7ed55c58950df60589a9a7a5d2f8fa5f54ebd287163be805adfe6ee95a9de123", size = 10521910, upload-time = "2026-06-04T16:32:32.54Z" }, - { url = "https://files.pythonhosted.org/packages/4d/bd/3ac7c6ae77a885c1004b3dda2446ea401768d24f851c14b4ad4b24f6639c/ruff-0.15.16-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d482feaf51512b50f9790ceb417a56a61dd1e9d9bf967662b9ed27c01b34f53a", size = 10979190, upload-time = "2026-06-04T16:32:57.492Z" }, - { url = "https://files.pythonhosted.org/packages/33/d7/609546e6a413c3f216fbf2a50c928f97c80939154f6a0503114094a86191/ruff-0.15.16-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1e15bc8c94513dae2a40cc9ef07c94fdd4ecc9e29dabebeebe170f952322c9e3", size = 11477014, upload-time = "2026-06-04T16:32:44.687Z" }, - { url = "https://files.pythonhosted.org/packages/74/0d/f2cd247ad32633a5c36e97141a2c21b11c6279f7957bc2ff360b1e08fddd/ruff-0.15.16-py3-none-win32.whl", hash = "sha256:580378f7bd4aa25f72e74aa54948a9622f142b1e509521dd10902e886681cc1e", size = 10735541, upload-time = "2026-06-04T16:32:30.145Z" }, - { url = "https://files.pythonhosted.org/packages/8b/9e/02e845ef151b1dee585e55c4739f8e1734ae1d9f1221dff65761c162208b/ruff-0.15.16-py3-none-win_amd64.whl", hash = "sha256:408256017284eddf98fff77b29aa4fb30f586042d535b2d9befc6512f400aaec", size = 11843403, upload-time = "2026-06-04T16:32:39.76Z" }, - { url = "https://files.pythonhosted.org/packages/15/19/016553f86f207450aebebc2b2b5088d086b901cc8186c02ac4284db3bd88/ruff-0.15.16-py3-none-win_arm64.whl", hash = "sha256:8cd61783afb39638a7133ef0d2dfb1e91277593962f81b5a8423eb0b888a6121", size = 11134555, upload-time = "2026-06-04T16:33:00.136Z" }, +version = "0.15.17" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/a9/3abdf488f1bf3d24c699415e454ed554a6350d5d89ce183be1ee0a3361ac/ruff-0.15.17.tar.gz", hash = "sha256:2ec446937fd16c8c4de2674a209cc5af64d9c6f17d21fbf1151054fa0bcf5219", size = 4743346, upload-time = "2026-06-11T17:54:47.663Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/4d/e11259f5da07cb6afb2d074c31bf09da9671993f7329d4f15d2fdc458301/ruff-0.15.17-py3-none-linux_armv6l.whl", hash = "sha256:d9feddb927fc68bd295f5eebc587a7e42cfaf9b65f60ca4a2386febff575da8f", size = 10856677, upload-time = "2026-06-11T17:54:49.533Z" }, + { url = "https://files.pythonhosted.org/packages/29/3e/772d679e1a0dc058e58875bd2c0cb713a0530877b4a76fee3c7966df0d49/ruff-0.15.17-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:25805a226d741c47d274a35ad5c10a7dde175fcddfa511d7cf3da0a21eb3eab7", size = 11223443, upload-time = "2026-06-11T17:55:00.573Z" }, + { url = "https://files.pythonhosted.org/packages/68/58/bd41f7688b2fd5623012605130ed70e60aa7f2244baa3d5066bdd61530c8/ruff-0.15.17-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f6ad73b14c2d18a3bf8ad7cb6974294d7f613a7898604826058e6ac64918ef4d", size = 10566458, upload-time = "2026-06-11T17:55:07.52Z" }, + { url = "https://files.pythonhosted.org/packages/d8/5b/733371013fcf1ec339e477ece6ab42bfe10bdd9bba8ee88a9516aa56bfc0/ruff-0.15.17-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ba0c1e4f95bcb3869d0d30cbd5917071ef2e28665abfec970cdab0492c713ed", size = 10914483, upload-time = "2026-06-11T17:55:05.501Z" }, + { url = "https://files.pythonhosted.org/packages/bd/cc/6f24251cc0252f7239391ccb85833f320efad14ebe5b443943f37ced6332/ruff-0.15.17-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:81647960f10bff57d2e51cadd0c3950fe598400c852863a038720ef5b8cca91e", size = 10647497, upload-time = "2026-06-11T17:54:57.733Z" }, + { url = "https://files.pythonhosted.org/packages/68/dd/0d10c17ce1a1624d6fc3156309c3f834fdb5dfaad026ec90c85684f3990e/ruff-0.15.17-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e01a84ddbc8c16c23055ba3924476850f1bbc1917cebbb9376665a63e74260d", size = 11416967, upload-time = "2026-06-11T17:54:51.461Z" }, + { url = "https://files.pythonhosted.org/packages/2f/91/556bfb156f6144f355e831c23db00b2fc4120f86b3ce81cc5f7fd2df51f3/ruff-0.15.17-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fe9f653152f8f294f9f7e03bf3a453d8b4a27f7a59c78c8666167f2b17b96c", size = 12335770, upload-time = "2026-06-11T17:54:45.793Z" }, + { url = "https://files.pythonhosted.org/packages/88/82/8b5999aa13355e926f06d9f42a32dcca862f623bf0363785ff89d607dffd/ruff-0.15.17-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c0fe88a7676e7a05b73174d4d4a59cb2ac21ff8263583f87a81a6018475a978", size = 11575441, upload-time = "2026-06-11T17:54:32.661Z" }, + { url = "https://files.pythonhosted.org/packages/11/93/f10377bb04109ca0e8cbc483ff1982c54b6d418210041776f93e8cdc7fa9/ruff-0.15.17-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecfc3c7878fff94633ab0348524e093f9ce3243080416dd7d14f8ba400174719", size = 11557614, upload-time = "2026-06-11T17:54:34.698Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a6/eeeae7f7d5493df41649ab3db92f086b2d0a30199e4efdf8e3dd7a033f24/ruff-0.15.17-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:b8461180b22420b1bdc289909410930761629fddf2a5aaf60fae1ab26cedc4c4", size = 11544450, upload-time = "2026-06-11T17:54:39.042Z" }, + { url = "https://files.pythonhosted.org/packages/32/88/5991ce565129a24dd4a00db1254b3b5db2e53018cbe4018ea5a89738e727/ruff-0.15.17-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6eccbe50a038b503e7140b441aa9c7fc8c1f36edf23ebef9f4165c2f28f568b7", size = 10892524, upload-time = "2026-06-11T17:55:09.432Z" }, + { url = "https://files.pythonhosted.org/packages/f5/1d/0fdd248313425f55223968af04b0a42125466a8d88d21c1d99c6af0a51e8/ruff-0.15.17-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:382fc0521025f5a8ad447d8bdd523545d0d7646adb718eb1c2dac5065ec27c0f", size = 10659573, upload-time = "2026-06-11T17:54:36.824Z" }, + { url = "https://files.pythonhosted.org/packages/9e/0e/072e8260deb9461062ce9311ced27a8e541229a6ffd483013dd37661e43e/ruff-0.15.17-py3-none-musllinux_1_2_i686.whl", hash = "sha256:456d41fcd1b2777ad63f09a6e7121d43f7b688bbc76a800c10f7f8fb1f912c3f", size = 11127818, upload-time = "2026-06-11T17:55:03.124Z" }, + { url = "https://files.pythonhosted.org/packages/ab/b4/55060a34163121498014696b5f656db5b8c6963768f227dbf0d76b311073/ruff-0.15.17-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b1a04bcc94ae6194e9db05d16ad31f298a7194bfbcb08258bbe589cee1d587b8", size = 11655901, upload-time = "2026-06-11T17:54:53.562Z" }, + { url = "https://files.pythonhosted.org/packages/49/71/9b29d6b87cef468d697f43c6a91e3fae4a80185779d7d5a4ef27d173439f/ruff-0.15.17-py3-none-win32.whl", hash = "sha256:596065960ab1ff593f744220c9fe6580eda00a95003cffa9f4048bb5b1bf0392", size = 10925574, upload-time = "2026-06-11T17:54:55.723Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b2/8fc77f3723228836fa5d12497eb71c808f83782e10d058d2b15cfa14640b/ruff-0.15.17-py3-none-win_amd64.whl", hash = "sha256:6769e5fa1710b179b92e0bfa5a51735b35baea9013dadb06d5f44cbcf9547084", size = 12058788, upload-time = "2026-06-11T17:54:41.042Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c7/c53e8dbff9c9dc4b7928773421ae294a5d28fcb8dcda1a089579d3a7e510/ruff-0.15.17-py3-none-win_arm64.whl", hash = "sha256:f3be1fbb34bcdfd146240d8fb92a709d4c2c8191348580a3c044ec60fa0b4456", size = 11355275, upload-time = "2026-06-11T17:54:43.635Z" }, ] [[package]] @@ -3717,31 +3717,31 @@ wheels = [ [[package]] name = "tornado" -version = "6.5.6" +version = "6.5.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/50/57/6d7303a77ae439d9189108f76c0c4fd89ee5e2cc8387bffb55232565c4ed/tornado-6.5.6.tar.gz", hash = "sha256:9a365179fe8ff6b8766f602c0f67c185d778193e9bdd828b19f0b6ed7764177d", size = 518139, upload-time = "2026-05-27T15:35:54.646Z" } +sdist = { url = "https://files.pythonhosted.org/packages/64/24/95ec527ad67b76d59299e5465b3935d05e4294b7e0290a3924b7487df30b/tornado-6.5.7.tar.gz", hash = "sha256:66c513a76cda70d53907bc27cf1447557699c2e95aa48ba27a442ff61c3ddfc2", size = 519252, upload-time = "2026-06-08T17:34:51.232Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/0d/b4f481e18c5a51864e6d12b9a05ecf72919696680b747c958c3fc1f4fbae/tornado-6.5.6-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:65fcfaafb079435c2c19dc9e07c0f1cf0fa9051759ed0a7d0a3ba7ea7f64919c", size = 447737, upload-time = "2026-05-27T15:35:38.122Z" }, - { url = "https://files.pythonhosted.org/packages/9e/9c/5430c39fcab1144d35860f457b15e9c08b4bc7ac86764354204e983d6183/tornado-6.5.6-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:38bc01b4acacded2de63ae78023548e41ebe6fbed3ec05a796d7ae3ad893887e", size = 445899, upload-time = "2026-05-27T15:35:40.519Z" }, - { url = "https://files.pythonhosted.org/packages/8b/79/fa7e14a2f939c807a8d30619b4eb604eab219601b78792516ebe22d40cf9/tornado-6.5.6-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b942e6a137fda31ff54bf8e6e2c8d1c37f1f50583f3ed53fb840b53b9601d104", size = 448964, upload-time = "2026-05-27T15:35:42.106Z" }, - { url = "https://files.pythonhosted.org/packages/a7/71/bd67d5f5199f937dafe03a49a37989f60f600ff6fef34c79412a829d97bd/tornado-6.5.6-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8666946e70171b8c3f1fc9b7876fac492e84822c4c7f3746f4e8f8bc9ac92a79", size = 449935, upload-time = "2026-05-27T15:35:43.906Z" }, - { url = "https://files.pythonhosted.org/packages/cc/a4/c24388c9cf5b3c3a513b56a158af9f23092c9a2810d789e294310797df21/tornado-6.5.6-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1c34cfab7ad6d104f052f55de06d39bbafc5885cfeb4da688803308dbcfa90b7", size = 449767, upload-time = "2026-05-27T15:35:45.793Z" }, - { url = "https://files.pythonhosted.org/packages/a5/eb/6a07ad550c3f7b37244bd0becdf293ec3d3e961783d8b720a97df50de1b2/tornado-6.5.6-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:385f35e4e22fb52551dfcda4cdc8c30c61c2c001aef5ddad99cdfe116952efd3", size = 449174, upload-time = "2026-05-27T15:35:47.485Z" }, - { url = "https://files.pythonhosted.org/packages/bb/84/3469e098dccdb6763130e06aacd786bb4363fca7b590a55c101ddf34ed30/tornado-6.5.6-cp39-abi3-win32.whl", hash = "sha256:db475f1b67b2809b10bb16264829087724ca8d24fe4ed47f7b8675cae453ef86", size = 450230, upload-time = "2026-05-27T15:35:49.322Z" }, - { url = "https://files.pythonhosted.org/packages/d2/3c/273a04e0b9dd9016f1685cca0c1c8795a71ac88a34a8c889a0b443483226/tornado-6.5.6-cp39-abi3-win_amd64.whl", hash = "sha256:6739bf1e8eb09230f1280ddbd3236f0309db70f2c551a8dbc40f62babdf82f79", size = 450667, upload-time = "2026-05-27T15:35:51.194Z" }, - { url = "https://files.pythonhosted.org/packages/02/98/0cffe22a224f60c5fb1e3aa0b76f9da2e1ca78b0e9545e3d077c68ce60a7/tornado-6.5.6-cp39-abi3-win_arm64.whl", hash = "sha256:2543597b24a695d72338a9a77818362d72387c03ae173f1f169eadc5c91466ac", size = 449690, upload-time = "2026-05-27T15:35:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/02/dc/c7043cab6fed8ae159fc1923ce829ada35c4dbd797d408a43858ffaf9639/tornado-6.5.7-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:148b2eb15c2c765a50796172c1e499649b35f30d2e3c3d3e15913cfa56bfb163", size = 448543, upload-time = "2026-06-08T17:34:38.052Z" }, + { url = "https://files.pythonhosted.org/packages/92/4f/090b1431e5a43df696feceffc268c5383cc079ecb5f08ce58f917109aafe/tornado-6.5.7-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9da38de27f1da3b78a966f0dae12b5a1ea9afe72ca805d84ff06508272ddf100", size = 446707, upload-time = "2026-06-08T17:34:39.594Z" }, + { url = "https://files.pythonhosted.org/packages/37/d8/ef374952fd5da67d4463122c2b8e5a96536ec10b4b339254c6dcde81d01c/tornado-6.5.7-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8d759e71906ee783f8867b93bf26a265743da4c1e2f4a018464c1ba019862972", size = 449774, upload-time = "2026-06-08T17:34:41.204Z" }, + { url = "https://files.pythonhosted.org/packages/35/37/d434c73f4c6e014b745b9b37085f34f40c022f007efff3d7fe65991899f3/tornado-6.5.7-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a46347a18f23fb92b396beebe0fb78f61dda0cc302445202c16203d8a18848b", size = 450745, upload-time = "2026-06-08T17:34:42.531Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2b/56b9aff361d7f1ab728a805ec7d7ea835f8807afa9f5cc690ea0e630efb9/tornado-6.5.7-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7778b30bef919231265e91c69963ce0f49a1e9c07ac900bbe75b19ce2575ba92", size = 450578, upload-time = "2026-06-08T17:34:43.787Z" }, + { url = "https://files.pythonhosted.org/packages/02/30/a7444fb23aa76860a14198fab96ac79f1866b0a6e19e26c4381b0938e50f/tornado-6.5.7-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e726f0c75da7726eec023aa62751ff8878bd2737e34fbdd33b1ae5897d2200f5", size = 449985, upload-time = "2026-06-08T17:34:45.326Z" }, + { url = "https://files.pythonhosted.org/packages/5c/42/5f0e56c01e8d9d36f4e23f367b85ae6cae0c1ecddd5e6977d8388ad27488/tornado-6.5.7-cp39-abi3-win32.whl", hash = "sha256:f8de3bf12d3efdd0cbe7c8887868198f8a91415e3f29fcf258d9b8eb7b1d9ae4", size = 451047, upload-time = "2026-06-08T17:34:46.784Z" }, + { url = "https://files.pythonhosted.org/packages/c9/a4/b393076ffb21b469eec5b328a0534cf03a3b90bfc6b1f09507cdd075d938/tornado-6.5.7-cp39-abi3-win_amd64.whl", hash = "sha256:de942f843533a039ef9fa3d9c88c7cd8a7c94553fb5ad0154270989b3d99a2c4", size = 451485, upload-time = "2026-06-08T17:34:48.248Z" }, + { url = "https://files.pythonhosted.org/packages/71/2e/7b1c769803121b809112cf9a00681c472eae1d80e32d7ec0e0bd61d0d0e1/tornado-6.5.7-cp39-abi3-win_arm64.whl", hash = "sha256:ff934fce95643af5f11efdae618eaa73d469dc588641e5c8d19295a0c65c4796", size = 450506, upload-time = "2026-06-08T17:34:49.702Z" }, ] [[package]] name = "tqdm" -version = "4.68.1" +version = "4.68.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/b3/36c8ecf72e8925200671613332db156d84b99b3aee742a41c1938ebb0808/tqdm-4.68.1.tar.gz", hash = "sha256:fc163d96b287bd031e1aa24421ce4411b25559bd0a1be4fe649bdaa4d2c02bf5", size = 171236, upload-time = "2026-06-05T17:23:15.267Z" } +sdist = { url = "https://files.pythonhosted.org/packages/85/05/0d5260f1f1ca784f4a4a0def9cbe6affe587f5b4025328d446c3d67765f4/tqdm-4.68.2.tar.gz", hash = "sha256:89c230e8dbc67c7615c142487111222f878c77427ea09549960f62389e258add", size = 171923, upload-time = "2026-06-09T13:26:42.539Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/aa/218a0eb34de1f753c83e4d0d1c8e7c4cef27f20dcb8342e024f63a80dc86/tqdm-4.68.1-py3-none-any.whl", hash = "sha256:fea4a90e4023f764914569f7802a297277c5ab1a66be5144143e142e1a4031d8", size = 78354, upload-time = "2026-06-05T17:23:13.654Z" }, + { url = "https://files.pythonhosted.org/packages/eb/75/1a0392bcc21c44dcdf87b3cf2d137e7829be2c083a1e38d44efca3d57a16/tqdm-4.68.2-py3-none-any.whl", hash = "sha256:d4240441fb5353290b87d6a85968c9decc131a99b8c7faa28269d829de669ede", size = 78578, upload-time = "2026-06-09T13:26:40.731Z" }, ] [[package]] @@ -3810,7 +3810,7 @@ wheels = [ [[package]] name = "virtualenv" -version = "21.4.2" +version = "21.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -3818,9 +3818,9 @@ dependencies = [ { name = "platformdirs" }, { name = "python-discovery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e1/0d/4e93c8e6d1001a75763f87d8f5ecda8ebc7f4aa2153dddfaf4ae8892821a/virtualenv-21.4.2.tar.gz", hash = "sha256:38e6ee0a555615c0ea9da2ac7e9998fe8dc3b911dd33ad8eaad2020957653b0c", size = 7613326, upload-time = "2026-05-31T17:01:22.827Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/0e/933bacb37b57ae7928b0030eef205a3dbb3e37afdbdde5be2e113318958f/virtualenv-21.5.0.tar.gz", hash = "sha256:98847aadf5e2037e0e4d2e19528eb3aca6f23906422e59a510bff231a6d32fce", size = 4577424, upload-time = "2026-06-13T20:36:45.066Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/c4/557dc082be035381b85fdb2b74e21d3d21b57750b74f2b47a32f3a639ff9/virtualenv-21.4.2-py3-none-any.whl", hash = "sha256:854210ca524a1a4d0d744734f4acbc721c3ffe163b85bbf5d56d14d5ae2f0fae", size = 7594079, upload-time = "2026-05-31T17:01:20.735Z" }, + { url = "https://files.pythonhosted.org/packages/e9/87/b0667ede418386ab631e48924b845d326f366d61e6bd08fe68a748fae4d4/virtualenv-21.5.0-py3-none-any.whl", hash = "sha256:8f7c38605023688c89789f566959006af6d61c99eeeb9e58342eb780c5761e5e", size = 4557937, upload-time = "2026-06-13T20:36:42.967Z" }, ] [[package]] From 03b0e001d148e12c8305a4f78b1c31106f95532c Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Thu, 18 Jun 2026 15:15:59 -0700 Subject: [PATCH 327/335] Normalizing by the quantile now an option in Tomo Dataset --- src/quantem/tomography/dataset_models.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index 2255636c..f9d87d06 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -169,6 +169,7 @@ def __init__( tilt_angles: NDArray | torch.Tensor, learn_shift: bool = True, learn_tilt_axis: bool = True, + norm_quantile: bool = True, _token: object | None = None, ): AutoSerialize.__init__(self) @@ -188,7 +189,10 @@ def __init__( tilt_stack = torch.from_numpy(tilt_stack) if type(tilt_angles) is not torch.Tensor: tilt_angles = torch.from_numpy(tilt_angles) - max_val = torch.quantile(tilt_stack, 0.95) + if norm_quantile: + max_val = torch.quantile(tilt_stack, 0.95) + else: + max_val = torch.max(tilt_stack) # Tilt stack normalization tilt_stack = tilt_stack / max_val @@ -221,12 +225,14 @@ def from_data( tilt_angles: NDArray | torch.Tensor, learn_shift: bool = True, learn_tilt_axis: bool = True, + norm_quantile: bool = True, ): return cls( tilt_stack=tilt_stack, tilt_angles=tilt_angles, learn_shift=learn_shift, learn_tilt_axis=learn_tilt_axis, + norm_quantile=norm_quantile, _token=cls._token, ) From 3f92787ad6fa4e85ba7fbe2908299926683f00b2 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Thu, 18 Jun 2026 15:31:20 -0700 Subject: [PATCH 328/335] Fixed tomography tests --- src/quantem/tomography/dataset_models.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index f9d87d06..aa11f0a1 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -403,6 +403,7 @@ def __init__( tilt_angles: NDArray | torch.Tensor, learn_shift: bool = True, learn_tilt_axis: bool = True, + norm_quantile: bool = True, _token: object | None = None, ): super().__init__( @@ -410,6 +411,7 @@ def __init__( tilt_angles=-tilt_angles, # TODO: Flip the tilt angles to be negative to match the convention of INR. learn_shift=learn_shift, learn_tilt_axis=learn_tilt_axis, + norm_quantile=norm_quantile, _token=_token, ) @@ -462,10 +464,18 @@ def __init__( tilt_angles: NDArray | torch.Tensor, learn_shift: bool = True, learn_tilt_axis: bool = True, + norm_quantile: bool = True, seed: int = 42, _token: object | None = None, ): - super().__init__(tilt_stack, tilt_angles, learn_shift, learn_tilt_axis, _token=_token) + super().__init__( + tilt_stack, + tilt_angles, + learn_shift, + learn_tilt_axis, + norm_quantile, + _token=_token, + ) # --- Forward Pass w/ Params Method for OptimizerMixin --- def forward(self, dummy_input: Any = None): From 8af2492b4e7b1f51441afb6ed6f8bda569ee9e52 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Thu, 18 Jun 2026 16:56:40 -0700 Subject: [PATCH 329/335] Last shape of the tilt stack is now checked with the tilt angles rather than comparing the shapes within the tilt series stack. --- src/quantem/tomography/dataset_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index aa11f0a1..6ede23be 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -179,7 +179,7 @@ def __init__( raise RuntimeError("Use TomographyPixDataset.from_* to instantiate this class.") if not ( - tilt_stack.shape[0] < tilt_stack.shape[1] or tilt_stack.shape[0] < tilt_stack.shape[2] + tilt_stack.shape[0] == tilt_angles.shape[0] ): raise ValueError( "The number of tilt projections should be in the first dimension of the dataset." From 3c29c6e33faa1291cdd8687cff39caa06bd759dc Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Thu, 18 Jun 2026 17:04:59 -0700 Subject: [PATCH 330/335] Test fixes --- .serena/.gitignore | 2 + .serena/project.yml | 133 ++++++++++++++++++++++++ tests/tomography/test_dataset_models.py | 7 +- 3 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 .serena/.gitignore create mode 100644 .serena/project.yml diff --git a/.serena/.gitignore b/.serena/.gitignore new file mode 100644 index 00000000..2e510aff --- /dev/null +++ b/.serena/.gitignore @@ -0,0 +1,2 @@ +/cache +/project.local.yml diff --git a/.serena/project.yml b/.serena/project.yml new file mode 100644 index 00000000..e660e68a --- /dev/null +++ b/.serena/project.yml @@ -0,0 +1,133 @@ +# the name by which the project can be referenced within Serena +project_name: "quantem" + + +# list of languages for which language servers are started; choose from: +# al angular ansible bash clojure +# cpp cpp_ccls crystal csharp csharp_omnisharp +# dart elixir elm erlang fortran +# fsharp go groovy haskell haxe +# hlsl html java json julia +# kotlin lean4 lua luau markdown +# matlab msl nix ocaml pascal +# perl php php_phpactor powershell python +# python_jedi python_ty r rego ruby +# ruby_solargraph rust scala scss solidity +# svelte swift systemverilog terraform toml +# typescript typescript_vts vue yaml zig +# (This list may be outdated. For the current list, see values of Language enum here: +# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py +# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.) +# Note: +# - For C, use cpp +# - For JavaScript, use typescript +# - For Angular projects, use angular (subsumes typescript+html; requires `npm install` in the project root) +# - For Svelte projects, use svelte (subsumes typescript/javascript for .svelte projects; requires npm) +# - For SCSS / Sass / plain CSS, use scss (some-sass-language-server handles all three) +# - For Free Pascal/Lazarus, use pascal +# Special requirements: +# Some languages require additional setup/installations. +# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers +# When using multiple languages, the first language server that supports a given file will be used for that file. +# The first language is the default language and the respective language server will be used as a fallback. +# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored. +languages: +- python + +# the encoding used by text files in the project +# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings +encoding: "utf-8" + +# line ending convention to use when writing source files. +# Possible values: unset (use global setting), "lf", "crlf", or "native" (platform default) +# This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings. +line_ending: + +# The language backend to use for this project. +# If not set, the global setting from serena_config.yml is used. +# Valid values: LSP, JetBrains +# Note: the backend is fixed at startup. If a project with a different backend +# is activated post-init, an error will be returned. +language_backend: + +# whether to use project's .gitignore files to ignore files +ignore_all_files_in_gitignore: true + +# advanced configuration option allowing to configure language server-specific options. +# Maps the language key to the options. +# Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available. +# No documentation on options means no options are available. +ls_specific_settings: {} + +# list of additional workspace folder paths for cross-package reference support (e.g. in monorepos). +# Paths can be absolute or relative to the project root. +# Each folder is registered as an LSP workspace folder, enabling language servers to discover +# symbols and references across package boundaries. +# Currently supported for: TypeScript. +# Example: +# additional_workspace_folders: +# - ../sibling-package +# - ../shared-lib +additional_workspace_folders: [] + +# list of additional paths to ignore in this project. +# Same syntax as gitignore, so you can use * and **. +# Note: global ignored_paths from serena_config.yml are also applied additively. +ignored_paths: [] + +# whether the project is in read-only mode +# If set to true, all editing tools will be disabled and attempts to use them will result in an error +# Added on 2025-04-18 +read_only: false + +# list of tool names to exclude. +# This extends the existing exclusions (e.g. from the global configuration) +# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html +excluded_tools: [] + +# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default). +# This extends the existing inclusions (e.g. from the global configuration). +# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html +included_optional_tools: [] + +# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools. +# This cannot be combined with non-empty excluded_tools or included_optional_tools. +# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html +fixed_tools: [] + +# list of mode names that are to be activated by default, overriding the setting in the global configuration. +# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes. +# If the setting is undefined/empty, the default_modes from the global configuration (serena_config.yml) apply. +# Otherwise, this overrides the setting from the global configuration (serena_config.yml). +# Therefore, you can set this to [] if you do not want the default modes defined in the global config to apply +# for this project. +# This setting can, in turn, be overridden by CLI parameters (--mode). +# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes +default_modes: + +# list of mode names to be activated additionally for this project, e.g. ["query-projects"] +# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes. +# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes +added_modes: + +# initial prompt for the project. It will always be given to the LLM upon activating the project +# (contrary to the memories, which are loaded on demand). +initial_prompt: "" + +# time budget (seconds) per tool call for the retrieval of additional symbol information +# such as docstrings or parameter information. +# This overrides the corresponding setting in the global configuration; see the documentation there. +# If null or missing, use the setting from the global configuration. +symbol_info_budget: + +# list of regex patterns which, when matched, mark a memory entry as read‑only. +# Extends the list from the global configuration, merging the two lists. +read_only_memory_patterns: [] + +# list of regex patterns for memories to completely ignore. +# Matching memories will not appear in list_memories or activate_project output +# and cannot be accessed via read_memory or write_memory. +# To access ignored memory files, use the read_file tool on the raw file path. +# Extends the list from the global configuration, merging the two lists. +# Example: ["_archive/.*", "_episodes/.*"] +ignored_memory_patterns: [] diff --git a/tests/tomography/test_dataset_models.py b/tests/tomography/test_dataset_models.py index 04abd3b5..f0b6aa2d 100644 --- a/tests/tomography/test_dataset_models.py +++ b/tests/tomography/test_dataset_models.py @@ -43,10 +43,11 @@ def _stack(nang=5, n=12, seed=0): class TestTomographyPixDataset: def test_wrong_projection_axis_raises(self): - # projections must live on axis 0 (i.e. fewer than the image dims). - bad = np.zeros((20, 5, 5), dtype=np.float32) + # projections must live on axis 0, matching the number of tilt angles. + # here the projections are on the last axis, so axis 0 (12) != n_angles (5). + bad = np.zeros((12, 12, 5), dtype=np.float32) with pytest.raises(ValueError): - TomographyPixDataset.from_data(bad, np.linspace(-60, 60, 20).astype(np.float32)) + TomographyPixDataset.from_data(bad, np.linspace(-60, 60, 5).astype(np.float32)) def test_tilt_angles_are_negated(self): angles = np.linspace(-40, 60, 5).astype(np.float32) From e00aeda112c250ad4c39f601433e98268c0465e8 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Thu, 18 Jun 2026 17:05:31 -0700 Subject: [PATCH 331/335] Test fixes --- .serena/.gitignore | 2 - .serena/project.yml | 133 -------------------------------------------- 2 files changed, 135 deletions(-) delete mode 100644 .serena/.gitignore delete mode 100644 .serena/project.yml diff --git a/.serena/.gitignore b/.serena/.gitignore deleted file mode 100644 index 2e510aff..00000000 --- a/.serena/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/cache -/project.local.yml diff --git a/.serena/project.yml b/.serena/project.yml deleted file mode 100644 index e660e68a..00000000 --- a/.serena/project.yml +++ /dev/null @@ -1,133 +0,0 @@ -# the name by which the project can be referenced within Serena -project_name: "quantem" - - -# list of languages for which language servers are started; choose from: -# al angular ansible bash clojure -# cpp cpp_ccls crystal csharp csharp_omnisharp -# dart elixir elm erlang fortran -# fsharp go groovy haskell haxe -# hlsl html java json julia -# kotlin lean4 lua luau markdown -# matlab msl nix ocaml pascal -# perl php php_phpactor powershell python -# python_jedi python_ty r rego ruby -# ruby_solargraph rust scala scss solidity -# svelte swift systemverilog terraform toml -# typescript typescript_vts vue yaml zig -# (This list may be outdated. For the current list, see values of Language enum here: -# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py -# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.) -# Note: -# - For C, use cpp -# - For JavaScript, use typescript -# - For Angular projects, use angular (subsumes typescript+html; requires `npm install` in the project root) -# - For Svelte projects, use svelte (subsumes typescript/javascript for .svelte projects; requires npm) -# - For SCSS / Sass / plain CSS, use scss (some-sass-language-server handles all three) -# - For Free Pascal/Lazarus, use pascal -# Special requirements: -# Some languages require additional setup/installations. -# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers -# When using multiple languages, the first language server that supports a given file will be used for that file. -# The first language is the default language and the respective language server will be used as a fallback. -# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored. -languages: -- python - -# the encoding used by text files in the project -# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings -encoding: "utf-8" - -# line ending convention to use when writing source files. -# Possible values: unset (use global setting), "lf", "crlf", or "native" (platform default) -# This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings. -line_ending: - -# The language backend to use for this project. -# If not set, the global setting from serena_config.yml is used. -# Valid values: LSP, JetBrains -# Note: the backend is fixed at startup. If a project with a different backend -# is activated post-init, an error will be returned. -language_backend: - -# whether to use project's .gitignore files to ignore files -ignore_all_files_in_gitignore: true - -# advanced configuration option allowing to configure language server-specific options. -# Maps the language key to the options. -# Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available. -# No documentation on options means no options are available. -ls_specific_settings: {} - -# list of additional workspace folder paths for cross-package reference support (e.g. in monorepos). -# Paths can be absolute or relative to the project root. -# Each folder is registered as an LSP workspace folder, enabling language servers to discover -# symbols and references across package boundaries. -# Currently supported for: TypeScript. -# Example: -# additional_workspace_folders: -# - ../sibling-package -# - ../shared-lib -additional_workspace_folders: [] - -# list of additional paths to ignore in this project. -# Same syntax as gitignore, so you can use * and **. -# Note: global ignored_paths from serena_config.yml are also applied additively. -ignored_paths: [] - -# whether the project is in read-only mode -# If set to true, all editing tools will be disabled and attempts to use them will result in an error -# Added on 2025-04-18 -read_only: false - -# list of tool names to exclude. -# This extends the existing exclusions (e.g. from the global configuration) -# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html -excluded_tools: [] - -# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default). -# This extends the existing inclusions (e.g. from the global configuration). -# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html -included_optional_tools: [] - -# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools. -# This cannot be combined with non-empty excluded_tools or included_optional_tools. -# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html -fixed_tools: [] - -# list of mode names that are to be activated by default, overriding the setting in the global configuration. -# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes. -# If the setting is undefined/empty, the default_modes from the global configuration (serena_config.yml) apply. -# Otherwise, this overrides the setting from the global configuration (serena_config.yml). -# Therefore, you can set this to [] if you do not want the default modes defined in the global config to apply -# for this project. -# This setting can, in turn, be overridden by CLI parameters (--mode). -# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes -default_modes: - -# list of mode names to be activated additionally for this project, e.g. ["query-projects"] -# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes. -# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes -added_modes: - -# initial prompt for the project. It will always be given to the LLM upon activating the project -# (contrary to the memories, which are loaded on demand). -initial_prompt: "" - -# time budget (seconds) per tool call for the retrieval of additional symbol information -# such as docstrings or parameter information. -# This overrides the corresponding setting in the global configuration; see the documentation there. -# If null or missing, use the setting from the global configuration. -symbol_info_budget: - -# list of regex patterns which, when matched, mark a memory entry as read‑only. -# Extends the list from the global configuration, merging the two lists. -read_only_memory_patterns: [] - -# list of regex patterns for memories to completely ignore. -# Matching memories will not appear in list_memories or activate_project output -# and cannot be accessed via read_memory or write_memory. -# To access ignored memory files, use the read_file tool on the raw file path. -# Extends the list from the global configuration, merging the two lists. -# Example: ["_archive/.*", "_episodes/.*"] -ignored_memory_patterns: [] From 1d0ca35483fc44927fdb6877756fd086bb69bd1e Mon Sep 17 00:00:00 2001 From: quantem-bot Date: Mon, 29 Jun 2026 13:11:26 +0000 Subject: [PATCH 332/335] chore: update lock file --- uv.lock | 861 ++++++++++++++++++++++++++++++++------------------------ 1 file changed, 492 insertions(+), 369 deletions(-) diff --git a/uv.lock b/uv.lock index 3bec33e5..0a665106 100644 --- a/uv.lock +++ b/uv.lock @@ -24,29 +24,29 @@ wheels = [ [[package]] name = "alembic" -version = "1.18.4" +version = "1.18.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mako" }, { name = "sqlalchemy" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/94/13/8b084e0f2efb0275a1d534838844926f798bd766566b1375174e2448cd31/alembic-1.18.4.tar.gz", hash = "sha256:cb6e1fd84b6174ab8dbb2329f86d631ba9559dd78df550b57804d607672cedbc", size = 2056725, upload-time = "2026-02-10T16:00:47.195Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/cc/ac0bed8e562e7407fe55c3ba85a4dce86e6dbd8730887bd1e406a6c5c18a/alembic-1.18.5.tar.gz", hash = "sha256:1554982221dd17e9a749b53902407578eb305e453f71999e8c7f0a48389fff8e", size = 2060480, upload-time = "2026-06-25T15:20:54.888Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/29/6533c317b74f707ea28f8d633734dbda2119bbadfc61b2f3640ba835d0f7/alembic-1.18.4-py3-none-any.whl", hash = "sha256:a5ed4adcf6d8a4cb575f3d759f071b03cd6e5c7618eb796cb52497be25bfe19a", size = 263893, upload-time = "2026-02-10T16:00:49.997Z" }, + { url = "https://files.pythonhosted.org/packages/96/78/5fe6dc3a3a5b2f5a2a4faef8bfe336d5fa049a38884ab3172e0098160c01/alembic-1.18.5-py3-none-any.whl", hash = "sha256:06d8ba9d04558022f5395e9317de03d270f3dced49cee01f89fe7a13c26f14bc", size = 264664, upload-time = "2026-06-25T15:20:56.673Z" }, ] [[package]] name = "anyio" -version = "4.13.0" +version = "4.14.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/72/5562aabb8dd7181e8e860622a38bea08d17842b99ecd4c91f84ac95251b0/anyio-4.14.1.tar.gz", hash = "sha256:8d648a3544c1a700e3ff78615cd679e4c5c3f149904287e73687b2596963629e", size = 254831, upload-time = "2026-06-24T20:56:06.017Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, + { url = "https://files.pythonhosted.org/packages/b0/7b/90df4a0a816d98d6ea26f559d87836d494a2cf1fcf063be67df50a7bcc30/anyio-4.14.1-py3-none-any.whl", hash = "sha256:4e5533c5b8ff0a24f5d7a176cbe6877129cd183893f66b537f8f227d10527d72", size = 124875, upload-time = "2026-06-24T20:56:04.413Z" }, ] [[package]] @@ -196,11 +196,11 @@ css = [ [[package]] name = "certifi" -version = "2026.5.20" +version = "2026.6.17" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" }, + { url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" }, ] [[package]] @@ -373,14 +373,14 @@ wheels = [ [[package]] name = "click" -version = "8.4.1" +version = "8.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9b/98/518d8e5081007684232226f475082b30087d0f585e8457db087298259f49/click-8.4.1.tar.gz", hash = "sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96", size = 353007, upload-time = "2026-05-22T04:08:37.769Z" } +sdist = { url = "https://files.pythonhosted.org/packages/76/d4/81420972a676e8ffea40450d8c8c92943e7218a78fe9b64359836cc9876b/click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6", size = 338000, upload-time = "2026-06-24T17:45:15.148Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl", hash = "sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2", size = 116639, upload-time = "2026-05-22T04:08:35.26Z" }, + { url = "https://files.pythonhosted.org/packages/fb/e2/79c688af8b210d232694e31e59da9f6ec747bae31c3f5946e4e9b98860d5/click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76", size = 119243, upload-time = "2026-06-24T17:45:13.73Z" }, ] [[package]] @@ -399,7 +399,8 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorspacious" }, { name = "matplotlib" }, - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2f/ca/39152b506133881be74bd46cced27ec10d9bbc85db5bdfc22c8f8e4011f9/cmasher-1.9.2.tar.gz", hash = "sha256:6c90c2cec87146e3210d3e7f2321fceee38ac7d87c136cd0de25160a14f57ff5", size = 520860, upload-time = "2024-11-19T11:17:02.084Z" } wheels = [ @@ -432,7 +433,8 @@ name = "colorspacious" version = "1.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/75/e4/aa41ae14c5c061205715006c8834496d86ec7500f1edda5981f0f0190cc6/colorspacious-1.1.2.tar.gz", hash = "sha256:5e9072e8cdca889dac445c35c9362a22ccf758e97b00b79ff0d5a7ba3e11b618", size = 688573, upload-time = "2018-04-08T04:27:30.83Z" } wheels = [ @@ -453,7 +455,8 @@ name = "contourpy" version = "1.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } wheels = [ @@ -532,101 +535,86 @@ wheels = [ [[package]] name = "coverage" -version = "7.14.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/fd/0ab2772530e946e1be1abd0bc09e647ec9b02e88f0867857601fefca8953/coverage-7.14.1.tar.gz", hash = "sha256:30c08f7d90415aa98b3c990385dea2939b0da55f38515e5b369b83655f8523be", size = 920132, upload-time = "2026-05-26T20:41:36.783Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7d/d7/477ad149490e6cb849f28abea1dabb9c823cea72e7500c81b4240ce619c0/coverage-7.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:478b5bcd63c2e1357c5c7e16c070690df7b07f676b1c114d7b93e533c664309f", size = 219848, upload-time = "2026-05-26T20:38:38.715Z" }, - { url = "https://files.pythonhosted.org/packages/91/82/a5eb47257c50601bb7b9a9d2857c67b7a3a85ad74180eb2c98bb1fbe0ce5/coverage-7.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a24a81f9715ee42ef59a316cc11611c98fe23920f7c81861315c9f3ff4a230f4", size = 220354, upload-time = "2026-05-26T20:38:40.232Z" }, - { url = "https://files.pythonhosted.org/packages/43/8b/78419b5391a5cb706b6544390507e469d83ffc9a8248b02c4011aceb9365/coverage-7.14.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:196a13319ad88d6d8ef5ab489ec4f44ddde2143c0c7d5b27786f6c3ffd56a7e1", size = 250771, upload-time = "2026-05-26T20:38:41.782Z" }, - { url = "https://files.pythonhosted.org/packages/77/63/e77aaacd491182210d639636b7a8bba23ffffa9b82aa3762da9431855fa9/coverage-7.14.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3d452fd08b5c72c5167c93e6867b5c08500bd40f2a21e1e854a500550b6cc36f", size = 252683, upload-time = "2026-05-26T20:38:43.305Z" }, - { url = "https://files.pythonhosted.org/packages/65/1c/a022e3cfbec2ac241640003cb3a817e161d9c7f5aa9b49173756cdc03204/coverage-7.14.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23bf7fa51ac02e07fc7c96849b82946da47ae862dc8f86d183b2a4864fc38129", size = 254791, upload-time = "2026-05-26T20:38:45.361Z" }, - { url = "https://files.pythonhosted.org/packages/61/d6/967e408aca4c1ceb88cb0cc677169110ae7f5995fb5eaf5fb1f5a1bb8f5d/coverage-7.14.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bcaa50684dcaadfa599ac48f81103c756d791cfd85c97203d2217c593d48b860", size = 256748, upload-time = "2026-05-26T20:38:46.91Z" }, - { url = "https://files.pythonhosted.org/packages/b8/be/869188f7fe28638078ec479331ace6dc5f7b40b7153eb616f47ab79404d8/coverage-7.14.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4ea1c034f95c9b056e856b794630b17f9fa3d57e4800ff1e503d3be0f9c9078c", size = 250907, upload-time = "2026-05-26T20:38:48.493Z" }, - { url = "https://files.pythonhosted.org/packages/07/aa/adb7d3b4278d690e68703abcd76ab1b948242e3668d921711551b78f9ddb/coverage-7.14.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c7e057326434e441306226fbeb5d1aaf14a2637efe97ba668306635835f32ad7", size = 252483, upload-time = "2026-05-26T20:38:50.074Z" }, - { url = "https://files.pythonhosted.org/packages/43/61/331c74103c62dcb0c4b9b3a0de9a61aca016208b0a90f109592a9f9ecc28/coverage-7.14.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:59baf88468dbc8d63b1887afd92bda52e40bb1561696e5819670601403810cec", size = 250545, upload-time = "2026-05-26T20:38:51.613Z" }, - { url = "https://files.pythonhosted.org/packages/f6/b6/c5dae3c104d89be04828f61810e6b3473825482e4c288cc4ed04553e08ae/coverage-7.14.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d34d75f892b3ab73ba11cab5442cce7b3e168fd64162b16f0e1e0d09c508edef", size = 254310, upload-time = "2026-05-26T20:38:53.503Z" }, - { url = "https://files.pythonhosted.org/packages/ad/a1/2b9d5863e3b83c01ad8199e3c597802fbb3a9dc90b058885804c20296d31/coverage-7.14.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3a56abc20a472baf0304c455721bc601477440d28ecfde8a03dde79ede07e0df", size = 250266, upload-time = "2026-05-26T20:38:55.414Z" }, - { url = "https://files.pythonhosted.org/packages/7f/5e/0e511fbdb269359be26fe678a1c3fa1f2aa2a01573cc3f54268c8d6d4797/coverage-7.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6a3cb83d1552c0cd1b4906655b6a33fd4a8473229633a901c6b73bf86914dee9", size = 251174, upload-time = "2026-05-26T20:38:57.141Z" }, - { url = "https://files.pythonhosted.org/packages/85/10/e55307b622b3dd9671cb321824502dc10f93e72f2802b9946159a8edadeb/coverage-7.14.1-cp311-cp311-win32.whl", hash = "sha256:10274a1fbeb8ec5d72966e17bb198a3104257aca4ac09d98667c5f8aca8c8548", size = 222354, upload-time = "2026-05-26T20:38:58.727Z" }, - { url = "https://files.pythonhosted.org/packages/71/cf/107421693cfb71e4f1ca5bf70443f64d4161878068d07a3e51c7ad21d17b/coverage-7.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:87ebdf787d4888e3f3f2d523eadc6e18c6d18c6d0eb173801a189641627fb37e", size = 223290, upload-time = "2026-05-26T20:39:00.413Z" }, - { url = "https://files.pythonhosted.org/packages/b8/1d/3e3644585eb29e9dafefb19555078529a4d7cce12bd21929664eea989277/coverage-7.14.1-cp311-cp311-win_arm64.whl", hash = "sha256:dd34767fa19848d35659ffc0a75314f58c7af3f1cd87ec521e8292a1238398a3", size = 221953, upload-time = "2026-05-26T20:39:02.159Z" }, - { url = "https://files.pythonhosted.org/packages/3d/b7/bdbb725ba02c5b42825b200c940f38b7a54fcad24627b7192f78f8110d76/coverage-7.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a06c76364a9360e33d6d23769aefdf7f66f38e2ffb60ceb1baaa4989d83b695c", size = 220022, upload-time = "2026-05-26T20:39:03.702Z" }, - { url = "https://files.pythonhosted.org/packages/72/81/fdc0898a55c6219223291ec1a1fe89966ef212ce82276aa0899df84b5de0/coverage-7.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fad54e871165f6ec2f536063ac74c3104508a12963e64072ba44bd822de52b0c", size = 220379, upload-time = "2026-05-26T20:39:05.381Z" }, - { url = "https://files.pythonhosted.org/packages/de/72/de048c4a25e13bce59ac6a339351c10bdf2515e07459afcdaf04dc3143a2/coverage-7.14.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:84b535f00655ecafe1d929d1fb00ed5d6fa3051ea643ab2c161a3887b86f294b", size = 251888, upload-time = "2026-05-26T20:39:07.367Z" }, - { url = "https://files.pythonhosted.org/packages/28/30/300c343f68beb9d4cbb64ec81e58c5b6b80b56927f72d2b38654ac26e013/coverage-7.14.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6b6b0853b895fe0e98cbfc580d1ec3393d9302b4b1e96a77b3f5c91fdab899e6", size = 254624, upload-time = "2026-05-26T20:39:09.037Z" }, - { url = "https://files.pythonhosted.org/packages/b1/ed/7b25642496e8170b6bac14adce00537c6e5fa2d586159401a4de3e8b49e6/coverage-7.14.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:442cc9c952b2df400cda54bb04ab87330cf2cd08a8692cbbea36773531eb6f37", size = 255739, upload-time = "2026-05-26T20:39:10.889Z" }, - { url = "https://files.pythonhosted.org/packages/7f/a2/abd210b8c4e29c24e4624916db97bb519097a91034aaeb767f937e7da794/coverage-7.14.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8270544c361ed405a27a060dbc9ed2c124b084d96dfdc2d9a2510482aef981ad", size = 257998, upload-time = "2026-05-26T20:39:12.722Z" }, - { url = "https://files.pythonhosted.org/packages/7f/24/7c50beed3792fe62f6ce0545c6686ce83379719e2c0276179333d97eae92/coverage-7.14.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:48b283b1dd6372e8de2a7a9a4c4d5dc06f4d4fd209b876f3c88a7a205a0c8f84", size = 252296, upload-time = "2026-05-26T20:39:14.259Z" }, - { url = "https://files.pythonhosted.org/packages/15/05/0f874628ebcbfc77ead559ff210281ef06a97db08481832e7dd39274a135/coverage-7.14.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5b0c99ba93a07d56f6df340bb79be53202a082b2fdb81bfe6190b741a3470d54", size = 253658, upload-time = "2026-05-26T20:39:15.923Z" }, - { url = "https://files.pythonhosted.org/packages/99/6f/ca6ad067364b337ef997802115e7ecad2abd2248b05471464b0dea02b4d4/coverage-7.14.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e471bc5769ff073b058cfadb0d736b56ce067c8560eabeb0da88462df98c23e7", size = 251803, upload-time = "2026-05-26T20:39:17.537Z" }, - { url = "https://files.pythonhosted.org/packages/c0/30/b9b4d377cd9f40baf228068f5a81faf8450c6228503011bd499708483a50/coverage-7.14.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f497a1ea81d4cd7c10ddcaa685135b9aabd291af3d55775a9ddf3cb7a364cdd9", size = 255873, upload-time = "2026-05-26T20:39:19.414Z" }, - { url = "https://files.pythonhosted.org/packages/3c/21/7c721a9e5e6bb88547d30a787aefb97512d3f54c1324c7488d9b3743f7f9/coverage-7.14.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2222be86d0b54f5dd5a38f45f17f315f737245e857bf0bdedc70734f84a13c02", size = 251372, upload-time = "2026-05-26T20:39:21.169Z" }, - { url = "https://files.pythonhosted.org/packages/9d/8c/f8ae5a2200130e1503cd7661a6cd3b2b7bacef98277fbf3571fb13f8b766/coverage-7.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:85e85586565842f6932abebd4c18bcb1074223dc0b3576e7d173ca710622813a", size = 253245, upload-time = "2026-05-26T20:39:23.097Z" }, - { url = "https://files.pythonhosted.org/packages/34/62/70a9024672a5f6910517d9628c52c9afbdd3cf8f46426af52bb148a56fff/coverage-7.14.1-cp312-cp312-win32.whl", hash = "sha256:4a28fd227808366b196a75476dced2eb35b351d6766ba9c858dc93319e87f4f1", size = 222567, upload-time = "2026-05-26T20:39:24.868Z" }, - { url = "https://files.pythonhosted.org/packages/f6/81/8b7cd386839b039ebe1855733b9f9449a8dec5d79564018234f185a7fa70/coverage-7.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:54acdb6674a4661768d7bf7db32dfb9f46ab1d764f8aba6df75ce1a6a088724e", size = 223372, upload-time = "2026-05-26T20:39:26.603Z" }, - { url = "https://files.pythonhosted.org/packages/ae/ba/b44d472022f620d289d95fa830143235c0c36461c6f2437ea8d51e5481ed/coverage-7.14.1-cp312-cp312-win_arm64.whl", hash = "sha256:99cd41ff91afd94896fea3bc002706b6ae4ce95727d06e4a0f39c0a8d8bd8b1a", size = 221989, upload-time = "2026-05-26T20:39:28.242Z" }, - { url = "https://files.pythonhosted.org/packages/8a/9e/5f6d56327c62b185225d145191c607e07515294a0aa6338e58805cd4a5ac/coverage-7.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:be9f2c802dcfce3f71298303aa5dad0dce440a76c52f2f60dacd8656dab78793", size = 220044, upload-time = "2026-05-26T20:39:29.902Z" }, - { url = "https://files.pythonhosted.org/packages/75/92/e82aca356744cbbc0f77a0b623e38918c1872361963413a3bab5d0340393/coverage-7.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6223a72fd0e4c7156353ec0f08a5f93623e1d3034d0e2683b9bb8ea674131b1d", size = 220412, upload-time = "2026-05-26T20:39:31.561Z" }, - { url = "https://files.pythonhosted.org/packages/27/c9/385bde0bf7ed0f4bf3a7ee5367060a86b5d218718cfd6fb943c0f836b34f/coverage-7.14.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7279d2110a28cebc738b6459ecda2771735a4c18465fbbd36b3288fe5ed92247", size = 251412, upload-time = "2026-05-26T20:39:33.337Z" }, - { url = "https://files.pythonhosted.org/packages/51/8c/23faf6a2343a0d17f960a4bd56c43bc7eb4cf312f774dd6ceebd82c7d8fc/coverage-7.14.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9eeb3fcbc13ba40dfbdb22d01d196a28e9cef9ed4c29b60061a1e0e823a9929d", size = 254008, upload-time = "2026-05-26T20:39:35.009Z" }, - { url = "https://files.pythonhosted.org/packages/42/06/36f4aa9ca8a815e6036156e80706a67828bb97bd826948244f6996dda957/coverage-7.14.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f0cfc27c539f07cf5c0a4cfe211d0b6cae039f8f40526dbaa71944e64b50a7b", size = 255241, upload-time = "2026-05-26T20:39:36.71Z" }, - { url = "https://files.pythonhosted.org/packages/ca/79/95266316352f90f6b1c6736bb413302edfde2453fb32422d3911642691b3/coverage-7.14.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:221c70f316241a78e77e607c227cefc8808d4e08f28d99c04f35694690e940be", size = 257373, upload-time = "2026-05-26T20:39:38.412Z" }, - { url = "https://files.pythonhosted.org/packages/e3/9c/58316d1f66c488b5fca8a0eb3e98348807813efa8a0d0833b9021be27488/coverage-7.14.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:da028256b04ec30e5e0114b6f76172938c313991f0a2d3d894271315cf5d5e43", size = 251635, upload-time = "2026-05-26T20:39:40.268Z" }, - { url = "https://files.pythonhosted.org/packages/ef/5a/ca2398a568e16fed7bb713e84ba3603a7164fb65779abe645c565ec890d5/coverage-7.14.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76a085d7005236a767e3426148b2c407e53ad61695c562f8a81da2d373324901", size = 253373, upload-time = "2026-05-26T20:39:42.145Z" }, - { url = "https://files.pythonhosted.org/packages/6e/2c/0396562c32deaebe7be51d865b3a41e9a87d7561acafe1a28f53b07e019a/coverage-7.14.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b553d04b5e778a8e56d57eb134aff42a92718ecba45e79c4764ecfa40efd92ff", size = 251341, upload-time = "2026-05-26T20:39:43.907Z" }, - { url = "https://files.pythonhosted.org/packages/fd/8f/a94f9221184c9cae1ee115820e3798e48b6b17777a9f19e46fb9a0c8dc74/coverage-7.14.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:46f714d2fb8ae2f4f29f23ada7f1e79b759fff5a70f94a1dac23af204c3ec9e4", size = 255497, upload-time = "2026-05-26T20:39:46.166Z" }, - { url = "https://files.pythonhosted.org/packages/71/69/505d70e47db1eaebcd002c39759707621ef184cd6b1ae084d9f41293f323/coverage-7.14.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:1896f5e19ff3f0431c7ce2172adc54890fd97f86b59ced8ca1649145d9ffe35d", size = 251159, upload-time = "2026-05-26T20:39:48.03Z" }, - { url = "https://files.pythonhosted.org/packages/e0/aa/58681c383aa33a9d2ed40a02d7a22fbf780d1fa4d575396365777828198c/coverage-7.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:62fd185ef9df3c33d1c8178c5af105f762afbad96038de9a4ae100aa6297ca33", size = 252934, upload-time = "2026-05-26T20:39:49.872Z" }, - { url = "https://files.pythonhosted.org/packages/eb/fd/11c928cd6bdffc7074bb5965c173d9ebf517fb00205e1da524b98d29ef92/coverage-7.14.1-cp313-cp313-win32.whl", hash = "sha256:ab4af6352741a604c431c6072fce5bee33bf0f20dc7a56618d6bf6bb89e9810c", size = 222584, upload-time = "2026-05-26T20:39:51.68Z" }, - { url = "https://files.pythonhosted.org/packages/6f/92/fb416fc26d340dcba19518c418d6048e913186e17243982c5e435e41fa7a/coverage-7.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:7af486dabe8954d03b087f0021540897afe084f04e16ff5579e08cc46f871416", size = 223394, upload-time = "2026-05-26T20:39:53.472Z" }, - { url = "https://files.pythonhosted.org/packages/73/c6/02d56e3867972f77d5036de924643f26c056e848f00452cafb4dbc3c29b4/coverage-7.14.1-cp313-cp313-win_arm64.whl", hash = "sha256:2224f89ffd0c5605ccce1ed7a584da162bc7c55f601ab1c946bc9de31a486b42", size = 222015, upload-time = "2026-05-26T20:39:55.374Z" }, - { url = "https://files.pythonhosted.org/packages/4d/9e/fcc77914050df73f7662fa1f00902774c79c075a8388ab334074574bf77e/coverage-7.14.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de286598cc65d2b489411174b1faec2f5a7775fb3201fd925db2a76b4030f37d", size = 220733, upload-time = "2026-05-26T20:39:57.189Z" }, - { url = "https://files.pythonhosted.org/packages/f7/67/2963cbdaf5cbadec44efa3a1e39eaa1f02df4079585f05387607a221e126/coverage-7.14.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:042c46ded7c288aeb07cf14a28b6c1e10b78fcba40171c3fa1e939377eeef0b5", size = 221086, upload-time = "2026-05-26T20:39:59.019Z" }, - { url = "https://files.pythonhosted.org/packages/c8/c5/8701645574e11881f2f47d8930f98bc48b5d43b25eb5b4430dfc4a2f9f48/coverage-7.14.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f4ddbe407477f04c45115d1a4e5bc480f753553b534d338d4c3358b1cdd0ea52", size = 262381, upload-time = "2026-05-26T20:40:00.822Z" }, - { url = "https://files.pythonhosted.org/packages/7c/28/7a64d73598263e0c5abd5084211a8474488d31b3c552ff531c719dfcff62/coverage-7.14.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d13e6725992e2d2fd7d81d4f5241952d13740121dfd501da09201be39b2c003a", size = 264458, upload-time = "2026-05-26T20:40:02.506Z" }, - { url = "https://files.pythonhosted.org/packages/fa/d8/4969179db9f7eb4df218e69540adf829d1c835f59452513d065d15446802/coverage-7.14.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f747dc8edcfe740130f28f32f3995e955494285717e86ee25af51db2219df08a", size = 266884, upload-time = "2026-05-26T20:40:04.421Z" }, - { url = "https://files.pythonhosted.org/packages/a6/78/a45d5794dbc9bafd97afc96a4377c86c7820d78b6cf51b89bc1d4e919275/coverage-7.14.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ced2f09ef276fd58611a1ef502164ad266d2b75174e5a40cabbdb4033f9f6cf2", size = 268022, upload-time = "2026-05-26T20:40:06.298Z" }, - { url = "https://files.pythonhosted.org/packages/21/cb/4f5e354e9e3e67af96bd4e57113e6db6b22298c7168b13eec408a549903d/coverage-7.14.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b84800013769a78ccb9ef4659402e26d06867e337b61ec365f77ad008adea80e", size = 261631, upload-time = "2026-05-26T20:40:08.226Z" }, - { url = "https://files.pythonhosted.org/packages/ec/49/eced49af4cb996d5d8b7e94e736175c513e4facd3398507b89892b4326d8/coverage-7.14.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ea8cd6ca0ee9f616aaef3afc6882e32c2cbf18b00d96313ffd76af650574034d", size = 264443, upload-time = "2026-05-26T20:40:10.137Z" }, - { url = "https://files.pythonhosted.org/packages/f1/d8/5603a88a7c5913a6b54f6cb1a8c46f7b39cbb30f27cd3f492908da09b2d7/coverage-7.14.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:aa5e304a873fabddc11e484e9b6b738bd38bd7bed17b09aa84eecf5332e8b8bb", size = 262069, upload-time = "2026-05-26T20:40:11.999Z" }, - { url = "https://files.pythonhosted.org/packages/f0/59/2ae3cb79da554a06c8619d6c88ea19dd1e4aed4b834b6a83bb1fa243bdc5/coverage-7.14.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5a1c5215be81035e629d5bc756650634d0bf31991038db7a0eccb90f025ce16d", size = 265780, upload-time = "2026-05-26T20:40:13.858Z" }, - { url = "https://files.pythonhosted.org/packages/af/5f/b130c1dc999031f2648bd25317fbce505ad8d5562079b4ed81e736a84967/coverage-7.14.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:79058c47dae6788504b5effb319961bcd72d7240551464b91d474bc0ed186d69", size = 260970, upload-time = "2026-05-26T20:40:16.142Z" }, - { url = "https://files.pythonhosted.org/packages/87/d1/ec13ccddeb48ec963bdfa72a11224bac2584bd045ba13beca82f8113e9c7/coverage-7.14.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:370c5afae3fa0658e11694a32b24c2778f6bc2d17718121f94ee185e69f26b54", size = 263157, upload-time = "2026-05-26T20:40:18.382Z" }, - { url = "https://files.pythonhosted.org/packages/cf/c2/cd91ead503045161092d3845f7bb95ea2f25131ce96d3e314dd835d91b9c/coverage-7.14.1-cp313-cp313t-win32.whl", hash = "sha256:3758dd0a7f1fa57365ef2e781df0f0731d38b6e3772259d13dae4bd8a958d4b1", size = 223259, upload-time = "2026-05-26T20:40:20.381Z" }, - { url = "https://files.pythonhosted.org/packages/71/9f/1e28d97e6bd2c76b07f38b7c02870f1371255ff6717f54eca578fcbbdd0e/coverage-7.14.1-cp313-cp313t-win_amd64.whl", hash = "sha256:6ff665fb023a77386fe11685190cee1f60a7d635994a30d9b0a061533d470fce", size = 224320, upload-time = "2026-05-26T20:40:22.316Z" }, - { url = "https://files.pythonhosted.org/packages/a9/e0/d936e908f0e1efa55e52b91e01b52f1055cef5e1ab2718493390ed8e2fb8/coverage-7.14.1-cp313-cp313t-win_arm64.whl", hash = "sha256:17a5a241e5997621a956a7f402a7433ef4221e5152809b785bec79e2323799f1", size = 222577, upload-time = "2026-05-26T20:40:24.894Z" }, - { url = "https://files.pythonhosted.org/packages/d6/34/fc2f101b151af3799a101f0550b0454aa008afdc0add677394ec4aa8ea10/coverage-7.14.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d5ed429d0b8edaac649e889b4ffcedb6c80b06629a3f93050e3dddfb99235bee", size = 220091, upload-time = "2026-05-26T20:40:27.249Z" }, - { url = "https://files.pythonhosted.org/packages/3d/a7/1ebae2ab5b961b5c79bb09fe7b3ac99edb190d8be4a8c510b2cf66f46468/coverage-7.14.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8011224a62280e50dab346960c03cf47aca1a1e09e608c0fb33fd6e0cc8e9500", size = 220421, upload-time = "2026-05-26T20:40:30.084Z" }, - { url = "https://files.pythonhosted.org/packages/5e/90/92aca9cf0acc95123c96cd1eb1f08917897a7f5dee01e15738922971ec31/coverage-7.14.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:12c42ec1e14f553c4f817e989365982e646e27211f10a0f717855b94a79c8906", size = 251466, upload-time = "2026-05-26T20:40:32.542Z" }, - { url = "https://files.pythonhosted.org/packages/26/2b/78048cbe3b999f6cbf9cc0d90abba6a88a3e0863a8c1c6cbc762f3f8802f/coverage-7.14.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:06144cd511cf2624873a035c5069cf297144f6e77a73ee3d7a55b605ec5efb42", size = 253973, upload-time = "2026-05-26T20:40:34.473Z" }, - { url = "https://files.pythonhosted.org/packages/8e/21/c2e33b29d1cfde484a19d437afc343c6cd30b08d78cbbf9f5aff14e57b2b/coverage-7.14.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a311d8e1da24be5c1ccf85cbfb06315dbaa1703d5a1eab3f6432c72b837917c8", size = 255318, upload-time = "2026-05-26T20:40:38.154Z" }, - { url = "https://files.pythonhosted.org/packages/8e/ee/aad2f108d63b769121005302f16bf66db8625c88ceaba466942e09a2607e/coverage-7.14.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c79cead5b5bc584d9c71451cb984d0e3a84e0c0937379c8efcbf27c8d661b851", size = 257633, upload-time = "2026-05-26T20:40:40.164Z" }, - { url = "https://files.pythonhosted.org/packages/c2/f8/11a2c29b4fd76d9849f81d0bb812ec0017a9396df3217214e38934a8c837/coverage-7.14.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dcbf65f1f66a26cdd88c35cf68fb4729c5d1cd2e88added72420541dfb212034", size = 251488, upload-time = "2026-05-26T20:40:42.631Z" }, - { url = "https://files.pythonhosted.org/packages/c9/b8/9a5820de4b8ac2b71d85e3b5fb49108d7469c665f0e2ad0dd7569023e305/coverage-7.14.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fd86572566fb40189a8260446158235159bc7a82dfbc87a3b39cf4fb57fcec1c", size = 253329, upload-time = "2026-05-26T20:40:45.208Z" }, - { url = "https://files.pythonhosted.org/packages/6b/ff/f33e4823667e27548e8fd8df44217515303f9808d0ff29817db56f87d990/coverage-7.14.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:7771b601718fdde84832c3a434ca9bbf4ae9adbc49d84198b4110700c3c77c36", size = 251291, upload-time = "2026-05-26T20:40:47.502Z" }, - { url = "https://files.pythonhosted.org/packages/68/9b/489db0ebb209054766b90a9014a45f6d26eb724c02ec21311c3733b5a644/coverage-7.14.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:39b21e212c55af06fa375e3dbf90a8a8e38792f3a910c580066d23563830ddd5", size = 255564, upload-time = "2026-05-26T20:40:49.372Z" }, - { url = "https://files.pythonhosted.org/packages/27/b5/16bc2d4c2409b23c7737edb68c83bc89e345f378050549fe1d75ac7d34d5/coverage-7.14.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f2302660e32562a532b442480121aef8aa61a5bdb20b30bf0adab29f10a5a4b4", size = 251107, upload-time = "2026-05-26T20:40:51.677Z" }, - { url = "https://files.pythonhosted.org/packages/7d/0c/2629997469a00cd069d588a41c9dc887610f2775ae89d250c4791e65272a/coverage-7.14.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:03a6f93c1ec3b7f2e77b5dbcc5573a2c21f12529a5c6bbe0f16f72303cc2fa4d", size = 252764, upload-time = "2026-05-26T20:40:54.267Z" }, - { url = "https://files.pythonhosted.org/packages/d2/ee/f78d63c8f079e0d7211c7e2401fa17e311514534ba61bae03e4b287ce4ab/coverage-7.14.1-cp314-cp314-win32.whl", hash = "sha256:8a3ce026d73290f42f08dafecbd82c193a74df280461fbf97300fec51fd133ee", size = 222837, upload-time = "2026-05-26T20:40:56.496Z" }, - { url = "https://files.pythonhosted.org/packages/dc/b9/be539854f93a70dfbeec69117f33ec70dc42ff0b65b5b07ab8d40d04228e/coverage-7.14.1-cp314-cp314-win_amd64.whl", hash = "sha256:114c95ef29302423b87d159075805f4ab973254a2638a5d7d046c94887cc87d7", size = 223650, upload-time = "2026-05-26T20:40:58.351Z" }, - { url = "https://files.pythonhosted.org/packages/fe/9e/24e2842fef40f35ac82ba3a7719c8023d011bf3bf652d0675316a9d088a1/coverage-7.14.1-cp314-cp314-win_arm64.whl", hash = "sha256:a07891c3f4805442b31b71e84ba3cf29ed1aa9a428284e06deeb4b23e5b46343", size = 222218, upload-time = "2026-05-26T20:41:00.321Z" }, - { url = "https://files.pythonhosted.org/packages/0a/1d/ac0a9df5fe31c1e8bdd658074905fc12844a05c1a7e3fdb8417e97c31e23/coverage-7.14.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1101a5ebb083aecb625ebb6209d4105b58f647b093cb2dc8122d7b33f743cfe1", size = 220822, upload-time = "2026-05-26T20:41:02.281Z" }, - { url = "https://files.pythonhosted.org/packages/32/cf/f964fd9aff20323f9f1a726c97135f8a76bcd87b92dad141a456a43f3c64/coverage-7.14.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:851b9e1e4e8a4608e77c79714b2e77c0970d2ed7202a05e92ae407817481887b", size = 221084, upload-time = "2026-05-26T20:41:04.593Z" }, - { url = "https://files.pythonhosted.org/packages/d8/5e/7e5ef2aba844de2b80d678619fcf0841b42e3f37f16411226f3fe4c1016f/coverage-7.14.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d5b89cdfb2ee051b71e8c3c70bd81a9eff81100f736a269136fe1a68efe00474", size = 262454, upload-time = "2026-05-26T20:41:06.641Z" }, - { url = "https://files.pythonhosted.org/packages/64/62/75809bded87015cc4935524218a2a8ed8dd1a8498bfed30a2f4f7a4b4d34/coverage-7.14.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0177614a0370f227888b4e436a7c55686d6a9f90eb1ade2b624ba685a1686e86", size = 264578, upload-time = "2026-05-26T20:41:08.556Z" }, - { url = "https://files.pythonhosted.org/packages/f3/42/d33392dc14633525012d2d504fa1a33b05538bf535f5c1d64675e5754b78/coverage-7.14.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d69af5dea2de76fc485a83032a630523f985198b7e25be901ec60181587b01e", size = 266981, upload-time = "2026-05-26T20:41:10.824Z" }, - { url = "https://files.pythonhosted.org/packages/2a/49/0157c4428c2aca7f1e09d5565930586fd5ae36f1655f08b0daa7cf1fcae1/coverage-7.14.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:35ab22d91de736e8966b980dc355cbcdd2c6dbbcfe275f9a2991bc8a91b3df65", size = 268112, upload-time = "2026-05-26T20:41:12.966Z" }, - { url = "https://files.pythonhosted.org/packages/96/26/86b9ce71f4092b1ed325ce1421698081df1286b833400b6836912834d6e0/coverage-7.14.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:357d4e32935c36588aaba057d734fa32428c360c9fc2e4442afbf1b646beee6e", size = 261558, upload-time = "2026-05-26T20:41:15Z" }, - { url = "https://files.pythonhosted.org/packages/20/4c/c311210c5472cf5401d8422b0d7812cdd520f24417673afabda6c323faca/coverage-7.14.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:51bd64741cc6fa065abd300ede1afe5a5291ece9c31da8b24884deda48bcc3f8", size = 264447, upload-time = "2026-05-26T20:41:17.369Z" }, - { url = "https://files.pythonhosted.org/packages/fb/71/59513f8710ed3e6b0ac0a050a5b7e977bb9c9e880354863b5d00d8809256/coverage-7.14.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:9132cd363a68a4c3daa7c8704a654b1e39d3360f6f5b8ddd470608a945236c07", size = 262048, upload-time = "2026-05-26T20:41:19.309Z" }, - { url = "https://files.pythonhosted.org/packages/84/8d/bceed32dc494f5bbf50f775cd2e78ca814953942b5ea28d3c1c3ac316f14/coverage-7.14.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:07c6290b1697b862c0478eab545eec949a0d0e4d6d03497f446d706da3b4f2de", size = 265781, upload-time = "2026-05-26T20:41:21.559Z" }, - { url = "https://files.pythonhosted.org/packages/e7/c5/9348fe40dbfd4991aaf78df2c6c3098bfb2cc834d1fd362a64b4efef855a/coverage-7.14.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5ea0c297e27133853b4d8a3eb799bff5a2dbd9f2f41537a240d337ac9b4df890", size = 260896, upload-time = "2026-05-26T20:41:23.428Z" }, - { url = "https://files.pythonhosted.org/packages/ca/92/1ea0f03929da7cf87206b1fa24f4c8e9c158be0455481af29ec0a1f3503f/coverage-7.14.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:01b7733daad0237daa01ef80fe2dfceffc911e6a17fa7b55d14aa8214eaaaecd", size = 263214, upload-time = "2026-05-26T20:41:25.419Z" }, - { url = "https://files.pythonhosted.org/packages/f6/a9/b2493c054c0e01a643266742ab45e15744e60743f9260cd930c7142b1124/coverage-7.14.1-cp314-cp314t-win32.whl", hash = "sha256:6adc5a36984624a70bf11d7184e20fa0a49aa7c47ffab43804106a1a695ea22e", size = 223624, upload-time = "2026-05-26T20:41:27.795Z" }, - { url = "https://files.pythonhosted.org/packages/fc/bd/3e1e6a57fccd2d7c83fcdf338e93ba98eb85c6e877dd34731ac585375490/coverage-7.14.1-cp314-cp314t-win_amd64.whl", hash = "sha256:ddf799247318f34dbcd2efa8c95a8d0642674e926bb1774cf9b63dfd2a389d1c", size = 224728, upload-time = "2026-05-26T20:41:30.098Z" }, - { url = "https://files.pythonhosted.org/packages/bb/d7/31066cf1d2f0c6c797fce911bcfa01dd35642dc6da992a950256097c5860/coverage-7.14.1-cp314-cp314t-win_arm64.whl", hash = "sha256:145986fe66647eb489f18d9a997567a3fd358584c4b5a808769113abc07466af", size = 222752, upload-time = "2026-05-26T20:41:32.123Z" }, - { url = "https://files.pythonhosted.org/packages/8a/3c/1a983b9a745d7f83d53f057bcc5bf79ba6a2bbc08266b3f0c7d6fe630c9b/coverage-7.14.1-py3-none-any.whl", hash = "sha256:a252f21c27e38347e60111a3266b03827422a7d5525951aceee313aa68bab1d2", size = 211815, upload-time = "2026-05-26T20:41:34.078Z" }, +version = "7.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b4/91/0a7c28934e50d8ac9a7b117712d176f2953c3170bccced5eaacfa3e96175/coverage-7.14.3.tar.gz", hash = "sha256:1a7563a443f3d53fdeb040ec8c9f7466aed7ca3dc5891aa09d3ca3625fa4387f", size = 924398, upload-time = "2026-06-22T23:10:25.584Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/24/efb17eb94018dd3415d0e8a76a4786a866e8964aa9c50f033399d23939c2/coverage-7.14.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e574801e1d643561594aa021206c46d80b257e9853087090ba97bed8b0a509d3", size = 220501, upload-time = "2026-06-22T23:08:02.182Z" }, + { url = "https://files.pythonhosted.org/packages/76/93/32f1bfca6cdd34259c8af42820a034b7a28dfb44969a13ed38c17e0ba5b0/coverage-7.14.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f82b6bb7d75a2613e85d07cefa3a8c973d0544a8993337f6e2728e4a1e94c305", size = 221008, upload-time = "2026-06-22T23:08:03.701Z" }, + { url = "https://files.pythonhosted.org/packages/eb/88/0d0f974855ff905d15a64f7873d00bdc4182e2736267486c6634f4af293c/coverage-7.14.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a2335ea5fed26af2e831094964fa3f8fae60b45f7e37fcc2d3b615b2add3ad87", size = 251420, upload-time = "2026-06-22T23:08:05.211Z" }, + { url = "https://files.pythonhosted.org/packages/39/7f/117dd2ec65e4140576f8ef991d88220f9b806769f7a8c20e0550c0f924e2/coverage-7.14.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fbb8c3a98e779013786ae01d229662aeacbc77100efbd3f2f245219ace5af700", size = 253331, upload-time = "2026-06-22T23:08:06.672Z" }, + { url = "https://files.pythonhosted.org/packages/87/55/f0bd6d6538e3f16829fb8a44b6c0d2fe9da638bbfdd6a20f8b5da8f4fa81/coverage-7.14.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac082660de8f429ba0ea363595abb838998570b9a7546777c60f413ab902bbde", size = 255441, upload-time = "2026-06-22T23:08:08.208Z" }, + { url = "https://files.pythonhosted.org/packages/1e/98/aa71f7879019c846a8a9662579ea4484b0202cf1e252ffeed647075e7eca/coverage-7.14.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ac012839ff7e396030f1e94e10553a431d14e4de2ab65cb3acb72bbd5628ca2", size = 257398, upload-time = "2026-06-22T23:08:09.749Z" }, + { url = "https://files.pythonhosted.org/packages/f3/4f/5fd367e59844190f5965015d7bee899e67a89d13eb2760118479bf836f2f/coverage-7.14.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5952f8c1bda2a5347154450379316e6dfa4d934d62ca35f6784451e6f55074fb", size = 251558, upload-time = "2026-06-22T23:08:11.37Z" }, + { url = "https://files.pythonhosted.org/packages/8f/de/5383a6ee5a6376701fe07d980fa8e4a66c0c377fead16712720340d701a3/coverage-7.14.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8cf0f2509acb4619e2471a1951089054dd58ebea7a912066d2ea56dd4c24ca4a", size = 253134, upload-time = "2026-06-22T23:08:13.04Z" }, + { url = "https://files.pythonhosted.org/packages/01/99/09542b1a99f788e3daec7f0fadc288821e71aca9ea298d51bfa1ba79fed5/coverage-7.14.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2e41fd3aab806770008279a93879b0924b16247e09ab537c043d08bbca53b4ab", size = 251195, upload-time = "2026-06-22T23:08:14.606Z" }, + { url = "https://files.pythonhosted.org/packages/02/9d/722fe8c13f0fbb064491b9e8656e56a606286792e5068c47ca1042e773e8/coverage-7.14.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f0a47095963cfe054e0df178daca95aec21e680d6076da807c3add28dfe920f7", size = 254959, upload-time = "2026-06-22T23:08:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/fb/58/943627179ff1d82da9e54d0a5b0bb907bb19cf19515599ccd921de50b469/coverage-7.14.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a090cbf9521e78ffdb2fcf448b72902afe9f5923ff6a12d5c0d0120200348af9", size = 250914, upload-time = "2026-06-22T23:08:18.03Z" }, + { url = "https://files.pythonhosted.org/packages/a5/d4/803efcbf9ae5567454a0c71e983589529448e2704ee0da2dc0163d482f18/coverage-7.14.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4d310baf69a4fbe8a098ce727e4808a34866ac718a6f759ae659cbd3221358bc", size = 251824, upload-time = "2026-06-22T23:08:19.704Z" }, + { url = "https://files.pythonhosted.org/packages/32/79/3f78ea9563132746eed5cecb75d2e576f9d8fec45a47242b5ae0950b82a3/coverage-7.14.3-cp311-cp311-win32.whl", hash = "sha256:74fdd718d88fe144f4579b8747873a07ec3f04cb837d5faec5a25d9e22fa31a8", size = 222594, upload-time = "2026-06-22T23:08:21.311Z" }, + { url = "https://files.pythonhosted.org/packages/85/22/9ebbc5a2ab42ac5d0eea1f48648629e1de9bbe41ec243ed6b93d55a5a53f/coverage-7.14.3-cp311-cp311-win_amd64.whl", hash = "sha256:cc96aa922e21d4bc5d5ed3c915cef27dfcbc13686f47d5e378d647fbfba655a2", size = 223073, upload-time = "2026-06-22T23:08:23.318Z" }, + { url = "https://files.pythonhosted.org/packages/71/af/69d5fcc16cb555153f99cec5467922f226be0369f7335a9506856d2a7bd0/coverage-7.14.3-cp311-cp311-win_arm64.whl", hash = "sha256:c66f9f9d4f1e9712eb9b1de5310f881d4e2188cfcba5065e1a8490f38687f2c4", size = 222617, upload-time = "2026-06-22T23:08:25.054Z" }, + { url = "https://files.pythonhosted.org/packages/bd/b0/8a911f6ffe6974dac4df95b468ab9a2899d0e59f0f99a489afeec39f00bc/coverage-7.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3d74ff26299c4879ce3a4d826f9d3d4d556fd285fde7bbce3c0ef5a8ab1cec24", size = 220672, upload-time = "2026-06-22T23:08:26.621Z" }, + { url = "https://files.pythonhosted.org/packages/36/16/0fc0cb52538783dbbae0934b834f5a58fd5354380ee6cad4a07b15dc845d/coverage-7.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:96150a9cf3468ea20f0bc5d0e21b3df8972c31480ef90fa7614b773cc6429665", size = 221035, upload-time = "2026-06-22T23:08:28.372Z" }, + { url = "https://files.pythonhosted.org/packages/77/e2/421ccfbb48335ac49e93301478cf5d623b0c2bf1c0cadd8e2b2fc6c0c710/coverage-7.14.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:27d07a46500ba23515b838dbcf52512026af04090755cf6cc64166d88c9b9a1a", size = 252540, upload-time = "2026-06-22T23:08:30.226Z" }, + { url = "https://files.pythonhosted.org/packages/06/c2/05b8c890097c61a7f4406b35396b997a635200ded0339eda83dfbe526c5f/coverage-7.14.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:621e13c6108234d7960aaf5762ab5c3c00f33c30c15af06dcbff0c73bf112727", size = 255274, upload-time = "2026-06-22T23:08:31.876Z" }, + { url = "https://files.pythonhosted.org/packages/dc/be/b6d9efe447f8ba3c3c854195f326bd64c54b907d936cd2fdebf8767ec72e/coverage-7.14.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b60ca6d8af70473491a15a343cbabab2e8f9ea66a4376e81c7aa24876a6f977", size = 256389, upload-time = "2026-06-22T23:08:33.843Z" }, + { url = "https://files.pythonhosted.org/packages/d4/3c/f26e50acc429e608bc534ac06f0a3c169019c798178ec5e9de3dbc0df9c9/coverage-7.14.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c90a7cdd5e380e1ce02f19792e2ac2fbfbf177e35a27e69fd3e873b30d895c0c", size = 258648, upload-time = "2026-06-22T23:08:35.481Z" }, + { url = "https://files.pythonhosted.org/packages/9e/a2/01c1fabf816c8e1dae197e258edf878a3d3ddc86fbda34b76e5794277d8f/coverage-7.14.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5d788e5fd55347eef06ca0732c77d04a264de67e8ff24631270cdff3767a60cf", size = 252949, upload-time = "2026-06-22T23:08:37.562Z" }, + { url = "https://files.pythonhosted.org/packages/89/c6/941166dd79c31fd44a13063780ae8d552eee0089a0a0930b9bdb7df554ed/coverage-7.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62c7f79db2851c95ef020e5d28b97afde3daf9f7febcd35b53e05638f729063f", size = 254310, upload-time = "2026-06-22T23:08:39.174Z" }, + { url = "https://files.pythonhosted.org/packages/10/31/80b1fd028201a961033ce95be3cd1e39e521b3762e6b4a1ac1616cb291e7/coverage-7.14.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:90f7608aeb5d9b60b523b9fb2a4ee1973867cc4865a3f26fe6c7577073b70205", size = 252453, upload-time = "2026-06-22T23:08:40.84Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/c3d9addd94c4b524f3f4af0232075f5fe7170ce99a1386edff803e5934db/coverage-7.14.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1e3b91f9c4740aeb571ecf82e5e8d8e4ab62d34fcb5a5d4e5baa38c6f7d2857c", size = 256522, upload-time = "2026-06-22T23:08:42.494Z" }, + { url = "https://files.pythonhosted.org/packages/91/14/e5a0575f73795af3a7a9ae13dadf812e17d32422896839987dc3f86947e1/coverage-7.14.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c946099774a7699de03cbd0ff0a64e21aed4525eed9d959adde4afe6d15758ef", size = 252023, upload-time = "2026-06-22T23:08:44.243Z" }, + { url = "https://files.pythonhosted.org/packages/38/9b/9652ee531937ce3b8a63a8896885b2b4a2d56adc30e53c9540c666286d88/coverage-7.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:16b206e521feb8b7133a45754643dead0538489cf8b783b90cf5f4e3299625fd", size = 253893, upload-time = "2026-06-22T23:08:46.113Z" }, + { url = "https://files.pythonhosted.org/packages/b1/05/42678841c8c38e4b08bdfc48269f5a16dfbf5806000fe6a89b4cece3c691/coverage-7.14.3-cp312-cp312-win32.whl", hash = "sha256:ea3169c7116eb6cdf7608c6c7da9ecfcb3da40688e3a510fac2d1d2bafd6dc35", size = 222734, upload-time = "2026-06-22T23:08:47.858Z" }, + { url = "https://files.pythonhosted.org/packages/df/87/07a4fcee55177a25f1b52331a8e92cf4f2c53b1a9c75ce2981fd59c684ad/coverage-7.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:7ea52fc08f007bcc494d4bb3df3851e95843d881860ba38fe2c64dc100db5e7d", size = 223266, upload-time = "2026-06-22T23:08:49.494Z" }, + { url = "https://files.pythonhosted.org/packages/aa/34/2b8b66a989282ea7b370beb49f50bab29470dc30bb0b03935b6b802782f7/coverage-7.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:8cec0ad652ec57790970d817490105bd917d783c2f7b38d6b58a0ca312e1a336", size = 222655, upload-time = "2026-06-22T23:08:51.766Z" }, + { url = "https://files.pythonhosted.org/packages/a9/83/7fefbf5df23ed2b7f489907564a7b34b9b07098128e12e0fdfa92626e456/coverage-7.14.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:47968988b367990ae4ab17523790c38cd125e02c6bfd379b6022be2d40bdc38c", size = 220699, upload-time = "2026-06-22T23:08:53.522Z" }, + { url = "https://files.pythonhosted.org/packages/31/e6/38c3653ff6d56d704b29241362387ca824e38e15b76fdcb7096538195790/coverage-7.14.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0ee68f5c34812780f3a7063382c0a9fcbb99985b7ddcdcaa626e4f3fb2e0783a", size = 221068, upload-time = "2026-06-22T23:08:55.571Z" }, + { url = "https://files.pythonhosted.org/packages/20/86/4f5c45d51c5cd10a128933f0fd235393c9146abbfd2ce2dfa68b3267ead3/coverage-7.14.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fa9e5c6857a7e80fa22ace5cf3550ae392bbfc322f1d8dd2d2d5a8be38cec027", size = 252060, upload-time = "2026-06-22T23:08:57.464Z" }, + { url = "https://files.pythonhosted.org/packages/82/50/dfce42eff2cecabcd5a9bbad5489449c87db3415f408d23ffee417ce01f6/coverage-7.14.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:98a0859b0e98e43e1178a9402e19c8127766b14f7109a374d976e5a62c0e5c73", size = 254657, upload-time = "2026-06-22T23:08:59.453Z" }, + { url = "https://files.pythonhosted.org/packages/ba/d2/639ceb1bc8038fd0d66768278d5dc22df3391918b8278c2a21aa2602a531/coverage-7.14.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69918344541ed9c8368566c2adc03c0e33d4550d7faa87d1b35e49b6a3286ea9", size = 255892, upload-time = "2026-06-22T23:09:01.291Z" }, + { url = "https://files.pythonhosted.org/packages/8b/96/002094a10e113512500dc1e10430a449417e17b0f90f7d496bcb820208b7/coverage-7.14.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b7f300ac92cd4b570724c8ffbbd0c130fee298d2447f41d5a3abf58976fae1de", size = 258026, upload-time = "2026-06-22T23:09:03.017Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ec/286a5d2fad9c4bee59bd724feeb7d5bf8303c6c9200b51d1dd945a9c72b0/coverage-7.14.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:11a7ec9f97ab950f4c5af62229befc7faf208fdbc0116d3902d7e306cf2c5abd", size = 252285, upload-time = "2026-06-22T23:09:04.773Z" }, + { url = "https://files.pythonhosted.org/packages/d9/7d/a17753a0b12dd48d0d50f5fab079ad99d3be1eac790494d89f3a417ca0b9/coverage-7.14.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a571bd889cd36c5922ce8e42e059f9d37d02301531d11374afa4c87a578625d5", size = 254023, upload-time = "2026-06-22T23:09:06.513Z" }, + { url = "https://files.pythonhosted.org/packages/86/ef/a76c6ceba6a2c313f905310abf2701d534cada22d372db11731831e9e209/coverage-7.14.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:de76caefc8deabb0dd1678b6a980be97d14c8d87e213ac194dbf8b09e96d63fb", size = 251989, upload-time = "2026-06-22T23:09:08.382Z" }, + { url = "https://files.pythonhosted.org/packages/d9/39/353013a75fec0fb49f7553519f9d52b4441e902e5178c93f38eb6c07cedb/coverage-7.14.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d20a15c622194234161535459affa8f7905830391c9ccfa060d495dbfe3a1c7f", size = 256144, upload-time = "2026-06-22T23:09:10.369Z" }, + { url = "https://files.pythonhosted.org/packages/29/0e/613878555d734def11c5b20a2701a15cb3781b9e9ea749da27c5f436e928/coverage-7.14.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b488bd4b23397db62e7a9459129d01ff06a846582a732efd24834b24a6ada498", size = 251808, upload-time = "2026-06-22T23:09:12.057Z" }, + { url = "https://files.pythonhosted.org/packages/af/76/359c058c9cfdcf1e8b107663881225b03b364a320017eda24a2a66e55102/coverage-7.14.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a3693b4153394d265f44fb855fdc80e72403024d4d6f91c4871b334d028e4e0", size = 253579, upload-time = "2026-06-22T23:09:13.858Z" }, + { url = "https://files.pythonhosted.org/packages/1d/d9/4ba2f060933a30ebe363cef9f67a365b0a317e580c0d5d9169d56a73ef1c/coverage-7.14.3-cp313-cp313-win32.whl", hash = "sha256:338b19131ab1a6b767b462bfcbaa692e7ae22f24463e39d49b02a83410ff6b37", size = 222741, upload-time = "2026-06-22T23:09:15.636Z" }, + { url = "https://files.pythonhosted.org/packages/76/e8/196ebc25d8f34c06d43a6e9c8513c9266ef8dbf3b5672beb1a00cf5e29fa/coverage-7.14.3-cp313-cp313-win_amd64.whl", hash = "sha256:b3d77f7f196abdef7e01415de1bce09f216189e83e58159cfeef2b92d0464994", size = 223283, upload-time = "2026-06-22T23:09:17.478Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/51d2aac6417523a286f10fb25f09eb9518a84df9f1151e93ff6871f34849/coverage-7.14.3-cp313-cp313-win_arm64.whl", hash = "sha256:e6230e688c7c3e65cedd41a774eb4ec221adc6bfee13768231015b702d5e4150", size = 222678, upload-time = "2026-06-22T23:09:19.7Z" }, + { url = "https://files.pythonhosted.org/packages/61/56/14e3b97facbfa1304dd19e676e26599ad359f04714bed32f7f1c5a88efdc/coverage-7.14.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:605ab2b566a22bd94834529d66d295c364aba84afd3e5498285c7a524017b1fc", size = 220741, upload-time = "2026-06-22T23:09:21.616Z" }, + { url = "https://files.pythonhosted.org/packages/12/1d/db378b5cca433b90b893f26dab728b280ddd89f272a1fdfed4aeaa05c686/coverage-7.14.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a3c2134809e80fac091bfed18a6991b5a5eb5df5ae32b17ac4f4f99864b73dd7", size = 221068, upload-time = "2026-06-22T23:09:23.452Z" }, + { url = "https://files.pythonhosted.org/packages/47/f0/3f8421b20d9c4fcd39be9a8ca3c3fda8bc204b44efbd09fede153afd3e2f/coverage-7.14.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c02efd507227bde9969cab0db8f48890eb3b5dcad6afac57a4792df4133543ce", size = 252117, upload-time = "2026-06-22T23:09:25.458Z" }, + { url = "https://files.pythonhosted.org/packages/27/ca/59ea35fb99743549ec8b37eff141ece4431fea590c89e536ed8032ef45cf/coverage-7.14.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1bb93c2aa61d2a5b38f1526546d95cf4132cb681e541a337bf8dfd092be816e5", size = 254622, upload-time = "2026-06-22T23:09:27.523Z" }, + { url = "https://files.pythonhosted.org/packages/c8/25/ec6de51ae7493b92a1cf74d1b763121c29636759167e2a593ba4db5881e4/coverage-7.14.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f502e948e03e866538048bba081c075caaa62e5bda6ea5b7432e45f587eb462a", size = 255968, upload-time = "2026-06-22T23:09:29.43Z" }, + { url = "https://files.pythonhosted.org/packages/5d/05/c8bfc77823f42b4664fb25842f13b567022f6f84a4c83c8ecbb16734b7cb/coverage-7.14.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9973ef2463f8e6cfb61a6324126bb3e17d67a85f22f58d856e583ea2e3ca6501", size = 258284, upload-time = "2026-06-22T23:09:31.397Z" }, + { url = "https://files.pythonhosted.org/packages/f6/15/1d1b242027124a32b26ef01f82018b8c4ef34ef174aa6aeba7b1eeef48e8/coverage-7.14.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9be4e7d4c5ca0427889f8f9d614bd630c2be741b1de7699bca3b2b6c0e41003e", size = 252143, upload-time = "2026-06-22T23:09:33.256Z" }, + { url = "https://files.pythonhosted.org/packages/74/b6/d2a9842fd2a5d7d27f1ac851c043a734a494ad75402c5331db3da79ed691/coverage-7.14.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a574912f3bde4b0619f6e97d01aa590b70998859244793769eb3a6df78ee56d3", size = 253976, upload-time = "2026-06-22T23:09:35.351Z" }, + { url = "https://files.pythonhosted.org/packages/fd/30/e1600ddf7e226db5558bb5323d2186fff00f505c4b764643ec89ce5d8175/coverage-7.14.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:e343fb086c9cd780b38622fea7c369acd64c1a0724312149b5d769c387a2b1f5", size = 251942, upload-time = "2026-06-22T23:09:37.313Z" }, + { url = "https://files.pythonhosted.org/packages/d9/2c/9159de64f9dd648e324328d588a44cfab1e331eb5259ce1141afe2a92dfb/coverage-7.14.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:3c68df8e61f1e09633fefc7538297145623957a048534368c9d212782aa5e845", size = 256220, upload-time = "2026-06-22T23:09:39.165Z" }, + { url = "https://files.pythonhosted.org/packages/91/67/b7f536cc2c124f48e91b22fbb741d2261f4e3d310faf6f76007f47566e5d/coverage-7.14.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3e5b550a128419373c2f6cec28a244207013ef15f5cbcff6a5ca09d1dfaaf027", size = 251756, upload-time = "2026-06-22T23:09:41.056Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ec/f3718038e2d4860c715a55428377ca7f6c75872caf98cabd982e1d76967d/coverage-7.14.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2bfc4dd0a912329eccc7484a7d0b2a38032b38c40663b1e1ac595f10c457954b", size = 253413, upload-time = "2026-06-22T23:09:43.306Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a5/91f11efeef89b3cc9b30461128db15b0511ef813ab889a7b7ab636b3a497/coverage-7.14.3-cp314-cp314-win32.whl", hash = "sha256:0423d64c013057a06e70f070f073cec4b0cbc7d2b27f3c7007292f2ff1d52965", size = 222946, upload-time = "2026-06-22T23:09:45.261Z" }, + { url = "https://files.pythonhosted.org/packages/58/fd/98ac9f524d9ec378de831c034dbdeb544ca7ef7d2d9c9996daf232a037fd/coverage-7.14.3-cp314-cp314-win_amd64.whl", hash = "sha256:92c22e19ce64ca3f2ad751f16f14df1468b4c231bd6af97185063a9c292a0cb3", size = 223436, upload-time = "2026-06-22T23:09:47.177Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a0/7cd612d650a772a0ae80144443406bf61981c896c3d57c9e6e79fb2cdbd1/coverage-7.14.3-cp314-cp314-win_arm64.whl", hash = "sha256:41de778bd41780586e2b04912079c73089ab5d839624e28db3bdb26de638da92", size = 222861, upload-time = "2026-06-22T23:09:49.384Z" }, + { url = "https://files.pythonhosted.org/packages/55/57/017353fab573779c0d00448e47d102edd36c792f7b6f233a4d89a7a08384/coverage-7.14.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8427f370ca67db4c975d2a26acfc0e5783ca0b52444dbc50278ace0f35445949", size = 221474, upload-time = "2026-06-22T23:09:51.417Z" }, + { url = "https://files.pythonhosted.org/packages/69/92/90cf1f1a5c468a9c1b7ba2716e0e205293ad9b02f5f573a6de4318b15ba1/coverage-7.14.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d8e88f335544a47e22ae2e45b344772925ec65166555c958720d5ed971880891", size = 221738, upload-time = "2026-06-22T23:09:53.487Z" }, + { url = "https://files.pythonhosted.org/packages/a4/c0/4df964fa539f8399fd7679c09c472d73744de334686fd3f01e3a2465ce4e/coverage-7.14.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:beaab199b9e5ceaf5a225e16a9d4df136f2a1eae0a5c20de1e277c8a5225f388", size = 263101, upload-time = "2026-06-22T23:09:55.895Z" }, + { url = "https://files.pythonhosted.org/packages/06/76/e5d33b2576ae3bf2be2058cd1cae57774b61e400f2c3c58f3783dc2ffb4a/coverage-7.14.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3ff255799f5a1676c71c1c32ec01fd043aa09d57b3d95764b24992757184784", size = 265225, upload-time = "2026-06-22T23:09:57.904Z" }, + { url = "https://files.pythonhosted.org/packages/61/d2/e52419afe391a39ba27fdefaf0737d8e34bf03faef6ab3b3006545bbd0d0/coverage-7.14.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:878832eaac515b62decfa76965aed558775f86bf1fc8cca76993c0c84ae31aed", size = 267643, upload-time = "2026-06-22T23:09:59.938Z" }, + { url = "https://files.pythonhosted.org/packages/58/7a/f2625d8d5006b6b20fba5afaef00b24a763fe96476ea798a3076cbc1f84e/coverage-7.14.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:611e62cb9386096d81b63e0a05330750268617231e7bd598e1fe77482a2c58a5", size = 268762, upload-time = "2026-06-22T23:10:01.943Z" }, + { url = "https://files.pythonhosted.org/packages/7d/bf/908024006bba57127354d74e938954b9c3cd765cc2e0412dc9c37b415cda/coverage-7.14.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:02c41de2a88011b893050fc9830267d927a50a215f7ad5ec17349db7090ccf26", size = 262208, upload-time = "2026-06-22T23:10:03.954Z" }, + { url = "https://files.pythonhosted.org/packages/34/a0/d4f9296441b909817442fdb26bd77a698f08272ec683a7394b00eb2e47a0/coverage-7.14.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:526ce9721116af23b1065089f0b75046fe521e7772ab94b641cd66b7a0421889", size = 265096, upload-time = "2026-06-22T23:10:05.936Z" }, + { url = "https://files.pythonhosted.org/packages/e8/da/4ae4f3f4e477b56a4ce1e5c48a35eff38a94b50130ce5bdc897024741cfc/coverage-7.14.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e4ed44705ca4bead6fc977a8b741f2145608289b33c8a9b42a95d0f15aedbf4d", size = 262699, upload-time = "2026-06-22T23:10:07.973Z" }, + { url = "https://files.pythonhosted.org/packages/d8/7a/6927148073ff32856d78baa77b4ddc07a9be7e90020f9db0661c4ca523a1/coverage-7.14.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2415902f385a23dcc4ccd26e0ba803249a169af6a930c003a4c715eeb9a5444e", size = 266433, upload-time = "2026-06-22T23:10:10.145Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a7/774f658dbe9c4c3f5daa86a87e0459ac3832e4e3cc67affe078547f727b9/coverage-7.14.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:b75ee850fc2d7c831e883220c445b035f2224de2ba6103f1e56dbd237ab913f7", size = 261547, upload-time = "2026-06-22T23:10:12.191Z" }, + { url = "https://files.pythonhosted.org/packages/3d/14/a0c18c0376c43cbf973f43ef6ca20019c950597180e6396232f7b6a27102/coverage-7.14.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dc9b4e35e7c3920e925ba7f14886fd5fbe481232754624e832ddba66c7535635", size = 263859, upload-time = "2026-06-22T23:10:14.492Z" }, + { url = "https://files.pythonhosted.org/packages/10/ac/43a3d0f460af524b131a6191805bc5d18b806ab4e828fbf82e8c8c3af446/coverage-7.14.3-cp314-cp314t-win32.whl", hash = "sha256:7b27c822a8161afbe48e99f1adfb098d270ae7e0f7d7b0555ce110529bdb69cc", size = 223250, upload-time = "2026-06-22T23:10:16.758Z" }, + { url = "https://files.pythonhosted.org/packages/3f/5f/d5e5c56b0712e96ce8f69fe7dbf229ff938b437bc50862743c8a0d2cea84/coverage-7.14.3-cp314-cp314t-win_amd64.whl", hash = "sha256:39e1dbbb6ff2c338e0196a482558a792a1de3aa64261196f5cdb3da016ad9cda", size = 224082, upload-time = "2026-06-22T23:10:19.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/35/947cbd5be1d3bcbbdc43d6791de8a56c6501903311d42915ae06a82815f0/coverage-7.14.3-cp314-cp314t-win_arm64.whl", hash = "sha256:68520c90babfa2d560eca6d497921ed3a4f469623bd709733124491b2aa8ef3f", size = 223400, upload-time = "2026-06-22T23:10:21.24Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e3/a0aa32bfa3a081951f60a23bc0e7b512891ef0eecda1153cf1d8ba36c6b1/coverage-7.14.3-py3-none-any.whl", hash = "sha256:fb7e18afb6e903c1a92401a2f0501ac277dca527bb9ca6fe1f691a8a0026a0e8", size = 212469, upload-time = "2026-06-22T23:10:23.405Z" }, ] [package.optional-dependencies] @@ -672,34 +660,34 @@ wheels = [ [package.optional-dependencies] cudart = [ - { name = "nvidia-cuda-runtime", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "nvidia-cuda-runtime" }, ] cufft = [ - { name = "nvidia-cufft", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "nvidia-cufft" }, ] cufile = [ - { name = "nvidia-cufile", marker = "sys_platform == 'linux'" }, + { name = "nvidia-cufile" }, ] cupti = [ - { name = "nvidia-cuda-cupti", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "nvidia-cuda-cupti" }, ] curand = [ - { name = "nvidia-curand", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "nvidia-curand" }, ] cusolver = [ - { name = "nvidia-cusolver", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "nvidia-cusolver" }, ] cusparse = [ - { name = "nvidia-cusparse", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "nvidia-cusparse" }, ] nvjitlink = [ - { name = "nvidia-nvjitlink", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "nvidia-nvjitlink" }, ] nvrtc = [ - { name = "nvidia-cuda-nvrtc", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "nvidia-cuda-nvrtc" }, ] nvtx = [ - { name = "nvidia-nvtx", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "nvidia-nvtx" }, ] [[package]] @@ -732,7 +720,8 @@ wheels = [ [package.optional-dependencies] array = [ - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] [[package]] @@ -919,11 +908,11 @@ wheels = [ [[package]] name = "fsspec" -version = "2026.4.0" +version = "2026.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d5/8d/1c51c094345df128ca4a990d633fe1a0ff28726c9e6b3c41ba65087bba1d/fsspec-2026.4.0.tar.gz", hash = "sha256:301d8ac70ae90ef3ad05dcf94d6c3754a097f9b5fe4667d2787aa359ec7df7e4", size = 312760, upload-time = "2026-04-29T20:42:38.635Z" } +sdist = { url = "https://files.pythonhosted.org/packages/10/a1/ae4e3e5003468d6391d2c77b6fa1cd73bd5d13511d81c642d7b28ac90ed4/fsspec-2026.6.0.tar.gz", hash = "sha256:f5bac145310fe30e16e1471bd6840b2d990d609e872251d7e674241822abf01a", size = 313646, upload-time = "2026-06-16T01:57:28.105Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl", hash = "sha256:11ef7bb35dab8a394fde6e608221d5cf3e8499401c249bebaeaad760a1a8dec2", size = 203402, upload-time = "2026-04-29T20:42:36.842Z" }, + { url = "https://files.pythonhosted.org/packages/e5/22/4222d7ddf3da30f363edaa98e329c2bce6c65497c9cb2810931c8b2c0fbc/fsspec-2026.6.0-py3-none-any.whl", hash = "sha256:02e0b71817df9b2169dc30a16832045764def1191b43dcff5bb85bdee212d2a1", size = 203949, upload-time = "2026-06-16T01:57:26.358Z" }, ] [[package]] @@ -958,65 +947,65 @@ wheels = [ [[package]] name = "greenlet" -version = "3.5.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6d/6e/802acd792aebb2256fbbee8cacf2727faaeb6f240ac11008f09eae4414bc/greenlet-3.5.1.tar.gz", hash = "sha256:5a56aeb7d5d9cc4b3a735efb5095bd4b4f6f0e4f93e5ca876d0e2315137b7829", size = 197356, upload-time = "2026-05-20T15:05:03.917Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/42/3c/ff890b466eaba2b0f5e6bdfff025f8c75f41b8ffdc3dbc3d24ad261e764a/greenlet-3.5.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:73f78f9b9f0a5c06e5c946ba1e8e36f5114923b6be109ee618c54f079c3ea14f", size = 284764, upload-time = "2026-05-20T13:09:10.204Z" }, - { url = "https://files.pythonhosted.org/packages/81/0e/5e5457be3d256918f6a4756f073548a3f0190836e2cc94aa6d0d617a940b/greenlet-3.5.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0cbed8bb44e23c5b199f888f4e4ce096b45ad9f25ff74a7ad0213875e936bb2", size = 603479, upload-time = "2026-05-20T14:00:04.757Z" }, - { url = "https://files.pythonhosted.org/packages/6d/e1/f89a21d58d308298e6f275f13a1b472ed96c680b601a371b08be6a725989/greenlet-3.5.1-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a203a8bd0acb0701653d3bbb26e404854a68674139ed5cbb778830f42b09bb33", size = 615495, upload-time = "2026-05-20T14:05:40.87Z" }, - { url = "https://files.pythonhosted.org/packages/75/de/af6cef182862d2ccd6975440d21c9058a77c3f9b469abf94e322dfd2e0e3/greenlet-3.5.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a271fcd66c74615cda6a964fda3f304267a12e50a084472218a39bb0376f563", size = 614754, upload-time = "2026-05-20T13:14:24.947Z" }, - { url = "https://files.pythonhosted.org/packages/1a/c6/50e520283a9f19388a7326b05f9e8637e566003475eacaadad04f558c68d/greenlet-3.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ded7b068c7c31c1a8657d4fd42d886b3e051ae29f88b80c5ff9d502257b0f071", size = 1574097, upload-time = "2026-05-20T14:02:24.003Z" }, - { url = "https://files.pythonhosted.org/packages/21/1c/13abd1f4860d987fa5e1170a01930d6e6cd40d328de487a3c9fdaff0ffd0/greenlet-3.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d0932b81d72f552ded9d810d00021b64d89f2195a91ce115b893f943b7a4ab3c", size = 1641058, upload-time = "2026-05-20T13:14:31.83Z" }, - { url = "https://files.pythonhosted.org/packages/f5/56/5f332b7705545eac2dc01b4e9254d24a793f2656d55d5cc6b94ee59d22ae/greenlet-3.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:88e300d136eac057b2397aa1cfd7328b4c87c7eb66a09c7bc6a1292234db474e", size = 238089, upload-time = "2026-05-20T13:14:03.229Z" }, - { url = "https://files.pythonhosted.org/packages/d9/a9/a3c2fa886c5b94863fb0e61b3bc14610b7aa94cf4f17f8741b11708305fc/greenlet-3.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:cc6ab7e555c8a112ad3a76e368e86e12a2754bcae1652a5602e133ec7b635523", size = 234989, upload-time = "2026-05-20T13:08:27.715Z" }, - { url = "https://files.pythonhosted.org/packages/c4/37/4549f149c9797c21b32c2683c33522af22522099de128b2406672526d005/greenlet-3.5.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:fa4f98af3a528f0c3fd592a26df7f376f93329c8f4d987f6bb979057af8bf5e2", size = 286220, upload-time = "2026-05-20T13:07:28.463Z" }, - { url = "https://files.pythonhosted.org/packages/38/ff/a4f436709716965eaab9f36ea7b906c8a927fbe32fb1372a2071d964f6b1/greenlet-3.5.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffea73584b216150eab159b6d12348fb253e68757974de1e2c40d8a318ac89ed", size = 601585, upload-time = "2026-05-20T14:00:06.141Z" }, - { url = "https://files.pythonhosted.org/packages/65/ad/54bc3fcee3ad368a61b19b67d88117f7a8c29727bf71fffdeda81fbd946e/greenlet-3.5.1-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1072b4f9edcc1e192d9283a66a3e68d6b84c561de33a83d7858beb9ba1effe10", size = 614215, upload-time = "2026-05-20T14:05:42.675Z" }, - { url = "https://files.pythonhosted.org/packages/40/69/b91cda0647df839483201545913514c2827ebea5e5ccdf931842763bc127/greenlet-3.5.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:add5217d68b31130f0beca584d7fef4878327d2e31642b66618a14eef312b63b", size = 611358, upload-time = "2026-05-20T13:14:26.37Z" }, - { url = "https://files.pythonhosted.org/packages/59/90/3cf77e080350cd02fa307bb2abf05df48f4482c240275bbd2c203ba8bb1c/greenlet-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a5ea42a752d47a145eae922b605cd1634665ac3d5ec1e72402d5048e8d60d207", size = 1570475, upload-time = "2026-05-20T14:02:25.29Z" }, - { url = "https://files.pythonhosted.org/packages/65/2c/18cece62045e74598c3c393f70dce4a63f56222015ba29a5d4eeb04f764c/greenlet-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c5551170cf4f5ff5623e9af81323751979fee2c731e2287b61f73cd27257b823", size = 1635625, upload-time = "2026-05-20T13:14:34.027Z" }, - { url = "https://files.pythonhosted.org/packages/30/f5/310d104ddf41eb5a70f4c268d22508dfb0c3c8e86fec152be34d0d2ed819/greenlet-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:3c8bb982ad117d29478ef8f5533e97df21f1e2befd17a299257b0c96d1371c0b", size = 238791, upload-time = "2026-05-20T13:10:39.018Z" }, - { url = "https://files.pythonhosted.org/packages/62/90/ceca11f504cd23a8047a3dea31919adc48df9b626dd0c13f0d858734fdfd/greenlet-3.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:80eb4b04dadc4e67df3fae179a32c4706a3f495bc7f22fc8a81115d5f5512188", size = 235580, upload-time = "2026-05-20T13:08:45.056Z" }, - { url = "https://files.pythonhosted.org/packages/27/69/7f7e5372d998b81001899b1c0823c957aa413ba0f2662e65821611cc31e4/greenlet-3.5.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:51518ff74664078fc51bffcc6fc529b0df5ae58da192691cee765d45ce944a2b", size = 285060, upload-time = "2026-05-20T13:08:51.899Z" }, - { url = "https://files.pythonhosted.org/packages/b1/bf/387f9b6b865fd2ae0d0be09e0004827295a01b71be76ed350dd1e28a91a4/greenlet-3.5.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ffdb3c0bb002c99cd8f298957e046c3dbf6006b5b7cdf11a4e19194624a0a0a", size = 604370, upload-time = "2026-05-20T14:00:07.492Z" }, - { url = "https://files.pythonhosted.org/packages/32/f5/169ce3d4e4c67291bd18f8cbe0299c9f3e45102c7f1fb3c14780c93e4532/greenlet-3.5.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7715a5a2c3378ba602c3a440558261e13a820bb53a82693aacd7b7f6d964e283", size = 616987, upload-time = "2026-05-20T14:05:44.237Z" }, - { url = "https://files.pythonhosted.org/packages/ee/e5/7f2e41d5273be07e77560d61ea4e56485b4d6c316d2a84518c62d1364061/greenlet-3.5.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc71ff466927a201b08305acac451ebe1aedfcea002f62f1f2f2ac2ac1e6a135", size = 613911, upload-time = "2026-05-20T13:14:27.539Z" }, - { url = "https://files.pythonhosted.org/packages/c5/a4/fbdc67579b73615a1f91615e814303cc71e06128f7baaba87be79b8fb90c/greenlet-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cd443683db272ebaaca03af98c0b063ab30db70ea8a31a1559f35e3f7b744ccd", size = 1570689, upload-time = "2026-05-20T14:02:27.225Z" }, - { url = "https://files.pythonhosted.org/packages/e6/b4/77abbe35078be39718a46cd49caf16bceb35662f97a34101dca28aa98e47/greenlet-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:089fff7a6ce8d9316d1f65ebc00273a56be258c1725b32b94de90a3a979557e1", size = 1635602, upload-time = "2026-05-20T13:14:36.344Z" }, - { url = "https://files.pythonhosted.org/packages/37/f7/129f27ca700845b8ee8ca88ce7f43435a1239c2eddb7677fc938822762cf/greenlet-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:110a1ca7b49b014b097f6078272c3f4ed31af45b254de5228b79adba879f6af9", size = 238683, upload-time = "2026-05-20T13:11:50.57Z" }, - { url = "https://files.pythonhosted.org/packages/6d/5c/a485a36e87df8d8fd0632ee01511244f5156a20ed3746cc6599340326395/greenlet-3.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:f16ba1efc0715b680a18b8123d90dad887c6112ae3555b4b5c32c149540c6b4e", size = 235499, upload-time = "2026-05-20T13:12:42.028Z" }, - { url = "https://files.pythonhosted.org/packages/8a/cb/c62454606daf5640369c94d8a9dd540599b1bfc090e2d2180cb77f4038d2/greenlet-3.5.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d8ab31c9de8651a2facdd5c5bb0011f2380dd1a7af78ce2adf4b56095294fc07", size = 285579, upload-time = "2026-05-20T13:08:56.396Z" }, - { url = "https://files.pythonhosted.org/packages/ec/71/c4270398c2eba968a6071af1dfbdcaeee6ec1c24bc8b435b8cc452700da6/greenlet-3.5.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e300185139abc337ade480c327183adf42a875ac7181bfe66d7d4efea31fbea", size = 651106, upload-time = "2026-05-20T14:00:09.448Z" }, - { url = "https://files.pythonhosted.org/packages/1a/ab/71e34b78a44ec271fb5f550c17bc46d301ddc5953890d935f270b0dcdb5a/greenlet-3.5.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7ffdb990dcaa0234cf9845aead5df2e3c3a8b6507d409274dd87e0d5ab05ffc2", size = 663478, upload-time = "2026-05-20T14:05:45.88Z" }, - { url = "https://files.pythonhosted.org/packages/77/96/4efd6fa5c62c85426a0c19077a586258ebc3a2a146ff2493e4312a697a22/greenlet-3.5.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f82b3597e9d83b63408affed0b48fd0f54935edac4302237b9a837be0dae33c", size = 660800, upload-time = "2026-05-20T13:14:29.129Z" }, - { url = "https://files.pythonhosted.org/packages/7a/e0/6c71401a25cac7000261304e866a2f2cc04dc74810d40e2f118aa4799495/greenlet-3.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c0141e37414c10164e702b8fb1473304221ad98f71600850c6ef7ff4880feba0", size = 1617518, upload-time = "2026-05-20T14:02:28.662Z" }, - { url = "https://files.pythonhosted.org/packages/41/26/c5c06643e8c0af9e7bf18e16cb51d0ab7625155f0392e1c9015d66d556cd/greenlet-3.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:50ae25a67bea74ea41fb14b960bc532df73eb713417b2d61892dced82fe8d3bc", size = 1681593, upload-time = "2026-05-20T13:14:39.417Z" }, - { url = "https://files.pythonhosted.org/packages/8a/bd/e11a108317485075e68af9d23039619b86b28130c3b50d227d42edece64b/greenlet-3.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:8a17c42330e261299766b75ac1ea32caa437a9453c8f65d16a13140db378ecd3", size = 239800, upload-time = "2026-05-20T13:09:30.128Z" }, - { url = "https://files.pythonhosted.org/packages/47/f8/8e8e8417b7bf28639a5a56356ef934d0375e1d0c70a57e04d7701e870ffe/greenlet-3.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:7b5f5fae05b8ac6d176a61b60c394a8cbdc2b5b91b81793066e68745cf165e54", size = 236862, upload-time = "2026-05-20T13:09:10.498Z" }, - { url = "https://files.pythonhosted.org/packages/90/12/41bf27fde4d3605d3773ae57751eda182b8be2f5398011c041173b1d9534/greenlet-3.5.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:ea8da1e900d758d078810d4255d8c6aa572181896a31ec79d779eb79c3adc9ad", size = 293637, upload-time = "2026-05-20T13:12:35.529Z" }, - { url = "https://files.pythonhosted.org/packages/44/44/ba14b23e9757707050c2f397d305bbcae62e5d7cad122f8b6baec5ae4a1f/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a19570c52a21420dcbc94e661994bc325c0b5b11304540fed514586da5dc8f2e", size = 650840, upload-time = "2026-05-20T14:00:11.079Z" }, - { url = "https://files.pythonhosted.org/packages/a8/37/5ddc2b686a6844f91abecef43411842426da2e1573f60b49ecf2547f4ae1/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3d955c89b75eeca4723d7cc14135f393cd47c32e2a6cb4a8e4c6e760a26b0986", size = 656416, upload-time = "2026-05-20T14:05:47.118Z" }, - { url = "https://files.pythonhosted.org/packages/e1/f0/d17510297c35a2992712f0bf84de3779749999f7d3d63aa1f09db7c62dbe/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2daaaebd1a5aa88c49045b6baf9310b3263796bd88db713edf37cf53e7bb4e", size = 654397, upload-time = "2026-05-20T13:14:30.696Z" }, - { url = "https://files.pythonhosted.org/packages/37/eb/147387705bb89092645b012586e7273cb5ed3c90ef7eaf3a69173eaf0209/greenlet-3.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bfbd69cc349e43bf3a8ae1c85548ff0718efc887615c2db16c3833d7b0b072d", size = 1614469, upload-time = "2026-05-20T14:02:30.192Z" }, - { url = "https://files.pythonhosted.org/packages/a6/4e/37ee0da7732b7aa9896f17e15579a9df34b9fcb9dd494f0adfa749af6623/greenlet-3.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4378720dd888136c27215a0214d32a4d37c3852765d45bc37aad0623423cfd78", size = 1675115, upload-time = "2026-05-20T13:14:40.972Z" }, - { url = "https://files.pythonhosted.org/packages/57/f3/97dfcf4a6eb5077f8a672234216fb5923eb89f2cab7081cb10b2cf75b605/greenlet-3.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:45718441607f9325d948db98cbc691276059316d0358c188c246da4e1d4d23d2", size = 245246, upload-time = "2026-05-20T13:12:22.646Z" }, - { url = "https://files.pythonhosted.org/packages/5d/73/d7f72e34b582f694f4a9b248162db7b09cc458a259ba8f0c0bfa1a34ea7d/greenlet-3.5.1-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:2baee5ca02031757ffe8cc3d69f0cc0aec7065ce362622da74f32d3bcab1c541", size = 285575, upload-time = "2026-05-20T13:12:07.043Z" }, - { url = "https://files.pythonhosted.org/packages/df/59/fa9c6e87dc8ad27a95dabe2f29f372b733d05a8a67470f6c901ed9975655/greenlet-3.5.1-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b1ec3274918a81d3ea778b9e75b56b72b33f300edb6cf7f3a7fe1dae56683de", size = 656428, upload-time = "2026-05-20T14:00:12.556Z" }, - { url = "https://files.pythonhosted.org/packages/f6/f9/e753408871eaa61dfe35e619cfc67512b036fde99893685d50eea9e07146/greenlet-3.5.1-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:111e2390ffffc47d5840b01711dd7fac07d4c09283d0283e7f3264b14e284c64", size = 667064, upload-time = "2026-05-20T14:05:48.662Z" }, - { url = "https://files.pythonhosted.org/packages/96/27/5565b5b40389f1c7753003a07e21892fda8660926787036d5bc0308b8113/greenlet-3.5.1-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e630136e905fe5ff43e86945ae41220b6d1470956a39220e708110ac48d01ea5", size = 665697, upload-time = "2026-05-20T13:14:32.943Z" }, - { url = "https://files.pythonhosted.org/packages/cf/82/e7de4178c0c2d1c9a5a3be3cc0b33e46a85b3ee4a77c071bf7ad8600e079/greenlet-3.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:975eac34b44a7077ca4d421348455b94f0f518246a7f14bc6d2fdcfe5b584368", size = 1621256, upload-time = "2026-05-20T14:02:31.91Z" }, - { url = "https://files.pythonhosted.org/packages/00/10/f2dddcf7dacac17dfc68691809589adad06135eb28930429cf58a6467a2f/greenlet-3.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:9ab3c3a0b2ae6198e67c898dad5215a49f9ae0d0081b3c3ec59f333e39eeca26", size = 1685956, upload-time = "2026-05-20T13:14:42.55Z" }, - { url = "https://files.pythonhosted.org/packages/22/17/4a232b32133230ada52f70e9d7f5b65b0caef8772f01849bd8d149e7e4ca/greenlet-3.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:cbfc69be86e10dcfef5b1e6269d1d6926552aa89ee39e1de3353360c1b6989ab", size = 239802, upload-time = "2026-05-20T13:13:15.481Z" }, - { url = "https://files.pythonhosted.org/packages/c2/ae/4e623a7e6d4d2a5f4cb8e4c82de4169fc637942caae68d6e676b8a128ac5/greenlet-3.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:92fd6d44ac5e5a887c8a5dc4a8ba0ba908527c31c12f78c6bc7dcfe8aab279f6", size = 236853, upload-time = "2026-05-20T13:15:37.301Z" }, - { url = "https://files.pythonhosted.org/packages/7a/57/816d9cff29119da3505b3d6a5e14a8af89006ac36f47f891ff293ee05af1/greenlet-3.5.1-cp315-cp315t-macosx_11_0_universal2.whl", hash = "sha256:a6fdf2433a5441ef9a95464f7c3e674775da1c8c1177fff311cee1acad4626ed", size = 293877, upload-time = "2026-05-20T13:10:19.078Z" }, - { url = "https://files.pythonhosted.org/packages/23/a1/59b0a7c7d140ff1a75626680b9a9899b79a9176cab298b394968fb023295/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7546556f0d649f99f6a361098a55f761181bb2ea12ff150bb16d26092ad88244", size = 655333, upload-time = "2026-05-20T14:00:14.758Z" }, - { url = "https://files.pythonhosted.org/packages/72/1b/5efe127597625042218939d01855109f352779050768b670b52edcc16a6c/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d5ee3ea898009fa898f85f9982255d35278c477bebe185beca249cab42d4526c", size = 659443, upload-time = "2026-05-20T14:05:50.159Z" }, - { url = "https://files.pythonhosted.org/packages/6c/6d/c404246ea4d22d097a7426d0efb5b781bd7eb67715f09e79001bd552ab18/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5c81f74d204d3edd136ebfd50dce53acbb776995d721a0fe801626cfc93b8cd", size = 658356, upload-time = "2026-05-20T13:14:35.091Z" }, - { url = "https://files.pythonhosted.org/packages/51/02/f8ee37fb6d2219329f350af241c27fcf12df57e723d11f6fc6d3bacdadaa/greenlet-3.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:2c18ef16bf6d4dd410e4dd52996888ea1497be26892fe5bbc73580aba4287b8e", size = 1619216, upload-time = "2026-05-20T14:02:33.403Z" }, - { url = "https://files.pythonhosted.org/packages/93/c5/3dc9475ace2c7a3680da12372cddd7f1ac874eb410a1ac48d3e9dab83782/greenlet-3.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:17d86354f0ae6b61bf9be5148d0dd34e06c3cb7c602c671f79f29ac3b150e659", size = 1678427, upload-time = "2026-05-20T13:14:43.71Z" }, - { url = "https://files.pythonhosted.org/packages/df/4e/750c15c317a41ffb36f0bf40b933e3d744a7dede61889f74443ea69690cf/greenlet-3.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:e7516cf6ae6b8a582c2770a0caed47b8a48373ed732c33d69a72913ae6ac923e", size = 245225, upload-time = "2026-05-20T13:13:59.366Z" }, - { url = "https://files.pythonhosted.org/packages/4f/fd/d3baea2eeb7b617efd47e87ca06e2ec2c6118d303aa9e918e0ce16eadc10/greenlet-3.5.1-cp315-cp315t-win_arm64.whl", hash = "sha256:5028648bf2253ec4745add746129d3904121fa7fe871a76bed23c5720573ce0a", size = 239590, upload-time = "2026-05-20T13:13:37.382Z" }, +version = "3.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/f1/fbbfef6af0bad0548f09bc28948ea3c275b4edb19e17fc5ca9900a6a634d/greenlet-3.5.3.tar.gz", hash = "sha256:a61efc018fd3eb317eeca31aba90ee9e7f26f22884a79b6c6ec715bf71bb62f1", size = 200270, upload-time = "2026-06-26T19:28:24.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/58/5404031044f55afad7aad1aff8be3f22b1bed03e237cfeabbc7e5c8cfde0/greenlet-3.5.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:aca9b4ce85b152b5524ef7d88170efdff80dc0032aa8b75f9aaf7f3479ea95b4", size = 287424, upload-time = "2026-06-26T18:20:31.469Z" }, + { url = "https://files.pythonhosted.org/packages/b4/bf/1c65e9b94a54d547068fa5b5a8a06f221f3316b48908e08668d29c77cb50/greenlet-3.5.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f71be4920368fe1fabeeaa53d1e3548337e2b223d9565f8ad5e392a75ba23fc", size = 606523, upload-time = "2026-06-26T19:07:08.859Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c7/b66baacc95775ad511287acb0137b95574a9ce5491902372b7564799d790/greenlet-3.5.3-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d77e67f65f98449e3fb83f795b5d0a8437aead2f874ca89c96576caf4be3af6", size = 618315, upload-time = "2026-06-26T19:10:06.055Z" }, + { url = "https://files.pythonhosted.org/packages/78/2b/28ed29463522fdbe4c15b1f63922041626a7478316b34ab4adda3f0a4aba/greenlet-3.5.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8540f1e6205bd13ca0ce685581037219ca54a1b41a0a15d228c6c9b8ad5903d7", size = 617381, upload-time = "2026-06-26T18:32:16.077Z" }, + { url = "https://files.pythonhosted.org/packages/2a/7b/ad04e9d1337fc04965dc9fc616b6a72cb65a24b800a014c011ec812f5489/greenlet-3.5.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7ef56fe650f50575bf843acde967b9c567687f3c22340941a899b7bc56e956a8", size = 1577771, upload-time = "2026-06-26T19:09:01.537Z" }, + { url = "https://files.pythonhosted.org/packages/d8/33/6c87ab7ba663f70ca21f3022aad1ffe56d3f3e0521e836c2415e13abcc3c/greenlet-3.5.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5121af01cf911e70056c00d4b46d5e9b5d1415550038573d744138bacb59e6b8", size = 1644048, upload-time = "2026-06-26T18:31:42.996Z" }, + { url = "https://files.pythonhosted.org/packages/1c/35/f0d8ee998b422cf8693b270f098e55d8d4ec8006b061b333f54f177d28d9/greenlet-3.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:0f41e4a05a3c0cb31b17023eff28dd111e1d16bf7d7d00406cd7df23f31398a7", size = 239137, upload-time = "2026-06-26T18:23:21.664Z" }, + { url = "https://files.pythonhosted.org/packages/fb/96/b9820295576ef18c9edc404f10e260ae7215ceaf3781a54b720ed2627862/greenlet-3.5.3-cp311-cp311-win_arm64.whl", hash = "sha256:ec6f1af59f6b5f3fc9678e2ea062d8377d22ac644f7844cb7a292910cf12ff44", size = 237630, upload-time = "2026-06-26T18:24:00.281Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6e/4c37d51a2b7f82d2ff11bb6b5f7d766d9a011726624af255e843727627a3/greenlet-3.5.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:719757059f5a53fd0dde23f78cffeafcdd97b21c850ddb7ca684a3c1a1f122e2", size = 288685, upload-time = "2026-06-26T18:22:08.977Z" }, + { url = "https://files.pythonhosted.org/packages/7a/73/815dd90131c1b71ebdf53dbc7c276cafec2a1173b97559f97aba72724a87/greenlet-3.5.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:efa9f765dd09f9d0cdac651ffdf631ee59ec5dc6ee7a73e0c012ba9c52fbdf5b", size = 604761, upload-time = "2026-06-26T19:07:10.114Z" }, + { url = "https://files.pythonhosted.org/packages/9f/57/079cfe76bcef36b153b25607ee91c6fcb58f17f8b23c86bbbeabe0c88d72/greenlet-3.5.3-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7faba15ac005376e02a0384504e0243be3370ce010296a44a820feb342b505ab", size = 617044, upload-time = "2026-06-26T19:10:07.25Z" }, + { url = "https://files.pythonhosted.org/packages/37/87/b4d095775a3fb1bcafbb483fc206b27ebb785724c83051447737085dc54e/greenlet-3.5.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:87142215824be6ac05e2e8e2786eec307ccbc27c36723c3881959df654af6861", size = 614244, upload-time = "2026-06-26T18:32:17.594Z" }, + { url = "https://files.pythonhosted.org/packages/8a/70/7559b609683650fa2b95b8ab84b4ab0b26556a635d19675e12aa832d826d/greenlet-3.5.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:215275b1b49320987352e6c1b054acca0064f965a2c66992bed9a6f7d913f149", size = 1574210, upload-time = "2026-06-26T19:09:03.077Z" }, + { url = "https://files.pythonhosted.org/packages/ae/73/be55392074c60fc37655ca40fa6022457bfbf6718e9e342a7b0b41f96dd2/greenlet-3.5.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6b1b0eed82364b0e32c4ea0f221452d33e6bb17ae094d9f72aed9851812747ea", size = 1638627, upload-time = "2026-06-26T18:31:44.748Z" }, + { url = "https://files.pythonhosted.org/packages/14/40/c57489acf8e37d74e2913d4eff63aa0dba17acccc4bdeef874dde2dbbec9/greenlet-3.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:cde8adafa2365676f74a979744629589999093bc86e2484214f58e61df08902c", size = 239882, upload-time = "2026-06-26T18:23:27.518Z" }, + { url = "https://files.pythonhosted.org/packages/71/fd/6fea0e3d6600f785069481ee637e09378dd4118acdfd38ad88ae2db31c98/greenlet-3.5.3-cp312-cp312-win_arm64.whl", hash = "sha256:c4e7b79d83805475f0102008843f6eb45fd3bb0b2e88c774adab5fbaab27117d", size = 238211, upload-time = "2026-06-26T18:22:37.671Z" }, + { url = "https://files.pythonhosted.org/packages/9b/ff/a620267401db30a50cc8450ee90730e2d4a85658c055c0e760d4ed47fb13/greenlet-3.5.3-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:c8d87c2134d871df96ecdea9cec7cbaab286dadab0f56476e57aaf9e8ac11550", size = 287609, upload-time = "2026-06-26T18:21:14.724Z" }, + { url = "https://files.pythonhosted.org/packages/d6/fa/5401ac78021c826a25b6dde0c705e0a8f29b617509f9185a31dac15fbe1b/greenlet-3.5.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2d185dd1621757e70c3861cceffd5317ab4e7ed7eb09c82994828468527ade5", size = 607435, upload-time = "2026-06-26T19:07:11.412Z" }, + { url = "https://files.pythonhosted.org/packages/e9/76/1dc144a2e56e65d36405078ed774224375ea520a1870a6e46e08bb4ac7bf/greenlet-3.5.3-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1c514a468149bf8fbbab874188a3535cd8a48a3e353eb53a3d424296f8dbacd3", size = 619787, upload-time = "2026-06-26T19:10:08.396Z" }, + { url = "https://files.pythonhosted.org/packages/bf/87/c298cee62df1de4ad7fec32abda73526cff347fd143a6ed4ac369246668a/greenlet-3.5.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:915f887cf2682b66419b879423a2e072634aa7b7dce6f3ada4957cfced3f1e9a", size = 616786, upload-time = "2026-06-26T18:32:19.128Z" }, + { url = "https://files.pythonhosted.org/packages/9e/2e/e6f009885ed0705ccf33fe0583c117cfd03cde77e31a596dd5785a30762b/greenlet-3.5.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:766cfd421c13e450feb340cd472a3ed9957d438727b7b4593ad7c76c5d2b0deb", size = 1574316, upload-time = "2026-06-26T19:09:04.273Z" }, + { url = "https://files.pythonhosted.org/packages/ef/fe/43fd110b01e40da0adb7c90ac7ea744bef2d43dca00de5095fd2351c2a68/greenlet-3.5.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2ecda9ec22edf38fa389369eaed8c3d37c05f3c54e69f69438dbb2cc1de1458b", size = 1638614, upload-time = "2026-06-26T18:31:46.297Z" }, + { url = "https://files.pythonhosted.org/packages/0f/7c/062447147a61f8b4337b156fe70d32a165fcf2f89d7ca6255e572806705c/greenlet-3.5.3-cp313-cp313-win_amd64.whl", hash = "sha256:c82304750f057167ff60d188df1d0cc1764ce9567eadf03e6a7443bcedd0b30b", size = 239850, upload-time = "2026-06-26T18:21:54.613Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7e/220a7f5824a64a60443fc03b39dfac4ea63a7fb6d481efa27eafa928e7f4/greenlet-3.5.3-cp313-cp313-win_arm64.whl", hash = "sha256:dc133a1569ee667b2a6ef56ce551084aeefd87a5acbc4736d336d1e2edc6cfc4", size = 238141, upload-time = "2026-06-26T18:22:48.507Z" }, + { url = "https://files.pythonhosted.org/packages/c3/93/43e116ee114b28737ba7e12952a0d4e2f55944d0f84e42bc91ba7192a3c9/greenlet-3.5.3-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:fd2e02fa07485778536a036222d616ab957b1d533f36b3ed98ce725d9c9d3117", size = 288202, upload-time = "2026-06-26T18:23:49.604Z" }, + { url = "https://files.pythonhosted.org/packages/82/2f/146d218299046a43d1f029fd544b3d110d0f175a09c715c7e8da4a4a345d/greenlet-3.5.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df0a0628d1597eb0897b62f55d1343f772405fd25f3b2a796c76874b0c2e22e8", size = 654096, upload-time = "2026-06-26T19:07:12.71Z" }, + { url = "https://files.pythonhosted.org/packages/a0/cc/04738cafb3f45fa991ea44f9de94c47dcec964f5a972300988a6751f49d9/greenlet-3.5.3-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ebd933a6adabc298bab47731a130fe6bfb888bd934eee37810f151159544540d", size = 666304, upload-time = "2026-06-26T19:10:09.503Z" }, + { url = "https://files.pythonhosted.org/packages/ce/aa/4e0dad5e605c270c784ab911c43da6adb136ccd4d81180f763ca429a723d/greenlet-3.5.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b9d501b40e80b70e32323c799dd9b420a5577a9601469d362ae1ffb690f3a7c", size = 663635, upload-time = "2026-06-26T18:32:20.802Z" }, + { url = "https://files.pythonhosted.org/packages/d1/50/13efdbea246fe3d3b735e191fec08fb50809f53cd2383ebe123d0809e44b/greenlet-3.5.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a1fad1d11e7d6aab184107baa8e4ece11ccba3ec9599cd7efa5ff4d70d43256a", size = 1621252, upload-time = "2026-06-26T19:09:05.647Z" }, + { url = "https://files.pythonhosted.org/packages/f7/22/c0a336ae4a1410fd5f5121098e5bfbf1865f64c5ef80b4b5412886c4a332/greenlet-3.5.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:fad5aec764399f1b5cc347ad250a59660f20c8f8888ea6bae1f93b769cce1154", size = 1684824, upload-time = "2026-06-26T18:31:47.738Z" }, + { url = "https://files.pythonhosted.org/packages/7a/94/91aec0030bea75c4b3244251d0de60a1f3432d1ecb53ab6c437fb5c3ba61/greenlet-3.5.3-cp314-cp314-win_amd64.whl", hash = "sha256:7669aa24cf2a1041d6f7899575b494a3ab4cf68bfcc8609b1dc0be7272db835e", size = 240754, upload-time = "2026-06-26T18:22:15.669Z" }, + { url = "https://files.pythonhosted.org/packages/e5/06/68d0983e79e02138f64b4d303c500c27ddb48e5e77f3debb80888a921eae/greenlet-3.5.3-cp314-cp314-win_arm64.whl", hash = "sha256:5b4807c4082c9d1b6d9eed56fcd041863e37f2228106eef24c30ca096e238605", size = 239549, upload-time = "2026-06-26T18:22:42.996Z" }, + { url = "https://files.pythonhosted.org/packages/91/95/3e161213d7f1d378d15aa9e792093e9bfe01844680d04b7fd6e0107c9098/greenlet-3.5.3-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:271a8ea7c1024e8a0d7dd2be66dd66dda8a07193f41a17b9e924f7600f5b62be", size = 296389, upload-time = "2026-06-26T18:22:20.657Z" }, + { url = "https://files.pythonhosted.org/packages/00/92/715c44721abe2b4d1ae9abde4179411868a5bff312479f54e105d372f131/greenlet-3.5.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19131729ae0ddc3c2e1ef85e650169b5e37ee32e400f215f78b94d7b0d567310", size = 653382, upload-time = "2026-06-26T19:07:14.209Z" }, + { url = "https://files.pythonhosted.org/packages/a0/83/37a10372a1090a6624cca8e74c12df1a36c2dc36429ed0255b7fb1aeee23/greenlet-3.5.3-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1540dd8e5fc2a5aec40fbb98ef8e149fa47c89a4b4a1cf2575a14d3d1869d7a8", size = 659401, upload-time = "2026-06-26T19:10:10.876Z" }, + { url = "https://files.pythonhosted.org/packages/db/e2/d1509cad4207da559cc42986ecdd8fc67ad0d1bba2bf03023c467fd5e0f3/greenlet-3.5.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e81fa194a1d20967877bdf9c7794db2bc99063e5be36aee710c08f04c5bb087f", size = 656969, upload-time = "2026-06-26T18:32:22.272Z" }, + { url = "https://files.pythonhosted.org/packages/86/7d/eaf70de20aadca3a5884aec58362861c64ce45e7b277f47ed026926a3b89/greenlet-3.5.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:55cf4d777485d43110e47133cbba6d74a8885a87ec1227ef0267f9ee80c5aa21", size = 1617822, upload-time = "2026-06-26T19:09:06.893Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f9/414d38fc400ae4350d4185eaad1827676f7cf5287b9136e0ed1cbbe20a7f/greenlet-3.5.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:12a248ba75f6a9a236375f52296c498c89ff1d8badf32deb9eca7abd5853f7da", size = 1677983, upload-time = "2026-06-26T18:31:49.396Z" }, + { url = "https://files.pythonhosted.org/packages/e4/15/7edb977e08f9bff702fe42d6c902702786ff6b9694058b4e6a2a6ac90e57/greenlet-3.5.3-cp314-cp314t-win_amd64.whl", hash = "sha256:efc6bd60ea02e085862c74a3ef64b147ffc6f1a5ea7d9f26e7a939943f68c1e3", size = 243626, upload-time = "2026-06-26T18:24:41.485Z" }, + { url = "https://files.pythonhosted.org/packages/2c/8a/93928dce91e6b3598b5e779e8d1fd6576a504640c58e78627077f6a7a91a/greenlet-3.5.3-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:ea03f2f04367845d6b58eeed276e1e56e51f0b97d8ad5a88a7d20a91dc9056cc", size = 288860, upload-time = "2026-06-26T18:22:48.07Z" }, + { url = "https://files.pythonhosted.org/packages/4f/ca/69db42d447a1378043e2c8f19c09cbbd1263371505053c496b49066d3d16/greenlet-3.5.3-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78dbef602fda6d97d957eb7937f70c9ce9e9527330347f8f6b6f9e554a9e7a47", size = 659747, upload-time = "2026-06-26T19:07:15.565Z" }, + { url = "https://files.pythonhosted.org/packages/a8/0b/af7ac2ef8dd41e3da1a40dda6305c23b9a03e13ba975ec916357b50f8575/greenlet-3.5.3-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f73857adb8fee13fa56c172bd11262f888c0c648f9fea113e777bb2c7904a81", size = 670419, upload-time = "2026-06-26T19:10:12.293Z" }, + { url = "https://files.pythonhosted.org/packages/51/1e/1d51640cacbfc455dbe9f9a9f594c49e4e244f63b9971a2f4764e46cc53d/greenlet-3.5.3-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:232fec92e823addaf02d9472cf7381e24a1d046a6ced1103c5caa4c21b9dfc1d", size = 668787, upload-time = "2026-06-26T18:32:24.298Z" }, + { url = "https://files.pythonhosted.org/packages/21/66/4030d5b0b5894500023f003bb054d9bb354dfbd1e186c3a296759172f5f5/greenlet-3.5.3-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:2421c3564da9429d5586d46ca31ebb26516b5498a802cf65c041a8e8a8980d34", size = 1626305, upload-time = "2026-06-26T19:09:08.281Z" }, + { url = "https://files.pythonhosted.org/packages/0e/50/5221371c7550108dfa3c378debc41d032aa9c78e89abb01d8011cfc93289/greenlet-3.5.3-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:e0f0d160f0b2e558e6c75f7930967183255dc9735e5f5b8cae58ee09c9576d8b", size = 1688631, upload-time = "2026-06-26T18:31:51.278Z" }, + { url = "https://files.pythonhosted.org/packages/68/5d/00d469daae3c65d2bf620b10eee82eb022127d483c6bc8c69fae6f3fbf17/greenlet-3.5.3-cp315-cp315-win_amd64.whl", hash = "sha256:dd99329bbc15ca78dcc583dba05d0b1b0bae01ab6c2174989f5aaee3e41ac930", size = 241027, upload-time = "2026-06-26T18:22:38.203Z" }, + { url = "https://files.pythonhosted.org/packages/e7/e8/883785b44c5780ed71e83d3e4437e710470be17a2e181e8b601e2da0dc4a/greenlet-3.5.3-cp315-cp315-win_arm64.whl", hash = "sha256:499fef2acede88c1864a57bb586b4bf533c81e1b82df7ab93451cdb47dfec227", size = 240085, upload-time = "2026-06-26T18:23:54.217Z" }, + { url = "https://files.pythonhosted.org/packages/1c/da/4f4a8450962fad137c1c8981a3f1b8919d06c829993d4d476f9c525d5173/greenlet-3.5.3-cp315-cp315t-macosx_11_0_universal2.whl", hash = "sha256:176bc16a721fa5fc294d70b87b4dfa5fbdd251b3da5d5372735ecef9bd7d6d0c", size = 297221, upload-time = "2026-06-26T18:23:27.176Z" }, + { url = "https://files.pythonhosted.org/packages/57/66/b3bfae3e220a9b63ea539a0eea681800c69ab1aada757eae8789f183e7ce/greenlet-3.5.3-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:629b614d2b786e89c50440e246f33eea78f58a962d0bdbbcc809e6d13605903f", size = 657221, upload-time = "2026-06-26T19:07:16.973Z" }, + { url = "https://files.pythonhosted.org/packages/7b/81/b6d4d73a709684fc77e7fa034d7c2fe82cffa9fc920fadcaa659c2626213/greenlet-3.5.3-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2b2e857ae16f5f72142edf75f9f176fe7526ba19a2841df1420516f83831c9f2", size = 663226, upload-time = "2026-06-26T19:10:13.723Z" }, + { url = "https://files.pythonhosted.org/packages/f5/07/e210b02b589f16e74ff48b730690e4a34ffe984219fce4f3c1a0e7ec8545/greenlet-3.5.3-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e515757e2e36bcbf1fad09a46e1557e8b1ae1797d4b44d09da7deed88ad28608", size = 660802, upload-time = "2026-06-26T18:32:26.081Z" }, + { url = "https://files.pythonhosted.org/packages/eb/2e/5303eb3fa06bca089060f479707182a93e360683bc252acf846c3090d34e/greenlet-3.5.3-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:b363d46ed1ea431825fdb01471bb024fc08399bad1572a616e853c7684415adb", size = 1622157, upload-time = "2026-06-26T19:09:09.527Z" }, + { url = "https://files.pythonhosted.org/packages/54/70/50de47a488f14df260b50ae34fb5d56016e308b098eab02c878b5223c26a/greenlet-3.5.3-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:e44da2f5bbdaabaf7d80b73dbb430c7035771e9f244e3c8b769715c9d8fa0a16", size = 1681159, upload-time = "2026-06-26T18:31:52.986Z" }, + { url = "https://files.pythonhosted.org/packages/a7/13/1055e1dda7882073eda533e2b96c62e55bbd2db7fda6d5ece992febc7071/greenlet-3.5.3-cp315-cp315t-win_amd64.whl", hash = "sha256:8ff8bed3e3baa20a3ea261ce00526f1898ad4801d4886fd2220580ee0ad8fadf", size = 244007, upload-time = "2026-06-26T18:22:04.353Z" }, + { url = "https://files.pythonhosted.org/packages/b4/0d/ca7d15afbdc397e3401134c9e1800d51d12b829661786187a4ad08fe484f/greenlet-3.5.3-cp315-cp315t-win_arm64.whl", hash = "sha256:b7068bd09f761f3f5b4d214c2bed063186b2a86148c740b3873e3f56d79bac31", size = 242586, upload-time = "2026-06-26T18:23:37.93Z" }, ] [[package]] @@ -1084,7 +1073,8 @@ name = "h5py" version = "3.16.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/db/33/acd0ce6863b6c0d7735007df01815403f5589a21ff8c2e1ee2587a38f548/h5py-3.16.0.tar.gz", hash = "sha256:a0dbaad796840ccaa67a4c144a0d0c8080073c34c76d5a6941d6818678ef2738", size = 446526, upload-time = "2026-03-06T13:49:08.07Z" } wheels = [ @@ -1132,18 +1122,19 @@ wheels = [ [[package]] name = "hdf5plugin" -version = "6.0.0" +version = "7.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "h5py" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/4f/9130151e3aa475b3e4e9a611bf608107fe5c72d277d74c4cf36f164b7c81/hdf5plugin-6.0.0.tar.gz", hash = "sha256:847ed9e96b451367a110f0ba64a3b260d38d64bbf3f25751858d3b56e094cfe0", size = 66372085, upload-time = "2025-10-08T18:16:28.423Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/64/0fc6b68e5bc671e7b81d67b930fbed3a4e8a2a92dc4af0f7282b2a2ff988/hdf5plugin-7.0.0.tar.gz", hash = "sha256:e6e6b1f8b0c4d2ca87e616ddc31d08330b36207e466357040f95269e5e0401c8", size = 68284761, upload-time = "2026-06-25T20:59:40.102Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/13/15017f6210bfea843316d62f0f121e364e17bb129444ed803a256a213036/hdf5plugin-6.0.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:a59fbd5d4290a8a5334d82ccb4c6b9bfc7aaf586de7fedb88762e8601bc05fd4", size = 13339413, upload-time = "2025-10-08T18:16:10.656Z" }, - { url = "https://files.pythonhosted.org/packages/40/bf/d1f3765fb879820d7331e30e860b684f5b78d3ec17324e8f54130cbe560b/hdf5plugin-6.0.0-py3-none-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d301f4b9295872bacf277c70628d4c5e965ee47db762d8fde2d4849f201b9897", size = 42858563, upload-time = "2025-10-08T18:16:14.106Z" }, - { url = "https://files.pythonhosted.org/packages/0a/67/37d0b84fbbf26bf0d6a99a8f98bcd82bb6d437dc8cabee259fb3d7506ec7/hdf5plugin-6.0.0-py3-none-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:78b082ea355fe46bf5b396024de1fb662a1aaf9a5e11861ad61a5a2a6316d59d", size = 45126124, upload-time = "2025-10-08T18:16:17.992Z" }, - { url = "https://files.pythonhosted.org/packages/ed/2f/1046d464ad1db29a4f6c70ba4e19b39baa8a6542c719eaa4e765108f07f1/hdf5plugin-6.0.0-py3-none-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:79e0524d18ddc41c0cf2e1bb2e529d4e154c286f6a1bd85f3d44019d2a17574a", size = 44857273, upload-time = "2025-10-08T18:16:22.007Z" }, - { url = "https://files.pythonhosted.org/packages/61/b3/75478bdfee85533777de4204373f563aa7a1074355300743c3aedc33cac5/hdf5plugin-6.0.0-py3-none-win_amd64.whl", hash = "sha256:99866f90be1ceac5519e6e038669564be326c233618d59ba1f38c9dd8c32099e", size = 3379316, upload-time = "2025-10-08T18:16:25.007Z" }, + { url = "https://files.pythonhosted.org/packages/03/5a/00d0f491d420d491b5134ee044cd8ead5103382d88f4c8aee623258a6348/hdf5plugin-7.0.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:e0ff0a81e6319575ffc8bf230b8df43f3f19f6fe96843e113e04c5ba9f7f3141", size = 6941923, upload-time = "2026-06-25T20:59:24.517Z" }, + { url = "https://files.pythonhosted.org/packages/96/ad/b28f4102e619c262b29bfcd13ccf6799e26f9ff113d2fdce19050ae29448/hdf5plugin-7.0.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:97ea4ff6114223c5e8ccce7e23cc6cd398b58c02f4e988e967aeea914fcb8030", size = 6259223, upload-time = "2026-06-25T20:59:26.246Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f4/67263173ff61c49800eec4f16b4d84e6a39170be8d4871d4eb0d540b71ee/hdf5plugin-7.0.0-py3-none-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f3ba9f4e2a370340b45e7042d1b1b1577ab259c1ddf00956264836fa33052ef", size = 42789778, upload-time = "2026-06-25T20:59:28.438Z" }, + { url = "https://files.pythonhosted.org/packages/75/d2/66673e2d0ef8499d08dd7061caa194e4ddec12df73ab512431c60538f675/hdf5plugin-7.0.0-py3-none-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1a4cb2207ec3ac538fc728e4596e9e49aed6c4487a641071fb370c04a361e7bf", size = 45295544, upload-time = "2026-06-25T20:59:31.765Z" }, + { url = "https://files.pythonhosted.org/packages/d7/0b/855e50e27eab8338c71c3157672c065cd2a2ab38887b3ea4bf128cbd89a1/hdf5plugin-7.0.0-py3-none-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4ad4ab0d3367699d132e61b1cc382a0c640a7dba56536e5105508205ebbe8762", size = 45012013, upload-time = "2026-06-25T20:59:35.029Z" }, + { url = "https://files.pythonhosted.org/packages/4e/14/32cf2aae083c74b95678875e11d25d0cb9e51a33d27f4218eb9aeacfcd70/hdf5plugin-7.0.0-py3-none-win_amd64.whl", hash = "sha256:2e052af8d7848e8bac92646584617503a08bb9b466cfa810a49ecd93e89b7ffa", size = 3523827, upload-time = "2026-06-25T20:59:37.758Z" }, ] [[package]] @@ -1197,7 +1188,8 @@ name = "imageio" version = "2.37.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "pillow" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b1/84/93bcd1300216ea50811cee96873b84a1bebf8d0489ffaf7f2a3756bab866/imageio-2.37.3.tar.gz", hash = "sha256:bbb37efbfc4c400fcd534b367b91fcd66d5da639aaa138034431a1c5e0a41451", size = 389673, upload-time = "2026-03-09T11:31:12.573Z" } @@ -1210,7 +1202,7 @@ name = "importlib-metadata" version = "9.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "zipp", marker = "python_full_version < '3.12'" }, + { name = "zipp" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a9/01/15bb152d77b21318514a96f43af312635eb2500c96b55398d020c93d86ea/importlib_metadata-9.0.0.tar.gz", hash = "sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc", size = 56405, upload-time = "2026-03-20T06:42:56.999Z" } wheels = [ @@ -1252,7 +1244,7 @@ wheels = [ [[package]] name = "ipython" -version = "9.14.1" +version = "9.15.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -1262,15 +1254,15 @@ dependencies = [ { name = "matplotlib-inline" }, { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, { name = "prompt-toolkit" }, - { name = "psutil", marker = "sys_platform != 'emscripten'" }, + { name = "psutil", marker = "sys_platform != 'cygwin' and sys_platform != 'emscripten'" }, { name = "pygments" }, { name = "stack-data" }, { name = "traitlets" }, { name = "typing-extensions", marker = "python_full_version < '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e2/23/3a27530575643c8bb7bfc757a28e2e7ef80092afbf59a2bc5716320b6602/ipython-9.14.1.tar.gz", hash = "sha256:f913bf74df06d458e46ced84ca506c23797590d594b236fe60b14df213291e7b", size = 4433457, upload-time = "2026-06-05T08:12:34.921Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/59/165d3b4d75cc34add3122c4417ecb229085140ac573103c223cd01dde96f/ipython-9.15.0.tar.gz", hash = "sha256:da2819ce2aa83135257df830660b1176d986c3d2876db24df01974fa955b2756", size = 4442580, upload-time = "2026-06-26T11:03:35.913Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/22/58818a63eaf8982b67632b1bc20585c811611b15a8da19d6012323dc76a5/ipython-9.14.1-py3-none-any.whl", hash = "sha256:5d4a9ecaa3b10e6e5f269dd0948bdb58ca9cb851899cd23e07c320d3eb11613c", size = 627770, upload-time = "2026-06-05T08:12:33.045Z" }, + { url = "https://files.pythonhosted.org/packages/40/3a/948263ca3b9d65bb2b1b0c521b3a49fad5d59ada58724bd87d2bd5ff3f36/ipython-9.15.0-py3-none-any.whl", hash = "sha256:515ad9c3cdf0c932a5a9f6245419e8aba706b7bd03c3e1d3a1c83d9351d6aa6e", size = 630895, upload-time = "2026-06-26T11:03:33.809Z" }, ] [[package]] @@ -1339,11 +1331,11 @@ wheels = [ [[package]] name = "json5" -version = "0.14.0" +version = "0.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9c/4b/6f8906aaf67d501e259b0adab4d312945bb7211e8b8d4dcc77c92320edaa/json5-0.14.0.tar.gz", hash = "sha256:b3f492fad9f6cdbced8b7d40b28b9b1c9701c5f561bef0d33b81c2ff433fefcb", size = 52656, upload-time = "2026-03-27T22:50:48.108Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/7d/05c46a96a78147ae3bf99c2f4169ce144a70220b8d6fcd56f6ec368b8ce9/json5-0.15.0.tar.gz", hash = "sha256:7424d1f1eb1d56da6e3d70643f53619862b4ce81440bdb8ecfd6f875e5ba4a71", size = 53278, upload-time = "2026-06-19T20:08:27.716Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/42/cf027b4ac873b076189d935b135397675dac80cb29acb13e1ab86ad6c631/json5-0.14.0-py3-none-any.whl", hash = "sha256:56cf861bab076b1178eb8c92e1311d273a9b9acea2ccc82c276abf839ebaef3a", size = 36271, upload-time = "2026-03-27T22:50:47.073Z" }, + { url = "https://files.pythonhosted.org/packages/eb/be/59527c99478aade6bb33a68d72e6e18dd4e6ff6eacfc7d01bdb15bc76912/json5-0.15.0-py3-none-any.whl", hash = "sha256:56636a30c0e8a4665fe2179c0212f32eae3796dea89ea6f649b9436ecdb39618", size = 36570, upload-time = "2026-06-19T20:08:26.748Z" }, ] [[package]] @@ -1395,6 +1387,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, ] +[[package]] +name = "jupyter-builder" +version = "1.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-core" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/45/d0df8b43c10a61529c0f4a8af5e19ebe108f0c3af8f57e0fc358969907af/jupyter_builder-1.0.2.tar.gz", hash = "sha256:6155d78a5325010532a6419ffcba89eac643fd1aa56ea83115e661924d6f6aab", size = 968638, upload-time = "2026-06-12T02:33:25.767Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/b6/c418e0b3256f67c04933566b80bfce947350682db92c4b786a8653db32d6/jupyter_builder-1.0.2-py3-none-any.whl", hash = "sha256:b024f65d36e1d530542db597b00dd513261aa59842e0d0fbbb1015a9f1935e9c", size = 910789, upload-time = "2026-06-12T02:33:23.317Z" }, +] + [[package]] name = "jupyter-client" version = "8.9.1" @@ -1458,7 +1463,7 @@ wheels = [ [[package]] name = "jupyter-server" -version = "2.19.0" +version = "2.20.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1481,9 +1486,9 @@ dependencies = [ { name = "traitlets" }, { name = "websocket-client" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/76/a0/eb3c511f54df7b54ca5fc7bff3f4d2277d69052d6a7f521643dfed5279d6/jupyter_server-2.19.0.tar.gz", hash = "sha256:1731236bc32b680223e1ceb9d68209a845203475012ef68773a81434b46a31a7", size = 754561, upload-time = "2026-05-29T11:21:26.057Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/dc/db3a582633170186f8c8b31298d7eb26ad0eb031a1f53476c258b64eed05/jupyter_server-2.20.0.tar.gz", hash = "sha256:b5778ba337d8015a3dc2b80803ecdd5ac18d3797fddf61a50ea5fb472b4ebe14", size = 756523, upload-time = "2026-06-17T12:09:09.435Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/78/d2881e68894cecdcd05912a9c585cfb776ef1fb38b62c8dba98f12ab3adc/jupyter_server-2.19.0-py3-none-any.whl", hash = "sha256:cb76591b76d7093584c2ad2ae72ac3d58614a4b597507a1bb04e1f9f683cf9ea", size = 392244, upload-time = "2026-05-29T11:21:23.871Z" }, + { url = "https://files.pythonhosted.org/packages/f3/71/8c002223e873a870f5c41dc69b0a7c922301123e4a31d5d01ecb700aef77/jupyter_server-2.20.0-py3-none-any.whl", hash = "sha256:c3b67c93c471e947c18b5026f04f21614218adb706df8f48227d3ee8e0a7cdcc", size = 393143, upload-time = "2026-06-17T12:09:07.234Z" }, ] [[package]] @@ -1501,26 +1506,26 @@ wheels = [ [[package]] name = "jupyterlab" -version = "4.5.8" +version = "4.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "async-lru" }, { name = "httpx" }, { name = "ipykernel" }, { name = "jinja2" }, + { name = "jupyter-builder" }, { name = "jupyter-core" }, { name = "jupyter-lsp" }, { name = "jupyter-server" }, { name = "jupyterlab-server" }, { name = "notebook-shim" }, { name = "packaging" }, - { name = "setuptools" }, { name = "tornado" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0e/74/089613e6099e851a6130816f2df592c839d8565f8746a701edada05a33e4/jupyterlab-4.5.8.tar.gz", hash = "sha256:af54d7242cc689a1e6c3ad213cc9b6d9781787d9ec67c52ec9a8f4707088cadd", size = 23994076, upload-time = "2026-06-04T12:32:12.906Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/2a/d6af53bfd45a43a5bfe7e40ba47ee7a8921a807daf4bb708e3a295bbb54d/jupyterlab-4.6.1.tar.gz", hash = "sha256:75315982ed28427edaa62bb85eadb5105e4043a757643c910efd787fe6ed0837", size = 28179125, upload-time = "2026-06-29T12:48:45.402Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/d1/56a400100559cbf154a23cd29989261941ae5c9f743898fc10e8a5508b7c/jupyterlab-4.5.8-py3-none-any.whl", hash = "sha256:7d514c856d0d607601ec7692374da4f26e2aaf3b6e7cd363136b422a50588d6c", size = 12449443, upload-time = "2026-06-04T12:32:08.442Z" }, + { url = "https://files.pythonhosted.org/packages/5a/81/90ac6cc31d248e83a0d1eab343a5e6e68bc783d3f74fbe61640f42a61da4/jupyterlab-4.6.1-py3-none-any.whl", hash = "sha256:85a58546c831f3dce6cf919468c26874c9065e99c42279fb4abb8e1b552a98bb", size = 17164660, upload-time = "2026-06-29T12:48:41.21Z" }, ] [[package]] @@ -1812,7 +1817,8 @@ dependencies = [ { name = "cycler" }, { name = "fonttools" }, { name = "kiwisolver" }, - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "packaging" }, { name = "pillow" }, { name = "pyparsing" }, @@ -1881,11 +1887,11 @@ wheels = [ [[package]] name = "mistune" -version = "3.2.1" +version = "3.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/84/620cc3f7e3adf6f5067e10f4dbae71295d8f9e16d5d3f9ef97c40f2f592c/mistune-3.2.1.tar.gz", hash = "sha256:7c8e5501d38bac1582e067e46c8343f17d57ea1aaa735823f3aba1fd59c88a28", size = 98003, upload-time = "2026-05-03T14:33:22.312Z" } +sdist = { url = "https://files.pythonhosted.org/packages/04/5f/007786743f962224423753b78f7d7acb0f2ade46d1604f2e0fa2bedf9020/mistune-3.3.2.tar.gz", hash = "sha256:e12ee4f1e74336e91aa1141e35f913b337c40bdf7c0cc49f21fb853a27e8b62f", size = 111284, upload-time = "2026-06-23T00:29:28.568Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/7f/a946aa4f8752b37102b41e64dca18a1976ac705c3a0d1dfe74d820a02552/mistune-3.2.1-py3-none-any.whl", hash = "sha256:78cdb0ba5e938053ccf63651b352508d2efa9411dc8810bfb05f2dc5140c0048", size = 53749, upload-time = "2026-05-03T14:33:20.551Z" }, + { url = "https://files.pythonhosted.org/packages/44/43/894c2cbbcbdf53b57d1257a249811abe2ee9ab7ef76af301b40f1c054533/mistune-3.3.2-py3-none-any.whl", hash = "sha256:a678a56387d487db7368ede4647cb2ba1deff22ce61f92343e4ebe0ddfce4f2d", size = 61554, upload-time = "2026-06-23T00:29:27.088Z" }, ] [[package]] @@ -1996,7 +2002,8 @@ name = "numcodecs" version = "0.16.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/44/bd/8a391e7c356366224734efd24da929cc4796fff468bfb179fe1af6548535/numcodecs-0.16.5.tar.gz", hash = "sha256:0d0fb60852f84c0bd9543cc4d2ab9eefd37fc8efcc410acd4777e62a1d300318", size = 6276387, upload-time = "2025-11-21T02:49:48.986Z" } @@ -2027,6 +2034,9 @@ wheels = [ name = "numpy" version = "2.4.6" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.12'", +] sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" }, @@ -2102,6 +2112,61 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" }, ] +[[package]] +name = "numpy" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version >= '3.12' and python_full_version < '3.14'", +] +sdist = { url = "https://files.pythonhosted.org/packages/e7/05/3d27272d30698dc0ecb7fdfaa41ad70303b444f81722bb99bce1d818638a/numpy-2.5.0.tar.gz", hash = "sha256:5a129578019311b6e56bdd714250f19b518f7dceeeb8d1af5490f4942d3f891c", size = 20652461, upload-time = "2026-06-21T20:57:51.95Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/0a/11486d02add7b1384dff7374d124b1cfbb0ee864dcc9f6a2c0380638cf84/numpy-2.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:489780423903667933b4ed6197b6ec3b75ea5dd17d1d8f0f38d798feb6921561", size = 16789987, upload-time = "2026-06-21T20:56:16.657Z" }, + { url = "https://files.pythonhosted.org/packages/55/b2/285f48640a181947b4587a3766d21ec1eaa7fea833d4b49957e09da467a2/numpy-2.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ece55976ced6bca95a03ae2839e2e5ccffe8eb6a3e7022415645eb154a81e4e6", size = 11760322, upload-time = "2026-06-21T20:56:19.813Z" }, + { url = "https://files.pythonhosted.org/packages/dd/67/b032db1eb03ca30d16eda3b0c22aaa615338b9263c2fd559d0f29451aca4/numpy-2.5.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:c83b664b0e6eee9594fa920cf0639d8af796606d3fad6cc70180c87e4b97c7be", size = 5319605, upload-time = "2026-06-21T20:56:22.173Z" }, + { url = "https://files.pythonhosted.org/packages/b9/83/03fc7300c7c6b6c84c487b1dc80d322817b95fbd1f4dd57a85e23b7198de/numpy-2.5.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:bf80333980bf37f523341ddd72c783f39d6829ec7736b9eb99086388a2d52cc2", size = 6653628, upload-time = "2026-06-21T20:56:23.914Z" }, + { url = "https://files.pythonhosted.org/packages/82/49/2ec21730bc63ccfda829323f7040a8ed4715b3852ce658689cf74ee96a8c/numpy-2.5.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1a4874217b36d5ac8fc876f52e39df56f8182c88463e9e2dceabf7ca8b7efb8", size = 15153691, upload-time = "2026-06-21T20:56:25.631Z" }, + { url = "https://files.pythonhosted.org/packages/bb/6b/f4a3d0637692c49da8ef99d72d52526f92e0a8d6ac4f0ca9f31441b9d9ea/numpy-2.5.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aaa760137137e8d3c920d27927748215b56014f92667dc9b6c27dfc61249255a", size = 16660066, upload-time = "2026-06-21T20:56:28.009Z" }, + { url = "https://files.pythonhosted.org/packages/3a/2f/c354ec86d1f3f5c19649463b0d39652e160736e5b0a4cd18dff0576715c4/numpy-2.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7174ce8265fc7f7417d171c9ea8fe905220748893ea67a2a7abe726ec331c4b0", size = 16514638, upload-time = "2026-06-21T20:56:30.26Z" }, + { url = "https://files.pythonhosted.org/packages/06/34/43efdcb319988648580f93c11f1ae82cf7e2faa74925e98e454ae3aa95f8/numpy-2.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b8c3daaf99de52415d20b42f8e8155c78642cb04207d02f9d317a0dcf1b3fb54", size = 18419647, upload-time = "2026-06-21T20:56:32.41Z" }, + { url = "https://files.pythonhosted.org/packages/71/e2/f5d1676b1d7fb682eb5e9a1641e7ebd2414b3216c370661d1029778908b4/numpy-2.5.0-cp312-cp312-win32.whl", hash = "sha256:6206db0af545d73d068add6d992279145f158428d1da6cc49adc4b630c5d6ee5", size = 6056688, upload-time = "2026-06-21T20:56:34.657Z" }, + { url = "https://files.pythonhosted.org/packages/8f/7c/48f115d1c58a34032facebcd51fdf2d02df2c51d4a46a81dd1197bb2ea6b/numpy-2.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:6f2d6873e2940c860a309d21e25b1e69af6aaffdd80aa056b04c16380db1c4f2", size = 12419237, upload-time = "2026-06-21T20:56:36.24Z" }, + { url = "https://files.pythonhosted.org/packages/86/26/2e0882f4044d1b1a1b63e875151fb2393389032022a8b7f5657a7996d3b2/numpy-2.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:a55e1eb2bca2cfd17a16b213c99dfc8502d47b0d494224d2122277d0400935ca", size = 10339912, upload-time = "2026-06-21T20:56:38.733Z" }, + { url = "https://files.pythonhosted.org/packages/8a/33/07675aaad7f26ea013d5e884d9a0d784b79c6bd7566c333f5a52fa3c610b/numpy-2.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:520e6b8be0a4b65840ac8090d4f51cef4bed66e2b0894d5a520f099adc24a9b2", size = 16784890, upload-time = "2026-06-21T20:56:40.799Z" }, + { url = "https://files.pythonhosted.org/packages/85/4b/953118a730ee3b35e28645e0eb4cf9beec5bdbb954e1ac2f5fcefba6bbc3/numpy-2.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:146b81cdd3967fdb6beca8ba25f00c58741d8f3cbd797f55af0fbe0bfec3469c", size = 11754584, upload-time = "2026-06-21T20:56:43.094Z" }, + { url = "https://files.pythonhosted.org/packages/44/9b/56dd530c367c74ae17411027cea4135ca57e1e0583bf5594cee18bd83217/numpy-2.5.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:126b88d95e8ff9b00c9e717aa540469f21d6180162f84c0caec51b16215d49cd", size = 5313904, upload-time = "2026-06-21T20:56:45.503Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b0/bcd672edad27ecca7da1f7bb0ce72cd1706a4f2d79ae94990afc97c13e1c/numpy-2.5.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d4313cef1594c5ce46c31b6e54e918338f63f16ee9322304e8c9114d6d81c8bd", size = 6648504, upload-time = "2026-06-21T20:56:47.567Z" }, + { url = "https://files.pythonhosted.org/packages/80/9e/15cdfcbd30a1544a46c9e487a00df331c4672450216538705a9e51fa6710/numpy-2.5.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:750fb097caf26fa878746d9d119f6f9da12dedcbff1eea966c3e3447647c4a9e", size = 15150086, upload-time = "2026-06-21T20:56:49.352Z" }, + { url = "https://files.pythonhosted.org/packages/32/4e/8d7656ccaab3e81e97258b8a9bc5f0c8502513a92fb4ceb0a2cbfebc17bf/numpy-2.5.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3893adc2dc7c0412ba76777db55a049215d99c9aa3113003be8f49f4f1290ab9", size = 16647250, upload-time = "2026-06-21T20:56:51.542Z" }, + { url = "https://files.pythonhosted.org/packages/3c/81/97060281b602ed07f21b12f4ec409eac1f75a2f91fbc829ed8b2becf3ad4/numpy-2.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:835e454dd99b238cdc5a3f63bce2371296f5ebc53ca1e0f8e6ddbb6d92a29aab", size = 16512864, upload-time = "2026-06-21T20:56:55.401Z" }, + { url = "https://files.pythonhosted.org/packages/33/ab/4496208146911f8d8ddb54f68a972aafa6c8d44babcb2ea03b0e5cc87c9d/numpy-2.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f9836778081a0a3c02a6a21493f3e9f5b311f8d2541934f31f05583dc999ea4", size = 18408407, upload-time = "2026-06-21T20:56:57.75Z" }, + { url = "https://files.pythonhosted.org/packages/d4/9f/a4df67c181e4ee8b467aa3332dc2db10fd5c515136831302f3ca48bc0a01/numpy-2.5.0-cp313-cp313-win32.whl", hash = "sha256:0b525be4744b60bb0557ac872d53ef07d085b5f39622bc579c98d3809d05b988", size = 6054431, upload-time = "2026-06-21T20:57:00.016Z" }, + { url = "https://files.pythonhosted.org/packages/30/53/491e1c47c55b62ccc6a63c1c5b8635c73fc2258dddeb9bda27cae4a0ae96/numpy-2.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:44353e2878930039db472b99dc353d749826e4010bd4d2a7f835e94a97a5c748", size = 12414420, upload-time = "2026-06-21T20:57:01.815Z" }, + { url = "https://files.pythonhosted.org/packages/eb/4a/25c2906f541e9d9f4c5769764db732e6627be91a13f4724fa10634d77db4/numpy-2.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:48f54b00711f83a5f796b70c518e8c2b3c5848dda03a54911f23eb68519b9b60", size = 10339533, upload-time = "2026-06-21T20:57:03.961Z" }, + { url = "https://files.pythonhosted.org/packages/86/ad/abc44aaceaf7b17ee1edde2bbb4458da591bc79574cffff50c4bb35f00d1/numpy-2.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f27582c55ba4c750b7c58c8faf021d2cd9324a662b466229db8a417b41368af9", size = 16783807, upload-time = "2026-06-21T20:57:06.253Z" }, + { url = "https://files.pythonhosted.org/packages/5d/39/b72e168daf9c00fb20c9fc996d00437ccecdef3102387775d29d7a62576d/numpy-2.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:28e7137057d551e4a83c4ae414e3451f50568409db7569aacc7f9811ee06a446", size = 11765215, upload-time = "2026-06-21T20:57:08.547Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a0/8400a9c0e3625182347593f5e1f57da9a617a534794805c8df5518154ddc/numpy-2.5.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:e1da54b53e75cd9fcfc23efcc7edab2c6aecf97b6037566d8a0fe804af8ec57c", size = 5324493, upload-time = "2026-06-21T20:57:11.012Z" }, + { url = "https://files.pythonhosted.org/packages/f6/8c/0d104deaa0401c93395a629ec902891618a2eff76d19229139cb5a887bfc/numpy-2.5.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:694d8f74e156f7fd01179f1aa8faa2f648ab6ae0f70b6c3fe57a03249aea2303", size = 6645211, upload-time = "2026-06-21T20:57:12.919Z" }, + { url = "https://files.pythonhosted.org/packages/6a/d9/4a4a628c812750363786afc3d33492709a5cd64b215469c16b0f6c7bb811/numpy-2.5.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a7569a7b53c77716f036bb28cb1c91f166a26ec7d9502cd1e4bdfe502fdec22", size = 15166004, upload-time = "2026-06-21T20:57:14.717Z" }, + { url = "https://files.pythonhosted.org/packages/a0/5e/2a902317d7fc4aa93236e80c932662dadfc459b323d758329e01775125e1/numpy-2.5.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:39a0433bd4086ebd462960cf375e19195bb07b53dc1d87dd5fcf47ad78576f03", size = 16650797, upload-time = "2026-06-21T20:57:16.906Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a0/a0090e6329f4ca5992c07847bb579c5259a19953dc57255bb08793142ffb/numpy-2.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:929f0c79ac38bcbd7154fe631dc907abfeddbcc5027a896bd1f7767323271e7a", size = 16524647, upload-time = "2026-06-21T20:57:19.165Z" }, + { url = "https://files.pythonhosted.org/packages/5e/7d/6caf27734c42b65837e7461ed0dbbd6b6fc835060c9714ec59d673bb383a/numpy-2.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cc4f247a47bbf070bfd70be53ccdcf47b800af563535e7bbe172322197c30e21", size = 18411841, upload-time = "2026-06-21T20:57:21.638Z" }, + { url = "https://files.pythonhosted.org/packages/13/dc/26edadbd812536769a82c2e9e002234e33feb5da43061d47a044f6d309b7/numpy-2.5.0-cp314-cp314-win32.whl", hash = "sha256:5dc71423499fab3f46f7a7201155ade1669ea101f2f429d332df9e72f8161731", size = 6106361, upload-time = "2026-06-21T20:57:23.844Z" }, + { url = "https://files.pythonhosted.org/packages/f2/9e/4dd1459282229a72d92dece2ae9138e5cac94a72263a7ceb48f37434c925/numpy-2.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:ebb81d9d5443e0309d6c54894c3fbed74ad7da0714352a67b6d773cd189eae73", size = 12551749, upload-time = "2026-06-21T20:57:25.945Z" }, + { url = "https://files.pythonhosted.org/packages/05/a7/6bc6384c080b86c7f6c85c5bc5b540b24f4f679cd144791d99574e90d462/numpy-2.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:3b94d0d0deceebfad3e67ae5c0e5eb87371e8f7a0581cd04a779928c2450cf1e", size = 10617072, upload-time = "2026-06-21T20:57:28.175Z" }, + { url = "https://files.pythonhosted.org/packages/86/6b/4a2b71d66ada5608ae02b63f150dfad520f6940721cb7f029ad270befc0e/numpy-2.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:22f3d43e362d650bc39db1f17851302874a148ca95ba6981c1dfb5fa6862f35b", size = 11881067, upload-time = "2026-06-21T20:57:30.104Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b2/d365eb40a20efb49d67e9feb90494ed8511282ee1f5fa16006675c65397d/numpy-2.5.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:243563efb4cd7528a264567e9fd206c87826457322521d06206a00bfa316c927", size = 5440290, upload-time = "2026-06-21T20:57:32.193Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5e/e9c03188de5f9b767e46a8fe988bcfd3efad066a4a3fda8b9cb11a93f895/numpy-2.5.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:84881d825ca75249b189bbee875fcfe3238aa5c479e6100893cda566e8e86826", size = 6748371, upload-time = "2026-06-21T20:57:33.933Z" }, + { url = "https://files.pythonhosted.org/packages/fd/1d/68c186a38a5027bae2c4ddd5ea681fdaf8b4d30fb7301def6d8ad270390f/numpy-2.5.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cda12aa4779d42b8771180aba759c96f527d43446d8f380ab59e2b35e8489efd", size = 15214643, upload-time = "2026-06-21T20:57:35.677Z" }, + { url = "https://files.pythonhosted.org/packages/8c/67/73f67b7c7e20635baae9c4c3ead4ae7326a005900297a6110971abd62eb5/numpy-2.5.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c0121101093d2bd74981b10f8837d78e794a8ff57834eb27179f49e1ba11ac6", size = 16690128, upload-time = "2026-06-21T20:57:38.159Z" }, + { url = "https://files.pythonhosted.org/packages/eb/05/d4c1fb0c46d02a27d6b2b8b319a78c90937acec8631c1641874670b31e6f/numpy-2.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d371c92cfa09da00022f501ab67fafaea813d752eb30ac44336d45b1e5b0268a", size = 16577902, upload-time = "2026-06-21T20:57:40.447Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1d/771c797d50fa26e4888989cccf1d50ee51f530d4e455ad2692dcb64fa711/numpy-2.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9990713e9c38154c6861e7547f1e3fc7a87e75ff09bab24ef1cc81d81c2835e9", size = 18452814, upload-time = "2026-06-21T20:57:42.875Z" }, + { url = "https://files.pythonhosted.org/packages/e8/46/52fc0d2a68d7643f0f149eeea5a5d8ea2a3507056ac8afa83c9212606e8b/numpy-2.5.0-cp314-cp314t-win32.whl", hash = "sha256:edadfbd4794b1086c0d822f81863e8a68fc129d132fd0bb9e31e955d7fbbbdb7", size = 6253168, upload-time = "2026-06-21T20:57:45.101Z" }, + { url = "https://files.pythonhosted.org/packages/2a/be/6c8d1118b5f13b2881dc095d5b345de19c6638b8959c17409b6eff84c8aa/numpy-2.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f7e5fa4382967ae6548bd2f174219afb908e294b0d5f625af01166edd5f7d9aa", size = 12736286, upload-time = "2026-06-21T20:57:46.935Z" }, + { url = "https://files.pythonhosted.org/packages/fd/6a/d3a169aaf8536cf228d56a09e04bcb713a2fe4410d4e2105b9419b5a9c89/numpy-2.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:016623417bb330d719d579daf2d6b9a01ddc52e41a9ed61a47f39fde46dcd865", size = 10686451, upload-time = "2026-06-21T20:57:49.313Z" }, +] + [[package]] name = "nvidia-cublas" version = "13.1.1.3" @@ -2261,7 +2326,8 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alembic" }, { name = "colorlog" }, - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "packaging" }, { name = "pyyaml" }, { name = "sqlalchemy" }, @@ -2609,7 +2675,7 @@ wheels = [ [[package]] name = "pytest" -version = "9.1.0" +version = "9.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -2618,9 +2684,9 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/84/0e/b5858858d74958632c49b72cb25a3976ff9f632397626715be71c89d3971/pytest-9.1.0.tar.gz", hash = "sha256:41dd9148c08072446394cefd3d79701701335a9f4cae69ba92e39f6c7f5c061c", size = 1634181, upload-time = "2026-06-13T18:52:45.983Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/5a/ba30a81239b909821b3153e303e7def45178bf353da4f72380e6c5e8793b/pytest-9.1.0-py3-none-any.whl", hash = "sha256:8ebb0e7888bdf2bdfc602ec51f8f62d50200af37356c74e503c79a94f5c81f32", size = 386453, upload-time = "2026-06-13T18:52:44.045Z" }, + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, ] [[package]] @@ -2835,11 +2901,13 @@ dependencies = [ { name = "h5py" }, { name = "hdf5plugin" }, { name = "matplotlib" }, - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "optuna" }, { name = "rosettasciio" }, { name = "scikit-image" }, - { name = "scipy" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "tensorboard" }, { name = "torch" }, { name = "torchinfo" }, @@ -2916,7 +2984,8 @@ source = { editable = "widget" } dependencies = [ { name = "anywidget" }, { name = "matplotlib" }, - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "pillow" }, { name = "torch" }, { name = "traitlets" }, @@ -3000,7 +3069,8 @@ version = "0.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "dask", extra = ["array"] }, - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "pint" }, { name = "python-box" }, { name = "python-dateutil" }, @@ -3186,27 +3256,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.17" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/a9/3abdf488f1bf3d24c699415e454ed554a6350d5d89ce183be1ee0a3361ac/ruff-0.15.17.tar.gz", hash = "sha256:2ec446937fd16c8c4de2674a209cc5af64d9c6f17d21fbf1151054fa0bcf5219", size = 4743346, upload-time = "2026-06-11T17:54:47.663Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/db/4d/e11259f5da07cb6afb2d074c31bf09da9671993f7329d4f15d2fdc458301/ruff-0.15.17-py3-none-linux_armv6l.whl", hash = "sha256:d9feddb927fc68bd295f5eebc587a7e42cfaf9b65f60ca4a2386febff575da8f", size = 10856677, upload-time = "2026-06-11T17:54:49.533Z" }, - { url = "https://files.pythonhosted.org/packages/29/3e/772d679e1a0dc058e58875bd2c0cb713a0530877b4a76fee3c7966df0d49/ruff-0.15.17-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:25805a226d741c47d274a35ad5c10a7dde175fcddfa511d7cf3da0a21eb3eab7", size = 11223443, upload-time = "2026-06-11T17:55:00.573Z" }, - { url = "https://files.pythonhosted.org/packages/68/58/bd41f7688b2fd5623012605130ed70e60aa7f2244baa3d5066bdd61530c8/ruff-0.15.17-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f6ad73b14c2d18a3bf8ad7cb6974294d7f613a7898604826058e6ac64918ef4d", size = 10566458, upload-time = "2026-06-11T17:55:07.52Z" }, - { url = "https://files.pythonhosted.org/packages/d8/5b/733371013fcf1ec339e477ece6ab42bfe10bdd9bba8ee88a9516aa56bfc0/ruff-0.15.17-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ba0c1e4f95bcb3869d0d30cbd5917071ef2e28665abfec970cdab0492c713ed", size = 10914483, upload-time = "2026-06-11T17:55:05.501Z" }, - { url = "https://files.pythonhosted.org/packages/bd/cc/6f24251cc0252f7239391ccb85833f320efad14ebe5b443943f37ced6332/ruff-0.15.17-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:81647960f10bff57d2e51cadd0c3950fe598400c852863a038720ef5b8cca91e", size = 10647497, upload-time = "2026-06-11T17:54:57.733Z" }, - { url = "https://files.pythonhosted.org/packages/68/dd/0d10c17ce1a1624d6fc3156309c3f834fdb5dfaad026ec90c85684f3990e/ruff-0.15.17-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e01a84ddbc8c16c23055ba3924476850f1bbc1917cebbb9376665a63e74260d", size = 11416967, upload-time = "2026-06-11T17:54:51.461Z" }, - { url = "https://files.pythonhosted.org/packages/2f/91/556bfb156f6144f355e831c23db00b2fc4120f86b3ce81cc5f7fd2df51f3/ruff-0.15.17-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fe9f653152f8f294f9f7e03bf3a453d8b4a27f7a59c78c8666167f2b17b96c", size = 12335770, upload-time = "2026-06-11T17:54:45.793Z" }, - { url = "https://files.pythonhosted.org/packages/88/82/8b5999aa13355e926f06d9f42a32dcca862f623bf0363785ff89d607dffd/ruff-0.15.17-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c0fe88a7676e7a05b73174d4d4a59cb2ac21ff8263583f87a81a6018475a978", size = 11575441, upload-time = "2026-06-11T17:54:32.661Z" }, - { url = "https://files.pythonhosted.org/packages/11/93/f10377bb04109ca0e8cbc483ff1982c54b6d418210041776f93e8cdc7fa9/ruff-0.15.17-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecfc3c7878fff94633ab0348524e093f9ce3243080416dd7d14f8ba400174719", size = 11557614, upload-time = "2026-06-11T17:54:34.698Z" }, - { url = "https://files.pythonhosted.org/packages/c7/a6/eeeae7f7d5493df41649ab3db92f086b2d0a30199e4efdf8e3dd7a033f24/ruff-0.15.17-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:b8461180b22420b1bdc289909410930761629fddf2a5aaf60fae1ab26cedc4c4", size = 11544450, upload-time = "2026-06-11T17:54:39.042Z" }, - { url = "https://files.pythonhosted.org/packages/32/88/5991ce565129a24dd4a00db1254b3b5db2e53018cbe4018ea5a89738e727/ruff-0.15.17-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6eccbe50a038b503e7140b441aa9c7fc8c1f36edf23ebef9f4165c2f28f568b7", size = 10892524, upload-time = "2026-06-11T17:55:09.432Z" }, - { url = "https://files.pythonhosted.org/packages/f5/1d/0fdd248313425f55223968af04b0a42125466a8d88d21c1d99c6af0a51e8/ruff-0.15.17-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:382fc0521025f5a8ad447d8bdd523545d0d7646adb718eb1c2dac5065ec27c0f", size = 10659573, upload-time = "2026-06-11T17:54:36.824Z" }, - { url = "https://files.pythonhosted.org/packages/9e/0e/072e8260deb9461062ce9311ced27a8e541229a6ffd483013dd37661e43e/ruff-0.15.17-py3-none-musllinux_1_2_i686.whl", hash = "sha256:456d41fcd1b2777ad63f09a6e7121d43f7b688bbc76a800c10f7f8fb1f912c3f", size = 11127818, upload-time = "2026-06-11T17:55:03.124Z" }, - { url = "https://files.pythonhosted.org/packages/ab/b4/55060a34163121498014696b5f656db5b8c6963768f227dbf0d76b311073/ruff-0.15.17-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b1a04bcc94ae6194e9db05d16ad31f298a7194bfbcb08258bbe589cee1d587b8", size = 11655901, upload-time = "2026-06-11T17:54:53.562Z" }, - { url = "https://files.pythonhosted.org/packages/49/71/9b29d6b87cef468d697f43c6a91e3fae4a80185779d7d5a4ef27d173439f/ruff-0.15.17-py3-none-win32.whl", hash = "sha256:596065960ab1ff593f744220c9fe6580eda00a95003cffa9f4048bb5b1bf0392", size = 10925574, upload-time = "2026-06-11T17:54:55.723Z" }, - { url = "https://files.pythonhosted.org/packages/3d/b2/8fc77f3723228836fa5d12497eb71c808f83782e10d058d2b15cfa14640b/ruff-0.15.17-py3-none-win_amd64.whl", hash = "sha256:6769e5fa1710b179b92e0bfa5a51735b35baea9013dadb06d5f44cbcf9547084", size = 12058788, upload-time = "2026-06-11T17:54:41.042Z" }, - { url = "https://files.pythonhosted.org/packages/2d/c7/c53e8dbff9c9dc4b7928773421ae294a5d28fcb8dcda1a089579d3a7e510/ruff-0.15.17-py3-none-win_arm64.whl", hash = "sha256:f3be1fbb34bcdfd146240d8fb92a709d4c2c8191348580a3c044ec60fa0b4456", size = 11355275, upload-time = "2026-06-11T17:54:43.635Z" }, +version = "0.15.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/dc/35b341fc554ba02f217fc10da57d1a75168cfbcf75b0ef2202176d4c4f2d/ruff-0.15.20.tar.gz", hash = "sha256:1416eb04349192646b54de98f146c4f59afe37d0decfc02c3cbbf396f3a28566", size = 4755489, upload-time = "2026-06-25T17:20:37.578Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/d9/2d5014f0253ba541d2061d9fa7193f48e941c8b21bb88a7ff9bbe0bd0596/ruff-0.15.20-py3-none-linux_armv6l.whl", hash = "sha256:00e188c53e499c3c1637f73c91dcf2fb56d576cab76ce1be50a27c4e80e37078", size = 10839665, upload-time = "2026-06-25T17:19:44.702Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d3/ac1798ba64f670698867fcfc591d50e7e421bef137db564858f619a30fcf/ruff-0.15.20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9ebd1fd9b9c95fc0bd7b2761aebec1f030013d2e193a2901b224af68fe47251b", size = 11208649, upload-time = "2026-06-25T17:19:48.787Z" }, + { url = "https://files.pythonhosted.org/packages/47/47/d3ac899991202095dfcf3d5176be4272642be3cf981a2f1a30f72a2afb95/ruff-0.15.20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5b16cdd67ca108185cd36dce98c576350c03b1660a751de725fb049193a0632", size = 10622638, upload-time = "2026-06-25T17:19:51.354Z" }, + { url = "https://files.pythonhosted.org/packages/33/13/4e043fe30aa94d4ff5213a9881fc296d12960f5971b234a5263fdc225312/ruff-0.15.20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3413bb3c3d2ca6a8208f1f4809cd2dca3c6de6d0b491c0e70847672bde6e6efd", size = 10984227, upload-time = "2026-06-25T17:19:54.044Z" }, + { url = "https://files.pythonhosted.org/packages/76/e6/92e7bf40388bc5800073b96564f56264f7e48bfd1a498f5ced6ae6d5a769/ruff-0.15.20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd7ec42b3bb3da066488db093308a69c4ac5ee6d2af333a86ba6e2eb2e7dd44b", size = 10622882, upload-time = "2026-06-25T17:19:57.037Z" }, + { url = "https://files.pythonhosted.org/packages/13/7a/43460be3f24495a3aa46d4b16873e2c4941b3b5f0b00cf88c03b7b94b339/ruff-0.15.20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1a36ad0eb77fba9aabfb69ede54de6f376d04ac18ebea022847046d340a8267", size = 11474808, upload-time = "2026-06-25T17:20:00.357Z" }, + { url = "https://files.pythonhosted.org/packages/27/a0/f37077884873221c6b33b4ab49eb18f9f88e54a16a25a5bca59bef46dd66/ruff-0.15.20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6df3b1e4610432f0386dba04d853b5f08cbbc903410c6fcc02f620f05aff53c", size = 12293094, upload-time = "2026-06-25T17:20:03.446Z" }, + { url = "https://files.pythonhosted.org/packages/a6/74/165545b60256a9704c21ac0ec4a0d07933b320812f9584836c9f4aca4292/ruff-0.15.20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e89f198a1ea6ef0d727c1cf16088bc91a6cb0ab947dedc966715691647186eae", size = 11526176, upload-time = "2026-06-25T17:20:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/86/b1/a976a136d40ade83ce743578399865f57001003a409acadc0ecbb3051082/ruff-0.15.20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309809086c2acb67624950a3c8133e80f32d0d3e27106c0cd60ff26657c9f24b", size = 11520767, upload-time = "2026-06-25T17:20:09.191Z" }, + { url = "https://files.pythonhosted.org/packages/19/0f/f032696cb01c9b54c0263fa393474d7758f1cdc021a01b04e3cbc2500999/ruff-0.15.20-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2d2374caa2f2c2f9e2b7da0a50802cfb8b79f55a9b5e49379f564544fbf56487", size = 11500132, upload-time = "2026-06-25T17:20:13.602Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f4/51b1a14bc69e8c224b15dab9cce8e99b425e0455d462caa2b3c9be2b6a8e/ruff-0.15.20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a1ed17b65293e0c2f22fc387bc13198a5de94bf4429589b0ff6946b0feaf21a3", size = 10943828, upload-time = "2026-06-25T17:20:16.635Z" }, + { url = "https://files.pythonhosted.org/packages/71/4b/fe267640783cd02bf6c5cc290b1df1051be2ec294c678b5c15fe19e52343/ruff-0.15.20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f701305e66b38ea6c91882490eb73459796808e4c6362a1b765255e0cdcd4053", size = 10645418, upload-time = "2026-06-25T17:20:19.4Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c0/a65aa4ec2f5e87a1df32dc3ec1fede434fe3dfd5cbcf3b503cafc676ab54/ruff-0.15.20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b9c0c367ad8e5d0d5b5b8537864c469a0a0e55417aadfbeca41fa61333be9f4", size = 11211770, upload-time = "2026-06-25T17:20:22.033Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a4/0caa331d954ae2723d729d351c989cb4ca8b6077d5c6c2cb6de75e98c041/ruff-0.15.20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:01cc00dd58f0df339d0e902219dd53990ea99996a0344e5d9cc8d45d5307e460", size = 11618698, upload-time = "2026-06-25T17:20:25.259Z" }, + { url = "https://files.pythonhosted.org/packages/10/9b/5f14927848d2fd4aa891fd88d883788c5a7baba561c7874732364045708c/ruff-0.15.20-py3-none-win32.whl", hash = "sha256:ed65ef510e43a137207e0f01cfcf998aeddb1aeeda5c9d35023e910284d7cf21", size = 10857322, upload-time = "2026-06-25T17:20:28.612Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f0/fe47c501f9dea92a26d788ff98bb5d92ed4cb4c88792c5c88af6b697dc8e/ruff-0.15.20-py3-none-win_amd64.whl", hash = "sha256:a525c81c70fb0380344dd1d8745d8cc1c890b7fc94a58d5a07bd8eb9557b8415", size = 11993274, upload-time = "2026-06-25T17:20:31.871Z" }, + { url = "https://files.pythonhosted.org/packages/d7/2b/9555445e1201d92b3195f45cdb153a0b68f24e0a4273f6e3d5ab46e212bb/ruff-0.15.20-py3-none-win_arm64.whl", hash = "sha256:2f5b2a6d614e8700388806a14996c40fab2c47b819ef57d790a34878858ed9ca", size = 11343498, upload-time = "2026-06-25T17:20:35.03Z" }, ] [[package]] @@ -3217,10 +3287,12 @@ dependencies = [ { name = "imageio" }, { name = "lazy-loader" }, { name = "networkx" }, - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "packaging" }, { name = "pillow" }, - { name = "scipy" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "tifffile", version = "2026.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "tifffile", version = "2026.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] @@ -3280,8 +3352,11 @@ wheels = [ name = "scipy" version = "1.17.1" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.12'", +] dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } wheels = [ @@ -3347,6 +3422,61 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, ] +[[package]] +name = "scipy" +version = "1.18.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version >= '3.12' and python_full_version < '3.14'", +] +dependencies = [ + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" } }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/25/c2700dfaf6442b4effaa91af24ebce5dc9d31bb4a69706313aae70d72cd0/scipy-1.18.0.tar.gz", hash = "sha256:67b2ad2ad54c72ca6d04975a9b2df8c3638c34ddd5b28738e94fc2b57929d378", size = 30774447, upload-time = "2026-06-19T15:01:43.456Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/19/ca10ead60b0acc80b2b833c2c4a4f2ff753d0f58b811f70d911c7e94a25c/scipy-1.18.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:7bd21faaf5a1a3b2eff922d02db5f191b99a6518db9078a8fb23169f6d22259a", size = 31056519, upload-time = "2026-06-19T14:59:45.203Z" }, + { url = "https://files.pythonhosted.org/packages/96/72/1e6442a00cd2924d361aa1b642ab6373ec35c6fabf311a760be9f76e0f13/scipy-1.18.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:265915e79107de9f946b855e50d7470d5893ec3f54b342e1aa6201cbdcd8bb6b", size = 28681889, upload-time = "2026-06-19T14:59:48.103Z" }, + { url = "https://files.pythonhosted.org/packages/9b/2d/11dd93d21e147a73ba22bd75c0b9208d3a2e0ec76d53170ce7d9029b1015/scipy-1.18.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9ab7b758be6940954a713ee466e2043e9f6e2ed965c1fce5c91039f4be3d90a9", size = 20423580, upload-time = "2026-06-19T14:59:50.665Z" }, + { url = "https://files.pythonhosted.org/packages/9c/01/93552f75e0d2a7dd115a45e59209c51e8d514daff02fc887d2623be06fe1/scipy-1.18.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:97b6cddaaee0a779ef6b5ca83c9604b27cc16b2b8fc22c142652df8793319fb8", size = 23054441, upload-time = "2026-06-19T14:59:53.564Z" }, + { url = "https://files.pythonhosted.org/packages/3c/23/21f5e703643d66f21faa6b4c73195bfcad70c55efcb4f1ab327cd7c4101a/scipy-1.18.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:52a96e21517c7292375c0e27dd796a811f03fcea5fd4d108fdfea8145dcf17ab", size = 33968720, upload-time = "2026-06-19T14:59:56.415Z" }, + { url = "https://files.pythonhosted.org/packages/dd/aa/1b939f6c67ed68635bb538e6752d3dacc02f66535182e939a89581a44e9c/scipy-1.18.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f55797419e16e7f30cf88ffb3113ce0467f00cfe3f70d5c281730b21769bfc2", size = 35287115, upload-time = "2026-06-19T14:59:59.411Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ff/eec46be7e9234208f801062b53e1983085eddebd693f6c9bfb03b459830d/scipy-1.18.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ad033410e2e0672ffdc1042110cef20e1c46f8fd0616cee1d44d8d58fad8fc11", size = 35577989, upload-time = "2026-06-19T15:00:02.235Z" }, + { url = "https://files.pythonhosted.org/packages/84/ca/210d4759c7210bb7d269437421959b39a33434e2776b60c5cb8a763bb30a/scipy-1.18.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a55985d54c769c872e64b7f4c8a81cc30ef700cc04296abbbf3705439c126de", size = 37421717, upload-time = "2026-06-19T15:00:05.102Z" }, + { url = "https://files.pythonhosted.org/packages/2b/54/9a9edb45345bd6744da5ddfb6628e5d5185920494c6a67ec45b6381004cb/scipy-1.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:71ccc8faa2dd16ac310233203474a8b5cb67f10dedd54a3116d34943f4b19132", size = 36597428, upload-time = "2026-06-19T15:00:08.112Z" }, + { url = "https://files.pythonhosted.org/packages/99/0e/33f32a2a58987e26aec0f7df252cbbad1e90ae77bdbc76f40dd4ed0cf0ea/scipy-1.18.0-cp312-cp312-win_arm64.whl", hash = "sha256:d88363fd9d8fbd3511bd273f1a49efb2a540773ddf92a91d57498ce7dd7f3e76", size = 24351481, upload-time = "2026-06-19T15:00:11.103Z" }, + { url = "https://files.pythonhosted.org/packages/05/52/9c0136c2de7ae0779b7b366447766cec6d9f0702c56bb8ffeb04c8fd3af4/scipy-1.18.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:09143f676d157d9f546d663504ef9c1becb819824f1afc018814176411942446", size = 31036107, upload-time = "2026-06-19T15:00:14.03Z" }, + { url = "https://files.pythonhosted.org/packages/02/73/0291a64843270f4efb86cdcf2ee0f2048631b65ec6b405398b2b4dbf11bf/scipy-1.18.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5efe260f69417b97ddae455bfb5a95e8359f7f66ad7fa9522a60feb66f169520", size = 28663303, upload-time = "2026-06-19T15:00:16.819Z" }, + { url = "https://files.pythonhosted.org/packages/d3/0f/10ffa0b697a572f4e0d48b92a88895d366422f019f723e7e14a84c050dac/scipy-1.18.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:68363b7eaacd8b5dd426df56d782cc156468ac79a127a1b87ca597d6e2e82197", size = 20404960, upload-time = "2026-06-19T15:00:19.635Z" }, + { url = "https://files.pythonhosted.org/packages/7e/d2/e896cea21ba8edd6c81d4c55b1ffcc717e79698dcbebf9641b4cfb4c6622/scipy-1.18.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:c5557d8be5da8e41353fcd4d21491fdbab83b062fc579e94dc09a7c8ab4f669b", size = 23034074, upload-time = "2026-06-19T15:00:22.107Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b2/e83ea34279a52c03374477c74006256ec78df65fc877baa4617d6de1d202/scipy-1.18.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0d13bca67c096d89fb95ced0d8921807300fce0275643aef9533cc63a0773468", size = 33942038, upload-time = "2026-06-19T15:00:24.964Z" }, + { url = "https://files.pythonhosted.org/packages/f6/af/e8fe5fb136f51e2b01678b92cb4106d10d8cd68ec147ead2e7cb0ac75398/scipy-1.18.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a46f9273dbd0eb1cefba61c9b8648b4dfe3cbc14a080176f9a73e44b8336dc7f", size = 35266390, upload-time = "2026-06-19T15:00:28.059Z" }, + { url = "https://files.pythonhosted.org/packages/3a/49/2c5cbb907b56695fc67517811d1db234dfd83381a84814ec220aded2794d/scipy-1.18.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5aba46108853ddfc77906b6557aac839d2b52e900c1d72a1180adaaab58d265f", size = 35551324, upload-time = "2026-06-19T15:00:31.014Z" }, + { url = "https://files.pythonhosted.org/packages/bb/73/eda39f7a2d306ff0ffc574afd13c0bbb6d10a603d9a413998ee269487a80/scipy-1.18.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b6f758e35f12757b5d95c00bc6de2438e229c2664b7a92e96f205959d9f2dfa4", size = 37404785, upload-time = "2026-06-19T15:00:34.072Z" }, + { url = "https://files.pythonhosted.org/packages/b7/d2/ae881ee28d014f38e0ccbfd974a06a919ba9af34f1f74bf42b5301891d63/scipy-1.18.0-cp313-cp313-win_amd64.whl", hash = "sha256:1afac4a847207c7ff8efd321734a50b06d0280b3b2a2c0fc2f413101747ad7c7", size = 36554943, upload-time = "2026-06-19T15:00:36.903Z" }, + { url = "https://files.pythonhosted.org/packages/70/3a/21154e2d54eb3639c6bf4dbae2e531c68356bfe95990daa30df33b30d556/scipy-1.18.0-cp313-cp313-win_arm64.whl", hash = "sha256:c5dbddf60e58c2312316d097271a8e73d40eaf2eabfa4d95ed7d3695bbf2ce7b", size = 24350911, upload-time = "2026-06-19T15:00:40.062Z" }, + { url = "https://files.pythonhosted.org/packages/78/b5/915a19b3de2f7430062b509653563db1633ddbb6f021b06731521115d4e2/scipy-1.18.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:4c256ee70c0d1a8a2ace807e199ccd4e3f57037433842abb3fb36bc17eaa9578", size = 31036253, upload-time = "2026-06-19T15:00:43.216Z" }, + { url = "https://files.pythonhosted.org/packages/d7/88/b72def7262e150d16be13fca37a96481138d624e700340bc3362a7588929/scipy-1.18.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:2ef3abc54a4ffc53765374b0d5728532dfdd2585ed23f6b11c206a1f0b1b9af8", size = 28673758, upload-time = "2026-06-19T15:00:46.663Z" }, + { url = "https://files.pythonhosted.org/packages/91/02/2e636a61a525632c373cf6a9c24442a3ffb79e364d38e98b32042964ac32/scipy-1.18.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:f2a6af57bd9e4a75d70e4117e78a1bbee84f79ae3fbb6d0111005d6ebcc4cb8d", size = 20415514, upload-time = "2026-06-19T15:00:49.399Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b6/2135974442f6aba159d9d39d774a1c8cb19947016725d69fecc685df45bf/scipy-1.18.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:3f1ac564d3bf6c03d861d2cd87a1bea0da2887136f7fb1bf519c05a8971452d6", size = 23034398, upload-time = "2026-06-19T15:00:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/f6/e6/ba89ec5abf6ee9257c0d1ec985573f3ae32742c24bc03e016388a40b1b15/scipy-1.18.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40395a5fcd1abee49a5c7aaa98c29db393eedc835138560a588c47ec16156690", size = 33998032, upload-time = "2026-06-19T15:00:54.838Z" }, + { url = "https://files.pythonhosted.org/packages/7f/c4/bc41eb19b0fd0db868f4132920879019318d80cc522ad8f2bca4611af808/scipy-1.18.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ca01e8ae69f1b18e9a58d91afead31be3cef0dd905a10249dac559ee15460a0", size = 35283333, upload-time = "2026-06-19T15:00:58.152Z" }, + { url = "https://files.pythonhosted.org/packages/53/a4/cbdeef6eb3830a8462a9d4ada814de5fc984345cc9ecf17cbec51a036f1e/scipy-1.18.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7a7f3b01647384dbc3a711e8c6778e0aabbe93959249fef5c7393396bcac0867", size = 35610216, upload-time = "2026-06-19T15:01:01.155Z" }, + { url = "https://files.pythonhosted.org/packages/80/4d/b2b82502b65f661d1b789c1665dcdf315d5f12194e06fc0b37946294ebae/scipy-1.18.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6aa94e78ec192a30063a5e72e561c28af769dc311190b24fe91774eff1969709", size = 37418960, upload-time = "2026-06-19T15:01:04.155Z" }, + { url = "https://files.pythonhosted.org/packages/93/3e/902d836831474b0ab5a37d16404f7bc5fafd9efba632890e271ba952635f/scipy-1.18.0-cp314-cp314-win_amd64.whl", hash = "sha256:2d8bbdc6c817f5b4006a54d799d4f5bab6f910193cbb9a1ff310833d4d270f61", size = 37288845, upload-time = "2026-06-19T15:01:07.822Z" }, + { url = "https://files.pythonhosted.org/packages/b6/43/8d73b337a3bdb14daa0314f0434210747c02d79d729ce1777574a817dcf6/scipy-1.18.0-cp314-cp314-win_arm64.whl", hash = "sha256:18e9575f1569b2c54174e6159d32942e03731177f63dce7975f0a0c88d102f5b", size = 24988971, upload-time = "2026-06-19T15:01:11.076Z" }, + { url = "https://files.pythonhosted.org/packages/b4/b4/f11918b0508a2787031a0499a03fbe3546f3bb5ca05d01038c45b278c09a/scipy-1.18.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f351e0dd702687d12a402b867a1b4146a256923e1c38317cbc472f6372b94707", size = 31399325, upload-time = "2026-06-19T15:01:13.723Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d1/1f287b57c0ff0ee5185dff3946d92c8017d39b0e431f0ae79a3ff1859512/scipy-1.18.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:7c7a51b33ce387193c97f228320cf8e87361daa1bba750638677729598b3e677", size = 29092110, upload-time = "2026-06-19T15:01:16.908Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1a/7b74eb6c392fdcb27d414c0e7558a6d0231eb3b6d73571f479bb81ea8794/scipy-1.18.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:84031d7b052a54fae2f8632e0ec802073d385476eb9a63079bce6e23ef9283d4", size = 20833811, upload-time = "2026-06-19T15:01:20.488Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ad/f3941716320a7b9cb4d68734a903b45fe16eff5fb7da7e16f2e619304979/scipy-1.18.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:56abf29a7c067dde59be8b9a22d606a4ea1b2f2a4b756d9d903c62818f5dacce", size = 23396644, upload-time = "2026-06-19T15:01:23.364Z" }, + { url = "https://files.pythonhosted.org/packages/22/22/1446b62ffe07f9719b7d9b1b6a4e05a772833ae8f441fe4c22c34c9b250f/scipy-1.18.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ad44305cfa24b1ba5803cbbebf033590ccbac1aa5d612d727b785325ab408b0", size = 34079318, upload-time = "2026-06-19T15:01:26.002Z" }, + { url = "https://files.pythonhosted.org/packages/56/3b/b87da667098bb470fa30c7011b0ba351ee976dd395c78798c66e941665a3/scipy-1.18.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:945c1761b93f38d7f99ae81ae80c63e621471608c7eeead563f6df025585cd58", size = 35324320, upload-time = "2026-06-19T15:01:28.881Z" }, + { url = "https://files.pythonhosted.org/packages/f8/a1/c7932f91909759b0267f75fdea34e91309f96b895757534b76a90b6b4344/scipy-1.18.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1a4441f15d620578772a49e5ab48c0ee1f7a0220e387110283062729136b2553", size = 35699541, upload-time = "2026-06-19T15:01:31.968Z" }, + { url = "https://files.pythonhosted.org/packages/f7/86/5185061a1fcc41d18c5dc2463969b3a3964b31d9ac67b2fb05d4c7ff7670/scipy-1.18.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9aac6192fac56bf2ca534389d24623f07b39ff83317d58287285e7fbd622ff76", size = 37472480, upload-time = "2026-06-19T15:01:35.136Z" }, + { url = "https://files.pythonhosted.org/packages/31/8e/f04c68e39919a010d34f2ee1367fd705b0a25a02f609d755f0bfbc0a15fc/scipy-1.18.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e40baea28ae7f5475c779741e2d90b1247c78531207b49c7030e698ff81cee3f", size = 37365390, upload-time = "2026-06-19T15:01:38.091Z" }, + { url = "https://files.pythonhosted.org/packages/d5/19/969dc072906c84dd0a3b05dcf57ea750936087d7873549e408b35cfc3f97/scipy-1.18.0-cp314-cp314t-win_arm64.whl", hash = "sha256:368e0a705903c466aa5f08eefb39e6b1b6b2d659e7352a31fd9e2438365be0f8", size = 25279661, upload-time = "2026-06-19T15:01:40.817Z" }, +] + [[package]] name = "send2trash" version = "2.1.0" @@ -3385,50 +3515,50 @@ wheels = [ [[package]] name = "sqlalchemy" -version = "2.0.50" +version = "2.0.51" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/57/da/6fbf010c8ebb347679d0d100b22fe9ba5e13fd04046c5df7280d2f0bf706/sqlalchemy-2.0.50.tar.gz", hash = "sha256:af5607d11ef90fd6a5c0549fe0045dce1663d427426bcfb506dcb5346a85a3b9", size = 9907424, upload-time = "2026-05-24T19:20:04.018Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/5d/3172686af1770e4de2805f919a51441085f589ddadf3dd76ec582f84f497/sqlalchemy-2.0.50-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1aa6e403663a9c43c8fef7ce4bdb4cf48bcd8d352e91deda2a99f963270bd508", size = 2161366, upload-time = "2026-05-24T20:00:02.061Z" }, - { url = "https://files.pythonhosted.org/packages/0f/90/e98dedea3c3e663a17afcd003a34ba45efdac2cea3b6f2e4585e2b1e2537/sqlalchemy-2.0.50-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51b637a84f9fa35ae1f9017e786cb142974a25305085e1b378b3647a67f65ad3", size = 3318926, upload-time = "2026-05-24T20:07:42.369Z" }, - { url = "https://files.pythonhosted.org/packages/3b/4f/501308c2babb62c11753ecb4ee88ba9eef019419a4d6cbf7cb13e2bad353/sqlalchemy-2.0.50-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2dab927761d9108550f0cf8e66ff21af56f907a0ce0a689793db615e2b55f62c", size = 3319199, upload-time = "2026-05-24T20:14:28.551Z" }, - { url = "https://files.pythonhosted.org/packages/ac/39/d88996c5e03ed6248c3a788d20f0b8d8b376b9f8a495e4bab9df7c72d2f8/sqlalchemy-2.0.50-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:545eae198d37bcf837a10ede3684e2af32458d6f35c597c35c2de7502dc38fc4", size = 3270301, upload-time = "2026-05-24T20:07:44.917Z" }, - { url = "https://files.pythonhosted.org/packages/42/1b/1ae0e65161b51cc43e5ca75430ef79d80e23b5042d645586c2c342c3b92e/sqlalchemy-2.0.50-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fec460e18cdbb4c7773531122ce9a27e96c6ca17af3933941d94da475ad2c86", size = 3293465, upload-time = "2026-05-24T20:14:30.501Z" }, - { url = "https://files.pythonhosted.org/packages/83/29/17c0003f2c0dfa6d1b97672475707e3ec5980db09defd7fa20beb6833bbd/sqlalchemy-2.0.50-cp311-cp311-win32.whl", hash = "sha256:e6e814658818fd165e749e3d8490ef16cc7f379a118c37ada8b0589ffbaaac22", size = 2120694, upload-time = "2026-05-24T20:08:09.237Z" }, - { url = "https://files.pythonhosted.org/packages/c9/18/280d00654cc19d1fccf236fa5070f6dd04b84dde6f1b2e637bde0ff340a7/sqlalchemy-2.0.50-cp311-cp311-win_amd64.whl", hash = "sha256:1c5f858fe79c9f5d8fda065c06186356acb7f8df3cd52dbd5ee3f200e4b144f5", size = 2145315, upload-time = "2026-05-24T20:08:10.952Z" }, - { url = "https://files.pythonhosted.org/packages/be/b0/a9d19b43f38f878b1278bca5b00b909f7540d41494396dd2561f9ad0956d/sqlalchemy-2.0.50-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23ae23d8b9d344d30d0a92f06d45825024a5790f1c1dd4cf452636a50d3e58cb", size = 2159807, upload-time = "2026-05-24T19:27:53.086Z" }, - { url = "https://files.pythonhosted.org/packages/f5/2c/191dd58a248fd2cfd4780fa82c375c505e4ad98c8b522fa69ec492130d77/sqlalchemy-2.0.50-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47b71b933e7b4ebad407c8fdfd70d2c4f08b78b3238bb30eebdd6eb32ca51b89", size = 3343358, upload-time = "2026-05-24T20:09:29.279Z" }, - { url = "https://files.pythonhosted.org/packages/8a/2b/514fce8a7df81cf5bad7ff7865de7ac0c5776a38cc043475c4703eb7fe8b/sqlalchemy-2.0.50-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:110fdac56ace278949f00de805edacbd6141e382d992f9ba28238b3a0827a600", size = 3357994, upload-time = "2026-05-24T20:17:13.495Z" }, - { url = "https://files.pythonhosted.org/packages/35/a6/a0e283f5494f92b0d77e319ff77e437b1ffe4a051ba67c81d53234825475/sqlalchemy-2.0.50-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0f5e4ac70e9e757f6b3e87c0491ff034442ecd8dfd36d041a50564c322dafc0e", size = 3289399, upload-time = "2026-05-24T20:09:32.239Z" }, - { url = "https://files.pythonhosted.org/packages/b7/96/1b07325ba71752d6a028b77d07bed1483ad545f794e8b1dc89b3ba3b3c68/sqlalchemy-2.0.50-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:724f3dcbe53dd0151e3cb5e7ec4ba4c620bede579caacd16275dc35ce06e8615", size = 3321216, upload-time = "2026-05-24T20:17:15.581Z" }, - { url = "https://files.pythonhosted.org/packages/ed/8e/bad6ed253e8a99edfc99af02f7173ec48a1d3ed1b9b35a1b8bc1700900cc/sqlalchemy-2.0.50-cp312-cp312-win32.whl", hash = "sha256:1208050441471d003b7c8cb4054fb084f185cf35ac3f0ea270803865bca9939a", size = 2119194, upload-time = "2026-05-24T19:50:04.943Z" }, - { url = "https://files.pythonhosted.org/packages/b6/2d/314a6690dda4b9cfc571eab1a63cf6fe6e1470aa3759ccda6aa016ee0f5a/sqlalchemy-2.0.50-cp312-cp312-win_amd64.whl", hash = "sha256:9d1af51558029a156a70986b7df88f042b3d158d7c8d8fb5072912d4b32d89c7", size = 2146186, upload-time = "2026-05-24T19:50:06.74Z" }, - { url = "https://files.pythonhosted.org/packages/0b/c4/c42356b527296e9862f67990efce31ef78b4cf69cd3f80873a528a060320/sqlalchemy-2.0.50-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:06a9210bdc5f4298cff0781087e2ff45683922252dacc452846373a58761f093", size = 2156697, upload-time = "2026-05-24T19:27:54.764Z" }, - { url = "https://files.pythonhosted.org/packages/60/a1/b1a70e3c4365ac7fe9e347f3710f19b562c866fb96d45e3c891588789a7b/sqlalchemy-2.0.50-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b53784972ade4f8174b9aa661f31a06f8a936d2cfdd602913ff3c6dd40ae873", size = 3284260, upload-time = "2026-05-24T20:09:34.195Z" }, - { url = "https://files.pythonhosted.org/packages/3f/4a/f3ac3caa19f263d57b0a47f8c91bbf56583dc2d3fc63acfbf644abb24fe0/sqlalchemy-2.0.50-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31648fa14460537e768a7303b078e4344d208e0d23e06867c1f376a227ed82db", size = 3302280, upload-time = "2026-05-24T20:17:17.825Z" }, - { url = "https://files.pythonhosted.org/packages/66/55/ccada3e3d62254587819749a0bc69f41173eb48a6e385d10e66d32a9c88e/sqlalchemy-2.0.50-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:03f4323c980ad0e918cc9e5369b015f759f4e534db5bbaf4dc36832c10d05064", size = 3231580, upload-time = "2026-05-24T20:09:36.406Z" }, - { url = "https://files.pythonhosted.org/packages/05/f6/6809349130a2de0e109e7f00fd7d431da9565b9b2868b32ee684754f672b/sqlalchemy-2.0.50-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2b9dcc43afef8ac157cd92fce96985d6b8b0cfbd3df4d666f66b4d55a75d202f", size = 3269375, upload-time = "2026-05-24T20:17:20.34Z" }, - { url = "https://files.pythonhosted.org/packages/48/84/278a811ef4e07be9c89dc5cdd7be833268509a66a68c4897cf585e67428f/sqlalchemy-2.0.50-cp313-cp313-win32.whl", hash = "sha256:60922d6599065ddca2c6f376b9aa2f41a6b85a271725e0909490bbc50b1998a5", size = 2117229, upload-time = "2026-05-24T19:50:08.215Z" }, - { url = "https://files.pythonhosted.org/packages/f6/1c/067cc6187ed32d2ec222fe6d2643acc1659a6d0659f8a7cbc5ad3ae83280/sqlalchemy-2.0.50-cp313-cp313-win_amd64.whl", hash = "sha256:287086e67275a212c4582d166a6fb03a65ccc5551d80866270ce0dd9f34eccd3", size = 2143126, upload-time = "2026-05-24T19:50:09.691Z" }, - { url = "https://files.pythonhosted.org/packages/df/32/10ac51b4be7cdecd7e93d069251c86dfbf70b7adbd7c67b48ccea6c49e1c/sqlalchemy-2.0.50-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c966932507a4d7d0a37314927dbfcd89720e3f37d2a1e3352e7ae7939fa8e8a0", size = 2158519, upload-time = "2026-05-24T19:27:56.472Z" }, - { url = "https://files.pythonhosted.org/packages/5a/76/e703d2f7681d7d66c4c891af3f07c7ccf4c76ad7f18351de035b5eda007a/sqlalchemy-2.0.50-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:faffef4bcc20a1892e65e155293d99d60855bbbc79250ab712819cfd56a8e6bb", size = 3282063, upload-time = "2026-05-24T20:09:38.57Z" }, - { url = "https://files.pythonhosted.org/packages/31/26/ef168b184a25701f9995e8fb7e503fafd7a99c1c77cda1bc1a26ea2ed486/sqlalchemy-2.0.50-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c206aec519a2e7bd08abbfb33436e325fd22c632d9c21a9047e376ce241646e", size = 3287069, upload-time = "2026-05-24T20:17:21.942Z" }, - { url = "https://files.pythonhosted.org/packages/c2/15/765acc2bc693bccc43ca4a95d5b69750da8aaf6db1b5c616536e087f8920/sqlalchemy-2.0.50-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bef4ac756363227ef6402a75fee025a4bc690f92328e825868939b3b3a446a6d", size = 3230453, upload-time = "2026-05-24T20:09:40.398Z" }, - { url = "https://files.pythonhosted.org/packages/63/61/08e03c3adbf5db0087a0b6816746fec8f3032fb2f7fc899a9bb9b2a48ce4/sqlalchemy-2.0.50-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:96fbee6b19c19cd1556c8bf9419447cf2ec149ffcab7ab64348c23e54ef8547f", size = 3252413, upload-time = "2026-05-24T20:17:24.067Z" }, - { url = "https://files.pythonhosted.org/packages/03/0c/370a1f2db38436c615e10134c8a37de3688e74084792380695f3f5083860/sqlalchemy-2.0.50-cp314-cp314-win32.whl", hash = "sha256:8f00e3eb43ba30eb1b238ee03a8a62309486d1321eda3328bb611e0340033ad8", size = 2120063, upload-time = "2026-05-24T19:50:11.08Z" }, - { url = "https://files.pythonhosted.org/packages/7f/a0/fe92bb9817863bc13ba093bda931979a26cc2ca69f8e8f26d07add3d7c6f/sqlalchemy-2.0.50-cp314-cp314-win_amd64.whl", hash = "sha256:15708c613cd5005b7dffe1f66ee6a63ee8f5e46799f71c70ebad74178c676a39", size = 2145830, upload-time = "2026-05-24T19:50:12.452Z" }, - { url = "https://files.pythonhosted.org/packages/cc/ff/e5640a98a0b2f491eb8fde10fb6c773621a2e44340de231fafcc9370f4a9/sqlalchemy-2.0.50-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3699dac4be410e97049a1658e9480da9cde956594aa0f3aebc60b88f21c5ba70", size = 2178435, upload-time = "2026-05-24T19:42:58.889Z" }, - { url = "https://files.pythonhosted.org/packages/b7/85/337116e186f1236375b5fb70c21cfac98e8e8ab0d3a47be838dc47a59e08/sqlalchemy-2.0.50-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f96233858e3df43932ac11589e22520da6e8aeb624b03fedfeebb0e8ea213086", size = 3566059, upload-time = "2026-05-24T20:01:20.848Z" }, - { url = "https://files.pythonhosted.org/packages/96/34/bb0e190e161c3c2c24314a65add57218be14a4a9486886b7f5047c1ff7c8/sqlalchemy-2.0.50-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c4e70c46fad30c3bcc6a4708bc0130a3173e11a5b25f0ea4a9d8911b450f1f52", size = 3535366, upload-time = "2026-05-24T20:03:56.768Z" }, - { url = "https://files.pythonhosted.org/packages/df/5a/a7f759f97e4fd499c5d4e4488c760d5a7fbecf3028b465a04274fcd52384/sqlalchemy-2.0.50-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1918a3cf564d16d95bca7301005f41ab2ad50b07cd3b9da50d3ed986db148d6a", size = 3474879, upload-time = "2026-05-24T20:01:23.058Z" }, - { url = "https://files.pythonhosted.org/packages/9d/d9/2907ea38eb60687d297bf9c39e5ee58053c87b57fe8a9cae97090cecbf10/sqlalchemy-2.0.50-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b00098cdbdbd38c7be3d568b0c9c3122b8c0ec62b911b57cd5e6e0254d60a76d", size = 3486117, upload-time = "2026-05-24T20:03:59.052Z" }, - { url = "https://files.pythonhosted.org/packages/f2/e3/5aa06f167559f8c0bdae487e297d23ba548150ab016a3418265d617a4985/sqlalchemy-2.0.50-cp314-cp314t-win32.whl", hash = "sha256:1fbd55a969d7ac44a98e3dec75016074f809fa08f871585ace58dde110d1bf3e", size = 2150823, upload-time = "2026-05-24T20:08:58.644Z" }, - { url = "https://files.pythonhosted.org/packages/65/9b/112fb8f977582d7489d036e409e3723948bcf5320b3ac465f3c481bbe8f9/sqlalchemy-2.0.50-cp314-cp314t-win_amd64.whl", hash = "sha256:c5c3cdb753a9004183e1ccb634b41611654c989e61bc68617ce878e46d6f1e51", size = 2185794, upload-time = "2026-05-24T20:09:00.319Z" }, - { url = "https://files.pythonhosted.org/packages/d0/10/f7220e9b784d295d241c86ed99aeb537f92afcd469a64861f2717e9bb077/sqlalchemy-2.0.50-py3-none-any.whl", hash = "sha256:92064363517a3ff8212b5a93b8c62876579d8dfd1ca5b561335f30152d884fa9", size = 1943861, upload-time = "2026-05-24T19:59:01.119Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/02/f1/a7a892f18d4d224e6b26f706531eafccc41e37594d37d304786969ee13cb/sqlalchemy-2.0.51.tar.gz", hash = "sha256:804dccd8a4a6242c4e30ad961e540e18a588f6527202f2d6791b01845d59fdc9", size = 9912201, upload-time = "2026-06-15T15:41:20.012Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/69/a67c69e5f28fc9c99d6f7bd60bd50e91f2fed2423e3b30fb228fa00e51f3/sqlalchemy-2.0.51-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1aa10c0daee6705294d181daadaa793221e1a59ed55000a3fab1d42b088ce4ba", size = 2161838, upload-time = "2026-06-15T16:05:17.144Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a4/c8c22b8438bddc0a030157c6ec0f6ef97b3c38effa444bdab2a27af04090/sqlalchemy-2.0.51-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a5b2ed6d828f1f09bd812861f4f59ca3bc3803f9df871f4555187f0faf018604", size = 3319402, upload-time = "2026-06-15T16:10:40.002Z" }, + { url = "https://files.pythonhosted.org/packages/90/54/44012d32fd77d991256d2ff793ba3807c51d40cb27a85b4796224f6744df/sqlalchemy-2.0.51-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:436728ce18a80f6951a1e11cc6112c2ede9faf20766f1a26195a7c441ca12dbd", size = 3319675, upload-time = "2026-06-15T16:12:25.658Z" }, + { url = "https://files.pythonhosted.org/packages/29/a5/de0592acaf5906cd7430874392d6f7e8b4a7c8437610953ee2d1501c0b44/sqlalchemy-2.0.51-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dc261707bf5739aea8a541593f3cc1d463c2701fb05fbcbba0ce031b69a21260", size = 3270777, upload-time = "2026-06-15T16:10:42.125Z" }, + { url = "https://files.pythonhosted.org/packages/cb/14/a44c90739c780b362238e4ac3cb19dd0ca40d13e6ddc5daa112166ddab4f/sqlalchemy-2.0.51-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a6d26094615306d116dd5e4a51b0304c99dd2356fc569eed6922a80a6bd3b265", size = 3293940, upload-time = "2026-06-15T16:12:27.156Z" }, + { url = "https://files.pythonhosted.org/packages/65/eb/fbd0f206a330e66f8c602a99c37c4e731f107faed62954b41b01f16dd9d9/sqlalchemy-2.0.51-cp311-cp311-win32.whl", hash = "sha256:ca8435d13829b92f4a97362d91975154a4015db3a2634154e1754e9a915e6b86", size = 2121183, upload-time = "2026-06-15T16:13:29.905Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fd/005bf80f3cf6e5c62b5dd68616280f51cd012c60840fa74781b3ed7b1623/sqlalchemy-2.0.51-cp311-cp311-win_amd64.whl", hash = "sha256:4a011ea4510683319ce4ed274b56ee05194b39b6da9d09ca7a39388f0fa84dcc", size = 2145796, upload-time = "2026-06-15T16:13:31.283Z" }, + { url = "https://files.pythonhosted.org/packages/d5/70/e868bc5412acd101a8280f25c95f10eeae0771c4eb806b02491142810ee8/sqlalchemy-2.0.51-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d78702b26ba1c18b2d0fb2ea940ba7f17a9581b42e8361ff93920ebbee1235a", size = 2160291, upload-time = "2026-06-15T16:08:48.918Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1c/71ee0f8a6b9d7316a1ccd30430b4c62b6c2e36adc96017a4e3a72dce49d6/sqlalchemy-2.0.51-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581921d849d6e6f994d560389192955e80e2950e18fcdfe2ccea863e01158e6e", size = 3343835, upload-time = "2026-06-15T16:19:42.613Z" }, + { url = "https://files.pythonhosted.org/packages/2b/7c/7ab9f9aadc5944fdd06612484ed7918fe376ad871a5f50404dc1536e0194/sqlalchemy-2.0.51-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d21ce524ab86c23046e992a5b81cb54c21079c6df6e78b8fc77d77cac70a6b9", size = 3358470, upload-time = "2026-06-15T16:26:38.011Z" }, + { url = "https://files.pythonhosted.org/packages/d0/7d/ff77169fee6186de145a7f2b87006c39638391130abbab2b1f63ac6ea583/sqlalchemy-2.0.51-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c5d98a2709840027f5a347c3af0a7c3d5f6c1ff93af2ca1c54494e23cba8f389", size = 3289874, upload-time = "2026-06-15T16:19:45.212Z" }, + { url = "https://files.pythonhosted.org/packages/6f/3b/6c505903710d781b55bc3141ee34a062bf9745a6b5bc7333305b9ed63b33/sqlalchemy-2.0.51-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1181256e0f16479691b5616d36375dc2620ad8332b25978763c3d206ad3f3f1d", size = 3321692, upload-time = "2026-06-15T16:26:39.747Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b7/c5ffe50aa2f4d947c9250e1519d939260329a07fe6272edfccd784b3d007/sqlalchemy-2.0.51-cp312-cp312-win32.whl", hash = "sha256:9f380393be5abeb6815f68fd39271b95127173511b6706b0a630a9995d53f8f5", size = 2119674, upload-time = "2026-06-15T16:23:09.543Z" }, + { url = "https://files.pythonhosted.org/packages/25/dc/46a65916af68a06ef6b972c6050ba4c8f97070fe3fb33097d34229d9bef6/sqlalchemy-2.0.51-cp312-cp312-win_amd64.whl", hash = "sha256:2cf39aabdf48e87c1c2c2ed6d20d33ffa0733b3071ce9c5f66357947dd009080", size = 2146670, upload-time = "2026-06-15T16:23:11.048Z" }, + { url = "https://files.pythonhosted.org/packages/54/fe/a210d52fd1a90ecfae8a78e9d8b27e18d733d60818a8bf250ff690b75120/sqlalchemy-2.0.51-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c2056838b6685b72fdb36c99996cf862753461a62f2e84f4196371d3b2d6a07", size = 2157184, upload-time = "2026-06-15T16:08:50.374Z" }, + { url = "https://files.pythonhosted.org/packages/17/6b/2dce8369b199cb855110e056032f94a9f66dacc2237d3d39c115a86eac56/sqlalchemy-2.0.51-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:483b11bd46bf35fc14c52faf338b04300c9e6ce554bce9b11be85bfec3bc3195", size = 3284735, upload-time = "2026-06-15T16:19:46.934Z" }, + { url = "https://files.pythonhosted.org/packages/53/ff/dbc495b8a14da840faffb353857a72d4190113cac33727906fb997047f0f/sqlalchemy-2.0.51-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1bed1ee8b01da6088210aa9412023326fb98a599ba502e6118308601dcbef77f", size = 3302756, upload-time = "2026-06-15T16:26:41.336Z" }, + { url = "https://files.pythonhosted.org/packages/cf/d5/fde8f4dddcf518ee15ab35a7c6a28acc32c8ba548d1d2aa451f96e6dbb0b/sqlalchemy-2.0.51-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:72ca54c952107ba5cd58854b67a5a6268631289d21651a1235396f3b98b47400", size = 3232055, upload-time = "2026-06-15T16:19:49.286Z" }, + { url = "https://files.pythonhosted.org/packages/67/d1/43d3a0ac955a58601c24fa23038b1c55ee3a1ec02c0f96ebb1eae2bcf614/sqlalchemy-2.0.51-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b3e693d15533a45cd5906f0589f9c35090bef6ef45bf1e8195c424aa0ae06a8d", size = 3269850, upload-time = "2026-06-15T16:26:43.017Z" }, + { url = "https://files.pythonhosted.org/packages/94/df/de669c7054cd47c4439ac34b1b2ee8b804a794791fbb10720e997a2c87c7/sqlalchemy-2.0.51-cp313-cp313-win32.whl", hash = "sha256:b93ab07b5292dbe7e6b8da89475275e7042744283921344b56105f3eeb0f828b", size = 2117721, upload-time = "2026-06-15T16:23:12.36Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8a/403c51d064196bae20a0bc2476577f83a3f8dd299719a97417086b7f2ec5/sqlalchemy-2.0.51-cp313-cp313-win_amd64.whl", hash = "sha256:0f053118c30e53161857a953e4de667d90e274980dccbe5dd3829bbbeece72a5", size = 2143615, upload-time = "2026-06-15T16:23:13.906Z" }, + { url = "https://files.pythonhosted.org/packages/b1/49/a739be2e1d02a96a658eb71ab45d921c874249252358ad24a5bffdd02525/sqlalchemy-2.0.51-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6ea306caaae6bd5afd0a46050003c88f6bf33227377a49298c498c3cb88ff491", size = 2158999, upload-time = "2026-06-15T16:08:51.759Z" }, + { url = "https://files.pythonhosted.org/packages/23/6b/2e0e38cf75c8780eca78d9b2e78164f8bcfd70125e5caa588ff5cbb9c9f4/sqlalchemy-2.0.51-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c45a496d6bc05dec41dcd4c3a2b183723f47473255c159cd80b503c8f246424d", size = 3282539, upload-time = "2026-06-15T16:19:51.065Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a1/e77854cb5336fd37dc3c6ae3b71de242c98caac5725120be0b526b31cbd0/sqlalchemy-2.0.51-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4004ada0aafe8ae1991b2cd1d99c6d9146126e123bd6f883c260d974aa012e54", size = 3287545, upload-time = "2026-06-15T16:26:44.735Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ab/9e17272fd4dac8df3b83c4fbe52b998a1c9d89a843c8c35ff29b74ff7364/sqlalchemy-2.0.51-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0f6bcad487aee1c638d707235682fc96f741de00663619881ab235400d03289e", size = 3230929, upload-time = "2026-06-15T16:19:52.625Z" }, + { url = "https://files.pythonhosted.org/packages/02/3c/52f408ea701781caee975606beccc48845f2aee8711ac29843d612c0306c/sqlalchemy-2.0.51-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:39a76529db6305693d8d4affa58ad5b5e2e18edd62daea628b29b97930b3513d", size = 3252888, upload-time = "2026-06-15T16:26:46.454Z" }, + { url = "https://files.pythonhosted.org/packages/24/16/3efd2ee6bc4ca4693a30a1dd17a91b606cae15d517d2a4746611d9b73ce8/sqlalchemy-2.0.51-cp314-cp314-win32.whl", hash = "sha256:08a204d8b5638717c26a24df18fcf40af45a6b22e35b70b1d62f0113c2e278e8", size = 2120551, upload-time = "2026-06-15T16:23:15.629Z" }, + { url = "https://files.pythonhosted.org/packages/7b/78/55b12e70f45bccc40d9e483925c065027b3b98ea4cbbdf6f8c2546feaf6c/sqlalchemy-2.0.51-cp314-cp314-win_amd64.whl", hash = "sha256:96747bfbadb055466e5b46d572618170046b45ce5a4879167f50d70a5319a499", size = 2146318, upload-time = "2026-06-15T16:23:17.108Z" }, + { url = "https://files.pythonhosted.org/packages/21/db/a9574ed40fed418924b1b1a3e54f47ee3963053b3d3d325a0d36b41f2c08/sqlalchemy-2.0.51-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e5ea1a213be1fcd5e49d9904c3b9939211ded90bc2a64e93f4c01963474285de", size = 2178920, upload-time = "2026-06-15T15:59:56.285Z" }, + { url = "https://files.pythonhosted.org/packages/bf/90/a1bb5c7cbba76b7bc1fbd586d0a5479a7bc9c27b4a8298f22ec9423b2bb3/sqlalchemy-2.0.51-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c6b36ed71f41942bdcd2ad2522be46bfce09d5705be5640ecf19bbc7660e4b7", size = 3566534, upload-time = "2026-06-15T15:58:35.024Z" }, + { url = "https://files.pythonhosted.org/packages/15/4b/481f1fed30e0e9e8dd24aecbb49f29eb57fe7657ece5cf06ee9b84bb97d8/sqlalchemy-2.0.51-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c2c62877097e1a0db401fba5cb4debee33265e5b2a55c4ccb489c02c53b4f72", size = 3535844, upload-time = "2026-06-15T16:02:43.973Z" }, + { url = "https://files.pythonhosted.org/packages/02/71/0aa64aeda645510af0a43f7d9ee70932f0d1dc4263aed34c50ee891d9df3/sqlalchemy-2.0.51-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0378d055e9e8cd6ce4d8dff683bdd3d7d413533c4ee51d67a2b1e0f9eacc0f23", size = 3475355, upload-time = "2026-06-15T15:58:36.592Z" }, + { url = "https://files.pythonhosted.org/packages/05/db/6061db32316446135a3abae5f308d144ab988a34234726042da3e58b1c63/sqlalchemy-2.0.51-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6e46fc36029eff666391e0531e5387b62ce6c4f1d8e50b3fb3099eaca1b42522", size = 3486591, upload-time = "2026-06-15T16:02:45.346Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c9/f14fdf71bb8957e0c7e39db69bbdf12b5c80f4ef775fdfa127bf4e0d6760/sqlalchemy-2.0.51-cp314-cp314t-win32.whl", hash = "sha256:9161cfc9efce70d1715f47d6ff40f79c6778c00d53be4fbc09d70301e4b83ba7", size = 2151313, upload-time = "2026-06-15T16:03:39.127Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c6/673e618e6f4f297e126d9b56ea2f6478708f6c1af4e3223835c22e2c3697/sqlalchemy-2.0.51-cp314-cp314t-win_amd64.whl", hash = "sha256:159bb6ba32059f57ad7375a8f50d844dd2f19d14954ecf820cd33e20debd46b2", size = 2186280, upload-time = "2026-06-15T16:03:40.569Z" }, + { url = "https://files.pythonhosted.org/packages/e2/22/dbf013a12ec759e54a34a119e9e217435b3f71b2dd5c61a7ade0a25dae87/sqlalchemy-2.0.51-py3-none-any.whl", hash = "sha256:bb024d8b621d0be75f4f44ecc7c950450026e76d66dc8f791bb5331d7fed59d5", size = 1944334, upload-time = "2026-06-15T16:09:22.418Z" }, ] [[package]] @@ -3465,7 +3595,8 @@ dependencies = [ { name = "absl-py" }, { name = "grpcio" }, { name = "markdown" }, - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "packaging" }, { name = "pillow" }, { name = "protobuf" }, @@ -3509,7 +3640,7 @@ resolution-markers = [ "python_full_version < '3.12'", ] dependencies = [ - { name = "numpy", marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/c5/cb/2f6d79c7576e22c116352a801f4c3c8ace5957e9aced862012430b62e14f/tifffile-2026.3.3.tar.gz", hash = "sha256:d9a1266bed6f2ee1dd0abde2018a38b4f8b2935cb843df381d70ac4eac5458b7", size = 388745, upload-time = "2026-03-03T19:14:38.134Z" } wheels = [ @@ -3525,7 +3656,7 @@ resolution-markers = [ "python_full_version >= '3.12' and python_full_version < '3.14'", ] dependencies = [ - { name = "numpy", marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/b7/38/5e2ecef5af2f4fd4a89bb8d6240de9458bab4d51a4cbd97aeb3a0cd618e2/tifffile-2026.6.1.tar.gz", hash = "sha256:626c892c0e899d959b9438e7c0e1491dc154a7fead1f1f37a991724a50eceba9", size = 429694, upload-time = "2026-05-31T23:57:12.165Z" } wheels = [ @@ -3609,7 +3740,7 @@ wheels = [ [[package]] name = "torch" -version = "2.12.0" +version = "2.12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cuda-bindings", marker = "sys_platform == 'linux'" }, @@ -3629,30 +3760,26 @@ dependencies = [ { name = "typing-extensions" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/18/62/131124fb95df03811b8260d1d43dcc5ee85ea1a344b964613d7efe77fb08/torch-2.12.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:10802fd383bbfed646212e765a72c37d2185205d4f26eb197a254e8ac7ddcb25", size = 87990344, upload-time = "2026-05-13T14:55:42.154Z" }, - { url = "https://files.pythonhosted.org/packages/12/9c/dda0dbd547dc549839824135f223792fd0e725f28ed0715dda366b7acaa2/torch-2.12.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:c12592630aef72feaf18bd3f197ef587bbfa21131b31c38b23ab2e55fce92e36", size = 426362932, upload-time = "2026-05-13T14:54:15.295Z" }, - { url = "https://files.pythonhosted.org/packages/e2/d2/a7dd5a3f9bdaa7842124e8e2359202b317c48d47d2fc5816fafdf2049adb/torch-2.12.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:415c1b8d0412f67551c8e89a2daca0fb3e56694af0281ba155eaa9da481f58b4", size = 532170085, upload-time = "2026-05-13T14:55:20.788Z" }, - { url = "https://files.pythonhosted.org/packages/12/1b/a61ce2004f9ab0ea8964a6e6168133a127795667639e2ff4f8f2bdb16a65/torch-2.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:dd37188ea325042cb1f6cafa56822b11ada2520c04791a52629b0af25bdfbfd9", size = 122953128, upload-time = "2026-05-13T14:54:52.744Z" }, - { url = "https://files.pythonhosted.org/packages/ef/bb/285d643f254731294c9b595a007eac39db4600a98682d7bca688f42ca164/torch-2.12.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b41339df93d491435e790ff8bcbae1c0ce777175889bfd1281d119862793e6a2", size = 88010197, upload-time = "2026-05-13T14:55:35.414Z" }, - { url = "https://files.pythonhosted.org/packages/79/81/76debf1db1343bd929bbb5d74c89fb437c2ed88eb144712557e7bd3eea45/torch-2.12.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:8fbef9f108a863e7722a73740998967e3b074742a834fc5be3a535a2befa7057", size = 426376751, upload-time = "2026-05-13T14:55:03.353Z" }, - { url = "https://files.pythonhosted.org/packages/de/f0/80026028b603c4650ff270fc3785bdef4bd6738765a9cc5a0f5a637d65a2/torch-2.12.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4b4f64c2c2b11f7510d93dd6412b87025ff6eddd6bb61c3b5a3d892ea20c4756", size = 532261691, upload-time = "2026-05-13T14:52:54.453Z" }, - { url = "https://files.pythonhosted.org/packages/b9/c2/64b06cbb7830fb3cd9be13e1158b31a3f36b68e6a209105ee3c9d9480be0/torch-2.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:8b958caff4a14d3a3b0b2dfc6a378f64dda9728a9dad28c08a0db9ce4dafb549", size = 122988114, upload-time = "2026-05-13T14:54:42.153Z" }, - { url = "https://files.pythonhosted.org/packages/86/ca/01896c80ba921676aa45886b2c5b8d774912de2a1f719de48169c6f755cd/torch-2.12.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:90dd587a5f61bfe1307148b581e2084fc5bc4a06e2b90a20e9a36b81087ff16b", size = 88009511, upload-time = "2026-05-13T14:54:47.411Z" }, - { url = "https://files.pythonhosted.org/packages/a5/04/52bdaf4787eab6ac7d7f5851dff934e4def0bc8ead9c8fd2b69b3e529699/torch-2.12.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:864392c73b7654f4d2b3ae712f607937d0dbb1101c4555fbb41848106b297f39", size = 426383231, upload-time = "2026-05-13T14:53:32.129Z" }, - { url = "https://files.pythonhosted.org/packages/49/8a/94bdecd13f5aaa90d45920b89789d9fe7c6f4af8c3cdd7ce01fcb59908fc/torch-2.12.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5d6b560dfa7d56291c07d615c3bb73e8d9943d9b6d87f76cd0d9d570c4797fa6", size = 532269288, upload-time = "2026-05-13T14:53:49.423Z" }, - { url = "https://files.pythonhosted.org/packages/3e/2f/bdbaaa267de519ef1b73054bf590d8c93c37a266c9a4e24a01bd38b6918f/torch-2.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:3fee918902090ade827643e758e98363278815de583c75d111fdd665ebffde9f", size = 122987706, upload-time = "2026-05-13T14:54:00.335Z" }, - { url = "https://files.pythonhosted.org/packages/9b/ad/e95e822f3538171e22640a7fbe839a1fdb666600bf6487025de2ff03b11a/torch-2.12.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:10ee1448a9f304d3b987eb4656f664ba6e4d7b410ca7a5a7c642199777a2cf88", size = 88319556, upload-time = "2026-05-13T14:54:05.574Z" }, - { url = "https://files.pythonhosted.org/packages/b7/07/055d06d985b445d67422d25b033c11cf55bbb81785d4c4e68e28bca5820e/torch-2.12.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:af68dbf403439cae9ceaeaaf92f8352b460787dcd27b92aa05c40dd4a19c0f1e", size = 426397656, upload-time = "2026-05-13T14:52:38.84Z" }, - { url = "https://files.pythonhosted.org/packages/43/94/b0b4fdc3014122e0a7302fb90086d352aa48f2576f0b252561ebb38c01a8/torch-2.12.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:a6a2eebb237d3b1d9ad3b378e86d9b9e0782afdea8b1e0eba6a13646b9b49c07", size = 532183124, upload-time = "2026-05-13T14:53:16.178Z" }, - { url = "https://files.pythonhosted.org/packages/d8/c8/052405e6ad05d3237bfe5a4df78f917773956f8e17813a2d44c059068b74/torch-2.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2140e373e9a51a3e22ef62e8d14366d0b470d18f0adf19fdc757368077133a34", size = 123232462, upload-time = "2026-05-13T14:52:27.26Z" }, - { url = "https://files.pythonhosted.org/packages/67/dc/ac069f8d6e8be701535921141055293b0d4819d3d7f224a4612cf157c7f9/torch-2.12.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:f7dfae4a519197dfa050e98d8e36378a0fb5899625a875c2b54445005a2e404e", size = 88027282, upload-time = "2026-05-13T14:53:05.258Z" }, - { url = "https://files.pythonhosted.org/packages/33/c3/1c1eb00e34555b536dddf792676026a988d710ed36981aa00499b36b0620/torch-2.12.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:891c769072637c74e9a5a77a3bc782894696d8ffec83b938df8536dee7f0ba78", size = 426386961, upload-time = "2026-05-13T14:51:28.406Z" }, - { url = "https://files.pythonhosted.org/packages/cd/d4/7e730dba0c7032a4154dc9056b76cf9625515e030e269cfbf8098fcfee7d/torch-2.12.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:e2ad3eb85d39c3cab62dfa93ed5a73516e6a53c6713cb97d004004fe089f0f1f", size = 532272265, upload-time = "2026-05-13T14:51:59.308Z" }, - { url = "https://files.pythonhosted.org/packages/f1/b4/92c80d1bbfee1c0036c06d1d2155a3065bd2423134c83bf8a47e65cd6b9b/torch-2.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:c66696857e987efb8bc1777a37357ec4f60ab5e8af6250b83d6034437fa2d8f3", size = 122987138, upload-time = "2026-05-13T14:51:45.942Z" }, - { url = "https://files.pythonhosted.org/packages/7b/78/2e12b37ce50a19a037d7bc62d652a5a8f27385a7b05859d6bc9204f20cfe/torch-2.12.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:b4556715c8572758625d62b6e0ae3b1f76c440221913a6fb5e100f321fb4fb02", size = 88320100, upload-time = "2026-05-13T14:51:39.955Z" }, - { url = "https://files.pythonhosted.org/packages/56/5e/83c450ec7b0bb40a7b74611c1b5440f9260e33c54c90d556fd4a1f0fd955/torch-2.12.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a43ac605a5e13116c72b64c359644cce0229f213dde48d2ae0ae5eb5becf7feb", size = 426391871, upload-time = "2026-05-13T14:52:14.989Z" }, - { url = "https://files.pythonhosted.org/packages/c9/e9/1a0b575d98d0afedd8f157d23fa3d2759421483660448e60d0a4b10b6daa/torch-2.12.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6a7512adfdd7f6732e40de1c620831e3c75b39b98cef60b11d0c5f0a76473ec5", size = 532192241, upload-time = "2026-05-13T14:51:07.795Z" }, - { url = "https://files.pythonhosted.org/packages/88/21/afadd25ecd81b3cea1e11c73cf1ab41a983a50271548c3ec7ec3b9efc3e9/torch-2.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5f96b63f8287f66a005dd1b5a6abba2920f11156c5e5c4d815f3e2050fd1aa16", size = 123231092, upload-time = "2026-05-13T14:51:18.854Z" }, + { url = "https://files.pythonhosted.org/packages/59/38/7028d3be540f1dcdf41660a2b01d0c51d2cb73915fe370d84e4d277a6d47/torch-2.12.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ef81f503912effea2ce3d9b12a2e3a6ed488943e91271c90c7a829f60baf6aa2", size = 87975425, upload-time = "2026-06-17T21:08:34.094Z" }, + { url = "https://files.pythonhosted.org/packages/5a/e3/750b3e3548635ceac03ba255daa26dbc7ed66ca3484dc4b4d955ab7f4501/torch-2.12.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:107df6888624bdea41508f9aeb6149d9333c737a5530ceecb56c904e811369ae", size = 426379894, upload-time = "2026-06-17T21:06:55.077Z" }, + { url = "https://files.pythonhosted.org/packages/dc/ca/ed24783da629ff3e640ba3f70a7639e9045d3d88b93ee6bc47b8a28a1f2c/torch-2.12.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:6e29e7e74d05bda7d955c75e99459f878ebd970ef851b4057edbd3b34a5eb4a3", size = 532169264, upload-time = "2026-06-17T21:08:17.65Z" }, + { url = "https://files.pythonhosted.org/packages/46/61/c63f0158446f3a98ea672b004d761b848911eba567ea4a624c7db5aadc04/torch-2.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:a513506cfda3c1c78dabeb6574c1597538c0254b3d39af174dde35d8177f4ce3", size = 122953086, upload-time = "2026-06-17T21:08:27.69Z" }, + { url = "https://files.pythonhosted.org/packages/f0/54/efb7ebca77970012b0cc21687a55d70eb2ba514b2c2b8e18d9fb1222f3be/torch-2.12.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:d2dd0f2c5f7ccbddaf34cade0deaf476808368f902b9cdb7f36a2ab42301bc0e", size = 87991951, upload-time = "2026-06-17T21:07:49.309Z" }, + { url = "https://files.pythonhosted.org/packages/1e/00/4210d76ca7424981f04033ebe7e48816ab83287a62538747a58825db770c/torch-2.12.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:2de4e19b88a481482c6c75291f2d6a52eda3ce51f311b29aa9b68499c830c07c", size = 426382721, upload-time = "2026-06-17T21:06:41.842Z" }, + { url = "https://files.pythonhosted.org/packages/76/1f/bc9f5a5aa569307076365f25afcebacb22e9c754b1bcfbaaa146627c7fda/torch-2.12.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:649e4ced014ba646f76f8cb9c9726735a6323eb321b7919f942790a923f90921", size = 532261322, upload-time = "2026-06-17T21:06:06.673Z" }, + { url = "https://files.pythonhosted.org/packages/9e/49/c549461daa008159d006a76a991fbc2f26fa8bac27a4030c858463dcb20f/torch-2.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:e86550597877fb272ddc52db2f85b82cb601ea7bd932576a0340152cae2200b3", size = 122988095, upload-time = "2026-06-17T21:07:44.9Z" }, + { url = "https://files.pythonhosted.org/packages/ff/4a/0300261818e1560d72cc160ac826005507e8b7ca0a35788b591436d05b4a/torch-2.12.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:c75e93173c700bccd6bfcc4a9d19ce242ab6dacd1f1781483027a16239b9e650", size = 87992358, upload-time = "2026-06-17T21:07:40.299Z" }, + { url = "https://files.pythonhosted.org/packages/30/a7/874a5ca05e8f159211dca7921060f7057acc1adb26431e119fd150623efc/torch-2.12.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:fcb61ccd20784b62bdd78ec84238a5cfb383b4994902e03bac95505ab360884c", size = 426386134, upload-time = "2026-06-17T21:07:31.481Z" }, + { url = "https://files.pythonhosted.org/packages/e1/75/20bb8fe9c1ad6538cce8cd0391b51927ae5af0b17ed1eab44b8824465dc1/torch-2.12.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:f4afc8083dff08719edbea346644476e3cec0cf40ebe256be0ee5d5b7c7e8c0d", size = 532268019, upload-time = "2026-06-17T21:05:37.925Z" }, + { url = "https://files.pythonhosted.org/packages/d1/fa/824ddb662af55b2eabc0dbb7b57c7c0b1bcd93693754a2b8509ec4d16490/torch-2.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:f92609e3b3ce72f25e2eb780d043ced2480c1a86c47c852604fc7a9108648386", size = 122987777, upload-time = "2026-06-17T21:07:09.49Z" }, + { url = "https://files.pythonhosted.org/packages/63/b7/1b49fe7086ea36839cc80abc43174c43d0ab6f676c0891c871c162f44fe3/torch-2.12.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:e9b6f7d2dd66ea87a3ae620069d31335d594c06effb1a383bdd21cfe61e44ece", size = 88010025, upload-time = "2026-06-17T21:07:03.934Z" }, + { url = "https://files.pythonhosted.org/packages/d7/06/5b44063a6545036dcc680d2d303b137d9176cfb2cc1e1863e3ef94abeb52/torch-2.12.1-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:7973ccd3d2cd35c74449213f7bded199bec6c6247e705cbeda7407af79703d91", size = 426392891, upload-time = "2026-06-17T21:05:52.261Z" }, + { url = "https://files.pythonhosted.org/packages/f8/dd/c9ce9a4b0eb3c5bb92d9ea56766e2c22559f0b45171149188494edcce80f/torch-2.12.1-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:c64ac4aac16be5e296dcd912305605804b203333c690bf98c55bc09494ee92ad", size = 532272494, upload-time = "2026-06-17T21:06:22.72Z" }, + { url = "https://files.pythonhosted.org/packages/21/7c/f3a601fc1b1f663ff269bfe553654e638651939aa6563e8daa7167c33098/torch-2.12.1-cp314-cp314-win_amd64.whl", hash = "sha256:f6dc4caf7eb4adb38a2d9f536b51db56310fdd1254e69a2d96767e1367c892b3", size = 122987254, upload-time = "2026-06-17T21:06:33.199Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/b8087556cf81ddd808dbeb34afb8396d7ae7a1694ab489f08b1a0004e7d0/torch-2.12.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:2afbb2bdaa8a95040e733f05492ddf133c3967c9b7ce0abd218d704b6cab437d", size = 88303173, upload-time = "2026-06-17T21:05:06.603Z" }, + { url = "https://files.pythonhosted.org/packages/4a/07/fe09d1699fbed2afa10ebc692ff2b99d113f2605b6748cea633989e2789a/torch-2.12.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:97eba061fcb042fed191400b15568990073d67eaacaa6ee9b7ca01dd8b790fe9", size = 426404009, upload-time = "2026-06-17T21:04:57.557Z" }, + { url = "https://files.pythonhosted.org/packages/2e/f7/0ce4f6c1962c60ded7270e0a9eb560fb615c92b89d332cf9e3dff36d5ecc/torch-2.12.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:3867b861391701012adb2df93360efb88494dca245a185e3bb7624495cfe3f33", size = 532184292, upload-time = "2026-06-17T21:05:17.526Z" }, + { url = "https://files.pythonhosted.org/packages/70/db/e384c12aba30320ca92aaaf557456cbcb26f04b4df307728bb8f019f5000/torch-2.12.1-cp314-cp314t-win_amd64.whl", hash = "sha256:dd15595f8fc764cffde8c6361a3beb6ef69a028c851b1b3e70e077f615980d4e", size = 123231142, upload-time = "2026-06-17T21:05:27.061Z" }, ] [[package]] @@ -3670,7 +3797,8 @@ version = "1.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "lightning-utilities" }, - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "packaging" }, { name = "torch" }, ] @@ -3681,38 +3809,35 @@ wheels = [ [[package]] name = "torchvision" -version = "0.27.0" +version = "0.27.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "pillow" }, { name = "torch" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/d6/a7e71e981042d5c573e2e61891b9023b190c88adb75b18bed8594371250c/torchvision-0.27.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:df0c166b6bdf7c47f88e81e8b43bc085451d5c50d0c5d1691bc474c1227d6fed", size = 1758812, upload-time = "2026-05-13T14:57:16.662Z" }, - { url = "https://files.pythonhosted.org/packages/93/f9/f542fb7e4476603fb237ebdc64369a7d11f18eb5a129aa2559cbdb710aee/torchvision-0.27.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:9bb9251f64b854124efed95d02953a89f7e2726c3ca662d7ea0151129157297f", size = 7831148, upload-time = "2026-05-13T14:57:08.37Z" }, - { url = "https://files.pythonhosted.org/packages/f6/61/7aa7cc2c9e8750027f6fb9ae3a7393ef43860bcdfe3966e2f71fee800e31/torchvision-0.27.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:f44453f107c296d5446a79f7ac59733ad8bf5ddfa04c53805dfbae298a42a798", size = 7575519, upload-time = "2026-05-13T14:56:50.552Z" }, - { url = "https://files.pythonhosted.org/packages/19/aa/929b358b1a643849b81ec95569938044cc37dc65ab10c84eb6d82fe1bfbb/torchvision-0.27.0-cp311-cp311-win_amd64.whl", hash = "sha256:b4aacff70ea4b7377f996f9048989c850d221fef33658ddbcae42aa5bd4ca11a", size = 3749475, upload-time = "2026-05-13T14:57:11.007Z" }, - { url = "https://files.pythonhosted.org/packages/9e/c8/5cd91932f7f3671b0743dc4ae1a4c16b1d0b45bf4087976277d325bda718/torchvision-0.27.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:1a6dd742a150645126df9e0b2e449874c1d635897c773b322c2e067e98382dfe", size = 1758824, upload-time = "2026-05-13T14:57:15.227Z" }, - { url = "https://files.pythonhosted.org/packages/d9/36/7fb7d19477b3d93283b52fea11fa8ee30ab9064a08c97b4a6b91445e26cb/torchvision-0.27.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:65772ff3ec4f4f5d680e30019835555dd239e7fefee4b0a846375fe1cb1592ef", size = 7831034, upload-time = "2026-05-13T14:57:06.483Z" }, - { url = "https://files.pythonhosted.org/packages/62/43/dfd894c3f8b01b5b33fde990f0159c1926ebc7b6e2c4193e2efb7da3c4cb/torchvision-0.27.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:7a9966a088d06b4cf6c610e03be62de469efa6f2cd2e7c7eed8e925ed6af59ac", size = 7579774, upload-time = "2026-05-13T14:56:59.337Z" }, - { url = "https://files.pythonhosted.org/packages/ff/0c/722e989f9cf026e97ef7cb24a9bb1859e099f72d247ae35388fb89729f73/torchvision-0.27.0-cp312-cp312-win_amd64.whl", hash = "sha256:2c037709072ca9b19750c0cbe9e8bb6f91c9a1be1befa26df33e281deccbd8c7", size = 4021073, upload-time = "2026-05-13T14:57:00.848Z" }, - { url = "https://files.pythonhosted.org/packages/d8/ae/36547812e6e047c1d80bcacd1b17a340612b08a6e876e0aabf3d0b9228b0/torchvision-0.27.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:41d6dae73e1af09fa82ded597ae57f2a2314285acde54b25890a8f8e51b999d7", size = 1758826, upload-time = "2026-05-13T14:57:05.262Z" }, - { url = "https://files.pythonhosted.org/packages/ae/30/32c4ea842738728a14e3df8c576c62dedcf5ae5cb6a5c984c6429ebe7524/torchvision-0.27.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:70f071c6f74b60d5fe8851636d8d4cd5f4fa29d57fd9348a87a6f17b990b95ba", size = 7789501, upload-time = "2026-05-13T14:56:57.786Z" }, - { url = "https://files.pythonhosted.org/packages/f6/24/4d0d48684251bd0673f87d633d5d88ab00227983b00591156eed2f86c8d5/torchvision-0.27.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:aaafa6962c9d91f42503de1957d6fa349907d028c06f335bd95da7a5bc57147d", size = 7579868, upload-time = "2026-05-13T14:56:41.618Z" }, - { url = "https://files.pythonhosted.org/packages/ba/da/e6edd051d2ba25adf23b120fa97f458dff888d098c51e84724f17d2d1470/torchvision-0.27.0-cp313-cp313-win_amd64.whl", hash = "sha256:aee384a2782c89517c4ab9061d2720ba59fd2ffe5ef89d0a149cc2d43abdf521", size = 4092700, upload-time = "2026-05-13T14:57:09.729Z" }, - { url = "https://files.pythonhosted.org/packages/fa/23/95dfa40431360f42ca949bf861434bed51164adfa8fb9801e05bf3194f50/torchvision-0.27.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:c5121f1b9ab09a7f73e837871deb8321551f7eaeb19d87aa00de9191968eae44", size = 1845008, upload-time = "2026-05-13T14:57:03.768Z" }, - { url = "https://files.pythonhosted.org/packages/23/b9/9dbdf76b2b49a75ba8088df6f7c755bdb520afb6c6dbac0102b46cde5e99/torchvision-0.27.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:1c01f0d1091ae22b9dfc082b0a0fe5faaf053686a29b4fb082ba7691375c73cf", size = 7791430, upload-time = "2026-05-13T14:56:56.206Z" }, - { url = "https://files.pythonhosted.org/packages/5c/6a/e4a16cf2f3310c2ea7760dc5d9054496844391e0f4c1fae87fefac2f3d9e/torchvision-0.27.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:dadea3c5ecfd05bbb2a3312ab0374f213c58bf6459cb059122e2f4dfe13d10ed", size = 7668441, upload-time = "2026-05-13T14:57:02.127Z" }, - { url = "https://files.pythonhosted.org/packages/00/70/01b6461117a6a94b5af3f8ee166bb0f045056f3cf187750c110dabfdfffa/torchvision-0.27.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a49e55055a39a8506fe7e59850522cab004efb2c3839f6057658889c1d69c815", size = 4141602, upload-time = "2026-05-13T14:56:53.449Z" }, - { url = "https://files.pythonhosted.org/packages/92/22/c0633677b3b3f3e69554a21ac087bf705f829c40cd5e3783507b8c006681/torchvision-0.27.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:c1fac0fc2a7adf29481fc1938a0e7845c57ba1147a986784109c4d98f434ea8c", size = 1758818, upload-time = "2026-05-13T14:56:54.988Z" }, - { url = "https://files.pythonhosted.org/packages/48/e8/55f9d9667b56dae470e69e31beac9b00d458ea393feec1aae95cc4f3f1c9/torchvision-0.27.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:cbf89764fc76f3f17fbf80c12d5a89c691e91cb9d82c38412aaf0568655ffb19", size = 7789667, upload-time = "2026-05-13T14:56:48.858Z" }, - { url = "https://files.pythonhosted.org/packages/00/bc/6f8681daf3bbc4c315bb0005110f99d28e3ecd675bf9c8f2c0d393fbac7a/torchvision-0.27.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:91f61b9865423037c327eb56afa207cc72de874e458c361840db9dcf5ce0c0eb", size = 7579848, upload-time = "2026-05-13T14:56:38.209Z" }, - { url = "https://files.pythonhosted.org/packages/19/6c/8d8020e6bd1e46c53e487c9c4e9457a07f2ee28931028fb5d71e2da40adc/torchvision-0.27.0-cp314-cp314-win_amd64.whl", hash = "sha256:5bb82fc3c55daf1788621e504310b0a286f1069627a8742f692aebb075ef25a7", size = 4119284, upload-time = "2026-05-13T14:56:46.625Z" }, - { url = "https://files.pythonhosted.org/packages/8d/7e/e78c48662a8d551606efdbe11c6b9c1d6d2391b92cd0e4591b9e6a2412b8/torchvision-0.27.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:2c4099a15150143b9b034730b404a56d572efe0b79489b4c765d929cb4eac7f3", size = 1758828, upload-time = "2026-05-13T14:56:52.293Z" }, - { url = "https://files.pythonhosted.org/packages/21/dd/d03ee9f9ee7bf11a8c7c776fb8e7fd6102f59c013791a2a4e5175bd6cba7/torchvision-0.27.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:b4c6bb0a670dcba017b3643e21902c9b8a1cc1c127d602f1488fa29ec3c6e865", size = 7790618, upload-time = "2026-05-13T14:56:44.721Z" }, - { url = "https://files.pythonhosted.org/packages/39/08/4002336a74742be70728603ec1769feb2b55e0d19c532c9ec9f92008de76/torchvision-0.27.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:1c2db4bde82bc48ebff73436a6adf34d4f809448268a70d9a1285f5c8f92313d", size = 7580217, upload-time = "2026-05-13T14:56:43.274Z" }, - { url = "https://files.pythonhosted.org/packages/ed/cb/4dd4783eb3565f526ba6e64b6f6ca26c00eacc924cdfe60455db9d91b84b/torchvision-0.27.0-cp314-cp314t-win_amd64.whl", hash = "sha256:72bf547e58ddb948689734eed6f4b6a2031f979dba4fb08e3690688b392e929f", size = 4226392, upload-time = "2026-05-13T14:56:40.235Z" }, + { url = "https://files.pythonhosted.org/packages/64/46/bc0ebd93282aeedc1759f054a252c6fadf14b42a0535db3233c85cce4ae5/torchvision-0.27.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ad8743a9c12c8c124ad0a1491e54c3ca0c749e91e374e3d92136060b22c9e0f4", size = 1852118, upload-time = "2026-06-17T21:09:32.448Z" }, + { url = "https://files.pythonhosted.org/packages/b2/00/752adc57b6aa8bb833f5b0672acb9538aa5535d64998b9d8dd48ee51fa80/torchvision-0.27.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:a726707e4cbe438fcc507d787af7acf6bca52de30bf4b03579f1dfc0675da829", size = 7831256, upload-time = "2026-06-17T21:09:26.767Z" }, + { url = "https://files.pythonhosted.org/packages/fa/8a/c474fb27faba02e84dc40e0ac9ea1aa828d6d3557a378f7d0a22468bb2a3/torchvision-0.27.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a1d6a123009af59ad288459f579f67a65cbe8f59372dc7b97e41bc01a6a9b767", size = 7659995, upload-time = "2026-06-17T21:09:25.325Z" }, + { url = "https://files.pythonhosted.org/packages/eb/7c/e254f8e242a921adc2cc62c11674fa8a16d33e0a1b6c6f5436cb91628ee7/torchvision-0.27.1-cp311-cp311-win_amd64.whl", hash = "sha256:f3b57a984283896f15c9698562418282f828332886c77315bf269936e6ba0280", size = 3807497, upload-time = "2026-06-17T21:09:31.234Z" }, + { url = "https://files.pythonhosted.org/packages/88/82/2e8fdc19e4f0bbe31d403a55d78318bcea4afcd3083e1e4700ef61ebb893/torchvision-0.27.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:448abfc3baba984da4577f737209e445da6be93e3b5f4799d90162bf61e3f485", size = 1852105, upload-time = "2026-06-17T21:09:33.695Z" }, + { url = "https://files.pythonhosted.org/packages/43/42/103fa8f9366cfd1329fe449d6b1a25a640c0c17862ed48f21c4af94af322/torchvision-0.27.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:9edfb5a549fc2f30ccadb24eca907901e92e426c91a59316be6703a9360e5098", size = 7830902, upload-time = "2026-06-17T21:09:29.739Z" }, + { url = "https://files.pythonhosted.org/packages/97/70/fa6052a42110a3657fc94073648da6171220469f4bf9f27e6a0b9378075c/torchvision-0.27.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ae3d49e57c4abc8eafc1a1971f80fc4948a6268fa69340737ca4466936def080", size = 7664211, upload-time = "2026-06-17T21:09:17.206Z" }, + { url = "https://files.pythonhosted.org/packages/d0/95/27aca854da7e536a339f46bab1ef67823ac2ac97c59ab2b3203b373d46cf/torchvision-0.27.1-cp312-cp312-win_amd64.whl", hash = "sha256:0b6e3aa98b7433506bbce1d0d05cb13ec787fc6eb8c5fbd998b26ce05f047543", size = 4079076, upload-time = "2026-06-17T21:09:15.907Z" }, + { url = "https://files.pythonhosted.org/packages/32/bb/b21e0f598ca191bb2a9e9fda2fee37c06ad113313b43c6769dbefa0e921d/torchvision-0.27.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:d60311a6d08df905f9656a3a312f0a8f55f0d46321bc737bad30a8dec9644309", size = 1852110, upload-time = "2026-06-17T21:09:22.577Z" }, + { url = "https://files.pythonhosted.org/packages/2f/90/d61171daa5d6cd5f9315f84f9ef947b047a9fdf283d53241327045a8dd6d/torchvision-0.27.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:08aa33bc8e062cca32aefa90ac714916c5a855cbe1ab4c6148fc0453eb40ca5a", size = 7789476, upload-time = "2026-06-17T21:09:13.105Z" }, + { url = "https://files.pythonhosted.org/packages/b8/dc/b21d7801562c23a770e7037989814582f22ca4db479204293561de4b62e8/torchvision-0.27.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:916448be4b19676677b0dbf47d08f68b7955ea0abec7fc79340c31e217a824ba", size = 7664256, upload-time = "2026-06-17T21:09:07.549Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b3/4386976ff77eda55f0aed504a288564f3ff8d170b6db49ee22e172eddfac/torchvision-0.27.1-cp313-cp313-win_amd64.whl", hash = "sha256:18bc906235bfa901c135acd239f05b8c8ab90d502830cf1ef2cba3301e1f8a23", size = 4150710, upload-time = "2026-06-17T21:09:14.457Z" }, + { url = "https://files.pythonhosted.org/packages/ff/74/1d237c61f665bf46d02e15f67c9d40be42b1b634f87164b9cefd257450e7/torchvision-0.27.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:9f5ef59ad60e695796eca6b64e97cb9b21b9d5463cac5ac0ef86cfb72b6e5db9", size = 1852112, upload-time = "2026-06-17T21:09:21.445Z" }, + { url = "https://files.pythonhosted.org/packages/24/84/f0d772e7ed85891f084755bd5d7f6f7fd279992a02652c653c1c8429dd84/torchvision-0.27.1-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:ab2f8047c2da5bf6742fec6da86840e5feaeb0cea76930d0536f3520df31e166", size = 7789751, upload-time = "2026-06-17T21:09:11.51Z" }, + { url = "https://files.pythonhosted.org/packages/76/68/3febd41b6eef453a83fb7a0178446334fbb0405eb4b0c40b00efaf99a2dc/torchvision-0.27.1-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:b44ef28ad1963f8cba5bf82f3564c454c74be300df9f79efa43f773312d17d6c", size = 7664350, upload-time = "2026-06-17T21:09:04.486Z" }, + { url = "https://files.pythonhosted.org/packages/52/49/a23e199faf29e42a90f7d6b76437ade5d17e3185da3c64d368973ba8243e/torchvision-0.27.1-cp314-cp314-win_amd64.whl", hash = "sha256:b3e9bc71854fddbf94ddb69ed8d88983945f3f28f78ee104214b0088669af66a", size = 4177297, upload-time = "2026-06-17T21:09:10.273Z" }, + { url = "https://files.pythonhosted.org/packages/ba/48/b3240eaf0fe3676dcf677ce8930ef477fe77d7f69ebe58ca8d0941384952/torchvision-0.27.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c2fd9902f23b56b6ac667213171672fb6c89287ff011918b04af053852a2c4eb", size = 1852118, upload-time = "2026-06-17T21:09:20.223Z" }, + { url = "https://files.pythonhosted.org/packages/73/01/6c8f3158994a9e5bb0c7b1bacc361d60e015ad79487af88fa4d7ce72c2b6/torchvision-0.27.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:8abb6d5cacd56486ca2240e5580750e53ac559412e472ea6a3cee83231a77ca7", size = 7791242, upload-time = "2026-06-17T21:09:02.062Z" }, + { url = "https://files.pythonhosted.org/packages/1b/e6/f66733fc411a9ce070c0d899c1ae562ff11654a0bc708511e23efe9d6872/torchvision-0.27.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:d11da1ce8a5cc7fc527f2d5e0fe25efba93687897fe9339382b593910b1d1c6e", size = 7664934, upload-time = "2026-06-17T21:09:06.221Z" }, + { url = "https://files.pythonhosted.org/packages/90/aa/d6179812ec52b70a7a8f5e99fe7937895d28c535106df1ca0d03f5f51425/torchvision-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:12deaee20d0d9dec6302025d3f93354266befeb692f5c50bca0137b395598b9e", size = 4284412, upload-time = "2026-06-17T21:09:08.989Z" }, ] [[package]] @@ -3734,14 +3859,14 @@ wheels = [ [[package]] name = "tqdm" -version = "4.68.2" +version = "4.68.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/85/05/0d5260f1f1ca784f4a4a0def9cbe6affe587f5b4025328d446c3d67765f4/tqdm-4.68.2.tar.gz", hash = "sha256:89c230e8dbc67c7615c142487111222f878c77427ea09549960f62389e258add", size = 171923, upload-time = "2026-06-09T13:26:42.539Z" } +sdist = { url = "https://files.pythonhosted.org/packages/87/d7/0535a28b1f5f24f6612fb3ff1e89fb1a8d160fee0f976e0aa6803862134b/tqdm-4.68.3.tar.gz", hash = "sha256:00dfa48452b6b6cfae3dd9885636c23d3422d1ec97c66d96818cbd5e0821d482", size = 170596, upload-time = "2026-06-17T07:36:52.105Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/75/1a0392bcc21c44dcdf87b3cf2d137e7829be2c083a1e38d44efca3d57a16/tqdm-4.68.2-py3-none-any.whl", hash = "sha256:d4240441fb5353290b87d6a85968c9decc131a99b8c7faa28269d829de669ede", size = 78578, upload-time = "2026-06-09T13:26:40.731Z" }, + { url = "https://files.pythonhosted.org/packages/d8/8e/bb97bb0c71802080bfc8952937d174e49cfc50de5c951dd47b2496f0dcdb/tqdm-4.68.3-py3-none-any.whl", hash = "sha256:39832cc2def2789a6f29df83f172db7416cea70052c0907a57801c5f2fdccb03", size = 78337, upload-time = "2026-06-17T07:36:50.132Z" }, ] [[package]] @@ -3755,21 +3880,19 @@ wheels = [ [[package]] name = "triton" -version = "3.7.0" +version = "3.7.1" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/c1/5d842314bb6c78442cc60437928781701c6050b8d479bc2a1aed691d37ca/triton-3.7.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a9e71fc392675fac364e0ecf4ef3f76f85b7f5433a16f4c3c5fe5f05a52c85fe", size = 188480277, upload-time = "2026-05-07T19:05:03.231Z" }, - { url = "https://files.pythonhosted.org/packages/13/31/8315ea5f8dd18e60970b3022e3a8b93fd37e0b784fbbef86e10c8e6e5ca1/triton-3.7.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22bacffce443f54593dd20f05294d5a40622e0ea9ab632816f87154504356221", size = 201415942, upload-time = "2026-05-07T18:46:06.479Z" }, - { url = "https://files.pythonhosted.org/packages/f7/13/ec05adfcd87311d532ba61e3af143e8be59fcd26675884c4682841406a20/triton-3.7.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4bf49b00a7a377a68a6da603a876e797614e6455a80e9021669c476a953ad9a", size = 188505104, upload-time = "2026-05-07T19:05:09.843Z" }, - { url = "https://files.pythonhosted.org/packages/62/7b/468a576e35beef1426e0828e28e9ba9e65f5474d496f16ee126c15646324/triton-3.7.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f111161d49bf903c0eaedde3962353a3d841c08a836839b7cc1025b8426efcf", size = 201457567, upload-time = "2026-05-07T18:46:13.505Z" }, - { url = "https://files.pythonhosted.org/packages/01/e1/a59a583de59b8f62c495d67c80ee3ea97d09e91ac80c4c6e76456ed8d8ac/triton-3.7.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:abdf6beaa89b1bcfb9a43cd990536ce66091a997841a4814b260b7bee4c88c3c", size = 188503209, upload-time = "2026-05-07T19:05:17.935Z" }, - { url = "https://files.pythonhosted.org/packages/30/b1/b7507bb9815d403927c8dd51d4158ed2e11751a92dbc118a044f247b6848/triton-3.7.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a35d7afe3f3f058e7ec49fcce09794049e0ffc5c59019ac25ec3413741b8c4e7", size = 201453566, upload-time = "2026-05-07T18:46:20.427Z" }, - { url = "https://files.pythonhosted.org/packages/a6/8f/0bea7a6a0c989315c9135a1d7fb37e41905cfb3a17cbc1f10044ebd4cc3a/triton-3.7.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc1d61c172d257db80ddf42595131fb196ad2e9bdd751e90fe2ef13531734e8b", size = 188612899, upload-time = "2026-05-07T19:05:24.955Z" }, - { url = "https://files.pythonhosted.org/packages/e1/02/d96f57828d0912aec733b9bc7e0e7dbfd2c6f079a8fa433ac25cb93d1a30/triton-3.7.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:70fb9bbdc9f400afc54bbf6eb2670af28829a6ae3996863317964783141daf56", size = 201553816, upload-time = "2026-05-07T18:46:27.49Z" }, - { url = "https://files.pythonhosted.org/packages/40/fb/82a802dac4689f2a2fb2e69302e6a138eecc3e175bbe976ba3cfc717683a/triton-3.7.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a44a8476d0d3571eac4e4d1048e1ff75aad81a09ff4602ccfc56c6dea1672e", size = 188507879, upload-time = "2026-05-07T19:05:32.209Z" }, - { url = "https://files.pythonhosted.org/packages/8f/af/9904ec6d3c93d9b24e5ec360445bbdf758b7f00bfbeedb89cb0eb64eb8bb/triton-3.7.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b9b85e72968a9d8bba5ddb24e9b64aaabaf48affb042f2755cb7cfa92b7531ce", size = 201460637, upload-time = "2026-05-07T18:46:34.749Z" }, - { url = "https://files.pythonhosted.org/packages/a1/f9/4835a8ea746b88727d8899f4e3ccce4f9cacb38abfc3bb0a638266c53111/triton-3.7.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18a160de426fd99f92b0baf509045360afbd3bfaa0b4a5171dde800ec9f09684", size = 188608706, upload-time = "2026-05-07T19:05:39.218Z" }, - { url = "https://files.pythonhosted.org/packages/c1/68/fa86e5a39608000f645535b2c124920126327ab731f8c4fafd5b07ff8d4b/triton-3.7.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce061073102714b725f3660ec6939d94a1da7984b3aa99c921417cae273672f5", size = 201546766, upload-time = "2026-05-07T18:46:42.088Z" }, + { url = "https://files.pythonhosted.org/packages/7b/f9/19d842d06a08559534fa1eaab6ca551b1bcf40f06620bddec1babaa2772d/triton-3.7.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4a0e1cd4c4a76370ed74a8432a53cea28716827d19e40ffc732233e35ceb3f6", size = 184664887, upload-time = "2026-06-17T20:03:42.913Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5e/fce69606f7f240297f163e25539906732b199530d486ce67ae319877e821/triton-3.7.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6744957e9fd610a29680ec2346057d0c86948ed3812468670719f391e94b44a5", size = 197701306, upload-time = "2026-06-17T19:53:13.673Z" }, + { url = "https://files.pythonhosted.org/packages/94/fa/f856e24deb462d5f18bd4b5a746957862ab9b6ee5834bda60605ec348366/triton-3.7.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9497f2e696ee368862a181a90b2dcc03ca978cc4f602abd67c7d81022a6988e1", size = 184692359, upload-time = "2026-06-17T20:03:48.288Z" }, + { url = "https://files.pythonhosted.org/packages/c4/6f/fb96d15db6f36d6eae4cafb998c2e0353bf59d7c4ea1662d7497f269134a/triton-3.7.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e40869937a68206ec70d7f25bb7ec6433cb083f9135e1f36dbd318dc449a728", size = 197719725, upload-time = "2026-06-17T19:53:20.419Z" }, + { url = "https://files.pythonhosted.org/packages/00/42/c5089d4d9327fcd1e862c599cc2927f39418f84dd11a84cb2ccff9d4787a/triton-3.7.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cdbfc09d9ec58bc5e68321525653220de7515c199e7a8097a97c85e62b52cd0a", size = 184694629, upload-time = "2026-06-17T20:03:53.444Z" }, + { url = "https://files.pythonhosted.org/packages/07/42/2c3ac59253ae8892b6f307875263dd23dc875cdf732d3aea40d6d41fb7cb/triton-3.7.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:58c0e131da05134a2a4788ccbcc0c1105cf0f54c8e98f19e34cd465396dc15eb", size = 197729241, upload-time = "2026-06-17T19:53:27.801Z" }, + { url = "https://files.pythonhosted.org/packages/40/71/e01aa7ad573883ed9456f130226babdec70b005e098c4d6226a6238e761b/triton-3.7.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe4ea396a06171f1f1f58cbd39c70b09294398f7dd7c620939bab54ad6f934fa", size = 184705764, upload-time = "2026-06-17T20:03:59.064Z" }, + { url = "https://files.pythonhosted.org/packages/a4/09/5683146fda6a2b569deb78ccfd8fbfea8bfe55f726b081c0a6bb18dd6f28/triton-3.7.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2020153b08280415ec0da6607834e79166442147e78e144df06b508c75b186d2", size = 197729537, upload-time = "2026-06-17T19:53:35.516Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f8/448220c3092019f9fdfab39ec47985968181d67da34b44f6a7f6280a5cbb/triton-3.7.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c58e4c61f0c73b5dba3b5d19b4a7093c32f90dc18b2a7f121a7c16ccd31107b7", size = 184814760, upload-time = "2026-06-17T20:04:04.984Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ac/229b7d4589d2e5937310e72c6d46e89599d16a4a12b479ffa1499fee8eb8/triton-3.7.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10ba85fa2cca4a2fbdeb36bf1cb082f2c252bda55bf9fccd74f65ec5bc647e68", size = 197824404, upload-time = "2026-06-17T19:53:42.772Z" }, ] [[package]] @@ -3810,7 +3933,7 @@ wheels = [ [[package]] name = "virtualenv" -version = "21.5.0" +version = "21.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -3818,9 +3941,9 @@ dependencies = [ { name = "platformdirs" }, { name = "python-discovery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cd/0e/933bacb37b57ae7928b0030eef205a3dbb3e37afdbdde5be2e113318958f/virtualenv-21.5.0.tar.gz", hash = "sha256:98847aadf5e2037e0e4d2e19528eb3aca6f23906422e59a510bff231a6d32fce", size = 4577424, upload-time = "2026-06-13T20:36:45.066Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/a5/81f987504738e6defeed61ec1c47e2aefab3c35d8eeb87e1b3f38cf28254/virtualenv-21.5.1.tar.gz", hash = "sha256:dca3bf98275a59c652b69d68e73433e597d977c2da9198882479d1a7188009c8", size = 4578798, upload-time = "2026-06-16T16:23:58.603Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/87/b0667ede418386ab631e48924b845d326f366d61e6bd08fe68a748fae4d4/virtualenv-21.5.0-py3-none-any.whl", hash = "sha256:8f7c38605023688c89789f566959006af6d61c99eeeb9e58342eb780c5761e5e", size = 4557937, upload-time = "2026-06-13T20:36:42.967Z" }, + { url = "https://files.pythonhosted.org/packages/2c/02/3623e6169bed617ed1e2d372f7c69f92ec28d54c4dfc997055c8578ec148/virtualenv-21.5.1-py3-none-any.whl", hash = "sha256:55aa670b67bbfb991b03fda39bd3276d92c419d702376e98c5df1c9989a26783", size = 4558820, upload-time = "2026-06-16T16:23:56.963Z" }, ] [[package]] @@ -3888,12 +4011,12 @@ resolution-markers = [ "python_full_version < '3.12'", ] dependencies = [ - { name = "donfig", marker = "python_full_version < '3.12'" }, - { name = "google-crc32c", marker = "python_full_version < '3.12'" }, - { name = "numcodecs", marker = "python_full_version < '3.12'" }, - { name = "numpy", marker = "python_full_version < '3.12'" }, - { name = "packaging", marker = "python_full_version < '3.12'" }, - { name = "typing-extensions", marker = "python_full_version < '3.12'" }, + { name = "donfig" }, + { name = "google-crc32c" }, + { name = "numcodecs" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" } }, + { name = "packaging" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/31/5a/b8a0cf39a14c770c30bd1f2d120c54000c8cd9e84e8e79f38d9a7ce58071/zarr-3.1.6.tar.gz", hash = "sha256:d95e72cbea4b90e9a70679468b8266400331756232576ae2b43400ac5108d0eb", size = 386531, upload-time = "2026-03-23T17:25:18.748Z" } wheels = [ @@ -3909,12 +4032,12 @@ resolution-markers = [ "python_full_version >= '3.12' and python_full_version < '3.14'", ] dependencies = [ - { name = "donfig", marker = "python_full_version >= '3.12'" }, - { name = "google-crc32c", marker = "python_full_version >= '3.12'" }, - { name = "numcodecs", marker = "python_full_version >= '3.12'" }, - { name = "numpy", marker = "python_full_version >= '3.12'" }, - { name = "packaging", marker = "python_full_version >= '3.12'" }, - { name = "typing-extensions", marker = "python_full_version >= '3.12'" }, + { name = "donfig" }, + { name = "google-crc32c" }, + { name = "numcodecs" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" } }, + { name = "packaging" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/93/8d/aeb164004f87543b06ef54f885d02c342c31ceb274e2bbec470a98927621/zarr-3.2.1.tar.gz", hash = "sha256:71565b738a0e7e8ed226f0516eba8c6bb53440ad7669a8c48ebb3534a161d035", size = 675161, upload-time = "2026-05-05T12:37:22.383Z" } wheels = [ From 076d96e2bea2141228ae17fe028b48ecc136069b Mon Sep 17 00:00:00 2001 From: quantem-bot Date: Mon, 6 Jul 2026 12:53:42 +0000 Subject: [PATCH 333/335] chore: update lock file --- uv.lock | 830 +++++++++++++++++++++++++++----------------------------- 1 file changed, 407 insertions(+), 423 deletions(-) diff --git a/uv.lock b/uv.lock index 0a665106..888375c2 100644 --- a/uv.lock +++ b/uv.lock @@ -15,11 +15,11 @@ members = [ [[package]] name = "absl-py" -version = "2.4.0" +version = "2.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/64/c7/8de93764ad66968d19329a7e0c147a2bb3c7054c554d4a119111b8f9440f/absl_py-2.4.0.tar.gz", hash = "sha256:8c6af82722b35cf71e0f4d1d47dcaebfff286e27110a99fc359349b247dfb5d4", size = 116543, upload-time = "2026-01-28T10:17:05.322Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/4f/d79676ab82f2e42fc3611618139f13a9c4c31d0cff4b486982047679a802/absl_py-2.5.0.tar.gz", hash = "sha256:0c996f25c0490700fadabe6351630f6111534fa0ae252cc6d2014ea3b141135f", size = 118119, upload-time = "2026-07-03T10:57:48.157Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/a6/907a406bb7d359e6a63f99c313846d9eec4f7e6f7437809e03aa00fa3074/absl_py-2.4.0-py3-none-any.whl", hash = "sha256:88476fd881ca8aab94ffa78b7b6c632a782ab3ba1cd19c9bd423abc4fb4cd28d", size = 135750, upload-time = "2026-01-28T10:17:04.19Z" }, + { url = "https://files.pythonhosted.org/packages/58/0a/a10b45aab35b175aded078a462dc8d0c698f5b13946e7cb0869097b78bb6/absl_py-2.5.0-py3-none-any.whl", hash = "sha256:0f17b89f2a4eaaedc4f28c622998aa690564b3012a396a4ffad0821007fe03ba", size = 137410, upload-time = "2026-07-03T10:57:46.735Z" }, ] [[package]] @@ -400,7 +400,7 @@ dependencies = [ { name = "colorspacious" }, { name = "matplotlib" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2f/ca/39152b506133881be74bd46cced27ec10d9bbc85db5bdfc22c8f8e4011f9/cmasher-1.9.2.tar.gz", hash = "sha256:6c90c2cec87146e3210d3e7f2321fceee38ac7d87c136cd0de25160a14f57ff5", size = 520860, upload-time = "2024-11-19T11:17:02.084Z" } wheels = [ @@ -434,7 +434,7 @@ version = "1.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/75/e4/aa41ae14c5c061205715006c8834496d86ec7500f1edda5981f0f0190cc6/colorspacious-1.1.2.tar.gz", hash = "sha256:5e9072e8cdca889dac445c35c9362a22ccf758e97b00b79ff0d5a7ba3e11b618", size = 688573, upload-time = "2018-04-08T04:27:30.83Z" } wheels = [ @@ -456,7 +456,7 @@ version = "1.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } wheels = [ @@ -535,86 +535,86 @@ wheels = [ [[package]] name = "coverage" -version = "7.14.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b4/91/0a7c28934e50d8ac9a7b117712d176f2953c3170bccced5eaacfa3e96175/coverage-7.14.3.tar.gz", hash = "sha256:1a7563a443f3d53fdeb040ec8c9f7466aed7ca3dc5891aa09d3ca3625fa4387f", size = 924398, upload-time = "2026-06-22T23:10:25.584Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/24/efb17eb94018dd3415d0e8a76a4786a866e8964aa9c50f033399d23939c2/coverage-7.14.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e574801e1d643561594aa021206c46d80b257e9853087090ba97bed8b0a509d3", size = 220501, upload-time = "2026-06-22T23:08:02.182Z" }, - { url = "https://files.pythonhosted.org/packages/76/93/32f1bfca6cdd34259c8af42820a034b7a28dfb44969a13ed38c17e0ba5b0/coverage-7.14.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f82b6bb7d75a2613e85d07cefa3a8c973d0544a8993337f6e2728e4a1e94c305", size = 221008, upload-time = "2026-06-22T23:08:03.701Z" }, - { url = "https://files.pythonhosted.org/packages/eb/88/0d0f974855ff905d15a64f7873d00bdc4182e2736267486c6634f4af293c/coverage-7.14.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a2335ea5fed26af2e831094964fa3f8fae60b45f7e37fcc2d3b615b2add3ad87", size = 251420, upload-time = "2026-06-22T23:08:05.211Z" }, - { url = "https://files.pythonhosted.org/packages/39/7f/117dd2ec65e4140576f8ef991d88220f9b806769f7a8c20e0550c0f924e2/coverage-7.14.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fbb8c3a98e779013786ae01d229662aeacbc77100efbd3f2f245219ace5af700", size = 253331, upload-time = "2026-06-22T23:08:06.672Z" }, - { url = "https://files.pythonhosted.org/packages/87/55/f0bd6d6538e3f16829fb8a44b6c0d2fe9da638bbfdd6a20f8b5da8f4fa81/coverage-7.14.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac082660de8f429ba0ea363595abb838998570b9a7546777c60f413ab902bbde", size = 255441, upload-time = "2026-06-22T23:08:08.208Z" }, - { url = "https://files.pythonhosted.org/packages/1e/98/aa71f7879019c846a8a9662579ea4484b0202cf1e252ffeed647075e7eca/coverage-7.14.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ac012839ff7e396030f1e94e10553a431d14e4de2ab65cb3acb72bbd5628ca2", size = 257398, upload-time = "2026-06-22T23:08:09.749Z" }, - { url = "https://files.pythonhosted.org/packages/f3/4f/5fd367e59844190f5965015d7bee899e67a89d13eb2760118479bf836f2f/coverage-7.14.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5952f8c1bda2a5347154450379316e6dfa4d934d62ca35f6784451e6f55074fb", size = 251558, upload-time = "2026-06-22T23:08:11.37Z" }, - { url = "https://files.pythonhosted.org/packages/8f/de/5383a6ee5a6376701fe07d980fa8e4a66c0c377fead16712720340d701a3/coverage-7.14.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8cf0f2509acb4619e2471a1951089054dd58ebea7a912066d2ea56dd4c24ca4a", size = 253134, upload-time = "2026-06-22T23:08:13.04Z" }, - { url = "https://files.pythonhosted.org/packages/01/99/09542b1a99f788e3daec7f0fadc288821e71aca9ea298d51bfa1ba79fed5/coverage-7.14.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2e41fd3aab806770008279a93879b0924b16247e09ab537c043d08bbca53b4ab", size = 251195, upload-time = "2026-06-22T23:08:14.606Z" }, - { url = "https://files.pythonhosted.org/packages/02/9d/722fe8c13f0fbb064491b9e8656e56a606286792e5068c47ca1042e773e8/coverage-7.14.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f0a47095963cfe054e0df178daca95aec21e680d6076da807c3add28dfe920f7", size = 254959, upload-time = "2026-06-22T23:08:16.431Z" }, - { url = "https://files.pythonhosted.org/packages/fb/58/943627179ff1d82da9e54d0a5b0bb907bb19cf19515599ccd921de50b469/coverage-7.14.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a090cbf9521e78ffdb2fcf448b72902afe9f5923ff6a12d5c0d0120200348af9", size = 250914, upload-time = "2026-06-22T23:08:18.03Z" }, - { url = "https://files.pythonhosted.org/packages/a5/d4/803efcbf9ae5567454a0c71e983589529448e2704ee0da2dc0163d482f18/coverage-7.14.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4d310baf69a4fbe8a098ce727e4808a34866ac718a6f759ae659cbd3221358bc", size = 251824, upload-time = "2026-06-22T23:08:19.704Z" }, - { url = "https://files.pythonhosted.org/packages/32/79/3f78ea9563132746eed5cecb75d2e576f9d8fec45a47242b5ae0950b82a3/coverage-7.14.3-cp311-cp311-win32.whl", hash = "sha256:74fdd718d88fe144f4579b8747873a07ec3f04cb837d5faec5a25d9e22fa31a8", size = 222594, upload-time = "2026-06-22T23:08:21.311Z" }, - { url = "https://files.pythonhosted.org/packages/85/22/9ebbc5a2ab42ac5d0eea1f48648629e1de9bbe41ec243ed6b93d55a5a53f/coverage-7.14.3-cp311-cp311-win_amd64.whl", hash = "sha256:cc96aa922e21d4bc5d5ed3c915cef27dfcbc13686f47d5e378d647fbfba655a2", size = 223073, upload-time = "2026-06-22T23:08:23.318Z" }, - { url = "https://files.pythonhosted.org/packages/71/af/69d5fcc16cb555153f99cec5467922f226be0369f7335a9506856d2a7bd0/coverage-7.14.3-cp311-cp311-win_arm64.whl", hash = "sha256:c66f9f9d4f1e9712eb9b1de5310f881d4e2188cfcba5065e1a8490f38687f2c4", size = 222617, upload-time = "2026-06-22T23:08:25.054Z" }, - { url = "https://files.pythonhosted.org/packages/bd/b0/8a911f6ffe6974dac4df95b468ab9a2899d0e59f0f99a489afeec39f00bc/coverage-7.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3d74ff26299c4879ce3a4d826f9d3d4d556fd285fde7bbce3c0ef5a8ab1cec24", size = 220672, upload-time = "2026-06-22T23:08:26.621Z" }, - { url = "https://files.pythonhosted.org/packages/36/16/0fc0cb52538783dbbae0934b834f5a58fd5354380ee6cad4a07b15dc845d/coverage-7.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:96150a9cf3468ea20f0bc5d0e21b3df8972c31480ef90fa7614b773cc6429665", size = 221035, upload-time = "2026-06-22T23:08:28.372Z" }, - { url = "https://files.pythonhosted.org/packages/77/e2/421ccfbb48335ac49e93301478cf5d623b0c2bf1c0cadd8e2b2fc6c0c710/coverage-7.14.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:27d07a46500ba23515b838dbcf52512026af04090755cf6cc64166d88c9b9a1a", size = 252540, upload-time = "2026-06-22T23:08:30.226Z" }, - { url = "https://files.pythonhosted.org/packages/06/c2/05b8c890097c61a7f4406b35396b997a635200ded0339eda83dfbe526c5f/coverage-7.14.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:621e13c6108234d7960aaf5762ab5c3c00f33c30c15af06dcbff0c73bf112727", size = 255274, upload-time = "2026-06-22T23:08:31.876Z" }, - { url = "https://files.pythonhosted.org/packages/dc/be/b6d9efe447f8ba3c3c854195f326bd64c54b907d936cd2fdebf8767ec72e/coverage-7.14.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b60ca6d8af70473491a15a343cbabab2e8f9ea66a4376e81c7aa24876a6f977", size = 256389, upload-time = "2026-06-22T23:08:33.843Z" }, - { url = "https://files.pythonhosted.org/packages/d4/3c/f26e50acc429e608bc534ac06f0a3c169019c798178ec5e9de3dbc0df9c9/coverage-7.14.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c90a7cdd5e380e1ce02f19792e2ac2fbfbf177e35a27e69fd3e873b30d895c0c", size = 258648, upload-time = "2026-06-22T23:08:35.481Z" }, - { url = "https://files.pythonhosted.org/packages/9e/a2/01c1fabf816c8e1dae197e258edf878a3d3ddc86fbda34b76e5794277d8f/coverage-7.14.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5d788e5fd55347eef06ca0732c77d04a264de67e8ff24631270cdff3767a60cf", size = 252949, upload-time = "2026-06-22T23:08:37.562Z" }, - { url = "https://files.pythonhosted.org/packages/89/c6/941166dd79c31fd44a13063780ae8d552eee0089a0a0930b9bdb7df554ed/coverage-7.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62c7f79db2851c95ef020e5d28b97afde3daf9f7febcd35b53e05638f729063f", size = 254310, upload-time = "2026-06-22T23:08:39.174Z" }, - { url = "https://files.pythonhosted.org/packages/10/31/80b1fd028201a961033ce95be3cd1e39e521b3762e6b4a1ac1616cb291e7/coverage-7.14.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:90f7608aeb5d9b60b523b9fb2a4ee1973867cc4865a3f26fe6c7577073b70205", size = 252453, upload-time = "2026-06-22T23:08:40.84Z" }, - { url = "https://files.pythonhosted.org/packages/5f/85/c3d9addd94c4b524f3f4af0232075f5fe7170ce99a1386edff803e5934db/coverage-7.14.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1e3b91f9c4740aeb571ecf82e5e8d8e4ab62d34fcb5a5d4e5baa38c6f7d2857c", size = 256522, upload-time = "2026-06-22T23:08:42.494Z" }, - { url = "https://files.pythonhosted.org/packages/91/14/e5a0575f73795af3a7a9ae13dadf812e17d32422896839987dc3f86947e1/coverage-7.14.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c946099774a7699de03cbd0ff0a64e21aed4525eed9d959adde4afe6d15758ef", size = 252023, upload-time = "2026-06-22T23:08:44.243Z" }, - { url = "https://files.pythonhosted.org/packages/38/9b/9652ee531937ce3b8a63a8896885b2b4a2d56adc30e53c9540c666286d88/coverage-7.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:16b206e521feb8b7133a45754643dead0538489cf8b783b90cf5f4e3299625fd", size = 253893, upload-time = "2026-06-22T23:08:46.113Z" }, - { url = "https://files.pythonhosted.org/packages/b1/05/42678841c8c38e4b08bdfc48269f5a16dfbf5806000fe6a89b4cece3c691/coverage-7.14.3-cp312-cp312-win32.whl", hash = "sha256:ea3169c7116eb6cdf7608c6c7da9ecfcb3da40688e3a510fac2d1d2bafd6dc35", size = 222734, upload-time = "2026-06-22T23:08:47.858Z" }, - { url = "https://files.pythonhosted.org/packages/df/87/07a4fcee55177a25f1b52331a8e92cf4f2c53b1a9c75ce2981fd59c684ad/coverage-7.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:7ea52fc08f007bcc494d4bb3df3851e95843d881860ba38fe2c64dc100db5e7d", size = 223266, upload-time = "2026-06-22T23:08:49.494Z" }, - { url = "https://files.pythonhosted.org/packages/aa/34/2b8b66a989282ea7b370beb49f50bab29470dc30bb0b03935b6b802782f7/coverage-7.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:8cec0ad652ec57790970d817490105bd917d783c2f7b38d6b58a0ca312e1a336", size = 222655, upload-time = "2026-06-22T23:08:51.766Z" }, - { url = "https://files.pythonhosted.org/packages/a9/83/7fefbf5df23ed2b7f489907564a7b34b9b07098128e12e0fdfa92626e456/coverage-7.14.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:47968988b367990ae4ab17523790c38cd125e02c6bfd379b6022be2d40bdc38c", size = 220699, upload-time = "2026-06-22T23:08:53.522Z" }, - { url = "https://files.pythonhosted.org/packages/31/e6/38c3653ff6d56d704b29241362387ca824e38e15b76fdcb7096538195790/coverage-7.14.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0ee68f5c34812780f3a7063382c0a9fcbb99985b7ddcdcaa626e4f3fb2e0783a", size = 221068, upload-time = "2026-06-22T23:08:55.571Z" }, - { url = "https://files.pythonhosted.org/packages/20/86/4f5c45d51c5cd10a128933f0fd235393c9146abbfd2ce2dfa68b3267ead3/coverage-7.14.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fa9e5c6857a7e80fa22ace5cf3550ae392bbfc322f1d8dd2d2d5a8be38cec027", size = 252060, upload-time = "2026-06-22T23:08:57.464Z" }, - { url = "https://files.pythonhosted.org/packages/82/50/dfce42eff2cecabcd5a9bbad5489449c87db3415f408d23ffee417ce01f6/coverage-7.14.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:98a0859b0e98e43e1178a9402e19c8127766b14f7109a374d976e5a62c0e5c73", size = 254657, upload-time = "2026-06-22T23:08:59.453Z" }, - { url = "https://files.pythonhosted.org/packages/ba/d2/639ceb1bc8038fd0d66768278d5dc22df3391918b8278c2a21aa2602a531/coverage-7.14.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69918344541ed9c8368566c2adc03c0e33d4550d7faa87d1b35e49b6a3286ea9", size = 255892, upload-time = "2026-06-22T23:09:01.291Z" }, - { url = "https://files.pythonhosted.org/packages/8b/96/002094a10e113512500dc1e10430a449417e17b0f90f7d496bcb820208b7/coverage-7.14.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b7f300ac92cd4b570724c8ffbbd0c130fee298d2447f41d5a3abf58976fae1de", size = 258026, upload-time = "2026-06-22T23:09:03.017Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ec/286a5d2fad9c4bee59bd724feeb7d5bf8303c6c9200b51d1dd945a9c72b0/coverage-7.14.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:11a7ec9f97ab950f4c5af62229befc7faf208fdbc0116d3902d7e306cf2c5abd", size = 252285, upload-time = "2026-06-22T23:09:04.773Z" }, - { url = "https://files.pythonhosted.org/packages/d9/7d/a17753a0b12dd48d0d50f5fab079ad99d3be1eac790494d89f3a417ca0b9/coverage-7.14.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a571bd889cd36c5922ce8e42e059f9d37d02301531d11374afa4c87a578625d5", size = 254023, upload-time = "2026-06-22T23:09:06.513Z" }, - { url = "https://files.pythonhosted.org/packages/86/ef/a76c6ceba6a2c313f905310abf2701d534cada22d372db11731831e9e209/coverage-7.14.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:de76caefc8deabb0dd1678b6a980be97d14c8d87e213ac194dbf8b09e96d63fb", size = 251989, upload-time = "2026-06-22T23:09:08.382Z" }, - { url = "https://files.pythonhosted.org/packages/d9/39/353013a75fec0fb49f7553519f9d52b4441e902e5178c93f38eb6c07cedb/coverage-7.14.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d20a15c622194234161535459affa8f7905830391c9ccfa060d495dbfe3a1c7f", size = 256144, upload-time = "2026-06-22T23:09:10.369Z" }, - { url = "https://files.pythonhosted.org/packages/29/0e/613878555d734def11c5b20a2701a15cb3781b9e9ea749da27c5f436e928/coverage-7.14.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b488bd4b23397db62e7a9459129d01ff06a846582a732efd24834b24a6ada498", size = 251808, upload-time = "2026-06-22T23:09:12.057Z" }, - { url = "https://files.pythonhosted.org/packages/af/76/359c058c9cfdcf1e8b107663881225b03b364a320017eda24a2a66e55102/coverage-7.14.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a3693b4153394d265f44fb855fdc80e72403024d4d6f91c4871b334d028e4e0", size = 253579, upload-time = "2026-06-22T23:09:13.858Z" }, - { url = "https://files.pythonhosted.org/packages/1d/d9/4ba2f060933a30ebe363cef9f67a365b0a317e580c0d5d9169d56a73ef1c/coverage-7.14.3-cp313-cp313-win32.whl", hash = "sha256:338b19131ab1a6b767b462bfcbaa692e7ae22f24463e39d49b02a83410ff6b37", size = 222741, upload-time = "2026-06-22T23:09:15.636Z" }, - { url = "https://files.pythonhosted.org/packages/76/e8/196ebc25d8f34c06d43a6e9c8513c9266ef8dbf3b5672beb1a00cf5e29fa/coverage-7.14.3-cp313-cp313-win_amd64.whl", hash = "sha256:b3d77f7f196abdef7e01415de1bce09f216189e83e58159cfeef2b92d0464994", size = 223283, upload-time = "2026-06-22T23:09:17.478Z" }, - { url = "https://files.pythonhosted.org/packages/7c/af/51d2aac6417523a286f10fb25f09eb9518a84df9f1151e93ff6871f34849/coverage-7.14.3-cp313-cp313-win_arm64.whl", hash = "sha256:e6230e688c7c3e65cedd41a774eb4ec221adc6bfee13768231015b702d5e4150", size = 222678, upload-time = "2026-06-22T23:09:19.7Z" }, - { url = "https://files.pythonhosted.org/packages/61/56/14e3b97facbfa1304dd19e676e26599ad359f04714bed32f7f1c5a88efdc/coverage-7.14.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:605ab2b566a22bd94834529d66d295c364aba84afd3e5498285c7a524017b1fc", size = 220741, upload-time = "2026-06-22T23:09:21.616Z" }, - { url = "https://files.pythonhosted.org/packages/12/1d/db378b5cca433b90b893f26dab728b280ddd89f272a1fdfed4aeaa05c686/coverage-7.14.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a3c2134809e80fac091bfed18a6991b5a5eb5df5ae32b17ac4f4f99864b73dd7", size = 221068, upload-time = "2026-06-22T23:09:23.452Z" }, - { url = "https://files.pythonhosted.org/packages/47/f0/3f8421b20d9c4fcd39be9a8ca3c3fda8bc204b44efbd09fede153afd3e2f/coverage-7.14.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c02efd507227bde9969cab0db8f48890eb3b5dcad6afac57a4792df4133543ce", size = 252117, upload-time = "2026-06-22T23:09:25.458Z" }, - { url = "https://files.pythonhosted.org/packages/27/ca/59ea35fb99743549ec8b37eff141ece4431fea590c89e536ed8032ef45cf/coverage-7.14.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1bb93c2aa61d2a5b38f1526546d95cf4132cb681e541a337bf8dfd092be816e5", size = 254622, upload-time = "2026-06-22T23:09:27.523Z" }, - { url = "https://files.pythonhosted.org/packages/c8/25/ec6de51ae7493b92a1cf74d1b763121c29636759167e2a593ba4db5881e4/coverage-7.14.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f502e948e03e866538048bba081c075caaa62e5bda6ea5b7432e45f587eb462a", size = 255968, upload-time = "2026-06-22T23:09:29.43Z" }, - { url = "https://files.pythonhosted.org/packages/5d/05/c8bfc77823f42b4664fb25842f13b567022f6f84a4c83c8ecbb16734b7cb/coverage-7.14.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9973ef2463f8e6cfb61a6324126bb3e17d67a85f22f58d856e583ea2e3ca6501", size = 258284, upload-time = "2026-06-22T23:09:31.397Z" }, - { url = "https://files.pythonhosted.org/packages/f6/15/1d1b242027124a32b26ef01f82018b8c4ef34ef174aa6aeba7b1eeef48e8/coverage-7.14.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9be4e7d4c5ca0427889f8f9d614bd630c2be741b1de7699bca3b2b6c0e41003e", size = 252143, upload-time = "2026-06-22T23:09:33.256Z" }, - { url = "https://files.pythonhosted.org/packages/74/b6/d2a9842fd2a5d7d27f1ac851c043a734a494ad75402c5331db3da79ed691/coverage-7.14.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a574912f3bde4b0619f6e97d01aa590b70998859244793769eb3a6df78ee56d3", size = 253976, upload-time = "2026-06-22T23:09:35.351Z" }, - { url = "https://files.pythonhosted.org/packages/fd/30/e1600ddf7e226db5558bb5323d2186fff00f505c4b764643ec89ce5d8175/coverage-7.14.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:e343fb086c9cd780b38622fea7c369acd64c1a0724312149b5d769c387a2b1f5", size = 251942, upload-time = "2026-06-22T23:09:37.313Z" }, - { url = "https://files.pythonhosted.org/packages/d9/2c/9159de64f9dd648e324328d588a44cfab1e331eb5259ce1141afe2a92dfb/coverage-7.14.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:3c68df8e61f1e09633fefc7538297145623957a048534368c9d212782aa5e845", size = 256220, upload-time = "2026-06-22T23:09:39.165Z" }, - { url = "https://files.pythonhosted.org/packages/91/67/b7f536cc2c124f48e91b22fbb741d2261f4e3d310faf6f76007f47566e5d/coverage-7.14.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3e5b550a128419373c2f6cec28a244207013ef15f5cbcff6a5ca09d1dfaaf027", size = 251756, upload-time = "2026-06-22T23:09:41.056Z" }, - { url = "https://files.pythonhosted.org/packages/dd/ec/f3718038e2d4860c715a55428377ca7f6c75872caf98cabd982e1d76967d/coverage-7.14.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2bfc4dd0a912329eccc7484a7d0b2a38032b38c40663b1e1ac595f10c457954b", size = 253413, upload-time = "2026-06-22T23:09:43.306Z" }, - { url = "https://files.pythonhosted.org/packages/b8/a5/91f11efeef89b3cc9b30461128db15b0511ef813ab889a7b7ab636b3a497/coverage-7.14.3-cp314-cp314-win32.whl", hash = "sha256:0423d64c013057a06e70f070f073cec4b0cbc7d2b27f3c7007292f2ff1d52965", size = 222946, upload-time = "2026-06-22T23:09:45.261Z" }, - { url = "https://files.pythonhosted.org/packages/58/fd/98ac9f524d9ec378de831c034dbdeb544ca7ef7d2d9c9996daf232a037fd/coverage-7.14.3-cp314-cp314-win_amd64.whl", hash = "sha256:92c22e19ce64ca3f2ad751f16f14df1468b4c231bd6af97185063a9c292a0cb3", size = 223436, upload-time = "2026-06-22T23:09:47.177Z" }, - { url = "https://files.pythonhosted.org/packages/b4/a0/7cd612d650a772a0ae80144443406bf61981c896c3d57c9e6e79fb2cdbd1/coverage-7.14.3-cp314-cp314-win_arm64.whl", hash = "sha256:41de778bd41780586e2b04912079c73089ab5d839624e28db3bdb26de638da92", size = 222861, upload-time = "2026-06-22T23:09:49.384Z" }, - { url = "https://files.pythonhosted.org/packages/55/57/017353fab573779c0d00448e47d102edd36c792f7b6f233a4d89a7a08384/coverage-7.14.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8427f370ca67db4c975d2a26acfc0e5783ca0b52444dbc50278ace0f35445949", size = 221474, upload-time = "2026-06-22T23:09:51.417Z" }, - { url = "https://files.pythonhosted.org/packages/69/92/90cf1f1a5c468a9c1b7ba2716e0e205293ad9b02f5f573a6de4318b15ba1/coverage-7.14.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d8e88f335544a47e22ae2e45b344772925ec65166555c958720d5ed971880891", size = 221738, upload-time = "2026-06-22T23:09:53.487Z" }, - { url = "https://files.pythonhosted.org/packages/a4/c0/4df964fa539f8399fd7679c09c472d73744de334686fd3f01e3a2465ce4e/coverage-7.14.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:beaab199b9e5ceaf5a225e16a9d4df136f2a1eae0a5c20de1e277c8a5225f388", size = 263101, upload-time = "2026-06-22T23:09:55.895Z" }, - { url = "https://files.pythonhosted.org/packages/06/76/e5d33b2576ae3bf2be2058cd1cae57774b61e400f2c3c58f3783dc2ffb4a/coverage-7.14.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3ff255799f5a1676c71c1c32ec01fd043aa09d57b3d95764b24992757184784", size = 265225, upload-time = "2026-06-22T23:09:57.904Z" }, - { url = "https://files.pythonhosted.org/packages/61/d2/e52419afe391a39ba27fdefaf0737d8e34bf03faef6ab3b3006545bbd0d0/coverage-7.14.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:878832eaac515b62decfa76965aed558775f86bf1fc8cca76993c0c84ae31aed", size = 267643, upload-time = "2026-06-22T23:09:59.938Z" }, - { url = "https://files.pythonhosted.org/packages/58/7a/f2625d8d5006b6b20fba5afaef00b24a763fe96476ea798a3076cbc1f84e/coverage-7.14.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:611e62cb9386096d81b63e0a05330750268617231e7bd598e1fe77482a2c58a5", size = 268762, upload-time = "2026-06-22T23:10:01.943Z" }, - { url = "https://files.pythonhosted.org/packages/7d/bf/908024006bba57127354d74e938954b9c3cd765cc2e0412dc9c37b415cda/coverage-7.14.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:02c41de2a88011b893050fc9830267d927a50a215f7ad5ec17349db7090ccf26", size = 262208, upload-time = "2026-06-22T23:10:03.954Z" }, - { url = "https://files.pythonhosted.org/packages/34/a0/d4f9296441b909817442fdb26bd77a698f08272ec683a7394b00eb2e47a0/coverage-7.14.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:526ce9721116af23b1065089f0b75046fe521e7772ab94b641cd66b7a0421889", size = 265096, upload-time = "2026-06-22T23:10:05.936Z" }, - { url = "https://files.pythonhosted.org/packages/e8/da/4ae4f3f4e477b56a4ce1e5c48a35eff38a94b50130ce5bdc897024741cfc/coverage-7.14.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e4ed44705ca4bead6fc977a8b741f2145608289b33c8a9b42a95d0f15aedbf4d", size = 262699, upload-time = "2026-06-22T23:10:07.973Z" }, - { url = "https://files.pythonhosted.org/packages/d8/7a/6927148073ff32856d78baa77b4ddc07a9be7e90020f9db0661c4ca523a1/coverage-7.14.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2415902f385a23dcc4ccd26e0ba803249a169af6a930c003a4c715eeb9a5444e", size = 266433, upload-time = "2026-06-22T23:10:10.145Z" }, - { url = "https://files.pythonhosted.org/packages/f7/a7/774f658dbe9c4c3f5daa86a87e0459ac3832e4e3cc67affe078547f727b9/coverage-7.14.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:b75ee850fc2d7c831e883220c445b035f2224de2ba6103f1e56dbd237ab913f7", size = 261547, upload-time = "2026-06-22T23:10:12.191Z" }, - { url = "https://files.pythonhosted.org/packages/3d/14/a0c18c0376c43cbf973f43ef6ca20019c950597180e6396232f7b6a27102/coverage-7.14.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dc9b4e35e7c3920e925ba7f14886fd5fbe481232754624e832ddba66c7535635", size = 263859, upload-time = "2026-06-22T23:10:14.492Z" }, - { url = "https://files.pythonhosted.org/packages/10/ac/43a3d0f460af524b131a6191805bc5d18b806ab4e828fbf82e8c8c3af446/coverage-7.14.3-cp314-cp314t-win32.whl", hash = "sha256:7b27c822a8161afbe48e99f1adfb098d270ae7e0f7d7b0555ce110529bdb69cc", size = 223250, upload-time = "2026-06-22T23:10:16.758Z" }, - { url = "https://files.pythonhosted.org/packages/3f/5f/d5e5c56b0712e96ce8f69fe7dbf229ff938b437bc50862743c8a0d2cea84/coverage-7.14.3-cp314-cp314t-win_amd64.whl", hash = "sha256:39e1dbbb6ff2c338e0196a482558a792a1de3aa64261196f5cdb3da016ad9cda", size = 224082, upload-time = "2026-06-22T23:10:19.23Z" }, - { url = "https://files.pythonhosted.org/packages/62/35/947cbd5be1d3bcbbdc43d6791de8a56c6501903311d42915ae06a82815f0/coverage-7.14.3-cp314-cp314t-win_arm64.whl", hash = "sha256:68520c90babfa2d560eca6d497921ed3a4f469623bd709733124491b2aa8ef3f", size = 223400, upload-time = "2026-06-22T23:10:21.24Z" }, - { url = "https://files.pythonhosted.org/packages/eb/e3/a0aa32bfa3a081951f60a23bc0e7b512891ef0eecda1153cf1d8ba36c6b1/coverage-7.14.3-py3-none-any.whl", hash = "sha256:fb7e18afb6e903c1a92401a2f0501ac277dca527bb9ca6fe1f691a8a0026a0e8", size = 212469, upload-time = "2026-06-22T23:10:23.405Z" }, +version = "7.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/8b/adeb62ea8951f13c4c7fef2e7a85e1a06b499c8d8237ea589d496029e53f/coverage-7.15.0.tar.gz", hash = "sha256:9ac3fe7a1435986463eaa8ee253ae2f2a268709ba4ae5c7dd1f52a05391ad78f", size = 925362, upload-time = "2026-07-02T13:10:50.535Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/23/82e910835ef4b8391047025e1d53aa48d66029f444eb8b25373c849bf503/coverage-7.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:003fff99412ea848c0aaebcc78ed2b6ce7d8a1227ed17e68470672770b78a02a", size = 220662, upload-time = "2026-07-02T13:08:39.205Z" }, + { url = "https://files.pythonhosted.org/packages/6d/0d/c7b213dde2f1579de5231062b386d8413f79c11667eb58c39319b25991da/coverage-7.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5cbd804bf2784ce7b45114516050f346ecd50f960c4bb630a7ee9e1d78fa2118", size = 221168, upload-time = "2026-07-02T13:08:40.471Z" }, + { url = "https://files.pythonhosted.org/packages/33/77/d000aeedfac085088337b3c7becdad328474b1f8a9e4c9368a0c99605d68/coverage-7.15.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8773e15c23305b58882a4611fb9b2755977eae0dc2e515366a1b6c98866cc4c2", size = 251587, upload-time = "2026-07-02T13:08:42.033Z" }, + { url = "https://files.pythonhosted.org/packages/cc/e0/86787c56b9df17afd370d5e293515dd4d9a107a561d13054873eefad8ecc/coverage-7.15.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f50e40081494c1dc4239ebb202014cbcc3306ea96fb6302a34c8cc0967fc5ae8", size = 253497, upload-time = "2026-07-02T13:08:43.387Z" }, + { url = "https://files.pythonhosted.org/packages/3f/02/181bc917359299c07dead6270f94e411151c8b60cec905c33499da69afe6/coverage-7.15.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daf96f37f5fc3a7b6c6da862eb4aee61c426bd63da236ed4a73ef0e503b4bca5", size = 255607, upload-time = "2026-07-02T13:08:44.897Z" }, + { url = "https://files.pythonhosted.org/packages/b9/35/ca5e7427699913da6788c4f910e73ab16c5f4b59ec5d3a999dce2a45112f/coverage-7.15.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:51aa20f6ae2788fd197747766edf4cd8234fd9423309b934257fa6b21a592723", size = 257563, upload-time = "2026-07-02T13:08:46.334Z" }, + { url = "https://files.pythonhosted.org/packages/0b/4d/b8220bacc2fc3c4e9078e27c32e99fb411479a4718a72bdd00036a9891c8/coverage-7.15.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03d1f922757662eb7af586e77834792274cff776bc7b1d1a0b66a49ea9d84735", size = 251726, upload-time = "2026-07-02T13:08:47.941Z" }, + { url = "https://files.pythonhosted.org/packages/c4/e4/2e145da1991d72189b9c3cf7eca05c716ee7080d099aaea6757cfc7df008/coverage-7.15.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a6d6acc9a7666245e6133dd15144ca038a85a9cd5026bb06d6bbae9e77440dc9", size = 253301, upload-time = "2026-07-02T13:08:49.5Z" }, + { url = "https://files.pythonhosted.org/packages/72/28/d2c841d698bf762e481f08bd4839d370246b6d9b61dab085a7b20b201a08/coverage-7.15.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1ac2c4c27c7df851dc9a017c2d7de00b69147e84ba3d96f37a530b0b6fb51035", size = 251361, upload-time = "2026-07-02T13:08:51.304Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ed/55d9ffde994fba3897c0c783f77a7d053b0c18787f6892ed5b0aed73f469/coverage-7.15.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b761a1d504fd4bd1f20f418753964dca9f5862a511fc854dac58296b3b223671", size = 255129, upload-time = "2026-07-02T13:08:52.661Z" }, + { url = "https://files.pythonhosted.org/packages/1d/c0/ecbf33b8c460ea2718aeb813e2df8140d0370e5f67261c31524ceb0a2a8d/coverage-7.15.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:e43b045e11c16e897895758ae90e4a90cf99e93d58549e2f90c0e2272e155695", size = 251081, upload-time = "2026-07-02T13:08:54.188Z" }, + { url = "https://files.pythonhosted.org/packages/a9/de/fb87b4261f54448dd2b9504ef19a58be42cef0d9520595fbfe1219b15234/coverage-7.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:589b54513e901739f4b4582c705ce96b80c96f57641b1464607e2367a270e540", size = 251988, upload-time = "2026-07-02T13:08:55.726Z" }, + { url = "https://files.pythonhosted.org/packages/df/27/3494d5f291b9a4cb868f73c11221a8bd2d5bd761a8f9acea61ff57128dd1/coverage-7.15.0-cp311-cp311-win32.whl", hash = "sha256:106781b8482749162d0b47056937ba0933508e5d9447f65a5e7d5c422f0d6bb4", size = 222754, upload-time = "2026-07-02T13:08:57.091Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ee/cd4847ebc9be6a9c0123d763645a6f1f3be6b8c58c962706368b79cbac07/coverage-7.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:821e92b3631d762a339695824cadbbc73020354eba2a23a551a99ad34938fbe6", size = 223225, upload-time = "2026-07-02T13:08:58.594Z" }, + { url = "https://files.pythonhosted.org/packages/57/37/5011581aa7f2be498b97dcc7c9902192442a42f4f9a748aeadb3d6506b42/coverage-7.15.0-cp311-cp311-win_arm64.whl", hash = "sha256:309990eb5fb8014b9f67cb211f7fd41876ec8a88a88d3ae76de0ed1d611e3640", size = 222774, upload-time = "2026-07-02T13:09:00.074Z" }, + { url = "https://files.pythonhosted.org/packages/2a/74/fd4c0901137c4f8d81a76ada99e43c65163b4c94a02ece107a4ec0c6b615/coverage-7.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b75ee5e8cb7575636ac598719b4307ac529ec8fcd79608a35c3cd4d4dada812d", size = 220838, upload-time = "2026-07-02T13:09:02.084Z" }, + { url = "https://files.pythonhosted.org/packages/0f/2e/2347583467bd7f0402635101a916961915cc68fce652cd0db5f173ea04fc/coverage-7.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffb31267816b93b075302248cc1737506081b4f163df4401e9df1a6424aafabe", size = 221197, upload-time = "2026-07-02T13:09:03.617Z" }, + { url = "https://files.pythonhosted.org/packages/f0/17/99fa688541ae1d6e84543a0e544f83de0c944815b63e9e7b1ed411d15036/coverage-7.15.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e4d0bb73455bf97ab243a8f12c37c686ccf1c13bb614b7b85f1d062f06f42b2c", size = 252705, upload-time = "2026-07-02T13:09:05.059Z" }, + { url = "https://files.pythonhosted.org/packages/fb/02/6a95a5cd83b74839017ef9cf48d2d8c9ae60af919e17a3f336e6f9f1b7bd/coverage-7.15.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:20d9ccc4ebd0edc434d86dfd2a1dd2a8efa6b6b3073d0485a394fee86459ebb4", size = 255441, upload-time = "2026-07-02T13:09:06.559Z" }, + { url = "https://files.pythonhosted.org/packages/67/f2/406f6c57d600f68185942422c4c00f1a3255d60aee6e5fd961425cd9987e/coverage-7.15.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20c8a976c365c8cb12f0cbd099508772ea41fb5fa80657a8506df0e11bd278c5", size = 256556, upload-time = "2026-07-02T13:09:08.197Z" }, + { url = "https://files.pythonhosted.org/packages/74/8e/d3fa48489c15ecdec1ba48fd61f68798555dddd2f6716f9ad42adeb1a2a9/coverage-7.15.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f948fd5ba1b9cbca91f0ae08b4c1ce2b139509149a435e2585d056d57d70bf01", size = 258815, upload-time = "2026-07-02T13:09:09.691Z" }, + { url = "https://files.pythonhosted.org/packages/47/2e/2d40ddd110462c6a2769677cf7f1c119a52b45f568978fc6c98e4cc0dd0f/coverage-7.15.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f58185f06edf6ad68ec9fb155d63ef650c82f3fbd7e1770e2867751fb13158f4", size = 253117, upload-time = "2026-07-02T13:09:11.212Z" }, + { url = "https://files.pythonhosted.org/packages/51/c0/310782f0d7c3cb2b5ac05ba8d205fe91f24a36f6bf3256098f1782181c38/coverage-7.15.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:02adc79a920c73c647c5d117f55747df7f2de94571884758ce8bc58e04f0a796", size = 254475, upload-time = "2026-07-02T13:09:13.029Z" }, + { url = "https://files.pythonhosted.org/packages/86/f7/702da6c275f8ae6ade423d2877243122932c9b27f5403003b9ef8c927d12/coverage-7.15.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6eb7c300fbed667fd6e3588eba71c1904cdb06110ca6fdf908c26bdd88b8e382", size = 252619, upload-time = "2026-07-02T13:09:14.699Z" }, + { url = "https://files.pythonhosted.org/packages/fb/84/c5b15a7e5ecba4e56218d772d99fe80a63e63f8d11f12783723a6005ab45/coverage-7.15.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b5fb23fa2de9dce1f5c36c09066d8fcda16cd96e8e26686caa2d7cb9b567d65c", size = 256689, upload-time = "2026-07-02T13:09:16.103Z" }, + { url = "https://files.pythonhosted.org/packages/95/2f/c8b07559b57701230c61b23a953858c052890c12ef568d81780c6c46e92e/coverage-7.15.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:cec79341dbe6281484024979976d0c7f22beae08b4a254655decd25d42cbe766", size = 252189, upload-time = "2026-07-02T13:09:17.828Z" }, + { url = "https://files.pythonhosted.org/packages/6b/80/6d2f049dd3fd3dbfd60b62ba6b2162a04009e2c002ce70b24cf3878dec7a/coverage-7.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c664c5444b1d970b1b2a450e21fb19ee5c9cfdf151ded2dda37260031cca0da", size = 254059, upload-time = "2026-07-02T13:09:19.304Z" }, + { url = "https://files.pythonhosted.org/packages/ce/92/b0287a2c42031d25c628f815f89a3cd9f8268ee78bb1252c9356cda1c689/coverage-7.15.0-cp312-cp312-win32.whl", hash = "sha256:5f764a3fa339bde6b3aa97657f5a6a3a9451e4a5b4ea98a2892c773a43525f77", size = 222893, upload-time = "2026-07-02T13:09:20.812Z" }, + { url = "https://files.pythonhosted.org/packages/a9/69/e34c481915fecb499b3146975061dac528752e37706edc1804f32c822469/coverage-7.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:52f9a4d2c4c56c8848bc2f524916698354b0211488b38c49ad9ae54f6cafbff6", size = 223429, upload-time = "2026-07-02T13:09:22.315Z" }, + { url = "https://files.pythonhosted.org/packages/fe/98/6e878f0b571d32684ef3f38d7c03db241ca5b82a5da8a5391596a8f209c4/coverage-7.15.0-cp312-cp312-win_arm64.whl", hash = "sha256:31e5c3e70c85307ea35a12964e2e40f56ca2ee4b1c8c721ccf4609d17071080b", size = 222810, upload-time = "2026-07-02T13:09:23.812Z" }, + { url = "https://files.pythonhosted.org/packages/76/04/145a3748098bcc86b631a85408d2c3dc5c104e0bd86d605468239b25b6c4/coverage-7.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5be4caf3b28836f078abe700f8944dac4a65d78f16d6c600c89cb624e5535782", size = 220863, upload-time = "2026-07-02T13:09:25.371Z" }, + { url = "https://files.pythonhosted.org/packages/a4/5c/4ed55708fed2c64b63c9bc5715daef670872202101938869b7fe5d5fbb8f/coverage-7.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dd58ad1404704303ca8d4f4b8a1095e7cbc7040ef17a66df1e6619aa10176430", size = 221230, upload-time = "2026-07-02T13:09:26.897Z" }, + { url = "https://files.pythonhosted.org/packages/7b/19/3a80b97d3b2a5c77a01ae359c6bed20c13738fe3d9380f08616d4fec0281/coverage-7.15.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bbcbb317c2e5ded5b21104af81c29f391be2af98d065693ffbe8d23949b948e5", size = 252227, upload-time = "2026-07-02T13:09:28.543Z" }, + { url = "https://files.pythonhosted.org/packages/a1/fa/b70062750686bd7da454da27927622f48bbac6990ac7a4c4a4653e7b0036/coverage-7.15.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:27f31ecb458da3f859aab3f15ada871eb7a7768807d88df4a9f186bb17737970", size = 254823, upload-time = "2026-07-02T13:09:30.177Z" }, + { url = "https://files.pythonhosted.org/packages/a9/09/dad6a75a2e561b9dc5086a8c5257a7591d584246f67e23e70d2995b89ab6/coverage-7.15.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13fb759be317fdc62e0f56bffdf61cfcb45c7761ad6b71e3e583e71a67ae753c", size = 256059, upload-time = "2026-07-02T13:09:31.979Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e7/b5d2941fa9564573d44b693a871ff3156f0c42cbefe977a09fa7fdc59971/coverage-7.15.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d5cf007add5ab4bb8fa9f4c77e3732127c9e6cad501d7db43355fbfafca0be84", size = 258190, upload-time = "2026-07-02T13:09:34.035Z" }, + { url = "https://files.pythonhosted.org/packages/7c/1d/8e895bcde3c57ccd46d896dda5f2b3d5df761a1b0c6c9d450d175dedc632/coverage-7.15.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cc78d9843bd576fbe2118248258d485e968dc535f95ed504a7b0867ba9b51389", size = 252456, upload-time = "2026-07-02T13:09:35.765Z" }, + { url = "https://files.pythonhosted.org/packages/14/4c/f6997da343ddeb959be82c3b05322793f92c071ad45f7cb8a96336e2dd5f/coverage-7.15.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a263060f1de0b4b74b4e089c2a70b8003b3781c733329a9c8fd54995328f9950", size = 254192, upload-time = "2026-07-02T13:09:37.445Z" }, + { url = "https://files.pythonhosted.org/packages/17/27/a0bc09d032267b9da89d95a2d874cfbef2a5aebbf0e87cf7aba221d79a99/coverage-7.15.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c48decf16e0dfd5b049c7d5e82200c23c08126719142998d4f172444e3d0529e", size = 252153, upload-time = "2026-07-02T13:09:39.422Z" }, + { url = "https://files.pythonhosted.org/packages/54/c0/77fc233d9fba07b244c40948c53fe27308b8f21732fb3417f87fbd6fd992/coverage-7.15.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:08fb028000ed0aaa0a4cbdfbb98be7cb42f370db973fbbb469733505ab20e13e", size = 256310, upload-time = "2026-07-02T13:09:41.006Z" }, + { url = "https://files.pythonhosted.org/packages/d5/24/601cecfb5825becacb8d45219a018a3b55b9dbaec624efdb0ea249d08be2/coverage-7.15.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fb7dc0c3b7d8a1077abea0b8546ebc5e26d6ef6ecefc2f0f5ad2b8a53bdad837", size = 251974, upload-time = "2026-07-02T13:09:42.733Z" }, + { url = "https://files.pythonhosted.org/packages/47/1e/6f45e5a5b3d5484318d368702af6716b5ab8913b0428bec981a562fcf296/coverage-7.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6cb3602054ccbe9f0d8c2dc04bbeba90d5719236e2cd06e042ddd6d3fc7b6e37", size = 253745, upload-time = "2026-07-02T13:09:44.376Z" }, + { url = "https://files.pythonhosted.org/packages/8e/db/4df027a77bd11d0e527f44c53557c76e54ad027413d0304252ea3a78d67e/coverage-7.15.0-cp313-cp313-win32.whl", hash = "sha256:0bf781da64326b677be344df505171435b6f58716108606621d5d27d964fff8b", size = 222902, upload-time = "2026-07-02T13:09:46.122Z" }, + { url = "https://files.pythonhosted.org/packages/a0/10/0355894d34e231f2c5449e71287e81a50793a325df2e2b027b7bcd9dfd19/coverage-7.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:2c57a275078ee3fa185f83e400f765bc764a549de66d99b47881645cbd4ea629", size = 223444, upload-time = "2026-07-02T13:09:47.687Z" }, + { url = "https://files.pythonhosted.org/packages/06/ef/bb725f263befaaff851203ab338e68af15e195d7f7b5f323162532d9b6a8/coverage-7.15.0-cp313-cp313-win_arm64.whl", hash = "sha256:3812c61afc6685c7999b39320779ab8f43b7a3081fdb0def39976e56fbdb9a21", size = 222839, upload-time = "2026-07-02T13:09:49.717Z" }, + { url = "https://files.pythonhosted.org/packages/4f/9c/1e3ca54f72a3185ece06c58d871099898c48f0ed6430d17b6ab75f0d180a/coverage-7.15.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:41cb79af843222e11da87127ad0ecbfa878abadd0f770a4a99391a27d3887324", size = 220906, upload-time = "2026-07-02T13:09:51.339Z" }, + { url = "https://files.pythonhosted.org/packages/09/37/f718613d83b274880382f6b67e78f3802549ae39b0b3e65ae5b5974df56e/coverage-7.15.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7d2008989ef8fe54188d3f3bfa2e3099b025af11e90a6a1b9e7dc433d04263d8", size = 221239, upload-time = "2026-07-02T13:09:53.138Z" }, + { url = "https://files.pythonhosted.org/packages/a7/ce/22bae91e0b75445f68d365c7643ed0aa4880bbf77450ee74ca65bdae53a7/coverage-7.15.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:769e8ece11a596315ebf5aa7ec383aeeed016c091d2bf6363ffb996d41529092", size = 252286, upload-time = "2026-07-02T13:09:54.996Z" }, + { url = "https://files.pythonhosted.org/packages/dd/1e/bec5e32aa508615d9d7a2790effb25fb4dc28606e995816afe400b25ece3/coverage-7.15.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:65a6b6164ee5c39e2f3803f314292d6c61a607ba7fee253d1e03c42dc3903502", size = 254789, upload-time = "2026-07-02T13:09:56.678Z" }, + { url = "https://files.pythonhosted.org/packages/17/29/0e865435b4354e4a7c03b1b7920046d31d0a273d55decefea27e011cb9bf/coverage-7.15.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75128817f95a5c45bb01d65fd2d8b9cb54bbe03d81608fb70e3e14b437ad56c2", size = 256135, upload-time = "2026-07-02T13:09:58.343Z" }, + { url = "https://files.pythonhosted.org/packages/84/ff/33a870b58a13325d62fc0a6c8f01fa0ff667cef60c7498e2382a147dfa18/coverage-7.15.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9887bb428fe2d4cd4bee89bac1a6c9932f484afd5b36fbd4ff6ea5f825bb1f5e", size = 258449, upload-time = "2026-07-02T13:10:00.057Z" }, + { url = "https://files.pythonhosted.org/packages/18/7b/6fffe596bf3ddba8462758d02c5dad730fd91055a6634aa2e4226229181a/coverage-7.15.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0bfc0be1f702042207a93a00523b1065ee1fe951e96edf311581c0bbc2e34888", size = 252313, upload-time = "2026-07-02T13:10:01.946Z" }, + { url = "https://files.pythonhosted.org/packages/58/1b/11468dd6c1676ab831a70cb9a8d4e198e8607fa0b7220ab918b73fe9bfbd/coverage-7.15.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f64627d55def5a43282d70e08396672692f77e4da610a5bb8bb4060b432b6859", size = 254142, upload-time = "2026-07-02T13:10:04.065Z" }, + { url = "https://files.pythonhosted.org/packages/79/41/29328e21d16b1b95092c30dd700e08cf915bd3734f836df8f3bdb0e8fa9f/coverage-7.15.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:2c6f0fa473003905c6d5bac328ee4eba9fbea654f15bc24b8a3274b23363fa99", size = 252108, upload-time = "2026-07-02T13:10:06.11Z" }, + { url = "https://files.pythonhosted.org/packages/9b/de/05ccfb990439655b35afbfd8e0d13fe66677565a7d4eb38c3f5ef2635e1c/coverage-7.15.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2bcf9afaf064172c6ec3c58a325a9957ad1178c05dd934e25f253321776e0676", size = 256385, upload-time = "2026-07-02T13:10:08.141Z" }, + { url = "https://files.pythonhosted.org/packages/51/0e/486828a3d2695ea7a2609f17ff572f6b01905e608379440a11da4b8dffbe/coverage-7.15.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:baf06bc987115d6fb938d403f7eab684a057766c490367999a2b71a6883110c6", size = 251923, upload-time = "2026-07-02T13:10:10.179Z" }, + { url = "https://files.pythonhosted.org/packages/18/c7/03582b6715f078e5e558354c87616d945b9894cda2dace8e4009b17035e4/coverage-7.15.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f0405f2ff97b1c4c0e782cb32e02f32369bcf2e6b618b591d67e1ea754575dfe", size = 253580, upload-time = "2026-07-02T13:10:12.052Z" }, + { url = "https://files.pythonhosted.org/packages/db/dc/9e578bbaf2ecb4959a81b7e7601ad8cca772cba2892e8d144cb749b4a71a/coverage-7.15.0-cp314-cp314-win32.whl", hash = "sha256:ab282853ed5fbd64bbb162f19cb8fcb7087187508a6374b4f9c34ec1577c4e8f", size = 223107, upload-time = "2026-07-02T13:10:13.994Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3e/c8c3b75d8dbe0e35f7b0cc3ff5e949fc59500f70b21d0398813f66740664/coverage-7.15.0-cp314-cp314-win_amd64.whl", hash = "sha256:3bb3040e9f4bbe26fcb0cd7cc85ac63e630d3f3a9c74f027abf4caa27e706663", size = 223597, upload-time = "2026-07-02T13:10:15.906Z" }, + { url = "https://files.pythonhosted.org/packages/cd/bc/3cbc9fb036eb388519bccd521f783499c39b64256013fbc362782f196fe1/coverage-7.15.0-cp314-cp314-win_arm64.whl", hash = "sha256:346771144d34f7fa84ec28386f78e0f31653f33cf35e19d253d5b35f9e8201da", size = 223020, upload-time = "2026-07-02T13:10:17.844Z" }, + { url = "https://files.pythonhosted.org/packages/28/00/199c4a8d656dff63102577a056c0fce2ff6a79e40adac092fc986c49cbf1/coverage-7.15.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d34a010905fb6401324ba016b5da03d574967f7b21ce48ea41e66f0f1f95f641", size = 221638, upload-time = "2026-07-02T13:10:19.703Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8e/9d0092c96a3d3a26951ecc7020826aa57bcb1b119ca81acbba996884ab13/coverage-7.15.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bb25d825d885ca8036795dacfc3924d33091fc76d71ebc99420c6b79e77d96fa", size = 221903, upload-time = "2026-07-02T13:10:21.514Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b4/c0ca3028f42c9a08e51feb4561ef1192e5de99797cd1db5b04590c215bda/coverage-7.15.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:94c9686bfe8a9a6810297aecbd99beaa3445f9e8dc2f80b1382cca0d86b64461", size = 263267, upload-time = "2026-07-02T13:10:23.261Z" }, + { url = "https://files.pythonhosted.org/packages/5f/aa/a375e3846e5d3c013dc600b2a3231089055c73d77f5393dd2192a8d64da6/coverage-7.15.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9bd671c25f9d85f09d7ec481d0e43d5139f486c06a37139847a7ce569788af72", size = 265390, upload-time = "2026-07-02T13:10:25.152Z" }, + { url = "https://files.pythonhosted.org/packages/92/e1/5783cdabb797305e1c9e4809fea496d31834c51fa772514f73dc148bcfc9/coverage-7.15.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:110cbdf8d2e216577312cf06ccf85539c0e5a5420ef747e4a4719b5e483c88cd", size = 267811, upload-time = "2026-07-02T13:10:27.249Z" }, + { url = "https://files.pythonhosted.org/packages/85/31/96d8bbf58b8e9193bc8389574a91a0db48355ee98feb66aa6bf8d1b32eea/coverage-7.15.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2c5d4619214f1d9993e7b00a8600d14614b7e9d84e89507460b126aa5e6559e5", size = 268928, upload-time = "2026-07-02T13:10:29.242Z" }, + { url = "https://files.pythonhosted.org/packages/5e/7a/5294567e811a1cb7eda93140c628fa050d66189da28da320f93d1d815c73/coverage-7.15.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:781a704516e2d8346fbbd5be6c6f3412dd824785146528b3a01816f26c081007", size = 262378, upload-time = "2026-07-02T13:10:31.107Z" }, + { url = "https://files.pythonhosted.org/packages/69/3f/3f48538421f899f28946f90a3d272136a4686e1abf461cc9249a783ee0f3/coverage-7.15.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd4a1b44bcb65ee29e947ac92bbee04956df3a6bfc6143641bb6cae7ede00fc9", size = 265263, upload-time = "2026-07-02T13:10:32.942Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d3/092df15efcab8a9c1467ee960eb8019bbad3f9300d115d89ea6195f369ff/coverage-7.15.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0e4950c9d6d3e39c64c991814ff315e2d0b9cb8152363594212c9e55208c0a8f", size = 262866, upload-time = "2026-07-02T13:10:35.104Z" }, + { url = "https://files.pythonhosted.org/packages/e5/ab/0254d2b88665efb2c57ad368cc77ab5de3435bd8d5add4729c1b0e79431e/coverage-7.15.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:fe9c87ff42e5472d80d21704972e1f96e104a0a599d77c5e35db5a3c562e2571", size = 266599, upload-time = "2026-07-02T13:10:37.05Z" }, + { url = "https://files.pythonhosted.org/packages/a8/79/1cfa4023e489ce6fbc7be4a5d442dbc375edb4f4fda39a352cedb53263c2/coverage-7.15.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f00d5ae1dd2fe13fb8186e3e7d37bcbd8b25c0d764ff7d1b32cef9be058510a8", size = 261714, upload-time = "2026-07-02T13:10:38.966Z" }, + { url = "https://files.pythonhosted.org/packages/b7/eb/fee5c8665656be63f497418d410484637c438172568688e8ac92e06574e7/coverage-7.15.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:363ab38cc78b615f11c9cac3cf1d7eef950c18b9fdedfb9066f59461dcf84d68", size = 264025, upload-time = "2026-07-02T13:10:40.789Z" }, + { url = "https://files.pythonhosted.org/packages/ab/99/63005db722f91edc81abc16302f9cc2f6228c1679e46e15be9ae144b14d0/coverage-7.15.0-cp314-cp314t-win32.whl", hash = "sha256:54fd9c53a5fafff509195f1b6a3f9be615d8e8362a3629ff1de23d270c03c86b", size = 223413, upload-time = "2026-07-02T13:10:42.597Z" }, + { url = "https://files.pythonhosted.org/packages/c1/e8/2bc6181c4fb06f1a6b981eb85330cc57bfad7e3f710fc9c9d350013ba228/coverage-7.15.0-cp314-cp314t-win_amd64.whl", hash = "sha256:87b47553097ba185ed964866078e7e63adea9f5f51b5f39691c34f30afd21080", size = 224245, upload-time = "2026-07-02T13:10:44.47Z" }, + { url = "https://files.pythonhosted.org/packages/79/b8/4d959bf9cc45d0cfed2f4d35cafcab978cdb6ea02eb5100009cd740632a3/coverage-7.15.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aeefb2dd178fe7eee79f0ad25d75855cb35ee9ed472db2c5ea06f5b4fd00cec5", size = 223558, upload-time = "2026-07-02T13:10:46.368Z" }, + { url = "https://files.pythonhosted.org/packages/52/30/21b2ad45959cd50e909e02ebac1e30b4ceb7162e91c11d4c570223a458b7/coverage-7.15.0-py3-none-any.whl", hash = "sha256:56da6a4cbe8f7e9e80bd072ca9cefe67d7106a440a7ec06519ec6507ac94ad19", size = 212632, upload-time = "2026-07-02T13:10:48.641Z" }, ] [package.optional-dependencies] @@ -644,10 +644,10 @@ wheels = [ [[package]] name = "cuda-pathfinder" -version = "1.5.5" +version = "1.5.6" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/11/c8/26f2e4aae92f11522a96043892ba39a90eac610d5242523aa863212bc1c7/cuda_pathfinder-1.5.5-py3-none-any.whl", hash = "sha256:0228c023f95d1480f143ef5c8922d27a2ab052087a942e81dc289c9eb8f91689", size = 51671, upload-time = "2026-05-27T01:21:25.413Z" }, + { url = "https://files.pythonhosted.org/packages/d2/53/8fc9b0cdc5b7f62746e6a01b85b6461e5ae27f871010a5fcf8fa6950766d/cuda_pathfinder-1.5.6-py3-none-any.whl", hash = "sha256:7e4c07c117b78ba1fb35dac4c444d21f3677b1b1ff56175c53a8e3025c5b43c0", size = 52972, upload-time = "2026-06-30T00:58:04.34Z" }, ] [[package]] @@ -721,7 +721,7 @@ wheels = [ [package.optional-dependencies] array = [ { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] [[package]] @@ -817,11 +817,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.29.4" +version = "3.29.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e6/dc/be6cbe99670cd6e4ad387123647cb08e0c32975e223f82551e914c5568a6/filelock-3.29.4.tar.gz", hash = "sha256:10cdb3656fc44541cdf30652a93fb10ec6b05325620eb316bd26893e4201538a", size = 63028, upload-time = "2026-06-13T16:12:00.744Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/ee/29c668c50888588c432a702f7c2e8ee8a0c9e5286028d91f170308d6b2e9/filelock-3.29.5.tar.gz", hash = "sha256:6e6034c57a00a020e767f2614a5539863f056de7e7991d6d1473aef7ff73f156", size = 68927, upload-time = "2026-07-03T03:50:31.818Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/37/a065dc3bd6e49423a6532c642ca7378d3f467b1ef44c2800c937af7f9739/filelock-3.29.4-py3-none-any.whl", hash = "sha256:dac1648087d5115554850d113e7dd8c83ab2d38e3435dde2d4f163847e57b767", size = 42757, upload-time = "2026-06-13T16:11:59.582Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e3/f1fae3647d170919c2cf2a898e77e7d1a4e5c7cae0aed7bb4bd3f5ebff6f/filelock-3.29.5-py3-none-any.whl", hash = "sha256:8af830889ba3a0ffcefbd6c7d2af8a54012058103771f2e10848222f476a1693", size = 45073, upload-time = "2026-07-03T03:50:30.445Z" }, ] [[package]] @@ -1010,53 +1010,53 @@ wheels = [ [[package]] name = "grpcio" -version = "1.81.1" +version = "1.82.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b0/b5/1ff353970a87eda4c98251e34d2dfd214abd4982dc89119c9252a2a482d2/grpcio-1.81.1.tar.gz", hash = "sha256:6fa10a767143a5e82e8eaab53918af0cd8909a57a27f8cb2288b80a613ac671b", size = 13026582, upload-time = "2026-06-11T12:46:51.673Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/52/ea/1c2fa386b718ff493225e61cfc052ef400b4d6ffc54cbe261026432624b5/grpcio-1.81.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:d71d30f2d92f67d944631c523713934fee37292469e182ebcd2c1dd8a64ce53f", size = 6093112, upload-time = "2026-06-11T12:44:52.131Z" }, - { url = "https://files.pythonhosted.org/packages/2b/18/acf45fa8bd1bc5d7b0c2fd3dc4c209379fbd5bb396b440b68a83342226b7/grpcio-1.81.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b137f4bf3ada9dc44d411478decc6ff09a79ed30b306cd2abaa98408c3588137", size = 12074277, upload-time = "2026-06-11T12:44:55.354Z" }, - { url = "https://files.pythonhosted.org/packages/48/d7/ee86a60699b7db039f772a2c4a7e4facc7138984ff42c0130933a0063884/grpcio-1.81.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a3acb384427816dd5d470f47e62137b87f74da694faa8a50147012cf40df276a", size = 6640348, upload-time = "2026-06-11T12:44:59.223Z" }, - { url = "https://files.pythonhosted.org/packages/26/ee/d2de5e47378ffc207d476c230fea3be4d2601edbce9995f4fe45535d4896/grpcio-1.81.1-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f9a0ebbe45c29b5e5866593c12b78bd9035f0f0f0d4bc8361680cd580d99db49", size = 7331842, upload-time = "2026-06-11T12:45:02.001Z" }, - { url = "https://files.pythonhosted.org/packages/23/d6/abeda5c2b896a0b341584fe5ac411bbf72e197a9a374c355fb90965e08d2/grpcio-1.81.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a37165cc80b1a368384b383e63a4c38116a10467ae44c904d2d7468c4470ec2", size = 6842229, upload-time = "2026-06-11T12:45:04.76Z" }, - { url = "https://files.pythonhosted.org/packages/10/1c/1f0da7d590b4aeee006826ba568d0e419ca14b23e18f901a3da3e9fba613/grpcio-1.81.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6282caffb41ec326d4cb67ca9cf53b739d1b2f975a2acb498c7418e9f7d9a416", size = 7446096, upload-time = "2026-06-11T12:45:07.499Z" }, - { url = "https://files.pythonhosted.org/packages/6a/81/5c505d508f7c887aa7982d21443a4126597c80d34b0bcf40f9cec576d7f3/grpcio-1.81.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a35009284d0d3d5c2c9601c164a911b8b4331608d98a9a66d47d97bb2f522b70", size = 8445238, upload-time = "2026-06-11T12:45:10.243Z" }, - { url = "https://files.pythonhosted.org/packages/f7/b2/524847365122ee509ca17bcc4e092198b700e94af7bfd5bb5e6dd9f3ee66/grpcio-1.81.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1b22c80559854b789a01fd89e8929b3798a156c0829b5282a8939f33ad4115ad", size = 7873989, upload-time = "2026-06-11T12:45:13.102Z" }, - { url = "https://files.pythonhosted.org/packages/18/fa/07c037c50b006909d1d13a5848774f8aa7b242f70dc03a035c64eea0e6db/grpcio-1.81.1-cp311-cp311-win32.whl", hash = "sha256:428bec0161b48d8cf583c068591bc0016d0d9cfff52462b72b3884861ea768c5", size = 4202223, upload-time = "2026-06-11T12:45:16.166Z" }, - { url = "https://files.pythonhosted.org/packages/41/ed/6bff15376920942fac6b95b9802752b837437172c9e8fc2d3170546b89cc/grpcio-1.81.1-cp311-cp311-win_amd64.whl", hash = "sha256:30e825f6848d9f18bba350ed6c75c1b02a0b5184474a31db9a32b1fa66fd8c79", size = 4941303, upload-time = "2026-06-11T12:45:18.724Z" }, - { url = "https://files.pythonhosted.org/packages/85/07/9a979c81738863a738dc23d65177056e71fbb2db817740ed870b33434e7a/grpcio-1.81.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:8b39472beafc0bdcafc4c8c73ad082ebfdb449d566897a61e7acb4fa88089115", size = 6053264, upload-time = "2026-06-11T12:45:21.017Z" }, - { url = "https://files.pythonhosted.org/packages/75/95/539706ca0d3bd40dbad583dc56fd883da941f37556b629132da5762781b9/grpcio-1.81.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:12b7524c88d4026d3dcb7b0ebe16b6714f3b4af402ddd0f0639ab064a00c87c3", size = 12052560, upload-time = "2026-06-11T12:45:23.652Z" }, - { url = "https://files.pythonhosted.org/packages/e0/44/f257b7e0bd69c93b06c6cb8ac8d1b901ccb42bedabd83c1a4c77a71f8810/grpcio-1.81.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1e123f9b37edb8375fd74130d1f69c944bbf0a7b06761ae7211154b8759e94d2", size = 6595983, upload-time = "2026-06-11T12:45:26.963Z" }, - { url = "https://files.pythonhosted.org/packages/b9/f3/19782aa04c960968bef8c5539329d8e3bbc3364e2e46d19eb5e5cc5e43b7/grpcio-1.81.1-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:2c2e2ae6867c2966b8daccc836d54a13218e0007e9a490aeb81dd05be64d22d7", size = 7303455, upload-time = "2026-06-11T12:45:29.707Z" }, - { url = "https://files.pythonhosted.org/packages/eb/8c/dea020b6d91508cd84463917a63149ec196ee7db505d032ae43fcb3303b9/grpcio-1.81.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:766bc7c9a9c340342f4c864ccbda8e78111e4751f13b895812b9c148fb79e9d0", size = 6809167, upload-time = "2026-06-11T12:45:32.52Z" }, - { url = "https://files.pythonhosted.org/packages/1c/c7/3030dd940408083bd32cd95d634777a71605ade4887154d93e8a89244946/grpcio-1.81.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b259a04a737cb3496be0901328eb8b7552ed8df4865d8c8f1cf1bffcfc0776a3", size = 7412536, upload-time = "2026-06-11T12:45:35.403Z" }, - { url = "https://files.pythonhosted.org/packages/e0/dd/1172a9e42b168edcafefad6115346ef619a3fc02158bb170e66ced24bcdd/grpcio-1.81.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:85b10a45b8993d195c4f3ff57025b8d1e11834909ee475c403bfa60cb4caefaf", size = 8408276, upload-time = "2026-06-11T12:45:37.78Z" }, - { url = "https://files.pythonhosted.org/packages/25/7a/71437c7f3596e5246155c515852795a85a1a8d228190212432b13b97a95d/grpcio-1.81.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8ea1936c26b99999b27479853039a7f34713f56c49375ad52b38535ec93a796c", size = 7849660, upload-time = "2026-06-11T12:45:40.627Z" }, - { url = "https://files.pythonhosted.org/packages/65/40/7debc0da45d2efebafb82da75644be347497fe4ee250514b8cd3b86ae8bf/grpcio-1.81.1-cp312-cp312-win32.whl", hash = "sha256:a185a04039df6cae8648bc8ab6d6fde7bf94f7188ecf7828e76ac52eef1e41d6", size = 4185819, upload-time = "2026-06-11T12:45:43.027Z" }, - { url = "https://files.pythonhosted.org/packages/2e/b9/8fe3ba5ed462067774ebc1f9c7f26aa7ebcc280ddd476be107153de1339e/grpcio-1.81.1-cp312-cp312-win_amd64.whl", hash = "sha256:3ad74f8bb1a18963914c5452d289422830b39459e8776ebbcd207be1fbfb1d94", size = 4930461, upload-time = "2026-06-11T12:45:45.775Z" }, - { url = "https://files.pythonhosted.org/packages/7a/42/dcc2e4b600538ef18327c0839d56b7d3c3812337c5d710df5877dbb39b1e/grpcio-1.81.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:b10e1ff4756ed27d5a29d7fc79cfce7ef1ff56ad20025b89bac7cf79e09abbbe", size = 6054466, upload-time = "2026-06-11T12:45:48.43Z" }, - { url = "https://files.pythonhosted.org/packages/7b/4a/a36e03210183a8a7d4c80c3936acee679f4bd77d5861f369db47b2cc5f05/grpcio-1.81.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:819edbdcb42ab8598b494bcf0222684bbb7a3c772bd1b1f0be7e029a6063c28e", size = 12048795, upload-time = "2026-06-11T12:45:54.011Z" }, - { url = "https://files.pythonhosted.org/packages/b0/d5/d68e30b29098f63beab6fe501100fe82674ff142b32c672532da86a99b3a/grpcio-1.81.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c5bf2dc311127d91230cc79b92188c082634a06cf66c5234db49a43b910183b0", size = 6599094, upload-time = "2026-06-11T12:45:57.799Z" }, - { url = "https://files.pythonhosted.org/packages/3d/b3/e837954d279754f638a11cca5dcf6b24a005efb398984cefaf7735945a54/grpcio-1.81.1-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:e8ca6a1fcdb2943c9cbc1804a1baf3acb6071d72a471591678ded84218006e14", size = 7307182, upload-time = "2026-06-11T12:46:00.568Z" }, - { url = "https://files.pythonhosted.org/packages/0d/1e/b47957057e729adc6cdf519a47f8be2562b7140e280f1418443eb4022192/grpcio-1.81.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e64dd101d380a115cc5a0c7856788adb535f1a4e21fc543775602f8be95180ae", size = 6810962, upload-time = "2026-06-11T12:46:03.312Z" }, - { url = "https://files.pythonhosted.org/packages/40/26/569868e364e05b19ec8f969da53d230bcd89c962cd198f7c29943155c4d3/grpcio-1.81.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:98a07f9bf591e3a8919797bee1c53f026ba4acd587e5a4404c8e57c9ec36b2a5", size = 7415698, upload-time = "2026-06-11T12:46:06.005Z" }, - { url = "https://files.pythonhosted.org/packages/36/0c/5440a0582cb5653fc42a6e262eeb22700943313f8076f9dc927491b20a59/grpcio-1.81.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c261d74b1a945cf895a9d6eccd1685a8e837531beaab782da4d630a8d12deffb", size = 8407779, upload-time = "2026-06-11T12:46:08.84Z" }, - { url = "https://files.pythonhosted.org/packages/ff/aa/66fe9f39871d766987d869a03ee0842a026f499c7b1e62decb9e78a8088e/grpcio-1.81.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:58ad1131c300d3c9b933802b3cc4dc69d380822935ba50b28703156ea826fbf7", size = 7844521, upload-time = "2026-06-11T12:46:12.171Z" }, - { url = "https://files.pythonhosted.org/packages/f0/9e/69bb7194861bcd28fb3193261d4f9c3831b4446993f002cf59068943e7ab/grpcio-1.81.1-cp313-cp313-win32.whl", hash = "sha256:78e29211f26da2fdd0e9c6d2b79f489476140cf7029b6a64808ade7ca4156a42", size = 4182786, upload-time = "2026-06-11T12:46:15.192Z" }, - { url = "https://files.pythonhosted.org/packages/0d/20/3da8bb0d637feccdc3e1e419bb511ce93651ce7d54164f95de22cc0b8b34/grpcio-1.81.1-cp313-cp313-win_amd64.whl", hash = "sha256:edb59506291b647a30884b1d51a599d605f40b20af4a7dc3d33786a47a31de60", size = 4928648, upload-time = "2026-06-11T12:46:17.823Z" }, - { url = "https://files.pythonhosted.org/packages/b6/58/19414622b1bf6981bc9c05a365bd548e71876c89000083b3af489251e9c0/grpcio-1.81.1-cp314-cp314-linux_armv7l.whl", hash = "sha256:506f48f2f9c29b143fca3dad7b0d518c188b6c9648c75a2ae6e2d9f2c13a060b", size = 6055336, upload-time = "2026-06-11T12:46:20.557Z" }, - { url = "https://files.pythonhosted.org/packages/32/f1/2ec88adb92b0eba970dd0e0e7dd086341daa3c75eba4f735f9e44bf684b0/grpcio-1.81.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d865db4a6318e1c1bea83292e0ed231090538fc4ca45425b0f0480eb338bbc6e", size = 12056279, upload-time = "2026-06-11T12:46:24.255Z" }, - { url = "https://files.pythonhosted.org/packages/41/36/e8c5f8c6ec71de73733695ebc809e98b178b534ec6d8eaa31a7ebab4ad4c/grpcio-1.81.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2aa72e3ce1770317ef534f63d397b55e130725f5149bd36077c3b539019db27", size = 6608225, upload-time = "2026-06-11T12:46:27.601Z" }, - { url = "https://files.pythonhosted.org/packages/30/22/96fc577a845ab093326d9ab1adb874bd4936c8cf98ac8ed2f3db13a0a2fb/grpcio-1.81.1-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0490c30c261eded63f3f354979f9dc4502a9fb944cccb60cd9dc85f5a7349854", size = 7306576, upload-time = "2026-06-11T12:46:30.514Z" }, - { url = "https://files.pythonhosted.org/packages/76/7b/61dab5d5969f28d97fb1009cead1df0a5cd987d3315e1b37f18a4449f8bc/grpcio-1.81.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:410482da976329fe5f4067270401b12cf2bd552ff8020f054ecfaddb5475f9d6", size = 6812165, upload-time = "2026-06-11T12:46:33.699Z" }, - { url = "https://files.pythonhosted.org/packages/82/78/6e501929d4f5f96462fd82fd9f0f06e5f9612207582b862868d68757b27d/grpcio-1.81.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e3657301562ac3cb8018d30d0d3ebfa39932239f7b5703422057ef14b69949f5", size = 7422962, upload-time = "2026-06-11T12:46:36.511Z" }, - { url = "https://files.pythonhosted.org/packages/2a/7e/f2157589e66daa78ebb3165942d05a08bdea93b9d11c2bc1e172aef89685/grpcio-1.81.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:24c8e57504c8f45b237e40b99262d181071e5099a07053695b75d97bb53053a0", size = 8408176, upload-time = "2026-06-11T12:46:39.803Z" }, - { url = "https://files.pythonhosted.org/packages/da/df/c6717fef716e00d235ffb96123baf6dce76d6004f6233fa767c502861460/grpcio-1.81.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b427c19380991a4eaab2f6144b64b99b412043314c6bf4ab544f97bb31ee4190", size = 7846681, upload-time = "2026-06-11T12:46:43.013Z" }, - { url = "https://files.pythonhosted.org/packages/36/84/3502e9f210a6a5c4438c8aca3f88edd2e04f6a27f3d41b26cf0a0024b096/grpcio-1.81.1-cp314-cp314-win32.whl", hash = "sha256:61233fe8951e5c85dff81c2458b6528624760166946b5b47ea150a589168411f", size = 4264615, upload-time = "2026-06-11T12:46:45.741Z" }, - { url = "https://files.pythonhosted.org/packages/ff/b0/4af731ff7492c68a96e4c71bfd0f4590acde92b31c6fe4894e6465c10ff6/grpcio-1.81.1-cp314-cp314-win_amd64.whl", hash = "sha256:3768a5ff1b2125e6f552e561b6b2dca0e64982d8949689b4df145cf8b98d7821", size = 5070275, upload-time = "2026-06-11T12:46:48.486Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/b6/19/e29d3979b420b92d516ee97f0bff4fb88cbb3d724791318f50beb55db549/grpcio-1.82.0.tar.gz", hash = "sha256:bfe3247e0bb598585ce26565730970d21bccf3c9dc547dc6703a1a3c7888e8e1", size = 13184479, upload-time = "2026-07-06T04:22:54.421Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ed/dfba8843c9e0cbf5b3ad33998fab400a6290e29432c5a2c94e684ebbfadf/grpcio-1.82.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:64be5bea5f32b1b13c81d8f322bd49ab22d388b87b5c146050c4faf2769c2036", size = 6181469, upload-time = "2026-07-06T04:21:04.208Z" }, + { url = "https://files.pythonhosted.org/packages/ce/41/fe3cc2de8ccdf0b6a8a9939d731c8bdab37f80c96581237de359baa37bbe/grpcio-1.82.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:03c0f3085e30e51aa4592a2a0b8ee518c9fb1593eb0368f57d7ccfdda2a1cff1", size = 11971108, upload-time = "2026-07-06T04:21:06.634Z" }, + { url = "https://files.pythonhosted.org/packages/27/49/896d665121bd0a806d62a5bacf93c0db2f7097b29bc52bfab76141589f68/grpcio-1.82.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bc428d9825f1f83e61c65c8e6fb662384105022231938d6775e1b6cfb2bf517a", size = 6760133, upload-time = "2026-07-06T04:21:09.779Z" }, + { url = "https://files.pythonhosted.org/packages/78/1a/49739ab1be3f66106f54af46da599f2be9fdb79d4fc52e4d5a43a73cfc92/grpcio-1.82.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4a9389c1b3568b1d6b8c1d85d43eea701814e59280dd955ea0a2d2d65b90cb24", size = 7484373, upload-time = "2026-07-06T04:21:11.942Z" }, + { url = "https://files.pythonhosted.org/packages/d4/a9/53cc88d792b318ec8ed924791cde109096a4174e6e2c115231ece5b69a9a/grpcio-1.82.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:848774370f0783f88e1fa9888f972fa61ab1ec985fffe2668f53bccfa51ef6cb", size = 6924263, upload-time = "2026-07-06T04:21:14.864Z" }, + { url = "https://files.pythonhosted.org/packages/f3/23/d4ea910fcfbd62ad4a7efde95f0fbfec9fe99595be9b58a1a6d67c57c4ff/grpcio-1.82.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:743390c02fb9b565351fc4429b3385c84d851626df11d91a537636b02eaa67df", size = 7531845, upload-time = "2026-07-06T04:21:17.674Z" }, + { url = "https://files.pythonhosted.org/packages/f0/a6/bc6503929c332ae9c74ccf74c57fb67c6b2d9e6ed27bfddb0888d812df52/grpcio-1.82.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b53959e9af6618b10b46dba4ac82706c4ee90443e95a28004c014c2532d7e053", size = 8568207, upload-time = "2026-07-06T04:21:19.868Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a5/7529b811efd1cb3181bc789d22ec9403b27aa3d3d8acb0b122987036143a/grpcio-1.82.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8045b89d863b5a271e65413361f75dddd70eeab6bf79f2b0e0eaeeb0900d8d24", size = 7938767, upload-time = "2026-07-06T04:21:22.597Z" }, + { url = "https://files.pythonhosted.org/packages/5f/b3/1fa12730f95458fba4319b7935fc4c02a604404ea27f74756c51bf73a263/grpcio-1.82.0-cp311-cp311-win32.whl", hash = "sha256:24310aaf1f96a5327fc0d8e00fc98f0e9f90be8e09cc51becd2553d4b823d286", size = 4256371, upload-time = "2026-07-06T04:21:25.157Z" }, + { url = "https://files.pythonhosted.org/packages/60/10/71202ae4c1cade358a07436c010c145210bc2a730a731e5d297a2dac4f6e/grpcio-1.82.0-cp311-cp311-win_amd64.whl", hash = "sha256:ddcd5f8db890c5c822e4c0b89d641b1db6a0c7255fff02535cbe77c4dabf450f", size = 5009634, upload-time = "2026-07-06T04:21:27.282Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e6/9ce68bc431224177b6f506cfb4896a742119c51255143069970955b6510b/grpcio-1.82.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:03cce11532da292215cd8e5f444de91982e39dfb41a916e0e0f91a5c128588ea", size = 6144680, upload-time = "2026-07-06T04:21:29.808Z" }, + { url = "https://files.pythonhosted.org/packages/59/93/00ecf28e1be7a70b4b4d148d3a551b6c9a37024a669c3650a2c374c64026/grpcio-1.82.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:01336fe31d1ea5d5154b0d803eb3584e69fb96e0e657acc87bd4d48d6abc9587", size = 11952213, upload-time = "2026-07-06T04:21:32.347Z" }, + { url = "https://files.pythonhosted.org/packages/38/05/9be6bf4cddb5b5f39c0748cdf5639525cd4116a157c8f3802c9217e54882/grpcio-1.82.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe56f1bea709ddb2531fbd510a2dd6040e993985507c3b2bf3404507aee989b1", size = 6710770, upload-time = "2026-07-06T04:21:35.382Z" }, + { url = "https://files.pythonhosted.org/packages/00/97/62c0969e993673ebef16d526db2504f38bc4c73cf2593c28746791ab7956/grpcio-1.82.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:77a5d8fe331376c7aaa4e06e903a43bd144de4f98c6e414e13cbc5d64e5aa61e", size = 7450671, upload-time = "2026-07-06T04:21:37.923Z" }, + { url = "https://files.pythonhosted.org/packages/67/1f/d83d9fbaaef2b6ebbf70a6e467000f13f92b4cf0b84c561c162b43128439/grpcio-1.82.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9ce80ea66c803398fde9c5b1fd925920c9742da7f10f8b85acac0adac3e4d7e0", size = 6886855, upload-time = "2026-07-06T04:21:40.527Z" }, + { url = "https://files.pythonhosted.org/packages/c5/36/5ed2e28b8c5f00599c7d5e94d5f82c32cf73a6fa42d13c2900cb37c861ec/grpcio-1.82.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4665dc8c9ec30acff455e2b15c47b825f18647c555483aa1ccf670adead8adcc", size = 7501322, upload-time = "2026-07-06T04:21:43.78Z" }, + { url = "https://files.pythonhosted.org/packages/89/4e/69113709e14e24289940d6aef067b797c73c8c96f580683af7d5f09b22b0/grpcio-1.82.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4405d19ecfc7a8caa9043ff5a651e1871b54fc620f917de5dede7e6fb78e9e14", size = 8536896, upload-time = "2026-07-06T04:21:46.387Z" }, + { url = "https://files.pythonhosted.org/packages/30/f6/08bd6eb89a558f0f59dacaef747afda7c21e2c2ecf138ae2ec533dea8aeb/grpcio-1.82.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:30e580c19178609799660f52b1e1eb09248969be20dc44caaa4dcaa3e8450dd9", size = 7913890, upload-time = "2026-07-06T04:21:49.733Z" }, + { url = "https://files.pythonhosted.org/packages/d5/a1/04b467e2ffc93f8e50b660bc6f47c3c8e9ba7603104a741e3d5663a261d4/grpcio-1.82.0-cp312-cp312-win32.whl", hash = "sha256:dbbd83dbaa856b387a4eba74ec3add7f594f090d3b4d390d829dd5834816aae4", size = 4240969, upload-time = "2026-07-06T04:21:52.281Z" }, + { url = "https://files.pythonhosted.org/packages/25/3c/15d3601384d5937e8d9fbe6d8e7b8171008d649744dd7b6c864932937ec9/grpcio-1.82.0-cp312-cp312-win_amd64.whl", hash = "sha256:73176d270699d76a9dc3753f25e01c19fb98f830f826a506e917d83629f1b39b", size = 5001575, upload-time = "2026-07-06T04:21:54.839Z" }, + { url = "https://files.pythonhosted.org/packages/f0/c8/7aace81e7cd12c6ffccf0d47ac389a3f19e5280e9ab04d301d02855ecd25/grpcio-1.82.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:841bcaa24882921d41cd7a82118f9a766edcf8807c2f486e7deeb0ff5ea36181", size = 6146066, upload-time = "2026-07-06T04:21:57.527Z" }, + { url = "https://files.pythonhosted.org/packages/10/17/b448f2265e4927188425c0b09fb943c39b50f7f53fbead644b44ccdf834b/grpcio-1.82.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:d4bb7219c39c735e6573ae3c33aeafb2a4e1f1cc93a762f7899c283defaed365", size = 11948617, upload-time = "2026-07-06T04:22:00.066Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a3/fba54e50fffc9f74b1ffd5eb4e47310e6650557ef136a165f1fe7df03a65/grpcio-1.82.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:254230d2cb773a87380858d0fc74653b4761bf2013c83fe1c5d77ed8f241fc9d", size = 6714591, upload-time = "2026-07-06T04:22:04.04Z" }, + { url = "https://files.pythonhosted.org/packages/29/6f/f0ec3f1a61a746dc7037a737ba4cbe60959645311ac8542bb1ad4e72ae8b/grpcio-1.82.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:16d4f215344057eb21038319b05a0c531306230c17b075fa8461a944581006dd", size = 7454986, upload-time = "2026-07-06T04:22:06.776Z" }, + { url = "https://files.pythonhosted.org/packages/b6/11/9d6ce94465d6a7f92c413430b3e77f0879b39427a217ffb3d23585df4bb3/grpcio-1.82.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a7c283b459151a2e84df32b28c31562ab3e7f624bccffce176c5608565b0212f", size = 6888623, upload-time = "2026-07-06T04:22:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/51/1a/b887adb7abb69081c4f24bc66c3612975e87caf5f7c6aa3916bb82d42e5f/grpcio-1.82.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:96a3be4d2a482ff004c036f6391f33553b36a7627138d1ca3ecc1e70d55d8af5", size = 7505067, upload-time = "2026-07-06T04:22:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/60/94/90f11c0f227ac653cff769e05c7f279da945e134c9351a1c37d475714686/grpcio-1.82.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bc481e6c7e6c8721797f2ab764092c843fd5cea06d4c7e9f4c3e3b7ecf331eb0", size = 8535382, upload-time = "2026-07-06T04:22:14.453Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3d/de8569386685213135dfbaab58e221add11858485524cf7d5987efc6d70b/grpcio-1.82.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:680f633bc788652222cb90a568ec374edcd91e1fe7715a7c248ece8e1032c2bd", size = 7910708, upload-time = "2026-07-06T04:22:17.994Z" }, + { url = "https://files.pythonhosted.org/packages/42/ab/3950b32369763818ae89ef76d52e84bd034d3a0ab46bf315669c913b32e2/grpcio-1.82.0-cp313-cp313-win32.whl", hash = "sha256:640b4715b9c206e5176e46601aa1422c47c779f642a869dfd27062e8fde13dfb", size = 4240351, upload-time = "2026-07-06T04:22:20.626Z" }, + { url = "https://files.pythonhosted.org/packages/a7/5c/c7b9a48efe973883f5707a311f87772a4c307a22ec80d6f3c851846a0a02/grpcio-1.82.0-cp313-cp313-win_amd64.whl", hash = "sha256:8523e81bd23f83e607aed28fbd8244b2be4aeb34e084fcef1bd1867709085c2a", size = 5000985, upload-time = "2026-07-06T04:22:23.365Z" }, + { url = "https://files.pythonhosted.org/packages/bd/6e/abc5efe11b73eefa0183531032eef2a53f33aeced6484b0d9da559c12ed6/grpcio-1.82.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:136969867c14fcc743fa39b12d7da133cae7da4f8e0e7767b6b8b7e75a8c24fd", size = 6146898, upload-time = "2026-07-06T04:22:26.082Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e4/d01ee88ceac221436dce1584ae1f046bd44d0dffc5a92e95aa34857c4516/grpcio-1.82.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:a7e246cd216662f513ae79ea3afadde6afe8a659b48a4170bae9a896e69ac254", size = 11954897, upload-time = "2026-07-06T04:22:29.114Z" }, + { url = "https://files.pythonhosted.org/packages/99/b9/2a35540ec08afc08859c35afd1a8a51e2585f39b12cf4eee68dcb1fa973d/grpcio-1.82.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:85c2a4e9e8a30d73d41e547a5101e57a1ed2093ec4b2daf6847c344adba00523", size = 6723102, upload-time = "2026-07-06T04:22:32.146Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ba/af71af59943b5d08879617a1d97495d45c53e43f3c6ef16f820c64e107f9/grpcio-1.82.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:a3a7802328e0d20e6ac458ff1974a8096cfaa3ca84dea0903235f641f1d703c1", size = 7454545, upload-time = "2026-07-06T04:22:34.776Z" }, + { url = "https://files.pythonhosted.org/packages/b3/7e/258aae12b68910140725873657469ff166f8968bdb0674e12d6452b1812c/grpcio-1.82.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9331e6b91b26cffd63fb70a545f3c2cac8dc1b434b4436f43915c23e1e22f265", size = 6889585, upload-time = "2026-07-06T04:22:37.411Z" }, + { url = "https://files.pythonhosted.org/packages/5e/04/cfada8930306b9101cfb405b33ecb3b72abce51d725f900f167ffbc3146b/grpcio-1.82.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:670ab93a0059e707de53b9e715304b9f566b6defee80e5490cdd15820ce032f6", size = 7514162, upload-time = "2026-07-06T04:22:40.076Z" }, + { url = "https://files.pythonhosted.org/packages/2e/57/955f95989338857a6d73ee838b4d9e8198d50972a70ca83ec22322396f4c/grpcio-1.82.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fbfe2e321bb30b84799677d6e8469896e487b494e9e4fbc9b8cc4425fe054d44", size = 8536163, upload-time = "2026-07-06T04:22:42.801Z" }, + { url = "https://files.pythonhosted.org/packages/06/62/826da0b0f01c7ba3f7ea0185968f551290deb28968d98b1edf604c895db8/grpcio-1.82.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2472b72a55da8570e089f1ca22f34a350d86c109be2755f6ee1cca5ebbdd9b54", size = 7912573, upload-time = "2026-07-06T04:22:46.187Z" }, + { url = "https://files.pythonhosted.org/packages/4d/b4/d8824a66ac1ac7fbcdf63806f1a8725d95426654e859afa4606070c78740/grpcio-1.82.0-cp314-cp314-win32.whl", hash = "sha256:c9feb986151c2726789599b3672c1c9faa1d0824da921dc3f33a8cb1f93e2861", size = 4321824, upload-time = "2026-07-06T04:22:48.866Z" }, + { url = "https://files.pythonhosted.org/packages/da/08/02e581cc0bbb4c7b842f85e66a945b46d5bcc5927575c1086edfbc3360e7/grpcio-1.82.0-cp314-cp314-win_amd64.whl", hash = "sha256:b1eeb0480d86965263f8c8ae7ee5360c284d22176a196aab02fce7fba8d89dd8", size = 5141112, upload-time = "2026-07-06T04:22:51.443Z" }, ] [[package]] @@ -1074,7 +1074,7 @@ version = "3.16.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/db/33/acd0ce6863b6c0d7735007df01815403f5589a21ff8c2e1ee2587a38f548/h5py-3.16.0.tar.gz", hash = "sha256:a0dbaad796840ccaa67a4c144a0d0c8080073c34c76d5a6941d6818678ef2738", size = 446526, upload-time = "2026-03-06T13:49:08.07Z" } wheels = [ @@ -1189,7 +1189,7 @@ version = "2.37.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "pillow" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b1/84/93bcd1300216ea50811cee96873b84a1bebf8d0489ffaf7f2a3756bab866/imageio-2.37.3.tar.gz", hash = "sha256:bbb37efbfc4c400fcd534b367b91fcd66d5da639aaa138034431a1c5e0a41451", size = 389673, upload-time = "2026-03-09T11:31:12.573Z" } @@ -1818,7 +1818,7 @@ dependencies = [ { name = "fonttools" }, { name = "kiwisolver" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "packaging" }, { name = "pillow" }, { name = "pyparsing" }, @@ -2003,7 +2003,7 @@ version = "0.16.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/44/bd/8a391e7c356366224734efd24da929cc4796fff468bfb179fe1af6548535/numcodecs-0.16.5.tar.gz", hash = "sha256:0d0fb60852f84c0bd9543cc4d2ab9eefd37fc8efcc410acd4777e62a1d300318", size = 6276387, upload-time = "2025-11-21T02:49:48.986Z" } @@ -2114,57 +2114,57 @@ wheels = [ [[package]] name = "numpy" -version = "2.5.0" +version = "2.5.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", "python_full_version >= '3.12' and python_full_version < '3.14'", ] -sdist = { url = "https://files.pythonhosted.org/packages/e7/05/3d27272d30698dc0ecb7fdfaa41ad70303b444f81722bb99bce1d818638a/numpy-2.5.0.tar.gz", hash = "sha256:5a129578019311b6e56bdd714250f19b518f7dceeeb8d1af5490f4942d3f891c", size = 20652461, upload-time = "2026-06-21T20:57:51.95Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/0a/11486d02add7b1384dff7374d124b1cfbb0ee864dcc9f6a2c0380638cf84/numpy-2.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:489780423903667933b4ed6197b6ec3b75ea5dd17d1d8f0f38d798feb6921561", size = 16789987, upload-time = "2026-06-21T20:56:16.657Z" }, - { url = "https://files.pythonhosted.org/packages/55/b2/285f48640a181947b4587a3766d21ec1eaa7fea833d4b49957e09da467a2/numpy-2.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ece55976ced6bca95a03ae2839e2e5ccffe8eb6a3e7022415645eb154a81e4e6", size = 11760322, upload-time = "2026-06-21T20:56:19.813Z" }, - { url = "https://files.pythonhosted.org/packages/dd/67/b032db1eb03ca30d16eda3b0c22aaa615338b9263c2fd559d0f29451aca4/numpy-2.5.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:c83b664b0e6eee9594fa920cf0639d8af796606d3fad6cc70180c87e4b97c7be", size = 5319605, upload-time = "2026-06-21T20:56:22.173Z" }, - { url = "https://files.pythonhosted.org/packages/b9/83/03fc7300c7c6b6c84c487b1dc80d322817b95fbd1f4dd57a85e23b7198de/numpy-2.5.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:bf80333980bf37f523341ddd72c783f39d6829ec7736b9eb99086388a2d52cc2", size = 6653628, upload-time = "2026-06-21T20:56:23.914Z" }, - { url = "https://files.pythonhosted.org/packages/82/49/2ec21730bc63ccfda829323f7040a8ed4715b3852ce658689cf74ee96a8c/numpy-2.5.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1a4874217b36d5ac8fc876f52e39df56f8182c88463e9e2dceabf7ca8b7efb8", size = 15153691, upload-time = "2026-06-21T20:56:25.631Z" }, - { url = "https://files.pythonhosted.org/packages/bb/6b/f4a3d0637692c49da8ef99d72d52526f92e0a8d6ac4f0ca9f31441b9d9ea/numpy-2.5.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aaa760137137e8d3c920d27927748215b56014f92667dc9b6c27dfc61249255a", size = 16660066, upload-time = "2026-06-21T20:56:28.009Z" }, - { url = "https://files.pythonhosted.org/packages/3a/2f/c354ec86d1f3f5c19649463b0d39652e160736e5b0a4cd18dff0576715c4/numpy-2.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7174ce8265fc7f7417d171c9ea8fe905220748893ea67a2a7abe726ec331c4b0", size = 16514638, upload-time = "2026-06-21T20:56:30.26Z" }, - { url = "https://files.pythonhosted.org/packages/06/34/43efdcb319988648580f93c11f1ae82cf7e2faa74925e98e454ae3aa95f8/numpy-2.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b8c3daaf99de52415d20b42f8e8155c78642cb04207d02f9d317a0dcf1b3fb54", size = 18419647, upload-time = "2026-06-21T20:56:32.41Z" }, - { url = "https://files.pythonhosted.org/packages/71/e2/f5d1676b1d7fb682eb5e9a1641e7ebd2414b3216c370661d1029778908b4/numpy-2.5.0-cp312-cp312-win32.whl", hash = "sha256:6206db0af545d73d068add6d992279145f158428d1da6cc49adc4b630c5d6ee5", size = 6056688, upload-time = "2026-06-21T20:56:34.657Z" }, - { url = "https://files.pythonhosted.org/packages/8f/7c/48f115d1c58a34032facebcd51fdf2d02df2c51d4a46a81dd1197bb2ea6b/numpy-2.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:6f2d6873e2940c860a309d21e25b1e69af6aaffdd80aa056b04c16380db1c4f2", size = 12419237, upload-time = "2026-06-21T20:56:36.24Z" }, - { url = "https://files.pythonhosted.org/packages/86/26/2e0882f4044d1b1a1b63e875151fb2393389032022a8b7f5657a7996d3b2/numpy-2.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:a55e1eb2bca2cfd17a16b213c99dfc8502d47b0d494224d2122277d0400935ca", size = 10339912, upload-time = "2026-06-21T20:56:38.733Z" }, - { url = "https://files.pythonhosted.org/packages/8a/33/07675aaad7f26ea013d5e884d9a0d784b79c6bd7566c333f5a52fa3c610b/numpy-2.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:520e6b8be0a4b65840ac8090d4f51cef4bed66e2b0894d5a520f099adc24a9b2", size = 16784890, upload-time = "2026-06-21T20:56:40.799Z" }, - { url = "https://files.pythonhosted.org/packages/85/4b/953118a730ee3b35e28645e0eb4cf9beec5bdbb954e1ac2f5fcefba6bbc3/numpy-2.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:146b81cdd3967fdb6beca8ba25f00c58741d8f3cbd797f55af0fbe0bfec3469c", size = 11754584, upload-time = "2026-06-21T20:56:43.094Z" }, - { url = "https://files.pythonhosted.org/packages/44/9b/56dd530c367c74ae17411027cea4135ca57e1e0583bf5594cee18bd83217/numpy-2.5.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:126b88d95e8ff9b00c9e717aa540469f21d6180162f84c0caec51b16215d49cd", size = 5313904, upload-time = "2026-06-21T20:56:45.503Z" }, - { url = "https://files.pythonhosted.org/packages/ce/b0/bcd672edad27ecca7da1f7bb0ce72cd1706a4f2d79ae94990afc97c13e1c/numpy-2.5.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d4313cef1594c5ce46c31b6e54e918338f63f16ee9322304e8c9114d6d81c8bd", size = 6648504, upload-time = "2026-06-21T20:56:47.567Z" }, - { url = "https://files.pythonhosted.org/packages/80/9e/15cdfcbd30a1544a46c9e487a00df331c4672450216538705a9e51fa6710/numpy-2.5.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:750fb097caf26fa878746d9d119f6f9da12dedcbff1eea966c3e3447647c4a9e", size = 15150086, upload-time = "2026-06-21T20:56:49.352Z" }, - { url = "https://files.pythonhosted.org/packages/32/4e/8d7656ccaab3e81e97258b8a9bc5f0c8502513a92fb4ceb0a2cbfebc17bf/numpy-2.5.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3893adc2dc7c0412ba76777db55a049215d99c9aa3113003be8f49f4f1290ab9", size = 16647250, upload-time = "2026-06-21T20:56:51.542Z" }, - { url = "https://files.pythonhosted.org/packages/3c/81/97060281b602ed07f21b12f4ec409eac1f75a2f91fbc829ed8b2becf3ad4/numpy-2.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:835e454dd99b238cdc5a3f63bce2371296f5ebc53ca1e0f8e6ddbb6d92a29aab", size = 16512864, upload-time = "2026-06-21T20:56:55.401Z" }, - { url = "https://files.pythonhosted.org/packages/33/ab/4496208146911f8d8ddb54f68a972aafa6c8d44babcb2ea03b0e5cc87c9d/numpy-2.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f9836778081a0a3c02a6a21493f3e9f5b311f8d2541934f31f05583dc999ea4", size = 18408407, upload-time = "2026-06-21T20:56:57.75Z" }, - { url = "https://files.pythonhosted.org/packages/d4/9f/a4df67c181e4ee8b467aa3332dc2db10fd5c515136831302f3ca48bc0a01/numpy-2.5.0-cp313-cp313-win32.whl", hash = "sha256:0b525be4744b60bb0557ac872d53ef07d085b5f39622bc579c98d3809d05b988", size = 6054431, upload-time = "2026-06-21T20:57:00.016Z" }, - { url = "https://files.pythonhosted.org/packages/30/53/491e1c47c55b62ccc6a63c1c5b8635c73fc2258dddeb9bda27cae4a0ae96/numpy-2.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:44353e2878930039db472b99dc353d749826e4010bd4d2a7f835e94a97a5c748", size = 12414420, upload-time = "2026-06-21T20:57:01.815Z" }, - { url = "https://files.pythonhosted.org/packages/eb/4a/25c2906f541e9d9f4c5769764db732e6627be91a13f4724fa10634d77db4/numpy-2.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:48f54b00711f83a5f796b70c518e8c2b3c5848dda03a54911f23eb68519b9b60", size = 10339533, upload-time = "2026-06-21T20:57:03.961Z" }, - { url = "https://files.pythonhosted.org/packages/86/ad/abc44aaceaf7b17ee1edde2bbb4458da591bc79574cffff50c4bb35f00d1/numpy-2.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f27582c55ba4c750b7c58c8faf021d2cd9324a662b466229db8a417b41368af9", size = 16783807, upload-time = "2026-06-21T20:57:06.253Z" }, - { url = "https://files.pythonhosted.org/packages/5d/39/b72e168daf9c00fb20c9fc996d00437ccecdef3102387775d29d7a62576d/numpy-2.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:28e7137057d551e4a83c4ae414e3451f50568409db7569aacc7f9811ee06a446", size = 11765215, upload-time = "2026-06-21T20:57:08.547Z" }, - { url = "https://files.pythonhosted.org/packages/f7/a0/8400a9c0e3625182347593f5e1f57da9a617a534794805c8df5518154ddc/numpy-2.5.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:e1da54b53e75cd9fcfc23efcc7edab2c6aecf97b6037566d8a0fe804af8ec57c", size = 5324493, upload-time = "2026-06-21T20:57:11.012Z" }, - { url = "https://files.pythonhosted.org/packages/f6/8c/0d104deaa0401c93395a629ec902891618a2eff76d19229139cb5a887bfc/numpy-2.5.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:694d8f74e156f7fd01179f1aa8faa2f648ab6ae0f70b6c3fe57a03249aea2303", size = 6645211, upload-time = "2026-06-21T20:57:12.919Z" }, - { url = "https://files.pythonhosted.org/packages/6a/d9/4a4a628c812750363786afc3d33492709a5cd64b215469c16b0f6c7bb811/numpy-2.5.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a7569a7b53c77716f036bb28cb1c91f166a26ec7d9502cd1e4bdfe502fdec22", size = 15166004, upload-time = "2026-06-21T20:57:14.717Z" }, - { url = "https://files.pythonhosted.org/packages/a0/5e/2a902317d7fc4aa93236e80c932662dadfc459b323d758329e01775125e1/numpy-2.5.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:39a0433bd4086ebd462960cf375e19195bb07b53dc1d87dd5fcf47ad78576f03", size = 16650797, upload-time = "2026-06-21T20:57:16.906Z" }, - { url = "https://files.pythonhosted.org/packages/e9/a0/a0090e6329f4ca5992c07847bb579c5259a19953dc57255bb08793142ffb/numpy-2.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:929f0c79ac38bcbd7154fe631dc907abfeddbcc5027a896bd1f7767323271e7a", size = 16524647, upload-time = "2026-06-21T20:57:19.165Z" }, - { url = "https://files.pythonhosted.org/packages/5e/7d/6caf27734c42b65837e7461ed0dbbd6b6fc835060c9714ec59d673bb383a/numpy-2.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cc4f247a47bbf070bfd70be53ccdcf47b800af563535e7bbe172322197c30e21", size = 18411841, upload-time = "2026-06-21T20:57:21.638Z" }, - { url = "https://files.pythonhosted.org/packages/13/dc/26edadbd812536769a82c2e9e002234e33feb5da43061d47a044f6d309b7/numpy-2.5.0-cp314-cp314-win32.whl", hash = "sha256:5dc71423499fab3f46f7a7201155ade1669ea101f2f429d332df9e72f8161731", size = 6106361, upload-time = "2026-06-21T20:57:23.844Z" }, - { url = "https://files.pythonhosted.org/packages/f2/9e/4dd1459282229a72d92dece2ae9138e5cac94a72263a7ceb48f37434c925/numpy-2.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:ebb81d9d5443e0309d6c54894c3fbed74ad7da0714352a67b6d773cd189eae73", size = 12551749, upload-time = "2026-06-21T20:57:25.945Z" }, - { url = "https://files.pythonhosted.org/packages/05/a7/6bc6384c080b86c7f6c85c5bc5b540b24f4f679cd144791d99574e90d462/numpy-2.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:3b94d0d0deceebfad3e67ae5c0e5eb87371e8f7a0581cd04a779928c2450cf1e", size = 10617072, upload-time = "2026-06-21T20:57:28.175Z" }, - { url = "https://files.pythonhosted.org/packages/86/6b/4a2b71d66ada5608ae02b63f150dfad520f6940721cb7f029ad270befc0e/numpy-2.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:22f3d43e362d650bc39db1f17851302874a148ca95ba6981c1dfb5fa6862f35b", size = 11881067, upload-time = "2026-06-21T20:57:30.104Z" }, - { url = "https://files.pythonhosted.org/packages/dc/b2/d365eb40a20efb49d67e9feb90494ed8511282ee1f5fa16006675c65397d/numpy-2.5.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:243563efb4cd7528a264567e9fd206c87826457322521d06206a00bfa316c927", size = 5440290, upload-time = "2026-06-21T20:57:32.193Z" }, - { url = "https://files.pythonhosted.org/packages/fa/5e/e9c03188de5f9b767e46a8fe988bcfd3efad066a4a3fda8b9cb11a93f895/numpy-2.5.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:84881d825ca75249b189bbee875fcfe3238aa5c479e6100893cda566e8e86826", size = 6748371, upload-time = "2026-06-21T20:57:33.933Z" }, - { url = "https://files.pythonhosted.org/packages/fd/1d/68c186a38a5027bae2c4ddd5ea681fdaf8b4d30fb7301def6d8ad270390f/numpy-2.5.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cda12aa4779d42b8771180aba759c96f527d43446d8f380ab59e2b35e8489efd", size = 15214643, upload-time = "2026-06-21T20:57:35.677Z" }, - { url = "https://files.pythonhosted.org/packages/8c/67/73f67b7c7e20635baae9c4c3ead4ae7326a005900297a6110971abd62eb5/numpy-2.5.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c0121101093d2bd74981b10f8837d78e794a8ff57834eb27179f49e1ba11ac6", size = 16690128, upload-time = "2026-06-21T20:57:38.159Z" }, - { url = "https://files.pythonhosted.org/packages/eb/05/d4c1fb0c46d02a27d6b2b8b319a78c90937acec8631c1641874670b31e6f/numpy-2.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d371c92cfa09da00022f501ab67fafaea813d752eb30ac44336d45b1e5b0268a", size = 16577902, upload-time = "2026-06-21T20:57:40.447Z" }, - { url = "https://files.pythonhosted.org/packages/9e/1d/771c797d50fa26e4888989cccf1d50ee51f530d4e455ad2692dcb64fa711/numpy-2.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9990713e9c38154c6861e7547f1e3fc7a87e75ff09bab24ef1cc81d81c2835e9", size = 18452814, upload-time = "2026-06-21T20:57:42.875Z" }, - { url = "https://files.pythonhosted.org/packages/e8/46/52fc0d2a68d7643f0f149eeea5a5d8ea2a3507056ac8afa83c9212606e8b/numpy-2.5.0-cp314-cp314t-win32.whl", hash = "sha256:edadfbd4794b1086c0d822f81863e8a68fc129d132fd0bb9e31e955d7fbbbdb7", size = 6253168, upload-time = "2026-06-21T20:57:45.101Z" }, - { url = "https://files.pythonhosted.org/packages/2a/be/6c8d1118b5f13b2881dc095d5b345de19c6638b8959c17409b6eff84c8aa/numpy-2.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f7e5fa4382967ae6548bd2f174219afb908e294b0d5f625af01166edd5f7d9aa", size = 12736286, upload-time = "2026-06-21T20:57:46.935Z" }, - { url = "https://files.pythonhosted.org/packages/fd/6a/d3a169aaf8536cf228d56a09e04bcb713a2fe4410d4e2105b9419b5a9c89/numpy-2.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:016623417bb330d719d579daf2d6b9a01ddc52e41a9ed61a47f39fde46dcd865", size = 10686451, upload-time = "2026-06-21T20:57:49.313Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/22/fd/89965aa4ac08c74998539fcbf24fa3540f3e15237fbeb6bcf9c908f4aade/numpy-2.5.1.tar.gz", hash = "sha256:a48a113e6afea91f5608793bafa7ef2ad481fefbda87ec5069f483de61cb9fa3", size = 20755553, upload-time = "2026-07-04T17:08:00.933Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/7b/14687aa674250e5e546f616f486b0d56d3631cd5b2415739141ce40bdcea/numpy-2.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c889b56fe48b1018f764b0eec8df59ab654e9148aa91faa12596043500de277", size = 16801574, upload-time = "2026-07-04T17:06:12.423Z" }, + { url = "https://files.pythonhosted.org/packages/e1/19/cc5bb2a3f2913d27d6dbb2c78d25921fabaedc6741d4a5a615a11f3c5bf3/numpy-2.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab451b59c5643c570974c43aef780703ef1d3b4965d2be07afd530615a9358d1", size = 11772250, upload-time = "2026-07-04T17:06:15.726Z" }, + { url = "https://files.pythonhosted.org/packages/42/77/fdf34a71dd30f54979b18603bee915e0aaf825b07afe79acd60b04b691e2/numpy-2.5.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:78798bd5b9ad744056af8efa90e3b9ddaa53272a0848a483084a1cc0a13b2dc0", size = 5331516, upload-time = "2026-07-04T17:06:17.913Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e2/eb7efa015b4cce41e2517bf182a7fce0d7d5b9d9ed76a29bfa0f4fe4505c/numpy-2.5.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2ae0ca40bcb22d6ba59c1dfd5446f49940b0f2d821fde133f10dda11f816b84e", size = 6664863, upload-time = "2026-07-04T17:06:20.02Z" }, + { url = "https://files.pythonhosted.org/packages/a9/4b/a2b32dd94ee9ffbeecb28152240042a3949db33b1c834d44090b80e1b3b8/numpy-2.5.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61ac47e772e6b8ea489e1d2f441a34c5c3ac17327e7ce294cbdf535795ad4e75", size = 15167977, upload-time = "2026-07-04T17:06:21.621Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a9/6e73d68500f80773f65f0654ea932019d6694329a0eb0ed0533de38df376/numpy-2.5.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:59fda5e192b570217ec2580c96f00e9a7e12ef6866a900eb089b62c1a32545ca", size = 16672469, upload-time = "2026-07-04T17:06:24.064Z" }, + { url = "https://files.pythonhosted.org/packages/24/7d/ad3e59015135f5261c95fd4cafeff159c955febd83a99a1d9250c4233815/numpy-2.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f7119ebff1a9829e9f431a4f9d28e703023bb6b9fe7c8f724467dbfc27c94ab3", size = 16527531, upload-time = "2026-07-04T17:06:26.69Z" }, + { url = "https://files.pythonhosted.org/packages/83/d0/a39b2fbcde9cb17a1dac678f254b33a6336298af9df338824c685425d5e8/numpy-2.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e824c2acf8862052246be5a44c15da1777940c60d010dd2aab897824d9c430f9", size = 18431940, upload-time = "2026-07-04T17:06:29.521Z" }, + { url = "https://files.pythonhosted.org/packages/04/12/cff070947791c1ed425ff76413189adbdc2fbe215eba7ce7fa454a03c7f8/numpy-2.5.1-cp312-cp312-win32.whl", hash = "sha256:08d60c810432eb83360958dea0999ac4cfb94531ea8efcbf0b7f277c2068aeb2", size = 6066764, upload-time = "2026-07-04T17:06:32.571Z" }, + { url = "https://files.pythonhosted.org/packages/65/66/53f31807a48a750f9d748da273bc3fcedd12b27ff1f3e373bfec55ef2dc0/numpy-2.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7d60026c0bdb1380e83bfa7a0419c4577ee4b9a08880afcb6dadeb74c649fa2", size = 12430966, upload-time = "2026-07-04T17:06:34.926Z" }, + { url = "https://files.pythonhosted.org/packages/2b/2a/d1a88066b1c14186f5d3c0d18c94f17b064511982bab0578d49ee9d43c29/numpy-2.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:17a25e09640602e10bc8de0e6fa2b3fd68eedd84ba6d7842dc8f32f9ab87bd0b", size = 10350488, upload-time = "2026-07-04T17:06:37.785Z" }, + { url = "https://files.pythonhosted.org/packages/eb/07/ec2a3f0c91761581d4b7104a740791800025983f9a4dc4e73f91a99aeac4/numpy-2.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0bfebd8695f9863592fe744be833a258120b14a9f39da255e8aa8fade2c0ddd1", size = 16796419, upload-time = "2026-07-04T17:06:40.37Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ab/ddb499fc4f8780354395face5b65c7fd107bcd6e1d667a5f07d046956f6f/numpy-2.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:30b44a6b53a7ae63c54c089a8726e5563ed302716c5b7ccc85afade40b0e7ff6", size = 11765832, upload-time = "2026-07-04T17:06:42.768Z" }, + { url = "https://files.pythonhosted.org/packages/88/b3/3c28c558a09fc72100c646dac6d2fce8e834c471b0edca01a29996706117/numpy-2.5.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6165343f81b56ef8f514f396989e529b61d9dc709b99421b07e9f3e698e2287d", size = 5325143, upload-time = "2026-07-04T17:06:45.466Z" }, + { url = "https://files.pythonhosted.org/packages/5e/0e/ce19b985bb15c596f4f05954e76cccc77c845083b3b8f938a6c68e523128/numpy-2.5.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4939237038ada79308dda3204ac6462df056b5672b2e25db1149cf873668b3e1", size = 6659749, upload-time = "2026-07-04T17:06:47.288Z" }, + { url = "https://files.pythonhosted.org/packages/2e/20/1ee6614d64332a1bba6411f38e68cb79eec1b2459e20a623777c5c5492a2/numpy-2.5.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c6759f538fb912fc46de0a6b1758ccf7b57bc7c7ebebc23974fdac3de8db0cd", size = 15164716, upload-time = "2026-07-04T17:06:49.494Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a7/2bcd3fdbb87804755c35b729bf8709d62025c5f4cfd7d5b2415997097515/numpy-2.5.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9726558e8db4a5bf7929a70ae50f63abda4daf0efe810e3bfbab95976f75fc1a", size = 16661440, upload-time = "2026-07-04T17:06:52.061Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d7/a41e3310c886fe457d36e670bbf24fae411aca8a7b6ad92a32afd924077c/numpy-2.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3935f3b419b244a02732676fa5317a9193cc596a4c0646db07e5b421229ac9f7", size = 16526305, upload-time = "2026-07-04T17:06:54.605Z" }, + { url = "https://files.pythonhosted.org/packages/53/75/4333a9a707c1edd3a4e1a0c58eca52c0f31e55089fa80db02b5565b24df7/numpy-2.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc932a65ded7ce9013d120845a2514dcccb1a67bfc8deb8d37633762951904a6", size = 18423008, upload-time = "2026-07-04T17:06:57.54Z" }, + { url = "https://files.pythonhosted.org/packages/ee/90/e314a32b1c11a2ffe818ddad3a57b50b4b6e1b6c487192eb50cdef0415d0/numpy-2.5.1-cp313-cp313-win32.whl", hash = "sha256:4b4ff1608417eb7a59da7b967bbb798cacfe071d2caf526a24281cd562072ed9", size = 6063885, upload-time = "2026-07-04T17:07:00.14Z" }, + { url = "https://files.pythonhosted.org/packages/10/70/800b3fca480af32df9e8ea9f3d4a0c8feb4b32d7f195d174eabbda4829ad/numpy-2.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:6c3fe51bc6a16453d452997053454f309e8e0ed7b42d6b361ce4ac8c32913d74", size = 12425674, upload-time = "2026-07-04T17:07:02.387Z" }, + { url = "https://files.pythonhosted.org/packages/8b/0b/196350c122f50f6ca56846f2d71efd5e0d24b7b2e07355e019b2e2c7a11e/numpy-2.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:f7feb014281029e628ba2d5a007407443b06e418b6fe451d1e2adcbc8eba0107", size = 10350256, upload-time = "2026-07-04T17:07:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/db/f4/731b6085a83faf6ca843394cbd5e217280c214399f7e8b21b9f552af0ae2/numpy-2.5.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7c786fe9a5bbe360022e584c5a34cf6b54265c71bd7ec8ac3d8fec38968071f8", size = 16795063, upload-time = "2026-07-04T17:07:07.374Z" }, + { url = "https://files.pythonhosted.org/packages/bf/64/0e215f2048dd11a55bb989ed41b3585ef57452404e638d703a211a3e4157/numpy-2.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32985c896d897419ef8da6917872d80b78ad0ea26d85b23245c7366ffde76d75", size = 11776652, upload-time = "2026-07-04T17:07:09.907Z" }, + { url = "https://files.pythonhosted.org/packages/b5/59/2b844c7a6e9deff69b404a66221e1542937734f65d5e6e39411876053862/numpy-2.5.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:efd736408cc97c79b9e6917338dfc8f06013b2274f992e96b1d9a81a71e2a2c2", size = 5335944, upload-time = "2026-07-04T17:07:12.227Z" }, + { url = "https://files.pythonhosted.org/packages/86/51/9bf7cb2cabcebc9e017e4ec7e6322b378317a542c08b4cb68479c1efc716/numpy-2.5.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:ab84dc6b074fa881cae55bea94cc4f68e285181ba7f32497bf7dee6b1496165b", size = 6656266, upload-time = "2026-07-04T17:07:14.368Z" }, + { url = "https://files.pythonhosted.org/packages/83/3e/fb7615b211b82a32f44d5180a6d421b61f84d4fadd578b48ba4ac34e189f/numpy-2.5.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caf3e317d33d60c37986b452613f4ab51246d0691350c03d0cb4a898627f4a95", size = 15179720, upload-time = "2026-07-04T17:07:16.272Z" }, + { url = "https://files.pythonhosted.org/packages/41/5f/0f992cb24560673496c5d68de61913b57166ce530ffda07c1f280e0cc464/numpy-2.5.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:54ad769f17bc2d833b620851989f62054fb9ab93c969d9e1dc3c8e3d56beea21", size = 16664835, upload-time = "2026-07-04T17:07:19.021Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2f/97d6475ee91afe2587797d09446f9d3e475ad4cb681662d824809327b75a/numpy-2.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c12afb53450fa976d4c681c50a7423729a4c51c0465ed9f32b8a9cabbc472373", size = 16539135, upload-time = "2026-07-04T17:07:22.015Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/4db81e4ba0be7e2776b1de68c82aa862c7f8ec27e1b4927d4ae075e20678/numpy-2.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e8c11c405efc5ff6816d5983c96cdfa215bab3428961243af3ff59b228490438", size = 18426684, upload-time = "2026-07-04T17:07:24.941Z" }, + { url = "https://files.pythonhosted.org/packages/1f/64/c0ba2d90724d450279a7df8f32057241070250a26a7e2b5337d77347f481/numpy-2.5.1-cp314-cp314-win32.whl", hash = "sha256:f2479a47f8d5932d1718168a681ad6e536a9df484c83cfcf9de365e164537ace", size = 6116103, upload-time = "2026-07-04T17:07:27.622Z" }, + { url = "https://files.pythonhosted.org/packages/c1/1a/837f9ed7405adcd7a40538792eb169eddd8fa5630c16a1ef49dae71a30f4/numpy-2.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:24d0eb82c0541d3415a33425db64ae439dffccd7b4dbcb30e7c35120205c506a", size = 12562177, upload-time = "2026-07-04T17:07:29.887Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/49707938b6dd0a78a9178dd93227dc89e4c11af47f5c798d70366e8d0483/numpy-2.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:5a4c988b38d261deeeaad9954e3deb091ad905c94e8bb6708654ef1d97f286b0", size = 10627739, upload-time = "2026-07-04T17:07:32.568Z" }, + { url = "https://files.pythonhosted.org/packages/a6/c7/bb4b882cfe7f299cbc8b66e42e7dd78cf9d14e40f9469fc5e3db7e15b3bd/numpy-2.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a33276be12fa045805f477f22482088b66bb758ffbe89a9d21457de863a32e22", size = 11894709, upload-time = "2026-07-04T17:07:34.941Z" }, + { url = "https://files.pythonhosted.org/packages/40/3f/5af7f4a7f6224aef48017aa82bb6174c7a659d724be0c75017b7e64a55b4/numpy-2.5.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:f089d7b00756190aacf1f5d34bdf38c3c430ac82b4f868f8cede73380460fce7", size = 5453810, upload-time = "2026-07-04T17:07:37.495Z" }, + { url = "https://files.pythonhosted.org/packages/20/c9/3474309bc94d634d3f9c3eddf03250ecb8c22cd948ef16fef69a77cc5d7b/numpy-2.5.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:09e9bfd8d2cf479c7d174804fb3811c53a8e9f20a37444008606b57d6b7a826d", size = 6761189, upload-time = "2026-07-04T17:07:39.563Z" }, + { url = "https://files.pythonhosted.org/packages/90/8a/558ae39fdd55d7e7f7fef9a84a6e964ac6b23edbd2a07e52bb084500507d/numpy-2.5.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e68d8dd1e7eba712948f2053a29ec86917bc70ba1358df869d9f06649ef9cf09", size = 15225039, upload-time = "2026-07-04T17:07:41.682Z" }, + { url = "https://files.pythonhosted.org/packages/63/27/ca7392b2d030277bdf0273e7d23255b3ee57d57a7c170a6f4fb3981e1e5d/numpy-2.5.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99d5095fa265a0c4152e7bb12759e14381ef5496152f1ce58f44bdf55c44beb4", size = 16701306, upload-time = "2026-07-04T17:07:44.611Z" }, + { url = "https://files.pythonhosted.org/packages/02/42/03d53ae7996c44d4374a8262e9dc41671fd56cbb98f7d47ef85cf5da4c6b/numpy-2.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ab87a91b3cc3382b8956095bd8f95e00cf679bb81554339be1a2ba404a1473c1", size = 16589955, upload-time = "2026-07-04T17:07:47.694Z" }, + { url = "https://files.pythonhosted.org/packages/7b/15/6c1784ae469640e65db111e9a34b3d0f14d91e8a38b9ce34810ced370dbb/numpy-2.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:224ca51130ef7da85bea2191625181cb4f337f9cb64b471f10c1a12aa8b60077", size = 18464252, upload-time = "2026-07-04T17:07:50.684Z" }, + { url = "https://files.pythonhosted.org/packages/94/a8/f98e50356cf167df656c526c2dfeec2d7dde182f2a3da4b458a5938e2776/numpy-2.5.1-cp314-cp314t-win32.whl", hash = "sha256:6eab239876581b2b3c5a242281b6007bbdbcd1c7085d7709bb57c5929b11e6bf", size = 6263298, upload-time = "2026-07-04T17:07:53.445Z" }, + { url = "https://files.pythonhosted.org/packages/72/ac/96ae880cdecad0b3275d9359fcec72667b49a4863c9f12942e43679dda02/numpy-2.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:83ce9c80d5b521b0d77ddcbe5447c218d247929b6cc056ca5351342accfff0af", size = 12748623, upload-time = "2026-07-04T17:07:55.384Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5a/4d2b1601df3602dba7a14f3348ba9bfe94a18adb428e693df6154c293831/numpy-2.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:5a6db61f9aaa57e369905c67d852045d3c4f7126405b29d09b19dec118e9c9cb", size = 10697674, upload-time = "2026-07-04T17:07:58.506Z" }, ] [[package]] @@ -2327,7 +2327,7 @@ dependencies = [ { name = "alembic" }, { name = "colorlog" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "packaging" }, { name = "pyyaml" }, { name = "sqlalchemy" }, @@ -2401,89 +2401,87 @@ wheels = [ [[package]] name = "pillow" -version = "12.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/e1/748f5663efe6edcfc4e74b2b93edfb9b8b99b67f21a854c3ae416500a2d9/pillow-12.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:8be29e59487a79f173507c30ddf57e733a357f67881430449bb32614075a40ab", size = 5354347, upload-time = "2026-04-01T14:42:44.255Z" }, - { url = "https://files.pythonhosted.org/packages/47/a1/d5ff69e747374c33a3b53b9f98cca7889fce1fd03d79cdc4e1bccc6c5a87/pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65", size = 4695873, upload-time = "2026-04-01T14:42:46.452Z" }, - { url = "https://files.pythonhosted.org/packages/df/21/e3fbdf54408a973c7f7f89a23b2cb97a7ef30c61ab4142af31eee6aebc88/pillow-12.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f490f9368b6fc026f021db16d7ec2fbf7d89e2edb42e8ec09d2c60505f5729c7", size = 6280168, upload-time = "2026-04-01T14:42:49.228Z" }, - { url = "https://files.pythonhosted.org/packages/d3/f1/00b7278c7dd52b17ad4329153748f87b6756ec195ff786c2bdf12518337d/pillow-12.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8bd7903a5f2a4545f6fd5935c90058b89d30045568985a71c79f5fd6edf9b91e", size = 8088188, upload-time = "2026-04-01T14:42:51.735Z" }, - { url = "https://files.pythonhosted.org/packages/ad/cf/220a5994ef1b10e70e85748b75649d77d506499352be135a4989c957b701/pillow-12.2.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3997232e10d2920a68d25191392e3a4487d8183039e1c74c2297f00ed1c50705", size = 6394401, upload-time = "2026-04-01T14:42:54.343Z" }, - { url = "https://files.pythonhosted.org/packages/e9/bd/e51a61b1054f09437acfbc2ff9106c30d1eb76bc1453d428399946781253/pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e74473c875d78b8e9d5da2a70f7099549f9eb37ded4e2f6a463e60125bccd176", size = 7079655, upload-time = "2026-04-01T14:42:56.954Z" }, - { url = "https://files.pythonhosted.org/packages/6b/3d/45132c57d5fb4b5744567c3817026480ac7fc3ce5d4c47902bc0e7f6f853/pillow-12.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:56a3f9c60a13133a98ecff6197af34d7824de9b7b38c3654861a725c970c197b", size = 6503105, upload-time = "2026-04-01T14:42:59.847Z" }, - { url = "https://files.pythonhosted.org/packages/7d/2e/9df2fc1e82097b1df3dce58dc43286aa01068e918c07574711fcc53e6fb4/pillow-12.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90e6f81de50ad6b534cab6e5aef77ff6e37722b2f5d908686f4a5c9eba17a909", size = 7203402, upload-time = "2026-04-01T14:43:02.664Z" }, - { url = "https://files.pythonhosted.org/packages/bd/2e/2941e42858ebb67e50ae741473de81c2984e6eff7b397017623c676e2e8d/pillow-12.2.0-cp311-cp311-win32.whl", hash = "sha256:8c984051042858021a54926eb597d6ee3012393ce9c181814115df4c60b9a808", size = 6378149, upload-time = "2026-04-01T14:43:05.274Z" }, - { url = "https://files.pythonhosted.org/packages/69/42/836b6f3cd7f3e5fa10a1f1a5420447c17966044c8fbf589cc0452d5502db/pillow-12.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60", size = 7082626, upload-time = "2026-04-01T14:43:08.557Z" }, - { url = "https://files.pythonhosted.org/packages/c2/88/549194b5d6f1f494b485e493edc6693c0a16f4ada488e5bd974ed1f42fad/pillow-12.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:9a8a34cc89c67a65ea7437ce257cea81a9dad65b29805f3ecee8c8fe8ff25ffe", size = 2463531, upload-time = "2026-04-01T14:43:10.743Z" }, - { url = "https://files.pythonhosted.org/packages/58/be/7482c8a5ebebbc6470b3eb791812fff7d5e0216c2be3827b30b8bb6603ed/pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5", size = 5308279, upload-time = "2026-04-01T14:43:13.246Z" }, - { url = "https://files.pythonhosted.org/packages/d8/95/0a351b9289c2b5cbde0bacd4a83ebc44023e835490a727b2a3bd60ddc0f4/pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421", size = 4695490, upload-time = "2026-04-01T14:43:15.584Z" }, - { url = "https://files.pythonhosted.org/packages/de/af/4e8e6869cbed569d43c416fad3dc4ecb944cb5d9492defaed89ddd6fe871/pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987", size = 6284462, upload-time = "2026-04-01T14:43:18.268Z" }, - { url = "https://files.pythonhosted.org/packages/e9/9e/c05e19657fd57841e476be1ab46c4d501bffbadbafdc31a6d665f8b737b6/pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76", size = 8094744, upload-time = "2026-04-01T14:43:20.716Z" }, - { url = "https://files.pythonhosted.org/packages/2b/54/1789c455ed10176066b6e7e6da1b01e50e36f94ba584dc68d9eebfe9156d/pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005", size = 6398371, upload-time = "2026-04-01T14:43:23.443Z" }, - { url = "https://files.pythonhosted.org/packages/43/e3/fdc657359e919462369869f1c9f0e973f353f9a9ee295a39b1fea8ee1a77/pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780", size = 7087215, upload-time = "2026-04-01T14:43:26.758Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f8/2f6825e441d5b1959d2ca5adec984210f1ec086435b0ed5f52c19b3b8a6e/pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5", size = 6509783, upload-time = "2026-04-01T14:43:29.56Z" }, - { url = "https://files.pythonhosted.org/packages/67/f9/029a27095ad20f854f9dba026b3ea6428548316e057e6fc3545409e86651/pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5", size = 7212112, upload-time = "2026-04-01T14:43:32.091Z" }, - { url = "https://files.pythonhosted.org/packages/be/42/025cfe05d1be22dbfdb4f264fe9de1ccda83f66e4fc3aac94748e784af04/pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940", size = 6378489, upload-time = "2026-04-01T14:43:34.601Z" }, - { url = "https://files.pythonhosted.org/packages/5d/7b/25a221d2c761c6a8ae21bfa3874988ff2583e19cf8a27bf2fee358df7942/pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5", size = 7084129, upload-time = "2026-04-01T14:43:37.213Z" }, - { url = "https://files.pythonhosted.org/packages/10/e1/542a474affab20fd4a0f1836cb234e8493519da6b76899e30bcc5d990b8b/pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414", size = 2463612, upload-time = "2026-04-01T14:43:39.421Z" }, - { url = "https://files.pythonhosted.org/packages/4a/01/53d10cf0dbad820a8db274d259a37ba50b88b24768ddccec07355382d5ad/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c", size = 4100837, upload-time = "2026-04-01T14:43:41.506Z" }, - { url = "https://files.pythonhosted.org/packages/0f/98/f3a6657ecb698c937f6c76ee564882945f29b79bad496abcba0e84659ec5/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2", size = 4176528, upload-time = "2026-04-01T14:43:43.773Z" }, - { url = "https://files.pythonhosted.org/packages/69/bc/8986948f05e3ea490b8442ea1c1d4d990b24a7e43d8a51b2c7d8b1dced36/pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c", size = 3640401, upload-time = "2026-04-01T14:43:45.87Z" }, - { url = "https://files.pythonhosted.org/packages/34/46/6c717baadcd62bc8ed51d238d521ab651eaa74838291bda1f86fe1f864c9/pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795", size = 5308094, upload-time = "2026-04-01T14:43:48.438Z" }, - { url = "https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f", size = 4695402, upload-time = "2026-04-01T14:43:51.292Z" }, - { url = "https://files.pythonhosted.org/packages/73/dd/42107efcb777b16fa0393317eac58f5b5cf30e8392e266e76e51cff28c3d/pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed", size = 6280005, upload-time = "2026-04-01T14:43:54.242Z" }, - { url = "https://files.pythonhosted.org/packages/a8/68/b93e09e5e8549019e61acf49f65b1a8530765a7f812c77a7461bca7e4494/pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9", size = 8090669, upload-time = "2026-04-01T14:43:57.335Z" }, - { url = "https://files.pythonhosted.org/packages/4b/6e/3ccb54ce8ec4ddd1accd2d89004308b7b0b21c4ac3d20fa70af4760a4330/pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed", size = 6395194, upload-time = "2026-04-01T14:43:59.864Z" }, - { url = "https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3", size = 7082423, upload-time = "2026-04-01T14:44:02.74Z" }, - { url = "https://files.pythonhosted.org/packages/78/5f/e9f86ab0146464e8c133fe85df987ed9e77e08b29d8d35f9f9f4d6f917ba/pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9", size = 6505667, upload-time = "2026-04-01T14:44:05.381Z" }, - { url = "https://files.pythonhosted.org/packages/ed/1e/409007f56a2fdce61584fd3acbc2bbc259857d555196cedcadc68c015c82/pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795", size = 7208580, upload-time = "2026-04-01T14:44:08.39Z" }, - { url = "https://files.pythonhosted.org/packages/23/c4/7349421080b12fb35414607b8871e9534546c128a11965fd4a7002ccfbee/pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e", size = 6375896, upload-time = "2026-04-01T14:44:11.197Z" }, - { url = "https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b", size = 7081266, upload-time = "2026-04-01T14:44:13.947Z" }, - { url = "https://files.pythonhosted.org/packages/c3/25/f968f618a062574294592f668218f8af564830ccebdd1fa6200f598e65c5/pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06", size = 2463508, upload-time = "2026-04-01T14:44:16.312Z" }, - { url = "https://files.pythonhosted.org/packages/4d/a4/b342930964e3cb4dce5038ae34b0eab4653334995336cd486c5a8c25a00c/pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b", size = 5309927, upload-time = "2026-04-01T14:44:18.89Z" }, - { url = "https://files.pythonhosted.org/packages/9f/de/23198e0a65a9cf06123f5435a5d95cea62a635697f8f03d134d3f3a96151/pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f", size = 4698624, upload-time = "2026-04-01T14:44:21.115Z" }, - { url = "https://files.pythonhosted.org/packages/01/a6/1265e977f17d93ea37aa28aa81bad4fa597933879fac2520d24e021c8da3/pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612", size = 6321252, upload-time = "2026-04-01T14:44:23.663Z" }, - { url = "https://files.pythonhosted.org/packages/3c/83/5982eb4a285967baa70340320be9f88e57665a387e3a53a7f0db8231a0cd/pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c", size = 8126550, upload-time = "2026-04-01T14:44:26.772Z" }, - { url = "https://files.pythonhosted.org/packages/4e/48/6ffc514adce69f6050d0753b1a18fd920fce8cac87620d5a31231b04bfc5/pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea", size = 6433114, upload-time = "2026-04-01T14:44:29.615Z" }, - { url = "https://files.pythonhosted.org/packages/36/a3/f9a77144231fb8d40ee27107b4463e205fa4677e2ca2548e14da5cf18dce/pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4", size = 7115667, upload-time = "2026-04-01T14:44:32.773Z" }, - { url = "https://files.pythonhosted.org/packages/c1/fc/ac4ee3041e7d5a565e1c4fd72a113f03b6394cc72ab7089d27608f8aaccb/pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4", size = 6538966, upload-time = "2026-04-01T14:44:35.252Z" }, - { url = "https://files.pythonhosted.org/packages/c0/a8/27fb307055087f3668f6d0a8ccb636e7431d56ed0750e07a60547b1e083e/pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea", size = 7238241, upload-time = "2026-04-01T14:44:37.875Z" }, - { url = "https://files.pythonhosted.org/packages/ad/4b/926ab182c07fccae9fcb120043464e1ff1564775ec8864f21a0ebce6ac25/pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24", size = 6379592, upload-time = "2026-04-01T14:44:40.336Z" }, - { url = "https://files.pythonhosted.org/packages/c2/c4/f9e476451a098181b30050cc4c9a3556b64c02cf6497ea421ac047e89e4b/pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98", size = 7085542, upload-time = "2026-04-01T14:44:43.251Z" }, - { url = "https://files.pythonhosted.org/packages/00/a4/285f12aeacbe2d6dc36c407dfbbe9e96d4a80b0fb710a337f6d2ad978c75/pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453", size = 2465765, upload-time = "2026-04-01T14:44:45.996Z" }, - { url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" }, - { url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" }, - { url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" }, - { url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" }, - { url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" }, - { url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" }, - { url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" }, - { url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" }, - { url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" }, - { url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" }, - { url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" }, - { url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" }, - { url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" }, - { url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" }, - { url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" }, - { url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" }, - { url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" }, - { url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" }, - { url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" }, - { url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" }, - { url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" }, - { url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" }, - { url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" }, - { url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" }, - { url = "https://files.pythonhosted.org/packages/4e/b7/2437044fb910f499610356d1352e3423753c98e34f915252aafecc64889f/pillow-12.2.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538bd5e05efec03ae613fd89c4ce0368ecd2ba239cc25b9f9be7ed426b0af1f", size = 5273969, upload-time = "2026-04-01T14:45:55.538Z" }, - { url = "https://files.pythonhosted.org/packages/f6/f4/8316e31de11b780f4ac08ef3654a75555e624a98db1056ecb2122d008d5a/pillow-12.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:394167b21da716608eac917c60aa9b969421b5dcbbe02ae7f013e7b85811c69d", size = 4659674, upload-time = "2026-04-01T14:45:58.093Z" }, - { url = "https://files.pythonhosted.org/packages/d4/37/664fca7201f8bb2aa1d20e2c3d5564a62e6ae5111741966c8319ca802361/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5d04bfa02cc2d23b497d1e90a0f927070043f6cbf303e738300532379a4b4e0f", size = 5288479, upload-time = "2026-04-01T14:46:01.141Z" }, - { url = "https://files.pythonhosted.org/packages/49/62/5b0ed78fce87346be7a5cfcfaaad91f6a1f98c26f86bdbafa2066c647ef6/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0c838a5125cee37e68edec915651521191cef1e6aa336b855f495766e77a366e", size = 7032230, upload-time = "2026-04-01T14:46:03.874Z" }, - { url = "https://files.pythonhosted.org/packages/c3/28/ec0fc38107fc32536908034e990c47914c57cd7c5a3ece4d8d8f7ffd7e27/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a6c9fa44005fa37a91ebfc95d081e8079757d2e904b27103f4f5fa6f0bf78c0", size = 5355404, upload-time = "2026-04-01T14:46:06.33Z" }, - { url = "https://files.pythonhosted.org/packages/5e/8b/51b0eddcfa2180d60e41f06bd6d0a62202b20b59c68f5a132e615b75aecf/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25373b66e0dd5905ed63fa3cae13c82fbddf3079f2c8bf15c6fb6a35586324c1", size = 6002215, upload-time = "2026-04-01T14:46:08.83Z" }, - { url = "https://files.pythonhosted.org/packages/bc/60/5382c03e1970de634027cee8e1b7d39776b778b81812aaf45b694dfe9e28/pillow-12.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e", size = 7080946, upload-time = "2026-04-01T14:46:11.734Z" }, +version = "12.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/c8/0a78b0e02d7ac54bc03e5321c9220da52f0c2ea83b21f7c40e7f3169c502/pillow-12.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:00808c5e14ef63ac5161091d242999076604ff74b883423a11e5d7bbb38bf756", size = 5392415, upload-time = "2026-07-01T11:53:47.162Z" }, + { url = "https://files.pythonhosted.org/packages/b2/5b/a02d30018abd97ced9f5a6c63d28597694a00d066516b9c1c6de45859fc9/pillow-12.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6", size = 4785266, upload-time = "2026-07-01T11:53:49.079Z" }, + { url = "https://files.pythonhosted.org/packages/c8/98/766667a4be768150a202836acd9fad19c06824ca86c4286d3cf6b274964e/pillow-12.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bcb46e2f9feff8d06323983bd83ed00c201fdcab3d74973e7072a889b3979fcd", size = 6263814, upload-time = "2026-07-01T11:53:51.32Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2d/ede717bc1144f63886c21fd349bb95860b0d1a21149ff16f2bb362b612b6/pillow-12.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23d27a3e0307ec2244cc51e7287b919aa68d097504ebe19df4e76a98a3eea5bd", size = 6934408, upload-time = "2026-07-01T11:53:53.487Z" }, + { url = "https://files.pythonhosted.org/packages/a3/48/9c58b685e69d49c31af6c8eb9012055fab7e665785165c84796e2c73ce72/pillow-12.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4f883547d4b7f0495ebe7056b0cc2aea76094e7a4abc8e933540f3271df27d9c", size = 6337160, upload-time = "2026-07-01T11:53:55.457Z" }, + { url = "https://files.pythonhosted.org/packages/ff/fa/dc2a5c0ba6df93f67c31d34b808b7ce440b40cdbf96f0b81cde1d1e6fa93/pillow-12.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:236ff70b9312fb68943c703aa842ca6a758abfa45ac187a5e7c1452e96ef72b5", size = 7045172, upload-time = "2026-07-01T11:53:57.736Z" }, + { url = "https://files.pythonhosted.org/packages/86/a5/444817a4d4c4c2417df00513086ca196f388d8f9ef40c2e4ccd1ad1af54b/pillow-12.3.0-cp311-cp311-win32.whl", hash = "sha256:10e41f0fbf1eec8cfd234b8fe17a4caac7c9d0db4c204d3c173a8f9f6ef3232b", size = 6472232, upload-time = "2026-07-01T11:53:59.767Z" }, + { url = "https://files.pythonhosted.org/packages/63/c6/4bad1b18d132a50b27e1365e1ab163616f7a5bb56d330f66f9d1d9d4f9d4/pillow-12.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8e95e1385e4998ae9694eeaa4730ba5457ff61185b3a55e2e7bea0880aef452a", size = 7233653, upload-time = "2026-07-01T11:54:02.066Z" }, + { url = "https://files.pythonhosted.org/packages/fd/16/00f91ab7760dc842f5aad55217e80fc4a7067a0604535249bc8a2d6d9870/pillow-12.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:ebaea975e03d3141d9d3a507df75c9b3ec90fa9d2ffd07567b3a978d9d790b26", size = 2568195, upload-time = "2026-07-01T11:54:04.622Z" }, + { url = "https://files.pythonhosted.org/packages/37/bf/fb3ebff8ddcb76aac5a01389251bbbb9519922a9b520d8247c1ca864a25d/pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965", size = 5345969, upload-time = "2026-07-01T11:54:06.397Z" }, + { url = "https://files.pythonhosted.org/packages/d8/66/9a386a92561f402389a4fc70c18838bf6d35eb5eb5c6850b4b2dc64f5048/pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7", size = 4780323, upload-time = "2026-07-01T11:54:09.351Z" }, + { url = "https://files.pythonhosted.org/packages/25/27/ac8f99618ffd3dde21db0f4d4b1d2ab00c0880595bfd17df103f7f39fd0c/pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9", size = 6266838, upload-time = "2026-07-01T11:54:11.71Z" }, + { url = "https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91", size = 6940830, upload-time = "2026-07-01T11:54:13.732Z" }, + { url = "https://files.pythonhosted.org/packages/eb/51/8b08617af3ad95e33ce6d7dd2c99ed6c8298f7fb131636303956be022e25/pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c", size = 6344383, upload-time = "2026-07-01T11:54:15.756Z" }, + { url = "https://files.pythonhosted.org/packages/1d/72/cf78ac9780bb93c28328f408973845a309d4d145041665f734572ced1b52/pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df", size = 7052934, upload-time = "2026-07-01T11:54:17.721Z" }, + { url = "https://files.pythonhosted.org/packages/20/20/25e0f4dc178a6bc0696793720055519a0de89e7661dae886992decbd2f81/pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f", size = 6472684, upload-time = "2026-07-01T11:54:19.839Z" }, + { url = "https://files.pythonhosted.org/packages/45/89/da2f7971a317f83d807fdd4065c0af40208e59e692cc43d315a71a0e96d1/pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09", size = 7227137, upload-time = "2026-07-01T11:54:22.025Z" }, + { url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267, upload-time = "2026-07-01T11:54:24.051Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ac/31fb64e1e7efb5a4b50cd3d92049ba89ac6e4d8d3bb6a74e15048ca3353e/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89", size = 4161684, upload-time = "2026-07-01T11:54:25.934Z" }, + { url = "https://files.pythonhosted.org/packages/87/b4/9805e23d2b4d77842b468513841fda254ee42f0289d25088340e4ff46e2d/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace", size = 4255487, upload-time = "2026-07-01T11:54:27.935Z" }, + { url = "https://files.pythonhosted.org/packages/df/39/ecf519435a200c693fe053a6ee4d835b41cf963a4dfc2551c4e637cb2a71/pillow-12.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec", size = 3696433, upload-time = "2026-07-01T11:54:29.813Z" }, + { url = "https://files.pythonhosted.org/packages/42/92/2fc3ffad878ae8dd5469ec1bc8eb83b71f48e13efdf68f02709003982a32/pillow-12.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66", size = 5345889, upload-time = "2026-07-01T11:54:31.97Z" }, + { url = "https://files.pythonhosted.org/packages/10/76/8803c13605b763d33d156c4678fc77f8443389c0c51c8aef707bb02015f4/pillow-12.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35", size = 4780109, upload-time = "2026-07-01T11:54:34.026Z" }, + { url = "https://files.pythonhosted.org/packages/1f/01/e18aff37cb0b4aac47ac90f016d347a49aca667ef97f190b06ac2aabc928/pillow-12.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65", size = 6263736, upload-time = "2026-07-01T11:54:36.131Z" }, + { url = "https://files.pythonhosted.org/packages/f7/62/de5bdd77d935331f4f802edc11e4d82950f642caad6cb2f949837b8560e2/pillow-12.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3", size = 6937129, upload-time = "2026-07-01T11:54:38.216Z" }, + { url = "https://files.pythonhosted.org/packages/70/4d/105627a13300c5e0df1d174230b32fd1273062c96f7745fd552b945d1e1d/pillow-12.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a", size = 6339562, upload-time = "2026-07-01T11:54:40.354Z" }, + { url = "https://files.pythonhosted.org/packages/6b/1d/f13de01a553988ab895ba1c722e06cf3144d4f57656fd5b81b6d881f1179/pillow-12.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e", size = 7049439, upload-time = "2026-07-01T11:54:42.489Z" }, + { url = "https://files.pythonhosted.org/packages/c9/f9/066794cca041b969964f779ee5fa66a9498bbf34248ac39c5d7954e4198f/pillow-12.3.0-cp313-cp313-win32.whl", hash = "sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f", size = 6473287, upload-time = "2026-07-01T11:54:44.9Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/7a58e61d62be561da3a356fe2384d4059a6345fc130e23ef1c36a5b81d24/pillow-12.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8", size = 7239691, upload-time = "2026-07-01T11:54:47.141Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b0/c4ed4f0ef8f8fa5ee8351537db6650bb8189f7e118842978dd6589065692/pillow-12.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b", size = 2568185, upload-time = "2026-07-01T11:54:49.137Z" }, + { url = "https://files.pythonhosted.org/packages/dc/01/001f65b68192f0228cc1dbbc8d2530ab5d58b61037ba0587f946fea607cd/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9cf95fe4d0f84c82d282745d9bb08ad9f926efa00be4697e767b814ce40d4330", size = 4161736, upload-time = "2026-07-01T11:54:51.156Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d2/0219746d0fd16fc8a84498e79452375be3797d3ce4044596ce565164b84f/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:8728f216dcdb6e6d555cf971cb34076139ad74b31fc2c14da4fafc741c5f6217", size = 4255435, upload-time = "2026-07-01T11:54:53.414Z" }, + { url = "https://files.pythonhosted.org/packages/c8/02/8d0bc62ef0302318c46ff2a512822d2610e81c7aa46c9b3abe6cbaca5ad0/pillow-12.3.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a45650e8ce7fafffd731db8550230db6b0d306d181a90b67d3e6bca2f1990930", size = 3696262, upload-time = "2026-07-01T11:54:55.739Z" }, + { url = "https://files.pythonhosted.org/packages/85/e2/73c77d218410b14f5f2d565e8a998d5317b7b9c75368d29985139f7a46f0/pillow-12.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ba54cfebe86920a559a7c4d6b9050791c20513650a1952ebe3368c7dc70306f8", size = 5350344, upload-time = "2026-07-01T11:54:57.657Z" }, + { url = "https://files.pythonhosted.org/packages/c7/da/32c752228ae345f489e3a42499d817b6c3996da7e8a3bc7a04fc806b243b/pillow-12.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e158cb00350dc278f3b91551101aa7d12415a66ebf2c91d8d5ac14e56ddd3ad0", size = 4780131, upload-time = "2026-07-01T11:54:59.713Z" }, + { url = "https://files.pythonhosted.org/packages/b1/9d/8b2c807dbef61a5197c047afe99823787eb66f63daf9fb2432f91d6f0462/pillow-12.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9aeb04d6aef139de265b29683e119b638208f88cf73cdd1658aa07221165321", size = 6263757, upload-time = "2026-07-01T11:55:01.778Z" }, + { url = "https://files.pythonhosted.org/packages/5c/44/c85361f65dbe00eea8576ee467c768d25129989efb76e94f205e9ca9bb46/pillow-12.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:251bf95b67017e27b13d82f5b326234ca62d70f9cf4c2b9032de2358a3b12c7b", size = 6936962, upload-time = "2026-07-01T11:55:03.93Z" }, + { url = "https://files.pythonhosted.org/packages/18/7e/e483414b35800b86b6f08dbbc7803fb5cd52c4d6f897f47d53ea2c7e6f65/pillow-12.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fe3cca2e4e8a592be0f269a1ca4835c25199d9f3ce815c8491048f785b0a0198", size = 6339171, upload-time = "2026-07-01T11:55:05.989Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f4/68c491844841ede6bed70189546b3ee9731cf9f2cbad396faff5e1ccba45/pillow-12.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:23aceaa007d6172b02c277f0cd359c79492bbb14f7072b4ede9fbcaf20648130", size = 7048116, upload-time = "2026-07-01T11:55:08.131Z" }, + { url = "https://files.pythonhosted.org/packages/a3/34/77f3f793fed8efc7d243f21b33c5a3f0d1c97ee70346d3db855587e155ff/pillow-12.3.0-cp314-cp314-win32.whl", hash = "sha256:af8d94b0db561cf68b88a267c5c44b49e134f525d0dc2cb7ed413a66bc23559a", size = 6467209, upload-time = "2026-07-01T11:55:10.408Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e0/492879f69d94f91f60fc8cd05ba03650e9520afebb2fb7aa12777d7c7f38/pillow-12.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:fdafc9cce40277e0f7a0feabce0ee50dd2fa1800f3b38015e51296b5e814048d", size = 7237707, upload-time = "2026-07-01T11:55:12.745Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ac/6b11f2875f1c2ac040d84e1bbf9cf22a88038f901ca1037898b280b38365/pillow-12.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:e91206ee562682b51b98ef4b26a6ef48fd84e15fd4c4bc5ec768eb641d206838", size = 2565995, upload-time = "2026-07-01T11:55:14.736Z" }, + { url = "https://files.pythonhosted.org/packages/52/69/c2208e56af9bfc1913afb24020297a691eb1d4ef688474c8a04913f65e04/pillow-12.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:164b31cd1a0490ab6efae01aa5df49da7061be0af1b30e035b6e9a1bfe34ee6e", size = 5352503, upload-time = "2026-07-01T11:55:17.076Z" }, + { url = "https://files.pythonhosted.org/packages/07/70/e5686d753e898a45d778ff1718dba8516ead6ab6b95d85fc8c4b70650cf2/pillow-12.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5afb51d599ea772b8365ae807ae557f18bccfe46ab261fd1c2a9ed700fc6eb17", size = 4782956, upload-time = "2026-07-01T11:55:19.448Z" }, + { url = "https://files.pythonhosted.org/packages/d5/37/25c6692f06927ee973ff18c8d9ee98ad0b4d84ee67a09610c2dd1447958e/pillow-12.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3edce1d53195db527e0191f84b71d02022de0540bf43a16ed734ed7537b07385", size = 6322855, upload-time = "2026-07-01T11:55:21.613Z" }, + { url = "https://files.pythonhosted.org/packages/cc/91/420637fcb8f1bc11029e403b4538e6694744428d8246118e45719f944556/pillow-12.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf16ba1b4d0b6b7c8e534936632270cf70eb00dbe09005bc345b2677b726855c", size = 6989642, upload-time = "2026-07-01T11:55:24.006Z" }, + { url = "https://files.pythonhosted.org/packages/10/08/b94d7811281ccf0d143a1cf768d1c49e1e54af63e7b708ab2ee3eb87face/pillow-12.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:24870b09b224f7ae3c39ed07d10e819d06f8720bc551847b1d623832b5b0e28d", size = 6391281, upload-time = "2026-07-01T11:55:26.252Z" }, + { url = "https://files.pythonhosted.org/packages/d2/87/24233f785f55474dc02ce3e739c5528a77e3a862e9333d1dd7a25cc31f70/pillow-12.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:30f2aa603c41533cc25c05acd0da21636e84a315768feb631c937177db558931", size = 7096716, upload-time = "2026-07-01T11:55:28.318Z" }, + { url = "https://files.pythonhosted.org/packages/23/26/fcb2f6e37175b04f53570b59937867e2b80ee1685e744023153028fc14f9/pillow-12.3.0-cp314-cp314t-win32.whl", hash = "sha256:4b0a7fe987b14c31ebda6083f74f22b561fd3739bc0ac51e019622e3d72668c7", size = 6474125, upload-time = "2026-07-01T11:55:30.956Z" }, + { url = "https://files.pythonhosted.org/packages/90/de/3634abee5f1c9e13c56787b7d5517b0ba8d6de51700b95578cf338349c9f/pillow-12.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:962864dc93511324d51ddbb5b9f8731bf71675b93ca612a07441896f4688fb8c", size = 7242939, upload-time = "2026-07-01T11:55:34.044Z" }, + { url = "https://files.pythonhosted.org/packages/ce/2a/fd13f8eb24de5714a6eb444a3d67e2842c6c576e159a43793adf23051351/pillow-12.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0740a512dc522224c77d9aa5a8d70d8b7d73fb91f2c21125d8d025d3b8990e45", size = 2567506, upload-time = "2026-07-01T11:55:35.988Z" }, + { url = "https://files.pythonhosted.org/packages/5d/dc/8fdce34ec725a33c81c6ba122b904d6b9024e50ea9ac7bede62fab54506c/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0feb2e9d6ad6c9e3c06effe9d00f3f1e618a6643273576b016f591e9315a7139", size = 4162063, upload-time = "2026-07-01T11:55:37.941Z" }, + { url = "https://files.pythonhosted.org/packages/76/66/2044b9a63d3b84ff048228dfcb7cd9bf0df983e8470971bf7d4c57b693de/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:9e881fca225083806662a5c43d627d215f258ff43c890f831966c7d7ba9c7402", size = 4255549, upload-time = "2026-07-01T11:55:40.022Z" }, + { url = "https://files.pythonhosted.org/packages/52/7e/1f67e6f4ece6b582ee4b539decbcc9f848dc245a93ed8cd7338bafef72f1/pillow-12.3.0-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:4998562bf62a445225f22e07c896bb04b35b1b1f2eb6d760584c9c51d7a5f78c", size = 3696331, upload-time = "2026-07-01T11:55:41.98Z" }, + { url = "https://files.pythonhosted.org/packages/12/40/d306fc2c8e4d45d7f175c77edca7063be7b86fe7fe6e68f4353bf71d808c/pillow-12.3.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:dc624f6bc473dacdf7ef7eb8678d0d08edf15cd94fad6ae5c7d6cc67a4e4902f", size = 5350370, upload-time = "2026-07-01T11:55:44.028Z" }, + { url = "https://files.pythonhosted.org/packages/dd/44/668fb1437e8ce420f62d6106eb66e44a5971602a4d794615bdf79315d82d/pillow-12.3.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:71d6097b330eea8fd15097780c8e89cb1a8ce7838669f48c5bacd6f663dd4701", size = 4780147, upload-time = "2026-07-01T11:55:46.073Z" }, + { url = "https://files.pythonhosted.org/packages/0c/08/93fa2e70e30a2d81547e481b6ee2bb9522117221fb1e0ce4b5df70967677/pillow-12.3.0-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:28ce87c5ab450a9dd970b52e5aca5fe63ed432d18a2eaddd1979a00a1ba24ace", size = 6273659, upload-time = "2026-07-01T11:55:48.264Z" }, + { url = "https://files.pythonhosted.org/packages/f8/6d/043e96ff814fc31a33077e4cba86082167db520c93632afdf2042febbb0c/pillow-12.3.0-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b02afb9b97f65fbca5f31db6a2a3ba21aa93030225f150fa3f249717e938fb4", size = 6947439, upload-time = "2026-07-01T11:55:50.503Z" }, + { url = "https://files.pythonhosted.org/packages/af/92/ba71d2ee2ac0edf3fa33bd9d5ee9ee080da70b1766f3ca3934f9938ddac9/pillow-12.3.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:1182d52bc2d5e5d7d0949503aa7e36d12f42205dc287e4883f407b1988820d39", size = 6353577, upload-time = "2026-07-01T11:55:52.697Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ce/e63064e2122923ff687c8ad792d0d736a7b3920a56a46982e81a7fdd25d6/pillow-12.3.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:e795b7eb908249c4e43c7c99fac7c2c75dab0c43566e37db472a355f63693d71", size = 7060394, upload-time = "2026-07-01T11:55:55.149Z" }, + { url = "https://files.pythonhosted.org/packages/54/76/a09cc3ccc8d773a7283d34c38bec1708f9e3cc932093cbc4c5e71ac4060b/pillow-12.3.0-cp315-cp315-win32.whl", hash = "sha256:57b3d78c95ba9059768b10e28b813002261d3f3dfc55cc48b0c988f625175827", size = 6467375, upload-time = "2026-07-01T11:55:57.769Z" }, + { url = "https://files.pythonhosted.org/packages/3e/03/1846c49ba3b1d5550392a4bbd06d6fb4578e1cd91a803198b5c90f5f7d53/pillow-12.3.0-cp315-cp315-win_amd64.whl", hash = "sha256:fa4ecea169a355be7a3ade2c783e2ed12f0e40d2c5621cda8b3297faf7fbb9f5", size = 7237048, upload-time = "2026-07-01T11:55:59.975Z" }, + { url = "https://files.pythonhosted.org/packages/fb/bb/89f35dcc79610423f9f195504d7def7f0d1416a711541b42867e25fe3412/pillow-12.3.0-cp315-cp315-win_arm64.whl", hash = "sha256:877c3f311ff35410f690861c4409e7ccbf0cd2f878e50628a28e5a0bb689e658", size = 2566006, upload-time = "2026-07-01T11:56:02.143Z" }, + { url = "https://files.pythonhosted.org/packages/30/88/707027ba09942dfa2c28759b5c222d769290a41c6d20ea60ec250801941f/pillow-12.3.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:e9871b1ffbfa9656b60aeee92ed5136a5742696006fa322b29ea3d8da0ecc9cf", size = 5352509, upload-time = "2026-07-01T11:56:04.2Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6d/00352fa25332c2569cd387851f568cc5a4b75a9adbfb37ac4fbce4c02eec/pillow-12.3.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:53aa02d20d10c3d814d536aa4e5ac9b84ca0ff5a88377963b085ad6822f93e64", size = 4783167, upload-time = "2026-07-01T11:56:06.631Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/9e049dfa21af7c22427275720e2490267ba8138120add5c4c574deb69782/pillow-12.3.0-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:446c34dcc4324b084a53b705127dc15717b22c5e140ae0a3c38349d4efec071e", size = 6329237, upload-time = "2026-07-01T11:56:08.868Z" }, + { url = "https://files.pythonhosted.org/packages/36/16/cf6eeaae8d0fce8dd390a33437cf68c5d5bd73834a2bc6e2f14efda0ab45/pillow-12.3.0-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf1845d02ad822a369a49f2bb9345b1614744267682e7a03527dc3bf6eea1777", size = 6997047, upload-time = "2026-07-01T11:56:11.379Z" }, + { url = "https://files.pythonhosted.org/packages/1e/69/dbf769bdd55f48bf5733cac28edc6364ffaa072ec9ba336266e4fe66be55/pillow-12.3.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:186941b6aef820ad110fb01fb06eb925374dc3a21b17e37ec9a53b250c6fe2d1", size = 6400440, upload-time = "2026-07-01T11:56:13.908Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e1/ffc9cfc2eea0d178da8018e18e959301ad9d6bc9f3edb7181e748a474b97/pillow-12.3.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:f13c32a3abd6079a66d9526e18dad9b6d280384d49d7c54040cd57b6424041d9", size = 7105895, upload-time = "2026-07-01T11:56:16.575Z" }, + { url = "https://files.pythonhosted.org/packages/18/f0/a5595c1e8c3ae44b9828cb2f0fa8155e5095ef04d6327b8f61cf44a3df85/pillow-12.3.0-cp315-cp315t-win32.whl", hash = "sha256:1657923d2d45afb66526e5b933e5b3052e6bdea196c90d3abb2424e18c77dae8", size = 6474384, upload-time = "2026-07-01T11:56:18.855Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/62bcd9f844984c5938d3b05264a61d797a29d3e0812341a8204af70bbdee/pillow-12.3.0-cp315-cp315t-win_amd64.whl", hash = "sha256:8cd2f7bdda092d99c9fc2fb7391354f306d01443d22785d0cbfafa2e2c8bb418", size = 7243537, upload-time = "2026-07-01T11:56:21.214Z" }, + { url = "https://files.pythonhosted.org/packages/3d/68/1f3066acedf37673694a7141381d8f811ae97f30d34413d236abe7d489f1/pillow-12.3.0-cp315-cp315t-win_arm64.whl", hash = "sha256:06ff022112bc9cbf83b60f8e028d94ad87b60621706487e65f673de61610ab59", size = 2567491, upload-time = "2026-07-01T11:56:23.506Z" }, + { url = "https://files.pythonhosted.org/packages/75/18/2e8b40223153ccbc60df07f9e8928dc0c76202aa4e55ae9f53962b6510d6/pillow-12.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b3c777e849237620b022f7f297dd67705f9f5cf1685f09f02e46f93e92725468", size = 5302510, upload-time = "2026-07-01T11:56:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/46/3e/51fabf59d5ab801ceab709453d3ab6b180083496579549de4c45ced6528a/pillow-12.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b343699e8308bdc51978310e1c959c584e7869cc8c40780058c87da7781a1e94", size = 4736058, upload-time = "2026-07-01T11:56:28.041Z" }, + { url = "https://files.pythonhosted.org/packages/bf/20/22fe9384b7949e25fb1293bcfc84fb82590ff4ea6b37c95b24d26d793d86/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbd139c8447d25dd750ab79ee274cc5e1fe80fc56340ab10b18a195e1b6eca3e", size = 5237776, upload-time = "2026-07-01T11:56:30.263Z" }, + { url = "https://files.pythonhosted.org/packages/08/14/f6ba68107680ffa74b39985f3f30884e41318fbc4250caa423c79b4788bb/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7e480451b9fa137494bccd3a7d69adbe8ac65a87d97be61e11f1b1050a5bac3", size = 5860358, upload-time = "2026-07-01T11:56:32.68Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0169bc772ec491108b62f644f8ecf1fe5d8ae5ebafde2ee2142210166903/pillow-12.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:04f01d28a6aaff387bf842a13be313df23ba0597a44f1a976c9feb3c6ff4711a", size = 7231786, upload-time = "2026-07-01T11:56:35.046Z" }, ] [[package]] @@ -2737,15 +2735,15 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.4.2" +version = "1.4.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0b/1a/cbbaf13b730abb0a16b964d984e19f2fe520c21a4dc664051359a3f5a9e7/python_discovery-1.4.2.tar.gz", hash = "sha256:8f3746c4b4968d22afbb97d36e1a0e5b66e6c0f297290f2e95f05b9b8bf18690", size = 70277, upload-time = "2026-06-11T16:10:42.383Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/26/8b004cc36f430345136f6f00fa1aa9ed596c8ed1e8504625fa79522ff39c/python_discovery-1.4.3.tar.gz", hash = "sha256:ad57d7045a862460d4a235986c33f13ed707d3aeb9153fa47eb7dfd0d4673289", size = 70438, upload-time = "2026-07-03T13:21:51.621Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/82/a70006589557f267f15bd384c0642ad49f0d97b690c3a05b166b9dcbad3b/python_discovery-1.4.2-py3-none-any.whl", hash = "sha256:475803f53b7b2ed6e490e27373f9d8340f7d2eebf9acdaf645d7d714c97bb500", size = 33886, upload-time = "2026-06-11T16:10:41.192Z" }, + { url = "https://files.pythonhosted.org/packages/28/78/9b77ecb4644d1bbea94d29abf78f21c47eca6eb79e9745b702ec0bed2e19/python_discovery-1.4.3-py3-none-any.whl", hash = "sha256:b6e1e4a7d9e3f6948c39746ffe8218225162d738ba39d05ab1d2f6c1cac4878c", size = 33885, upload-time = "2026-07-03T13:21:50.174Z" }, ] [[package]] @@ -2902,7 +2900,7 @@ dependencies = [ { name = "hdf5plugin" }, { name = "matplotlib" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "optuna" }, { name = "rosettasciio" }, { name = "scikit-image" }, @@ -2985,7 +2983,7 @@ dependencies = [ { name = "anywidget" }, { name = "matplotlib" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "pillow" }, { name = "torch" }, { name = "traitlets" }, @@ -3070,7 +3068,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "dask", extra = ["array"] }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "pint" }, { name = "python-box" }, { name = "python-dateutil" }, @@ -3119,139 +3117,125 @@ wheels = [ [[package]] name = "rpds-py" -version = "2026.5.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2e/43/25a8dcd3feedd735039a8f0b5b7e3b118232b5eae288c4fd9ab200d41094/rpds_py-2026.5.1.tar.gz", hash = "sha256:07b24fea40541e28570e5b795a4a38fbdcd12550c06bd0748005ecc8116ca256", size = 64459, upload-time = "2026-05-28T12:02:13.232Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/a0/acf8b6fc20bfdcd3a45bd3f57680fb198e157b7e997b9123b10763798bd2/rpds_py-2026.5.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3397a5ed7174dc2786bb214030232fc36fe8e5584fec43a9952cc542b1a12036", size = 355609, upload-time = "2026-05-28T11:58:50.78Z" }, - { url = "https://files.pythonhosted.org/packages/b6/95/f8203fd997484b1690a6869cd0e503b6c3c6be55b0ecc36d1a491fe742f0/rpds_py-2026.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:99ab6ba7bfa2cb0f96a04e3652355bf04e3f51aceb1e943b8541dab7ba4828cc", size = 348460, upload-time = "2026-05-28T11:58:52.374Z" }, - { url = "https://files.pythonhosted.org/packages/33/8c/b47326ad2f0be545a5e5c1a55937a12afaea7d392ba2837bb9680f57e6c9/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0efbe45632665e53e3db8fe1e5692db58fc5cb9bab4459d570b83efefe11164", size = 381031, upload-time = "2026-05-28T11:58:53.775Z" }, - { url = "https://files.pythonhosted.org/packages/22/0b/e83bbd97ffac6f6389b605cd4e1c8ac5761dc7e977769c9255d8c5adb7bd/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:01d17b29c0c23d82b1f4751147ec49cf451f1fc2554eb9ef5f957e55d2656ead", size = 387121, upload-time = "2026-05-28T11:58:55.243Z" }, - { url = "https://files.pythonhosted.org/packages/fd/0e/d285d1bc8864245919c61e1ca82263e4a66d337759c3a4cef72766ff9afc/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7559f72b94ae52659086c595dfa017cde03155f7832071d30959049052cb3ece", size = 501026, upload-time = "2026-05-28T11:58:56.788Z" }, - { url = "https://files.pythonhosted.org/packages/86/06/ccb2109a1e543437b5e43816f2b43b9554cc6783145528a4e3711e05c011/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e25b7088f9ccbfc0dfcaa52bf969300ca229e10ecf758974ebcbb080a4b37bb", size = 391865, upload-time = "2026-05-28T11:58:58.298Z" }, - { url = "https://files.pythonhosted.org/packages/3d/33/237173db1cfef10105b3839a24de00eb8d2a523711add4632447cdf0aedd/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613fc4ee9eaef26dc5840666214dd6fbcebcf32f46e76f4abc473059f4e13dda", size = 378012, upload-time = "2026-05-28T11:58:59.589Z" }, - { url = "https://files.pythonhosted.org/packages/97/64/1eae54e34d5161f9969295e80bd6b62a55f2b6ac5f2a5b60d02c2140e758/rpds_py-2026.5.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:85264a90ff4c05c1568dd65f5921c837614b67c60358fb4c17df3b7f2e90690a", size = 391111, upload-time = "2026-05-28T11:59:01.104Z" }, - { url = "https://files.pythonhosted.org/packages/d8/34/5bb334a5a0f65d77869217c4654f34c78a7d11b93938a3c076a2edeafc52/rpds_py-2026.5.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe71bca7d547acb17027c7fd1624ff8aae623499c498d3e7011182c4de5c25e0", size = 409225, upload-time = "2026-05-28T11:59:02.433Z" }, - { url = "https://files.pythonhosted.org/packages/16/0f/007ec21283b5b040b4ec3bd95e0402591e22bfa7d5c93dfe01c465c2d2d7/rpds_py-2026.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05fa4f41f37ec97c9c260441a940450a192f78d774d2b097eee1379f1e1246a", size = 556487, upload-time = "2026-05-28T11:59:04.012Z" }, - { url = "https://files.pythonhosted.org/packages/ff/10/5437c94508169b6b22d8418fef7a66e9ffb5f3b9e9c94460f2eedafe06ff/rpds_py-2026.5.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df1d2a1996755b24b9ecee92cb4d36c28f86f464a6a173349c26bab41e94b8c2", size = 620798, upload-time = "2026-05-28T11:59:05.485Z" }, - { url = "https://files.pythonhosted.org/packages/e0/d5/9937dce4d6bda74157b954e7d1460db05a22f5929dccfeeba1ed27a93df0/rpds_py-2026.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8895840ac4809e5f60c88fd07617cd71326e73d6e5a8aa783c5c0f7c24985de2", size = 584053, upload-time = "2026-05-28T11:59:06.837Z" }, - { url = "https://files.pythonhosted.org/packages/6c/31/750617dd0ae1752471bf43f9e41d263398fae7cde7849d23b8574a70e617/rpds_py-2026.5.1-cp311-cp311-win32.whl", hash = "sha256:3684a59b158a7683aaeb8e25352e9a9dd2122cec78f2d8530266e4f91b4c7b3f", size = 214390, upload-time = "2026-05-28T11:59:08.402Z" }, - { url = "https://files.pythonhosted.org/packages/3c/bb/3dcab0e1d9516303f2eb672a5d6f62eca5a69e2886301e9c8c54b520c39b/rpds_py-2026.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:7bd530e6a530bb3ea892f194fafa455f3516ac25ecf7143fd33c09be62b0470a", size = 231097, upload-time = "2026-05-28T11:59:09.786Z" }, - { url = "https://files.pythonhosted.org/packages/49/d6/c6bbf5cb1cf12b9732df8074b57f6ef8341ba884c95d40632ae8bddb44e4/rpds_py-2026.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:0a5ae4dbe43c1076983b72616496919872ae7bbe7a1e21cc48336bc3154d130b", size = 226361, upload-time = "2026-05-28T11:59:11.079Z" }, - { url = "https://files.pythonhosted.org/packages/d4/e7/a78582dc57caa592dcc7d4fb69b61390561e908eb3d2f5df5928a8e354c0/rpds_py-2026.5.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3abe24a66e57adcfa645d718063a5fa5103ecc71ddbf26d78af8f9368018ff1d", size = 353040, upload-time = "2026-05-28T11:59:12.531Z" }, - { url = "https://files.pythonhosted.org/packages/a3/43/35e3f136343aef451e545ce8c38d36c2f93c0ed88703db8b64ba2b205c68/rpds_py-2026.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58b1d94308ddf0b1982f61f2eb54bf92997c9ece8a8093ef014250f4a517906c", size = 345775, upload-time = "2026-05-28T11:59:13.827Z" }, - { url = "https://files.pythonhosted.org/packages/20/e1/0f2160c5982d3157734d5cb3ed63d8b2d583a73c9864f77b666449f32cf8/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa92420128dadce7f54bd73ba1825a273e9268fe9e35dbf7e6362890efa4e08", size = 376329, upload-time = "2026-05-28T11:59:15.271Z" }, - { url = "https://files.pythonhosted.org/packages/d0/11/ee0ba42aff83bf4effdbc576673c6be64c5e173978c3f6d537e94482f77d/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ca653c6546386227cd9800d1bef6a348099acf8db4250341da6d90f663d6dfcb", size = 383539, upload-time = "2026-05-28T11:59:16.665Z" }, - { url = "https://files.pythonhosted.org/packages/11/df/d94aa6a499d4ac40afe2d7620f2c597fd3c0f182e854ad7cf3f596a81cb6/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66c93681c4729e4e3ecba31b8179fae083ff3118841672835140338b4b9867c1", size = 494674, upload-time = "2026-05-28T11:59:17.991Z" }, - { url = "https://files.pythonhosted.org/packages/1f/75/33d30f43bb2f458de11979486a591b1bf6e5651765ed1704c6197c2dc773/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40ff257542e04796880e011e15cd4dc21c2599975df2aaa8f2c8495ca574e1a5", size = 389268, upload-time = "2026-05-28T11:59:19.434Z" }, - { url = "https://files.pythonhosted.org/packages/f4/1e/2c9096fc19d5fd084b0184ca2b651e659aa0a37e6fdbecf6ece47f147fe1/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6825cc329b290e93c5f6a9be2393118a763f6ccf6abd83704e0c102ca583644", size = 376280, upload-time = "2026-05-28T11:59:21Z" }, - { url = "https://files.pythonhosted.org/packages/b9/e5/61ec9f8be8211ea7f48448195549e4aaf02004083475493b0e137702ecb2/rpds_py-2026.5.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:de42116e69cb53b911cc34aee5ab98f36c597b822545045d49e938818b99e5e4", size = 387233, upload-time = "2026-05-28T11:59:22.454Z" }, - { url = "https://files.pythonhosted.org/packages/0d/ca/bcec1005c4f4a234f92a29078631fee49206c7265ccae966f18fd332e80e/rpds_py-2026.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0f920015df2a504bebaba6d4c31ccf3fcf942f92655c086da30b671aad19aa6", size = 405009, upload-time = "2026-05-28T11:59:23.845Z" }, - { url = "https://files.pythonhosted.org/packages/72/e6/4d5718c5cf26c522dc7c9999e238da1e77380b81d0c5d1df11e271ddfeb1/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0408a24e44feb919423dc6d9da677cb5cddb894d2ca9e763967d156d9c60fab4", size = 553113, upload-time = "2026-05-28T11:59:25.184Z" }, - { url = "https://files.pythonhosted.org/packages/d4/25/2ee807bdb3e1f0b7eddf7782acd5665a8b5205a331a7d7244a52c4812fd9/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cea68bcd53467561ae2f96a6bdad1544299ba97b5b0ddcd5ac3d376e5c781c24", size = 618838, upload-time = "2026-05-28T11:59:26.749Z" }, - { url = "https://files.pythonhosted.org/packages/6a/c1/7d4c26f167f8c41501cc073d30ee22082b16ce358cf5b00ec97cbc7804ea/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4be8b1d2a705cc37d08256004e1d07de143fa0075c8e85a3df020b776f62b732", size = 582436, upload-time = "2026-05-28T11:59:28.11Z" }, - { url = "https://files.pythonhosted.org/packages/04/1d/9d12b0a337bab46f4769f8857f4007e3b2d639e14f9a44a0efe157696e64/rpds_py-2026.5.1-cp312-cp312-win32.whl", hash = "sha256:6736718bd4fc49cbcb538ba30516fdbef161522acefb739657d48b97bd864fed", size = 212734, upload-time = "2026-05-28T11:59:29.689Z" }, - { url = "https://files.pythonhosted.org/packages/c5/93/e4116f2de7f56bc7406a76033dc501811ddeb22b7f056b92d632871ebb0c/rpds_py-2026.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:0a7d1eec967df0e9b22614a5e177622e0c89611d03727fa0cb48e45028907870", size = 229045, upload-time = "2026-05-28T11:59:31.033Z" }, - { url = "https://files.pythonhosted.org/packages/cb/53/6c3419d85eb2ec5938a37627c585b42d76a63bb731d6e42ed4b079ebf486/rpds_py-2026.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:1841d067089e117142d79b98aa0df2f08b52f2ecc1819dd2700636c0db74a473", size = 223967, upload-time = "2026-05-28T11:59:32.318Z" }, - { url = "https://files.pythonhosted.org/packages/6c/32/14c961ad295f490eb0849ada8b79683e93a59b9de3afdd983eaf55fa6867/rpds_py-2026.5.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:efef4ac29c6ff495531eb17ee705b62841ecaa291b7c7077e848ea03e237164d", size = 352787, upload-time = "2026-05-28T11:59:33.655Z" }, - { url = "https://files.pythonhosted.org/packages/ca/bb/d1b85117967c11191441a7274ae616c65d93901d082c588f89a50a8da5ae/rpds_py-2026.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c39f5b67a8a2e67179ada2a954227d670fe65fa9098457f698f56ddf248709b3", size = 345179, upload-time = "2026-05-28T11:59:35Z" }, - { url = "https://files.pythonhosted.org/packages/7c/46/d84105f062e626a1b233f863907288a4708c2d833b8b4c6fb2764bc080c0/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5c30f3f04eef4fbd362226a6f31d7c8895ca4fbb6e0b790f6890a98d8da8559", size = 376173, upload-time = "2026-05-28T11:59:36.43Z" }, - { url = "https://files.pythonhosted.org/packages/e2/ae/469d7959ce5b1201e1de135dc735b86db3b35dd0d1734f6a44246d5f061c/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:277f6c82f0580848796c7ecc8a7173aa3bfb928e4ff831261c2f60a81dc270db", size = 383162, upload-time = "2026-05-28T11:59:37.995Z" }, - { url = "https://files.pythonhosted.org/packages/dc/a2/57853d31a1116a561aa072794602ad3f6341e18d70a8523f1bd5b9fc1e5a/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63c2c4c213f1a4e3f3de28ecab029dbdee976324e729c0d7a55211be72576b02", size = 495093, upload-time = "2026-05-28T11:59:39.453Z" }, - { url = "https://files.pythonhosted.org/packages/99/63/3a8eabcad9314b7daf5c65f451d2c33d989235cd8a5762186cf2c3f5a4f8/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3350ec808fb538fe71a1f94dfaa0e29c598dfad805ce49f0caec5ae3183c652b", size = 389829, upload-time = "2026-05-28T11:59:40.896Z" }, - { url = "https://files.pythonhosted.org/packages/4b/25/05678d97fc25e2622df14dc530fb82023174ecfff6733991ed0d78f167bd/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1b964e3ab599e718dc46c018d104b1ebc007cbc6567d827c94a687fca56d77e", size = 374786, upload-time = "2026-05-28T11:59:42.626Z" }, - { url = "https://files.pythonhosted.org/packages/88/d1/8c90b6431e80a3b91b284a5c7c8c0c4f9c006444d90477a740d6e0f9c694/rpds_py-2026.5.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:19cb09fab7b7fc96b2a6e28f2e34b72a3705ff27b37edb77455316e5d3f3dc9b", size = 386920, upload-time = "2026-05-28T11:59:44.124Z" }, - { url = "https://files.pythonhosted.org/packages/ff/99/4638f672ab356682d633ee0da9255f5b67ce6efd0b85eb94ad3e255e65a5/rpds_py-2026.5.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:abe76bcdba31e576cb83eeb8797aa0d882b738fef6dc65d0601fc753806a5b46", size = 405059, upload-time = "2026-05-28T11:59:47.177Z" }, - { url = "https://files.pythonhosted.org/packages/66/3f/3546524b6eb4cc2e1f363a3d638fa52f6c24faae3500c25fb488b02f1740/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bff7073db3899158fff55ebf57b113a67030af26f80a18978f9f0aa60250ddf", size = 553030, upload-time = "2026-05-28T11:59:48.603Z" }, - { url = "https://files.pythonhosted.org/packages/c6/c3/7b3388c796fcf471bd17194242d4dc1a7608567c0fa422bcc1c5e79f9c1e/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8ba264fa49be666cd9cc56bf34ec7002fb3d27a4aee5bcb4d43d0d18feb1bb6f", size = 618975, upload-time = "2026-05-28T11:59:50.314Z" }, - { url = "https://files.pythonhosted.org/packages/61/1e/a3cb07f2795075d1d88efddae2f541359fde5f08c81ee114c29c2949c90a/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4860b603ddda0475a8885499b3729e90229d480105b42651962a5397d995fa89", size = 581178, upload-time = "2026-05-28T11:59:51.673Z" }, - { url = "https://files.pythonhosted.org/packages/a1/74/e758c03a5ef46f04c37f2651a2893db846d569ba8a7bca469d4b58939bcd/rpds_py-2026.5.1-cp313-cp313-win32.whl", hash = "sha256:7944270ae71383f6e2657dd7d5ce4eeb4ac2d0059a6738f0510583d462ab4842", size = 212481, upload-time = "2026-05-28T11:59:53.148Z" }, - { url = "https://files.pythonhosted.org/packages/70/ec/a2aca432db9c7359b40fa393eeeaa0d166c2f70175be956e75fa24197c44/rpds_py-2026.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:88647f43a73c4e01be19b04ceef0c8d3a1958153604d13c773becd8016f2a0cf", size = 228519, upload-time = "2026-05-28T11:59:54.505Z" }, - { url = "https://files.pythonhosted.org/packages/29/60/a73bfdd45b096574556acf303bbd9fa9eed36ca8a818b514e2a5d5fe2b9d/rpds_py-2026.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:453895624ecf7db7063b1004e44037522bbaef9ff6a945e59bc71662d7a03abd", size = 223446, upload-time = "2026-05-28T11:59:56.081Z" }, - { url = "https://files.pythonhosted.org/packages/18/e2/408105fd611823f00882aea810f3989a30d26b1bab8b6beb20f98c724e0e/rpds_py-2026.5.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:b4e4bc98639ec915f512fde3aa7a95e0041d95d9c3cc86eea841fa63cb1e8600", size = 355287, upload-time = "2026-05-28T11:59:57.448Z" }, - { url = "https://files.pythonhosted.org/packages/8d/58/5c4a43436843c90d0f6d19f82c200c80e3843ca9fa07b237623327f6d384/rpds_py-2026.5.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cacedb7a6e167680acba45ad5716e89067d225dc80da0d7040cae8c81d4572fa", size = 347033, upload-time = "2026-05-28T11:59:58.881Z" }, - { url = "https://files.pythonhosted.org/packages/fb/c2/1a71acdacaf4e259b10278fb87b039ded3cf80041bcd89dd8a3ea702ded6/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68700371c5d7ae1412862ddfa719090925c93ecf351c566d66f09d04b136ea00", size = 376891, upload-time = "2026-05-28T12:00:00.516Z" }, - { url = "https://files.pythonhosted.org/packages/c2/c8/535f3d9b65addd8e28aa87b83c6e526799c3717a88273db8ea795beeef7a/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:296c799becfa849c779c8725494fe9ed94959ed886787df4364b058465bad7f0", size = 385646, upload-time = "2026-05-28T12:00:02.394Z" }, - { url = "https://files.pythonhosted.org/packages/1c/91/dc033f313345c354ade914dbe73cdb90b615a4409ea02430d5356794f3d8/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3858b908218ee108d0bbfb2095ccc237648053c9bf98affad7cb079acaf1d97", size = 498830, upload-time = "2026-05-28T12:00:04.189Z" }, - { url = "https://files.pythonhosted.org/packages/27/fc/90fcbea459dbb8ddc18a2e0fd1de9412b48bc84ffff2db771cf714bacfd6/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4fb8d2e7cb2f850b169806d61d1b991738acec96500a75c30f49caf064ce7cef", size = 392830, upload-time = "2026-05-28T12:00:05.797Z" }, - { url = "https://files.pythonhosted.org/packages/b2/1d/46cd11a228c9750684a798d98f878be6f614aa762438da7378f035e79e35/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27b74c10ed6a8f190f4287f53bcfea348b92a84a9c9f70d30183d1e6172d580d", size = 379613, upload-time = "2026-05-28T12:00:07.433Z" }, - { url = "https://files.pythonhosted.org/packages/24/4a/d9b0c6af3a1de03eb93741bbe8be2bdce84d8fda8224f3005451d86df389/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:b9a6528956191c48c52294a592dbd4a8386d7048bdb25c0efcb6b966466c6d83", size = 388183, upload-time = "2026-05-28T12:00:09.227Z" }, - { url = "https://files.pythonhosted.org/packages/c5/b4/db7aaabdda6d020afc87d981bcc2f57a434c7dec60ecfc2ab3dd50b20351/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:af03e34e860047bc7a352b842856fcf78798fbb81132cc98bd2f907ab4eb9cd2", size = 408578, upload-time = "2026-05-28T12:00:10.779Z" }, - { url = "https://files.pythonhosted.org/packages/08/d6/070f6a41cbb343e2ac4171859bf3f3623e0ab002f72619d6d505313ec2de/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fea6e836d10abbe191d557d33bd58bd5987725fe63aa1eefe557d230209855bd", size = 553573, upload-time = "2026-05-28T12:00:12.443Z" }, - { url = "https://files.pythonhosted.org/packages/75/ab/1a71ea3589c4345dac0a0518f0e6a031cb42689277851b683c46d27463a5/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:fc0c0f878ea770a0a8a462456c5ad36fc9fe6358e6b76fdadc7f17575e0b8bf1", size = 620861, upload-time = "2026-05-28T12:00:14.09Z" }, - { url = "https://files.pythonhosted.org/packages/8a/22/9bf80a56069c0c443fcfefac639a86a744550a2898817a6dfd3e26654924/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e0b360f316d966b048b085857630b3cc51f3db2f07b06f440eac8f695374d1e3", size = 585633, upload-time = "2026-05-28T12:00:15.66Z" }, - { url = "https://files.pythonhosted.org/packages/da/68/3b2c0a75c9e04125696f84ebdbbf304acf5a40b58ba4481cdb98a922c3ba/rpds_py-2026.5.1-cp313-cp313t-win32.whl", hash = "sha256:a2999883eedf72fdfb7520b92c7d4ec2572a71ff40239377aa604cc529eecafc", size = 210074, upload-time = "2026-05-28T12:00:17.291Z" }, - { url = "https://files.pythonhosted.org/packages/e7/8b/609157d5a25d37d4f29f92840ba531f416907c34ae5c5739dd21fc2bef98/rpds_py-2026.5.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e07be2a9d7122bd6e82dea89814ef8dc893feb1aae97fec1630f3263bbb30e55", size = 228635, upload-time = "2026-05-28T12:00:18.73Z" }, - { url = "https://files.pythonhosted.org/packages/d4/6f/19c1918a4b590d8de87e712e4abe4b3875771eff60216fb6153cf6665c68/rpds_py-2026.5.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:1f2c391c3059798093b65df23aca2cac150460ae9c630d99dec83d703d9485b9", size = 349756, upload-time = "2026-05-28T12:00:20.217Z" }, - { url = "https://files.pythonhosted.org/packages/e5/60/a06fe7da34eca79dacbf958a2ba0c6eea85bc2b29de20080bf40f72f66fa/rpds_py-2026.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:413b424f7c4ee65ab5e5be91f5731be0f8b41a1ee2b12dfe810d716312e95a78", size = 343831, upload-time = "2026-05-28T12:00:21.711Z" }, - { url = "https://files.pythonhosted.org/packages/bf/ec/b2333b97b90e2a6ef6ca8ad386ee284968e74bcfe113b3f1a8d9036429a9/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c595a1d9255dce0599e13130d1440ab2506654f2b50294226ee06402f8fef63", size = 375127, upload-time = "2026-05-28T12:00:23.326Z" }, - { url = "https://files.pythonhosted.org/packages/14/7f/e00aae54067f2b488c4637961d5f58204d470795fc791085fa3f15060d2e/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1c27c5f6102eac8c03e7595a00827a53b271ba40a53b59ff8709170e0855ea4a", size = 379034, upload-time = "2026-05-28T12:00:24.89Z" }, - { url = "https://files.pythonhosted.org/packages/be/cc/423999bbb8ae8dc93c77fc1d5e984ade5eb89d237d3bb884ccfa72ae2890/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c7fcf61d44cacecaf3aea542b0e053db77972a4573e7ceda16fb2b399161195", size = 490823, upload-time = "2026-05-28T12:00:26.676Z" }, - { url = "https://files.pythonhosted.org/packages/0f/aa/c671bf660f12e68d3c52ff86c7066ed1372df5a0f4f2ff584e419b8207e7/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c817a189d4ee14290420e5ff051e4dd6baa13f3edf84685071dee07a6d538ee", size = 388144, upload-time = "2026-05-28T12:00:28.577Z" }, - { url = "https://files.pythonhosted.org/packages/19/c8/d63bb75b68afe77b229e3021c6031bcaf01da5db5b0e69d0d10f9ba679a7/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21846aac0ed2e0589f38c12dc44e77bb64e494b771eadbcf169cba00566ba7ba", size = 371959, upload-time = "2026-05-28T12:00:30.304Z" }, - { url = "https://files.pythonhosted.org/packages/82/35/c51122014d8274ff37dc606d60049c3db7d83da02b5b282511e5a906a9a6/rpds_py-2026.5.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b317c87a13f769a4e787819bd508aaa5d69aa09b0880de9af6d3a8a54571cdec", size = 383558, upload-time = "2026-05-28T12:00:31.764Z" }, - { url = "https://files.pythonhosted.org/packages/e3/f9/2790cb99c136a5363acdeacf5c27c56f3de0d4118a1f48fca83404c99c89/rpds_py-2026.5.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ce87129d9f2c14fa6c4a8601fb80eb4488c80d38a20cd13758ef11123e14995d", size = 402789, upload-time = "2026-05-28T12:00:33.247Z" }, - { url = "https://files.pythonhosted.org/packages/e5/1b/e4fb584f8c75d35c38150ff6a332cda949e6f97acba1f4fd123b14ab56fe/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9cdddb6c1207d284d94fd1530adf57fbd797fe7c4b8704ba85f49414f2557e7d", size = 551405, upload-time = "2026-05-28T12:00:34.819Z" }, - { url = "https://files.pythonhosted.org/packages/d8/f7/a6731b4216cb3793ea1af5391da240f5683dacc0d13e034fe5fc3503f240/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:4e237e139f94d3c036fd28eb9f564c99055476ff4ff05cd42be55ce349b5aa02", size = 616975, upload-time = "2026-05-28T12:00:36.268Z" }, - { url = "https://files.pythonhosted.org/packages/2c/ea/2e051a81d95d8e63f4b35a1c463a87e8766bc3d083c067c5dfb6bf220747/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ed0954b524873214369184a9c82b0eaa45a3fbb9a798cd95b17e0d98499e7ea0", size = 578701, upload-time = "2026-05-28T12:00:37.82Z" }, - { url = "https://files.pythonhosted.org/packages/65/56/b5f6fdb2083e32bca8a8993d89e70db114b4756c9e2c38421328126689d2/rpds_py-2026.5.1-cp314-cp314-win32.whl", hash = "sha256:2d88621d6a7d4dfa633d21abe90f280bb205274e16b1d1e61c6ad4640b2453b7", size = 209806, upload-time = "2026-05-28T12:00:39.492Z" }, - { url = "https://files.pythonhosted.org/packages/fb/80/65a5aa96c155e611d1ed844e4e1f57f3e36b021f396d9f8585d756e6b90d/rpds_py-2026.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:cef8ac28d26f4dda3533060c20fbf80a325458fa9fd23ea72a73cdfa8e978838", size = 225985, upload-time = "2026-05-28T12:00:40.94Z" }, - { url = "https://files.pythonhosted.org/packages/27/7c/ad185212e87b05f196daef92bc5f3caf07298eb47c295b5585c3dd3093ac/rpds_py-2026.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:eaaea962c68cdc68d4a533ba985ab8e9484277910bbfaa2ab3ef7732667bfed8", size = 221219, upload-time = "2026-05-28T12:00:43.15Z" }, - { url = "https://files.pythonhosted.org/packages/23/58/e14ae18759020334646b031e708ab4158d653a938822bfb7b95ef2e93aa3/rpds_py-2026.5.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:21942f52dbbd5f8758bf021213d28bd45c39e873e65e2407faf5f1846f5761ad", size = 352148, upload-time = "2026-05-28T12:00:44.638Z" }, - { url = "https://files.pythonhosted.org/packages/31/9b/5f4a1e2f960bca3ac5d052b139dd31eed97b259f9d909173821760d542e8/rpds_py-2026.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f414556f6e3958300ff941e40c9f97e3dc9774ddd1b3434c475d73dd354bbed3", size = 345196, upload-time = "2026-05-28T12:00:46.14Z" }, - { url = "https://files.pythonhosted.org/packages/1a/71/1d9574d6a2fa20ab60eaa55c7467f5aa20cbc770f341a05f09c0876f59e2/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef1013a8625c74043210190b246f5b1551e09757c1f356c6e4160ef96c5bc081", size = 374981, upload-time = "2026-05-28T12:00:47.531Z" }, - { url = "https://files.pythonhosted.org/packages/0c/9a/37e99f4915a80aa71670263c1267f7ae0af95f53a3f61e6c3bdc016d4515/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cc68e231a77a5f0d774ae278a1f8e55c0456501820847c1e4efb3829f3441df6", size = 379961, upload-time = "2026-05-28T12:00:49.216Z" }, - { url = "https://files.pythonhosted.org/packages/a8/ff/6e73f74b89d2e0715e0fc86b7dde893f9a61ae2f9b256ff3bdfe41ac4e94/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9baffb505aff33acc69b422a19f77806680f3c8632227d79f48de8a810d1c2c5", size = 495965, upload-time = "2026-05-28T12:00:51.111Z" }, - { url = "https://files.pythonhosted.org/packages/ea/e0/425faba25f59d74d4638b267f7c7a80e8649d2ef4db10a19b0c4a71e6e6f/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8d2f912928d426e8cfa396f7f3f8d29a59e6689c86dcca3c420730c1096322b", size = 389526, upload-time = "2026-05-28T12:00:52.77Z" }, - { url = "https://files.pythonhosted.org/packages/c6/76/7a41960e3fddae47fab43a28684d5da981401dffd88253de0944148654cb/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90f628283be835db980c941767d41c9a27b5239e54ba0a9c1335247e82406964", size = 376190, upload-time = "2026-05-28T12:00:54.215Z" }, - { url = "https://files.pythonhosted.org/packages/27/60/5f38dc70824fc6951b51d35377e577a3a3a4c81a6769cc5a2de25ebe0ad1/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:1ebb2f0ab7e16132995a72de805170e0203df0c3dd22e1ef1cd1fdd90bd7a131", size = 383921, upload-time = "2026-05-28T12:00:55.673Z" }, - { url = "https://files.pythonhosted.org/packages/60/1a/d60a38caa1505f4b9483c3fbbde12c94e1079154f4f401a6da96f7e77621/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f3df3d16ded76f1f8c9cdebd0e1ea55fdf4c23b812de189814da7cf229c22a81", size = 404766, upload-time = "2026-05-28T12:00:57.518Z" }, - { url = "https://files.pythonhosted.org/packages/87/ff/602fd3f174d6425f0bce05ad0dfbec0e96b38d0f7d08a79af5aa20083885/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9af8905b8f854990e40d5206aa5ac58d9b0fe0b7f351ff2bb086c20f6c8c6a47", size = 551343, upload-time = "2026-05-28T12:00:58.978Z" }, - { url = "https://files.pythonhosted.org/packages/b8/c1/1be13327acdbead3eca1fde03b6a34dbb011f1e864e217f0d32cc1779a7f/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:036a36a87fb1cd3b214d11c4b3c4f7d2ddad933625dca1c900b56a057c07740a", size = 618502, upload-time = "2026-05-28T12:01:00.656Z" }, - { url = "https://files.pythonhosted.org/packages/f3/d7/afb49b49d7f2be8b7ba1a9f0977fa5168003437b93086726f066544e8351/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:62ae3853454fe9ef283a03c96c2d835d39e84b14643a9d62c82ef0fb87d702ca", size = 581916, upload-time = "2026-05-28T12:01:02.22Z" }, - { url = "https://files.pythonhosted.org/packages/25/d1/dbef8c1f8a10f07beb62b5f054e20099fd9924b3ec001b8f0b6ac7813a85/rpds_py-2026.5.1-cp314-cp314t-win32.whl", hash = "sha256:6c3d771a46ec18b12af06ce36243a9a80b07a5d0515236332d90863ca8bb326a", size = 207855, upload-time = "2026-05-28T12:01:03.821Z" }, - { url = "https://files.pythonhosted.org/packages/2a/72/bfa4e61ab8e7dc1c8adf397e05e6cbdd4239357bd72b248d3de662f23915/rpds_py-2026.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c93c629be4636cf54337bd5f06c104d55e42ced54d681f6fe21ae510a65116f6", size = 225422, upload-time = "2026-05-28T12:01:05.194Z" }, - { url = "https://files.pythonhosted.org/packages/27/3a/7b5da92b640f67b6717ccafc83cdd06bfa7ff2395c3685c68922bb54d703/rpds_py-2026.5.1-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:3574b55c604b8f75dacb007136508bbc0db406e626301778096a133327e7f2fb", size = 349576, upload-time = "2026-05-28T12:01:06.722Z" }, - { url = "https://files.pythonhosted.org/packages/d7/8a/2aafd7ad355a1bd48ca76e2262b74b15e6432b5a1efe150efd4d779cd55d/rpds_py-2026.5.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:94068eb3ae6d43f5a786b7db96a406a34e6d5c24489feef32fd6e8946ea7b291", size = 343640, upload-time = "2026-05-28T12:01:08.441Z" }, - { url = "https://files.pythonhosted.org/packages/f7/7d/6c9523c1abbe840a1b7fba3c516d48e1d3487cc80fea4366c4071cf56784/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a5b10e8ce894825f380a8f1b6444cf73c294dfea62afbb2d13e3a9e630cec1", size = 375322, upload-time = "2026-05-28T12:01:09.934Z" }, - { url = "https://files.pythonhosted.org/packages/5a/5d/0b7b03fb1dc509321f01de3149784ab773e34c8573022029af8076afcb9c/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fc09f82e63d4bcd58149572f857a431bae851dc747e313c3b5bdf7abb907fda8", size = 379066, upload-time = "2026-05-28T12:01:11.48Z" }, - { url = "https://files.pythonhosted.org/packages/d7/e2/8ef6012999ebf1cb1c22f876d9ce5e63d960fd4631d2af3202d3f480aa25/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e10464d17df3b582745c25cec695cb9558bca2cb6ddb631aee1787fc72c767b2", size = 494586, upload-time = "2026-05-28T12:01:13.051Z" }, - { url = "https://files.pythonhosted.org/packages/80/af/1eeb029bec67582c226b7809172207cd005073af4ebd906e65ff494f4983/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ba05adbf15d994c38ec0b7ab32e858e5110c21e9009a00a86545fd220f84e038", size = 388415, upload-time = "2026-05-28T12:01:14.631Z" }, - { url = "https://files.pythonhosted.org/packages/18/23/ffbe10711c4d766c1cab0557d6906c074f795814863c67b351355d29354a/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77c004fdc7b891967106f78ddfd7b076bfe6813c6139c6fff6aed3bcaa960b26", size = 372427, upload-time = "2026-05-28T12:01:16.153Z" }, - { url = "https://files.pythonhosted.org/packages/bd/3a/30ba4a6ad457e5b070c18d742a33fb77d8d922b565cc881f8a5313d63bfe/rpds_py-2026.5.1-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:83bcf894486c9d78dd290d3c0124ff6dd8875d3025e2090a8ec49fcc37c55fdd", size = 383615, upload-time = "2026-05-28T12:01:17.809Z" }, - { url = "https://files.pythonhosted.org/packages/d3/69/62e242b53ce39c0814bd24e1a6e6eba6c92be716277745f317f9540a2e7b/rpds_py-2026.5.1-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3df104083952a0e0c6f10de33e440eabe98fb6317d23e1a58c68f6df08d01b9", size = 402786, upload-time = "2026-05-28T12:01:19.419Z" }, - { url = "https://files.pythonhosted.org/packages/38/c1/a770b9c186928a1ed0f7e6d7ae50e7f3950ed23e3f9e366dbc8e38cb55de/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:980450826cf22e133c57e0835070bdd0dd3f73b9b708c3ce223def2cb9469e14", size = 551583, upload-time = "2026-05-28T12:01:21.013Z" }, - { url = "https://files.pythonhosted.org/packages/21/7c/68e8579b95375b70d2a963103c42e705856cdb98569258bd807f4423891c/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:205dde846f24332ab0c1188699a043b8d165b79bb84529ce272c45048ff6be01", size = 616941, upload-time = "2026-05-28T12:01:22.548Z" }, - { url = "https://files.pythonhosted.org/packages/70/a1/a6135aed5730ff03ab957182259987ac11e55fb392a28dc6f0592048a280/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:3966b82dd563176396df030f3dd52a6e54cb69b718e95e78bd555ed3d1e0185d", size = 578349, upload-time = "2026-05-28T12:01:24.118Z" }, - { url = "https://files.pythonhosted.org/packages/09/6e/f24201a76a84e6c49d0bdfdfcb735210e21701e9b21c5bfc0ba497dd62f6/rpds_py-2026.5.1-cp315-cp315-win32.whl", hash = "sha256:7818f8d0a415be74d2be3590b0a1c1f463a642f4d0217e7d10602dceef5b79aa", size = 209922, upload-time = "2026-05-28T12:01:25.522Z" }, - { url = "https://files.pythonhosted.org/packages/9e/e4/966bc240bb0485fc265278f6de44d05834bf0b3618886e0b22e33d54c49a/rpds_py-2026.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:b3cc20c0d800af78fd0fac68086e28c1856cec51ea528bb81ea851aa40d39325", size = 226003, upload-time = "2026-05-28T12:01:27.062Z" }, - { url = "https://files.pythonhosted.org/packages/5c/5c/a15a59269cd5e74472734516c73795c15eccfc841b3d4b0228c3f53f19d0/rpds_py-2026.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:3609e9939a8a76cd904cf98a3f1f13b5dc7e150adeaee89e0ea09652ea213e16", size = 221245, upload-time = "2026-05-28T12:01:28.51Z" }, - { url = "https://files.pythonhosted.org/packages/e0/22/135ce03804e179a71ceb13be095deda4a279bc88f7a6b8fa161c5ad44e12/rpds_py-2026.5.1-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:5d333a7127d4b307601ac37792bee01bb95c867cbfacf21b6375b804d6bbd723", size = 352015, upload-time = "2026-05-28T12:01:30.214Z" }, - { url = "https://files.pythonhosted.org/packages/3b/5f/f1f6d2652eb9d848f6eb369d8db83a2da6249bb49ad2c2a48f45d54538d3/rpds_py-2026.5.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:b5f077b44a4f7808520f66dae234988d867deb9aed9be5da057ce9ba831b2a41", size = 345016, upload-time = "2026-05-28T12:01:31.656Z" }, - { url = "https://files.pythonhosted.org/packages/88/66/b74182775691ea2290c99e52ac8d5db844e56fbec90ce421f107658c8314/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d8f9b7b78c9538fc9e04e82ec0e888ff0c3cffcfad152c77e57cd09351a98a", size = 374775, upload-time = "2026-05-28T12:01:33.136Z" }, - { url = "https://files.pythonhosted.org/packages/ff/8f/15e5a61d9f0a43902d36561d4f07cae6ae9f4716be825159fd72717f33af/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e3a8ae58895ac107ed934a6bf51e5846f95c53b9b940c2c6d310838fd5846358", size = 380270, upload-time = "2026-05-28T12:01:34.574Z" }, - { url = "https://files.pythonhosted.org/packages/02/c3/f859b12763a80540cdf2af0f15b19904cf756a71d7bdd3f82ff3e5b1bbf9/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0957cf3c2b8632ec7aaebffebea8005b353cc2a237b6e2ae3c2cac0820704cfb", size = 495285, upload-time = "2026-05-28T12:01:36.127Z" }, - { url = "https://files.pythonhosted.org/packages/1c/c7/ff27c2ac8411d30b03b1829fd88cae8dad1a4d0da48dd25e57c4038042e6/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c396c1304de421050b3681ea70f371874b54d41b0151e96109758144c231e30b", size = 389581, upload-time = "2026-05-28T12:01:37.635Z" }, - { url = "https://files.pythonhosted.org/packages/6e/67/fe92ee32a6cc05c77228a2f8b1762e7124f386ec20ff83d0757b762d58d0/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad1bff7f666b9598e573815affd666aac6a13a585dde336f843e33350c7fadc", size = 376041, upload-time = "2026-05-28T12:01:39.307Z" }, - { url = "https://files.pythonhosted.org/packages/f8/91/b4d6685c27aba55bd82f25b278be8237038117d05f9659a6213ad3408130/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:656a042550878f12d45752452d47094b7cfe5ad1e9d7b87b5a22ad3ae5ff8015", size = 383946, upload-time = "2026-05-28T12:01:41.043Z" }, - { url = "https://files.pythonhosted.org/packages/bd/79/2c1d832a53c8e0f8e98fc970ec257b950fecd4f62be2ab7182b500a0cbc8/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c4bd4f70294737b5206a3e8e30ccadbf8a60301831c8ea23eec5dbeea1ecfa", size = 405526, upload-time = "2026-05-28T12:01:43.032Z" }, - { url = "https://files.pythonhosted.org/packages/78/c4/c98117b03c6a8581ab2c2dfccfe9a5ad82bd8128a3c28b46a6ad2d97c393/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:43bca78665423cabae77146f2fe7ce55272b6c8d55d82cca83effd42c7e13972", size = 551165, upload-time = "2026-05-28T12:01:44.648Z" }, - { url = "https://files.pythonhosted.org/packages/3b/c1/bc479ca069200af730881b1bd525e3114b2b391a351509fcb1b772f28086/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:42d0f20e85e549c870749d0e247f0c10d318a45b7e9676d575d2dcb04a1b2e66", size = 618778, upload-time = "2026-05-28T12:01:46.337Z" }, - { url = "https://files.pythonhosted.org/packages/77/65/38ab2f90df44c2febfb63cc10ced40763d9b4bc94d173e734528663fe7f5/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:b1be5c35683684d5331b93600c210e8367c254683d8a6df6bd21bd2da3a334fb", size = 581839, upload-time = "2026-05-28T12:01:48.109Z" }, - { url = "https://files.pythonhosted.org/packages/15/2d/ce1f605fe036aadd460e5822e578c6c7ec3a860936cca37d6e0f299daa77/rpds_py-2026.5.1-cp315-cp315t-win32.whl", hash = "sha256:75808f6c38ce7749bb68cc2770161aae5045e6c6f6781a9782e74b93304399df", size = 207866, upload-time = "2026-05-28T12:01:49.648Z" }, - { url = "https://files.pythonhosted.org/packages/79/cb/966040123eb102371559746908ef2c9471f4d43e17ec9a645a2258dab64b/rpds_py-2026.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:90bd6630002a1c7f09e7843dd79f0d24f3d2897cc25a753480917865d14f15b3", size = 225441, upload-time = "2026-05-28T12:01:51.408Z" }, - { url = "https://files.pythonhosted.org/packages/42/56/3fe0fb34820ff667be791b3a3c22b85e8bcba54e9c832f47438c191fa7be/rpds_py-2026.5.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:edf2765d84e42447f112ad877af8fe1db0089aaec5b28e88d6eab45e7fe99cea", size = 357151, upload-time = "2026-05-28T12:01:53.43Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f2/3eb9ccdb9f143b8c9b003978898cb497f942a324c077401e6b8834238e63/rpds_py-2026.5.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ad3773236e95f7f33991eb125224b7da66f206504d032a253a02da7e134519fb", size = 350195, upload-time = "2026-05-28T12:01:54.901Z" }, - { url = "https://files.pythonhosted.org/packages/a7/24/dbda232bc4f3ed732120692ab0d2c8402cb020516556d8bee622dcef2413/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a04df86b3f0fade39ec8fd0e0aab089b1da9fbd2b48df778a57ef96f5e7d38df", size = 381850, upload-time = "2026-05-28T12:01:56.601Z" }, - { url = "https://files.pythonhosted.org/packages/40/30/32e769839a358f78810c234f160f2cc21d1e4e47e1c0e0e0d535be5a0219/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6142dbd80c4df62a5d899f0d616d417f84e0bc8d32526c8e5589019d75d028a7", size = 387899, upload-time = "2026-05-28T12:01:58.212Z" }, - { url = "https://files.pythonhosted.org/packages/ab/86/ec84d243aadb3b34b71dd26a010d0930b2d284ff5fc9a69fec53810ee6fd/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0b35217adefe87f2fe4db7e9766cabe84744bfe9616d9667be18988928c7f2dc", size = 501618, upload-time = "2026-05-28T12:01:59.888Z" }, - { url = "https://files.pythonhosted.org/packages/74/25/b60e52686bbff777a64f9e4f4d3dd57980dc846913777177a2c92e4937aa/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b95d5e11fc712b752081183a55a244c03cd00570489edd7014d8899f8ceb8162", size = 394003, upload-time = "2026-05-28T12:02:01.482Z" }, - { url = "https://files.pythonhosted.org/packages/9b/c7/b3a6a588cc2219510ef3f42e207483a93950bedd1e3a0fd4015c95cff9e5/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141c9498daf2ace9eda35d2b0e376f9ea8b058d84f2aef4f96fccfd449a2f251", size = 379778, upload-time = "2026-05-28T12:02:03.197Z" }, - { url = "https://files.pythonhosted.org/packages/31/00/c7dba3fc8a3da8cb3f6db1eb3386be4d79c2e97c6890d20eb9ac66ae8c43/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:6f249f8b860a200ad35193af961183ebe9132710484e6f6ce0cf89fd83c63a9a", size = 392359, upload-time = "2026-05-28T12:02:04.817Z" }, - { url = "https://files.pythonhosted.org/packages/93/dd/472ba494c70753f93745992c99855bee0636daf74e6984e5e003f150316f/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e4abbf391a70be864920858bf360f4fb380577c9a0f732438a1996726e2c195b", size = 412820, upload-time = "2026-05-28T12:02:06.401Z" }, - { url = "https://files.pythonhosted.org/packages/1d/6f/93831a3bfe789542ed0c1d0d74b78b440f055d6dc3ea4640eba2d95e6e23/rpds_py-2026.5.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:c74005a7bb87752acf351c93897ec63ad77a07a0da7ecad9c050e32e7286ba34", size = 557243, upload-time = "2026-05-28T12:02:08.013Z" }, - { url = "https://files.pythonhosted.org/packages/1f/ff/0b3d604614ffc77522c6b288fdbce68957eb583da1002aa65ba38ac0ee40/rpds_py-2026.5.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:8213afbe8a3a906fb9acb2014423fe3359ee783d0bf90995f70623a3217bfa6c", size = 623541, upload-time = "2026-05-28T12:02:09.661Z" }, - { url = "https://files.pythonhosted.org/packages/ea/ea/e7b0251441da9adfeaebcf29601d10f2a1455fcf0772fae9e7e19032bd96/rpds_py-2026.5.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:8c43a8a973270fd173bf48cdf80bbe66312421cba68d40845034f174f2389049", size = 586326, upload-time = "2026-05-28T12:02:11.47Z" }, +version = "2026.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/2a/9618a122aeb2a169a28b03889a2995fe297588964333d4a7d67bdf46e147/rpds_py-2026.6.3.tar.gz", hash = "sha256:1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4", size = 64051, upload-time = "2026-06-30T07:17:53.009Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/1f/a2dca5ffdbf1d475ffc4e80e4d5d720ff3a00f691795910116960ee12511/rpds_py-2026.6.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7b689145a1485c335569bd056464f3243a29af7ed3871c7be31ad624ba239bc7", size = 342174, upload-time = "2026-06-30T07:14:54.821Z" }, + { url = "https://files.pythonhosted.org/packages/4d/dc/323d08583c0832911768663d1944f0107fcd4088704858d84b5e06d105a0/rpds_py-2026.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:db08f45aecde626498fb3df07bcf6d2ec040af42e859a4f5040d79c200342911", size = 345513, upload-time = "2026-06-30T07:14:56.515Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2a/e31989834d18d2f26ec1d2774c5b1eb3331df4ea8ada525175294c94b48a/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acc992ab27b15f852c76755eb2ab7dce86585ddadba6fa5946e58556088845b4", size = 373783, upload-time = "2026-06-30T07:14:57.736Z" }, + { url = "https://files.pythonhosted.org/packages/87/fe/e80107ee3639585c9941c17d6a42cd65325022f656c023191fce78c324c8/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7f88d653e7b3b779d71ae7454e20dcc9b6bae903f33c269db9f2be41bda3f261", size = 378316, upload-time = "2026-06-30T07:14:59.077Z" }, + { url = "https://files.pythonhosted.org/packages/22/6f/81e3adf81acfb6fa694de2a6e4e7d8863121e3e0799e0a7725e6cf5679c4/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e52655eaf81e32593abedaa4bfe33170c8cfedf3365ed9be6e11e07f148f0278", size = 499423, upload-time = "2026-06-30T07:15:00.488Z" }, + { url = "https://files.pythonhosted.org/packages/2d/9a/41263969df0ce3d9af2a96d5005a288200af1989aed3354bfceb5fc0b21f/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dfcc8b909769d19db55c7cc9541eb64b9b774b1057ffffb4f1048070475bb9f9", size = 386077, upload-time = "2026-06-30T07:15:01.911Z" }, + { url = "https://files.pythonhosted.org/packages/5e/19/7e98f468bd50346faff5b10e5297374b443bfdddacc8e9fbc65984539597/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c1255b302953c86a486b81d330d5ee1d5bd937691ce271b6be0ef0e299eaab7", size = 371315, upload-time = "2026-06-30T07:15:03.317Z" }, + { url = "https://files.pythonhosted.org/packages/99/3c/2b973b4d371906a134b03decfea7f5d9835a2c6d263454392e15b64b5b18/rpds_py-2026.6.3-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:8d2294a31386bfa251d8c8a39472beee17db67d4f1a6eabea665d35c9a4461c3", size = 383502, upload-time = "2026-06-30T07:15:04.627Z" }, + { url = "https://files.pythonhosted.org/packages/98/2a/12e2799500af0a307bca76b63361c51f9fe479223561489c29eea1f2ee41/rpds_py-2026.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8f23ead891a3b762f35ab3b04623da7056545b48aa60d59957e6789914545da", size = 402673, upload-time = "2026-06-30T07:15:05.856Z" }, + { url = "https://files.pythonhosted.org/packages/2d/e3/21e5872d165fe08be4f229e3d5ee9d90019c0bf0e5538de60dbd54009450/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:421aba32367055614287a4292b6a17f1939c9452299f7a0209c117e990b646d4", size = 549964, upload-time = "2026-06-30T07:15:07.159Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d0/5ee0fe36844297de8123bee27bc12078c1a7416ad9f1b8a8ca18d6b0c0ac/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1e5822dfc2f0d4ab7e745eaa6d85945069329beeccef965af3f3bb26058fcab6", size = 615446, upload-time = "2026-06-30T07:15:08.531Z" }, + { url = "https://files.pythonhosted.org/packages/b1/80/1ea5873cb683f2fbe5f21b23ea1f6d179ead19f3c5b249b7eb5dca568ef2/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:83e35b57523816c8613fd0776b40cd8bb9f596b37ddd2692eb4a6bb5ab2f8c93", size = 576975, upload-time = "2026-06-30T07:15:09.97Z" }, + { url = "https://files.pythonhosted.org/packages/c9/e1/90ef639217a5ddb15b7f4f61b1c33911fd044ad03c311bafdd2bcab85582/rpds_py-2026.6.3-cp311-cp311-win32.whl", hash = "sha256:de3eceba0b683bcbb1ab93da016d0270df1f9ae7be716b40214c5dafac6ea45a", size = 204453, upload-time = "2026-06-30T07:15:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/f2/b7/b7a1695d7af36f521fb11e80d6d3adbd744f73b921859bd3c2a2c0dc706f/rpds_py-2026.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:2c54a076ca4d370980ab57bc0e31df57bbe8d41340436a90ef8b1219a3cbb127", size = 223219, upload-time = "2026-06-30T07:15:12.476Z" }, + { url = "https://files.pythonhosted.org/packages/d7/a2/145afacf796e4506062825941176ad9445c2dcf2b3b6a1f13d3030a15e19/rpds_py-2026.6.3-cp311-cp311-win_arm64.whl", hash = "sha256:168c733a7112e071bb7a66460e667edfcff06c017a3c523f7a8a8e08d0140804", size = 219137, upload-time = "2026-06-30T07:15:13.631Z" }, + { url = "https://files.pythonhosted.org/packages/5c/be/2e8974163072e7bab7df1a5acd54c4498e75e35d6d18b864d3a9d5dadc92/rpds_py-2026.6.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a0811d33247c3d6128a3001d763f2aa056bb3425204335400ac54f89eec3a0d0", size = 343691, upload-time = "2026-06-30T07:15:14.96Z" }, + { url = "https://files.pythonhosted.org/packages/a4/73/319dfa745dd668efe89309141ded489126461fcecd2b8f3a3cda185129b6/rpds_py-2026.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:538949e262e46caa31ac01bdb3c1e8f642622922cacbabbae6a8445d9dc33eaf", size = 338542, upload-time = "2026-06-30T07:15:16.267Z" }, + { url = "https://files.pythonhosted.org/packages/21/63/4239893be1c4d09b709b1a8f6be4188f0870084ff547f46606b8a75f1b03/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55927d532399c2c646100ff7feb48eaa940ad70f42cd68e1328f3ded9f81ca24", size = 368180, upload-time = "2026-06-30T07:15:17.62Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ca/9c5de382225234ceb37b1844ebdb140db12b2a278bb9efe2fcd19f6c82ce/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f56f1695bc5c0871cbc33dc0130fcf503aab0c57dcc5a6700a4f49eba4f2652e", size = 375067, upload-time = "2026-06-30T07:15:18.952Z" }, + { url = "https://files.pythonhosted.org/packages/87/dc/863f69d1bf04ade34b7fe0d59b9fdf6f0135fe2d7cbca74f1d665589559d/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:270b293dae9058fc9fcedab50f13cebf46fb8ed1d1d54e0521a9da5d6b211975", size = 490509, upload-time = "2026-06-30T07:15:20.434Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ef/eac16a12048b45ec7c7fa94f2be3438a5f26bf9cc8580b18a1cfd609b7f6/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:127565fead0a10943b282957bd5447804ff3160ad79f2ad2635e6d249e380680", size = 382754, upload-time = "2026-06-30T07:15:21.831Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/d2f3f532616be4d06c316ef119683e832bd3d41e112bf3a88f4151c95b17/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecabd69db66de867690f9797f2f8fa27ba501bbc24540cbdbdc649cd15888ba6", size = 366189, upload-time = "2026-06-30T07:15:23.371Z" }, + { url = "https://files.pythonhosted.org/packages/e3/29/41a7b0e98a4b44cd676ab7598419623373eb43b20be68c084935c1a8cf88/rpds_py-2026.6.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:58eadac9cd119677b60e1cf8ac4052f35949d71b8a9e5556efccbe82533cf22a", size = 377750, upload-time = "2026-06-30T07:15:24.659Z" }, + { url = "https://files.pythonhosted.org/packages/2e/05/ecda0bec46f9a1565090bcdc941d023f6a25aff85fda28f89f8d19878152/rpds_py-2026.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7491ee23305ac3eb59e492b6945881f5cd77a6f731061a3f25b77fd40f9e99a4", size = 395576, upload-time = "2026-06-30T07:15:25.987Z" }, + { url = "https://files.pythonhosted.org/packages/68/a8/6ed52f03ee6cb854ce78785cc9a9a672eb880e83fd7224d471f667d151f1/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2c99f7e8ccb3dd6e3e4bfeac657a7b208c9bac8075f4b078c02d7404c34107fa", size = 543807, upload-time = "2026-06-30T07:15:27.356Z" }, + { url = "https://files.pythonhosted.org/packages/8f/d6/156c0d3eea27ba09b92562ba2364ba124c0a061b199e17eac637cd25a5e2/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62698275682bf121181861295c9181e789030a2d516071f5b8f3c23c170cd0fc", size = 611187, upload-time = "2026-06-30T07:15:28.931Z" }, + { url = "https://files.pythonhosted.org/packages/f1/31/774212ed989c62f7f310220089f9b0a3fb8f40f5443d1727abd5d9f52bc9/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a214c993455f99a89aaeadc9b21241900037adc9d97203e374d75513c5911822", size = 573030, upload-time = "2026-06-30T07:15:30.553Z" }, + { url = "https://files.pythonhosted.org/packages/c9/50/22f73127a41f1ce4f87fe39aadfb9a126345801c274aa93ae88456249327/rpds_py-2026.6.3-cp312-cp312-win32.whl", hash = "sha256:501f9f04a588d6a09179368c57071301445191767c64e4b52a6aa9871f1ef5ed", size = 202185, upload-time = "2026-06-30T07:15:32.027Z" }, + { url = "https://files.pythonhosted.org/packages/04/3a/f0ee4d4dde9d3b69dedf1b5f74e7a40017046d55052d173e418c6a94f960/rpds_py-2026.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:2c958bf94822e9290a40aaf2a822d4bc5c88099093e3948ad6c571eca9272e5f", size = 220394, upload-time = "2026-06-30T07:15:33.359Z" }, + { url = "https://files.pythonhosted.org/packages/f3/83/3382fe37f809b59f02aac04dbc4e765b480b46ee0227ed516e3bdc4d3dfc/rpds_py-2026.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:22bffe6042b9bcb0822bcd1955ec00e245daf17b4344e4ed8e9551b976b63e96", size = 215753, upload-time = "2026-06-30T07:15:34.778Z" }, + { url = "https://files.pythonhosted.org/packages/a4/9e/b818ee580026ec578138e961027a68820c40afeb1ec8f6819b54fb99e196/rpds_py-2026.6.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3cfe765c1da0072636ca06628261e0ea05688e160d5c8a03e0217c3854037223", size = 343012, upload-time = "2026-06-30T07:15:36.005Z" }, + { url = "https://files.pythonhosted.org/packages/f3/6b/686d9dc4359a8f163cfbbf89ee0b4e586431de22fe8248edb63a8cf50d49/rpds_py-2026.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f4d78253f6996be4901669ad25319f842f740eccf4d58e3c7f3dd39e6dde1d8f", size = 338203, upload-time = "2026-06-30T07:15:37.462Z" }, + { url = "https://files.pythonhosted.org/packages/9e/9b/069aa329940f8207615e091f5eedbbd40e1e15eac68a0790fd05ccdf796c/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54f45a148e28767bf343d33a684693c70e451c6f4c0e9904709a723fafbdfc1f", size = 367984, upload-time = "2026-06-30T07:15:39.008Z" }, + { url = "https://files.pythonhosted.org/packages/14/db/34c203e4becff3703e4d3bc121842c00b8689197f398161203a880052f4e/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:842e7b070435622248c7a2c44ae53fa1440e073cc3023bc919fed570884097a7", size = 374815, upload-time = "2026-06-30T07:15:40.253Z" }, + { url = "https://files.pythonhosted.org/packages/ee/7d/8071067d2cc453d916ad836e828c943f575e8a44612537759002a1e07381/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8020133a74bd81b4572dd8e4be028a6b1ebcd70e6726edc3918008c08bee6ee6", size = 490545, upload-time = "2026-06-30T07:15:41.729Z" }, + { url = "https://files.pythonhosted.org/packages/a3/42/da06c5aa8f0484ff07f270787434204d9f4535e2f8c3b51ed402267e63c3/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdc7e35386f3847df728fbcb5e887e2d79c19e2fa1eba9e51b6621d23e3243af", size = 382828, upload-time = "2026-06-30T07:15:43.327Z" }, + { url = "https://files.pythonhosted.org/packages/57/d7/fe978efc2ae50abe48eb7464668ea99f53c010c60aeebb7b35ad27f23661/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acac386b453c2516111b50985d60ce46e7fadb5ea71ae7b25f4c946935bf27cf", size = 365678, upload-time = "2026-06-30T07:15:44.992Z" }, + { url = "https://files.pythonhosted.org/packages/69/9d/1d8922e1990b2a6eb532b6ff53d3e73d2b3bbffc84116c75826bee73dfc6/rpds_py-2026.6.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:425560c6fa0415f27261727bb20bd097568485e5eb0c121f1949417d1c516885", size = 377811, upload-time = "2026-06-30T07:15:46.523Z" }, + { url = "https://files.pythonhosted.org/packages/b1/3d/198dceafb4fb034a6a47347e1b0735d34e0bd4a50be4e898d408ee66cb14/rpds_py-2026.6.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a550fb4950a06dde3beb4721f5ad4b25bf4513784665b0a8522c792e2bd822a4", size = 395382, upload-time = "2026-06-30T07:15:47.955Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f1/13968e49655d40b6b19d8b9140296bbc6f1d86b3f0f6c346cf9f1adddf4b/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f4bca01b63096f606e095734dd56e74e175f94cfbf24ff3d63281cec61f7bb7", size = 543832, upload-time = "2026-06-30T07:15:49.33Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ab/289bcb1b90bd3e40a2900c561fa0e2087345ecbb094f0b870f2345142b7c/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ccffae9a092a00deb7efd545fe5e2c33c33b88e7c054337e9a74c179347d0b7d", size = 611011, upload-time = "2026-06-30T07:15:50.847Z" }, + { url = "https://files.pythonhosted.org/packages/1e/16/5043105e679436ccfbc8e5e0dd2d663ed18a8b8113515fd06a5e5d77c83e/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1cf01971c4f2c5553b772a542e4aaf191789cd331bc2cd4ff0e6e65ba49e1e97", size = 572431, upload-time = "2026-06-30T07:15:52.394Z" }, + { url = "https://files.pythonhosted.org/packages/85/ed/adab103321c0a6565d5ae1c2998349bc3ee175b82ccc5ae8fc04cc413075/rpds_py-2026.6.3-cp313-cp313-win32.whl", hash = "sha256:8c3d1e9c15b9d51ca0391e13da1a25a0a4df3c58a37c9dc368e0736cf7f69df0", size = 201710, upload-time = "2026-06-30T07:15:53.894Z" }, + { url = "https://files.pythonhosted.org/packages/7b/ed/a03b09668e74e5dabbf2e211f6468e1820c0552f7b0500082da31841bf7b/rpds_py-2026.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:9250a9a0a6fd4648b3f868da8d91a4c52b5811a62df58e753d50ae4454a36f80", size = 219454, upload-time = "2026-06-30T07:15:55.25Z" }, + { url = "https://files.pythonhosted.org/packages/27/17/b8642c12930b71bc2b25831f6708ccf0f75abcd11883932ec9ce54ba3a78/rpds_py-2026.6.3-cp313-cp313-win_arm64.whl", hash = "sha256:900a67df3fd1660b035a4761c4ce73c382ea6b35f90f9863c36c6fd8bf8b09bb", size = 215063, upload-time = "2026-06-30T07:15:56.573Z" }, + { url = "https://files.pythonhosted.org/packages/b6/36/7fbe9dcdaf857fb3f63c2a2284b62492d95f5e8334e947e5fb6e7f68c9be/rpds_py-2026.6.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:931908d9fc855d8f74783377822be318edb6dcb19e47169dc038f9a1bf60b06e", size = 344510, upload-time = "2026-06-30T07:15:57.921Z" }, + { url = "https://files.pythonhosted.org/packages/ba/54/f785cc3d3f60839ca57a5af4927a9f347b07b2799c373fc20f7949f87c7e/rpds_py-2026.6.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d7469697dce35be237db177d42e2a2ee26e6dcc5fc052078a6fefabd288c6edd", size = 339495, upload-time = "2026-06-30T07:15:59.238Z" }, + { url = "https://files.pythonhosted.org/packages/63/ef/d4cdaf309e6b095b43597103cf8c0b951d6cca2acce68c474f75ec12e0c7/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcfbcf66006befb9fd2aeaa9e01feaf881b4dc330a02ba07d2322b1c11be7b5d", size = 369454, upload-time = "2026-06-30T07:16:01.021Z" }, + { url = "https://files.pythonhosted.org/packages/96/4a/9559a68b7ee15db09d7981212e8c2e219d2a1d6d4faa0391d813c3496a36/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:847927daf4cffbd4e90e42bc890069897101edd015f956cb8721b3473372edda", size = 374583, upload-time = "2026-06-30T07:16:02.287Z" }, + { url = "https://files.pythonhosted.org/packages/ef/75/8964aa7d2c6e8ac43eba8eb6e6b0fdda1f46d39f2fc3e6aa9f2cb17f485d/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aca6c1ef08a82bfe327cc156da694660f599923e2e6665b6d81c9c2d0ac9ffc8", size = 492919, upload-time = "2026-06-30T07:16:03.723Z" }, + { url = "https://files.pythonhosted.org/packages/8f/97/6908094ac804115e65aedfd90f1b5fee4eebebd3f6c4cfc5419939267565/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae50181a047c871561212bb97f7932a2d45fb53e947bd9b57ebad85b529cbc53", size = 383725, upload-time = "2026-06-30T07:16:05.305Z" }, + { url = "https://files.pythonhosted.org/packages/d1/9c/0d1fdc2e7aba23e290d603bc494e97bd205bae262ce33c6b32a69768ed5e/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc319e5a1de4b6913aac94bf6a2f9e847371e0a140a43dd4991db1a09bc2d504", size = 367255, upload-time = "2026-06-30T07:16:07.086Z" }, + { url = "https://files.pythonhosted.org/packages/c4/fe/f0209ca4a9ed074bc8acb44dfd0e81c3122e94c9689f5645b7973a866719/rpds_py-2026.6.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:e4316bf32babbed84e691e352faf967ce2f0f024174a8643c37c94a1080374fc", size = 379060, upload-time = "2026-06-30T07:16:08.525Z" }, + { url = "https://files.pythonhosted.org/packages/c6/8d/f1cc54c616b9d8897de8738aac148d20afca93f68187475fe194d09a71b9/rpds_py-2026.6.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8c6e5a2f750cc71c3e3b11d71661f21d6f9bc6cebc6564b1466417a1ec03ec77", size = 395960, upload-time = "2026-06-30T07:16:09.989Z" }, + { url = "https://files.pythonhosted.org/packages/fb/04/aafff00f73aeca2945f734f1d483c64ab8f472d0864ab02377fd8e89c3b2/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4470ce197d4090875cf6affbf1f853338387428df97c4fb7b7106317b8214698", size = 545356, upload-time = "2026-06-30T07:16:11.816Z" }, + { url = "https://files.pythonhosted.org/packages/fd/cc/e229663b9e4ddac5a4acbe9085dd80a71af2a5d356b8b39d6bff233f24b0/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ea964164cc9afa72d4d9b23cc28dafae93693c0a53e0b42acbff15b22c3f9ddd", size = 612319, upload-time = "2026-06-30T07:16:13.586Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7a/8a0e6d3e6cd066af108b71b43122c3fe158dd9eb86acac626593a2582eb1/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:639c8929aa0afe81be836b04de888460d6bed38b9c54cfc18da8f6bfabf5af5d", size = 573508, upload-time = "2026-06-30T07:16:15.23Z" }, + { url = "https://files.pythonhosted.org/packages/87/03/2a69ab618a789cf6cf85c86bb844c62d090e700ab1a2aa676b3741b6c516/rpds_py-2026.6.3-cp314-cp314-win32.whl", hash = "sha256:882076c00c0a608b131187055ddc5ae29f2e7eaf870d6168980420d58528a5c8", size = 202504, upload-time = "2026-06-30T07:16:16.893Z" }, + { url = "https://files.pythonhosted.org/packages/85/62/a3892ba945f4e24c78f352e5de3c7620d8479f73f211406a97263d13c7d2/rpds_py-2026.6.3-cp314-cp314-win_amd64.whl", hash = "sha256:0be972be84cfcaf46c8c6edf690ca0f154ac17babf1f6a955a51579b34ad2dc5", size = 220380, upload-time = "2026-06-30T07:16:18.108Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e7/c2bd44dc831931815ad11ebb5f430b5a0a4d3caa9de837107876c30c3432/rpds_py-2026.6.3-cp314-cp314-win_arm64.whl", hash = "sha256:2a9c6f195058cb45335e8cc3802745c603d716eb96bc9625950c1aac71c0c703", size = 215976, upload-time = "2026-06-30T07:16:19.654Z" }, + { url = "https://files.pythonhosted.org/packages/79/9c/fff7b74bce9a091ec9a012a03f9ff5f69364eaf9451060dfc4486da2ffdd/rpds_py-2026.6.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:f90938e92afda60266da758ee7d363447f7f0138c9559f9e1811629580582d90", size = 346840, upload-time = "2026-06-30T07:16:21.268Z" }, + { url = "https://files.pythonhosted.org/packages/e9/44/77bcb1168b33704908295533d27f10eb811e9e3e193e8993dc99572211d3/rpds_py-2026.6.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ec829541c45bca16e61c7ae50c20501f213605beb75d1aba91a6ee37fbbb56a4", size = 340282, upload-time = "2026-06-30T07:16:22.875Z" }, + { url = "https://files.pythonhosted.org/packages/87/3c/7a9081c7c9e645b39efe19e4ffbeccd80add246327cd9b888aecffd72317/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afd70d95892096cdb26f15a00c45907b17817577aa8d1c76b2dcc2788391f9e9", size = 370403, upload-time = "2026-06-30T07:16:24.415Z" }, + { url = "https://files.pythonhosted.org/packages/f7/69/af47021eb7dad6ff3396cb001c08f0f3c4d06c20253f75be6421a59fe6b7/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29dfa0533a5d4c94d4dfa1b694fcb56c9c63aad8330ffdd816fd225d0a7a162f", size = 376055, upload-time = "2026-06-30T07:16:26.111Z" }, + { url = "https://files.pythonhosted.org/packages/81/fc/a3bcf517084396a6dd258c592567a3c011ba4557f2fde23dceaf26e74f2e/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:af05d726809bff6b141be124d4c7ce998f9c9c7f30edb1f46c07aa103d540b41", size = 494419, upload-time = "2026-06-30T07:16:27.596Z" }, + { url = "https://files.pythonhosted.org/packages/c9/eb/13d529d1788135425c7bf207f8463458ca5d92e43f3f701365b83e9dffc1/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9826217f048f620d9a712672818bf231442c1b35d96b227a07eabd11b4bb6945", size = 384848, upload-time = "2026-06-30T07:16:29.183Z" }, + { url = "https://files.pythonhosted.org/packages/8e/f4/b7ac49f30013aba8f7b9566b1dd07e81de95e708c1374b7bacc5b9bc5c9c/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:536bceea4fa4acf7e1c61da2b5786304367c816c8895be71b8f537c480b0ea1f", size = 371369, upload-time = "2026-06-30T07:16:30.912Z" }, + { url = "https://files.pythonhosted.org/packages/31/86/6260bafa622f788b07ddec0e52d810305c8b9b0b8c27f58a2ab04bf62b4f/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:bc0011654b91cc4fb2ae701bec0a0ba1e552c0714247fa7af6c59e0ccfa3a4e1", size = 379673, upload-time = "2026-06-30T07:16:32.486Z" }, + { url = "https://files.pythonhosted.org/packages/19/c3/03f1ee79a047b48daeca157c89a18509cde22b6b951d642b9b0af1be660a/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:539d75de9e0d536c84ff18dfeb805398e58227001ce09231a26a08b9aed1ee0e", size = 397500, upload-time = "2026-06-30T07:16:34.471Z" }, + { url = "https://files.pythonhosted.org/packages/f0/95/8ed0cd8c377dca12aea498f119fe639fc474d1461545c39d2b5872eb1c0f/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:166cf54d9f44fc6ceb53c7860258dde44a81406646de79f8ed3234fca3b6e538", size = 545978, upload-time = "2026-06-30T07:16:36.45Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f2/0eb57f0eaa83f8fc152a7e03de968ab77e1f00732bebc892b190c6eebde7/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:d34c20167764fbcf927194d532dd7e0c56772f0a5f943fa5ef9e9afbba8fb9db", size = 613350, upload-time = "2026-06-30T07:16:38.213Z" }, + { url = "https://files.pythonhosted.org/packages/5b/de/e0674bdbc3ef7634989b3f854c3f34bc1f587d36e5bfdc5c378d57034619/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ea7bb13b7c9a29791f87a0387ba7d3ad3a6d783d827e4d3f27b40a0ff44495e2", size = 576486, upload-time = "2026-06-30T07:16:39.797Z" }, + { url = "https://files.pythonhosted.org/packages/f2/f6/21101359743cd136ada781e8210a85769578422ba460672eea0e29739200/rpds_py-2026.6.3-cp314-cp314t-win32.whl", hash = "sha256:6de4744d05bd1aa1be4ed7ea1189e3979196808008113bbbf899a460966b925e", size = 201068, upload-time = "2026-06-30T07:16:41.316Z" }, + { url = "https://files.pythonhosted.org/packages/a6/b2/9574d4d44f7760c2aa32d92a0a4f41698e33f5b204a0bf5c9758f52c79d5/rpds_py-2026.6.3-cp314-cp314t-win_amd64.whl", hash = "sha256:c7b9a2f8f4d8e90af72571d3d495deebdd7e3c75451f5b41719aee166e940fc2", size = 220600, upload-time = "2026-06-30T07:16:43.091Z" }, + { url = "https://files.pythonhosted.org/packages/08/ae/f23a2697e6ee6340a578b0f136be6483657bef0c6f9497b752bb5c0964bb/rpds_py-2026.6.3-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:e059c5dde6452b44424bd1834557556c226b57781dee1227af23518459722b13", size = 344726, upload-time = "2026-06-30T07:16:44.5Z" }, + { url = "https://files.pythonhosted.org/packages/c3/63/e7b3a1a5358dd32c930a1062d8e15b67fd6e8922e81df9e91706d66ee5c8/rpds_py-2026.6.3-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2f7c26fbc5acd2522b95d4177fe4710ffd8e9b20529e703ffbf8db4d93903f05", size = 339587, upload-time = "2026-06-30T07:16:46.255Z" }, + { url = "https://files.pythonhosted.org/packages/ec/64/10a85681916ca55fffb91b0a211f84e34297c109243484dd6394660a8a7c/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3086b538543802f84c843911242db20447de00d8752dd0efc936dbcf02218ba", size = 369585, upload-time = "2026-06-30T07:16:48.101Z" }, + { url = "https://files.pythonhosted.org/packages/76/c2/baf95c7c38823e12ba34407c5f5767a89e5cf2233895e56f608167ae9493/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8f2e5c5ee828d42cb11760761c0af6507927bec42d0ad5458f97c9203b054617", size = 375479, upload-time = "2026-06-30T07:16:49.93Z" }, + { url = "https://files.pythonhosted.org/packages/6a/94/0aad06c72d65101e11d33528d438cda99a39ce0da99466e156158f2541d3/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed0c1e5d10cdc7135537988c74a0188da68e2f3c30813ba3744ab1e42e0480f9", size = 492418, upload-time = "2026-06-30T07:16:51.641Z" }, + { url = "https://files.pythonhosted.org/packages/b5/17/de3f5a479a1f056535d7489819639d8cd591ea6281d700390b43b1abd745/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c2642a7603ec0b16ed77da4555db3b4b472341904873788327c0b0d7b95f1bb", size = 384123, upload-time = "2026-06-30T07:16:53.622Z" }, + { url = "https://files.pythonhosted.org/packages/46/7d/bf09bd1b145bb2671c03e1e6d1ab8651858d90d8c7dfeadd85a37a934fd8/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e4320744c1ffdd95a603def63344bfab2d33edeab301c5007e7de9f9f5b3885", size = 367351, upload-time = "2026-06-30T07:16:55.241Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ea/1bb734f314b8be319149ddee80b18bd41372bdcfbdf88d28131c0cd37719/rpds_py-2026.6.3-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:a9f4645593036b81bbdb36b9c8e0ea0d1c3fee968c4d59db0344c14087ef143a", size = 378827, upload-time = "2026-06-30T07:16:56.841Z" }, + { url = "https://files.pythonhosted.org/packages/4b/93/d9611e5b25e26df9a3649813ed66193ace9347a7c7fc4ab7cf70e94851c0/rpds_py-2026.6.3-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e55d236be29255554da47abe5c577637db7c24a02b8b46f0ca9524c855801868", size = 395966, upload-time = "2026-06-30T07:16:58.557Z" }, + { url = "https://files.pythonhosted.org/packages/c3/cb/99d77e16e5534ae1d90629bbe419ba6ee170833a6a85e3aa1cc41726fbbc/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:24e9c5386e16669b674a69c156c8eeefcb578f3b3397b713b08e6d60f3c7b187", size = 545680, upload-time = "2026-06-30T07:17:00.164Z" }, + { url = "https://files.pythonhosted.org/packages/59/15/11a29755f790cef7a2f755e8e14f4f0c33f39489e1893a632a2eee59672b/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:c60924535c75f1566b6eb75b5c31a48a43fef04fa2d0d201acbad8a9969c6107", size = 611853, upload-time = "2026-06-30T07:17:01.962Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/0c27547e21644da938fb530f7e1a8148dd24d02db07e7a5f2567a17ce710/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:38a2fea2787428f811719ceb9114cb78964a3138838320c29ac39526c79c16ba", size = 573715, upload-time = "2026-06-30T07:17:03.693Z" }, + { url = "https://files.pythonhosted.org/packages/29/71/4d8fcf700931815594bce892255bbd973b94efaf0fc1932b0590df18d886/rpds_py-2026.6.3-cp315-cp315-win32.whl", hash = "sha256:d483fe17f01ad64b7bf7cc38fcefff1ca9fb83f8c2b2542b68f97ffe0611b369", size = 202864, upload-time = "2026-06-30T07:17:05.746Z" }, + { url = "https://files.pythonhosted.org/packages/eb/62/b577562de0edbb55b2be85ce5fd09c33e386b9b13eee09833af4240fd5c4/rpds_py-2026.6.3-cp315-cp315-win_amd64.whl", hash = "sha256:67e3a721ffc5d8d2210d3671872298c4a84e4b8035cfe42ffd7cde35d772b146", size = 220430, upload-time = "2026-06-30T07:17:07.471Z" }, + { url = "https://files.pythonhosted.org/packages/c8/95/d6d0b2509825141eef60669a5739eec88dbc6a48053d6c92993a5704defe/rpds_py-2026.6.3-cp315-cp315-win_arm64.whl", hash = "sha256:6e84adbcf4bf841aed8116a8264b9f50b4cb3e7bd89b516122e616ac56ca269e", size = 215877, upload-time = "2026-06-30T07:17:09.008Z" }, + { url = "https://files.pythonhosted.org/packages/b7/bf/f3ea278f0afd615c1d0f19cb69043a41526e2bb600c2b536eb192218eb27/rpds_py-2026.6.3-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:ae6dd8f10bd17aad820876d24caec9efdafd80a318d16c0a48edb5e136902c6b", size = 346933, upload-time = "2026-06-30T07:17:10.762Z" }, + { url = "https://files.pythonhosted.org/packages/9d/29/9907bdf1c5346763cf10b7f6852aad86652168c259def904cbe0082c5864/rpds_py-2026.6.3-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:bdbd97738551fca3917c1bd7188bec1920bb520104f28e7e1007f9ceb17b7690", size = 340274, upload-time = "2026-06-30T07:17:12.266Z" }, + { url = "https://files.pythonhosted.org/packages/6f/2c/8e03767b5778ef25cebf74a7a91a2c3806f8eced4c92cb7406bbe060756d/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b95977e7211527ab0ba576e286d023389fbeeb32a6b7b771665d333c60e5342", size = 370763, upload-time = "2026-06-30T07:17:14.107Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e1/df2a7e1ba2efd796af26194250b8d42c821b46592311595162af9ef0528d/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d15fde0e6fb0d88a60d221204873743e5d9f0b7d29165e62cd86d0413ad74ba6", size = 376467, upload-time = "2026-06-30T07:17:15.76Z" }, + { url = "https://files.pythonhosted.org/packages/6b/de/8a0814d1946af29cb068fb259aa8622f856df1d0bab58429448726b537f5/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a136d453475ac0fcbda502ef1e6504bd28d6d904700915d278deeab0d00fe140", size = 496689, upload-time = "2026-06-30T07:17:17.308Z" }, + { url = "https://files.pythonhosted.org/packages/df/f3/f19e0c852ba13694f5a79f3b719331051573cb5693feacf8a88ffffc3a71/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f826877d462181e5eb1c26a0026b8d0cab05d99844ecb6d8bf3627a2ca0c0442", size = 385340, upload-time = "2026-06-30T07:17:18.928Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ae/7ec3a9d2d4351f99e37bcb06b6b6f954512646bfdbf9742e1de727865daf/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79486287de1730dbaff3dbd124d0ca4d2ef7f9d29bf2544f1f93c09b5bcbbd12", size = 372179, upload-time = "2026-06-30T07:17:20.539Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ac/9cee911dff2aaa9a5a8354f6610bf2e6a616de9197c5fff4f54f82585f1e/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:808345f53cb952433ca2816f1604ff3515608a81784954f38d4452acfe8e61d5", size = 379993, upload-time = "2026-06-30T07:17:22.212Z" }, + { url = "https://files.pythonhosted.org/packages/83/6b/7c2a07ba88d1e9a936612f7a5d067467ed03d971d5a06f7d309dff044a7e/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1967debc37f64f2c4dc90a7f563aec558b471966e12adcac4e1c4240496b6ebf", size = 398909, upload-time = "2026-06-30T07:17:23.66Z" }, + { url = "https://files.pythonhosted.org/packages/97/0b/776ffcb66783637b0031f6d58d6fb55913c8b5abf00aeecd46bf933fb477/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:f0840b5b17057f7fd918b76183a4b5a0635f43e14eb2ce60dce1d4ee4707ea00", size = 546584, upload-time = "2026-06-30T07:17:25.264Z" }, + { url = "https://files.pythonhosted.org/packages/55/33/ba3bc04d7092bd553c9b2b195624992d2cc4f3de1f380b7b93cbee67bd79/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:faa679d19a6696fd54259ad321251ad77a13e70e03dd834daa762a44fb6196ef", size = 614357, upload-time = "2026-06-30T07:17:26.888Z" }, + { url = "https://files.pythonhosted.org/packages/8b/71/14edf065f04630b1a8472f7653cad03f6c478bcf95ea0e6aed55451e33ea/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:23a439f31ccbeff1574e24889128821d1f7917470e830cf6544dced1c662262a", size = 576533, upload-time = "2026-06-30T07:17:28.546Z" }, + { url = "https://files.pythonhosted.org/packages/ba/76/65002b08596c389105720a8c0d22298b8dc25a4baf89b2ce431343c8b1de/rpds_py-2026.6.3-cp315-cp315t-win32.whl", hash = "sha256:913ca42ccad3f8cc6e292b587ae8ae49c8c823e5dce51a736252fc7c7cdfa577", size = 201204, upload-time = "2026-06-30T07:17:30.193Z" }, + { url = "https://files.pythonhosted.org/packages/8c/97/d855d6b3c322d1f27e26f5241c42016b56cf01377ea8ed348285f54652f0/rpds_py-2026.6.3-cp315-cp315t-win_amd64.whl", hash = "sha256:ae3d4fe8c0b9213624fdce7279d70e3b148b682ca20719ebd193a23ebfa47324", size = 220719, upload-time = "2026-06-30T07:17:31.788Z" }, + { url = "https://files.pythonhosted.org/packages/b4/9c/f0d19ac587fd0e4ab6b72cda355e9c5a6166b01ef7e064e437aef8eb9fef/rpds_py-2026.6.3-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:4cf2d36a2357e4d07bb5a4f98801265327b48256867816cfd2ceb001e9754a8f", size = 349791, upload-time = "2026-06-30T07:17:33.315Z" }, + { url = "https://files.pythonhosted.org/packages/38/c7/1d49d204c9fd2ee6c537601dc4c1ba921e03363ca576bfab94a00254ac9a/rpds_py-2026.6.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:30c6dc199b24a5e3e81d50da0f00858c5bbdb2617a750395687f4339c5818171", size = 352842, upload-time = "2026-06-30T07:17:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e5/c0b5dc93cd0d4c06ce1f438907649514e2ea077bcd911e3154a51e96c38e/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9891e594296ab9dada6551c8e7b387b2721f27a67eecd528412e8906247a7b90", size = 382094, upload-time = "2026-06-30T07:17:36.514Z" }, + { url = "https://files.pythonhosted.org/packages/0d/54/ec0e907b4ca8d541112db352409bd15f871c9b243e0c92c9b5a46ae96f01/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5c2dc92304aa48a4a60443b548bb12f12e119d4b72f314015e67b9e1be97fca", size = 388662, upload-time = "2026-06-30T07:17:38.235Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f4/921c22a4fd0f1c1ac13a3996ffbf0aa67951e2c8ad0d1d9574938a2932e8/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:127e08c0642d880cf32ca47ec2a4a77b901f7e2dd1ad9762adb13955d72ffcc9", size = 504896, upload-time = "2026-06-30T07:17:39.689Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1b/a114b972cefa1ab1cdb3c7bb177cd3844a12826c507c722d3a73516dbbaf/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8bb68f03f395eb793220b45c097bd4d8c32944393da0fad8b999efac0868fc8c", size = 391545, upload-time = "2026-06-30T07:17:41.336Z" }, + { url = "https://files.pythonhosted.org/packages/4e/98/af9b3db77d47fcbe6c8c1f36e2c2147ec70292819e99c325f871584a1c11/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3450b693fde92133e9f51060568a4c31fcca76d5e53bbd611e689ca446517e9", size = 380059, upload-time = "2026-06-30T07:17:42.857Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ba/0efd8668b97c1d26a61566386c636a7a7a09829e474fdf807caa15a2c844/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:5e8d07bddee435a2ff6f1920e18feff28d0bc4533e42f4bf6927fbd073312c41", size = 393235, upload-time = "2026-06-30T07:17:44.637Z" }, + { url = "https://files.pythonhosted.org/packages/62/90/8c139ee9690f73b0829f32647de6f40d826f8f443af6fa72644f96351aac/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3a83ae6c67b7676b9878378547ca8e93ed77a580037bcbcd1d32f739e1e6089c", size = 413008, upload-time = "2026-06-30T07:17:46.225Z" }, + { url = "https://files.pythonhosted.org/packages/9c/97/0043896fdd7828ce09a1d9a8b06433714d0960fc4ff3fc4aa72b666b764e/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2bfd04c19ddbd6640de0b51894d764bd2758854d5b75bd102d2ef10cb9c293a9", size = 558118, upload-time = "2026-06-30T07:17:47.759Z" }, + { url = "https://files.pythonhosted.org/packages/f6/40/02355f0e134f783a8f9814c4680a1bd311d37671577a5964ea838573ff37/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:ca6546b66be9dc4738b1b043d5ebd5488c66c578c5ff0fd0e8065313fe3afb76", size = 623138, upload-time = "2026-06-30T07:17:49.355Z" }, + { url = "https://files.pythonhosted.org/packages/10/85/48f0abdcef5cce4e034c7a5b0ceeceba0b01bf0d942824f4bb720afe2dec/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:8e65860d238379ed982fd9ba690579b5e95af2f4840f99c772816dbe573cb826", size = 586486, upload-time = "2026-06-30T07:17:51.141Z" }, ] [[package]] @@ -3288,7 +3272,7 @@ dependencies = [ { name = "lazy-loader" }, { name = "networkx" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "packaging" }, { name = "pillow" }, { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, @@ -3431,7 +3415,7 @@ resolution-markers = [ "python_full_version >= '3.12' and python_full_version < '3.14'", ] dependencies = [ - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" } }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/a7/25/c2700dfaf6442b4effaa91af24ebce5dc9d31bb4a69706313aae70d72cd0/scipy-1.18.0.tar.gz", hash = "sha256:67b2ad2ad54c72ca6d04975a9b2df8c3638c34ddd5b28738e94fc2b57929d378", size = 30774447, upload-time = "2026-06-19T15:01:43.456Z" } wheels = [ @@ -3589,14 +3573,14 @@ wheels = [ [[package]] name = "tensorboard" -version = "2.20.0" +version = "2.21.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "absl-py" }, { name = "grpcio" }, { name = "markdown" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "packaging" }, { name = "pillow" }, { name = "protobuf" }, @@ -3605,7 +3589,7 @@ dependencies = [ { name = "werkzeug" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/d9/a5db55f88f258ac669a92858b70a714bbbd5acd993820b41ec4a96a4d77f/tensorboard-2.20.0-py3-none-any.whl", hash = "sha256:9dc9f978cb84c0723acf9a345d96c184f0293d18f166bb8d59ee098e6cfaaba6", size = 5525680, upload-time = "2025-07-17T19:20:49.638Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/cd2eec9642781a8f5b2fb9994e3933a7b259ab18e9d49aeede9b5acf6311/tensorboard-2.21.0-py3-none-any.whl", hash = "sha256:7279316dcb6bd5bc391d623dea841531299cde1887310e8133bc34a996d32255", size = 5516204, upload-time = "2026-06-29T20:48:04.472Z" }, ] [[package]] @@ -3656,7 +3640,7 @@ resolution-markers = [ "python_full_version >= '3.12' and python_full_version < '3.14'", ] dependencies = [ - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" } }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/b7/38/5e2ecef5af2f4fd4a89bb8d6240de9458bab4d51a4cbd97aeb3a0cd618e2/tifffile-2026.6.1.tar.gz", hash = "sha256:626c892c0e899d959b9438e7c0e1491dc154a7fead1f1f37a991724a50eceba9", size = 429694, upload-time = "2026-05-31T23:57:12.165Z" } wheels = [ @@ -3798,7 +3782,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "lightning-utilities" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "packaging" }, { name = "torch" }, ] @@ -3813,7 +3797,7 @@ version = "0.27.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "pillow" }, { name = "torch" }, ] @@ -3897,11 +3881,11 @@ wheels = [ [[package]] name = "typing-extensions" -version = "4.15.0" +version = "4.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, ] [[package]] @@ -3948,11 +3932,11 @@ wheels = [ [[package]] name = "wcwidth" -version = "0.8.1" +version = "0.8.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/49/b4/51fe890511f0f242d07cb1ebe6a5b6db417262b9d2568b460347c57d95cc/wcwidth-0.8.1.tar.gz", hash = "sha256:faf5b4a5366a72dc49cad48cdf21f52bdf63bdda995178e483ba247ff79089b9", size = 1466072, upload-time = "2026-06-08T05:57:23.146Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/74/c6428f875774288bec1396f5bfcbc2d925700a4dad61727fd5f2b12f249d/wcwidth-0.8.2.tar.gz", hash = "sha256:91fbef97204b96a3d4d421609b80340b760cf33e26da123ff243d76b1fda8dda", size = 1466253, upload-time = "2026-06-29T18:11:11.601Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/6e/95b0e537de1f4d4301f76f944642c6da50d1511cc7b3d64dc418a66c7509/wcwidth-0.8.1-py3-none-any.whl", hash = "sha256:f453740b1e4a4f3291faa37944c555d71056c4da08d59809b307ef4feba695c8", size = 323092, upload-time = "2026-06-08T05:57:21.413Z" }, + { url = "https://files.pythonhosted.org/packages/96/42/3e5985a0a7e57de470b320c6d6a1a67c844f6737a587f3d44dd13d1819e7/wcwidth-0.8.2-py3-none-any.whl", hash = "sha256:d63947694a0539a1d51e01eda7caf800c291020e6cdd7e28ad7b14dd33ad4f85", size = 323166, upload-time = "2026-06-29T18:11:09.888Z" }, ] [[package]] @@ -4035,7 +4019,7 @@ dependencies = [ { name = "donfig" }, { name = "google-crc32c" }, { name = "numcodecs" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" } }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" } }, { name = "packaging" }, { name = "typing-extensions" }, ] From 39cc13c2a912de67fd28c930937f0dc1cd441515 Mon Sep 17 00:00:00 2001 From: cedriclim1 Date: Thu, 9 Jul 2026 09:44:19 -0700 Subject: [PATCH 334/335] Remove torch.compile from INR ray helpers for Windows compatibility create_batch_rays and integrate_rays were decorated with @torch.compile(mode="reduce-overhead"). Calling them triggers TorchInductor, which needs a C compiler to build its kernel. On Windows runners without MSVC (cl.exe) on PATH this raises InductorError, failing tests/tomography/test_dataset_models.py::TestINRRayMath on windows-latest. Both are trivial tensor ops (a small fill and a single reduction) and reduce-overhead is a no-op on CPU, so eager execution is equivalent and portable across platforms. --- src/quantem/tomography/dataset_models.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/quantem/tomography/dataset_models.py b/src/quantem/tomography/dataset_models.py index 6ede23be..a1a51bad 100644 --- a/src/quantem/tomography/dataset_models.py +++ b/src/quantem/tomography/dataset_models.py @@ -536,7 +536,6 @@ def get_coords( return all_coords @staticmethod - @torch.compile(mode="reduce-overhead") def create_batch_rays( pixel_i: torch.Tensor, pixel_j: torch.Tensor, N: int, num_samples_per_ray: int ) -> torch.Tensor: @@ -599,7 +598,6 @@ def transform_batch_rays( return transformed_rays @staticmethod - @torch.compile(mode="reduce-overhead") def integrate_rays( rays: torch.Tensor, num_samples_per_ray: int, target_values_len: int ) -> torch.Tensor: From 17bceb76040fd3a71c266f5b2106ec68c52c42d9 Mon Sep 17 00:00:00 2001 From: quantem-bot Date: Thu, 9 Jul 2026 17:00:45 +0000 Subject: [PATCH 335/335] Bump version to 0.1.9 --- pyproject.toml | 4 ++-- uv.lock | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 19882bf9..4df9fc22 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ members = ["widget"] [project] name = "quantem" -version = "0.1.8" +version = "0.1.9" description = "quantitative electron microscopy analysis toolkit." keywords = ["EM","TEM","STEM","4DSTEM"] readme = "README.md" @@ -82,4 +82,4 @@ dev = [ "pre-commit>=4.2.0", "ruff>=0.11.5", "tomli>=2.2.1", -] \ No newline at end of file +] diff --git a/uv.lock b/uv.lock index 888375c2..f1c50c06 100644 --- a/uv.lock +++ b/uv.lock @@ -2890,7 +2890,7 @@ wheels = [ [[package]] name = "quantem" -version = "0.1.8" +version = "0.1.9" source = { editable = "." } dependencies = [ { name = "cmasher" },